From 3e0ae5765f106ec3d2e6d9370fff33f7f9793065 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 24 Jul 2024 00:56:23 +0530 Subject: [PATCH] feat: updated kms service to return only kms details and some more minor changes --- backend/src/ee/routes/v1/project-router.ts | 10 +- .../external-kms/external-kms-service.ts | 48 +++-- .../secret-approval-request-service.ts | 24 ++- backend/src/services/kms/kms-key-dal.ts | 2 + backend/src/services/kms/kms-service.ts | 178 ++++++++++-------- backend/src/services/kms/kms-types.ts | 38 ++-- .../src/services/project/project-service.ts | 7 +- backend/src/services/project/project-types.ts | 3 +- frontend/src/hooks/api/kms/mutations.tsx | 6 +- frontend/src/hooks/api/kms/types.ts | 5 + .../EncryptionTab/EncryptionTab.tsx | 21 ++- 11 files changed, 200 insertions(+), 142 deletions(-) diff --git a/backend/src/ee/routes/v1/project-router.ts b/backend/src/ee/routes/v1/project-router.ts index 1f8c5b7ee..7d0d5f3b3 100644 --- a/backend/src/ee/routes/v1/project-router.ts +++ b/backend/src/ee/routes/v1/project-router.ts @@ -7,6 +7,7 @@ import { getLastMidnightDateISO, 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"; +import { KmsType } from "@app/services/kms/kms-types"; export const registerProjectRouter = async (server: FastifyZodProvider) => { server.route({ @@ -194,7 +195,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT]), handler: async (req) => { - const kmsKeys = await server.services.project.getProjectKmsKeys({ + const kmsKey = await server.services.project.getProjectKmsKeys({ actor: req.permission.type, actorId: req.permission.id, actorAuthMethod: req.permission.authMethod, @@ -202,7 +203,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { projectId: req.params.workspaceId }); - return kmsKeys; + return kmsKey; } }); @@ -217,7 +218,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => { workspaceId: z.string().trim() }), body: z.object({ - secretManagerKmsKeyId: z.string() + kms: z.discriminatedUnion("type", [ + z.object({ type: z.literal(KmsType.Internal) }), + z.object({ type: z.literal(KmsType.External), kmsId: z.string() }) + ]) }), response: { 200: z.object({ diff --git a/backend/src/ee/services/external-kms/external-kms-service.ts b/backend/src/ee/services/external-kms/external-kms-service.ts index 59e6e5bc4..491971a04 100644 --- a/backend/src/ee/services/external-kms/external-kms-service.ts +++ b/backend/src/ee/services/external-kms/external-kms-service.ts @@ -5,6 +5,7 @@ import { BadRequestError } from "@app/lib/errors"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; @@ -23,10 +24,7 @@ import { ExternalKmsAwsSchema, KmsProviders } from "./providers/model"; type TExternalKmsServiceFactoryDep = { externalKmsDAL: TExternalKmsDALFactory; - kmsService: Pick< - TKmsServiceFactory, - "getOrgKmsKeyId" | "decryptWithInputKey" | "encryptWithInputKey" | "getOrgKmsDataKey" - >; + kmsService: Pick; kmsDAL: Pick; permissionService: Pick; licenseService: Pick; @@ -84,12 +82,12 @@ export const externalKmsServiceFactory = ({ throw new BadRequestError({ message: "external kms provided is invalid" }); } - const orgKmsDataKey = await kmsService.getOrgKmsDataKey(actorOrgId); - const kmsEncryptor = await kmsService.encryptWithInputKey({ - key: orgKmsDataKey + const { encryptor: orgDataKeyEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: actorOrgId }); - const { cipherTextBlob: encryptedProviderInputs } = kmsEncryptor({ + const { cipherTextBlob: encryptedProviderInputs } = orgDataKeyEncryptor({ plainText: Buffer.from(sanitizedProviderInput, "utf8") }); @@ -150,13 +148,13 @@ export const externalKmsServiceFactory = ({ if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" }); let sanitizedProviderInput = ""; - if (provider) { - const orgKmsDataKey = await kmsService.getOrgKmsDataKey(kmsDoc.orgId); - const kmsDecryptor = await kmsService.decryptWithInputKey({ - key: orgKmsDataKey + const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } = + await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: actorOrgId }); - - const decryptedProviderInputBlob = kmsDecryptor({ + if (provider) { + const decryptedProviderInputBlob = orgDataKeyDecryptor({ cipherTextBlob: externalKmsDoc.encryptedProviderInputs }); @@ -179,11 +177,7 @@ export const externalKmsServiceFactory = ({ let encryptedProviderInputs: Buffer | undefined; if (sanitizedProviderInput) { - const orgKmsDataKey = await kmsService.getOrgKmsDataKey(actorOrgId); - const kmsEncryptor = await kmsService.encryptWithInputKey({ - key: orgKmsDataKey - }); - const { cipherTextBlob } = kmsEncryptor({ + const { cipherTextBlob } = orgDataKeyEncryptor({ plainText: Buffer.from(sanitizedProviderInput, "utf8") }); encryptedProviderInputs = cipherTextBlob; @@ -266,12 +260,12 @@ export const externalKmsServiceFactory = ({ const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" }); - const orgKmsDataKey = await kmsService.getOrgKmsDataKey(kmsDoc.orgId); - const kmsDecryptor = await kmsService.decryptWithInputKey({ - key: orgKmsDataKey + const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: actorOrgId }); - const decryptedProviderInputBlob = kmsDecryptor({ + const decryptedProviderInputBlob = orgDataKeyDecryptor({ cipherTextBlob: externalKmsDoc.encryptedProviderInputs }); switch (externalKmsDoc.provider) { @@ -306,12 +300,12 @@ export const externalKmsServiceFactory = ({ const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" }); - const orgKmsDataKey = await kmsService.getOrgKmsDataKey(kmsDoc.orgId); - const kmsDecryptor = await kmsService.decryptWithInputKey({ - key: orgKmsDataKey + const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: actorOrgId }); - const decryptedProviderInputBlob = kmsDecryptor({ + const decryptedProviderInputBlob = orgDataKeyDecryptor({ cipherTextBlob: externalKmsDoc.encryptedProviderInputs }); 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 f0cdf0951..5157c78c0 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 @@ -17,6 +17,7 @@ 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 { 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 { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal"; @@ -89,10 +90,7 @@ type TSecretApprovalRequestServiceFactoryDep = { projectEnvDAL: Pick; projectDAL: Pick; secretQueueService: Pick; - kmsService: Pick< - TKmsServiceFactory, - "getProjectSecretManagerKmsDataKey" | "encryptWithInputKey" | "decryptWithInputKey" - >; + kmsService: Pick; secretV2BridgeDAL: Pick< TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" @@ -214,8 +212,10 @@ export const secretApprovalRequestServiceFactory = ({ let secrets; if (shouldUseSecretV2Bridge) { - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2( secretApprovalRequest.id ); @@ -427,8 +427,10 @@ export const secretApprovalRequestServiceFactory = ({ ); if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" }); - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerDecryptor = await kmsService.decryptWithInputKey({ key: secretManagerDataKey }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const conflicts: Array<{ secretId: string; op: SecretOperations }> = []; let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create); @@ -1074,8 +1076,10 @@ export const secretApprovalRequestServiceFactory = ({ const commits: Omit[] = []; const commitTagIds: Record = {}; - const secretManagerDataKey = await kmsService.getProjectSecretManagerKmsDataKey(projectId); - const secretManagerEncryptor = await kmsService.encryptWithInputKey({ key: secretManagerDataKey }); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); // for created secret approval change const createdSecrets = data[SecretOperations.Create]; diff --git a/backend/src/services/kms/kms-key-dal.ts b/backend/src/services/kms/kms-key-dal.ts index c2b1c0358..004348722 100644 --- a/backend/src/services/kms/kms-key-dal.ts +++ b/backend/src/services/kms/kms-key-dal.ts @@ -10,6 +10,8 @@ export type TKmsKeyDALFactory = ReturnType; export const kmskeyDALFactory = (db: TDbClient) => { const kmsOrm = ormify(db, TableName.KmsKey); + // akhilmhdh: this function should never be called outside kms service + // why: because the encrypted key should never be shared with another service const findByIdWithAssociatedKms = async (id: string, tx?: Knex) => { try { const result = await (tx || db.replicaNode())(TableName.KmsKey) diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index 4555bb0e0..08465284b 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -1,6 +1,8 @@ import slugify from "@sindresorhus/slugify"; import { Knex } from "knex"; +import { z } from "zod"; +import { KmsKeysSchema } from "@app/db/schemas"; import { AwsKmsProviderFactory } from "@app/ee/services/external-kms/providers/aws-kms"; import { ExternalKmsAwsSchema, @@ -23,12 +25,14 @@ import { TKmsKeyDALFactory } from "./kms-key-dal"; import { TKmsRootConfigDALFactory } from "./kms-root-config-dal"; import { KmsDataKey, + KmsType, TDecryptWithKeyDTO, TDecryptWithKmsDTO, TEncryptionWithKeyDTO, TEncryptWithKmsDataKeyDTO, TEncryptWithKmsDTO, - TGenerateKMSDTO + TGenerateKMSDTO, + TUpdateProjectSecretManagerKmsKeyDTO } from "./kms-types"; type TKmsServiceFactoryDep = { @@ -42,7 +46,6 @@ type TKmsServiceFactoryDep = { export type TKmsServiceFactory = ReturnType; -export const INTERNAL_KMS_KEY_ID = "internal"; const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000"; const KMS_ROOT_CREATION_WAIT_KEY = "wait_till_ready_kms_root_key"; @@ -51,6 +54,8 @@ const KMS_ROOT_CREATION_WAIT_TIME = 10; // akhilmhdh: Don't edit this value. This is measured for blob concatination in kms const KMS_VERSION = "v01"; const KMS_VERSION_BLOB_LENGTH = 3; +const KmsSanitizedSchema = KmsKeysSchema.extend({ isExternal: z.boolean() }); + export const kmsServiceFactory = ({ kmsDAL, kmsRootConfigDAL, @@ -61,7 +66,11 @@ export const kmsServiceFactory = ({ }: TKmsServiceFactoryDep) => { let ROOT_ENCRYPTION_KEY = Buffer.alloc(0); - // this is used symmetric encryption + /* + * Generate KMS Key + * This function is responsibile for generating the infisical internal KMS for various entities + * Like for secret manager, cert manager or for organization + */ const generateKmsKey = async ({ orgId, isReserved = true, tx, slug }: TGenerateKMSDTO) => { const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); const kmsKeyMaterial = randomSecureBytes(32); @@ -93,6 +102,11 @@ export const kmsServiceFactory = ({ return doc; }; + /* + * Simple encryption service function to do all the encryption tasks in infisical + * This can be even later exposed directly as api for encryption as function + * The encrypted binary even has everything into it. The IV, the version etc + */ const encryptWithInputKey = async ({ key }: Omit) => { // akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); @@ -105,6 +119,10 @@ export const kmsServiceFactory = ({ }; }; + /* + * Simple decryption service function to do all the encryption tasks in infisical + * This can be even later exposed directly as api for encryption as function + */ const decryptWithInputKey = async ({ key }: Omit) => { const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); @@ -115,6 +133,13 @@ export const kmsServiceFactory = ({ }; }; + /* + * Function to generate a KMS for an org + * We handle concurrent with redis locking and waitReady + * What happens is first we check kms is assigned else first we acquire lock and create the kms with connection + * In mean time the rest of the request will wait until creation is finished followed by getting the created on + * In real time this would be milliseconds + */ const getOrgKmsKeyId = async (orgId: string) => { let org = await orgDAL.findById(orgId); @@ -176,7 +201,12 @@ export const kmsServiceFactory = ({ return org.kmsDefaultKeyId; }; - const decryptWithKmsKey = async ({ kmsId }: Omit) => { + const decryptWithKmsKey = async ({ + kmsId, + depth = 0 + }: Omit & { depth?: number }) => { + if (depth > 2) throw new BadRequestError({ message: "KMS depth max limit" }); + const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId); if (!kmsDoc) { throw new NotFoundError({ message: "KMS ID not found" }); @@ -189,8 +219,12 @@ export const kmsServiceFactory = ({ throw new Error("Invalid organization KMS"); } + // The idea is external kms connection info is encrypted by an org default KMS + // This could be external kms(in future) but at the end of the day, the end KMS will be an infisical internal one + // we put a limit of depth to avoid too many cycles const orgKmsDecryptor = await decryptWithKmsKey({ - kmsId: kmsDoc.orgKms.id + kmsId: kmsDoc.orgKms.id, + depth: depth + 1 }); const orgKmsDataKey = await orgKmsDecryptor({ @@ -303,7 +337,7 @@ export const kmsServiceFactory = ({ }; }; - const getOrgKmsDataKey = async (orgId: string) => { + const $getOrgKmsDataKey = async (orgId: string) => { const kmsKeyId = await getOrgKmsKeyId(orgId); let org = await orgDAL.findById(orgId); @@ -313,7 +347,7 @@ export const kmsServiceFactory = ({ if (!org.kmsEncryptedDataKey) { const lock = await keyStore - .acquireLock([KeyStorePrefixes.KmsOrgDataKeyCreation, orgId], 3000, { retryCount: 3 }) + .acquireLock([KeyStorePrefixes.KmsOrgDataKeyCreation, orgId], 500, { retryCount: 0 }) .catch(() => null); try { @@ -448,14 +482,7 @@ export const kmsServiceFactory = ({ return project.kmsSecretManagerKeyId; }; - const getProjectSecretManagerKmsKey = async (projectId: string) => { - const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId); - const kmsKey = await kmsDAL.findByIdWithAssociatedKms(kmsKeyId); - - return kmsKey; - }; - - const getProjectSecretManagerKmsDataKey = async (projectId: string) => { + const $getProjectSecretManagerKmsDataKey = async (projectId: string) => { const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId); let project = await projectDAL.findById(projectId); @@ -528,21 +555,58 @@ export const kmsServiceFactory = ({ }); }; - const updateProjectSecretManagerKmsKey = async (projectId: string, kmsId: string) => { - const currentKms = await getProjectSecretManagerKmsKey(projectId); + const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO) => { + switch (dto.type) { + case KmsDataKey.SecretManager: { + return $getProjectSecretManagerKmsDataKey(dto.projectId); + } + default: { + return $getOrgKmsDataKey(dto.orgId); + } + } + }; - if ((currentKms.isReserved && kmsId === INTERNAL_KMS_KEY_ID) || currentKms.id === kmsId) { - return currentKms; + // by keeping the decrypted data key in inner scope + // none of the entities outside can interact directly or expose the data key + const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO) => { + const dataKey = await $getDataKey(encryptionContext); + const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); + + return { + encryptor: ({ plainText }: Pick) => { + const encryptedPlainTextBlob = cipher.encrypt(plainText, dataKey); + + // Buffer#1 encrypted text + Buffer#2 version number + const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 + const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]); + return { cipherTextBlob }; + }, + decryptor: ({ cipherTextBlob: versionedCipherTextBlob }: Pick) => { + const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH); + const decryptedBlob = cipher.decrypt(cipherTextBlob, dataKey); + return decryptedBlob; + } + }; + }; + + const updateProjectSecretManagerKmsKey = async ({ projectId, kms }: TUpdateProjectSecretManagerKmsKeyDTO) => { + const kmsKeyId = await getProjectSecretManagerKmsKeyId(projectId); + const currentKms = await kmsDAL.findById(kmsKeyId); + + // case: internal kms -> internal kms. no change needed + if (kms.type === KmsType.Internal && currentKms.isReserved) { + return KmsSanitizedSchema.parseAsync({ isExternal: false, ...currentKms }); } - if (kmsId !== INTERNAL_KMS_KEY_ID) { + if (kms.type === KmsType.External) { + // validate kms is scoped in org + const { kmsId } = kms; const project = await projectDAL.findById(projectId); if (!project) { throw new NotFoundError({ message: "Project not found." }); } - const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId); if (!kmsDoc) { throw new NotFoundError({ message: "KMS ID not found." }); @@ -555,38 +619,36 @@ export const kmsServiceFactory = ({ } } - const dataKey = await getProjectSecretManagerKmsDataKey(projectId); - + const dataKey = await $getProjectSecretManagerKmsDataKey(projectId); return kmsDAL.transaction(async (tx) => { const project = await projectDAL.findById(projectId, tx); - let newKmsId = kmsId; - - if (newKmsId === INTERNAL_KMS_KEY_ID) { - const key = await generateKmsKey({ + let kmsId; + if (kms.type === KmsType.Internal) { + const internalKms = await generateKmsKey({ isReserved: true, orgId: project.orgId, tx }); - - newKmsId = key.id; + kmsId = internalKms.id; + } else { + kmsId = kms.kmsId; } - const kmsEncryptor = await encryptWithKmsKey({ kmsId: newKmsId }, tx); + const kmsEncryptor = await encryptWithKmsKey({ kmsId }, tx); const { cipherTextBlob } = await kmsEncryptor({ plainText: dataKey }); await projectDAL.updateById( projectId, { - kmsSecretManagerKeyId: newKmsId, + kmsSecretManagerKeyId: kmsId, kmsSecretManagerEncryptedDataKey: cipherTextBlob }, tx ); - if (currentKms.isReserved) { await kmsDAL.deleteById(currentKms.id, tx); } - - return kmsDAL.findByIdWithAssociatedKms(newKmsId, tx); + const newKms = await kmsDAL.findById(kmsId, tx); + return KmsSanitizedSchema.parseAsync({ isExternal: !currentKms.isReserved, ...newKms }); }); }; @@ -598,13 +660,15 @@ export const kmsServiceFactory = ({ }); } - const secretManagerDataKey = await getProjectSecretManagerKmsDataKey(projectId); + const secretManagerDataKey = await $getProjectSecretManagerKmsDataKey(projectId); const kmsKeyIdForEncrypt = await getOrgKmsKeyId(project.orgId); const kmsEncryptor = await encryptWithKmsKey({ kmsId: kmsKeyIdForEncrypt }); - const { cipherTextBlob: encryptedSecretManagerDataKey } = await kmsEncryptor({ plainText: secretManagerDataKey }); + const { cipherTextBlob: encryptedSecretManagerDataKeyWithOrgKms } = await kmsEncryptor({ + plainText: secretManagerDataKey + }); // backup format: version.projectId.kmsFunction.kmsId.Base64(encryptedDataKey).verificationHash - let secretManagerBackup = `v1.${projectId}.secretManager.${kmsKeyIdForEncrypt}.${encryptedSecretManagerDataKey.toString( + let secretManagerBackup = `v1.${projectId}.secretManager.${kmsKeyIdForEncrypt}.${encryptedSecretManagerDataKeyWithOrgKms.toString( "base64" )}`; @@ -678,39 +742,8 @@ export const kmsServiceFactory = ({ message: "KMS not found" }); } - - return kms; - }; - - const $getDataKey = async (dto: TEncryptWithKmsDataKeyDTO) => { - switch (dto.type) { - case KmsDataKey.SecretManager: { - return getProjectSecretManagerKmsDataKey(dto.projectId); - } - default: { - return getProjectSecretManagerKmsDataKey(dto.orgId); - } - } - }; - - const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO) => { - const dataKey = await $getDataKey(encryptionContext); - const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); - return { - encryptor: ({ plainText }: Pick) => { - const encryptedPlainTextBlob = cipher.encrypt(plainText, dataKey); - - // Buffer#1 encrypted text + Buffer#2 version number - const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 - const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]); - return { cipherTextBlob }; - }, - decryptor: ({ cipherTextBlob: versionedCipherTextBlob }: Pick) => { - const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH); - const decryptedBlob = cipher.decrypt(cipherTextBlob, dataKey); - return decryptedBlob; - } - }; + const { id, slug, orgId, isExternal } = kms; + return { id, slug, orgId, isExternal }; }; const startService = async () => { @@ -762,14 +795,11 @@ export const kmsServiceFactory = ({ startService, generateKmsKey, encryptWithKmsKey, - encryptWithInputKey, decryptWithKmsKey, + encryptWithInputKey, decryptWithInputKey, getOrgKmsKeyId, getProjectSecretManagerKmsKeyId, - getOrgKmsDataKey, - getProjectSecretManagerKmsDataKey, - getProjectSecretManagerKmsKey, updateProjectSecretManagerKmsKey, getProjectKeyBackup, loadProjectKeyBackup, diff --git a/backend/src/services/kms/kms-types.ts b/backend/src/services/kms/kms-types.ts index 335c67868..dc7de10a4 100644 --- a/backend/src/services/kms/kms-types.ts +++ b/backend/src/services/kms/kms-types.ts @@ -1,5 +1,25 @@ import { Knex } from "knex"; +export enum KmsDataKey { + Organization, + SecretManager + // CertificateManager +} + +export enum KmsType { + External = "external", + Internal = "internal" +} + +export type TEncryptWithKmsDataKeyDTO = + | { type: KmsDataKey.Organization; orgId: string } + | { type: KmsDataKey.SecretManager; projectId: string }; +// akhilmhdh: not implemented yet +// | { +// type: KmsDataKey.CertificateManager; +// projectId: string; +// }; + export type TGenerateKMSDTO = { orgId: string; isReserved?: boolean; @@ -27,17 +47,7 @@ export type TDecryptWithKeyDTO = { cipherTextBlob: Buffer; }; -export enum KmsDataKey { - Organization, - SecretManager - // CertificateManager -} - -export type TEncryptWithKmsDataKeyDTO = - | { type: KmsDataKey.Organization; orgId: string } - | { type: KmsDataKey.SecretManager; projectId: string }; -// akhilmhdh: not implemented yet -// | { -// type: KmsDataKey.CertificateManager; -// projectId: string; -// }; +export type TUpdateProjectSecretManagerKmsKeyDTO = { + projectId: string; + kms: { type: KmsType.Internal } | { type: KmsType.External; kmsId: string }; +}; diff --git a/backend/src/services/project/project-service.ts b/backend/src/services/project/project-service.ts index a00c04f25..bfb7fcbab 100644 --- a/backend/src/services/project/project-service.ts +++ b/backend/src/services/project/project-service.ts @@ -650,7 +650,7 @@ export const projectServiceFactory = ({ const updateProjectKmsKey = async ({ projectId, - secretManagerKmsKeyId, + kms, actor, actorId, actorAuthMethod, @@ -666,7 +666,10 @@ export const projectServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms); - const secretManagerKmsKey = await kmsService.updateProjectSecretManagerKmsKey(projectId, secretManagerKmsKeyId); + const secretManagerKmsKey = await kmsService.updateProjectSecretManagerKmsKey({ + projectId, + kms + }); return { secretManagerKmsKey diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 43156f415..1c2279c7a 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -3,6 +3,7 @@ import { TProjectPermission } from "@app/lib/types"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; import { CaStatus } from "../certificate-authority/certificate-authority-types"; +import { KmsType } from "../kms/kms-types"; export enum ProjectFilterType { ID = "id", @@ -106,7 +107,7 @@ export type TListProjectCertsDTO = { } & Omit; export type TUpdateProjectKmsDTO = { - secretManagerKmsKeyId: string; + kms: { type: KmsType.Internal } | { type: KmsType.External; kmsId: string }; } & TProjectPermission; export type TLoadProjectKmsBackupDTO = { diff --git a/frontend/src/hooks/api/kms/mutations.tsx b/frontend/src/hooks/api/kms/mutations.tsx index db2248a9e..c15ddd1d0 100644 --- a/frontend/src/hooks/api/kms/mutations.tsx +++ b/frontend/src/hooks/api/kms/mutations.tsx @@ -3,7 +3,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { kmsKeys } from "./queries"; -import { AddExternalKmsType } from "./types"; +import { AddExternalKmsType, KmsType } from "./types"; export const useAddExternalKms = (orgId: string) => { const queryClient = useQueryClient(); @@ -66,7 +66,9 @@ export const useRemoveExternalKms = (orgId: string) => { export const useUpdateProjectKms = (projectId: string) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: async (updatedData: { secretManagerKmsKeyId: string }) => { + mutationFn: async ( + updatedData: { type: KmsType.Internal } | { type: KmsType.External; kmsId: string } + ) => { const { data } = await apiRequest.patch(`/api/v1/workspace/${projectId}/kms`, updatedData); return data; diff --git a/frontend/src/hooks/api/kms/types.ts b/frontend/src/hooks/api/kms/types.ts index 5808a71fc..7b726e6d3 100644 --- a/frontend/src/hooks/api/kms/types.ts +++ b/frontend/src/hooks/api/kms/types.ts @@ -29,6 +29,11 @@ export type KmsListEntry = { }; }; +export enum KmsType { + Internal = "internal", + External = "external" +} + export enum ExternalKmsProvider { AWS = "aws" } diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/EncryptionTab/EncryptionTab.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/EncryptionTab/EncryptionTab.tsx index 66fc5c617..b32111792 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/EncryptionTab/EncryptionTab.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/EncryptionTab/EncryptionTab.tsx @@ -31,7 +31,7 @@ import { useUpdateProjectKms } from "@app/hooks/api"; import { fetchProjectKmsBackup } from "@app/hooks/api/kms/queries"; -import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types"; +import { INTERNAL_KMS_KEY_ID, KmsType } from "@app/hooks/api/kms/types"; import { Organization, Workspace } from "@app/hooks/api/types"; const formSchema = z.object({ @@ -207,7 +207,9 @@ export const EncryptionTab = () => { const { data: externalKmsList } = useGetExternalKmsList(currentOrg?.id!); const { data: activeKms } = useGetActiveProjectKms(currentWorkspace?.id!); - const { mutateAsync: updateProjectKms } = useUpdateProjectKms(currentWorkspace?.id!); + const { mutateAsync: updateProjectKms, isLoading: isUpdatingProjectKms } = useUpdateProjectKms( + currentWorkspace?.id! + ); const { popUp, handlePopUpToggle, handlePopUpOpen } = usePopUp([ "createBackupConfirmation", "loadBackup" @@ -239,9 +241,11 @@ export const EncryptionTab = () => { const onUpdateProjectKms = async (data: TForm) => { try { - await updateProjectKms({ - secretManagerKmsKeyId: data.kmsKeyId - }); + await updateProjectKms( + data.kmsKeyId === INTERNAL_KMS_KEY_ID + ? { type: KmsType.Internal } + : { type: KmsType.External, kmsId: data.kmsKeyId } + ); createNotification({ text: "Successfully updated project KMS", @@ -287,10 +291,9 @@ export const EncryptionTab = () => {