diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index 341ed0a41fc..5fecc81a79f 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -54,7 +54,6 @@ import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-con import { TScimServiceFactory } from "@app/ee/services/scim/scim-types"; import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; -import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; import { TSecretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; import { TSecretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service"; import { TSecretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service"; @@ -305,7 +304,6 @@ declare module "fastify" { accessApprovalRequest: TAccessApprovalRequestServiceFactory; secretApprovalPolicy: TSecretApprovalPolicyServiceFactory; secretApprovalRequest: TSecretApprovalRequestServiceFactory; - secretRotation: TSecretRotationServiceFactory; snapshot: TSecretSnapshotServiceFactory; saml: TSamlConfigServiceFactory; scim: TScimServiceFactory; diff --git a/backend/src/@types/knex.d.ts b/backend/src/@types/knex.d.ts index 6acab0f437b..d9459487acc 100644 --- a/backend/src/@types/knex.d.ts +++ b/backend/src/@types/knex.d.ts @@ -500,15 +500,6 @@ import { TSecretReferencesV2, TSecretReferencesV2Insert, TSecretReferencesV2Update, - TSecretRotationOutputs, - TSecretRotationOutputsInsert, - TSecretRotationOutputsUpdate, - TSecretRotationOutputV2, - TSecretRotationOutputV2Insert, - TSecretRotationOutputV2Update, - TSecretRotations, - TSecretRotationsInsert, - TSecretRotationsUpdate, TSecretRotationsV2, TSecretRotationsV2Insert, TSecretRotationsV2Update, @@ -1282,16 +1273,6 @@ declare module "knex/types/tables" { TSecretApprovalPoliciesEnvironmentsInsert, TSecretApprovalPoliciesEnvironmentsUpdate >; - [TableName.SecretRotation]: KnexOriginal.CompositeTableType< - TSecretRotations, - TSecretRotationsInsert, - TSecretRotationsUpdate - >; - [TableName.SecretRotationOutput]: KnexOriginal.CompositeTableType< - TSecretRotationOutputs, - TSecretRotationOutputsInsert, - TSecretRotationOutputsUpdate - >; [TableName.Snapshot]: KnexOriginal.CompositeTableType< TSecretSnapshots, TSecretSnapshotsInsert, @@ -1392,11 +1373,6 @@ declare module "knex/types/tables" { TSecretApprovalRequestSecretTagsV2Insert, TSecretApprovalRequestSecretTagsV2Update >; - [TableName.SecretRotationOutputV2]: KnexOriginal.CompositeTableType< - TSecretRotationOutputV2, - TSecretRotationOutputV2Insert, - TSecretRotationOutputV2Update - >; // KMS service [TableName.KmsServerRootConfig]: KnexOriginal.CompositeTableType< TKmsRootConfig, diff --git a/backend/src/db/migrations/20240102152111_secret-rotation.ts b/backend/src/db/migrations/20240102152111_secret-rotation.ts index ea962cc6ec6..1e3508a728f 100644 --- a/backend/src/db/migrations/20240102152111_secret-rotation.ts +++ b/backend/src/db/migrations/20240102152111_secret-rotation.ts @@ -4,8 +4,8 @@ import { TableName } from "../schemas"; import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; export async function up(knex: Knex): Promise { - if (!(await knex.schema.hasTable(TableName.SecretRotation))) { - await knex.schema.createTable(TableName.SecretRotation, (t) => { + if (!(await knex.schema.hasTable(TableName.DeprecatedSecretRotationV1))) { + await knex.schema.createTable(TableName.DeprecatedSecretRotationV1, (t) => { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.string("provider").notNullable(); t.string("secretPath").notNullable(); @@ -23,22 +23,22 @@ export async function up(knex: Knex): Promise { t.timestamps(true, true, true); }); } - await createOnUpdateTrigger(knex, TableName.SecretRotation); + await createOnUpdateTrigger(knex, TableName.DeprecatedSecretRotationV1); - if (!(await knex.schema.hasTable(TableName.SecretRotationOutput))) { - await knex.schema.createTable(TableName.SecretRotationOutput, (t) => { + if (!(await knex.schema.hasTable(TableName.DeprecatedSecretRotationOutput))) { + await knex.schema.createTable(TableName.DeprecatedSecretRotationOutput, (t) => { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.string("key").notNullable(); t.uuid("secretId").notNullable(); t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("CASCADE"); t.uuid("rotationId").notNullable(); - t.foreign("rotationId").references("id").inTable(TableName.SecretRotation).onDelete("CASCADE"); + t.foreign("rotationId").references("id").inTable(TableName.DeprecatedSecretRotationV1).onDelete("CASCADE"); }); } } export async function down(knex: Knex): Promise { - await knex.schema.dropTableIfExists(TableName.SecretRotationOutput); - await knex.schema.dropTableIfExists(TableName.SecretRotation); - await dropOnUpdateTrigger(knex, TableName.SecretRotation); + await dropOnUpdateTrigger(knex, TableName.DeprecatedSecretRotationV1); + await knex.schema.dropTableIfExists(TableName.DeprecatedSecretRotationOutput); + await knex.schema.dropTableIfExists(TableName.DeprecatedSecretRotationV1); } diff --git a/backend/src/db/migrations/20240730181850_secret-v2.ts b/backend/src/db/migrations/20240730181850_secret-v2.ts index d44c67cf1a5..1d461afde3f 100644 --- a/backend/src/db/migrations/20240730181850_secret-v2.ts +++ b/backend/src/db/migrations/20240730181850_secret-v2.ts @@ -134,14 +134,14 @@ export async function up(knex: Knex): Promise { }); } - if (!(await knex.schema.hasTable(TableName.SecretRotationOutputV2))) { - await knex.schema.createTable(TableName.SecretRotationOutputV2, (t) => { + if (!(await knex.schema.hasTable(TableName.DeprecatedSecretRotationOutputV2))) { + await knex.schema.createTable(TableName.DeprecatedSecretRotationOutputV2, (t) => { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.string("key").notNullable(); t.uuid("secretId").notNullable(); t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE"); t.uuid("rotationId").notNullable(); - t.foreign("rotationId").references("id").inTable(TableName.SecretRotation).onDelete("CASCADE"); + t.foreign("rotationId").references("id").inTable(TableName.DeprecatedSecretRotationV1).onDelete("CASCADE"); }); } } @@ -154,7 +154,7 @@ export async function down(knex: Knex): Promise { await knex.schema.dropTableIfExists(TableName.SecretV2JnTag); await knex.schema.dropTableIfExists(TableName.SecretReferenceV2); - await knex.schema.dropTableIfExists(TableName.SecretRotationOutputV2); + await knex.schema.dropTableIfExists(TableName.DeprecatedSecretRotationOutputV2); await dropOnUpdateTrigger(knex, TableName.SecretVersionV2); await knex.schema.dropTableIfExists(TableName.SecretVersionV2Tag); diff --git a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts index aef429ab98d..4ce97480db5 100644 --- a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts @@ -2,7 +2,6 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; import { crypto } from "@app/lib/crypto/cryptography"; -import { selectAllTableCols } from "@app/lib/knex"; import { initLogger } from "@app/lib/logger"; import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { KmsDataKey } from "@app/services/kms/kms-types"; @@ -15,11 +14,14 @@ import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/ const BATCH_SIZE = 500; export async function up(knex: Knex): Promise { - const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); + const hasEncryptedRotationData = await knex.schema.hasColumn( + TableName.DeprecatedSecretRotationV1, + "encryptedRotationData" + ); - const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); + const hasRotationTable = await knex.schema.hasTable(TableName.DeprecatedSecretRotationV1); if (hasRotationTable) { - await knex.schema.alterTable(TableName.SecretRotation, (t) => { + await knex.schema.alterTable(TableName.DeprecatedSecretRotationV1, (t) => { if (!hasEncryptedRotationData) t.binary("encryptedRotationData"); }); } @@ -36,9 +38,9 @@ export async function up(knex: Knex): Promise { const projectEncryptionRingBuffer = createCircularCache>>(25); - const secretRotations = await knex(TableName.SecretRotation) - .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretRotation}.envId`) - .select(selectAllTableCols(TableName.SecretRotation)) + const secretRotations = await knex(TableName.DeprecatedSecretRotationV1) + .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.DeprecatedSecretRotationV1}.envId`) + .select(`${TableName.DeprecatedSecretRotationV1}.*`) .select(knex.ref("projectId").withSchema(TableName.Environment)) .orderBy(`${TableName.Environment}.projectId` as "projectId"); @@ -88,25 +90,28 @@ export async function up(knex: Knex): Promise { for (let i = 0; i < updatedRotationData.length; i += BATCH_SIZE) { // eslint-disable-next-line no-await-in-loop - await knex(TableName.SecretRotation) + await knex(TableName.DeprecatedSecretRotationV1) .insert(updatedRotationData.slice(i, i + BATCH_SIZE)) .onConflict("id") .merge(); } if (hasRotationTable) { - await knex.schema.alterTable(TableName.SecretRotation, (t) => { + await knex.schema.alterTable(TableName.DeprecatedSecretRotationV1, (t) => { if (!hasEncryptedRotationData) t.binary("encryptedRotationData").notNullable().alter(); }); } } export async function down(knex: Knex): Promise { - const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); + const hasEncryptedRotationData = await knex.schema.hasColumn( + TableName.DeprecatedSecretRotationV1, + "encryptedRotationData" + ); - const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); + const hasRotationTable = await knex.schema.hasTable(TableName.DeprecatedSecretRotationV1); if (hasRotationTable) { - await knex.schema.alterTable(TableName.SecretRotation, (t) => { + await knex.schema.alterTable(TableName.DeprecatedSecretRotationV1, (t) => { if (hasEncryptedRotationData) t.dropColumn("encryptedRotationData"); }); } diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index 993c20382ba..e52edba42a6 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -196,10 +196,7 @@ export * from "./secret-folders"; export * from "./secret-imports"; export * from "./secret-references"; export * from "./secret-references-v2"; -export * from "./secret-rotation-output-v2"; -export * from "./secret-rotation-outputs"; export * from "./secret-rotation-v2-secret-mappings"; -export * from "./secret-rotations"; export * from "./secret-rotations-v2"; export * from "./secret-scanning-configs"; export * from "./secret-scanning-data-sources"; diff --git a/backend/src/db/schemas/models.ts b/backend/src/db/schemas/models.ts index 062c20f9520..45fc2a5a418 100644 --- a/backend/src/db/schemas/models.ts +++ b/backend/src/db/schemas/models.ts @@ -131,8 +131,6 @@ export enum TableName { SecretApprovalRequestSecret = "secret_approval_requests_secrets", SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags", SecretApprovalPolicyEnvironment = "secret_approval_policies_environments", - SecretRotation = "secret_rotations", - SecretRotationOutput = "secret_rotation_outputs", SamlConfig = "saml_configs", LdapConfig = "ldap_configs", OidcConfig = "oidc_configs", @@ -163,7 +161,6 @@ export enum TableName { JnSecretTag = "secret_tag_junction", SecretVersionTag = "secret_version_tag_junction", SecretVersionV2Tag = "secret_version_v2_tag_junction", - SecretRotationOutputV2 = "secret_rotation_output_v2", // KMS Service KmsServerRootConfig = "kms_root_config", KmsKey = "kms_keys", @@ -288,11 +285,14 @@ export enum TableName { PkiSigners = "pki_signers", PkiSigningOperations = "pki_signing_operations", + CaSigningConfig = "ca_signing_configs", + SecretValidationRule = "secret_validation_rules", + // Deprecated - Not used anymore now that Redis is persistent DeprecatedDurableQueueJobs = "queue_jobs", - - CaSigningConfig = "ca_signing_configs", - SecretValidationRule = "secret_validation_rules" + DeprecatedSecretRotationV1 = "secret_rotations", + DeprecatedSecretRotationOutput = "secret_rotation_outputs", + DeprecatedSecretRotationOutputV2 = "secret_rotation_output_v2" } export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId"; diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index 683af442195..d11c2afda0f 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -55,8 +55,6 @@ import { registerRelayRouter } from "./relay-router"; import { registerSamlRouter } from "./saml-router"; import { registerScimRouter } from "./scim-router"; import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router"; -import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router"; -import { registerSecretRotationRouter } from "./secret-rotation-router"; import { registerSecretRouter } from "./secret-router"; import { registerSecretScanningRouter } from "./secret-scanning-router"; import { registerSecretVersionRouter } from "./secret-version-router"; @@ -102,9 +100,6 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { await server.register(registerSecretApprovalRequestRouter, { prefix: "/secret-approval-requests" }); - await server.register(registerSecretRotationProviderRouter, { - prefix: "/secret-rotation-providers" - }); await server.register(registerAccessApprovalPolicyRouter, { prefix: "/access-approvals/policies" }); await server.register(registerAccessApprovalRequestRouter, { prefix: "/access-approvals/requests" }); @@ -157,7 +152,6 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => { await server.register(registerScimRouter, { prefix: "/scim" }); await server.register(registerLdapRouter, { prefix: "/ldap" }); await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" }); - await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" }); await server.register(registerSecretRouter, { prefix: "/secrets" }); await server.register(registerSecretVersionRouter, { prefix: "/secret" }); await server.register(registerGroupRouter, { prefix: "/groups" }); diff --git a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts b/backend/src/ee/routes/v1/secret-rotation-provider-router.ts deleted file mode 100644 index e6a1ac72bcc..00000000000 --- a/backend/src/ee/routes/v1/secret-rotation-provider-router.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { z } from "zod"; - -import { readLimit } from "@app/server/config/rateLimiter"; -import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { AuthMode } from "@app/services/auth/auth-type"; - -export const registerSecretRotationProviderRouter = async (server: FastifyZodProvider) => { - server.route({ - method: "GET", - url: "/:workspaceId", - config: { - rateLimit: readLimit - }, - schema: { - params: z.object({ - workspaceId: z.string().trim() - }), - response: { - 200: z.object({ - providers: z - .object({ - name: z.string(), - title: z.string(), - image: z.string().optional(), - description: z.string().optional(), - template: z.any(), - isDeprecated: z.boolean().optional() - }) - .array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const providers = await server.services.secretRotation.getProviderTemplates({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.params.workspaceId - }); - return providers; - } - }); -}; diff --git a/backend/src/ee/routes/v1/secret-rotation-router.ts b/backend/src/ee/routes/v1/secret-rotation-router.ts deleted file mode 100644 index 936459fa162..00000000000 --- a/backend/src/ee/routes/v1/secret-rotation-router.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { z } from "zod"; - -import { SecretRotationOutputsSchema, SecretRotationsSchema } from "@app/db/schemas"; -import { removeTrailingSlash } from "@app/lib/fn"; -import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; -import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { AuthMode } from "@app/services/auth/auth-type"; - -export const registerSecretRotationRouter = async (server: FastifyZodProvider) => { - server.route({ - method: "POST", - url: "/", - config: { - rateLimit: writeLimit - }, - schema: { - body: z.object({ - workspaceId: z.string().trim(), - secretPath: z.string().trim().transform(removeTrailingSlash), - environment: z.string().trim(), - interval: z.number().min(1), - provider: z.string().trim(), - customProvider: z.string().trim().optional(), - inputs: z.record(z.unknown()), - outputs: z.record(z.string()) - }), - response: { - 200: z.object({ - secretRotation: SecretRotationsSchema.merge( - z.object({ - environment: z.object({ - id: z.string(), - name: z.string(), - slug: z.string() - }), - outputs: SecretRotationOutputsSchema.array() - }) - ) - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const secretRotation = await server.services.secretRotation.createRotation({ - actor: req.permission.type, - actorAuthMethod: req.permission.authMethod, - actorId: req.permission.id, - actorOrgId: req.permission.orgId, - ...req.body, - projectId: req.body.workspaceId - }); - return { secretRotation }; - } - }); - - server.route({ - url: "/restart", - method: "POST", - config: { - rateLimit: writeLimit - }, - schema: { - body: z.object({ - id: z.string().trim() - }), - response: { - 200: z.object({ - secretRotation: SecretRotationsSchema.merge( - z.object({ - environment: z.object({ - id: z.string(), - name: z.string(), - slug: z.string() - }) - }) - ) - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const secretRotation = await server.services.secretRotation.restartById({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - rotationId: req.body.id - }); - return { secretRotation }; - } - }); - - server.route({ - url: "/", - method: "GET", - config: { - rateLimit: readLimit - }, - schema: { - querystring: z.object({ - workspaceId: z.string().trim() - }), - response: { - 200: z.object({ - secretRotations: SecretRotationsSchema.merge( - z.object({ - environment: z.object({ - id: z.string(), - name: z.string(), - slug: z.string() - }), - outputs: z - .object({ - key: z.string(), - secret: z.object({ - secretKey: z.string(), - id: z.string(), - version: z.number() - }) - }) - .array() - }) - ).array() - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const secretRotations = await server.services.secretRotation.getByProjectId({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - projectId: req.query.workspaceId - }); - return { secretRotations }; - } - }); - - server.route({ - method: "DELETE", - url: "/:id", - config: { - rateLimit: writeLimit - }, - schema: { - params: z.object({ - id: z.string().trim() - }), - response: { - 200: z.object({ - secretRotation: SecretRotationsSchema.merge( - z.object({ - environment: z.object({ - id: z.string(), - name: z.string(), - slug: z.string() - }) - }) - ) - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const secretRotation = await server.services.secretRotation.deleteById({ - actor: req.permission.type, - actorId: req.permission.id, - actorAuthMethod: req.permission.authMethod, - actorOrgId: req.permission.orgId, - rotationId: req.params.id - }); - return { secretRotation }; - } - }); -}; diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts index 688db03690d..52f6b03232a 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts @@ -1,8 +1,7 @@ import { ProjectMembershipRole } from "@app/db/schemas"; -import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue"; import { getConfig } from "@app/lib/config/env"; import { applyJitter } from "@app/lib/delay"; -import { NotFoundError } from "@app/lib/errors"; +import { DisableRotationErrors, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { TIdentityDALFactory } from "@app/services/identity/identity-dal"; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-dal.ts b/backend/src/ee/services/secret-rotation/secret-rotation-dal.ts deleted file mode 100644 index 7f885e4f935..00000000000 --- a/backend/src/ee/services/secret-rotation/secret-rotation-dal.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { Knex } from "knex"; - -import { TDbClient } from "@app/db"; -import { SecretRotationsSchema, TableName, TSecretRotations } from "@app/db/schemas"; -import { DatabaseError } from "@app/lib/errors"; -import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex"; - -export type TSecretRotationDALFactory = ReturnType; - -export const secretRotationDALFactory = (db: TDbClient) => { - const secretRotationOrm = ormify(db, TableName.SecretRotation); - const secretRotationOutputOrm = ormify(db, TableName.SecretRotationOutput); - const secretRotationOutputV2Orm = ormify(db, TableName.SecretRotationOutputV2); - - const findQuery = (filter: TFindFilter, tx: Knex) => - tx(TableName.SecretRotation) - .where(filter) - .join(TableName.Environment, `${TableName.SecretRotation}.envId`, `${TableName.Environment}.id`) - .leftJoin( - TableName.SecretRotationOutput, - `${TableName.SecretRotation}.id`, - `${TableName.SecretRotationOutput}.rotationId` - ) - .join(TableName.Secret, `${TableName.SecretRotationOutput}.secretId`, `${TableName.Secret}.id`) - .select(selectAllTableCols(TableName.SecretRotation)) - .select(tx.ref("name").withSchema(TableName.Environment).as("envName")) - .select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug")) - .select(tx.ref("id").withSchema(TableName.Environment).as("envId")) - .select(tx.ref("projectId").withSchema(TableName.Environment)) - .select(tx.ref("key").withSchema(TableName.SecretRotationOutput).as("outputKey")) - .select(tx.ref("id").withSchema(TableName.Secret).as("secId")) - .select(tx.ref("version").withSchema(TableName.Secret).as("secVersion")) - .select(tx.ref("secretKeyIV").withSchema(TableName.Secret)) - .select(tx.ref("secretKeyTag").withSchema(TableName.Secret)) - .select(tx.ref("secretKeyCiphertext").withSchema(TableName.Secret)); - - const find = async (filter: TFindFilter, tx?: Knex) => { - try { - const data = await findQuery(filter, tx || db.replicaNode()); - return sqlNestRelationships({ - data, - key: "id", - parentMapper: (el) => ({ - ...SecretRotationsSchema.parse(el), - projectId: el.projectId, - environment: { id: el.envId, name: el.envName, slug: el.envSlug } - }), - childrenMapper: [ - { - key: "secId", - label: "outputs" as const, - mapper: ({ secId, outputKey, secVersion, secretKeyIV, secretKeyTag, secretKeyCiphertext }) => ({ - key: outputKey, - secret: { - id: secId, - version: secVersion, - secretKeyIV, - secretKeyTag, - secretKeyCiphertext - } - }) - } - ] - }); - } catch (error) { - throw new DatabaseError({ error, name: "SecretRotationFind" }); - } - }; - - const findQuerySecretV2 = (filter: TFindFilter, tx: Knex) => - tx(TableName.SecretRotation) - .where(filter) - .join(TableName.Environment, `${TableName.SecretRotation}.envId`, `${TableName.Environment}.id`) - .leftJoin( - TableName.SecretRotationOutputV2, - `${TableName.SecretRotation}.id`, - `${TableName.SecretRotationOutputV2}.rotationId` - ) - .join(TableName.SecretV2, `${TableName.SecretRotationOutputV2}.secretId`, `${TableName.SecretV2}.id`) - .select(selectAllTableCols(TableName.SecretRotation)) - .select(tx.ref("name").withSchema(TableName.Environment).as("envName")) - .select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug")) - .select(tx.ref("id").withSchema(TableName.Environment).as("envId")) - .select(tx.ref("projectId").withSchema(TableName.Environment)) - .select(tx.ref("key").withSchema(TableName.SecretRotationOutputV2).as("outputKey")) - .select(tx.ref("id").withSchema(TableName.SecretV2).as("secId")) - .select(tx.ref("version").withSchema(TableName.SecretV2).as("secVersion")) - .select(tx.ref("key").withSchema(TableName.SecretV2).as("secretKey")); - - const findSecretV2 = async (filter: TFindFilter, tx?: Knex) => { - try { - const data = await findQuerySecretV2(filter, tx || db.replicaNode()); - return sqlNestRelationships({ - data, - key: "id", - parentMapper: (el) => ({ - ...SecretRotationsSchema.parse(el), - projectId: el.projectId, - environment: { id: el.envId, name: el.envName, slug: el.envSlug } - }), - childrenMapper: [ - { - key: "secId", - label: "outputs" as const, - mapper: ({ secId, outputKey, secVersion, secretKey }) => ({ - key: outputKey, - secret: { - id: secId, - version: secVersion, - secretKey - } - }) - } - ] - }); - } catch (error) { - throw new DatabaseError({ error, name: "SecretRotationFind" }); - } - }; - - const findById = async (id: string, tx?: Knex) => { - try { - const doc = await (tx || db.replicaNode())(TableName.SecretRotation) - .join(TableName.Environment, `${TableName.SecretRotation}.envId`, `${TableName.Environment}.id`) - .where({ [`${TableName.SecretRotation}.id` as "id"]: id }) - .select(selectAllTableCols(TableName.SecretRotation)) - .select( - db.ref("id").withSchema(TableName.Environment).as("envId"), - db.ref("projectId").withSchema(TableName.Environment), - db.ref("slug").withSchema(TableName.Environment).as("envSlug"), - db.ref("name").withSchema(TableName.Environment).as("envName") - ) - .first(); - if (doc) { - const { envName, envSlug, envId, ...el } = doc; - return { ...el, envId, environment: { id: envId, slug: envSlug, name: envName } }; - } - } catch (error) { - throw new DatabaseError({ error, name: "SecretRotationFindById" }); - } - }; - - const findRotationOutputsByRotationId = async (rotationId: string) => secretRotationOutputOrm.find({ rotationId }); - const findRotationOutputsV2ByRotationId = async (rotationId: string) => - secretRotationOutputV2Orm.find({ rotationId }); - - // special query - - return { - ...secretRotationOrm, - find, - findSecretV2, - findById, - secretOutputInsertMany: secretRotationOutputOrm.insertMany, - secretOutputV2InsertMany: secretRotationOutputV2Orm.insertMany, - findRotationOutputsByRotationId, - findRotationOutputsV2ByRotationId - }; -}; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/index.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/index.ts deleted file mode 100644 index 1d8c50b363f..00000000000 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type { TSecretRotationQueueFactory } from "./secret-rotation-queue"; -export { DisableRotationErrors, secretRotationQueueFactory } from "./secret-rotation-queue"; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-fn.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-fn.ts deleted file mode 100644 index 8d30686b25e..00000000000 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-fn.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable no-param-reassign */ -import knex from "knex"; - -import { alphaNumericNanoId } from "@app/lib/nanoid"; - -import { verifyHostInputValidity } from "../../dynamic-secret/dynamic-secret-fns"; -import { TDbProviderClients, TDirectAssignOp } from "../templates/types"; -import { TSecretRotationData, TSecretRotationDbFn } from "./secret-rotation-queue-types"; - -const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; - -const replaceTemplateVariables = (str: string, getValue: (key: string) => unknown) => { - // Use array to collect pieces and join at the end (more efficient for large strings) - const parts: string[] = []; - let pos = 0; - - while (pos < str.length) { - const start = str.indexOf("${", pos); - if (start === -1) { - parts.push(str.slice(pos)); - break; - } - - parts.push(str.slice(pos, start)); - const end = str.indexOf("}", start + 2); - - if (end === -1) { - parts.push(str.slice(start)); - break; - } - - const varName = str.slice(start + 2, end); - parts.push(String(getValue(varName))); - pos = end + 1; - } - - return parts.join(""); -}; - -export const interpolate = (data: any, getValue: (key: string) => unknown) => { - if (!data) return; - - if (typeof data === "number") return data; - - if (typeof data === "string") { - return replaceTemplateVariables(data, getValue); - } - - if (typeof data === "object" && Array.isArray(data)) { - data.forEach((el, index) => { - // eslint-disable-next-line - data[index] = interpolate(el, getValue); - }); - } - - if (typeof data === "object") { - if ((data as { ref: string })?.ref) return getValue((data as { ref: string }).ref); - const temp = data as Record; // for converting ts object to record type - Object.keys(temp).forEach((key) => { - temp[key] = interpolate(data[key], getValue); - }); - } - return data; -}; - -const getInterpolationValue = (variables: TSecretRotationData) => (key: string) => { - if (key.includes("|")) { - const [keyword, ...arg] = key.split("|").map((el) => el.trim()); - switch (keyword) { - case "random": { - return alphaNumericNanoId(parseInt(arg[0], 10)); - } - default: { - throw Error(`Interpolation key not found - ${key}`); - } - } - } - const [type, keyName] = key.split(".").map((el) => el.trim()); - return variables[type as keyof TSecretRotationData][keyName]; -}; - -export const secretRotationHttpFn = async () => {}; - -export const secretRotationDbFn = async ({ - ca, - host, - port, - query, - database, - password, - username, - client, - variables, - options -}: TSecretRotationDbFn) => { - const ssl = ca ? { rejectUnauthorized: false, ca } : undefined; - const [hostIp] = await verifyHostInputValidity({ host, isDynamicSecret: false }); - const db = knex({ - client, - connection: { - database, - port, - host: hostIp, - user: username, - password, - connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT, - ssl, - pool: { min: 0, max: 1 }, - options - } - }); - const data = await db.raw(query, variables); - return data; -}; - -export const secretRotationPreSetFn = (op: Record, variables: TSecretRotationData) => { - const getValFn = getInterpolationValue(variables); - Object.entries(op || {}).forEach(([key, assignFn]) => { - const [type, keyName] = key.split(".") as [keyof TSecretRotationData, string]; - variables[type][keyName] = interpolate(assignFn.value, getValFn); - }); -}; - -export const secretRotationHttpSetFn = async () => {}; - -export const getDbSetQuery = (db: TDbProviderClients, variables: { username: string; password: string }) => { - if (db === TDbProviderClients.Pg) { - return { - query: `ALTER USER ?? WITH PASSWORD '${variables.password}'`, - variables: [variables.username] - }; - } - - if (db === TDbProviderClients.MsSqlServer) { - return { - query: `ALTER LOGIN ?? WITH PASSWORD = '${variables.password}'`, - variables: [variables.username] - }; - } - - if (db === TDbProviderClients.MySql) { - return { - query: `ALTER USER ??@'%' IDENTIFIED BY '${variables.password}'`, - variables: [variables.username] - }; - } - - if (db === TDbProviderClients.OracleDB) { - return { - query: `ALTER USER ?? IDENTIFIED BY "${variables.password}"`, - variables: [variables.username] - }; - } - - // add more based on client - return { - query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`, - variables: [variables.username] - }; -}; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-types.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-types.ts deleted file mode 100644 index e0d4c18761b..00000000000 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TDbProviderClients } from "../templates/types"; - -export type TSecretRotationEncData = { - inputs: Record; - creds: Array<{ - outputs: Record; - internal: Record; - }>; -}; - -export type TSecretRotationData = { - inputs: Record; - outputs: Record; - internal: Record; -}; - -export type TSecretRotationDbFn = { - client: TDbProviderClients; - username: string; - password: string; - host: string; - database: string; - port: number; - query: string; - variables: unknown[]; - ca?: string; - options?: Record; -}; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts deleted file mode 100644 index 6eaad6a92b8..00000000000 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ /dev/null @@ -1,441 +0,0 @@ -import { - CreateAccessKeyCommand, - DeleteAccessKeyCommand, - GetAccessKeyLastUsedCommand, - IAMClient -} from "@aws-sdk/client-iam"; - -import { SecretType } from "@app/db/schemas"; -import { CustomAWSHasher } from "@app/lib/aws/hashing"; -import { getConfig } from "@app/lib/config/env"; -import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; -import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; -import { NotFoundError } from "@app/lib/errors"; -import { logger } from "@app/lib/logger"; -import { alphaNumericNanoId } from "@app/lib/nanoid"; -import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; -import { ActorType } from "@app/services/auth/auth-type"; -import { CommitType, TFolderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; -import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; -import { TSecretDALFactory } from "@app/services/secret/secret-dal"; -import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal"; -import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal"; -import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; -import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service"; -import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; - -import { TSecretRotationDALFactory } from "../secret-rotation-dal"; -import { rotationTemplates } from "../templates"; -import { - TAwsProviderSystems, - TDbProviderClients, - TProviderFunctionTypes, - TSecretRotationProviderTemplate -} from "../templates/types"; -import { - getDbSetQuery, - secretRotationDbFn, - secretRotationHttpFn, - secretRotationHttpSetFn, - secretRotationPreSetFn -} from "./secret-rotation-queue-fn"; -import { TSecretRotationData, TSecretRotationDbFn, TSecretRotationEncData } from "./secret-rotation-queue-types"; - -export type TSecretRotationQueueFactory = ReturnType; - -type TSecretRotationQueueFactoryDep = { - queue: TQueueServiceFactory; - secretRotationDAL: TSecretRotationDALFactory; - projectBotService: Pick; - secretDAL: Pick; - secretV2BridgeDAL: Pick; - secretVersionDAL: Pick; - secretVersionV2BridgeDAL: Pick; - telemetryService: Pick; - kmsService: Pick; - folderCommitService: Pick; -}; - -// These error should stop the repeatable job and ask user to reconfigure rotation -export class DisableRotationErrors extends Error { - name: string; - - error: unknown; - - constructor({ name, error, message }: { message: string; name?: string; error?: unknown }) { - super(message); - this.name = name || "DisableRotationErrors"; - this.error = error; - } -} - -export const secretRotationQueueFactory = ({ - queue, - secretRotationDAL, - projectBotService, - secretDAL, - secretVersionDAL, - telemetryService, - secretV2BridgeDAL, - secretVersionV2BridgeDAL, - folderCommitService, - kmsService -}: TSecretRotationQueueFactoryDep) => { - const addToQueue = async () => {}; - - const removeFromQueue = async (rotationId: string, interval: number) => { - const appCfg = getConfig(); - await queue.stopRepeatableJob( - QueueName.SecretRotation, - QueueJobs.SecretRotation, - { - // on prod it this will be in days, in development this will be second - every: appCfg.NODE_ENV === "development" ? secondsToMillis(interval) : daysToMillisecond(interval) - }, - rotationId - ); - }; - - queue.start(QueueName.SecretRotation, async (job) => { - const { rotationId } = job.data; - const appCfg = getConfig(); - - logger.info(`secretRotationQueue.process: [rotationDocument=${rotationId}]`); - const secretRotation = await secretRotationDAL.findById(rotationId); - const rotationProvider = rotationTemplates.find(({ name }) => name === secretRotation?.provider); - - try { - if (!rotationProvider || !secretRotation) throw new DisableRotationErrors({ message: "Provider not found" }); - - const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(secretRotation.projectId); - let rotationOutputs; - if (shouldUseSecretV2Bridge) { - rotationOutputs = await secretRotationDAL.findRotationOutputsV2ByRotationId(rotationId); - } else { - rotationOutputs = await secretRotationDAL.findRotationOutputsByRotationId(rotationId); - } - if (!rotationOutputs.length) throw new DisableRotationErrors({ message: "Secrets not found" }); - - // deep copy - const provider = JSON.parse(JSON.stringify(rotationProvider)) as TSecretRotationProviderTemplate; - const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = - await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: secretRotation.projectId - }); - - const decryptedData = secretManagerDecryptor({ - cipherTextBlob: secretRotation.encryptedRotationData - }).toString(); - - const variables = JSON.parse(decryptedData) as TSecretRotationEncData; - // rotation set cycle - const newCredential: TSecretRotationData = { - inputs: variables.inputs, - outputs: {}, - internal: {} - }; - - /* Rotation Function For Database - * A database like sql cannot have multiple password for a user - * thus we ask users to create two users with required permission and then we keep cycling between these two db users - */ - if (provider.template.type === TProviderFunctionTypes.DB) { - const lastCred = variables.creds.at(-1); - if (lastCred && variables.creds.length === 1) { - newCredential.internal.username = - lastCred.internal.username === variables.inputs.username1 - ? variables.inputs.username2 - : variables.inputs.username1; - } else { - newCredential.internal.username = lastCred ? lastCred.internal.username : variables.inputs.username1; - } - // set a random value for new password - newCredential.internal.rotated_password = alphaNumericNanoId(32); - const { admin_username: username, admin_password: password, host, database, port, ca } = newCredential.inputs; - - const options = - provider.template.client === TDbProviderClients.MsSqlServer - ? ({ - encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT, - // when ca is provided use that - trustServerCertificate: !ca, - cryptoCredentialsDetails: ca ? { ca } : {} - } as Record) - : undefined; - - const dbFunctionArg = { - username, - password, - host, - database, - port, - ca: ca as string, - client: provider.template.client === TDbProviderClients.MySql ? "mysql2" : provider.template.client, - options - } as TSecretRotationDbFn; - - // set function - await secretRotationDbFn({ - ...dbFunctionArg, - ...getDbSetQuery(provider.template.client, { - password: newCredential.internal.rotated_password as string, - username: newCredential.internal.username as string - }) - }); - - // test function - const testQuery = - provider.template.client === TDbProviderClients.MsSqlServer ? "SELECT GETDATE()" : "SELECT NOW()"; - - await secretRotationDbFn({ - ...dbFunctionArg, - query: testQuery, - variables: [] - }); - - newCredential.outputs.db_username = newCredential.internal.username; - newCredential.outputs.db_password = newCredential.internal.rotated_password; - // clean up - if (variables.creds.length === 2) variables.creds.pop(); - } - - /* - * Rotation Function For AWS Services - * Due to complexity in AWS Authorization hashing signature process we keep it as seperate entity instead of http template mode - * We first delete old key before creating a new one because aws iam has a quota limit of 2 keys - * */ - if (provider.template.type === TProviderFunctionTypes.AWS) { - if (provider.template.client === TAwsProviderSystems.IAM) { - const client = new IAMClient({ - useFipsEndpoint: crypto.isFipsModeEnabled(), - sha256: CustomAWSHasher, - region: newCredential.inputs.manager_user_aws_region as string, - credentials: { - accessKeyId: newCredential.inputs.manager_user_access_key as string, - secretAccessKey: newCredential.inputs.manager_user_secret_key as string - } - }); - - const iamUserName = newCredential.inputs.iam_username as string; - - if (variables.creds.length === 2) { - const deleteCycleCredential = variables.creds.pop(); - if (deleteCycleCredential) { - const deletedIamAccessKey = await client.send( - new DeleteAccessKeyCommand({ - UserName: iamUserName, - AccessKeyId: deleteCycleCredential.outputs.iam_user_access_key as string - }) - ); - - if ( - !deletedIamAccessKey?.$metadata?.httpStatusCode || - deletedIamAccessKey?.$metadata?.httpStatusCode > 300 - ) { - throw new DisableRotationErrors({ - message: "Failed to delete aws iam access key. Check managed iam user policy" - }); - } - } - } - - const newIamAccessKey = await client.send(new CreateAccessKeyCommand({ UserName: iamUserName })); - if (!newIamAccessKey.AccessKey) - throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" }); - - // test - const testAccessKey = await client.send( - new GetAccessKeyLastUsedCommand({ AccessKeyId: newIamAccessKey.AccessKey.AccessKeyId }) - ); - if (testAccessKey?.UserName !== iamUserName) - throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" }); - - newCredential.outputs.iam_user_access_key = newIamAccessKey.AccessKey.AccessKeyId; - newCredential.outputs.iam_user_secret_key = newIamAccessKey.AccessKey.SecretAccessKey; - } - } - - /* Rotation function of HTTP infisical template - * This is a generic http based template system for rotation - * we use this for sendgrid and for custom secret rotation - * This will ensure user provided rotation is easier to make - * */ - if (provider.template.type === TProviderFunctionTypes.HTTP) { - if (provider.template.functions.set?.pre) { - secretRotationPreSetFn(provider.template.functions.set.pre, newCredential); - } - await secretRotationHttpSetFn(); - // now test - await secretRotationHttpFn(); - if (variables.creds.length === 2) { - const deleteCycleCred = variables.creds.pop(); - if (deleteCycleCred && provider.template.functions.remove) { - await secretRotationHttpFn(); - } - } - } - - // insert the new variables to start - // encrypt the data - save it - variables.creds.unshift({ - outputs: newCredential.outputs, - internal: newCredential.internal - }); - const encryptedRotationData = secretManagerEncryptor({ - plainText: Buffer.from(JSON.stringify(variables)) - }).cipherTextBlob; - - const numberOfSecretsRotated = rotationOutputs.length; - if (shouldUseSecretV2Bridge) { - const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({ - secretId, - value: - typeof newCredential.outputs[outputKey] === "object" - ? JSON.stringify(newCredential.outputs[outputKey]) - : String(newCredential.outputs[outputKey]) - })); - // map the final values to output keys in the board - await secretRotationDAL.transaction(async (tx) => { - await secretRotationDAL.updateById( - rotationId, - { - encryptedRotationData, - lastRotatedAt: new Date(), - statusMessage: "Rotated successfull", - status: "success" - }, - tx - ); - const updatedSecrets = await secretV2BridgeDAL.bulkUpdate( - encryptedSecrets.map(({ secretId, value }) => ({ - // this secret id is validated when user is inserted - filter: { id: secretId, type: SecretType.Shared }, - data: { - encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob - } - })), - tx - ); - const secretVersions = await secretVersionV2BridgeDAL.insertMany( - updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({ - ...el, - actorType: ActorType.PLATFORM, - secretId: id - })), - tx - ); - - await folderCommitService.createCommit( - { - actor: { - type: ActorType.PLATFORM - }, - message: "Changed by Secret rotation", - folderId: secretVersions[0].folderId, - changes: secretVersions.map((sv) => ({ - type: CommitType.ADD, - isUpdate: true, - secretVersionId: sv.id - })) - }, - tx - ); - await secretV2BridgeDAL.invalidateSecretCacheByProjectId(secretRotation.projectId, tx); - }); - } else { - if (!botKey) - throw new NotFoundError({ - message: `Project bot not found for project with ID '${secretRotation.projectId}'` - }); - - const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({ - secretId, - value: crypto - .encryption() - .symmetric() - .encrypt({ - plaintext: - typeof newCredential.outputs[outputKey] === "object" - ? JSON.stringify(newCredential.outputs[outputKey]) - : String(newCredential.outputs[outputKey]), - key: botKey, - keySize: SymmetricKeySize.Bits128 - }) - })); - - // map the final values to output keys in the board - await secretRotationDAL.transaction(async (tx) => { - await secretRotationDAL.updateById( - rotationId, - { - encryptedRotationData, - lastRotatedAt: new Date(), - statusMessage: "Rotated successfull", - status: "success" - }, - tx - ); - const updatedSecrets = await secretDAL.bulkUpdate( - encryptedSecrets.map(({ secretId, value }) => ({ - // this secret id is validated when user is inserted - filter: { id: secretId, type: SecretType.Shared }, - data: { - secretValueCiphertext: value.ciphertext, - secretValueIV: value.iv, - secretValueTag: value.tag - } - })), - tx - ); - await secretVersionDAL.insertMany( - updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => { - if (!el.secretBlindIndex) { - throw new NotFoundError({ message: `Secret blind index not found on secret with ID '${id}` }); - } - return { - ...el, - secretId: id, - secretBlindIndex: el.secretBlindIndex - }; - }), - tx - ); - }); - } - - await telemetryService.sendPostHogEvents({ - event: PostHogEventTypes.SecretRotated, - distinctId: "", - properties: { - numberOfSecrets: numberOfSecretsRotated, - environment: secretRotation.environment.slug, - secretPath: secretRotation.secretPath, - projectId: secretRotation.projectId - } - }); - - logger.info("Finished rotating: rotation id: ", rotationId); - } catch (error) { - logger.error(error, "Failed to execute secret rotation"); - if (error instanceof DisableRotationErrors) { - if (job.id) { - await queue.stopRepeatableJobByJobId(QueueName.SecretRotation, job.id); - } - } - - await secretRotationDAL.updateById(rotationId, { - status: "failed", - statusMessage: (error as Error).message.slice(0, 500), - lastRotatedAt: new Date() - }); - } - }); - - return { - addToQueue, - removeFromQueue - }; -}; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts deleted file mode 100644 index a093f879072..00000000000 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { ForbiddenError, subject } from "@casl/ability"; -import Ajv from "ajv"; - -import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas"; -import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; -import { BadRequestError, NotFoundError } from "@app/lib/errors"; -import { TProjectPermission } from "@app/lib/types"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; -import { TProjectDALFactory } from "@app/services/project/project-dal"; -import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; -import { TSecretDALFactory } from "@app/services/secret/secret-dal"; -import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; -import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal"; - -import { TLicenseServiceFactory } from "../license/license-service"; -import { TPermissionServiceFactory } from "../permission/permission-service-types"; -import { - ProjectPermissionSecretActions, - ProjectPermissionSecretRotationActions, - ProjectPermissionSub -} from "../permission/project-permission"; -import { TSecretRotationDALFactory } from "./secret-rotation-dal"; -import { TSecretRotationQueueFactory } from "./secret-rotation-queue"; -import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types"; -import { TCreateSecretRotationDTO, TDeleteDTO, TListByProjectIdDTO, TRestartDTO } from "./secret-rotation-types"; -import { rotationTemplates } from "./templates"; - -type TSecretRotationServiceFactoryDep = { - secretRotationDAL: TSecretRotationDALFactory; - projectDAL: Pick; - folderDAL: Pick; - secretDAL: Pick; - secretV2BridgeDAL: Pick; - licenseService: Pick; - permissionService: Pick; - secretRotationQueue: TSecretRotationQueueFactory; - projectBotService: Pick; - kmsService: Pick; -}; - -export type TSecretRotationServiceFactory = ReturnType; - -const ajv = new Ajv({ strict: false }); -export const secretRotationServiceFactory = ({ - secretRotationDAL, - permissionService, - secretRotationQueue, - licenseService, - projectDAL, - folderDAL, - secretDAL, - projectBotService, - secretV2BridgeDAL, - kmsService -}: TSecretRotationServiceFactoryDep) => { - const getProviderTemplates = async ({ - actor, - actorId, - actorOrgId, - actorAuthMethod, - projectId - }: TProjectPermission) => { - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager - }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretRotationActions.Read, - ProjectPermissionSub.SecretRotation - ); - - return { - custom: [], - providers: rotationTemplates - }; - }; - - const createRotation = async ({ - projectId, - actorId, - actor, - actorOrgId, - actorAuthMethod, - inputs, - outputs, - interval, - provider, - secretPath, - environment - }: TCreateSecretRotationDTO) => { - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager - }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretRotationActions.Read, - ProjectPermissionSub.SecretRotation - ); - - const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); - if (!folder) { - throw new NotFoundError({ - message: `Secret path with path '${secretPath}' not found in environment with slug '${environment}'` - }); - } - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretActions.Edit, - subject(ProjectPermissionSub.Secrets, { environment, secretPath }) - ); - - const project = await projectDAL.findById(projectId); - const shouldUseBridge = project.version === ProjectVersion.V3; - - if (shouldUseBridge) { - const selectedSecrets = await secretV2BridgeDAL.find({ - folderId: folder.id, - $in: { [`${TableName.SecretV2}.id` as "id"]: Object.values(outputs) } - }); - if (selectedSecrets.length !== Object.values(outputs).length) - throw new NotFoundError({ message: `Secrets not found in folder with ID '${folder.id}'` }); - const rotatedSecrets = selectedSecrets.filter(({ isRotatedSecret }) => isRotatedSecret); - if (rotatedSecrets.length) - throw new BadRequestError({ - message: `Selected secrets are already used for rotation: ${rotatedSecrets - .map((secret) => secret.key) - .join(", ")}` - }); - } else { - const selectedSecrets = await secretDAL.find({ - folderId: folder.id, - $in: { id: Object.values(outputs) } - }); - if (selectedSecrets.length !== Object.values(outputs).length) - throw new NotFoundError({ message: `Secrets not found in folder with ID '${folder.id}'` }); - } - - const plan = await licenseService.getPlan(project.orgId); - if (!plan.secretRotation) - throw new BadRequestError({ - message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation." - }); - - const selectedTemplate = rotationTemplates.find(({ name }) => name === provider); - if (!selectedTemplate) throw new NotFoundError({ message: `Provider with name '${provider}' not found` }); - const formattedInputs: Record = {}; - Object.entries(inputs).forEach(([key, value]) => { - const { type } = selectedTemplate.template.inputs.properties[key]; - if (type === "string") { - formattedInputs[key] = value; - return; - } - if (type === "integer") { - formattedInputs[key] = parseInt(value as string, 10); - return; - } - formattedInputs[key] = JSON.parse(value as string); - }); - // ensure input one follows the correct schema - const valid = ajv.validate(selectedTemplate.template.inputs, formattedInputs); - if (!valid) { - throw new BadRequestError({ message: ajv.errors?.[0].message }); - } - - const unencryptedData: Partial = { - inputs: formattedInputs, - creds: [] - }; - const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); - - const secretRotation = await secretRotationDAL.transaction(async (tx) => { - const doc = await secretRotationDAL.create( - { - provider, - secretPath, - interval, - envId: folder.envId, - encryptedRotationData: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(unencryptedData)) }) - .cipherTextBlob - }, - tx - ); - let outputSecretMapping; - if (shouldUseBridge) { - outputSecretMapping = await secretRotationDAL.secretOutputV2InsertMany( - Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })), - tx - ); - } else { - outputSecretMapping = await secretRotationDAL.secretOutputInsertMany( - Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })), - tx - ); - } - return { ...doc, outputs: outputSecretMapping, environment: folder.environment }; - }); - await secretRotationQueue.addToQueue(); - return secretRotation; - }; - - const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => { - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager - }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretRotationActions.Read, - ProjectPermissionSub.SecretRotation - ); - const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); - if (shouldUseSecretV2Bridge) { - const docs = await secretRotationDAL.findSecretV2({ projectId }); - return docs; - } - - if (!botKey) throw new NotFoundError({ message: `Project bot not found for project with ID '${projectId}'` }); - const docs = await secretRotationDAL.find({ projectId }); - - return docs.map((el) => ({ - ...el, - outputs: el.outputs.map((output) => ({ - ...output, - secret: { - id: output.secret.id, - version: output.secret.version, - secretKey: crypto.encryption().symmetric().decrypt({ - ciphertext: output.secret.secretKeyCiphertext, - iv: output.secret.secretKeyIV, - tag: output.secret.secretKeyTag, - key: botKey, - keySize: SymmetricKeySize.Bits128 - }) - } - })) - })); - }; - - const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => { - const doc = await secretRotationDAL.findById(rotationId); - if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` }); - - const project = await projectDAL.findById(doc.projectId); - const plan = await licenseService.getPlan(project.orgId); - if (!plan.secretRotation) - throw new BadRequestError({ - message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation." - }); - - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId: project.id, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager - }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretRotationActions.Edit, - ProjectPermissionSub.SecretRotation - ); - await secretRotationQueue.removeFromQueue(doc.id, doc.interval); - await secretRotationQueue.addToQueue(); - return doc; - }; - - const deleteById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TDeleteDTO) => { - const doc = await secretRotationDAL.findById(rotationId); - if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` }); - - const { permission } = await permissionService.getProjectPermission({ - actor, - actorId, - projectId: doc.projectId, - actorAuthMethod, - actorOrgId, - actionProjectType: ActionProjectType.SecretManager - }); - ForbiddenError.from(permission).throwUnlessCan( - ProjectPermissionSecretRotationActions.Delete, - ProjectPermissionSub.SecretRotation - ); - const deletedDoc = await secretRotationDAL.transaction(async (tx) => { - const strat = await secretRotationDAL.deleteById(rotationId, tx); - return strat; - }); - await secretRotationQueue.removeFromQueue(deletedDoc.id, deletedDoc.interval); - return { ...doc, ...deletedDoc }; - }; - - return { - getProviderTemplates, - getByProjectId, - createRotation, - restartById, - deleteById - }; -}; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-types.ts b/backend/src/ee/services/secret-rotation/secret-rotation-types.ts deleted file mode 100644 index 990bf3ecaab..00000000000 --- a/backend/src/ee/services/secret-rotation/secret-rotation-types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { TProjectPermission } from "@app/lib/types"; - -export type TCreateSecretRotationDTO = { - secretPath: string; - environment: string; - interval: number; - provider: string; - inputs: Record; - outputs: Record; -} & TProjectPermission; - -export type TListByProjectIdDTO = TProjectPermission; - -export type TDeleteDTO = { - rotationId: string; -} & Omit; - -export type TRestartDTO = { - rotationId: string; -} & Omit; diff --git a/backend/src/ee/services/secret-rotation/templates/aws-iam.ts b/backend/src/ee/services/secret-rotation/templates/aws-iam.ts deleted file mode 100644 index d4506c26e91..00000000000 --- a/backend/src/ee/services/secret-rotation/templates/aws-iam.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { TAwsProviderSystems, TProviderFunctionTypes } from "./types"; - -export const AWS_IAM_TEMPLATE = { - type: TProviderFunctionTypes.AWS as const, - client: TAwsProviderSystems.IAM, - inputs: { - type: "object" as const, - properties: { - manager_user_access_key: { type: "string" as const }, - manager_user_secret_key: { type: "string" as const }, - manager_user_aws_region: { type: "string" as const }, - iam_username: { type: "string" as const } - }, - required: ["manager_user_access_key", "manager_user_secret_key", "manager_user_aws_region", "iam_username"], - additionalProperties: false - }, - outputs: { - iam_user_access_key: { type: "string" }, - iam_user_secret_key: { type: "string" } - } -}; diff --git a/backend/src/ee/services/secret-rotation/templates/index.ts b/backend/src/ee/services/secret-rotation/templates/index.ts deleted file mode 100644 index ce3ecd68724..00000000000 --- a/backend/src/ee/services/secret-rotation/templates/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { AWS_IAM_TEMPLATE } from "./aws-iam"; -import { MSSQL_TEMPLATE } from "./mssql"; -import { MYSQL_TEMPLATE } from "./mysql"; -import { POSTGRES_TEMPLATE } from "./postgres"; -import { SENDGRID_TEMPLATE } from "./sendgrid"; -import { TSecretRotationProviderTemplate } from "./types"; - -export const rotationTemplates: TSecretRotationProviderTemplate[] = [ - { - name: "sendgrid", - title: "Twilio Sendgrid", - image: "sendgrid.png", - description: "Rotate Twilio Sendgrid API keys", - template: SENDGRID_TEMPLATE - }, - { - name: "postgres", - title: "PostgreSQL", - image: "postgres.png", - description: "Rotate PostgreSQL/CockroachDB user credentials", - template: POSTGRES_TEMPLATE, - isDeprecated: true - }, - { - name: "mysql", - title: "MySQL", - image: "mysql.png", - description: "Rotate MySQL@7/MariaDB user credentials", - template: MYSQL_TEMPLATE - }, - { - name: "mssql", - title: "Microsoft SQL Server", - image: "mssqlserver.png", - description: "Rotate Microsoft SQL server user credentials", - template: MSSQL_TEMPLATE, - isDeprecated: true - }, - { - name: "aws-iam", - title: "AWS IAM", - image: "aws-iam.svg", - description: "Rotate AWS IAM User credentials", - template: AWS_IAM_TEMPLATE - } -]; diff --git a/backend/src/ee/services/secret-rotation/templates/mssql.ts b/backend/src/ee/services/secret-rotation/templates/mssql.ts deleted file mode 100644 index 30096590d9c..00000000000 --- a/backend/src/ee/services/secret-rotation/templates/mssql.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TDbProviderClients, TProviderFunctionTypes } from "./types"; - -export const MSSQL_TEMPLATE = { - type: TProviderFunctionTypes.DB as const, - client: TDbProviderClients.MsSqlServer, - inputs: { - type: "object" as const, - properties: { - admin_username: { type: "string" as const }, - admin_password: { type: "string" as const }, - host: { type: "string" as const }, - database: { type: "string" as const, default: "master" }, - port: { type: "integer" as const, default: "1433" }, - username1: { - type: "string", - default: "infisical-sql-user1", - desc: "SQL Server login name that must be created at server level with a matching database user" - }, - username2: { - type: "string", - default: "infisical-sql-user2", - desc: "SQL Server login name that must be created at server level with a matching database user" - }, - ca: { type: "string", desc: "SSL certificate for db auth(string)" } - }, - required: ["admin_username", "admin_password", "host", "database", "username1", "username2", "port"], - additionalProperties: false - }, - outputs: { - db_username: { type: "string" }, - db_password: { type: "string" } - } -}; diff --git a/backend/src/ee/services/secret-rotation/templates/mysql.ts b/backend/src/ee/services/secret-rotation/templates/mysql.ts deleted file mode 100644 index 723560a3e8b..00000000000 --- a/backend/src/ee/services/secret-rotation/templates/mysql.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TDbProviderClients, TProviderFunctionTypes } from "./types"; - -export const MYSQL_TEMPLATE = { - type: TProviderFunctionTypes.DB as const, - client: TDbProviderClients.MySql, - inputs: { - type: "object" as const, - properties: { - admin_username: { type: "string" as const }, - admin_password: { type: "string" as const }, - host: { type: "string" as const }, - database: { type: "string" as const }, - port: { type: "integer" as const, default: "3306" }, - username1: { - type: "string", - default: "infisical-sql-user1", - desc: "This user must be created in your database" - }, - username2: { - type: "string", - default: "infisical-sql-user2", - desc: "This user must be created in your database" - }, - ca: { type: "string", desc: "SSL certificate for db auth(string)" } - }, - required: ["admin_username", "admin_password", "host", "database", "username1", "username2", "port"], - additionalProperties: false - }, - outputs: { - db_username: { type: "string" }, - db_password: { type: "string" } - } -}; diff --git a/backend/src/ee/services/secret-rotation/templates/postgres.ts b/backend/src/ee/services/secret-rotation/templates/postgres.ts deleted file mode 100644 index c894631cb28..00000000000 --- a/backend/src/ee/services/secret-rotation/templates/postgres.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TDbProviderClients, TProviderFunctionTypes } from "./types"; - -export const POSTGRES_TEMPLATE = { - type: TProviderFunctionTypes.DB as const, - client: TDbProviderClients.Pg as const, - inputs: { - type: "object" as const, - properties: { - admin_username: { type: "string" as const }, - admin_password: { type: "string" as const }, - host: { type: "string" as const }, - database: { type: "string" as const }, - port: { type: "integer" as const, default: "5432" }, - username1: { - type: "string", - default: "infisical-pg-user1", - desc: "This user must be created in your database" - }, - username2: { - type: "string", - default: "infisical-pg-user2", - desc: "This user must be created in your database" - }, - ca: { type: "string", desc: "SSL certificate for db auth(string)" } - }, - required: ["admin_username", "admin_password", "host", "database", "username1", "username2", "port"], - additionalProperties: false - }, - outputs: { - db_username: { type: "string" }, - db_password: { type: "string" } - } -}; diff --git a/backend/src/ee/services/secret-rotation/templates/sendgrid.ts b/backend/src/ee/services/secret-rotation/templates/sendgrid.ts deleted file mode 100644 index bfdda7606ce..00000000000 --- a/backend/src/ee/services/secret-rotation/templates/sendgrid.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable no-template-curly-in-string */ -import { TAssignOp, TProviderFunctionTypes } from "./types"; - -export const SENDGRID_TEMPLATE = { - type: TProviderFunctionTypes.HTTP as const, - inputs: { - type: "object" as const, - properties: { - admin_api_key: { type: "string" as const, desc: "Sendgrid admin api key to create new keys" }, - api_key_scopes: { - type: "array", - items: { type: "string" as const }, - desc: "Scopes for created tokens by rotation(Array)" - } - }, - required: ["admin_api_key", "api_key_scopes"], - additionalProperties: false - }, - outputs: { - api_key: { type: "string" } - }, - internal: { - api_key_id: { type: "string" } - }, - functions: { - set: { - url: "https://api.sendgrid.com/v3/api_keys", - method: "POST", - header: { - Authorization: "Bearer ${inputs.admin_api_key}" - }, - body: { - name: "infisical-${random | 16}", - scopes: { ref: "inputs.api_key_scopes" } - }, - setter: { - "outputs.api_key": { - assign: TAssignOp.JmesPath as const, - path: "api_key" - }, - "internal.api_key_id": { - assign: TAssignOp.JmesPath as const, - path: "api_key_id" - } - } - }, - remove: { - url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", - header: { - Authorization: "Bearer ${inputs.admin_api_key}" - }, - method: "DELETE" - }, - test: { - url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", - header: { - Authorization: "Bearer ${inputs.admin_api_key}" - }, - method: "GET" - } - } -}; diff --git a/backend/src/ee/services/secret-rotation/templates/types.ts b/backend/src/ee/services/secret-rotation/templates/types.ts deleted file mode 100644 index e2ad8384fe9..00000000000 --- a/backend/src/ee/services/secret-rotation/templates/types.ts +++ /dev/null @@ -1,92 +0,0 @@ -export enum TProviderFunctionTypes { - HTTP = "http", - DB = "database", - AWS = "aws" -} - -export enum TDbProviderClients { - // postgres, cockroack db, amazon red shift - Pg = "pg", - // mysql and maria db - MySql = "mysql", - - MsSqlServer = "mssql", - OracleDB = "oracledb" -} - -export enum TAwsProviderSystems { - IAM = "iam" -} - -export enum TAssignOp { - Direct = "direct", - JmesPath = "jmesopath" -} - -export type TJmesPathAssignOp = { - assign: TAssignOp.JmesPath; - path: string; -}; - -export type TDirectAssignOp = { - assign: TAssignOp.Direct; - value: string; -}; - -export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp; - -export type THttpProviderFunction = { - url: string; - method: string; - header?: Record; - query?: Record; - body?: Record; - setter?: Record; - pre?: Record; -}; - -export type TSecretRotationProviderTemplate = { - name: string; - title: string; - image?: string; - description?: string; - template: THttpProviderTemplate | TDbProviderTemplate | TAwsProviderTemplate; - isDeprecated?: boolean; -}; - -export type THttpProviderTemplate = { - type: TProviderFunctionTypes.HTTP; - inputs: { - type: "object"; - properties: Record; - required?: string[]; - }; - outputs: Record; - functions: { - set: THttpProviderFunction; - remove?: THttpProviderFunction; - test: THttpProviderFunction; - }; -}; - -export type TDbProviderTemplate = { - type: TProviderFunctionTypes.DB; - client: TDbProviderClients; - inputs: { - type: "object"; - properties: Record; - required?: string[]; - }; - outputs: Record; -}; - -export type TAwsProviderTemplate = { - type: TProviderFunctionTypes.AWS; - client: TAwsProviderSystems; - inputs: { - type: "object"; - properties: Record; - required?: string[]; - }; - outputs: Record; -}; diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index 50eb951dcc5..295e840e269 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -54,7 +54,6 @@ import { TWebhookPayloads } from "@app/services/webhook/webhook-types"; export const JOB_SCHEDULER_PREFIX = "jsv1"; export enum QueueName { - SecretRotation = "secret-rotation", SecretReminder = "secret-reminder", AuditLog = "audit-log", // TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue @@ -110,7 +109,6 @@ export enum QueueName { export enum QueueJobs { SecretReminder = "secret-reminder-job", - SecretRotation = "secret-rotation-job", AuditLog = "audit-log-job", // TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue AuditLogPrune = "audit-log-prune-job", @@ -211,10 +209,6 @@ export type TQueueJobTypes = { }; name: QueueJobs.SecretReminder; }; - [QueueName.SecretRotation]: { - payload: { rotationId: string }; - name: QueueJobs.SecretRotation; - }; [QueueName.AuditLog]: { name: QueueJobs.AuditLog; payload: TCreateAuditLogDTO; @@ -649,7 +643,7 @@ export const queueServiceFactory = (redisCfg: TRedisConfigKeys): TQueueServiceFa // Remove orphaned job schedulers left in Redis by the QueueInternalRecovery/QueueInternalReconciliation deleted queues. void (async () => { - const staleQueueNames = ["queue-internal-recovery", "queue-internal-reconciliation"]; + const staleQueueNames = ["queue-internal-recovery", "queue-internal-reconciliation", "secret-rotation"]; await Promise.allSettled( staleQueueNames.map(async (name) => { const staleQueue = new Queue(name, { diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index ad69433edf3..dcd76b4e633 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -165,9 +165,6 @@ import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret import { secretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; import { secretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { secretReplicationServiceFactory } from "@app/ee/services/secret-replication/secret-replication-service"; -import { secretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; -import { secretRotationQueueFactory } from "@app/ee/services/secret-rotation/secret-rotation-queue"; -import { secretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service"; import { secretRotationV2DALFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-dal"; import { secretRotationV2QueueServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-queue"; import { secretRotationV2ServiceFactory } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-service"; @@ -606,7 +603,6 @@ export const registerRoutes = async ( const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db); const secretApprovalRequestSecretDAL = secretApprovalRequestSecretDALFactory(db); - const secretRotationDAL = secretRotationDALFactory(db); const snapshotDAL = snapshotDALFactory(db); const snapshotSecretDAL = snapshotSecretDALFactory(db); const snapshotSecretV2BridgeDAL = snapshotSecretV2DALFactory(db); @@ -1528,7 +1524,6 @@ export const registerRoutes = async ( secretVersionV2BridgeDAL, secretV2BridgeDAL, secretVersionTagV2BridgeDAL, - secretRotationDAL, integrationAuthDAL, snapshotDAL, snapshotSecretV2BridgeDAL, @@ -1820,32 +1815,6 @@ export const registerRoutes = async ( folderCommitService }); - const secretRotationQueue = secretRotationQueueFactory({ - telemetryService, - secretRotationDAL, - queue: queueService, - secretDAL, - secretVersionDAL, - projectBotService, - secretVersionV2BridgeDAL, - secretV2BridgeDAL, - folderCommitService, - kmsService - }); - - const secretRotationService = secretRotationServiceFactory({ - permissionService, - secretRotationDAL, - secretRotationQueue, - projectDAL, - licenseService, - secretDAL, - folderDAL, - projectBotService, - secretV2BridgeDAL, - kmsService - }); - const integrationService = integrationServiceFactory({ permissionService, folderDAL, @@ -3169,7 +3138,6 @@ export const registerRoutes = async ( accessApprovalRequest: accessApprovalRequestService, secretApprovalPolicy: secretApprovalPolicyService, secretApprovalRequest: secretApprovalRequestService, - secretRotation: secretRotationService, dynamicSecret: dynamicSecretService, dynamicSecretLease: dynamicSecretLeaseService, emailDomain: emailDomainService, diff --git a/backend/src/services/offline-usage-report/offline-usage-report-dal.ts b/backend/src/services/offline-usage-report/offline-usage-report-dal.ts index 109d90da838..5a86d3694a8 100644 --- a/backend/src/services/offline-usage-report/offline-usage-report-dal.ts +++ b/backend/src/services/offline-usage-report/offline-usage-report-dal.ts @@ -183,16 +183,14 @@ export const offlineUsageReportDALFactory = (db: TDbClient) => { const getSecretRotationMetrics = async () => { // Check both v1 and v2 secret rotation tables - const [v1RotationsResult, v2RotationsResult] = await Promise.all([ - db.from(TableName.SecretRotation).count("* as count").first() as Promise<{ count: string } | undefined>, - db.from(TableName.SecretRotationV2).count("* as count").first() as Promise<{ count: string } | undefined> - ]); + const v2RotationsResult = (await db.from(TableName.SecretRotationV2).count("* as count").first()) as + | { count: string } + | undefined; - const totalV1Rotations = parseInt(v1RotationsResult?.count || "0", 10); - const totalV2Rotations = parseInt(v2RotationsResult?.count || "0", 10); + const totalSecretRotations = parseInt(v2RotationsResult?.count || "0", 10); return { - totalSecretRotations: totalV1Rotations + totalV2Rotations + totalSecretRotations }; }; diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index 3f5163c76ba..8b6f8aab034 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -18,7 +18,6 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service import { TProjectEventsService } from "@app/ee/services/project-events/project-events-service"; import { ProjectEvents, TProjectEventPayload } from "@app/ee/services/project-events/project-events-types"; import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; -import { TSecretRotationDALFactory } from "@app/ee/services/secret-rotation/secret-rotation-dal"; import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal"; import { TSnapshotSecretV2DALFactory } from "@app/ee/services/secret-snapshot/snapshot-secret-v2-dal"; import { KeyStorePrefixes, KeyStoreTtls, TKeyStoreFactory } from "@app/keystore/keystore"; @@ -113,7 +112,6 @@ type TSecretQueueFactoryDep = { secretV2BridgeDAL: TSecretV2BridgeDALFactory; secretVersionV2BridgeDAL: Pick; secretVersionTagV2BridgeDAL: Pick; - secretRotationDAL: Pick; secretApprovalRequestDAL: Pick; snapshotDAL: Pick; snapshotSecretV2BridgeDAL: Pick; @@ -178,8 +176,8 @@ export const secretQueueFactory = ({ secretVersionV2BridgeDAL, kmsService, secretVersionTagV2BridgeDAL, - secretRotationDAL, snapshotDAL, + snapshotSecretV2BridgeDAL, secretApprovalRequestDAL, keyStore, @@ -1557,17 +1555,6 @@ export const secretQueueFactory = ({ "id", tx ); - /* - * Secret Rotation Secret Migration - * Saving the new encrypted colum - * */ - const projectV1SecretRotations = await secretRotationDAL.find({ projectId }, tx); - await secretRotationDAL.secretOutputV2InsertMany( - projectV1SecretRotations.flatMap((el) => - el.outputs.map((output) => ({ rotationId: el.id, key: output.key, secretId: output.secret.id })) - ), - tx - ); /* * approvals: we will delete all approvals this is because some secret versions may not be added yet diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 4221f193e26..8f6158b7e9f 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -56,7 +56,6 @@ export * from "./secretApprovalRequest"; export * from "./secretFolders"; export * from "./secretImports"; export * from "./secretInsights"; -export * from "./secretRotation"; export * from "./secrets"; export * from "./secretSharing"; export * from "./secretSnapshots"; diff --git a/frontend/src/hooks/api/secretRotation/index.ts b/frontend/src/hooks/api/secretRotation/index.ts deleted file mode 100644 index 906c8b498c2..00000000000 --- a/frontend/src/hooks/api/secretRotation/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { - useCreateSecretRotation, - useDeleteSecretRotation, - useRestartSecretRotation -} from "./mutation"; -export { useGetSecretRotationProviders, useGetSecretRotations } from "./queries"; diff --git a/frontend/src/hooks/api/secretRotation/mutation.tsx b/frontend/src/hooks/api/secretRotation/mutation.tsx deleted file mode 100644 index 6abb206b795..00000000000 --- a/frontend/src/hooks/api/secretRotation/mutation.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useMutation, useQueryClient } from "@tanstack/react-query"; - -import { apiRequest } from "@app/config/request"; - -import { secretRotationKeys } from "./queries"; -import { - TCreateSecretRotationDTO, - TDeleteSecretRotationDTO, - TRestartSecretRotationDTO -} from "./types"; - -export const useCreateSecretRotation = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async (dto) => { - const { data } = await apiRequest.post("/api/v1/secret-rotations", dto); - return data; - }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: secretRotationKeys.list({ workspaceId }) }); - } - }); -}; - -export const useDeleteSecretRotation = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async (dto) => { - const { data } = await apiRequest.delete(`/api/v1/secret-rotations/${dto.id}`); - return data; - }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: secretRotationKeys.list({ workspaceId }) }); - } - }); -}; - -export const useRestartSecretRotation = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async (dto) => { - const { data } = await apiRequest.post("/api/v1/secret-rotations/restart", { id: dto.id }); - return data; - }, - onSuccess: (_, { workspaceId }) => { - queryClient.invalidateQueries({ queryKey: secretRotationKeys.list({ workspaceId }) }); - } - }); -}; diff --git a/frontend/src/hooks/api/secretRotation/queries.tsx b/frontend/src/hooks/api/secretRotation/queries.tsx deleted file mode 100644 index d612189ad72..00000000000 --- a/frontend/src/hooks/api/secretRotation/queries.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { useQuery, UseQueryOptions } from "@tanstack/react-query"; - -import { apiRequest } from "@app/config/request"; - -import { - TGetSecretRotationListDTO, - TGetSecretRotationProviders, - TSecretRotation, - TSecretRotationProviderList -} from "./types"; - -export const secretRotationKeys = { - listProviders: ({ workspaceId }: TGetSecretRotationProviders) => [ - { workspaceId }, - "secret-rotation-providers" - ], - list: ({ workspaceId }: Omit) => - [{ workspaceId }, "secret-rotations"] as const -}; - -const fetchSecretRotationProviders = async ({ workspaceId }: TGetSecretRotationProviders) => { - const { data } = await apiRequest.get( - `/api/v1/secret-rotation-providers/${workspaceId}` - ); - return data; -}; - -export const useGetSecretRotationProviders = ({ - workspaceId, - options = {} -}: TGetSecretRotationProviders & { - options?: Omit< - UseQueryOptions< - TSecretRotationProviderList, - unknown, - TSecretRotationProviderList, - ReturnType - >, - "queryKey" | "queryFn" - >; -}) => - useQuery({ - ...options, - queryKey: secretRotationKeys.listProviders({ workspaceId }), - enabled: Boolean(workspaceId) && (options?.enabled ?? true), - queryFn: async () => fetchSecretRotationProviders({ workspaceId }) - }); - -const fetchSecretRotations = async ({ - workspaceId -}: Omit) => { - const { data } = await apiRequest.get<{ secretRotations: TSecretRotation[] }>( - "/api/v1/secret-rotations", - { params: { workspaceId } } - ); - return data.secretRotations; -}; - -export const useGetSecretRotations = ({ - workspaceId, - options = {} -}: TGetSecretRotationListDTO & { - options?: Omit< - UseQueryOptions< - TSecretRotation[], - unknown, - TSecretRotation[], - ReturnType - >, - "queryKey" | "queryFn" - >; -}) => - useQuery({ - ...options, - queryKey: secretRotationKeys.list({ workspaceId }), - enabled: Boolean(workspaceId) && (options?.enabled ?? true), - queryFn: async () => fetchSecretRotations({ workspaceId }) - }); diff --git a/frontend/src/hooks/api/secretRotation/types.ts b/frontend/src/hooks/api/secretRotation/types.ts deleted file mode 100644 index c5f939c5c67..00000000000 --- a/frontend/src/hooks/api/secretRotation/types.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { ProjectEnv } from "../projects/types"; - -export enum TProviderFunctionTypes { - HTTP = "http", - DB = "database" -} - -export enum TDbProviderClients { - // postgres, cockroack db, amazon red shift - Pg = "pg", - // mysql and maria db - Sql = "sql" -} - -export enum TAssignOp { - Direct = "direct", - JmesPath = "jmesopath" -} - -export type TJmesPathAssignOp = { - assign: TAssignOp.JmesPath; - path: string; -}; - -export type TDirectAssignOp = { - assign: TAssignOp.Direct; - value: string; -}; - -export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp; - -export type THttpProviderFunction = { - url: string; - method: string; - header?: Record; - query?: Record; - body?: Record; - setter?: Record; - pre?: Record; -}; - -export type TSecretRotationProviderTemplate = { - name: string; - title: string; - image?: string; - description?: string; - template: THttpProviderTemplate | TDbProviderTemplate; - isDeprecated?: boolean; -}; - -export type THttpProviderTemplate = { - type: TProviderFunctionTypes.HTTP; - inputs: { - type: "object"; - properties: Record; - required: string[]; - }; - outputs: Record; - functions: { - set: THttpProviderFunction; - remove?: THttpProviderFunction; - test: THttpProviderFunction; - }; -}; - -export type TDbProviderTemplate = { - type: TProviderFunctionTypes.DB; - inputs: { - type: "object"; - properties: Record; - required: string[]; - }; - outputs: Record; -}; - -export type TSecretRotation = { - id: string; - interval: number; - provider: string; - customProvider: string; - workspace: string; - envId: string; - environment: ProjectEnv; - secretPath: string; - outputs: Array<{ - key: string; - secret: { - version: number; - id: string; - secretKey: string; - }; - }>; - status?: "success" | "failed"; - lastRotatedAt?: string; - statusMessage?: string; - algorithm: string; - keyEncoding: string; -}; - -export type TSecretRotationProviderList = { - custom: TSecretRotationProviderTemplate[]; - providers: TSecretRotationProviderTemplate[]; -}; - -export type TGetSecretRotationProviders = { - workspaceId: string; -}; - -export type TGetSecretRotationListDTO = { - workspaceId: string; -}; - -export type TCreateSecretRotationDTO = { - workspaceId: string; - secretPath: string; - environment: string; - interval: number; - provider: string; - customProvider?: string; - inputs: Record; - outputs: Record; -}; - -export type TDeleteSecretRotationDTO = { - id: string; - workspaceId: string; -}; - -export type TRestartSecretRotationDTO = { - id: string; - workspaceId: string; -}; diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts index 4b73eba28b2..6389421590c 100644 --- a/frontend/src/hooks/api/types.ts +++ b/frontend/src/hooks/api/types.ts @@ -29,11 +29,6 @@ export type { export { ApprovalStatus, CommitType } from "./secretApprovalRequest/types"; export type { TSecretFolder } from "./secretFolders/types"; export type { TImportedSecrets, TSecretImport } from "./secretImports/types"; -export type { - TGetSecretRotationProviders, - TSecretRotationProviderList, - TSecretRotationProviderTemplate -} from "./secretRotation/types"; export * from "./secrets/types"; export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types"; export type { SubscriptionPlan } from "./subscriptions/types"; diff --git a/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/SecretManagerNav.tsx b/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/SecretManagerNav.tsx index 7a6f21392af..981e694c7d6 100644 --- a/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/SecretManagerNav.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/OrgSidebar/SecretManagerNav.tsx @@ -1,8 +1,6 @@ -import { ActivityIcon, BookCheck, FileText, Plug, RefreshCw, Settings, Shield } from "lucide-react"; +import { ActivityIcon, BookCheck, FileText, Plug, Settings, Shield } from "lucide-react"; import { ProjectIcon } from "@app/components/v3"; -import { useProject } from "@app/context"; -import { useGetSecretRotations } from "@app/hooks/api"; import { ProjectNavList } from "./ProjectNavLink"; import { @@ -18,13 +16,7 @@ export const SecretManagerNav = ({ }: { onSubmenuOpen: (submenu: Submenu) => void; }) => { - const { projectId } = useProject(); - const { submenu: approvalsSubmenu, pendingRequestsCount } = useApprovalSubmenu(); - const { data: secretRotations } = useGetSecretRotations({ - workspaceId: projectId, - options: { refetchOnMount: false } - }); const items: NavItem[] = [ { @@ -46,13 +38,6 @@ export const SecretManagerNav = ({ pathSuffix: "integrations", submenu: INTEGRATIONS_SUBMENU }, - { - label: "Secret Rotations", - icon: RefreshCw, - pathSuffix: "secret-rotation", - hidden: !secretRotations?.length - }, - { label: "Access Control", icon: Shield, diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx deleted file mode 100644 index e853092ad42..00000000000 --- a/frontend/src/pages/secret-manager/SecretRotationPage/SecretRotationPage.tsx +++ /dev/null @@ -1,457 +0,0 @@ -import { Helmet } from "react-helmet"; -import { useTranslation } from "react-i18next"; -import { subject } from "@casl/ability"; -import { - faArrowsSpin, - faArrowUpRightFromSquare, - faExclamationTriangle, - faFolder, - faInfoCircle, - faPlus, - faRotate, - faTrash -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link, useNavigate } from "@tanstack/react-router"; -import { formatDistance } from "date-fns"; - -import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; -import { createNotification } from "@app/components/notifications"; -import { ProjectPermissionCan } from "@app/components/permissions"; -import { - Button, - DeleteActionModal, - EmptyState, - IconButton, - Modal, - ModalContent, - PageHeader, - Skeleton, - Spinner, - Table, - TableContainer, - TableSkeleton, - TBody, - Td, - Th, - THead, - Tooltip, - Tr -} from "@app/components/v2"; -import { NoticeBannerV2 } from "@app/components/v2/NoticeBannerV2/NoticeBannerV2"; -import { - ProjectPermissionSub, - useOrganization, - useProject, - useProjectPermission, - useSubscription -} from "@app/context"; -import { ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; -import { usePopUp } from "@app/hooks"; -import { - useDeleteSecretRotation, - useGetSecretRotationProviders, - useGetSecretRotations, - useRestartSecretRotation -} from "@app/hooks/api"; -import { ProjectType } from "@app/hooks/api/projects/types"; -import { TSecretRotationProviderTemplate } from "@app/hooks/api/secretRotation/types"; -import { CreateRotationForm } from "@app/pages/secret-manager/SecretRotationPage/components/CreateRotationForm"; - -const Page = () => { - const { currentOrg } = useOrganization(); - const { currentProject } = useProject(); - const { permission } = useProjectPermission(); - - const navigate = useNavigate(); - - const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ - "createRotation", - "activeBot", - "deleteRotation", - "upgradePlan", - "secretRotationV2" - ] as const); - const workspaceId = currentProject?.id || ""; - const canCreateRotation = permission.can( - ProjectPermissionSecretRotationActions.Create, - ProjectPermissionSub.SecretRotation - ); - const { subscription } = useSubscription(); - - const { data: secretRotationProviders, isPending: isRotationProviderLoading } = - useGetSecretRotationProviders({ workspaceId }); - const { data: secretRotations, isPending: isRotationLoading } = useGetSecretRotations({ - workspaceId - }); - - const { - mutateAsync: deleteSecretRotation, - variables: deleteSecretRotationVars, - isPending: isDeletingRotation - } = useDeleteSecretRotation(); - const { - mutateAsync: restartSecretRotation, - variables: restartSecretRotationVar, - isPending: isRestartingRotation - } = useRestartSecretRotation(); - - const handleDeleteRotation = async () => { - const { id } = popUp.deleteRotation.data as { id: string }; - await deleteSecretRotation({ - id, - workspaceId - }); - handlePopUpClose("deleteRotation"); - createNotification({ - type: "success", - text: "Successfully removed rotation" - }); - }; - - const handleRestartRotation = async (id: string) => { - await restartSecretRotation({ - id, - workspaceId - }); - createNotification({ - type: "success", - text: "Secret rotation initiated" - }); - }; - - const handleCreateRotation = (provider: TSecretRotationProviderTemplate) => { - if (subscription && !subscription?.secretRotation) { - handlePopUpOpen("upgradePlan"); - return; - } - if (!canCreateRotation) { - createNotification({ type: "error", text: "Access permission denied!!" }); - return; - } - handlePopUpOpen("createRotation", provider); - }; - - return ( -
- - - - Documentation - - - - - -

- Infisical is revamping its Secret Rotation experience. -

-

- PostgreSQL and Microsoft SQL Server Rotations can now be created from the{" "} - - Secret Manager Dashboard - {" "} - from the actions dropdown. -

-
-
-
Rotated Secrets
-
- - - - - - - - - - - - - - {isRotationLoading && ( - - )} - {!isRotationLoading && secretRotations?.length === 0 && ( - - - - )} - {secretRotations?.map( - ({ - environment, - secretPath, - outputs, - provider, - id, - lastRotatedAt, - status, - statusMessage - }) => { - const isDeleting = deleteSecretRotationVars?.id === id && isDeletingRotation; - const isRestarting = - restartSecretRotationVar?.id === id && isRestartingRotation; - return ( - - - - - - - - - ); - } - )} - -
Secret NameEnvironmentProviderStatusLast RotationAction
- -
- {outputs - .map(({ key }) => key) - .join(",") - .toUpperCase()} - -
-
{environment.slug}
-
- - {secretPath} -
-
-
{provider} -
- {status} - {status === "failed" && ( - - - - )} -
-
- {lastRotatedAt - ? formatDistance(new Date(lastRotatedAt), new Date()) - : "-"} - -
- - {(isAllowed) => ( - handleRestartRotation(id)} - > - {isRestarting ? ( - - ) : ( - - )} - - )} - - - {(isAllowed) => ( - handlePopUpOpen("deleteRotation", { id })} - > - {isDeleting ? ( - - ) : ( - - )} - - )} - -
-
-
-
-
-
- Infisical Rotation Providers -
-
- {isRotationProviderLoading && - Array.from({ length: 12 }).map((_, index) => ( - - ))} - {!isRotationProviderLoading && - secretRotationProviders?.providers.map((provider) => ( -
{ - if (evt.key !== "Enter") return; - if (provider.isDeprecated) { - handlePopUpOpen("secretRotationV2", provider.title); - } else { - handleCreateRotation(provider); - } - }} - onClick={() => { - if (provider.isDeprecated) { - handlePopUpOpen("secretRotationV2", provider.title); - } else { - handleCreateRotation(provider); - } - }} - > - rotation provider logo -
- {provider.title} -
-
- - - -
-
- ))} - -
- -
- Request or create your own template -
-
-
-
- handlePopUpToggle("createRotation", isOpen)} - provider={(popUp.createRotation.data as TSecretRotationProviderTemplate) || {}} - /> - handlePopUpToggle("deleteRotation", isOpen)} - deleteKey="delete" - onDeleteApproved={handleDeleteRotation} - /> - handlePopUpToggle("upgradePlan", isOpen)} - text="Adding secret rotations can be unlocked if you upgrade to Infisical Pro plan." - /> - handlePopUpToggle("secretRotationV2", isOpen)} - > - -
-

- Infisical is revamping its Secret Rotation experience. Navigate to the{" "} - - Secret Manager Dashboard - {" "} - to create a {popUp.secretRotationV2.data} Rotation. -

-
- Secret Rotation V2 location -
-
- -
-
-
-
-
- ); -}; -export const SecretRotationPage = () => { - const { t } = useTranslation(); - - return ( -
- - {t("common.head-title", { title: t("settings.project.title") })} - - - - - - -
- ); -}; diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx deleted file mode 100644 index cfc34df2cb5..00000000000 --- a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { useRef, useState } from "react"; -import { AnimatePresence, motion } from "framer-motion"; - -import { Modal, ModalContent, Step, Stepper } from "@app/components/v2"; -import { useCreateSecretRotation } from "@app/hooks/api"; -import { TSecretRotationProviderTemplate } from "@app/hooks/api/types"; - -import { RotationInputForm } from "./steps/RotationInputForm"; -import { - RotationOutputForm, - TFormSchema as TRotationOutputSchema -} from "./steps/RotationOutputForm"; - -const WIZARD_STEPS = [ - { - title: "Inputs", - description: "Provider secrets" - }, - { - title: "Outputs", - description: "Map rotated secrets to keys" - } -]; - -type Props = { - isOpen?: boolean; - onToggle: (isOpen: boolean) => void; - customProvider?: string; - workspaceId: string; - provider: TSecretRotationProviderTemplate; -}; - -export const CreateRotationForm = ({ - isOpen, - onToggle, - provider, - workspaceId, - customProvider -}: Props) => { - const [wizardStep, setWizardStep] = useState(0); - const wizardData = useRef<{ - input?: Record; - output?: TRotationOutputSchema; - }>({}); - - const { mutateAsync: createSecretRotation } = useCreateSecretRotation(); - - const handleFormCancel = () => { - onToggle(false); - setWizardStep(0); - wizardData.current = {}; - }; - - const handleFormSubmit = async () => { - if (!wizardData.current.input || !wizardData.current.output) return; - await createSecretRotation({ - workspaceId, - provider: provider.name, - customProvider, - secretPath: wizardData.current.output.secretPath, - environment: wizardData.current.output.environment, - interval: wizardData.current.output.interval, - inputs: wizardData.current.input, - outputs: wizardData.current.output.secrets - }); - setWizardStep(0); - onToggle(false); - wizardData.current = {}; - }; - - return ( - { - onToggle(state); - setWizardStep(0); - wizardData.current = {}; - }} - > - - - {WIZARD_STEPS.map(({ title, description }, index) => ( - - ))} - - - {wizardStep === 0 && ( - - { - wizardData.current.input = data; - setWizardStep((state) => state + 1); - }} - inputSchema={provider.template?.inputs || {}} - /> - - )} - {wizardStep === 1 && ( - - { - wizardData.current.output = data; - await handleFormSubmit(); - }} - /> - - )} - - - - ); -}; diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/index.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/index.tsx deleted file mode 100644 index 8392c4cdb12..00000000000 --- a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { CreateRotationForm } from "./CreateRotationForm"; diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx deleted file mode 100644 index 3b336a750ca..00000000000 --- a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Controller, useForm } from "react-hook-form"; -import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; - -import { Button, FormControl, FormLabel, SecretInput, Tooltip } from "@app/components/v2"; - -type Props = { - onSubmit: (data: Record) => void; - onCancel: () => void; - inputSchema: { - properties: Record; - required: string[]; - }; -}; - -const formSchema = z.record(z.string().trim().optional()); - -export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) => { - const { - control, - handleSubmit, - formState: { isSubmitting } - } = useForm>({ - resolver: zodResolver(formSchema) - }); - - return ( -
- {Object.keys(inputSchema.properties || {}).map((inputName) => ( - ( - - - {Boolean(inputSchema.properties[inputName]?.desc) && ( - - - - )} - - } - > - - - )} - /> - ))} -
- - -
- - ); -}; diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx deleted file mode 100644 index 41236502812..00000000000 --- a/frontend/src/pages/secret-manager/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { Controller, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; - -import { Button, FormControl, Input, Select, SelectItem, Spinner } from "@app/components/v2"; -import { SecretPathInput } from "@app/components/v2/SecretPathInput"; -import { useProject } from "@app/context"; -import { useGetProjectSecrets } from "@app/hooks/api"; - -const formSchema = z.object({ - environment: z.string().trim(), - secretPath: z.string().trim().default("/"), - interval: z.number().min(1), - secrets: z.record(z.string()) -}); - -export type TFormSchema = z.infer; -type Props = { - outputSchema: Record; - onSubmit: (data: TFormSchema) => void; - onCancel: () => void; -}; - -export const RotationOutputForm = ({ onSubmit, onCancel, outputSchema = {} }: Props) => { - const { currentProject } = useProject(); - const environments = currentProject?.environments || []; - const projectId = currentProject?.id || ""; - const { - control, - handleSubmit, - watch, - formState: { isSubmitting } - } = useForm({ - resolver: zodResolver(formSchema) - }); - - const environment = watch("environment", environments?.[0]?.slug); - const secretPath = watch("secretPath"); - const selectedSecrets = watch("secrets"); - - const { data: secrets, isPending: isSecretsLoading } = useGetProjectSecrets({ - projectId, - environment, - secretPath - }); - - return ( -
- ( - - - - )} - /> - ( - - - - )} - /> - ( - - field.onChange(parseInt(evt.target.value, 10))} - /> - - )} - /> -
-
Mapping
-
Select keys for rotated value to get saved
-
- {Object.keys(outputSchema).map((outputName) => ( - ( - - - - )} - /> - ))} -
- - -
- - ); -}; diff --git a/frontend/src/pages/secret-manager/SecretRotationPage/route.tsx b/frontend/src/pages/secret-manager/SecretRotationPage/route.tsx deleted file mode 100644 index 9add977843a..00000000000 --- a/frontend/src/pages/secret-manager/SecretRotationPage/route.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; - -import { SecretRotationPage } from "./SecretRotationPage"; - -export const Route = createFileRoute( - "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation" -)({ - component: SecretRotationPage, - beforeLoad: ({ context }) => { - return { - breadcrumbs: [ - ...context.breadcrumbs, - { - label: "Secret Rotation" - } - ] - }; - } -}); diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index f4432478fcc..9b31bc789bf 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -108,7 +108,6 @@ import { Route as sshSshCasPageRouteImport } from './pages/ssh/SshCasPage/route' import { Route as secretScanningSettingsPageRouteImport } from './pages/secret-scanning/SettingsPage/route' import { Route as secretScanningSecretScanningFindingsPageRouteImport } from './pages/secret-scanning/SecretScanningFindingsPage/route' import { Route as secretManagerSettingsPageRouteImport } from './pages/secret-manager/SettingsPage/route' -import { Route as secretManagerSecretRotationPageRouteImport } from './pages/secret-manager/SecretRotationPage/route' import { Route as secretManagerOverviewPageRouteImport } from './pages/secret-manager/OverviewPage/route' import { Route as secretManagerInsightsPageRouteImport } from './pages/secret-manager/InsightsPage/route' import { Route as secretManagerSecretApprovalsPageRouteImport } from './pages/secret-manager/SecretApprovalsPage/route' @@ -1306,13 +1305,6 @@ const secretManagerSettingsPageRouteRoute = getParentRoute: () => secretManagerLayoutRoute, } as any) -const secretManagerSecretRotationPageRouteRoute = - secretManagerSecretRotationPageRouteImport.update({ - id: '/secret-rotation', - path: '/secret-rotation', - getParentRoute: () => secretManagerLayoutRoute, - } as any) - const secretManagerOverviewPageRouteRoute = secretManagerOverviewPageRouteImport.update({ id: '/overview', @@ -3331,13 +3323,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof secretManagerOverviewPageRouteImport parentRoute: typeof secretManagerLayoutImport } - '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation': { - id: '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation' - path: '/secret-rotation' - fullPath: '/organizations/$orgId/projects/secret-management/$projectId/secret-rotation' - preLoaderRoute: typeof secretManagerSecretRotationPageRouteImport - parentRoute: typeof secretManagerLayoutImport - } '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/settings': { id: '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/settings' path: '/settings' @@ -5415,7 +5400,6 @@ interface secretManagerLayoutRouteChildren { secretManagerSecretApprovalsPageRouteRoute: typeof secretManagerSecretApprovalsPageRouteRoute secretManagerInsightsPageRouteRoute: typeof secretManagerInsightsPageRouteRoute secretManagerOverviewPageRouteRoute: typeof secretManagerOverviewPageRouteRoute - secretManagerSecretRotationPageRouteRoute: typeof secretManagerSecretRotationPageRouteRoute secretManagerSettingsPageRouteRoute: typeof secretManagerSettingsPageRouteRoute projectAccessControlPageRouteSecretManagerRoute: typeof projectAccessControlPageRouteSecretManagerRoute projectAppConnectionsPageRouteSecretManagerRoute: typeof projectAppConnectionsPageRouteSecretManagerRoute @@ -5436,8 +5420,6 @@ const secretManagerLayoutRouteChildren: secretManagerLayoutRouteChildren = { secretManagerSecretApprovalsPageRouteRoute, secretManagerInsightsPageRouteRoute: secretManagerInsightsPageRouteRoute, secretManagerOverviewPageRouteRoute: secretManagerOverviewPageRouteRoute, - secretManagerSecretRotationPageRouteRoute: - secretManagerSecretRotationPageRouteRoute, secretManagerSettingsPageRouteRoute: secretManagerSettingsPageRouteRoute, projectAccessControlPageRouteSecretManagerRoute: projectAccessControlPageRouteSecretManagerRoute, @@ -6039,7 +6021,6 @@ export interface FileRoutesByFullPath { '/organizations/$orgId/projects/secret-management/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute '/organizations/$orgId/projects/secret-management/$projectId/insights': typeof secretManagerInsightsPageRouteRoute '/organizations/$orgId/projects/secret-management/$projectId/overview': typeof secretManagerOverviewPageRouteRoute - '/organizations/$orgId/projects/secret-management/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute '/organizations/$orgId/projects/secret-management/$projectId/settings': typeof secretManagerSettingsPageRouteRoute '/organizations/$orgId/projects/secret-scanning/$projectId/findings': typeof secretScanningSecretScanningFindingsPageRouteRoute '/organizations/$orgId/projects/secret-scanning/$projectId/settings': typeof secretScanningSettingsPageRouteRoute @@ -6313,7 +6294,6 @@ export interface FileRoutesByTo { '/organizations/$orgId/projects/secret-management/$projectId/approval': typeof secretManagerSecretApprovalsPageRouteRoute '/organizations/$orgId/projects/secret-management/$projectId/insights': typeof secretManagerInsightsPageRouteRoute '/organizations/$orgId/projects/secret-management/$projectId/overview': typeof secretManagerOverviewPageRouteRoute - '/organizations/$orgId/projects/secret-management/$projectId/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute '/organizations/$orgId/projects/secret-management/$projectId/settings': typeof secretManagerSettingsPageRouteRoute '/organizations/$orgId/projects/secret-scanning/$projectId/findings': typeof secretScanningSecretScanningFindingsPageRouteRoute '/organizations/$orgId/projects/secret-scanning/$projectId/settings': typeof secretScanningSettingsPageRouteRoute @@ -6589,7 +6569,6 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/approval': typeof secretManagerSecretApprovalsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/insights': typeof secretManagerInsightsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/overview': typeof secretManagerOverviewPageRouteRoute - '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation': typeof secretManagerSecretRotationPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/settings': typeof secretManagerSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-scanning/$projectId/_secret-scanning-layout/findings': typeof secretScanningSecretScanningFindingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-scanning/$projectId/_secret-scanning-layout/settings': typeof secretScanningSettingsPageRouteRoute @@ -6872,7 +6851,6 @@ export interface FileRouteTypes { | '/organizations/$orgId/projects/secret-management/$projectId/approval' | '/organizations/$orgId/projects/secret-management/$projectId/insights' | '/organizations/$orgId/projects/secret-management/$projectId/overview' - | '/organizations/$orgId/projects/secret-management/$projectId/secret-rotation' | '/organizations/$orgId/projects/secret-management/$projectId/settings' | '/organizations/$orgId/projects/secret-scanning/$projectId/findings' | '/organizations/$orgId/projects/secret-scanning/$projectId/settings' @@ -7145,7 +7123,6 @@ export interface FileRouteTypes { | '/organizations/$orgId/projects/secret-management/$projectId/approval' | '/organizations/$orgId/projects/secret-management/$projectId/insights' | '/organizations/$orgId/projects/secret-management/$projectId/overview' - | '/organizations/$orgId/projects/secret-management/$projectId/secret-rotation' | '/organizations/$orgId/projects/secret-management/$projectId/settings' | '/organizations/$orgId/projects/secret-scanning/$projectId/findings' | '/organizations/$orgId/projects/secret-scanning/$projectId/settings' @@ -7419,7 +7396,6 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/approval' | '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/insights' | '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/overview' - | '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation' | '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/settings' | '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-scanning/$projectId/_secret-scanning-layout/findings' | '/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-scanning/$projectId/_secret-scanning-layout/settings' @@ -8179,7 +8155,6 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/approval", "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/insights", "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/overview", - "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation", "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/settings", "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/access-management", "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/app-connections", @@ -8299,10 +8274,6 @@ export const routeTree = rootRoute "filePath": "secret-manager/OverviewPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout" }, - "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/secret-rotation": { - "filePath": "secret-manager/SecretRotationPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout" - }, "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout/settings": { "filePath": "secret-manager/SettingsPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organizations/$orgId/projects/secret-management/$projectId/_secret-manager-layout" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index eb3af05f987..70029338bbb 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -22,7 +22,6 @@ const secretManagerRoutes = route("/organizations/$orgId/projects/secret-managem route("/secrets/$envSlug", "secret-manager/SecretDashboardPage/route.tsx"), route("/allowlist", "secret-manager/IPAllowlistPage/route.tsx"), route("/approval", "secret-manager/SecretApprovalsPage/route.tsx"), - route("/secret-rotation", "secret-manager/SecretRotationPage/route.tsx"), route("/insights", "secret-manager/InsightsPage/route.tsx"), route("/settings", "secret-manager/SettingsPage/route.tsx"), route("/commits/$environment/$folderId", [