diff --git a/backend/src/db/migrations/20240716105646_secret-v2.ts b/backend/src/db/migrations/20240716105646_secret-v2.ts index 4aae70513..e2c0e976f 100644 --- a/backend/src/db/migrations/20240716105646_secret-v2.ts +++ b/backend/src/db/migrations/20240716105646_secret-v2.ts @@ -115,6 +115,22 @@ export async function up(knex: Knex): Promise { t.timestamps(true, true, true); }); } + + if (await knex.schema.hasTable(TableName.IntegrationAuth)) { + const hasEncryptedAccess = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccess"); + const hasEncryptedAccessId = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccessId"); + const hasEncryptedRefresh = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedRefresh"); + const hasEncryptedAwsIamAssumRole = await knex.schema.hasColumn( + TableName.IntegrationAuth, + "encryptedAwsAssumeIamRoleArn" + ); + await knex.schema.alterTable(TableName.IntegrationAuth, (t) => { + if (!hasEncryptedAccess) t.binary("encryptedAccess"); + if (!hasEncryptedAccessId) t.binary("encryptedAccessId"); + if (!hasEncryptedRefresh) t.binary("encryptedRefresh"); + if (!hasEncryptedAwsIamAssumRole) t.binary("hasEncryptedAwsIamAssumRole"); + }); + } } export async function down(knex: Knex): Promise { @@ -131,4 +147,20 @@ export async function down(knex: Knex): Promise { await dropOnUpdateTrigger(knex, TableName.SecretVersionV2); await knex.schema.dropTableIfExists(TableName.SecretVersionV2Tag); await knex.schema.dropTableIfExists(TableName.SecretVersionV2); + + if (await knex.schema.hasTable(TableName.IntegrationAuth)) { + const hasEncryptedAccess = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccess"); + const hasEncryptedAccessId = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccessId"); + const hasEncryptedRefresh = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedRefresh"); + const hasEncryptedAwsIamAssumRole = await knex.schema.hasColumn( + TableName.IntegrationAuth, + "encryptedAwsAssumeIamRoleArn" + ); + await knex.schema.alterTable(TableName.IntegrationAuth, (t) => { + if (hasEncryptedAccess) t.dropColumn("encryptedAccess"); + if (hasEncryptedAccessId) t.dropColumn("encryptedAccessId"); + if (hasEncryptedRefresh) t.dropColumn("encryptedRefresh"); + if (hasEncryptedAwsIamAssumRole) t.dropColumn("hasEncryptedAwsIamAssumRole"); + }); + } } diff --git a/backend/src/db/schemas/integration-auths.ts b/backend/src/db/schemas/integration-auths.ts index 0a980edc8..709d3b1ef 100644 --- a/backend/src/db/schemas/integration-auths.ts +++ b/backend/src/db/schemas/integration-auths.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const IntegrationAuthsSchema = z.object({ @@ -32,7 +34,11 @@ export const IntegrationAuthsSchema = z.object({ updatedAt: z.date(), awsAssumeIamRoleArnCipherText: z.string().nullable().optional(), awsAssumeIamRoleArnIV: z.string().nullable().optional(), - awsAssumeIamRoleArnTag: z.string().nullable().optional() + awsAssumeIamRoleArnTag: z.string().nullable().optional(), + encryptedAccess: zodBuffer.nullable().optional(), + encryptedAccessId: zodBuffer.nullable().optional(), + encryptedRefresh: zodBuffer.nullable().optional(), + hasEncryptedAwsIamAssumRole: zodBuffer.nullable().optional() }); export type TIntegrationAuths = z.infer; diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts index b7174b96c..998bc4bcf 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts @@ -16,6 +16,7 @@ export type TSecretApprovalRequestSecretDALFactory = ReturnType { const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret); const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag); + const secretApprovalRequestSecretV2TagOrm = ormify(db, TableName.SecretApprovalRequestSecretTagV2); const secretApprovalRequestSecretV2Orm = ormify(db, TableName.SecretApprovalRequestSecretV2); const bulkUpdateNoVersionIncrement = async (data: TSecretApprovalRequestsSecrets[], tx?: Knex) => { @@ -359,6 +360,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { findByRequestId, findByRequestIdBridgeSecretV2, bulkUpdateNoVersionIncrement, - insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany + insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany, + insertApprovalSecretV2Tags: secretApprovalRequestSecretV2TagOrm.insertMany }; }; diff --git a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts index db805df8d..7d2197a58 100644 --- a/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -15,6 +15,7 @@ import { groupBy, pick, unique } from "@app/lib/fn"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { EnforcementLevel } from "@app/lib/types"; import { ActorType } from "@app/services/auth/auth-type"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; @@ -28,13 +29,6 @@ import { fnSecretBulkUpdate, getAllNestedSecretReferences } from "@app/services/secret/secret-fns"; -import { - fnSecretBulkInsert as fnSecretV2BridgeBulkInsert, - fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate, - fnSecretBulkDelete as fnSecretV2BridgeBulkDelete, - getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge, - secretEncryptionHelper -} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns"; import { TSecretQueueFactory } from "@app/services/secret/secret-queue"; import { SecretOperations } from "@app/services/secret/secret-types"; import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal"; @@ -44,6 +38,16 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { TUserDALFactory } from "@app/services/user/user-dal"; +import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal"; +import { + fnSecretBulkDelete as fnSecretV2BridgeBulkDelete, + fnSecretBulkInsert as fnSecretV2BridgeBulkInsert, + fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate, + getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge, + secretEncryptionHelper +} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns"; +import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; +import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission"; @@ -63,10 +67,6 @@ import { TSecretApprovalDetailsDTO, TStatusChangeDTO } from "./secret-approval-request-types"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal"; -import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; -import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; type TSecretApprovalRequestServiceFactoryDep = { permissionService: Pick; @@ -730,7 +730,7 @@ export const secretApprovalRequestServiceFactory = ({ }); } - await snapshotService.performSnapshot(folderId, shouldUseSecretV2Bridge); + await snapshotService.performSnapshot(folderId); const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [folderId]); if (!folder) throw new BadRequestError({ message: "Folder not found" }); await secretQueueService.syncSecrets({ diff --git a/backend/src/ee/services/secret-replication/secret-replication-service.ts b/backend/src/ee/services/secret-replication/secret-replication-service.ts index dc165612c..52d10b690 100644 --- a/backend/src/ee/services/secret-replication/secret-replication-service.ts +++ b/backend/src/ee/services/secret-replication/secret-replication-service.ts @@ -10,15 +10,11 @@ import { logger } from "@app/lib/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { QueueName, TQueueServiceFactory } from "@app/queue"; import { ActorType } from "@app/services/auth/auth-type"; +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 { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns"; -import { - fnSecretBulkInsert as fnSecretV2BridgeBulkInsert, - fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate, - getAllNestedSecretReferences, - getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge -} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns"; import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue"; import { SecretOperations } from "@app/services/secret/secret-types"; import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal"; @@ -28,13 +24,17 @@ import { ReservedFolders } from "@app/services/secret-folder/secret-folder-types import { TSecretImportDALFactory } from "@app/services/secret-import/secret-import-dal"; import { fnSecretsFromImports, fnSecretsV2FromImports } from "@app/services/secret-import/secret-import-fns"; import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; - -import { MAX_REPLICATION_DEPTH } from "./secret-replication-constants"; import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal"; +import { + fnSecretBulkInsert as fnSecretV2BridgeBulkInsert, + fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate, + getAllNestedSecretReferences, + getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge +} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns"; import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { MAX_REPLICATION_DEPTH } from "./secret-replication-constants"; type TSecretReplicationServiceFactoryDep = { secretDAL: Pick< 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 index b10873e5b..11b6c34af 100644 --- 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 @@ -17,9 +17,13 @@ import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; +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"; @@ -39,10 +43,6 @@ import { secretRotationPreSetFn } from "./secret-rotation-queue-fn"; import { TSecretRotationData, TSecretRotationDbFn, TSecretRotationEncData } from "./secret-rotation-queue-types"; -import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal"; -import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; export type TSecretRotationQueueFactory = ReturnType; diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 8487d9d8c..39d3a0997 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -10,6 +10,9 @@ import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal"; import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-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 { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; import { TLicenseServiceFactory } from "../license/license-service"; import { TPermissionServiceFactory } from "../permission/permission-service"; @@ -23,11 +26,8 @@ import { import { TSnapshotDALFactory } from "./snapshot-dal"; import { TSnapshotFolderDALFactory } from "./snapshot-folder-dal"; import { TSnapshotSecretDALFactory } from "./snapshot-secret-dal"; -import { getFullFolderPath } from "./snapshot-service-fns"; -import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal"; import { TSnapshotSecretV2DALFactory } from "./snapshot-secret-v2-dal"; -import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal"; -import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal"; +import { getFullFolderPath } from "./snapshot-service-fns"; type TSecretSnapshotServiceFactoryDep = { snapshotDAL: TSnapshotDALFactory; @@ -167,14 +167,15 @@ export const secretSnapshotServiceFactory = ({ return snapshotDetails; }; - const performSnapshot = async (folderId: string, shouldUseSecretV2Bridge: boolean) => { + const performSnapshot = async (folderId: string) => { try { if (!licenseService.isValidLicense) throw new InternalServerError({ message: "Invalid license" }); + const folder = await folderDAL.findById(folderId); + if (!folder) throw new BadRequestError({ message: "Folder not found" }); + const shouldUseSecretV2Bridge = folder.projectVersion === 3; + if (shouldUseSecretV2Bridge) { const snapshot = await snapshotDAL.transaction(async (tx) => { - const folder = await folderDAL.findById(folderId, tx); - if (!folder) throw new BadRequestError({ message: "Folder not found" }); - const secretVersions = await secretVersionV2BridgeDAL.findLatestVersionByFolderId(folderId, tx); const folderVersions = await folderVersionDAL.findLatestVersionByFolderId(folderId, tx); const newSnapshot = await snapshotDAL.create( @@ -208,9 +209,6 @@ export const secretSnapshotServiceFactory = ({ } const snapshot = await snapshotDAL.transaction(async (tx) => { - const folder = await folderDAL.findById(folderId, tx); - if (!folder) throw new BadRequestError({ message: "Folder not found" }); - const secretVersions = await secretVersionDAL.findLatestVersionByFolderId(folderId, tx); const folderVersions = await folderVersionDAL.findLatestVersionByFolderId(folderId, tx); const newSnapshot = await snapshotDAL.create( diff --git a/backend/src/lib/fn/index.ts b/backend/src/lib/fn/index.ts index 381ecebf3..82a4c4914 100644 --- a/backend/src/lib/fn/index.ts +++ b/backend/src/lib/fn/index.ts @@ -6,3 +6,4 @@ export * from "./array"; export * from "./dates"; export * from "./object"; export * from "./string"; +export * from "./undefined"; diff --git a/backend/src/lib/fn/undefined.ts b/backend/src/lib/fn/undefined.ts new file mode 100644 index 000000000..00f9d8e51 --- /dev/null +++ b/backend/src/lib/fn/undefined.ts @@ -0,0 +1,3 @@ +export const executeIfDefined = (func: (input: T) => R, input: T | undefined): R | undefined => { + return input === undefined ? undefined : func(input); +}; diff --git a/backend/src/services/integration-auth/integration-auth-service.ts b/backend/src/services/integration-auth/integration-auth-service.ts index e5126532f..bccdbd23f 100644 --- a/backend/src/services/integration-auth/integration-auth-service.ts +++ b/backend/src/services/integration-auth/integration-auth-service.ts @@ -11,6 +11,8 @@ import { BadRequestError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; import { TIntegrationDALFactory } from "../integration/integration-dal"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TProjectBotDALFactory } from "../project-bot/project-bot-dal"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; import { getApps } from "./integration-app-list"; @@ -55,6 +57,7 @@ type TIntegrationAuthServiceFactoryDep = { projectBotService: Pick; projectBotDAL: Pick; permissionService: Pick; + kmsService: Pick; }; export type TIntegrationAuthServiceFactory = ReturnType; @@ -64,7 +67,8 @@ export const integrationAuthServiceFactory = ({ integrationAuthDAL, integrationDAL, projectBotDAL, - projectBotService + projectBotService, + kmsService }: TIntegrationAuthServiceFactoryDep) => { const listIntegrationAuthByProjectId = async ({ actorId, @@ -145,18 +149,38 @@ export const integrationAuthServiceFactory = ({ }; } - const key = await projectBotService.getBotKey(projectId); - if (tokenExchange.refreshToken) { - const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.refreshToken, key); - updateDoc.refreshIV = refreshEncToken.iv; - updateDoc.refreshTag = refreshEncToken.tag; - updateDoc.refreshCiphertext = refreshEncToken.ciphertext; - } - if (tokenExchange.accessToken) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.accessToken, key); - updateDoc.accessIV = accessEncToken.iv; - updateDoc.accessTag = accessEncToken.tag; - updateDoc.accessCiphertext = accessEncToken.ciphertext; + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId); + if (shouldUseSecretV2Bridge) { + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + if (tokenExchange.refreshToken) { + const refreshEncToken = secretManagerEncryptor({ + plainText: Buffer.from(tokenExchange.refreshToken) + }).cipherTextBlob; + updateDoc.encryptedRefresh = refreshEncToken; + } + if (tokenExchange.accessToken) { + const accessToken = secretManagerEncryptor({ + plainText: Buffer.from(tokenExchange.accessToken) + }).cipherTextBlob; + updateDoc.encryptedAccess = accessToken; + } + } else { + if (!botKey) throw new BadRequestError({ message: "Bot key not found" }); + if (tokenExchange.refreshToken) { + const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.refreshToken, botKey); + updateDoc.refreshIV = refreshEncToken.iv; + updateDoc.refreshTag = refreshEncToken.tag; + updateDoc.refreshCiphertext = refreshEncToken.ciphertext; + } + if (tokenExchange.accessToken) { + const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.accessToken, botKey); + updateDoc.accessIV = accessEncToken.iv; + updateDoc.accessTag = accessEncToken.tag; + updateDoc.accessCiphertext = accessEncToken.ciphertext; + } } return integrationAuthDAL.transaction(async (tx) => { const doc = await integrationAuthDAL.findOne({ projectId, integration }, tx); @@ -212,109 +236,210 @@ export const integrationAuthServiceFactory = ({ : {}) }; - const key = await projectBotService.getBotKey(projectId); - if (refreshToken) { - const tokenDetails = await exchangeRefresh( - integration, - refreshToken, - url, - updateDoc.metadata as Record - ); - const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, key); - updateDoc.refreshIV = refreshEncToken.iv; - updateDoc.refreshTag = refreshEncToken.tag; - updateDoc.refreshCiphertext = refreshEncToken.ciphertext; - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, key); - updateDoc.accessIV = accessEncToken.iv; - updateDoc.accessTag = accessEncToken.tag; - updateDoc.accessCiphertext = accessEncToken.ciphertext; - updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt; - } + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId); + if (shouldUseSecretV2Bridge) { + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + if (refreshToken) { + const tokenDetails = await exchangeRefresh( + integration, + refreshToken, + url, + updateDoc.metadata as Record + ); + const refreshEncToken = secretManagerEncryptor({ + plainText: Buffer.from(tokenDetails.refreshToken) + }).cipherTextBlob; + updateDoc.encryptedRefresh = refreshEncToken; - if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) { - if (accessToken) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, key); + const accessEncToken = secretManagerEncryptor({ + plainText: Buffer.from(tokenDetails.accessToken) + }).cipherTextBlob; + updateDoc.encryptedAccess = accessEncToken; + updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt; + } + + if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) { + if (accessToken) { + const accessEncToken = secretManagerEncryptor({ + plainText: Buffer.from(accessToken) + }).cipherTextBlob; + updateDoc.encryptedAccess = accessEncToken; + } + if (accessId) { + const accessEncToken = secretManagerEncryptor({ + plainText: Buffer.from(accessId) + }).cipherTextBlob; + updateDoc.encryptedAccessId = accessEncToken; + } + if (awsAssumeIamRoleArn) { + const awsAssumeIamRoleArnEncrypted = secretManagerEncryptor({ + plainText: Buffer.from(awsAssumeIamRoleArn) + }).cipherTextBlob; + updateDoc.hasEncryptedAwsIamAssumRole = awsAssumeIamRoleArnEncrypted; + } + } + } else { + if (!botKey) throw new BadRequestError({ message: "Bot key not found" }); + if (refreshToken) { + const tokenDetails = await exchangeRefresh( + integration, + refreshToken, + url, + updateDoc.metadata as Record + ); + const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey); + updateDoc.refreshIV = refreshEncToken.iv; + updateDoc.refreshTag = refreshEncToken.tag; + updateDoc.refreshCiphertext = refreshEncToken.ciphertext; + const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey); updateDoc.accessIV = accessEncToken.iv; updateDoc.accessTag = accessEncToken.tag; updateDoc.accessCiphertext = accessEncToken.ciphertext; + + updateDoc.accessExpiresAt = tokenDetails.accessExpiresAt; } - if (accessId) { - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, key); - updateDoc.accessIdIV = accessEncToken.iv; - updateDoc.accessIdTag = accessEncToken.tag; - updateDoc.accessIdCiphertext = accessEncToken.ciphertext; - } - if (awsAssumeIamRoleArn) { - const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, key); - updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext; - updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv; - updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag; + + if (!refreshToken && (accessId || accessToken || awsAssumeIamRoleArn)) { + if (accessToken) { + const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessToken, botKey); + updateDoc.accessIV = accessEncToken.iv; + updateDoc.accessTag = accessEncToken.tag; + updateDoc.accessCiphertext = accessEncToken.ciphertext; + } + if (accessId) { + const accessEncToken = encryptSymmetric128BitHexKeyUTF8(accessId, botKey); + updateDoc.accessIdIV = accessEncToken.iv; + updateDoc.accessIdTag = accessEncToken.tag; + updateDoc.accessIdCiphertext = accessEncToken.ciphertext; + } + if (awsAssumeIamRoleArn) { + const awsAssumeIamRoleArnEnc = encryptSymmetric128BitHexKeyUTF8(awsAssumeIamRoleArn, botKey); + updateDoc.awsAssumeIamRoleArnCipherText = awsAssumeIamRoleArnEnc.ciphertext; + updateDoc.awsAssumeIamRoleArnIV = awsAssumeIamRoleArnEnc.iv; + updateDoc.awsAssumeIamRoleArnTag = awsAssumeIamRoleArnEnc.tag; + } } } return integrationAuthDAL.create(updateDoc); }; // helper function - const getIntegrationAccessToken = async (integrationAuth: TIntegrationAuths, botKey: string) => { + const getIntegrationAccessToken = async ( + integrationAuth: TIntegrationAuths, + shouldUseSecretV2Bridge: boolean, + botKey?: string + ) => { let accessToken: string | undefined; let accessId: string | undefined; // this means its not access token based if ( integrationAuth.integration === Integrations.AWS_SECRET_MANAGER && - integrationAuth.awsAssumeIamRoleArnCipherText + (shouldUseSecretV2Bridge + ? integrationAuth.hasEncryptedAwsIamAssumRole + : integrationAuth.awsAssumeIamRoleArnCipherText) ) { return { accessToken: "", accessId: "" }; } + if (shouldUseSecretV2Bridge) { + const { decryptor: secretManagerDecryptor, encryptor: secretManagerEncryptor } = + await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: integrationAuth.projectId + }); + if (integrationAuth.encryptedAccess) { + accessToken = secretManagerDecryptor({ cipherTextBlob: integrationAuth.encryptedAccess }).toString(); + } - if (integrationAuth.accessTag && integrationAuth.accessIV && integrationAuth.accessCiphertext) { - accessToken = decryptSymmetric128BitHexKeyUTF8({ - ciphertext: integrationAuth.accessCiphertext, - iv: integrationAuth.accessIV, - tag: integrationAuth.accessTag, - key: botKey - }); - } + if (integrationAuth.encryptedRefresh) { + const refreshToken = secretManagerDecryptor({ cipherTextBlob: integrationAuth.encryptedRefresh }).toString(); - if (integrationAuth.refreshCiphertext && integrationAuth.refreshIV && integrationAuth.refreshTag) { - const refreshToken = decryptSymmetric128BitHexKeyUTF8({ - key: botKey, - ciphertext: integrationAuth.refreshCiphertext, - iv: integrationAuth.refreshIV, - tag: integrationAuth.refreshTag - }); + if (integrationAuth.accessExpiresAt && integrationAuth.accessExpiresAt < new Date()) { + // refer above it contains same logic except not saving + const tokenDetails = await exchangeRefresh( + integrationAuth.integration, + refreshToken, + integrationAuth?.url, + integrationAuth.metadata as Record + ); + const encryptedRefresh = secretManagerEncryptor({ + plainText: Buffer.from(tokenDetails.refreshToken) + }).cipherTextBlob; + const encryptedAccess = secretManagerEncryptor({ + plainText: Buffer.from(tokenDetails.accessToken) + }).cipherTextBlob; + accessToken = tokenDetails.accessToken; + await integrationAuthDAL.updateById(integrationAuth.id, { + accessExpiresAt: tokenDetails.accessExpiresAt, + encryptedRefresh, + encryptedAccess + }); + } + } + if (!accessToken) throw new BadRequestError({ message: "Missing access token" }); - if (integrationAuth.accessExpiresAt && integrationAuth.accessExpiresAt < new Date()) { - // refer above it contains same logic except not saving - const tokenDetails = await exchangeRefresh( - integrationAuth.integration, - refreshToken, - integrationAuth?.url, - integrationAuth.metadata as Record - ); - const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey); - const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey); - accessToken = tokenDetails.accessToken; - await integrationAuthDAL.updateById(integrationAuth.id, { - refreshIV: refreshEncToken.iv, - refreshTag: refreshEncToken.tag, - refreshCiphertext: refreshEncToken.ciphertext, - accessExpiresAt: tokenDetails.accessExpiresAt, - accessIV: accessEncToken.iv, - accessTag: accessEncToken.tag, - accessCiphertext: accessEncToken.ciphertext + if (integrationAuth.encryptedAccessId) { + accessId = secretManagerDecryptor({ + cipherTextBlob: integrationAuth.encryptedAccessId + }).toString(); + } + + // the old bot key is else + } else { + if (!botKey) throw new BadRequestError({ message: "bot key is missing" }); + if (integrationAuth.accessTag && integrationAuth.accessIV && integrationAuth.accessCiphertext) { + accessToken = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: integrationAuth.accessCiphertext, + iv: integrationAuth.accessIV, + tag: integrationAuth.accessTag, + key: botKey + }); + } + + if (integrationAuth.refreshCiphertext && integrationAuth.refreshIV && integrationAuth.refreshTag) { + const refreshToken = decryptSymmetric128BitHexKeyUTF8({ + key: botKey, + ciphertext: integrationAuth.refreshCiphertext, + iv: integrationAuth.refreshIV, + tag: integrationAuth.refreshTag + }); + + if (integrationAuth.accessExpiresAt && integrationAuth.accessExpiresAt < new Date()) { + // refer above it contains same logic except not saving + const tokenDetails = await exchangeRefresh( + integrationAuth.integration, + refreshToken, + integrationAuth?.url, + integrationAuth.metadata as Record + ); + const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.refreshToken, botKey); + const accessEncToken = encryptSymmetric128BitHexKeyUTF8(tokenDetails.accessToken, botKey); + accessToken = tokenDetails.accessToken; + await integrationAuthDAL.updateById(integrationAuth.id, { + refreshIV: refreshEncToken.iv, + refreshTag: refreshEncToken.tag, + refreshCiphertext: refreshEncToken.ciphertext, + accessExpiresAt: tokenDetails.accessExpiresAt, + accessIV: accessEncToken.iv, + accessTag: accessEncToken.tag, + accessCiphertext: accessEncToken.ciphertext + }); + } + } + if (!accessToken) throw new BadRequestError({ message: "Missing access token" }); + + if (integrationAuth.accessIdTag && integrationAuth.accessIdIV && integrationAuth.accessIdCiphertext) { + accessId = decryptSymmetric128BitHexKeyUTF8({ + key: botKey, + ciphertext: integrationAuth.accessIdCiphertext, + iv: integrationAuth.accessIdIV, + tag: integrationAuth.accessIdTag }); } } - if (!accessToken) throw new BadRequestError({ message: "Missing access token" }); - if (integrationAuth.accessIdTag && integrationAuth.accessIdIV && integrationAuth.accessIdCiphertext) { - accessId = decryptSymmetric128BitHexKeyUTF8({ - key: botKey, - ciphertext: integrationAuth.accessIdCiphertext, - iv: integrationAuth.accessIdIV, - tag: integrationAuth.accessIdTag - }); - } return { accessId, accessToken }; }; @@ -339,8 +464,8 @@ export const integrationAuthServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken, accessId } = await getIntegrationAccessToken(integrationAuth, botKey); + const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken, accessId } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const apps = await getApps({ integration: integrationAuth.integration, accessToken, @@ -371,8 +496,8 @@ export const integrationAuthServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const teams = await getTeams({ integration: integrationAuth.integration, accessToken, @@ -400,8 +525,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (appId) { const { data } = await request.get( @@ -441,8 +566,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (accountId) { const { data } = await request.get(`${IntegrationUrls.CHECKLY_API_URL}/v1/check-groups`, { headers: { @@ -468,8 +593,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const octokit = new Octokit({ auth: accessToken @@ -505,8 +630,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const octokit = new Octokit({ auth: accessToken @@ -537,8 +662,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const { data } = await request.get<{ results: Array<{ id: string; name: string }> }>( `${IntegrationUrls.QOVERY_API_URL}/organization`, { @@ -571,8 +696,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const kms = new AWS.KMS({ region, @@ -629,8 +754,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (orgId) { const { data } = await request.get<{ results: Array<{ id: string; name: string }> }>( `${IntegrationUrls.QOVERY_API_URL}/organization/${orgId}/project`, @@ -665,8 +790,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (projectId && projectId !== "none") { // TODO: fix const { data } = await request.get<{ results: { id: string; name: string }[] }>( @@ -706,8 +831,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (environmentId) { const { data } = await request.get<{ results: { id: string; name: string }[] }>( `${IntegrationUrls.QOVERY_API_URL}/environment/${environmentId}/application`, @@ -746,8 +871,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (environmentId) { const { data } = await request.get<{ results: { id: string; name: string }[] }>( `${IntegrationUrls.QOVERY_API_URL}/environment/${environmentId}/container`, @@ -786,8 +911,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (environmentId) { const { data } = await request.get<{ results: { id: string; name: string }[] }>( `${IntegrationUrls.QOVERY_API_URL}/environment/${environmentId}/job`, @@ -825,8 +950,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const { data } = await request.get( `${IntegrationUrls.HEROKU_API_URL}/pipeline-couplings`, @@ -865,8 +990,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (appId) { const query = ` query GetEnvironments($projectId: String!, $after: String, $before: String, $first: Int, $isEphemeral: Boolean, $last: Int) { @@ -933,8 +1058,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (appId && appId !== "") { const query = ` @@ -1007,8 +1132,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const workspaces: TBitbucketWorkspace[] = []; let hasNextPage = true; let workspaceUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/workspaces`; @@ -1056,8 +1181,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); const secretGroups: { name: string; groupId: string }[] = []; if (appId) { @@ -1124,8 +1249,8 @@ export const integrationAuthServiceFactory = ({ actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); - const botKey = await projectBotService.getBotKey(integrationAuth.projectId); - const { accessToken } = await getIntegrationAccessToken(integrationAuth, botKey); + const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId); + const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey); if (appId) { const { data: { buildType } diff --git a/backend/src/services/secret-folder/secret-folder-dal.ts b/backend/src/services/secret-folder/secret-folder-dal.ts index 1803f8977..f9768ecc2 100644 --- a/backend/src/services/secret-folder/secret-folder-dal.ts +++ b/backend/src/services/secret-folder/secret-folder-dal.ts @@ -312,12 +312,14 @@ export const secretFolderDALFactory = (db: TDbClient) => { const folder = await (tx || db.replicaNode())(TableName.SecretFolder) .where({ [`${TableName.SecretFolder}.id` as "id"]: id }) .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) + .join(TableName.Project, `${TableName.Environment}.projectId`, `${TableName.Project}.id`) .select(selectAllTableCols(TableName.SecretFolder)) .select( db.ref("id").withSchema(TableName.Environment).as("envId"), db.ref("slug").withSchema(TableName.Environment).as("envSlug"), db.ref("name").withSchema(TableName.Environment).as("envName"), - db.ref("projectId").withSchema(TableName.Environment) + db.ref("projectId").withSchema(TableName.Environment), + db.ref("version").withSchema(TableName.Project).as("projectVersion") ) .first(); if (folder) { diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index c20c2345e..d2d189c2a 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -9,6 +9,8 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services import { getReplicationFolderName } from "@app/ee/services/secret-replication/secret-replication-service"; import { BadRequestError } from "@app/lib/errors"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; @@ -16,8 +18,9 @@ import { TSecretDALFactory } from "../secret/secret-dal"; import { decryptSecretRaw } from "../secret/secret-fns"; import { TSecretQueueFactory } from "../secret/secret-queue"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; +import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretImportDALFactory } from "./secret-import-dal"; -import { fnSecretsFromImports } from "./secret-import-fns"; +import { fnSecretsFromImports, fnSecretsV2FromImports } from "./secret-import-fns"; import { TCreateSecretImportDTO, TDeleteSecretImportDTO, @@ -31,12 +34,14 @@ type TSecretImportServiceFactoryDep = { secretImportDAL: TSecretImportDALFactory; folderDAL: TSecretFolderDALFactory; secretDAL: Pick; + secretV2BridgeDAL: Pick; projectBotService: Pick; projectDAL: Pick; projectEnvDAL: TProjectEnvDALFactory; permissionService: Pick; secretQueueService: Pick; licenseService: Pick; + kmsService: Pick; }; const ERR_SEC_IMP_NOT_FOUND = new BadRequestError({ message: "Secret import not found" }); @@ -52,7 +57,9 @@ export const secretImportServiceFactory = ({ secretDAL, secretQueueService, licenseService, - projectBotService + projectBotService, + secretV2BridgeDAL, + kmsService }: TSecretImportServiceFactoryDep) => { const createImport = async ({ environment, @@ -489,7 +496,23 @@ export const secretImportServiceFactory = ({ ) ); - const botKey = await projectBotService.getBotKey(projectId); + const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); + if (shouldUseSecretV2Bridge) { + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const importedSecrets = await fnSecretsV2FromImports({ + allowedImports, + folderDAL, + secretDAL: secretV2BridgeDAL, + secretImportDAL, + decryptor: (value) => + value ? secretManagerEncryptor({ plainText: value }).cipherTextBlob.toString() : undefined + }); + return importedSecrets; + } + if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL }); diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts index 24fbff609..a7717eed3 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-fns.ts @@ -4,11 +4,11 @@ import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas"; import { groupBy } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; +import { TKmsServiceFactory } from "../kms/kms-service"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types"; -import { TKmsServiceFactory } from "../kms/kms-service"; const INTERPOLATION_SYNTAX_REG = /\${([^}]+)}/g; diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts index 0753a4f50..9656221d2 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-service.ts @@ -14,6 +14,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { ActorType } from "../auth/auth-type"; import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TSecretQueueFactory } from "../secret/secret-queue"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; @@ -22,7 +23,6 @@ import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal"; import { - secretEncryptionHelper, fnSecretBulkDelete, fnSecretBulkInsert, fnSecretBulkUpdate, @@ -51,10 +51,7 @@ import { TSecretVersionV2TagDALFactory } from "./secret-version-tag-dal"; type TSecretV2BridgeServiceFactoryDep = { secretDAL: TSecretV2BridgeDALFactory; secretVersionDAL: TSecretVersionV2DALFactory; - kmsService: Pick< - TKmsServiceFactory, - "getProjectSecretManagerKmsDataKey" | "encryptWithInputKey" | "decryptWithInputKey" - >; + kmsService: Pick; secretVersionTagDAL: Pick; secretTagDAL: TSecretTagDALFactory; permissionService: Pick; @@ -69,7 +66,7 @@ type TSecretV2BridgeServiceFactoryDep = { secretApprovalRequestDAL: Pick; secretApprovalRequestSecretDAL: Pick< TSecretApprovalRequestSecretDALFactory, - "insertMany" | "insertApprovalSecretTags" + "insertV2Bridge" | "insertApprovalSecretV2Tags" >; snapshotService: Pick; }; @@ -150,8 +147,10 @@ export const secretV2BridgeServiceFactory = ({ const { secretName, type, ...el } = inputSecret; const references = getAllNestedSecretReferences(inputSecret.secretValue); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerEncryptor = await kmsService.encryptWithInputKey({ key: secretManagerDataKey }); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const secret = await secretDAL.transaction((tx) => fnSecretBulkInsert({ @@ -161,8 +160,12 @@ export const secretV2BridgeServiceFactory = ({ version: 1, type, reminderRepeatDays: el.secretReminderRepeatDays, - encryptedComment: secretEncryptionHelper.encryptValue(secretManagerEncryptor, el.secretComment), - encryptedValue: secretEncryptionHelper.encryptValue(secretManagerEncryptor, el.secretValue), + encryptedComment: el.secretComment + ? secretManagerEncryptor({ plainText: Buffer.from(el.secretComment) }).cipherTextBlob + : undefined, + encryptedValue: el.secretValue + ? secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob + : undefined, reminderNote: el.secretReminderNote, skipMultilineEncoding: el.skipMultilineEncoding, key: secretName, @@ -179,7 +182,7 @@ export const secretV2BridgeServiceFactory = ({ }) ); - await snapshotService.performSnapshot(folderId, true); + await snapshotService.performSnapshot(folderId); await secretQueueService.syncSecrets({ secretPath, actorId, @@ -274,14 +277,16 @@ export const secretV2BridgeServiceFactory = ({ const tags = inputSecret.tagIds ? await secretTagDAL.find({ projectId, $in: { id: inputSecret.tagIds } }) : []; if ((inputSecret.tagIds || []).length !== tags.length) throw new BadRequestError({ message: "Tag not found" }); - const { secretName, secretValue, secretComment } = inputSecret; + const { secretName, secretValue } = inputSecret; - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerEncryptor = await kmsService.encryptWithInputKey({ key: secretManagerDataKey }); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const encryptedValue = typeof secretValue !== "undefined" ? { - encryptedValue: secretEncryptionHelper.encryptValue(secretManagerEncryptor, secretValue) as Buffer, + encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(secretValue) }).cipherTextBlob, references: getAllNestedSecretReferences(secretValue) } : {}; @@ -294,7 +299,9 @@ export const secretV2BridgeServiceFactory = ({ filter: { id: secretId }, data: { reminderRepeatDays: inputSecret.secretReminderRepeatDays, - encryptedComment: secretEncryptionHelper.encryptValue(secretManagerEncryptor, secretComment), + encryptedComment: inputSecret.secretComment + ? secretManagerEncryptor({ plainText: Buffer.from(inputSecret.secretComment) }).cipherTextBlob + : undefined, reminderNote: inputSecret.secretReminderNote, skipMultilineEncoding: inputSecret.skipMultilineEncoding, key: inputSecret.newSecretName || secretName, @@ -319,7 +326,7 @@ export const secretV2BridgeServiceFactory = ({ projectId }); - await snapshotService.performSnapshot(folderId, true); + await snapshotService.performSnapshot(folderId); await secretQueueService.syncSecrets({ actor, actorId, @@ -385,7 +392,7 @@ export const secretV2BridgeServiceFactory = ({ }) ); - await snapshotService.performSnapshot(folderId, true); + await snapshotService.performSnapshot(folderId); await secretQueueService.syncSecrets({ actor, actorId, @@ -394,12 +401,18 @@ export const secretV2BridgeServiceFactory = ({ environmentSlug: folder.environment.slug }); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); return reshapeBridgeSecret(projectId, environment, secretPath, { ...deletedSecret[0], - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, deletedSecret[0].encryptedValue), - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, deletedSecret[0].encryptedComment) + value: deletedSecret[0].encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedValue }).toString() + : undefined, + comment: deletedSecret[0].encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: deletedSecret[0].encryptedComment }).toString() + : undefined }); }; @@ -463,8 +476,10 @@ export const secretV2BridgeServiceFactory = ({ actorId ); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); if (includeImports) { const secretImports = await secretImportDAL.findByFolderIds(paths.map((p) => p.folderId)); @@ -486,15 +501,19 @@ export const secretV2BridgeServiceFactory = ({ secretDAL, folderDAL, secretImportDAL, - decryptor: (value) => secretEncryptionHelper.decryptValue(secretManagerDecryptor, value) + decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined) }); return { secrets: secrets.map((secret) => reshapeBridgeSecret(projectId, environment, groupedPaths[secret.folderId][0].path, { ...secret, - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedValue), - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedComment) + value: secret.encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString() + : undefined, + comment: secret.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString() + : undefined }) ), imports: importedSecrets @@ -505,8 +524,12 @@ export const secretV2BridgeServiceFactory = ({ secrets: secrets.map((secret) => reshapeBridgeSecret(projectId, environment, groupedPaths[secret.folderId][0].path, { ...secret, - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedValue), - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedComment) + value: secret.encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString() + : undefined, + comment: secret.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString() + : undefined }) ) }; @@ -553,8 +576,10 @@ export const secretV2BridgeServiceFactory = ({ secretType = SecretType.Shared; } - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const secret = await (version === undefined ? secretDAL.findOneWithTags({ @@ -571,9 +596,10 @@ export const secretV2BridgeServiceFactory = ({ key: secretName }) .then((el) => SecretsV2Schema.parse({ ...el, id: el.secretId }))); + const interpolateInlineSecretReference = interpolateSecrets({ projectId, - decryptSecret: (encryptedValue) => secretEncryptionHelper.decryptValue(secretManagerDecryptor, encryptedValue), + decryptSecret: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined), secretDAL, folderDAL }); @@ -600,17 +626,17 @@ export const secretV2BridgeServiceFactory = ({ secretDAL, folderDAL, secretImportDAL, - decryptor: (value) => secretEncryptionHelper.decryptValue(secretManagerDecryptor, value) + decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined) }); for (let i = importedSecrets.length - 1; i >= 0; i -= 1) { for (let j = 0; j < importedSecrets[i].secrets.length; j += 1) { if (secretName === importedSecrets[i].secrets[j].key) { const importedSecret = importedSecrets[i].secrets[j]; - let secretValue = secretEncryptionHelper.decryptValue( - secretManagerDecryptor, - importedSecret.encryptedValue - ); + let secretValue = importedSecret.encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: importedSecret.encryptedValue }).toString() + : undefined; + if (expandSecretReferences && secretValue) { const secretReferenceExpandedString = { [importedSecret.key]: { value: secretValue } @@ -623,7 +649,9 @@ export const secretV2BridgeServiceFactory = ({ return reshapeBridgeSecret(projectId, importedSecrets[i].environment, importedSecrets[i].secretPath, { ...importedSecret, value: secretValue, - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, importedSecret.encryptedComment) + comment: importedSecret.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: importedSecret.encryptedComment }).toString() + : undefined }); } } @@ -631,7 +659,9 @@ export const secretV2BridgeServiceFactory = ({ } if (!secret) throw new BadRequestError({ message: "Secret not found" }); - let secretValue = secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedValue); + let secretValue = secret.encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString() + : undefined; if (expandSecretReferences && secretValue) { const secretReferenceExpandedString = { [secret.key]: { value: secretValue } @@ -644,7 +674,9 @@ export const secretV2BridgeServiceFactory = ({ return reshapeBridgeSecret(projectId, environment, path, { ...secret, value: secretValue, - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedComment) + comment: secret.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedComment }).toString() + : undefined }); }; @@ -693,15 +725,19 @@ export const secretV2BridgeServiceFactory = ({ const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds) : []; if (tags.length !== sanitizedTagIds.length) throw new BadRequestError({ message: "Tag not found" }); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerEncryptor = await kmsService.encryptWithInputKey({ key: secretManagerDataKey }); + const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = + await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId }); const newSecrets = await secretDAL.transaction(async (tx) => fnSecretBulkInsert({ inputSecrets: inputSecrets.map((el) => ({ version: 1, - encryptedComment: secretEncryptionHelper.encryptValue(secretManagerEncryptor, el.secretComment), - encryptedValue: secretEncryptionHelper.encryptValue(secretManagerEncryptor, el.secretValue), + encryptedComment: el.secretComment + ? secretManagerEncryptor({ plainText: Buffer.from(el.secretComment) }).cipherTextBlob + : undefined, + encryptedValue: el.secretValue + ? secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob + : undefined, skipMultilineEncoding: el.skipMultilineEncoding, key: el.secretKey, tagIds: el.tagIds, @@ -717,7 +753,7 @@ export const secretV2BridgeServiceFactory = ({ }) ); - await snapshotService.performSnapshot(folderId, true); + await snapshotService.performSnapshot(folderId); await secretQueueService.syncSecrets({ actor, actorId, @@ -726,12 +762,13 @@ export const secretV2BridgeServiceFactory = ({ environmentSlug: folder.environment.slug }); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); return newSecrets.map((el) => reshapeBridgeSecret(projectId, environment, secretPath, { ...el, - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, el.encryptedValue), - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, el.encryptedComment) + value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : undefined, + comment: el.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() + : undefined }) ); }; @@ -799,8 +836,8 @@ export const secretV2BridgeServiceFactory = ({ const tags = sanitizedTagIds.length ? await secretTagDAL.findManyTagsById(projectId, sanitizedTagIds) : []; if (tags.length !== sanitizedTagIds.length) throw new BadRequestError({ message: "Tag not found" }); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerEncryptor = await kmsService.encryptWithInputKey({ key: secretManagerDataKey }); + const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = + await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId }); const secrets = await secretDAL.transaction(async (tx) => fnSecretBulkUpdate({ @@ -811,7 +848,7 @@ export const secretV2BridgeServiceFactory = ({ const encryptedValue = typeof el.secretValue !== "undefined" ? { - encryptedValue: secretEncryptionHelper.encryptValue(secretManagerEncryptor, el.secretValue) as Buffer, + encryptedValue: secretManagerEncryptor({ plainText: Buffer.from(el.secretValue) }).cipherTextBlob, references: getAllNestedSecretReferences(el.secretValue) } : {}; @@ -819,7 +856,9 @@ export const secretV2BridgeServiceFactory = ({ filter: { id: originalSecret.id, type: SecretType.Shared }, data: { reminderRepeatDays: el.secretReminderRepeatDays, - encryptedComment: secretEncryptionHelper.encryptValue(secretManagerEncryptor, el.secretComment), + encryptedComment: el.secretComment + ? secretManagerEncryptor({ plainText: Buffer.from(el.secretComment) }).cipherTextBlob + : undefined, reminderNote: el.secretReminderNote, skipMultilineEncoding: el.skipMultilineEncoding, key: el.newSecretName || el.secretKey, @@ -834,7 +873,7 @@ export const secretV2BridgeServiceFactory = ({ secretVersionTagDAL }) ); - await snapshotService.performSnapshot(folderId, true); + await snapshotService.performSnapshot(folderId); await secretQueueService.syncSecrets({ actor, actorId, @@ -843,12 +882,13 @@ export const secretV2BridgeServiceFactory = ({ environmentSlug: folder.environment.slug }); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); return secrets.map((el) => reshapeBridgeSecret(projectId, environment, secretPath, { ...el, - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, el.encryptedValue), - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, el.encryptedComment) + value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : undefined, + comment: el.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() + : undefined }) ); }; @@ -917,13 +957,17 @@ export const secretV2BridgeServiceFactory = ({ environmentSlug: folder.environment.slug }); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); return secretsDeleted.map((el) => reshapeBridgeSecret(projectId, environment, secretPath, { ...el, - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, el.encryptedValue), - comment: secretEncryptionHelper.decryptValue(secretManagerDecryptor, el.encryptedComment) + value: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : undefined, + comment: el.encryptedComment + ? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString() + : undefined }) ); }; @@ -977,8 +1021,10 @@ export const secretV2BridgeServiceFactory = ({ if (!hasRole(ProjectMembershipRole.Admin)) throw new BadRequestError({ message: "Only admins are allowed to take this action" }); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); await secretDAL.transaction(async (tx) => { const secrets = await secretDAL.findAllProjectSecretValues(projectId, tx); await secretDAL.upsertSecretReferences( @@ -987,9 +1033,7 @@ export const secretV2BridgeServiceFactory = ({ .map(({ id, encryptedValue }) => ({ secretId: id, references: encryptedValue - ? getAllNestedSecretReferences( - secretEncryptionHelper.decryptValue(secretManagerDecryptor, encryptedValue) as string - ) + ? getAllNestedSecretReferences(secretManagerDecryptor({ cipherTextBlob: encryptedValue }).toString()) : [] })), tx @@ -1067,11 +1111,15 @@ export const secretV2BridgeServiceFactory = ({ }); } - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const decryptedSourceSecrets = sourceSecrets.map((secret) => ({ ...secret, - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedValue) + value: secret.encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString() + : undefined })); let isSourceUpdated = false; @@ -1090,7 +1138,9 @@ export const secretV2BridgeServiceFactory = ({ const decryptedDestinationSecrets = destinationSecretsFromDB.map((secret) => { return { ...secret, - value: secretEncryptionHelper.decryptValue(secretManagerDecryptor, secret.encryptedValue) + value: secret.encryptedValue + ? secretManagerDecryptor({ cipherTextBlob: secret.encryptedValue }).toString() + : undefined }; }); @@ -1151,33 +1201,25 @@ export const secretV2BridgeServiceFactory = ({ tx ); - // TODO(akhilmhdh-sev2): fix this - // const commits = locallyCreatedSecrets.concat(locallyUpdatedSecrets).map((doc) => { - // const { operation } = doc; - // const localSecret = destinationSecretsGroupedByKey[doc.key]?.[0]; - // - // return { - // op: operation, - // requestId: approvalRequestDoc.id, - // metadata: doc.metadata, - // secretKeyIV: doc.secretKeyIV, - // secretKeyTag: doc.secretKeyTag, - // secretKeyCiphertext: doc.secretKeyCiphertext, - // secretValueIV: doc.secretValueIV, - // secretValueTag: doc.secretValueTag, - // secretValueCiphertext: doc.secretValueCiphertext, - // secretBlindIndex: doc.secretBlindIndex, - // secretCommentIV: doc.secretCommentIV, - // secretCommentTag: doc.secretCommentTag, - // secretCommentCiphertext: doc.secretCommentCiphertext, - // skipMultilineEncoding: doc.skipMultilineEncoding, - // // except create operation other two needs the secret id and version id - // ...(operation !== SecretOperations.Create - // ? { secretId: localSecret.id, secretVersion: latestSecretVersions[localSecret.id].id } - // : {}) - // }; - // }); - await secretApprovalRequestSecretDAL.insertMany([], tx); + const commits = locallyCreatedSecrets.concat(locallyUpdatedSecrets).map((doc) => { + const { operation } = doc; + const localSecret = destinationSecretsGroupedByKey[doc.key]?.[0]; + + return { + op: operation, + requestId: approvalRequestDoc.id, + metadata: doc.metadata, + key: doc.key, + encryptedValue: doc.encryptedValue, + encryptedComment: doc.encryptedComment, + skipMultilineEncoding: doc.skipMultilineEncoding, + // except create operation other two needs the secret id and version id + ...(operation !== SecretOperations.Create + ? { secretId: localSecret.id, secretVersion: latestSecretVersions[localSecret.id].id } + : {}) + }; + }); + await secretApprovalRequestSecretDAL.insertV2Bridge(commits, tx); } else { // apply changes directly if (locallyCreatedSecrets.length) { @@ -1268,34 +1310,24 @@ export const secretV2BridgeServiceFactory = ({ tx ); - // TODO(akhilmhdh-sev2): finish this - // const commits = locallyDeletedSecrets.map((doc) => { - // const { operation } = doc; - // const localSecret = sourceSecretsGroupByKey[doc.key]?.[0]; - // - // return { - // op: operation, - // keyEncoding: doc.keyEncoding, - // algorithm: doc.algorithm, - // requestId: approvalRequestDoc.id, - // metadata: doc.metadata, - // secretKeyIV: doc.secretKeyIV, - // secretKeyTag: doc.secretKeyTag, - // secretKeyCiphertext: doc.secretKeyCiphertext, - // secretValueIV: doc.secretValueIV, - // secretValueTag: doc.secretValueTag, - // secretValueCiphertext: doc.secretValueCiphertext, - // secretBlindIndex: doc.secretBlindIndex, - // secretCommentIV: doc.secretCommentIV, - // secretCommentTag: doc.secretCommentTag, - // secretCommentCiphertext: doc.secretCommentCiphertext, - // skipMultilineEncoding: doc.skipMultilineEncoding, - // secretId: localSecret.id, - // secretVersion: latestSecretVersions[localSecret.id].id - // }; - // }); + const commits = locallyDeletedSecrets.map((doc) => { + const { operation } = doc; + const localSecret = sourceSecretsGroupByKey[doc.key]?.[0]; - await secretApprovalRequestSecretDAL.insertMany([], tx); + return { + op: operation, + requestId: approvalRequestDoc.id, + metadata: doc.metadata, + key: doc.key, + encryptedComment: doc.encryptedComment, + encryptedValue: doc.encryptedValue, + skipMultilineEncoding: doc.skipMultilineEncoding, + secretId: localSecret.id, + secretVersion: latestSecretVersions[localSecret.id].id + }; + }); + + await secretApprovalRequestSecretDAL.insertV2Bridge(commits, tx); } else { // if no secret approval policy is present, we delete directly. await secretDAL.delete( @@ -1313,7 +1345,7 @@ export const secretV2BridgeServiceFactory = ({ }); if (isDestinationUpdated) { - await snapshotService.performSnapshot(destinationFolder.id, true); + await snapshotService.performSnapshot(destinationFolder.id); await secretQueueService.syncSecrets({ projectId, secretPath: destinationFolder.path, @@ -1324,7 +1356,7 @@ export const secretV2BridgeServiceFactory = ({ } if (isSourceUpdated) { - await snapshotService.performSnapshot(sourceFolder.id, true); + await snapshotService.performSnapshot(sourceFolder.id); await secretQueueService.syncSecrets({ projectId, secretPath: sourceFolder.path, diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index f4c66841c..d6a860b93 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -22,8 +22,14 @@ import { import { BadRequestError } from "@app/lib/errors"; import { groupBy, unique } from "@app/lib/fn"; import { logger } from "@app/lib/logger"; +import { + fnSecretBulkInsert as fnSecretV2BridgeBulkInsert, + fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate, + getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge +} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; +import { KmsDataKey } from "../kms/kms-types"; import { getBotKeyFnFactory } from "../project-bot/project-bot-fns"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; @@ -39,12 +45,6 @@ import { TUpdateManySecretsRawFn, TUpdateManySecretsRawFnFactory } from "./secret-types"; -import { KmsDataKey } from "../kms/kms-types"; -import { - fnSecretBulkInsert as fnSecretV2BridgeBulkInsert, - fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate, - getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge -} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns"; export const generateSecretBlindIndexBySalt = async (secretName: string, secretBlindIndexDoc: TSecretBlindIndexes) => { const appCfg = getConfig(); diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index 337eba8f5..d5af6d137 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -18,6 +18,8 @@ import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { TIntegrationDALFactory } from "../integration/integration-dal"; import { TIntegrationAuthServiceFactory } from "../integration-auth/integration-auth-service"; import { syncIntegrationSecrets } from "../integration-auth/integration-sync-secret"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TOrgDALFactory } from "../org/org-dal"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; @@ -25,6 +27,9 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; +import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; +import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; +import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service"; import { TWebhookDALFactory } from "../webhook/webhook-dal"; import { fnTriggerWebhook } from "../webhook/webhook-fns"; @@ -36,10 +41,6 @@ import { TRemoveSecretReminderDTO, TSyncSecretsDTO } from "./secret-types"; -import { TKmsServiceFactory } from "../kms/kms-service"; -import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; -import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; -import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; export type TSecretQueueFactory = ReturnType; type TSecretQueueFactoryDep = { @@ -557,6 +558,10 @@ export const secretQueueFactory = ({ } const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); let referencedFolderIds; if (shouldUseSecretV2Bridge) { const secretReferences = await secretV2BridgeDAL.findReferencedSecretReferences( @@ -623,31 +628,44 @@ export const secretQueueFactory = ({ projectId: integration.projectId }; - const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken(integrationAuth, botKey); - const awsAssumeRoleArn = + const { accessToken, accessId } = await integrationAuthService.getIntegrationAccessToken( + integrationAuth, + shouldUseSecretV2Bridge, + botKey + ); + let awsAssumeRoleArn = null; + if (shouldUseSecretV2Bridge) { + if (integrationAuth.awsAssumeIamRoleArnCipherText) { + awsAssumeRoleArn = secretManagerDecryptor({ + cipherTextBlob: Buffer.from(integrationAuth.awsAssumeIamRoleArnCipherText) + }).toString(); + } + } else if ( integrationAuth.awsAssumeIamRoleArnTag && integrationAuth.awsAssumeIamRoleArnIV && integrationAuth.awsAssumeIamRoleArnCipherText - ? decryptSymmetric128BitHexKeyUTF8({ - ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText, - iv: integrationAuth.awsAssumeIamRoleArnIV, - tag: integrationAuth.awsAssumeIamRoleArnTag, - key: botKey - }) - : null; + ) { + awsAssumeRoleArn = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: integrationAuth.awsAssumeIamRoleArnCipherText, + iv: integrationAuth.awsAssumeIamRoleArnIV, + tag: integrationAuth.awsAssumeIamRoleArnTag, + key: botKey as string + }); + } const secrets = shouldUseSecretV2Bridge ? await getIntegrationSecretsV2({ environment, projectId, folderId: folder.id, - depth: 1 + depth: 1, + decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "") }) : await getIntegrationSecrets({ environment, projectId, folderId: folder.id, - key: botKey, + key: botKey as string, depth: 1 }); const suffixedSecrets: typeof secrets = {}; diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index 1a157ddc0..11222ec2b 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -14,6 +14,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal"; +import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service"; import { getConfig } from "@app/lib/config/env"; import { @@ -73,7 +74,6 @@ import { } from "./secret-types"; import { TSecretVersionDALFactory } from "./secret-version-dal"; import { TSecretVersionTagDALFactory } from "./secret-version-tag-dal"; -import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service"; type TSecretServiceFactoryDep = { secretDAL: TSecretDALFactory; diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index cac5adef2..8628e12f3 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -12,10 +12,10 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { ActorType } from "../auth/auth-type"; +import { TKmsServiceFactory } from "../kms/kms-service"; import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal"; import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal"; import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal"; -import { TKmsServiceFactory } from "../kms/kms-service"; type TPartialSecret = Pick;