feat(fips): fips inside

This commit is contained in:
Daniel Hougaard
2025-07-07 18:16:53 +04:00
parent 70071015d2
commit ce88b0cbb1
19 changed files with 244 additions and 100 deletions

View File

@@ -46,3 +46,4 @@ cli/detect/config/gitleaks.toml:gcp-api-key:582
.github/workflows/helm-release-infisical-core.yml:generic-api-key:47
backend/src/services/smtp/smtp-service.ts:generic-api-key:79
frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/CloudflarePagesSyncFields.tsx:cloudflare-api-key:7
docs/integrations/app-connections/zabbix.mdx:generic-api-key:91

View File

@@ -6,7 +6,7 @@ import jwt from "jsonwebtoken";
import path from "path";
import { seedData1 } from "@app/db/seed-data";
import { initEnvConfig } from "@app/lib/config/env";
import { getDatabaseCredentials, initEnvConfig } from "@app/lib/config/env";
import { initLogger } from "@app/lib/logger";
import { main } from "@app/server/app";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
@@ -26,16 +26,15 @@ export default {
transformMode: "ssr",
async setup() {
const logger = initLogger();
const { envCfg, updateRootEncryptionKey } = initEnvConfig(logger);
const databaseCredentials = getDatabaseCredentials(logger);
const db = initDbConnection({
dbConnectionUri: envCfg.DB_CONNECTION_URI,
dbRootCert: envCfg.DB_ROOT_CERT
dbConnectionUri: databaseCredentials.dbConnectionUri,
dbRootCert: databaseCredentials.dbRootCert
});
const superAdminDAL = superAdminDALFactory(db);
const fipsEnabled = await crypto.initialize(superAdminDAL);
if (fipsEnabled) {
updateRootEncryptionKey(envCfg.ENCRYPTION_KEY);
}
const envCfg = await initEnvConfig(superAdminDAL, logger);
const redis = buildRedisFromConfig(envCfg);
await redis.flushdb("SYNC");

View File

@@ -4,6 +4,7 @@ import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto } from "@app/lib/crypto/cryptography";
import { initLogger } from "@app/lib/logger";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
@@ -26,9 +27,12 @@ export async function up(knex: Knex): Promise<void> {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const projectEncryptionRingBuffer =
createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25);
const webhooks = await knex(TableName.Webhook)

View File

@@ -5,6 +5,7 @@ import { crypto } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
@@ -29,7 +30,9 @@ export async function up(knex: Knex): Promise<void> {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const projectEncryptionRingBuffer =

View File

@@ -5,6 +5,7 @@ import { crypto } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
@@ -23,7 +24,9 @@ export async function up(knex: Knex): Promise<void> {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const projectEncryptionRingBuffer =

View File

@@ -5,6 +5,7 @@ import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
@@ -54,7 +55,9 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer =

View File

@@ -5,6 +5,7 @@ import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
@@ -34,7 +35,9 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer =

View File

@@ -10,6 +10,7 @@ import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
const BATCH_SIZE = 500;
const reencryptSamlConfig = async (knex: Knex) => {
@@ -27,7 +28,8 @@ const reencryptSamlConfig = async (knex: Knex) => {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer =
@@ -189,7 +191,8 @@ const reencryptLdapConfig = async (knex: Knex) => {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer =
@@ -345,7 +348,8 @@ const reencryptOidcConfig = async (knex: Knex) => {
}
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer =

View File

@@ -4,6 +4,7 @@ import { inMemoryKeyStore } from "@app/keystore/memory";
import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
@@ -39,7 +40,8 @@ export async function up(knex: Knex): Promise<void> {
);
initLogger();
const envConfig = getMigrationEnvConfig();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });

View File

@@ -1,6 +1,8 @@
import { z } from "zod";
import { zpStr } from "@app/lib/zod";
import { crypto } from "@app/lib/crypto/cryptography";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
const envSchema = z
.object({
@@ -35,7 +37,7 @@ const envSchema = z
export type TMigrationEnvConfig = z.infer<typeof envSchema>;
export const getMigrationEnvConfig = () => {
export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory) => {
const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) {
// eslint-disable-next-line no-console
@@ -49,5 +51,19 @@ export const getMigrationEnvConfig = () => {
process.exit(-1);
}
return Object.freeze(parsedEnv.data);
let envCfg = Object.freeze(parsedEnv.data);
const fipsEnabled = await crypto.initialize(superAdminDAL);
if (fipsEnabled) {
const newEnvCfg = {
...envCfg,
ROOT_ENCRYPTION_KEY: envCfg.ENCRYPTION_KEY
};
delete newEnvCfg.ENCRYPTION_KEY;
envCfg = Object.freeze(newEnvCfg);
}
return envCfg;
};

View File

@@ -58,7 +58,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
sshHostGroups: false,
secretScanning: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
enterpriseAppConnections: false,
fips: true
});
export const setupLicenseRequestWithStore = (

View File

@@ -75,6 +75,7 @@ export type TFeatureSet = {
secretScanning: false;
enterpriseSecretSyncs: false;
enterpriseAppConnections: false;
fips: false;
};
export type TOrgPlansTableDTO = {

View File

@@ -1,6 +1,8 @@
import { z } from "zod";
import { crypto } from "@app/lib/crypto/cryptography";
import { QueueWorkerProfile } from "@app/lib/types";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { BadRequestError } from "../errors";
import { removeTrailingSlash } from "../fn";
@@ -349,7 +351,7 @@ export const getConfig = () => envCfg;
export const getOriginalConfig = () => originalEnvConfig;
// cannot import singleton logger directly as it needs config to load various transport
export const initEnvConfig = (logger?: CustomLogger) => {
export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logger?: CustomLogger) => {
const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) {
(logger ?? console).error("Invalid environment variables. Check the error below");
@@ -357,16 +359,50 @@ export const initEnvConfig = (logger?: CustomLogger) => {
process.exit(-1);
}
const config = Object.freeze(parsedEnv.data);
envCfg = config;
if (superAdminDAL) {
const fipsEnabled = await crypto.initialize(superAdminDAL);
if (!originalEnvConfig) {
originalEnvConfig = config;
if (fipsEnabled) {
const newEnvCfg = {
...envCfg,
ROOT_ENCRYPTION_KEY: envCfg.ENCRYPTION_KEY
};
delete newEnvCfg.ENCRYPTION_KEY;
envCfg = Object.freeze(newEnvCfg);
}
}
if (!envCfg) {
const config = Object.freeze(parsedEnv.data);
envCfg = config;
if (!originalEnvConfig) {
originalEnvConfig = config;
}
}
return envCfg;
};
export const getDatabaseCredentials = (logger?: CustomLogger) => {
const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) {
(logger ?? console).error("Invalid environment variables. Check the error below");
(logger ?? console).error(parsedEnv.error.issues);
process.exit(-1);
}
return {
dbConnectionUri: envCfg.DB_CONNECTION_URI,
dbRootCert: envCfg.DB_ROOT_CERT,
readReplicas: envCfg.DB_READ_REPLICAS?.map((el) => ({
dbRootCert: el.DB_ROOT_CERT,
dbConnectionUri: el.DB_CONNECTION_URI
}))
};
};
// A list of environment variables that can be overwritten
export const overwriteSchema: {
[key: string]: {
@@ -462,7 +498,11 @@ export const overrideEnvConfig = (config: Record<string, string>) => {
const parsedResult = envSchema.safeParse(tempEnv);
if (parsedResult.success) {
envCfg = Object.freeze(parsedResult.data);
envCfg = Object.freeze({
...parsedResult.data,
ENCRYPTION_KEY: envCfg.ENCRYPTION_KEY,
ROOT_ENCRYPTION_KEY: envCfg.ROOT_ENCRYPTION_KEY
});
}
};

View File

@@ -8,6 +8,7 @@ import nacl from "tweetnacl";
import naclUtils from "tweetnacl-util";
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service";
@@ -67,40 +68,101 @@ const IV_BYTES_SIZE = 12;
const BLOCK_SIZE_BYTES_16 = 16;
const hasherFipsValidated = () => {
const $hashPassword = (password: string, salt: string, iterations: number, keyLength: number) => {
return new Promise<string>((resolve, reject) => {
const keySize = 32;
// For the salt when using pkdf2, we do salt rounds^6. If the salt rounds are 10, this will result in 10^6 = 1.000.000 iterations.
// The reason for this is because pbkdf2 is not as compute intense as bcrypt, making it faster to brute-force.
// From my testing, doing salt rounds^6 brings the computational power required to a little more than bcrypt.
// OWASP recommends a minimum of 600.000 iterations for pbkdf2, so 1.000.000 is more than enough.
// Ref: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
const MIN_COST_FACTOR = 10;
const MAX_COST_FACTOR = 20; // Iterations scales polynomial (costFactor^6), so we need an upper bound
const $calculateIterations = (costFactor: number) => {
return Math.round(costFactor ** 6);
};
const $hashPassword = (password: Buffer, salt: Buffer, iterations: number, keyLength: number) => {
return new Promise<Buffer>((resolve, reject) => {
crypto.pbkdf2(password, salt, iterations, keyLength, "sha256", (err, derivedKey) => {
if (err) {
return reject(err);
}
resolve(derivedKey.toString("hex"));
resolve(derivedKey);
});
});
};
const $validatePassword = (
inputPassword: string,
storedHash: string,
salt: string,
const $validatePassword = async (
inputPassword: Buffer,
storedHash: Buffer,
salt: Buffer,
iterations: number,
keyLength: number
) => {
return $hashPassword(inputPassword, salt, iterations, keyLength).then((hash) => hash === storedHash);
const computedHash = await $hashPassword(inputPassword, salt, iterations, keyLength);
return crypto.timingSafeEqual(computedHash, storedHash);
};
const hash = async (password: string, saltRounds: number) => {
const salt = crypto.randomBytes(16).toString("hex");
const derivedKey = await $hashPassword(password, salt, saltRounds, 32);
return `$infisical$${saltRounds}$${salt}$${derivedKey}`;
const hash = async (password: string, costFactor: number) => {
// Strict input validation
if (typeof password !== "string" || password.length === 0) {
throw new CryptographyError({
message: "Invalid input, password must be a non-empty string"
});
}
if (!Number.isInteger(costFactor)) {
throw new CryptographyError({
message: "Invalid cost factor, must be an integer"
});
}
if (costFactor < MIN_COST_FACTOR || costFactor > MAX_COST_FACTOR) {
throw new CryptographyError({
message: `Invalid cost factor, must be between ${MIN_COST_FACTOR} and ${MAX_COST_FACTOR}`
});
}
const iterations = $calculateIterations(costFactor);
const salt = crypto.randomBytes(16);
const derivedKey = await $hashPassword(Buffer.from(password), salt, iterations, keySize);
const combined = Buffer.concat([salt, derivedKey]);
return `$v1$${costFactor}$${combined.toString("base64")}`; // Store original costFactor!
};
const compare = async (password: string, hashedPassword: string) => {
if (!hashedPassword.startsWith("$infisical$")) {
throw new Error("Invalid hash format");
}
try {
if (!hashedPassword?.startsWith("$v1$")) return false;
const [, , iterations, salt, storedHash] = hashedPassword.split("$");
return $validatePassword(password, storedHash, salt, Number(iterations), 32);
const parts = hashedPassword.split("$");
if (parts.length !== 4) return false;
const [, , storedCostFactor, combined] = parts;
if (
!Number.isInteger(Number(storedCostFactor)) ||
Number(storedCostFactor) < MIN_COST_FACTOR ||
Number(storedCostFactor) > MAX_COST_FACTOR
) {
return false;
}
const combinedBuffer = Buffer.from(combined, "base64");
const salt = combinedBuffer.subarray(0, 16);
const storedHash = combinedBuffer.subarray(16);
const iterations = $calculateIterations(Number(storedCostFactor));
const isMatch = await $validatePassword(Buffer.from(password), storedHash, salt, iterations, keySize);
return isMatch;
} catch {
return false;
}
};
return {
@@ -216,7 +278,9 @@ const decryptAsymmetricFipsValidated = ({
const final = decipher.final();
return Buffer.concat([plaintext, final]).toString("utf8");
} catch (error) {
throw new Error("Invalid ciphertext or keys");
throw new CryptographyError({
message: "Invalid ciphertext or keys"
});
}
};
@@ -276,7 +340,9 @@ export const computeMd5 = (message: string, digest: DigestType = DigestType.Hex)
encoder = cryptoJs.enc.Base64;
break;
default:
throw new Error(`Invalid digest type: ${digest as string}`);
throw new CryptographyError({
message: `Invalid digest type: ${digest as string}`
});
}
return cryptoJs.MD5(message).toString(encoder);
@@ -289,7 +355,9 @@ const cryptographyFactory = () => {
const $checkIsInitialized = () => {
if (!$isInitialized) {
throw new Error("Internal cryptography module is not initialized");
throw new CryptographyError({
message: "Internal cryptography module is not initialized"
});
}
};
@@ -297,6 +365,15 @@ const cryptographyFactory = () => {
$checkIsInitialized();
return $fipsEnabled;
};
const verifyFipsLicense = (licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">) => {
if (isFipsModeEnabled() && !licenseService.onPremFeatures?.fips) {
throw new CryptographyError({
message: "FIPS mode is enabled but your license does not include FIPS support. Please contact support."
});
}
};
const $setFipsModeEnabled = (enabled: boolean) => {
// If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key.
if (enabled) {
@@ -305,6 +382,7 @@ const cryptographyFactory = () => {
if (appCfg.ENCRYPTION_KEY) {
// we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key
// note(daniel): for some reason this resolves as true for some hex-encoded strings.
if (!isBase64(appCfg.ENCRYPTION_KEY)) {
throw new CryptographyError({
message:
@@ -398,15 +476,8 @@ const cryptographyFactory = () => {
let decipher;
if (keySize === SymmetricKeySize.Bits128) {
if (isFipsModeEnabled()) {
throw new CryptographyError({
message: "128-bit symmetric key is not supported in FIPS mode of operation."
});
}
// Not ideal: 128-bit hex key (32 chars) gets interpreted as 32 UTF-8 bytes (256 bits)
// This works but reduces effective key entropy from 256 to 128 bits
// Note: Never use this for FIPS mode of operation.
decipher = crypto.createDecipheriv(SecretEncryptionAlgo.AES_256_GCM, key, Buffer.from(iv, "base64"));
} else {
const secretKey = crypto.createSecretKey(key, "base64");
@@ -425,12 +496,6 @@ const cryptographyFactory = () => {
let cipher;
if (keySize === SymmetricKeySize.Bits128) {
if (isFipsModeEnabled()) {
throw new CryptographyError({
message: "128-bit symmetric key is not supported in FIPS mode of operation."
});
}
iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16);
cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv);
} else {
@@ -452,6 +517,7 @@ const cryptographyFactory = () => {
const appCfg = getConfig();
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
const encryptionKey = appCfg.ENCRYPTION_KEY;
if (rootEncryptionKey) {
const { iv, tag, ciphertext } = encryptSymmetric({
plaintext: data,
@@ -480,7 +546,9 @@ const cryptographyFactory = () => {
encoding: SecretKeyEncoding.UTF8
};
}
throw new Error("Missing both encryption keys");
throw new CryptographyError({
message: "Missing both encryption keys"
});
};
const decryptWithRootEncryptionKey = <T = string>({
@@ -509,7 +577,9 @@ const cryptographyFactory = () => {
const data = decryptSymmetric({ key: encryptionKey, iv, tag, ciphertext, keySize: SymmetricKeySize.Bits128 });
return data as T;
}
throw new Error("Missing both encryption keys");
throw new CryptographyError({
message: "Missing both encryption keys"
});
};
return {
@@ -540,12 +610,7 @@ const cryptographyFactory = () => {
if (isFipsModeEnabled()) {
const hasher = hasherFipsValidated();
// For the salt when using pkdf2, we do salt rounds * 100.000.
// The reason for this is because pbkdf2 is not as compute intense as bcrypt, making it faster to brute-force.
// From my testing, doing salt rounds * 100.000 brings the computational power required to roughly the same as bcrypt.
// OWASP recommends a minimum of 600.000 iterations for pbkdf2.
// Ref: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
const hash = await hasher.hash(password, saltRounds * 100_000);
const hash = await hasher.hash(password, saltRounds);
return hash;
}
const hash = await bcrypt.hash(password, saltRounds);
@@ -572,6 +637,7 @@ const cryptographyFactory = () => {
initialize,
isFipsModeEnabled,
hashing,
verifyFipsLicense,
encryption,
randomBytes: crypto.randomBytes,
randomInt: crypto.randomInt,

View File

@@ -74,10 +74,8 @@ const initTelemetryInstrumentation = ({
});
};
const setupTelemetry = () => {
const { envCfg } = initEnvConfig();
console.log("envCfg", envCfg);
const setupTelemetry = async () => {
const envCfg = await initEnvConfig();
if (envCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) {
console.log("Initializing telemetry instrumentation");

View File

@@ -7,9 +7,8 @@ import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns";
import { runMigrations } from "./auto-start-migrations";
import { initAuditLogDbConnection, initDbConnection } from "./db";
import { keyStoreFactory } from "./keystore/keystore";
import { formatSmtpConfig, initEnvConfig } from "./lib/config/env";
import { formatSmtpConfig, getDatabaseCredentials, initEnvConfig } from "./lib/config/env";
import { buildRedisFromConfig } from "./lib/config/redis";
import { crypto } from "./lib/crypto/cryptography";
import { removeTemporaryBaseDirectory } from "./lib/files";
import { initLogger } from "./lib/logger";
import { queueServiceFactory } from "./queue";
@@ -22,29 +21,23 @@ dotenv.config();
const run = async () => {
const logger = initLogger();
const { envCfg, updateRootEncryptionKey } = initEnvConfig(logger);
await removeTemporaryBaseDirectory();
const databaseCredentials = getDatabaseCredentials(logger);
const db = initDbConnection({
dbConnectionUri: envCfg.DB_CONNECTION_URI,
dbRootCert: envCfg.DB_ROOT_CERT,
readReplicas: envCfg.DB_READ_REPLICAS?.map((el) => ({
dbRootCert: el.DB_ROOT_CERT,
dbConnectionUri: el.DB_CONNECTION_URI
}))
dbConnectionUri: databaseCredentials.dbConnectionUri,
dbRootCert: databaseCredentials.dbRootCert,
readReplicas: databaseCredentials.readReplicas
});
const superAdminDAL = superAdminDALFactory(db);
const fipsEnabled = await crypto.initialize(superAdminDAL);
if (fipsEnabled) {
updateRootEncryptionKey(envCfg.ENCRYPTION_KEY);
}
const envConfig = await initEnvConfig(superAdminDAL, logger);
const auditLogDb = envCfg.AUDIT_LOGS_DB_CONNECTION_URI
const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI
? initAuditLogDbConnection({
dbConnectionUri: envCfg.AUDIT_LOGS_DB_CONNECTION_URI,
dbRootCert: envCfg.AUDIT_LOGS_DB_ROOT_CERT
dbConnectionUri: envConfig.AUDIT_LOGS_DB_CONNECTION_URI,
dbRootCert: envConfig.AUDIT_LOGS_DB_ROOT_CERT
})
: undefined;
@@ -52,17 +45,17 @@ const run = async () => {
const smtp = smtpServiceFactory(formatSmtpConfig());
const queue = queueServiceFactory(envCfg, {
dbConnectionUrl: envCfg.DB_CONNECTION_URI,
dbRootCert: envCfg.DB_ROOT_CERT
const queue = queueServiceFactory(envConfig, {
dbConnectionUrl: envConfig.DB_CONNECTION_URI,
dbRootCert: envConfig.DB_ROOT_CERT
});
await queue.initialize();
const keyStore = keyStoreFactory(envCfg);
const redis = buildRedisFromConfig(envCfg);
const keyStore = keyStoreFactory(envConfig);
const redis = buildRedisFromConfig(envConfig);
const hsmModule = initializeHsmModule(envCfg);
const hsmModule = initializeHsmModule(envConfig);
hsmModule.initialize();
const server = await main({
@@ -75,7 +68,7 @@ const run = async () => {
queue,
keyStore,
redis,
envConfig: envCfg
envConfig
});
const bootstrap = await bootstrapCheck({ db });
@@ -99,7 +92,7 @@ const run = async () => {
process.exit(0);
});
if (!envCfg.isDevelopmentMode) {
if (!envConfig.isDevelopmentMode) {
process.on("uncaughtException", (error) => {
logger.error(error, "CRITICAL ERROR: Uncaught Exception");
});
@@ -110,8 +103,8 @@ const run = async () => {
}
await server.listen({
port: envCfg.PORT,
host: envCfg.HOST,
port: envConfig.PORT,
host: envConfig.HOST,
listenTextResolver: (address) => {
void bootstrap();
return address;

View File

@@ -119,6 +119,7 @@ import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig, TEnvConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography";
import { logger } from "@app/lib/logger";
import { TQueueServiceFactory } from "@app/queue";
import { readLimit } from "@app/server/config/rateLimiter";
@@ -1903,11 +1904,14 @@ export const registerRoutes = async (
kmsService
});
await superAdminService.initServerCfg();
// setup the communication with license key server
await licenseService.init();
// If FIPS is enabled, we check to ensure that the users license includes FIPS mode.
crypto.verifyFipsLicense(licenseService);
await superAdminService.initServerCfg();
// Start HSM service if it's configured/enabled.
await hsmService.startService();

View File

@@ -830,6 +830,7 @@ export const kmsServiceFactory = ({
const $getBasicEncryptionKey = () => {
const encryptionKey = envConfig.ENCRYPTION_KEY || envConfig.ROOT_ENCRYPTION_KEY;
const isBase64 = !envConfig.ENCRYPTION_KEY;
if (!encryptionKey)
throw new Error(

View File

@@ -277,6 +277,7 @@ export const superAdminServiceFactory = ({
const $syncEnvConfig = async () => {
const config = await getEnvOverrides();
overrideEnvConfig(config);
};
@@ -483,6 +484,7 @@ export const superAdminServiceFactory = ({
userAgent
}: TAdminSignUpDTO) => {
const appCfg = getConfig();
const sanitizedEmail = email.trim().toLowerCase();
const existingUser = await userDAL.findOne({ username: sanitizedEmail });
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exists" });