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

View File

@@ -3,7 +3,7 @@ ARG POSTHOG_API_KEY=posthog-api-key
ARG INTERCOM_ID=intercom-id
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
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
# Rebuild the source code only when needed
FROM --platform=linux/amd64 base AS frontend-builder
FROM base AS frontend-builder
WORKDIR /app
# Copy dependencies
@@ -39,7 +39,7 @@ ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
RUN npm run build
# Production image
FROM --platform=linux/amd64 base AS frontend-runner
FROM base AS frontend-runner
WORKDIR /app
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
##
FROM --platform=linux/amd64 base AS backend-build
FROM base AS backend-build
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
@@ -85,7 +85,7 @@ RUN npm i -D tsconfig-paths
RUN npm run build
# Production stage
FROM --platform=linux/amd64 base AS backend-runner
FROM base AS backend-runner
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
@@ -106,7 +106,7 @@ COPY --from=backend-build /app .
RUN mkdir frontend-build
# Production stage
FROM --platform=linux/amd64 base AS production
FROM base AS production
# Install necessary packages
RUN apt-get update && apt-get install -y \

View File

@@ -27,8 +27,8 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
const HMAC_KEY_SIZE = 256;
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
const RETRY_INTERVAL = 200; // 200ms between attempts
const MAX_TIMEOUT = 90_000; // 90 seconds maximum total time
let sessionHandle: pkcs11js.Handle | null = null;
@@ -39,7 +39,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
pkcs11.C_CloseSession(sessionHandle);
logger.info("HSM: Terminated session successfully");
} catch (error) {
logger.error("Error during session cleanup:", error);
logger.error(error, "HSM: Failed to terminate session");
} finally {
sessionHandle = null;
}
@@ -82,15 +82,15 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
logger.info("HSM: Successfully authenticated");
break;
} catch (error) {
// Handle specific error cases
if (error instanceof pkcs11js.Pkcs11Error) {
// Handle specific error cases
if (error.code === pkcs11js.CKR_PIN_INCORRECT) {
logger.error(error, `Incorrect PIN detected for HSM slot ${appCfg.HSM_SLOT}`);
throw new Error("Incorrect HSM Pin detected. Please check the HSM configuration.");
// We throw instantly here to prevent further attempts, because if too many attempts are made, the HSM will potentially wipe all key material
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) {
logger.warn("HSM session already logged in");
logger.warn("HSM: Session already logged in");
}
}
throw error; // Re-throw other errors
@@ -102,7 +102,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
try {
pkcs11.C_CloseSession(sessionHandle);
} catch (closeError) {
logger.error("Error closing failed session:", closeError);
logger.error(closeError, "HSM: Failed to close session");
}
sessionHandle = null;
}
@@ -116,7 +116,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
}
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
@@ -124,7 +124,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
removeSession();
return result;
} catch (error) {
logger.error("Error in HSM session handling:", error);
logger.error(error, "HSM: Failed to open session");
throw error;
} finally {
// Ensure cleanup
@@ -160,7 +160,6 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
pkcs11.C_FindObjectsFinal(sessionHandle);
}
} catch (error) {
logger.error("Error finding master key:", error);
return null;
}
};
@@ -174,7 +173,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
} catch (error) {
// 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
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.code === pkcs11js.CKR_OBJECT_HANDLE_INVALID) {
@@ -198,12 +197,12 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
try {
const aesKey = $findKey(sessionHandle, HsmKeyType.AES);
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);
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);
@@ -244,8 +243,8 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
return Buffer.concat([iv, finalBuffer]);
} catch (error) {
logger.error("Encryption error:", error);
throw new Error(`Encryption failed: ${(error as Error)?.message}`);
logger.error(error, "HSM: Failed to perform encryption");
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): Promise<Buffer>;
} = async (encryptedBlob: Buffer, providedSession?: pkcs11js.Handle) => {
if (!isInitialized) {
throw new Error("HSM service not initialized");
if (!pkcs11 || !isInitialized) {
throw new Error("PKCS#11 module is not initialized");
}
const $performDecryption = (sessionHandle: pkcs11js.Handle) => {
@@ -279,12 +278,12 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
// Find the keys
const aesKey = $findKey(sessionHandle, HsmKeyType.AES);
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);
if (!hmacKey) {
throw new Error("HMAC key not found");
throw new Error("HSM: Decryption failed, HMAC key not found");
}
// 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.
const $testPkcs11Module = async (session: pkcs11js.Handle) => {
try {
if (!isInitialized) {
throw new Error("HSM service not initialized");
if (!pkcs11 || !isInitialized) {
throw new Error("PKCS#11 module is not initialized");
}
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));
@@ -350,12 +349,12 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
const decryptedDataHex = decryptedData.toString("hex");
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;
} catch (error) {
logger.error(error, "Error testing PKCS#11 module");
logger.error(error, "HSM: Error testing PKCS#11 module");
return false;
}
};
@@ -411,7 +410,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
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
@@ -435,7 +434,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
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
@@ -457,7 +456,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm
}
});
} catch (error) {
logger.error("Error initializing HSM service:", error);
logger.error(error, "HSM: Error initializing HSM service:");
throw error;
}
};

View File

@@ -43,10 +43,11 @@ type TMain = {
// Run the server!
export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore }: TMain) => {
const appCfg = getConfig();
const server = fastify({
logger: appCfg.NODE_ENV === "test" ? false : logger,
trustProxy: true,
connectionTimeout: 30 * 1000,
connectionTimeout: appCfg.isHsmConfigured ? 90_000 : 30_000,
ignoreTrailingSlash: true,
pluginTimeout: 40_000
}).withTypeProvider<ZodTypeProvider>();

View File

@@ -208,7 +208,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
strategies: z
.object({
strategy: z.nativeEnum(RootKeyEncryptionStrategy),
name: z.string(),
enabled: z.boolean()
})
.array()

View File

@@ -303,13 +303,12 @@ export const superAdminServiceFactory = ({
}
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) {
const basicStrategy = RootKeyEncryptionStrategy.Software;
enabledStrategies.push({
name: "Software-based Encryption",
enabled: selectedStrategy === basicStrategy,
strategy: basicStrategy
});
@@ -318,7 +317,6 @@ export const superAdminServiceFactory = ({
const hsmStrategy = RootKeyEncryptionStrategy.HSM;
enabledStrategies.push({
name: "Hardware Security Module (HSM)",
enabled: selectedStrategy === hsmStrategy,
strategy: hsmStrategy
});

View File

@@ -59,7 +59,6 @@ export type TGetServerRootKmsEncryptionDetails = {
strategies: {
strategy: RootKeyEncryptionStrategy;
enabled: boolean;
name: string;
}[];
};

View File

@@ -144,7 +144,7 @@ export const AdminDashboardPage = () => {
<TabList>
<div className="flex w-full flex-row border-b border-mineshaft-600">
<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.RateLimit}>Rate Limit</Tab>
<Tab value={TabSections.Integrations}>Integrations</Tab>
@@ -329,11 +329,9 @@ export const AdminDashboardPage = () => {
</Button>
</form>
</TabPanel>
{!!serverRootKmsDetails && (
<TabPanel value={TabSections.Encryption}>
<EncryptionPanel rootKmsDetails={serverRootKmsDetails} />
</TabPanel>
)}
<TabPanel value={TabSections.Encryption}>
<EncryptionPanel rootKmsDetails={serverRootKmsDetails} />
</TabPanel>
<TabPanel value={TabSections.Auth}>
<AuthPanel />
</TabPanel>

View File

@@ -17,10 +17,15 @@ const formSchema = z.object({
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 Props = {
rootKmsDetails: TGetServerRootKmsEncryptionDetails;
rootKmsDetails?: TGetServerRootKmsEncryptionDetails;
};
export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
@@ -84,27 +89,33 @@ export const EncryptionPanel = ({ rootKmsDetails }: Props) => {
supported on Enterprise plans.
</div>
<Controller
control={control}
name="encryptionStrategy"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl className="max-w-sm" errorText={error?.message} isError={Boolean(error)}>
<Select
className="w-full bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-800"
defaultValue={field.value}
onValueChange={(e) => onChange(e)}
{...field}
{!!rootKmsDetails && (
<Controller
control={control}
name="encryptionStrategy"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
className="max-w-sm"
errorText={error?.message}
isError={Boolean(error)}
>
{rootKmsDetails.strategies?.map((strategy) => (
<SelectItem key={strategy.strategy} value={strategy.strategy}>
{strategy.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Select
className="w-full bg-mineshaft-700"
dropdownContainerClassName="bg-mineshaft-800"
defaultValue={field.value}
onValueChange={(e) => onChange(e)}
{...field}
>
{rootKmsDetails.strategies?.map((strategy) => (
<SelectItem key={strategy.strategy} value={strategy.strategy}>
{strategies[strategy.strategy]}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
)}
</div>
<Button