requested changes

This commit is contained in:
Daniel Hougaard
2024-11-12 02:27:38 +04:00
parent 148f522c58
commit f22a5580a6
8 changed files with 73 additions and 68 deletions
+6 -6
View File
@@ -3,7 +3,7 @@ ARG POSTHOG_API_KEY=posthog-api-key
ARG INTERCOM_ID=intercom-id ARG INTERCOM_ID=intercom-id
ARG CAPTCHA_SITE_KEY=captcha-site-key ARG CAPTCHA_SITE_KEY=captcha-site-key
FROM --platform=linux/amd64 node:20-slim AS base FROM node:20-slim AS base
FROM base AS frontend-dependencies FROM base AS frontend-dependencies
WORKDIR /app WORKDIR /app
@@ -14,7 +14,7 @@ COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
RUN npm ci --only-production --ignore-scripts RUN npm ci --only-production --ignore-scripts
# Rebuild the source code only when needed # Rebuild the source code only when needed
FROM --platform=linux/amd64 base AS frontend-builder FROM base AS frontend-builder
WORKDIR /app WORKDIR /app
# Copy dependencies # Copy dependencies
@@ -39,7 +39,7 @@ ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
RUN npm run build RUN npm run build
# Production image # Production image
FROM --platform=linux/amd64 base AS frontend-runner FROM base AS frontend-runner
WORKDIR /app WORKDIR /app
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
@@ -61,7 +61,7 @@ ENV NEXT_TELEMETRY_DISABLED 1
## ##
## BACKEND ## BACKEND
## ##
FROM --platform=linux/amd64 base AS backend-build FROM base AS backend-build
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/ ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
@@ -85,7 +85,7 @@ RUN npm i -D tsconfig-paths
RUN npm run build RUN npm run build
# Production stage # Production stage
FROM --platform=linux/amd64 base AS backend-runner FROM base AS backend-runner
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/ ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
@@ -106,7 +106,7 @@ COPY --from=backend-build /app .
RUN mkdir frontend-build RUN mkdir frontend-build
# Production stage # Production stage
FROM --platform=linux/amd64 base AS production FROM base AS production
# Install necessary packages # Install necessary packages
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
+28 -29
View File
@@ -27,8 +27,8 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
const HMAC_KEY_SIZE = 256; const HMAC_KEY_SIZE = 256;
const $withSession = async <T>(callbackWithSession: SessionCallback<T>): Promise<T> => { const $withSession = async <T>(callbackWithSession: SessionCallback<T>): Promise<T> => {
const RETRY_INTERVAL = 300; // 300ms between attempts const RETRY_INTERVAL = 200; // 200ms between attempts
const MAX_TIMEOUT = 30_000; // 30 seconds maximum total time const MAX_TIMEOUT = 90_000; // 90 seconds maximum total time
let sessionHandle: pkcs11js.Handle | null = null; let sessionHandle: pkcs11js.Handle | null = null;
@@ -39,7 +39,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
pkcs11.C_CloseSession(sessionHandle); pkcs11.C_CloseSession(sessionHandle);
logger.info("HSM: Terminated session successfully"); logger.info("HSM: Terminated session successfully");
} catch (error) { } catch (error) {
logger.error("Error during session cleanup:", error); logger.error(error, "HSM: Failed to terminate session");
} finally { } finally {
sessionHandle = null; sessionHandle = null;
} }
@@ -82,15 +82,15 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
logger.info("HSM: Successfully authenticated"); logger.info("HSM: Successfully authenticated");
break; break;
} catch (error) { } catch (error) {
// Handle specific error cases
if (error instanceof pkcs11js.Pkcs11Error) { if (error instanceof pkcs11js.Pkcs11Error) {
// Handle specific error cases
if (error.code === pkcs11js.CKR_PIN_INCORRECT) { if (error.code === pkcs11js.CKR_PIN_INCORRECT) {
logger.error(error, `Incorrect PIN detected for HSM slot ${appCfg.HSM_SLOT}`); // We throw instantly here to prevent further attempts, because if too many attempts are made, the HSM will potentially wipe all key material
throw new Error("Incorrect HSM Pin detected. Please check the HSM configuration."); logger.error(error, `HSM: Incorrect PIN detected for HSM slot ${appCfg.HSM_SLOT}`);
throw new Error("HSM: Incorrect HSM Pin detected. Please check the HSM configuration.");
} }
if (error.code === pkcs11js.CKR_USER_ALREADY_LOGGED_IN) { if (error.code === pkcs11js.CKR_USER_ALREADY_LOGGED_IN) {
logger.warn("HSM session already logged in"); logger.warn("HSM: Session already logged in");
} }
} }
throw error; // Re-throw other errors throw error; // Re-throw other errors
@@ -102,7 +102,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
try { try {
pkcs11.C_CloseSession(sessionHandle); pkcs11.C_CloseSession(sessionHandle);
} catch (closeError) { } catch (closeError) {
logger.error("Error closing failed session:", closeError); logger.error(closeError, "HSM: Failed to close session");
} }
sessionHandle = null; sessionHandle = null;
} }
@@ -116,7 +116,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
} }
if (sessionHandle === null) { if (sessionHandle === null) {
throw new Error("Failed to open session after maximum retries"); throw new Error("HSM: Failed to open session after maximum retries");
} }
// Execute callback with session handle // Execute callback with session handle
@@ -124,7 +124,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
removeSession(); removeSession();
return result; return result;
} catch (error) { } catch (error) {
logger.error("Error in HSM session handling:", error); logger.error(error, "HSM: Failed to open session");
throw error; throw error;
} finally { } finally {
// Ensure cleanup // Ensure cleanup
@@ -160,7 +160,6 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
pkcs11.C_FindObjectsFinal(sessionHandle); pkcs11.C_FindObjectsFinal(sessionHandle);
} }
} catch (error) { } catch (error) {
logger.error("Error finding master key:", error);
return null; return null;
} }
}; };
@@ -174,7 +173,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
} catch (error) { } catch (error) {
// If items(0) throws, it means no key was found // If items(0) throws, it means no key was found
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-call
logger.error(error, "Error checking for HSM key presence"); logger.error(error, "HSM: Failed while checking for HSM key presence");
if (error instanceof pkcs11js.Pkcs11Error) { if (error instanceof pkcs11js.Pkcs11Error) {
if (error.code === pkcs11js.CKR_OBJECT_HANDLE_INVALID) { if (error.code === pkcs11js.CKR_OBJECT_HANDLE_INVALID) {
@@ -198,12 +197,12 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
try { try {
const aesKey = $findKey(sessionHandle, HsmKeyType.AES); const aesKey = $findKey(sessionHandle, HsmKeyType.AES);
if (!aesKey) { if (!aesKey) {
throw new Error("AES key not found"); throw new Error("HSM: Encryption failed, AES key not found");
} }
const hmacKey = $findKey(sessionHandle, HsmKeyType.HMAC); const hmacKey = $findKey(sessionHandle, HsmKeyType.HMAC);
if (!hmacKey) { if (!hmacKey) {
throw new Error("HMAC key not found"); throw new Error("HSM: Encryption failed, HMAC key not found");
} }
const iv = Buffer.alloc(IV_LENGTH); const iv = Buffer.alloc(IV_LENGTH);
@@ -244,8 +243,8 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
return Buffer.concat([iv, finalBuffer]); return Buffer.concat([iv, finalBuffer]);
} catch (error) { } catch (error) {
logger.error("Encryption error:", error); logger.error(error, "HSM: Failed to perform encryption");
throw new Error(`Encryption failed: ${(error as Error)?.message}`); throw new Error(`HSM: Encryption failed: ${(error as Error)?.message}`);
} }
}; };
@@ -261,8 +260,8 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
(encryptedBlob: Buffer, providedSession: pkcs11js.Handle): Promise<Buffer>; (encryptedBlob: Buffer, providedSession: pkcs11js.Handle): Promise<Buffer>;
(encryptedBlob: Buffer): Promise<Buffer>; (encryptedBlob: Buffer): Promise<Buffer>;
} = async (encryptedBlob: Buffer, providedSession?: pkcs11js.Handle) => { } = async (encryptedBlob: Buffer, providedSession?: pkcs11js.Handle) => {
if (!isInitialized) { if (!pkcs11 || !isInitialized) {
throw new Error("HSM service not initialized"); throw new Error("PKCS#11 module is not initialized");
} }
const $performDecryption = (sessionHandle: pkcs11js.Handle) => { const $performDecryption = (sessionHandle: pkcs11js.Handle) => {
@@ -279,12 +278,12 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
// Find the keys // Find the keys
const aesKey = $findKey(sessionHandle, HsmKeyType.AES); const aesKey = $findKey(sessionHandle, HsmKeyType.AES);
if (!aesKey) { if (!aesKey) {
throw new Error("AES key not found"); throw new Error("HSM: Decryption failed, AES key not found");
} }
const hmacKey = $findKey(sessionHandle, HsmKeyType.HMAC); const hmacKey = $findKey(sessionHandle, HsmKeyType.HMAC);
if (!hmacKey) { if (!hmacKey) {
throw new Error("HMAC key not found"); throw new Error("HSM: Decryption failed, HMAC key not found");
} }
// Verify HMAC first // Verify HMAC first
@@ -333,12 +332,12 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
// We test the core functionality of the PKCS#11 module that we are using throughout Infisical. This is to ensure that the user doesn't configure a faulty or unsupported HSM device. // We test the core functionality of the PKCS#11 module that we are using throughout Infisical. This is to ensure that the user doesn't configure a faulty or unsupported HSM device.
const $testPkcs11Module = async (session: pkcs11js.Handle) => { const $testPkcs11Module = async (session: pkcs11js.Handle) => {
try { try {
if (!isInitialized) { if (!pkcs11 || !isInitialized) {
throw new Error("HSM service not initialized"); throw new Error("PKCS#11 module is not initialized");
} }
if (!session) { if (!session) {
throw new Error("Session not initialized"); throw new Error("HSM: Attempted to run test without a valid session");
} }
const randomData = pkcs11.C_GenerateRandom(session, Buffer.alloc(500)); const randomData = pkcs11.C_GenerateRandom(session, Buffer.alloc(500));
@@ -350,12 +349,12 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
const decryptedDataHex = decryptedData.toString("hex"); const decryptedDataHex = decryptedData.toString("hex");
if (randomDataHex !== decryptedDataHex && Buffer.compare(randomData, decryptedData)) { if (randomDataHex !== decryptedDataHex && Buffer.compare(randomData, decryptedData)) {
throw new Error("Decrypted data does not match original data"); throw new Error("HSM: Startup test failed. Decrypted data does not match original data");
} }
return true; return true;
} catch (error) { } catch (error) {
logger.error(error, "Error testing PKCS#11 module"); logger.error(error, "HSM: Error testing PKCS#11 module");
return false; return false;
} }
}; };
@@ -411,7 +410,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
keyTemplate keyTemplate
); );
logger.info(`Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`); logger.info(`HSM: Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`);
} }
// Check if HMAC key exists, create if not // Check if HMAC key exists, create if not
@@ -435,7 +434,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
hmacKeyTemplate hmacKeyTemplate
); );
logger.info(`HMAC key created successfully with label: ${appCfg.HSM_KEY_LABEL}_HMAC`); logger.info(`HSM: HMAC key created successfully with label: ${appCfg.HSM_KEY_LABEL}_HMAC`);
} }
// Get slot info to check supported mechanisms // Get slot info to check supported mechanisms
@@ -457,7 +456,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
} }
}); });
} catch (error) { } catch (error) {
logger.error("Error initializing HSM service:", error); logger.error(error, "HSM: Error initializing HSM service:");
throw error; throw error;
} }
}; };
+2 -1
View File
@@ -43,10 +43,11 @@ type TMain = {
// Run the server! // Run the server!
export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore }: TMain) => { export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore }: TMain) => {
const appCfg = getConfig(); const appCfg = getConfig();
const server = fastify({ const server = fastify({
logger: appCfg.NODE_ENV === "test" ? false : logger, logger: appCfg.NODE_ENV === "test" ? false : logger,
trustProxy: true, trustProxy: true,
connectionTimeout: 30 * 1000, connectionTimeout: appCfg.isHsmConfigured ? 90_000 : 30_000,
ignoreTrailingSlash: true, ignoreTrailingSlash: true,
pluginTimeout: 40_000 pluginTimeout: 40_000
}).withTypeProvider<ZodTypeProvider>(); }).withTypeProvider<ZodTypeProvider>();
@@ -208,7 +208,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
strategies: z strategies: z
.object({ .object({
strategy: z.nativeEnum(RootKeyEncryptionStrategy), strategy: z.nativeEnum(RootKeyEncryptionStrategy),
name: z.string(),
enabled: z.boolean() enabled: z.boolean()
}) })
.array() .array()
@@ -303,13 +303,12 @@ export const superAdminServiceFactory = ({
} }
const selectedStrategy = kmsRootCfg.encryptionStrategy; const selectedStrategy = kmsRootCfg.encryptionStrategy;
const enabledStrategies: { enabled: boolean; strategy: RootKeyEncryptionStrategy; name: string }[] = []; const enabledStrategies: { enabled: boolean; strategy: RootKeyEncryptionStrategy }[] = [];
if (appCfg.ROOT_ENCRYPTION_KEY || appCfg.ENCRYPTION_KEY) { if (appCfg.ROOT_ENCRYPTION_KEY || appCfg.ENCRYPTION_KEY) {
const basicStrategy = RootKeyEncryptionStrategy.Software; const basicStrategy = RootKeyEncryptionStrategy.Software;
enabledStrategies.push({ enabledStrategies.push({
name: "Software-based Encryption",
enabled: selectedStrategy === basicStrategy, enabled: selectedStrategy === basicStrategy,
strategy: basicStrategy strategy: basicStrategy
}); });
@@ -318,7 +317,6 @@ export const superAdminServiceFactory = ({
const hsmStrategy = RootKeyEncryptionStrategy.HSM; const hsmStrategy = RootKeyEncryptionStrategy.HSM;
enabledStrategies.push({ enabledStrategies.push({
name: "Hardware Security Module (HSM)",
enabled: selectedStrategy === hsmStrategy, enabled: selectedStrategy === hsmStrategy,
strategy: hsmStrategy strategy: hsmStrategy
}); });
-1
View File
@@ -59,7 +59,6 @@ export type TGetServerRootKmsEncryptionDetails = {
strategies: { strategies: {
strategy: RootKeyEncryptionStrategy; strategy: RootKeyEncryptionStrategy;
enabled: boolean; enabled: boolean;
name: string;
}[]; }[];
}; };
@@ -144,7 +144,7 @@ export const AdminDashboardPage = () => {
<TabList> <TabList>
<div className="flex w-full flex-row border-b border-mineshaft-600"> <div className="flex w-full flex-row border-b border-mineshaft-600">
<Tab value={TabSections.Settings}>General</Tab> <Tab value={TabSections.Settings}>General</Tab>
{!!serverRootKmsDetails && <Tab value={TabSections.Encryption}>Encryption</Tab>} <Tab value={TabSections.Encryption}>Encryption</Tab>
<Tab value={TabSections.Auth}>Authentication</Tab> <Tab value={TabSections.Auth}>Authentication</Tab>
<Tab value={TabSections.RateLimit}>Rate Limit</Tab> <Tab value={TabSections.RateLimit}>Rate Limit</Tab>
<Tab value={TabSections.Integrations}>Integrations</Tab> <Tab value={TabSections.Integrations}>Integrations</Tab>
@@ -329,11 +329,9 @@ export const AdminDashboardPage = () => {
</Button> </Button>
</form> </form>
</TabPanel> </TabPanel>
{!!serverRootKmsDetails && ( <TabPanel value={TabSections.Encryption}>
<TabPanel value={TabSections.Encryption}> <EncryptionPanel rootKmsDetails={serverRootKmsDetails} />
<EncryptionPanel rootKmsDetails={serverRootKmsDetails} /> </TabPanel>
</TabPanel>
)}
<TabPanel value={TabSections.Auth}> <TabPanel value={TabSections.Auth}>
<AuthPanel /> <AuthPanel />
</TabPanel> </TabPanel>
@@ -17,10 +17,15 @@ const formSchema = z.object({
encryptionStrategy: z.nativeEnum(RootKeyEncryptionStrategy) encryptionStrategy: z.nativeEnum(RootKeyEncryptionStrategy)
}); });
const strategies: Record<RootKeyEncryptionStrategy, string> = {
[RootKeyEncryptionStrategy.Software]: "Software-based Encryption",
[RootKeyEncryptionStrategy.HSM]: "Hardware Security Module (HSM)"
};
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
type Props = { type Props = {
rootKmsDetails: TGetServerRootKmsEncryptionDetails; rootKmsDetails?: TGetServerRootKmsEncryptionDetails;
}; };
export const EncryptionPanel = ({ rootKmsDetails }: Props) => { export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
@@ -84,27 +89,33 @@ export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
supported on Enterprise plans. supported on Enterprise plans.
</div> </div>
<Controller {!!rootKmsDetails && (
control={control} <Controller
name="encryptionStrategy" control={control}
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( name="encryptionStrategy"
<FormControl className="max-w-sm" errorText={error?.message} isError={Boolean(error)}> render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<Select <FormControl
className="w-full bg-mineshaft-700" className="max-w-sm"
dropdownContainerClassName="bg-mineshaft-800" errorText={error?.message}
defaultValue={field.value} isError={Boolean(error)}
onValueChange={(e) => onChange(e)}
{...field}
> >
{rootKmsDetails.strategies?.map((strategy) => ( <Select
<SelectItem key={strategy.strategy} value={strategy.strategy}> className="w-full bg-mineshaft-700"
{strategy.name} dropdownContainerClassName="bg-mineshaft-800"
</SelectItem> defaultValue={field.value}
))} onValueChange={(e) => onChange(e)}
</Select> {...field}
</FormControl> >
)} {rootKmsDetails.strategies?.map((strategy) => (
/> <SelectItem key={strategy.strategy} value={strategy.strategy}>
{strategies[strategy.strategy]}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
)}
</div> </div>
<Button <Button