feat: updated kms service to return only kms details and some more minor changes

This commit is contained in:
=
2024-07-24 00:56:23 +05:30
parent f7ef86eb11
commit 3e0ae5765f
11 changed files with 200 additions and 142 deletions

View File

@@ -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({

View File

@@ -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<TKmsServiceFactory, "getOrgKmsKeyId" | "createCipherPairWithDataKey">;
kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@@ -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
});

View File

@@ -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<TProjectEnvDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findById" | "findProjectById">;
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
kmsService: Pick<
TKmsServiceFactory,
"getProjectSecretManagerKmsDataKey" | "encryptWithInputKey" | "decryptWithInputKey"
>;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
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<TSecretApprovalRequestsSecretsV2Insert, "requestId">[] = [];
const commitTagIds: Record<string, string[]> = {};
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];

View File

@@ -10,6 +10,8 @@ export type TKmsKeyDALFactory = ReturnType<typeof kmskeyDALFactory>;
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)

View File

@@ -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<typeof kmsServiceFactory>;
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<TEncryptionWithKeyDTO, "plainText">) => {
// 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<TDecryptWithKeyDTO, "cipherTextBlob">) => {
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<TDecryptWithKmsDTO, "cipherTextBlob">) => {
const decryptWithKmsKey = async ({
kmsId,
depth = 0
}: Omit<TDecryptWithKmsDTO, "cipherTextBlob"> & { 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<TEncryptWithKmsDTO, "plainText">) => {
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<TDecryptWithKeyDTO, "cipherTextBlob">) => {
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<TEncryptWithKmsDTO, "plainText">) => {
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<TDecryptWithKeyDTO, "cipherTextBlob">) => {
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,

View File

@@ -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 };
};

View File

@@ -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

View File

@@ -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<TProjectPermission, "projectId">;
export type TUpdateProjectKmsDTO = {
secretManagerKmsKeyId: string;
kms: { type: KmsType.Internal } | { type: KmsType.External; kmsId: string };
} & TProjectPermission;
export type TLoadProjectKmsBackupDTO = {

View File

@@ -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;

View File

@@ -29,6 +29,11 @@ export type KmsListEntry = {
};
};
export enum KmsType {
Internal = "internal",
External = "external"
}
export enum ExternalKmsProvider {
AWS = "aws"
}

View File

@@ -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 = () => {
<FormControl errorText={error?.message} isError={Boolean(error)}>
<Select
{...field}
isDisabled={!isAllowed}
onValueChange={(e) => {
onChange(e);
}}
isDisabled={!isAllowed || isUpdatingProjectKms}
onValueChange={onChange}
isLoading={isUpdatingProjectKms}
className="w-3/4 bg-mineshaft-600"
>
<SelectItem value={INTERNAL_KMS_KEY_ID} key="kms-internal">