mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
added keystore
This commit is contained in:
@@ -1,14 +1,19 @@
|
|||||||
import grapheneLib from "graphene-pk11";
|
import grapheneLib from "graphene-pk11";
|
||||||
|
|
||||||
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { Lock } from "@app/lib/red-lock";
|
||||||
|
|
||||||
import { HsmModule, RequiredMechanisms } from "./hsm-types";
|
import { HsmModule, RequiredMechanisms } from "./hsm-types";
|
||||||
|
|
||||||
type THsmServiceFactoryDep = {
|
type THsmServiceFactoryDep = {
|
||||||
hsmModule: HsmModule;
|
hsmModule: HsmModule;
|
||||||
|
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "waitTillReady" | "setItemWithExpiry">;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const HSM_SESSION_WAIT_KEY = "wait_till_hsm_session_ready";
|
||||||
|
|
||||||
const USER_ALREADY_LOGGED_IN_ERROR = "CKR_USER_ALREADY_LOGGED_IN";
|
const USER_ALREADY_LOGGED_IN_ERROR = "CKR_USER_ALREADY_LOGGED_IN";
|
||||||
const WRAPPED_KEY_LENGTH = 32 + 8; // AES-256 key + padding
|
const WRAPPED_KEY_LENGTH = 32 + 8; // AES-256 key + padding
|
||||||
|
|
||||||
@@ -17,80 +22,99 @@ export type THsmServiceFactory = ReturnType<typeof hsmServiceFactory>;
|
|||||||
type SyncOrAsync<T> = T | Promise<T>;
|
type SyncOrAsync<T> = T | Promise<T>;
|
||||||
type SessionCallback<T> = (session: grapheneLib.Session) => SyncOrAsync<T>;
|
type SessionCallback<T> = (session: grapheneLib.Session) => SyncOrAsync<T>;
|
||||||
|
|
||||||
export const withSession = async <T>(
|
|
||||||
{ module, graphene }: HsmModule,
|
|
||||||
callbackWithSession: SessionCallback<T>
|
|
||||||
): Promise<T> => {
|
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
let session: grapheneLib.Session | null = null;
|
|
||||||
try {
|
|
||||||
if (!module) {
|
|
||||||
throw new Error("PKCS#11 module is not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new session
|
|
||||||
const slot = module.getSlots(appCfg.HSM_SLOT);
|
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
if (!(slot.flags & graphene.SlotFlag.TOKEN_PRESENT)) {
|
|
||||||
throw new Error("Slot is not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i += 1) {
|
|
||||||
try {
|
|
||||||
// eslint-disable-next-line no-bitwise
|
|
||||||
session = slot.open(graphene.SessionFlag.RW_SESSION | graphene.SessionFlag.SERIAL_SESSION);
|
|
||||||
session.login(appCfg.HSM_PIN!);
|
|
||||||
} catch (error) {
|
|
||||||
if ((error as Error)?.message !== USER_ALREADY_LOGGED_IN_ERROR) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
logger.warn("HSM session already logged in");
|
|
||||||
session = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.warn("Waiting for session to be available...");
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
let sleepAmount = 1_500 * (i + 1);
|
|
||||||
if (sleepAmount > 5000) sleepAmount = 5000;
|
|
||||||
|
|
||||||
setTimeout(resolve, sleepAmount);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
throw new Error("Failed to open session");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the callback and await its result (works for both sync and async)
|
|
||||||
const result = await callbackWithSession(session);
|
|
||||||
return result;
|
|
||||||
} finally {
|
|
||||||
// Clean up session if it was created
|
|
||||||
if (session) {
|
|
||||||
try {
|
|
||||||
session.logout();
|
|
||||||
session.close();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error cleaning up HSM session:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-empty-pattern
|
// eslint-disable-next-line no-empty-pattern
|
||||||
export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServiceFactoryDep) => {
|
export const hsmServiceFactory = ({ hsmModule: { module, graphene }, keyStore }: THsmServiceFactoryDep) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
// Constants for buffer structure
|
// Constants for buffer structure
|
||||||
const IV_LENGTH = 12;
|
const IV_LENGTH = 12;
|
||||||
const TAG_LENGTH = 16;
|
const TAG_LENGTH = 16;
|
||||||
|
|
||||||
|
const $withSession = async <T>(callbackWithSession: SessionCallback<T>): Promise<T> => {
|
||||||
|
const RETRY_INTERVAL = 300; // 300ms between attempts
|
||||||
|
const MAX_TIMEOUT = 30_000; // 30 seconds maximum total time
|
||||||
|
|
||||||
|
let session: grapheneLib.Session | null = null;
|
||||||
|
let lock: Lock | null = null;
|
||||||
|
|
||||||
|
const removeSession = () => {
|
||||||
|
if (session) {
|
||||||
|
session.logout();
|
||||||
|
session.close();
|
||||||
|
session = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!module) {
|
||||||
|
throw new Error("PKCS#11 module is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new session
|
||||||
|
const slot = module.getSlots(appCfg.HSM_SLOT);
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
if (!(slot.flags & graphene.SlotFlag.TOKEN_PRESENT)) {
|
||||||
|
throw new Error("Slot is not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
lock = await keyStore.acquireLock(["HSM_SESSION_LOCK"], 10_000, { retryCount: 3 }).catch(() => null);
|
||||||
|
|
||||||
|
if (!lock) {
|
||||||
|
await keyStore.waitTillReady({
|
||||||
|
key: HSM_SESSION_WAIT_KEY,
|
||||||
|
keyCheckCb: (val) => val === "true",
|
||||||
|
waitingCb: () => logger.info("HSM Lock: Waiting for session to be available...")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
while (Date.now() - startTime < MAX_TIMEOUT) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
session = slot.open(graphene.SessionFlag.RW_SESSION | graphene.SessionFlag.SERIAL_SESSION);
|
||||||
|
session.login(appCfg.HSM_PIN!);
|
||||||
|
// session.login("4311");
|
||||||
|
break;
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as Error)?.message !== USER_ALREADY_LOGGED_IN_ERROR) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
logger.warn("HSM session already logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn(`HSM: No session available. Waiting for session to be available... [retry=${RETRY_INTERVAL}ms]`);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, RETRY_INTERVAL);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
throw new Error("Failed to open session");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the callback and await its result (works for both sync and async)
|
||||||
|
const result = await callbackWithSession(session);
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
removeSession();
|
||||||
|
await keyStore.setItemWithExpiry(HSM_SESSION_WAIT_KEY, 10, "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
// Clean up session if it was created
|
||||||
|
try {
|
||||||
|
removeSession();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Error cleaning up HSM session:");
|
||||||
|
}
|
||||||
|
|
||||||
|
await lock?.release();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const $findMasterKey = (session: grapheneLib.Session) => {
|
const $findMasterKey = (session: grapheneLib.Session) => {
|
||||||
// Find the master key (root key)
|
// Find the master key (root key)
|
||||||
const template = {
|
const template = {
|
||||||
@@ -203,7 +227,7 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
return $performEncryption(providedSession);
|
return $performEncryption(providedSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
const encrypted = await withSession({ module, graphene }, $performEncryption);
|
const encrypted = await $withSession($performEncryption);
|
||||||
|
|
||||||
return encrypted;
|
return encrypted;
|
||||||
};
|
};
|
||||||
@@ -239,7 +263,7 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
if (providedSession) {
|
if (providedSession) {
|
||||||
return $performDecryption(providedSession);
|
return $performDecryption(providedSession);
|
||||||
}
|
}
|
||||||
const decrypted = await withSession({ module, graphene }, (newSession) => $performDecryption(newSession));
|
const decrypted = await $withSession($performDecryption);
|
||||||
|
|
||||||
return decrypted;
|
return decrypted;
|
||||||
};
|
};
|
||||||
@@ -278,7 +302,7 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
let pkcs11TestPassed = false;
|
let pkcs11TestPassed = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pkcs11TestPassed = await withSession({ module, graphene }, $testPkcs11Module);
|
pkcs11TestPassed = await $withSession($testPkcs11Module);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err, "isActive: Error testing PKCS#11 module");
|
logger.error(err, "isActive: Error testing PKCS#11 module");
|
||||||
}
|
}
|
||||||
@@ -290,7 +314,7 @@ export const hsmServiceFactory = ({ hsmModule: { module, graphene } }: THsmServi
|
|||||||
if (!appCfg.isHsmConfigured || !module) return;
|
if (!appCfg.isHsmConfigured || !module) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await withSession({ module, graphene }, async (session) => {
|
await $withSession(async (session) => {
|
||||||
// Check if master key exists, create if not
|
// Check if master key exists, create if not
|
||||||
if (!$keyExists(session)) {
|
if (!$keyExists(session)) {
|
||||||
// Generate 256-bit AES master key with persistent storage
|
// Generate 256-bit AES master key with persistent storage
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
|
|||||||
logger: appCfg.NODE_ENV === "test" ? false : logger,
|
logger: appCfg.NODE_ENV === "test" ? false : logger,
|
||||||
trustProxy: true,
|
trustProxy: true,
|
||||||
connectionTimeout: 30 * 1000,
|
connectionTimeout: 30 * 1000,
|
||||||
ignoreTrailingSlash: true
|
ignoreTrailingSlash: true,
|
||||||
|
pluginTimeout: 40_000
|
||||||
}).withTypeProvider<ZodTypeProvider>();
|
}).withTypeProvider<ZodTypeProvider>();
|
||||||
|
|
||||||
server.setValidatorCompiler(validatorCompiler);
|
server.setValidatorCompiler(validatorCompiler);
|
||||||
|
|||||||
@@ -363,7 +363,8 @@ export const registerRoutes = async (
|
|||||||
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore });
|
const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore });
|
||||||
|
|
||||||
const hsmService = hsmServiceFactory({
|
const hsmService = hsmServiceFactory({
|
||||||
hsmModule
|
hsmModule,
|
||||||
|
keyStore
|
||||||
});
|
});
|
||||||
|
|
||||||
const kmsService = kmsServiceFactory({
|
const kmsService = kmsServiceFactory({
|
||||||
|
|||||||
Reference in New Issue
Block a user