misc: modified encryption/decryption of external kms config

This commit is contained in:
Sheen Capadngan
2024-07-30 23:03:13 +05:30
committed by =
parent 5eafdba6c8
commit 26a5d74b14
4 changed files with 95 additions and 17 deletions
@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasKmsDataKeyCol = await knex.schema.hasColumn(TableName.Organization, "kmsEncryptedDataKey");
await knex.schema.alterTable(TableName.Organization, (tb) => {
if (!hasKmsDataKeyCol) {
tb.binary("kmsEncryptedDataKey");
}
});
}
export async function down(knex: Knex): Promise<void> {
const hasKmsDataKeyCol = await knex.schema.hasColumn(TableName.Organization, "kmsEncryptedDataKey");
await knex.schema.alterTable(TableName.Organization, (t) => {
if (hasKmsDataKeyCol) {
t.dropColumn("kmsEncryptedDataKey");
}
});
}
+4 -1
View File
@@ -5,6 +5,8 @@
import { z } from "zod"; import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const OrganizationsSchema = z.object({ export const OrganizationsSchema = z.object({
@@ -16,7 +18,8 @@ export const OrganizationsSchema = z.object({
updatedAt: z.date(), updatedAt: z.date(),
authEnforced: z.boolean().default(false).nullable().optional(), authEnforced: z.boolean().default(false).nullable().optional(),
scimEnabled: z.boolean().default(false).nullable().optional(), scimEnabled: z.boolean().default(false).nullable().optional(),
kmsDefaultKeyId: z.string().uuid().nullable().optional() kmsDefaultKeyId: z.string().uuid().nullable().optional(),
kmsEncryptedDataKey: zodBuffer.nullable().optional()
}); });
export type TOrganizations = z.infer<typeof OrganizationsSchema>; export type TOrganizations = z.infer<typeof OrganizationsSchema>;
@@ -22,7 +22,10 @@ import { ExternalKmsAwsSchema, KmsProviders } from "./providers/model";
type TExternalKmsServiceFactoryDep = { type TExternalKmsServiceFactoryDep = {
externalKmsDAL: TExternalKmsDALFactory; externalKmsDAL: TExternalKmsDALFactory;
kmsService: Pick<TKmsServiceFactory, "getOrgKmsKeyId" | "encryptWithKmsKey" | "decryptWithKmsKey">; kmsService: Pick<
TKmsServiceFactory,
"getOrgKmsKeyId" | "decryptWithInputKey" | "encryptWithInputKey" | "getOrgKmsDataKey"
>;
kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">; kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
}; };
@@ -69,10 +72,11 @@ export const externalKmsServiceFactory = ({
throw new BadRequestError({ message: "external kms provided is invalid" }); throw new BadRequestError({ message: "external kms provided is invalid" });
} }
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId); const orgKmsDataKey = await kmsService.getOrgKmsDataKey(actorOrgId);
const kmsEncryptor = await kmsService.encryptWithKmsKey({ const kmsEncryptor = await kmsService.encryptWithInputKey({
kmsId: orgKmsKeyId key: orgKmsDataKey
}); });
const { cipherTextBlob: encryptedProviderInputs } = kmsEncryptor({ const { cipherTextBlob: encryptedProviderInputs } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8") plainText: Buffer.from(sanitizedProviderInput, "utf8")
}); });
@@ -125,12 +129,13 @@ export const externalKmsServiceFactory = ({
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" }); if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
let sanitizedProviderInput = ""; let sanitizedProviderInput = "";
if (provider) { if (provider) {
const kmsDecryptor = await kmsService.decryptWithKmsKey({ const orgKmsDataKey = await kmsService.getOrgKmsDataKey(kmsDoc.orgId);
kmsId: orgDefaultKmsId const kmsDecryptor = await kmsService.decryptWithInputKey({
key: orgKmsDataKey
}); });
const decryptedProviderInputBlob = kmsDecryptor({ const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs cipherTextBlob: externalKmsDoc.encryptedProviderInputs
}); });
@@ -154,8 +159,9 @@ export const externalKmsServiceFactory = ({
let encryptedProviderInputs: Buffer | undefined; let encryptedProviderInputs: Buffer | undefined;
if (sanitizedProviderInput) { if (sanitizedProviderInput) {
const kmsEncryptor = await kmsService.encryptWithKmsKey({ const orgKmsDataKey = await kmsService.getOrgKmsDataKey(actorOrgId);
kmsId: orgDefaultKmsId const kmsEncryptor = await kmsService.encryptWithInputKey({
key: orgKmsDataKey
}); });
const { cipherTextBlob } = kmsEncryptor({ const { cipherTextBlob } = kmsEncryptor({
plainText: Buffer.from(sanitizedProviderInput, "utf8") plainText: Buffer.from(sanitizedProviderInput, "utf8")
@@ -239,10 +245,11 @@ export const externalKmsServiceFactory = ({
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" }); if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId); const orgKmsDataKey = await kmsService.getOrgKmsDataKey(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({ const kmsDecryptor = await kmsService.decryptWithInputKey({
kmsId: orgDefaultKmsId key: orgKmsDataKey
}); });
const decryptedProviderInputBlob = kmsDecryptor({ const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs cipherTextBlob: externalKmsDoc.encryptedProviderInputs
}); });
@@ -278,10 +285,11 @@ export const externalKmsServiceFactory = ({
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" }); if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId); const orgKmsDataKey = await kmsService.getOrgKmsDataKey(kmsDoc.orgId);
const kmsDecryptor = await kmsService.decryptWithKmsKey({ const kmsDecryptor = await kmsService.decryptWithInputKey({
kmsId: orgDefaultKmsId key: orgKmsDataKey
}); });
const decryptedProviderInputBlob = kmsDecryptor({ const decryptedProviderInputBlob = kmsDecryptor({
cipherTextBlob: externalKmsDoc.encryptedProviderInputs cipherTextBlob: externalKmsDoc.encryptedProviderInputs
}); });
+47 -1
View File
@@ -1,3 +1,5 @@
import crypto from "node:crypto";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { Knex } from "knex"; import { Knex } from "knex";
@@ -166,6 +168,50 @@ export const kmsServiceFactory = ({
return keyId; return keyId;
}; };
const getOrgKmsDataKey = async (orgId: string) => {
const kmsKeyId = await getOrgKmsKeyId(orgId);
const orgKmsDataKey = await orgDAL.transaction(async (tx) => {
const org = await orgDAL.findById(orgId, tx);
if (!org) {
throw new BadRequestError({ message: "Org not found" });
}
let encryptedDataKey = org.kmsEncryptedDataKey;
if (!encryptedDataKey) {
const dataKey = crypto.randomBytes(32);
const kmsEncryptor = await encryptWithKmsKey({
kmsId: kmsKeyId
});
const { cipherTextBlob } = kmsEncryptor({
plainText: dataKey
});
encryptedDataKey = cipherTextBlob;
await orgDAL.updateById(
org.id,
{
kmsEncryptedDataKey: encryptedDataKey
},
tx
);
return dataKey;
}
const kmsDecryptor = await decryptWithKmsKey({
kmsId: kmsKeyId
});
return kmsDecryptor({
cipherTextBlob: encryptedDataKey
});
});
return orgKmsDataKey;
};
const getProjectSecretManagerKmsKeyId = async (projectId: string) => { const getProjectSecretManagerKmsKeyId = async (projectId: string) => {
let project = await projectDAL.findById(projectId); let project = await projectDAL.findById(projectId);
if (!project) { if (!project) {
@@ -337,6 +383,6 @@ export const kmsServiceFactory = ({
decryptWithInputKey, decryptWithInputKey,
getOrgKmsKeyId, getOrgKmsKeyId,
getProjectSecretManagerKmsKeyId, getProjectSecretManagerKmsKeyId,
getProjectSecretManagerKmsDataKey getOrgKmsDataKey
}; };
}; };