mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
feat(fips): requested changes & additional fixes
This commit is contained in:
@@ -200,9 +200,12 @@ WORKDIR /backend
|
|||||||
ENV TELEMETRY_ENABLED true
|
ENV TELEMETRY_ENABLED true
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 443
|
EXPOSE 443
|
||||||
|
|
||||||
|
# Remove telemetry. dd-trace uses BullMQ with MD5 hashing, which breaks when FIPS mode is enabled.
|
||||||
|
RUN grep -v 'import "./lib/telemetry/instrumentation.mjs";' dist/main.mjs > dist/main.mjs.tmp && \
|
||||||
|
mv dist/main.mjs.tmp dist/main.mjs
|
||||||
|
|
||||||
USER non-root-user
|
USER non-root-user
|
||||||
|
|
||||||
CMD ["./standalone-entrypoint.sh"]
|
CMD ["./standalone-entrypoint.sh"]
|
||||||
@@ -80,7 +80,7 @@ COPY . .
|
|||||||
ENV HOST=0.0.0.0
|
ENV HOST=0.0.0.0
|
||||||
ENV OPENSSL_CONF=/app/nodejs.fips.cnf
|
ENV OPENSSL_CONF=/app/nodejs.fips.cnf
|
||||||
ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
|
ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
|
||||||
ENV NODE_OPTIONS=--force-fips
|
# ENV NODE_OPTIONS=--force-fips # Note(Daniel): We can't set this on the node options because it may break for existing folks using the infisical/infisical-fips image. Instead we call crypto.setFips(true) at runtime.
|
||||||
ENV FIPS_ENABLED=true
|
ENV FIPS_ENABLED=true
|
||||||
|
|
||||||
CMD ["npm", "run", "dev:docker"]
|
CMD ["npm", "run", "dev:docker"]
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const seedData1 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const generateUserSrpKeys = async (password: string) => {
|
export const generateUserSrpKeys = async (password: string) => {
|
||||||
const { publicKey, privateKey } = crypto.encryption().asymmetric().generateKeyPair();
|
const { publicKey, privateKey } = await crypto.encryption().asymmetric().generateKeyPair();
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const client = new jsrp.client();
|
const client = new jsrp.client();
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ export const kmipServiceFactory = ({
|
|||||||
description,
|
description,
|
||||||
permissions
|
permissions
|
||||||
}: TCreateKmipClientDTO) => {
|
}: TCreateKmipClientDTO) => {
|
||||||
|
if (crypto.isFipsModeEnabled()) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "KMIP is currently not supported in FIPS mode of operation."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
|
|||||||
@@ -386,6 +386,34 @@ export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logge
|
|||||||
return envCfg;
|
return envCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getTelemetryConfig = () => {
|
||||||
|
const parsedEnv = envSchema.safeParse(process.env);
|
||||||
|
if (!parsedEnv.success) {
|
||||||
|
console.error("Invalid environment variables. Check the error below");
|
||||||
|
console.error(parsedEnv.error.issues);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
useOtel: parsedEnv.data.OTEL_TELEMETRY_COLLECTION_ENABLED,
|
||||||
|
useDataDogTracer: parsedEnv.data.SHOULD_USE_DATADOG_TRACER,
|
||||||
|
OTEL: {
|
||||||
|
otlpURL: parsedEnv.data.OTEL_EXPORT_OTLP_ENDPOINT,
|
||||||
|
otlpUser: parsedEnv.data.OTEL_COLLECTOR_BASIC_AUTH_USERNAME,
|
||||||
|
otlpPassword: parsedEnv.data.OTEL_COLLECTOR_BASIC_AUTH_PASSWORD,
|
||||||
|
otlpPushInterval: parsedEnv.data.OTEL_OTLP_PUSH_INTERVAL,
|
||||||
|
exportType: parsedEnv.data.OTEL_EXPORT_TYPE
|
||||||
|
},
|
||||||
|
TRACER: {
|
||||||
|
profiling: parsedEnv.data.DATADOG_PROFILING_ENABLED,
|
||||||
|
version: parsedEnv.data.INFISICAL_PLATFORM_VERSION,
|
||||||
|
env: parsedEnv.data.DATADOG_ENV,
|
||||||
|
service: parsedEnv.data.DATADOG_SERVICE,
|
||||||
|
hostname: parsedEnv.data.DATADOG_HOSTNAME
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const getDatabaseCredentials = (logger?: CustomLogger) => {
|
export const getDatabaseCredentials = (logger?: CustomLogger) => {
|
||||||
const parsedEnv = envSchema.safeParse(process.env);
|
const parsedEnv = envSchema.safeParse(process.env);
|
||||||
if (!parsedEnv.success) {
|
if (!parsedEnv.success) {
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
|
|
||||||
export const generateCacheKeyFromData = (data: unknown) =>
|
export const generateCacheKeyFromData = (data: unknown) =>
|
||||||
crypto.rawCrypto.createHash("sha256").update(JSON.stringify(data)).digest("base64");
|
crypto.rawCrypto.
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(JSON.stringify(data))
|
||||||
|
.digest("base64")
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
.replace(/=/g, "");
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
import crypto from "crypto";
|
import crypto, { KeyObject } from "crypto";
|
||||||
|
|
||||||
import { SecretEncryptionAlgo } from "@app/db/schemas";
|
import { SecretEncryptionAlgo } from "@app/db/schemas";
|
||||||
import { CryptographyError } from "@app/lib/errors";
|
import { CryptographyError } from "@app/lib/errors";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
export const asymmetricFipsValidated = () => {
|
export const asymmetricFipsValidated = () => {
|
||||||
const generateKeyPair = () => {
|
const generateKeyPair = async () => {
|
||||||
const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519");
|
const { publicKey, privateKey } = await new Promise<{ publicKey: KeyObject; privateKey: KeyObject }>((resolve) => {
|
||||||
|
crypto.generateKeyPair("x25519", undefined, (err, pubKey, privKey) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err, "FIPS generateKeyPair: Failed to generate key pair");
|
||||||
|
throw new CryptographyError({
|
||||||
|
message: "Failed to generate key pair"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resolve({
|
||||||
|
publicKey: pubKey,
|
||||||
|
privateKey: privKey
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
publicKey: publicKey.export({ type: "spki", format: "der" }).toString("base64"),
|
publicKey: publicKey.export({ type: "spki", format: "der" }).toString("base64"),
|
||||||
|
|||||||
@@ -19,9 +19,8 @@ import { CryptographyError } from "../../errors";
|
|||||||
import { logger } from "../../logger";
|
import { logger } from "../../logger";
|
||||||
import { asymmetricFipsValidated } from "./asymmetric-fips";
|
import { asymmetricFipsValidated } from "./asymmetric-fips";
|
||||||
import { hasherFipsValidated } from "./hash-fips";
|
import { hasherFipsValidated } from "./hash-fips";
|
||||||
import { jwtFipsValidated } from "./jwt-fips";
|
|
||||||
import type { TDecryptAsymmetricInput, TDecryptSymmetricInput, TEncryptSymmetricInput } from "./types";
|
import type { TDecryptAsymmetricInput, TDecryptSymmetricInput, TEncryptSymmetricInput } from "./types";
|
||||||
import { DigestType, JWTPayload, JWTSecretOrKey, JWTSignOptions, JWTVerifyOptions, SymmetricKeySize } from "./types";
|
import { DigestType, SymmetricKeySize } from "./types";
|
||||||
|
|
||||||
const bytesToBits = (bytes: number) => bytes * 8;
|
const bytesToBits = (bytes: number) => bytes * 8;
|
||||||
|
|
||||||
@@ -122,6 +121,8 @@ const cryptographyFactory = () => {
|
|||||||
const $setFipsModeEnabled = (enabled: boolean) => {
|
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 FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key.
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
crypto.setFips(true);
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
if (appCfg.ENCRYPTION_KEY) {
|
if (appCfg.ENCRYPTION_KEY) {
|
||||||
@@ -190,9 +191,10 @@ const cryptographyFactory = () => {
|
|||||||
$checkIsInitialized();
|
$checkIsInitialized();
|
||||||
|
|
||||||
const asymmetric = () => {
|
const asymmetric = () => {
|
||||||
const generateKeyPair = () => {
|
const generateKeyPair = async () => {
|
||||||
if (isFipsModeEnabled()) {
|
if (isFipsModeEnabled()) {
|
||||||
return asymmetricFipsValidated().generateKeyPair();
|
const keyPair = await asymmetricFipsValidated().generateKeyPair();
|
||||||
|
return keyPair;
|
||||||
}
|
}
|
||||||
return generateAsymmetricKeyPairNoFipsValidation();
|
return generateAsymmetricKeyPairNoFipsValidation();
|
||||||
};
|
};
|
||||||
@@ -381,31 +383,10 @@ const cryptographyFactory = () => {
|
|||||||
const jwt = () => {
|
const jwt = () => {
|
||||||
$checkIsInitialized();
|
$checkIsInitialized();
|
||||||
|
|
||||||
const sign = (payload: JWTPayload, secretOrKey: JWTSecretOrKey, options: JWTSignOptions = {}) => {
|
|
||||||
if (isFipsModeEnabled()) {
|
|
||||||
return jwtFipsValidated().sign(payload, secretOrKey, options);
|
|
||||||
}
|
|
||||||
return jwtDep.sign(payload, secretOrKey, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
const verify = (token: string, secretOrKey: JWTSecretOrKey, options: JWTVerifyOptions = {}) => {
|
|
||||||
if (isFipsModeEnabled()) {
|
|
||||||
return jwtFipsValidated().verify(token, secretOrKey, options);
|
|
||||||
}
|
|
||||||
return jwtDep.verify(token, secretOrKey, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
const decode = (token: string, options: { complete?: boolean } = {}) => {
|
|
||||||
if (isFipsModeEnabled()) {
|
|
||||||
return jwtFipsValidated().decode(token, options);
|
|
||||||
}
|
|
||||||
return jwtDep.decode(token, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sign,
|
sign: jwtDep.sign,
|
||||||
verify,
|
verify: jwtDep.verify,
|
||||||
decode
|
decode: jwtDep.decode
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,327 +0,0 @@
|
|||||||
import crypto from "crypto";
|
|
||||||
import RE2 from "re2";
|
|
||||||
|
|
||||||
import { CryptographyError } from "@app/lib/errors";
|
|
||||||
|
|
||||||
import { Algorithm, CompleteJWTPayload, JWTPayload, JWTSecretOrKey, JWTSignOptions, JWTVerifyOptions } from "./types";
|
|
||||||
|
|
||||||
export const jwtFipsValidated = () => {
|
|
||||||
const $base64urlEncode = (str: string) => Buffer.from(str).toString("base64url");
|
|
||||||
const $isRSAAlgorithm = (algorithm: string) => algorithm.startsWith("RS");
|
|
||||||
|
|
||||||
const $parseTimeToSeconds = (timeStr: string | number) => {
|
|
||||||
if (typeof timeStr === "number") {
|
|
||||||
return timeStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = new RE2(/^(\d+)([smhd])$/).exec(timeStr);
|
|
||||||
if (!match) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: `Invalid JWT time format: ${timeStr}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = parseInt(match[1], 10);
|
|
||||||
const unit = match[2];
|
|
||||||
|
|
||||||
switch (unit) {
|
|
||||||
case "s":
|
|
||||||
return value;
|
|
||||||
case "m":
|
|
||||||
return value * 60;
|
|
||||||
case "h":
|
|
||||||
return value * 60 * 60;
|
|
||||||
case "d":
|
|
||||||
return value * 60 * 60 * 24;
|
|
||||||
default:
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: `Unknown JWT time unit: ${unit}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const $getHashAlgorithm = (algorithm: string) => {
|
|
||||||
switch (algorithm) {
|
|
||||||
case "HS256":
|
|
||||||
case "RS256":
|
|
||||||
return "sha256";
|
|
||||||
case "HS384":
|
|
||||||
case "RS384":
|
|
||||||
return "sha384";
|
|
||||||
case "HS512":
|
|
||||||
case "RS512":
|
|
||||||
return "sha512";
|
|
||||||
default:
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: `Unsupported JWT algorithm: ${algorithm}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sign a JWT token
|
|
||||||
*/
|
|
||||||
const sign = (payload: JWTPayload, secretOrPrivateKey: JWTSecretOrKey, options: JWTSignOptions = {}) => {
|
|
||||||
const algorithm = options.algorithm || "HS256";
|
|
||||||
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
// Create header
|
|
||||||
const header = {
|
|
||||||
alg: algorithm,
|
|
||||||
typ: "JWT",
|
|
||||||
...(options.keyid && { kid: options.keyid })
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create payload with timestamps
|
|
||||||
const finalPayload = {
|
|
||||||
...payload,
|
|
||||||
iat: now,
|
|
||||||
...(options.expiresIn !== undefined && { exp: now + $parseTimeToSeconds(options.expiresIn) })
|
|
||||||
};
|
|
||||||
|
|
||||||
// Encode header and payload
|
|
||||||
const encodedHeader = $base64urlEncode(JSON.stringify(header));
|
|
||||||
const encodedPayload = $base64urlEncode(JSON.stringify(finalPayload));
|
|
||||||
|
|
||||||
// Create signature
|
|
||||||
const data = `${encodedHeader}.${encodedPayload}`;
|
|
||||||
const hashAlgorithm = $getHashAlgorithm(algorithm);
|
|
||||||
let signature: string;
|
|
||||||
|
|
||||||
if ($isRSAAlgorithm(algorithm)) {
|
|
||||||
let privateKey: crypto.KeyLike | { key: string | Buffer };
|
|
||||||
|
|
||||||
if (typeof secretOrPrivateKey === "string") {
|
|
||||||
try {
|
|
||||||
// Try to create a proper private key object
|
|
||||||
privateKey = crypto.createPrivateKey(secretOrPrivateKey);
|
|
||||||
} catch (error) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "Invalid JWT private key"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
privateKey = secretOrPrivateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
const signatureBuffer = crypto.sign(hashAlgorithm, Buffer.from(data), privateKey);
|
|
||||||
signature = signatureBuffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
||||||
} else {
|
|
||||||
// HMAC signing
|
|
||||||
if (typeof secretOrPrivateKey !== "string") {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "HMAC algorithms require a string secret"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
signature = crypto
|
|
||||||
.createHmac(hashAlgorithm, secretOrPrivateKey)
|
|
||||||
.update(data)
|
|
||||||
.digest("base64")
|
|
||||||
.replace(/\+/g, "-")
|
|
||||||
.replace(/\//g, "_")
|
|
||||||
.replace(/=/g, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${data}.${signature}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const verify = (token: string, secretOrKey: JWTSecretOrKey, options: JWTVerifyOptions = {}) => {
|
|
||||||
const parts = token.split(".");
|
|
||||||
if (parts.length !== 3) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "Invalid JWT format"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const [encodedHeader, encodedPayload, signature] = parts;
|
|
||||||
|
|
||||||
// Decode header
|
|
||||||
const headerJson = Buffer.from(encodedHeader, "base64").toString();
|
|
||||||
const header = JSON.parse(headerJson) as { alg: string; typ: string; kid?: string };
|
|
||||||
|
|
||||||
if (!header.alg || header.typ !== "JWT") {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "Invalid JWT header"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the actual key from different input types
|
|
||||||
let keyString: string;
|
|
||||||
if (Buffer.isBuffer(secretOrKey)) {
|
|
||||||
keyString = secretOrKey.toString();
|
|
||||||
} else if (typeof secretOrKey === "object" && "key" in secretOrKey) {
|
|
||||||
keyString = Buffer.isBuffer(secretOrKey.key) ? secretOrKey.key.toString() : secretOrKey.key;
|
|
||||||
} else {
|
|
||||||
keyString = secretOrKey as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify signature
|
|
||||||
const data = `${encodedHeader}.${encodedPayload}`;
|
|
||||||
const hashAlgorithm = $getHashAlgorithm(header.alg);
|
|
||||||
let isValidSignature: boolean;
|
|
||||||
|
|
||||||
if ($isRSAAlgorithm(header.alg)) {
|
|
||||||
// For RSA, handle both private and public keys
|
|
||||||
let verificationKey: crypto.KeyLike;
|
|
||||||
|
|
||||||
// Clean up the key format
|
|
||||||
const cleanKey = keyString.replace(/\\n/g, "\n");
|
|
||||||
|
|
||||||
try {
|
|
||||||
// If it's a private key, extract the public key for verification
|
|
||||||
if (cleanKey.includes("PRIVATE KEY")) {
|
|
||||||
const privateKeyObj = crypto.createPrivateKey(cleanKey);
|
|
||||||
verificationKey = crypto.createPublicKey(privateKeyObj);
|
|
||||||
} else {
|
|
||||||
// It's already a public key
|
|
||||||
verificationKey = crypto.createPublicKey(cleanKey);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "Invalid JWT signature"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert base64url signature back to buffer
|
|
||||||
const signatureBuffer = Buffer.from(
|
|
||||||
signature.replace(/-/g, "+").replace(/_/g, "/") + "==".slice(0, (4 - (signature.length % 4)) % 4),
|
|
||||||
"base64"
|
|
||||||
);
|
|
||||||
isValidSignature = crypto.verify(hashAlgorithm, Buffer.from(data), verificationKey, signatureBuffer);
|
|
||||||
} else {
|
|
||||||
// HMAC verification
|
|
||||||
const expectedSignature = crypto
|
|
||||||
.createHmac(hashAlgorithm, keyString)
|
|
||||||
.update(data)
|
|
||||||
.digest("base64")
|
|
||||||
.replace(/\+/g, "-")
|
|
||||||
.replace(/\//g, "_")
|
|
||||||
.replace(/=/g, "");
|
|
||||||
isValidSignature = signature === expectedSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidSignature) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "Invalid JWT signature"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode payload
|
|
||||||
const payloadJson = Buffer.from(encodedPayload, "base64").toString();
|
|
||||||
const payload = JSON.parse(payloadJson) as JWTPayload & {
|
|
||||||
aud?: string | string[];
|
|
||||||
iss?: string;
|
|
||||||
sub?: string;
|
|
||||||
nbf?: number;
|
|
||||||
jti?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
const clockTolerance = options.clockTolerance || 0;
|
|
||||||
|
|
||||||
// Check expiration
|
|
||||||
if (!options.ignoreExpiration && payload.exp && now - clockTolerance > payload.exp) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "JWT token has expired"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check not before
|
|
||||||
if (!options.ignoreNotBefore && payload.nbf && now + clockTolerance < payload.nbf) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "JWT not active"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check audience
|
|
||||||
if (options.audience) {
|
|
||||||
const audiences = Array.isArray(options.audience) ? options.audience : [options.audience];
|
|
||||||
const tokenAudiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
|
|
||||||
const hasValidAudience = audiences.some((aud) => tokenAudiences.includes(aud));
|
|
||||||
if (!hasValidAudience) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "JWT audience invalid"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check issuer
|
|
||||||
if (options.issuer) {
|
|
||||||
const issuers = Array.isArray(options.issuer) ? options.issuer : [options.issuer];
|
|
||||||
if (!payload.iss || !issuers.includes(payload.iss)) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "JWT issuer invalid"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check subject
|
|
||||||
if (options.subject && payload.sub !== options.subject) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "JWT subject invalid"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check JWT ID
|
|
||||||
if (options.jwtid && payload.jti !== options.jwtid) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "JWT ID invalid"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check max age
|
|
||||||
if (options.maxAge && payload.iat) {
|
|
||||||
const maxAgeSeconds = typeof options.maxAge === "string" ? $parseTimeToSeconds(options.maxAge) : options.maxAge;
|
|
||||||
if (now - payload.iat > maxAgeSeconds) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "JWT max age exceeded"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check algorithms
|
|
||||||
if (options.algorithms && !options.algorithms.includes(header.alg as Algorithm)) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: `Algorithm not allowed: ${header.alg}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
};
|
|
||||||
|
|
||||||
const decode = (token: string, options: { complete?: boolean } = {}): JWTPayload | CompleteJWTPayload => {
|
|
||||||
const parts = token.split(".");
|
|
||||||
if (parts.length !== 3) {
|
|
||||||
throw new CryptographyError({
|
|
||||||
message: "Invalid JWT format"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const [encodedHeader, encodedPayload] = parts;
|
|
||||||
|
|
||||||
// Decode header
|
|
||||||
const headerJson = Buffer.from(encodedHeader, "base64").toString();
|
|
||||||
const header = JSON.parse(headerJson) as Record<string, unknown>;
|
|
||||||
|
|
||||||
// Decode payload
|
|
||||||
const payloadJson = Buffer.from(encodedPayload, "base64").toString();
|
|
||||||
const payload = JSON.parse(payloadJson) as Record<string, unknown>;
|
|
||||||
|
|
||||||
// Return complete token info or just payload
|
|
||||||
if (options.complete) {
|
|
||||||
return {
|
|
||||||
header,
|
|
||||||
payload,
|
|
||||||
signature: parts[2]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return payload as JWTPayload;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
sign,
|
|
||||||
verify,
|
|
||||||
decode
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import { KeyObject } from "crypto";
|
|
||||||
|
|
||||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
|
||||||
export enum DigestType {
|
export enum DigestType {
|
||||||
@@ -54,39 +52,3 @@ export type TEncryptedWithRootEncryptionKey = {
|
|||||||
algorithm: SecretEncryptionAlgo;
|
algorithm: SecretEncryptionAlgo;
|
||||||
encoding: SecretKeyEncoding;
|
encoding: SecretKeyEncoding;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface JWTPayload {
|
|
||||||
iat?: number;
|
|
||||||
exp?: number;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CompleteJWTPayload {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
header: any;
|
|
||||||
payload: JWTPayload;
|
|
||||||
signature: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Algorithm = "HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512";
|
|
||||||
|
|
||||||
export interface JWTSignOptions {
|
|
||||||
algorithm?: Algorithm | undefined;
|
|
||||||
keyid?: string | undefined;
|
|
||||||
expiresIn?: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type JWTSecretOrKey = string | Buffer | KeyObject | { key: string | Buffer; passphrase: string };
|
|
||||||
|
|
||||||
export interface JWTVerifyOptions {
|
|
||||||
algorithms?: Algorithm[] | undefined;
|
|
||||||
audience?: string | string[];
|
|
||||||
issuer?: string | string[];
|
|
||||||
subject?: string;
|
|
||||||
ignoreExpiration?: boolean;
|
|
||||||
ignoreNotBefore?: boolean;
|
|
||||||
clockTolerance?: number;
|
|
||||||
maxAge?: string | number;
|
|
||||||
jwtid?: string;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const generateUserSrpKeys = async (
|
|||||||
password: string,
|
password: string,
|
||||||
customKeys?: { publicKey: string; privateKey: string }
|
customKeys?: { publicKey: string; privateKey: string }
|
||||||
) => {
|
) => {
|
||||||
const pair = crypto.encryption().asymmetric().generateKeyPair();
|
const pair = await crypto.encryption().asymmetric().generateKeyPair();
|
||||||
|
|
||||||
const privateKey = customKeys?.privateKey || pair.privateKey;
|
const privateKey = customKeys?.privateKey || pair.privateKey;
|
||||||
const publicKey = customKeys?.publicKey || pair.publicKey;
|
const publicKey = customKeys?.publicKey || pair.publicKey;
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|||||||
import { Resource } from "@opentelemetry/resources";
|
import { Resource } from "@opentelemetry/resources";
|
||||||
import { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
import { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
||||||
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
|
||||||
|
import tracer from "dd-trace";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
import { initEnvConfig } from "../config/env";
|
import { getTelemetryConfig } from "../config/env";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -73,31 +74,17 @@ const initTelemetryInstrumentation = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupTelemetry = async () => {
|
const setupTelemetry = () => {
|
||||||
const envCfg = await initEnvConfig();
|
const appCfg = getTelemetryConfig();
|
||||||
|
|
||||||
if (envCfg.OTEL_TELEMETRY_COLLECTION_ENABLED) {
|
if (appCfg.useOtel) {
|
||||||
console.log("Initializing telemetry instrumentation");
|
console.log("Initializing telemetry instrumentation");
|
||||||
initTelemetryInstrumentation({
|
initTelemetryInstrumentation({ ...appCfg.OTEL });
|
||||||
otlpURL: envCfg.OTEL_EXPORT_OTLP_ENDPOINT,
|
|
||||||
otlpUser: envCfg.OTEL_COLLECTOR_BASIC_AUTH_USERNAME,
|
|
||||||
otlpPassword: envCfg.OTEL_COLLECTOR_BASIC_AUTH_PASSWORD,
|
|
||||||
otlpPushInterval: envCfg.OTEL_OTLP_PUSH_INTERVAL,
|
|
||||||
exportType: envCfg.OTEL_EXPORT_TYPE
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envCfg.SHOULD_USE_DATADOG_TRACER) {
|
if (appCfg.useDataDogTracer) {
|
||||||
const tracer = await import("dd-trace");
|
|
||||||
|
|
||||||
console.log("Initializing Datadog tracer");
|
console.log("Initializing Datadog tracer");
|
||||||
tracer.init({
|
tracer.init({ ...appCfg.TRACER });
|
||||||
profiling: envCfg.DATADOG_PROFILING_ENABLED,
|
|
||||||
version: envCfg.INFISICAL_PLATFORM_VERSION,
|
|
||||||
env: envCfg.DATADOG_ENV,
|
|
||||||
service: envCfg.DATADOG_SERVICE,
|
|
||||||
hostname: envCfg.DATADOG_HOSTNAME
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Note(Daniel): Do not rename this import, as it is strictly removed from FIPS standalone builds to avoid FIPS mode issues.
|
||||||
|
// If you rename the import, update the Dockerfile.fips.standalone-infisical file as well.
|
||||||
import "./lib/telemetry/instrumentation";
|
import "./lib/telemetry/instrumentation";
|
||||||
|
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { requestContext } from "@fastify/request-context";
|
import { requestContext } from "@fastify/request-context";
|
||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
import fp from "fastify-plugin";
|
import fp from "fastify-plugin";
|
||||||
|
import type { JwtPayload } from "jsonwebtoken";
|
||||||
|
|
||||||
import { TServiceTokens, TUsers } from "@app/db/schemas";
|
import { TServiceTokens, TUsers } from "@app/db/schemas";
|
||||||
import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
|
import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { JWTPayload } from "@app/lib/crypto/cryptography/types";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
||||||
@@ -73,7 +73,7 @@ const extractAuth = async (req: FastifyRequest, jwtSecret: string) => {
|
|||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decodedToken = crypto.jwt().verify(authTokenValue, jwtSecret) as JWTPayload;
|
const decodedToken = crypto.jwt().verify(authTokenValue, jwtSecret) as JwtPayload;
|
||||||
|
|
||||||
switch (decodedToken.authTokenType) {
|
switch (decodedToken.authTokenType) {
|
||||||
case AuthTokenType.ACCESS_TOKEN:
|
case AuthTokenType.ACCESS_TOKEN:
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { CompleteJWTPayload } from "@app/lib/crypto/cryptography/types";
|
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
ForbiddenRequestError,
|
ForbiddenRequestError,
|
||||||
@@ -81,7 +80,7 @@ export const identityJwtAuthServiceFactory = ({
|
|||||||
orgId: identityMembershipOrg.orgId
|
orgId: identityMembershipOrg.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
const decodedToken = crypto.jwt().decode(jwtValue, { complete: true }) as CompleteJWTPayload;
|
const decodedToken = crypto.jwt().decode(jwtValue, { complete: true });
|
||||||
if (!decodedToken) {
|
if (!decodedToken) {
|
||||||
throw new UnauthorizedError({
|
throw new UnauthorizedError({
|
||||||
message: "Invalid JWT"
|
message: "Invalid JWT"
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import {
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { CompleteJWTPayload } from "@app/lib/crypto/cryptography/types";
|
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
ForbiddenRequestError,
|
ForbiddenRequestError,
|
||||||
@@ -95,7 +94,7 @@ export const identityOidcAuthServiceFactory = ({
|
|||||||
);
|
);
|
||||||
const jwksUri = discoveryDoc.jwks_uri;
|
const jwksUri = discoveryDoc.jwks_uri;
|
||||||
|
|
||||||
const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true }) as CompleteJWTPayload;
|
const decodedToken = crypto.jwt().decode(oidcJwt, { complete: true });
|
||||||
if (!decodedToken) {
|
if (!decodedToken) {
|
||||||
throw new UnauthorizedError({
|
throw new UnauthorizedError({
|
||||||
message: "Invalid JWT"
|
message: "Invalid JWT"
|
||||||
|
|||||||
@@ -497,7 +497,7 @@ export const orgServiceFactory = ({
|
|||||||
orgName: string;
|
orgName: string;
|
||||||
userEmail?: string | null;
|
userEmail?: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const { privateKey, publicKey } = crypto.encryption().asymmetric().generateKeyPair();
|
const { privateKey, publicKey } = await crypto.encryption().asymmetric().generateKeyPair();
|
||||||
const key = crypto.randomBytes(32).toString("base64");
|
const key = crypto.randomBytes(32).toString("base64");
|
||||||
const {
|
const {
|
||||||
ciphertext: encryptedPrivateKey,
|
ciphertext: encryptedPrivateKey,
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const getBotKeyFnFactory = (
|
|||||||
publicKey: projectV1Keys.senderPublicKey,
|
publicKey: projectV1Keys.senderPublicKey,
|
||||||
privateKey: userPrivateKey
|
privateKey: userPrivateKey
|
||||||
});
|
});
|
||||||
const botKey = crypto.encryption().asymmetric().generateKeyPair();
|
const botKey = await crypto.encryption().asymmetric().generateKeyPair();
|
||||||
const { iv, tag, ciphertext, encoding, algorithm } = crypto
|
const { iv, tag, ciphertext, encoding, algorithm } = crypto
|
||||||
.encryption()
|
.encryption()
|
||||||
.encryptWithRootEncryptionKey(botKey.privateKey);
|
.encryptWithRootEncryptionKey(botKey.privateKey);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const projectBotServiceFactory = ({
|
|||||||
if (doc) return doc;
|
if (doc) return doc;
|
||||||
|
|
||||||
const keys =
|
const keys =
|
||||||
privateKey && publicKey ? { privateKey, publicKey } : crypto.encryption().asymmetric().generateKeyPair();
|
privateKey && publicKey ? { privateKey, publicKey } : await crypto.encryption().asymmetric().generateKeyPair();
|
||||||
|
|
||||||
const { iv, tag, ciphertext, encoding, algorithm } = crypto
|
const { iv, tag, ciphertext, encoding, algorithm } = crypto
|
||||||
.encryption()
|
.encryption()
|
||||||
|
|||||||
Reference in New Issue
Block a user