Merge remote-tracking branch 'origin/main' into feat/kmip-client-management

This commit is contained in:
Sheen Capadngan
2025-02-18 02:50:12 +08:00
507 changed files with 10002 additions and 2517 deletions
+52 -3
View File
@@ -3,12 +3,32 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { KmsKeysSchema, TableName, TInternalKms, TKmsKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { buildFindFilter, ormify, prependTableNameToFindFilter, selectAllTableCols } from "@app/lib/knex";
import { OrderByDirection } from "@app/lib/types";
import { CmekOrderBy, TListCmeksByProjectIdDTO } from "@app/services/cmek/cmek-types";
export type TKmsKeyDALFactory = ReturnType<typeof kmskeyDALFactory>;
type TCmekFindFilter = Parameters<typeof buildFindFilter<TKmsKeys>>[0];
const baseCmekQuery = ({ filter, db, tx }: { db: TDbClient; filter?: TCmekFindFilter; tx?: Knex }) => {
const query = (tx || db.replicaNode())(TableName.KmsKey)
.where(`${TableName.KmsKey}.isReserved`, false)
.join(TableName.InternalKms, `${TableName.InternalKms}.kmsKeyId`, `${TableName.KmsKey}.id`)
.select(
selectAllTableCols(TableName.KmsKey),
db.ref("encryptionAlgorithm").withSchema(TableName.InternalKms),
db.ref("version").withSchema(TableName.InternalKms)
);
if (filter) {
/* eslint-disable @typescript-eslint/no-misused-promises */
void query.where(buildFindFilter(prependTableNameToFindFilter(TableName.KmsKey, filter)));
}
return query;
};
export const kmskeyDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsKey);
@@ -101,7 +121,7 @@ export const kmskeyDALFactory = (db: TDbClient) => {
}
};
const findKmsKeysByProjectId = async (
const listCmeksByProjectId = async (
{
projectId,
offset = 0,
@@ -120,6 +140,7 @@ export const kmskeyDALFactory = (db: TDbClient) => {
void qb.whereILike("name", `%${search}%`);
}
})
.where(`${TableName.KmsKey}.isReserved`, false)
.join(TableName.InternalKms, `${TableName.InternalKms}.kmsKeyId`, `${TableName.KmsKey}.id`)
.select<
(TKmsKeys &
@@ -146,5 +167,33 @@ export const kmskeyDALFactory = (db: TDbClient) => {
}
};
return { ...kmsOrm, findByIdWithAssociatedKms, findKmsKeysByProjectId, findProjectCmeks };
const findCmekById = async (id: string, tx?: Knex) => {
try {
const key = await baseCmekQuery({
filter: { id },
db,
tx
}).first();
return key;
} catch (error) {
throw new DatabaseError({ error, name: "Find by ID - KMS Key" });
}
};
const findCmekByName = async (keyName: string, projectId: string, tx?: Knex) => {
try {
const key = await baseCmekQuery({
filter: { name: keyName, projectId },
db,
tx
}).first();
return key;
} catch (error) {
throw new DatabaseError({ error, name: "Find by Name - KMS Key" });
}
};
return { ...kmsOrm, findByIdWithAssociatedKms, listCmeksByProjectId, findCmekById, findCmekByName, findProjectCmeks };
};
@@ -1,10 +1,24 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { Knex } from "knex";
export type TKmsRootConfigDALFactory = ReturnType<typeof kmsRootConfigDALFactory>;
export const kmsRootConfigDALFactory = (db: TDbClient) => {
const kmsOrm = ormify(db, TableName.KmsServerRootConfig);
return kmsOrm;
const findById = async (id: string, tx?: Knex) => {
try {
const result = await (tx || db)(TableName.KmsServerRootConfig)
.where({ id } as never)
.first("*");
return result;
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });
}
};
return { ...kmsOrm, findById };
};
+30 -53
View File
@@ -12,8 +12,8 @@ import {
TExternalKmsProviderFns
} from "@app/ee/services/external-kms/providers/model";
import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { TEnvConfig } from "@app/lib/config/env";
import { randomSecureBytes } from "@app/lib/crypto";
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
import { generateHash } from "@app/lib/crypto/encryption";
@@ -46,23 +46,22 @@ type TKmsServiceFactoryDep = {
kmsDAL: TKmsKeyDALFactory;
projectDAL: Pick<TProjectDALFactory, "findById" | "updateById" | "transaction">;
orgDAL: Pick<TOrgDALFactory, "findById" | "updateById" | "transaction">;
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById" | "create" | "updateById">;
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById" | "create" | "updateById" | "transaction">;
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "waitTillReady" | "setItemWithExpiry">;
internalKmsDAL: Pick<TInternalKmsDALFactory, "create">;
hsmService: THsmServiceFactory;
envConfig: Pick<TEnvConfig, "ENCRYPTION_KEY" | "ROOT_ENCRYPTION_KEY">;
};
export type TKmsServiceFactory = ReturnType<typeof kmsServiceFactory>;
const KMS_ROOT_CREATION_WAIT_KEY = "wait_till_ready_kms_root_key";
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 = ({
envConfig,
kmsDAL,
kmsRootConfigDAL,
keyStore,
@@ -541,7 +540,8 @@ export const kmsServiceFactory = ({
}
const kmsDecryptor = await decryptWithKmsKey({
kmsId: kmsKeyId
kmsId: kmsKeyId,
tx: trx
});
return kmsDecryptor({
@@ -703,10 +703,8 @@ export const kmsServiceFactory = ({
};
const $getBasicEncryptionKey = () => {
const appCfg = getConfig();
const encryptionKey = appCfg.ENCRYPTION_KEY || appCfg.ROOT_ENCRYPTION_KEY;
const isBase64 = !appCfg.ENCRYPTION_KEY;
const encryptionKey = envConfig.ENCRYPTION_KEY || envConfig.ROOT_ENCRYPTION_KEY;
const isBase64 = !envConfig.ENCRYPTION_KEY;
if (!encryptionKey)
throw new Error(
"Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?"
@@ -942,54 +940,33 @@ export const kmsServiceFactory = ({
return { id, name, orgId, isExternal };
};
// akhilmhdh: a copy of this is made in migrations/utils/kms
const startService = async () => {
const lock = await keyStore.acquireLock([`KMS_ROOT_CFG_LOCK`], 3000, { retryCount: 3 }).catch(() => null);
if (!lock) {
await keyStore.waitTillReady({
key: KMS_ROOT_CREATION_WAIT_KEY,
keyCheckCb: (val) => val === "true",
waitingCb: () => logger.info("KMS. Waiting for leader to finish creation of KMS Root Key")
const kmsRootConfig = await kmsRootConfigDAL.transaction(async (tx) => {
await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.KmsRootKeyInit]);
// check if KMS root key was already generated and saved in DB
const existingRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID);
if (existingRootConfig) return existingRootConfig;
logger.info("KMS: Generating new ROOT Key");
const newRootKey = randomSecureBytes(32);
const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => {
logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key");
throw err;
});
}
// check if KMS root key was already generated and saved in DB
const kmsRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID);
// case 1: a root key already exists in the DB
if (kmsRootConfig) {
if (lock) await lock.release();
logger.info(`KMS: Encrypted ROOT Key found from DB. Decrypting. [strategy=${kmsRootConfig.encryptionStrategy}]`);
const decryptedRootKey = await $decryptRootKey(kmsRootConfig);
// set the flag so that other instance nodes can start
await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true");
logger.info("KMS: Loading ROOT Key into Memory.");
ROOT_ENCRYPTION_KEY = decryptedRootKey;
return;
}
// case 2: no config is found, so we create a new root key with basic encryption
logger.info("KMS: Generating new ROOT Key");
const newRootKey = randomSecureBytes(32);
const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => {
logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key");
throw err;
const newRootConfig = await kmsRootConfigDAL.create({
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
id: KMS_ROOT_CONFIG_UUID,
encryptedRootKey,
encryptionStrategy: RootKeyEncryptionStrategy.Software
});
return newRootConfig;
});
await kmsRootConfigDAL.create({
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
id: KMS_ROOT_CONFIG_UUID,
encryptedRootKey,
encryptionStrategy: RootKeyEncryptionStrategy.Software
});
const decryptedRootKey = await $decryptRootKey(kmsRootConfig);
// set the flag so that other instance nodes can start
await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true");
logger.info("KMS: Saved and loaded ROOT Key into memory");
if (lock) await lock.release();
ROOT_ENCRYPTION_KEY = newRootKey;
logger.info("KMS: Loading ROOT Key into Memory.");
ROOT_ENCRYPTION_KEY = decryptedRootKey;
};
const updateEncryptionStrategy = async (strategy: RootKeyEncryptionStrategy) => {