From 8d4115925c1af621b563ab0fc422f9489a5846e6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 4 Nov 2024 19:00:24 +0400 Subject: [PATCH] requested changes --- backend/e2e-test/vitest-environment-knex.ts | 8 ++++---- .../20241028134337_kms-root-cfg-hsm.ts | 2 +- backend/src/lib/config/env.ts | 2 +- backend/src/main.ts | 12 ++++++------ backend/src/server/app.ts | 2 +- backend/src/server/routes/index.ts | 4 ++-- backend/src/services/hsm/hsm-fns.ts | 7 ++----- backend/src/services/hsm/hsm-service.ts | 11 +++-------- backend/src/services/hsm/hsm-types.ts | 11 +++++++++++ backend/src/services/kms/kms-service.ts | 18 ++++++++---------- backend/src/services/kms/kms-types.ts | 4 ++-- .../super-admin/super-admin-service.ts | 6 +++--- frontend/src/hooks/api/admin/types.ts | 4 ++-- frontend/src/hooks/useFileDownload.tsx | 13 ------------- .../admin/DashboardPage/EncryptionPanel.tsx | 2 +- 15 files changed, 47 insertions(+), 59 deletions(-) create mode 100644 backend/src/services/hsm/hsm-types.ts delete mode 100644 frontend/src/hooks/useFileDownload.tsx diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index d44fc0729..ce8301690 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -16,7 +16,7 @@ import { initDbConnection } from "@app/db"; import { queueServiceFactory } from "@app/queue"; import { keyStoreFactory } from "@app/keystore/keystore"; import { Redis } from "ioredis"; -import { initializePkcs11Module } from "@app/services/hsm/hsm-fns"; +import { initializeHsmModule } from "@app/services/hsm/hsm-fns"; dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true }); export default { @@ -56,10 +56,10 @@ export default { const queue = queueServiceFactory(cfg.REDIS_URL); const keyStore = keyStoreFactory(cfg.REDIS_URL); - const pkcs11Module = initializePkcs11Module(); - pkcs11Module.initialize(); + const hsmModule = initializeHsmModule(); + hsmModule.initialize(); - const server = await main({ db, smtp, logger, queue, keyStore, hsmModule: pkcs11Module.getModule() }); + const server = await main({ db, smtp, logger, queue, keyStore, hsmModule: hsmModule.getModule() }); // @ts-expect-error type globalThis.testServer = server; diff --git a/backend/src/db/migrations/20241028134337_kms-root-cfg-hsm.ts b/backend/src/db/migrations/20241028134337_kms-root-cfg-hsm.ts index a586f94b8..501eccb8b 100644 --- a/backend/src/db/migrations/20241028134337_kms-root-cfg-hsm.ts +++ b/backend/src/db/migrations/20241028134337_kms-root-cfg-hsm.ts @@ -7,7 +7,7 @@ export async function up(knex: Knex): Promise { const hasTimestampsCol = await knex.schema.hasColumn(TableName.KmsServerRootConfig, "createdAt"); await knex.schema.alterTable(TableName.KmsServerRootConfig, (t) => { - if (!hasEncryptionStrategy) t.string("encryptionStrategy").defaultTo("BASIC"); + if (!hasEncryptionStrategy) t.string("encryptionStrategy").defaultTo("SOFTWARE"); if (!hasTimestampsCol) t.timestamps(true, true, true); }); } diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 7662bd8be..cb41ebd72 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -198,7 +198,7 @@ const envSchema = z }) // To ensure that basic encryption is always possible. .refine( - (data) => data.ENCRYPTION_KEY != null || data.ROOT_ENCRYPTION_KEY != null, + (data) => Boolean(data.ENCRYPTION_KEY) || Boolean(data.ROOT_ENCRYPTION_KEY), "Either ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY must be defined." ) .transform((data) => ({ diff --git a/backend/src/main.ts b/backend/src/main.ts index f0ed3fddb..db67e7a59 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -9,7 +9,7 @@ import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; import { main } from "./server/app"; import { bootstrapCheck } from "./server/boot-strap-check"; -import { initializePkcs11Module } from "./services/hsm/hsm-fns"; +import { initializeHsmModule } from "./services/hsm/hsm-fns"; import { smtpServiceFactory } from "./services/smtp/smtp-service"; dotenv.config(); @@ -54,17 +54,17 @@ const run = async () => { const queue = queueServiceFactory(appCfg.REDIS_URL); const keyStore = keyStoreFactory(appCfg.REDIS_URL); - const pkcs11Module = initializePkcs11Module(); - pkcs11Module.initialize(); + const hsmModule = initializeHsmModule(); + hsmModule.initialize(); - const server = await main({ db, auditLogDb, hsmModule: pkcs11Module.getModule(), smtp, logger, queue, keyStore }); + const server = await main({ db, auditLogDb, hsmModule: hsmModule.getModule(), smtp, logger, queue, keyStore }); const bootstrap = await bootstrapCheck({ db }); // eslint-disable-next-line process.on("SIGINT", async () => { await server.close(); await db.destroy(); - pkcs11Module.finalize(); + hsmModule.finalize(); process.exit(0); }); @@ -72,7 +72,7 @@ const run = async () => { process.on("SIGTERM", async () => { await server.close(); await db.destroy(); - pkcs11Module.finalize(); + hsmModule.finalize(); process.exit(0); }); diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 03f3f98f9..4d002f3f5 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -17,7 +17,7 @@ import { Logger } from "pino"; import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig, IS_PACKAGED } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; -import { HsmModule } from "@app/services/hsm/hsm-fns"; +import { HsmModule } from "@app/services/hsm/hsm-types"; import { TSmtpService } from "@app/services/smtp/smtp-service"; import { globalRateLimiterCfg } from "./config/rateLimiter"; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 907ef66e7..4c7aee7ff 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -107,8 +107,8 @@ import { externalMigrationServiceFactory } from "@app/services/external-migratio import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal"; import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service"; -import { HsmModule } from "@app/services/hsm/hsm-fns"; import { hsmServiceFactory } from "@app/services/hsm/hsm-service"; +import { HsmModule } from "@app/services/hsm/hsm-types"; import { identityDALFactory } from "@app/services/identity/identity-dal"; import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal"; @@ -363,7 +363,7 @@ export const registerRoutes = async ( const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore }); const hsmService = hsmServiceFactory({ - pkcs11Module: hsmModule + hsmModule }); const kmsService = kmsServiceFactory({ diff --git a/backend/src/services/hsm/hsm-fns.ts b/backend/src/services/hsm/hsm-fns.ts index 83b05568e..746948105 100644 --- a/backend/src/services/hsm/hsm-fns.ts +++ b/backend/src/services/hsm/hsm-fns.ts @@ -3,12 +3,9 @@ import * as grapheneLib from "graphene-pk11"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; -export type HsmModule = { - module: grapheneLib.Module | null; - graphene: typeof grapheneLib; -}; +import { HsmModule } from "./hsm-types"; -export const initializePkcs11Module = () => { +export const initializeHsmModule = () => { const appCfg = getConfig(); let module: grapheneLib.Module | null = null; diff --git a/backend/src/services/hsm/hsm-service.ts b/backend/src/services/hsm/hsm-service.ts index 8f9aa8556..2208a7295 100644 --- a/backend/src/services/hsm/hsm-service.ts +++ b/backend/src/services/hsm/hsm-service.ts @@ -3,21 +3,16 @@ import grapheneLib from "graphene-pk11"; import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; -import { HsmModule } from "./hsm-fns"; +import { HsmModule, RequiredMechanisms } from "./hsm-types"; type THsmServiceFactoryDep = { - pkcs11Module: HsmModule; + hsmModule: HsmModule; }; const SESSION_TIMEOUT = 5 * 60 * 1000; // 5 minutes const USER_ALREADY_LOGGED_IN_ERROR = "CKR_USER_ALREADY_LOGGED_IN"; export type THsmServiceFactory = ReturnType; -enum RequiredMechanisms { - AesGcm = "AES_GCM", - AesKeyWrap = "AES_KEY_WRAP" -} - class HsmSessionManager { private session: grapheneLib.Session | null = null; @@ -114,7 +109,7 @@ class HsmSessionManager { } // eslint-disable-next-line no-empty-pattern -export const hsmServiceFactory = ({ pkcs11Module: { module, graphene } }: THsmServiceFactoryDep) => { +export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServiceFactoryDep) => { const appCfg = getConfig(); // Constants for buffer structure diff --git a/backend/src/services/hsm/hsm-types.ts b/backend/src/services/hsm/hsm-types.ts new file mode 100644 index 000000000..e7c33ffda --- /dev/null +++ b/backend/src/services/hsm/hsm-types.ts @@ -0,0 +1,11 @@ +import * as grapheneLib from "graphene-pk11"; + +export type HsmModule = { + module: grapheneLib.Module | null; + graphene: typeof grapheneLib; +}; + +export enum RequiredMechanisms { + AesGcm = "AES_GCM", + AesKeyWrap = "AES_KEY_WRAP" +} diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index df6f4692b..102333038 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -629,7 +629,7 @@ export const kmsServiceFactory = ({ const $decryptRootKey = async (kmsRootConfig: TKmsRootConfig) => { // case 1: root key is encrypted with HSM - if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.Hsm) { + if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.HSM) { if (!hsmService.isActive()) { throw new Error("Unable to decrypt root KMS key. HSM service is inactive. Did you configure the HSM?"); } @@ -637,8 +637,8 @@ export const kmsServiceFactory = ({ return hsmService.decrypt(kmsRootConfig.encryptedRootKey); } - // case 2: root key is encrypted with basic encryption - if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.Basic) { + // case 2: root key is encrypted with software encryption + if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.Software) { const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); const encryptionKeyBuffer = $getBasicEncryptionKey(); @@ -649,14 +649,14 @@ export const kmsServiceFactory = ({ }; const $encryptRootKey = async (plainKeyBuffer: Buffer, strategy: RootKeyEncryptionStrategy) => { - if (strategy === RootKeyEncryptionStrategy.Hsm) { + if (strategy === RootKeyEncryptionStrategy.HSM) { if (!hsmService.isActive()) { throw new Error("Unable to encrypt root KMS key. HSM service is inactive. Did you configure the HSM?"); } return hsmService.encrypt(plainKeyBuffer); } - if (strategy === RootKeyEncryptionStrategy.Basic) { + if (strategy === RootKeyEncryptionStrategy.Software) { const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); const encryptionKeyBuffer = $getBasicEncryptionKey(); @@ -870,9 +870,7 @@ export const kmsServiceFactory = ({ const decryptedRootKey = await $decryptRootKey(kmsRootConfig).catch((err) => { logger.error(err, `KMS: Failed to decrypt ROOT Key [strategy=${kmsRootConfig.encryptionStrategy}]`); - // We do not want to throw on startup. If the HSM has issues, this will throw an error, causing the entire API to shut down. - // If the API shuts down, the user will have no way to do recovery by importing their backup decryption key and rolling back to basic encryption. - return Buffer.alloc(0); + throw err; }); // set the flag so that other instance nodes can start @@ -885,7 +883,7 @@ export const kmsServiceFactory = ({ // 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.Basic).catch((err) => { + const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); throw err; }); @@ -894,7 +892,7 @@ export const kmsServiceFactory = ({ // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition id: KMS_ROOT_CONFIG_UUID, encryptedRootKey, - encryptionStrategy: RootKeyEncryptionStrategy.Basic + encryptionStrategy: RootKeyEncryptionStrategy.Software }); // set the flag so that other instance nodes can start diff --git a/backend/src/services/kms/kms-types.ts b/backend/src/services/kms/kms-types.ts index e1aa7b5e0..f655d4b5d 100644 --- a/backend/src/services/kms/kms-types.ts +++ b/backend/src/services/kms/kms-types.ts @@ -58,6 +58,6 @@ export type TUpdateProjectSecretManagerKmsKeyDTO = { }; export enum RootKeyEncryptionStrategy { - Basic = "BASIC", - Hsm = "HSM" + Software = "SOFTWARE", + HSM = "HSM" } diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 97f314db8..370890a09 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -306,16 +306,16 @@ export const superAdminServiceFactory = ({ const enabledStrategies: { enabled: boolean; strategy: RootKeyEncryptionStrategy; name: string }[] = []; if (appCfg.ROOT_ENCRYPTION_KEY || appCfg.ENCRYPTION_KEY) { - const basicStrategy = RootKeyEncryptionStrategy.Basic; + const basicStrategy = RootKeyEncryptionStrategy.Software; enabledStrategies.push({ - name: "Regular Encryption", + name: "Software-based Encryption", enabled: selectedStrategy === basicStrategy, strategy: basicStrategy }); } if (appCfg.isHsmConfigured) { - const hsmStrategy = RootKeyEncryptionStrategy.Hsm; + const hsmStrategy = RootKeyEncryptionStrategy.HSM; enabledStrategies.push({ name: "Hardware Security Module (HSM)", diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 0fa6d23ef..7d35f70be 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -64,6 +64,6 @@ export type TGetServerRootKmsEncryptionDetails = { }; export enum RootKeyEncryptionStrategy { - Basic = "BASIC", - Hsm = "HSM" + Software = "SOFTWARE", + HSM = "HSM" } diff --git a/frontend/src/hooks/useFileDownload.tsx b/frontend/src/hooks/useFileDownload.tsx deleted file mode 100644 index 6cf833cde..000000000 --- a/frontend/src/hooks/useFileDownload.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useCallback } from "react"; - -export const useFileDownload = () => { - return useCallback((content: string, filename: string) => { - const downloadUrl = `data:text/plain;charset=utf-8,${encodeURIComponent(content)}`; - const link = document.createElement("a"); - link.href = downloadUrl; - link.setAttribute("download", filename); - document.body.appendChild(link); - link.click(); - link.remove(); - }, []); -}; diff --git a/frontend/src/views/admin/DashboardPage/EncryptionPanel.tsx b/frontend/src/views/admin/DashboardPage/EncryptionPanel.tsx index ad32df998..8be1982d8 100644 --- a/frontend/src/views/admin/DashboardPage/EncryptionPanel.tsx +++ b/frontend/src/views/admin/DashboardPage/EncryptionPanel.tsx @@ -38,7 +38,7 @@ export const EncryptionPanel = ({ rootKmsDetails }: Props) => { values: { encryptionStrategy: rootKmsDetails?.strategies?.find((s) => s.enabled)?.strategy ?? - RootKeyEncryptionStrategy.Basic + RootKeyEncryptionStrategy.Software } });