mirror of
https://github.com/Infisical/infisical.git
synced 2026-01-08 23:18:05 -05:00
feat(fips): fips inside, AWS patch-up and docker improvements
This commit is contained in:
@@ -73,6 +73,17 @@ RUN apt-get update && apt-get install -y \
|
||||
# Configure ODBC
|
||||
RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini
|
||||
|
||||
|
||||
# Build and install FIPS validated OpenSSL
|
||||
WORKDIR /openssl-build
|
||||
RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
||||
&& tar -xf openssl-3.1.2.tar.gz \
|
||||
&& cd openssl-3.1.2 \
|
||||
&& ./Configure enable-fips \
|
||||
&& make \
|
||||
&& make install_fips
|
||||
|
||||
|
||||
COPY backend/package*.json ./
|
||||
RUN npm ci --only-production
|
||||
|
||||
@@ -103,6 +114,15 @@ RUN apt-get update && apt-get install -y \
|
||||
# Configure ODBC
|
||||
RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini
|
||||
|
||||
# Build and install FIPS validated OpenSSL
|
||||
WORKDIR /openssl-build
|
||||
RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
||||
&& tar -xf openssl-3.1.2.tar.gz \
|
||||
&& cd openssl-3.1.2 \
|
||||
&& ./Configure enable-fips \
|
||||
&& make \
|
||||
&& make install_fips
|
||||
|
||||
COPY backend/package*.json ./
|
||||
RUN npm ci --only-production
|
||||
|
||||
@@ -173,6 +193,12 @@ ENV STANDALONE_MODE true
|
||||
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
|
||||
ENV NODE_OPTIONS="--max-old-space-size=1024"
|
||||
|
||||
# FIPS mode of operation:
|
||||
ENV OPENSSL_CONF=/backend/nodejs.fips.cnf
|
||||
ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
|
||||
ENV NODE_OPTIONS=--force-fips
|
||||
ENV FIPS_ENABLED=true
|
||||
|
||||
WORKDIR /backend
|
||||
|
||||
ENV TELEMETRY_ENABLED true
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
FROM node:20-slim
|
||||
|
||||
RUN echo "RUNNING FIPS BUILD"
|
||||
|
||||
# Install build dependencies including python3 (required for pkcs11js and partially TDS driver)
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
@@ -29,11 +27,26 @@ RUN apt-get install -y \
|
||||
|
||||
RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nFileUsage = 1\n" > /etc/odbcinst.ini
|
||||
|
||||
# Build and install SoftHSM2
|
||||
RUN git clone https://github.com/opendnssec/SoftHSMv2.git ${SOFTHSM2_SOURCES}
|
||||
WORKDIR ${SOFTHSM2_SOURCES}
|
||||
|
||||
RUN git checkout ${SOFTHSM2_VERSION} -b ${SOFTHSM2_VERSION} \
|
||||
&& sh autogen.sh \
|
||||
&& ./configure --prefix=/usr/local --disable-gost \
|
||||
&& make \
|
||||
&& make install
|
||||
|
||||
WORKDIR /root
|
||||
RUN rm -fr ${SOFTHSM2_SOURCES}
|
||||
|
||||
# Install pkcs11-tool
|
||||
RUN apt-get install -y opensc
|
||||
|
||||
RUN mkdir -p /etc/softhsm2/tokens && \
|
||||
softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000
|
||||
|
||||
# Build and install FIPS validated OpenSSL
|
||||
WORKDIR /openssl-build
|
||||
RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
|
||||
&& tar -xf openssl-3.1.2.tar.gz \
|
||||
@@ -59,7 +72,7 @@ RUN npm install
|
||||
COPY . .
|
||||
|
||||
ENV HOST=0.0.0.0
|
||||
ENV OPENSSL_CONF=/app/nodejs.cnf
|
||||
ENV OPENSSL_CONF=/app/nodejs.fips.cnf
|
||||
ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
|
||||
ENV NODE_OPTIONS=--force-fips
|
||||
ENV FIPS_ENABLED=true
|
||||
|
||||
921
backend/package-lock.json
generated
921
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,7 @@
|
||||
"@babel/plugin-syntax-import-attributes": "^7.24.7",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-react": "^7.24.7",
|
||||
"@smithy/types": "^4.3.1",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/jmespath": "^0.15.2",
|
||||
|
||||
@@ -12,6 +12,8 @@ import handlebars from "handlebars";
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
|
||||
@@ -39,8 +41,11 @@ type TDeleteElastiCacheUserInput = z.infer<typeof DeleteElasticCacheUserSchema>;
|
||||
const ElastiCacheUserManager = (credentials: TBasicAWSCredentials, region: string) => {
|
||||
const elastiCache = new ElastiCache({
|
||||
region,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials
|
||||
});
|
||||
|
||||
const infisicalGroup = "infisical-managed-group-elasticache";
|
||||
|
||||
const ensureInfisicalGroupExists = async (clusterName: string) => {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
||||
import { z } from "zod";
|
||||
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
@@ -49,6 +50,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
||||
if (providerInputs.method === AwsIamAuthType.AssumeRole) {
|
||||
const stsClient = new STSClient({
|
||||
region: providerInputs.region,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials:
|
||||
appCfg.DYNAMIC_SECRET_AWS_ACCESS_KEY_ID && appCfg.DYNAMIC_SECRET_AWS_SECRET_ACCESS_KEY
|
||||
? {
|
||||
@@ -72,6 +75,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
||||
}
|
||||
const client = new IAMClient({
|
||||
region: providerInputs.region,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials: {
|
||||
accessKeyId: assumeRes.Credentials?.AccessKeyId,
|
||||
secretAccessKey: assumeRes.Credentials?.SecretAccessKey,
|
||||
@@ -83,6 +88,8 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
||||
|
||||
const client = new IAMClient({
|
||||
region: providerInputs.region,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials: {
|
||||
accessKeyId: providerInputs.accessKey,
|
||||
secretAccessKey: providerInputs.secretAccessKey
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { CreateKeyCommand, DecryptCommand, DescribeKeyCommand, EncryptCommand, KMSClient } from "@aws-sdk/client-kms";
|
||||
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
||||
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
|
||||
import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model";
|
||||
@@ -9,7 +10,9 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
|
||||
if (providerInputs.credential.type === KmsAwsCredentialType.AssumeRole) {
|
||||
const awsCredential = providerInputs.credential.data;
|
||||
const stsClient = new STSClient({
|
||||
region: providerInputs.awsRegion
|
||||
region: providerInputs.awsRegion,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher
|
||||
});
|
||||
const command = new AssumeRoleCommand({
|
||||
RoleArn: awsCredential.assumeRoleArn,
|
||||
@@ -23,6 +26,8 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
|
||||
|
||||
const kmsClient = new KMSClient({
|
||||
region: providerInputs.awsRegion,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials: {
|
||||
accessKeyId: response.Credentials.AccessKeyId,
|
||||
secretAccessKey: response.Credentials.SecretAccessKey,
|
||||
@@ -35,6 +40,8 @@ const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
|
||||
const awsCredential = providerInputs.credential.data;
|
||||
const kmsClient = new KMSClient({
|
||||
region: providerInputs.awsRegion,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials: {
|
||||
accessKeyId: awsCredential.accessKey,
|
||||
secretAccessKey: awsCredential.secretKey
|
||||
|
||||
@@ -18,7 +18,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
environmentsUsed: 0,
|
||||
identityLimit: null,
|
||||
identitiesUsed: 0,
|
||||
dynamicSecret: false,
|
||||
dynamicSecret: true,
|
||||
secretVersioning: true,
|
||||
pitRecovery: false,
|
||||
ipAllowlisting: false,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from "@aws-sdk/client-iam";
|
||||
|
||||
import { SecretType } from "@app/db/schemas";
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
|
||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||
@@ -226,6 +227,8 @@ export const secretRotationQueueFactory = ({
|
||||
if (provider.template.type === TProviderFunctionTypes.AWS) {
|
||||
if (provider.template.client === TAwsProviderSystems.IAM) {
|
||||
const client = new IAMClient({
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
region: newCredential.inputs.manager_user_aws_region as string,
|
||||
credentials: {
|
||||
accessKeyId: newCredential.inputs.manager_user_access_key as string,
|
||||
|
||||
57
backend/src/lib/aws/hashing.ts
Normal file
57
backend/src/lib/aws/hashing.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import type { SourceData } from "@smithy/types";
|
||||
import { Hash, Hmac } from "crypto";
|
||||
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
|
||||
export class CustomAWSHasher {
|
||||
public algorithmIdentifier: string = "sha256";
|
||||
|
||||
public secret: SourceData | undefined;
|
||||
|
||||
public hash: Hash | Hmac | undefined;
|
||||
|
||||
private _hash: Hash | Hmac | undefined;
|
||||
|
||||
constructor(secret?: SourceData) {
|
||||
this.secret = secret;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.secret) {
|
||||
// Convert any secret type to Buffer
|
||||
let secretBuffer = this.secret as Buffer;
|
||||
if (this.secret instanceof ArrayBuffer) {
|
||||
secretBuffer = Buffer.from(this.secret);
|
||||
} else if (ArrayBuffer.isView && ArrayBuffer.isView(this.secret)) {
|
||||
secretBuffer = Buffer.from(this.secret.buffer, this.secret.byteOffset, this.secret.byteLength);
|
||||
}
|
||||
this._hash = crypto.rawCrypto.createHmac(this.algorithmIdentifier, secretBuffer);
|
||||
} else {
|
||||
this._hash = crypto.rawCrypto.createHash(this.algorithmIdentifier);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
update(data: SourceData) {
|
||||
// Handle all possible data types
|
||||
let buffer: Buffer = data as Buffer;
|
||||
if (typeof data === "string") {
|
||||
buffer = Buffer.from(data, "utf8");
|
||||
} else if (data instanceof ArrayBuffer) {
|
||||
buffer = Buffer.from(data);
|
||||
} else if (ArrayBuffer.isView && ArrayBuffer.isView(data)) {
|
||||
buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||
}
|
||||
|
||||
this._hash?.update(buffer);
|
||||
return this;
|
||||
}
|
||||
|
||||
digest(): Promise<Uint8Array> {
|
||||
const result = new Uint8Array(this._hash?.digest() || []);
|
||||
this.reset();
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
}
|
||||
@@ -193,13 +193,12 @@ const encryptAsymmetricFipsValidated = (data: string, publicKey: string, private
|
||||
format: "der"
|
||||
});
|
||||
|
||||
// Generate shared secret using X25519
|
||||
// Generate shared secret using x25519 curve
|
||||
const sharedSecret = crypto.diffieHellman({
|
||||
privateKey: privKeyObj,
|
||||
publicKey: pubKeyObj
|
||||
});
|
||||
|
||||
// Generate 24-byte nonce (same as NaCl)
|
||||
const nonce = crypto.randomBytes(24);
|
||||
|
||||
// Derive 32-byte key from shared secret
|
||||
@@ -209,7 +208,7 @@ const encryptAsymmetricFipsValidated = (data: string, publicKey: string, private
|
||||
const iv = nonce.subarray(0, 12);
|
||||
|
||||
// Encrypt with AES-256-GCM
|
||||
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
||||
const cipher = crypto.createCipheriv(SecretEncryptionAlgo.AES_256_GCM, key, iv);
|
||||
|
||||
const ciphertext = cipher.update(data, "utf8");
|
||||
cipher.final();
|
||||
@@ -665,7 +664,8 @@ const cryptographyFactory = () => {
|
||||
},
|
||||
constants: crypto.constants,
|
||||
X509Certificate: crypto.X509Certificate,
|
||||
KeyObject: crypto.KeyObject
|
||||
KeyObject: crypto.KeyObject,
|
||||
Hash: crypto.Hash
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
|
||||
import AWS from "aws-sdk";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
@@ -35,6 +36,8 @@ export const getAwsConnectionConfig = async (appConnection: TAwsConnectionConfig
|
||||
case AwsConnectionMethod.AssumeRole: {
|
||||
const client = new STSClient({
|
||||
region,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials:
|
||||
appCfg.INF_APP_CONNECTION_AWS_ACCESS_KEY_ID && appCfg.INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY
|
||||
? {
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as x509 from "@peculiar/x509";
|
||||
import acme from "acme-client";
|
||||
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
@@ -102,6 +103,8 @@ export const route53InsertTxtRecord = async (
|
||||
) => {
|
||||
const config = await getAwsConnectionConfig(connection, AWSRegion.US_WEST_1); // REGION is irrelevant because Route53 is global
|
||||
const route53Client = new Route53Client({
|
||||
sha256: CustomAWSHasher,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
credentials: config.credentials!,
|
||||
region: config.region
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretType, TIntegrationAuths, TIntegrations } from "@app/db/schemas";
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
@@ -796,6 +797,8 @@ const syncSecretsAWSParameterStore = async ({
|
||||
if (awsAssumeRoleArn) {
|
||||
const client = new STSClient({
|
||||
region: integration.region as string,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials:
|
||||
appCfg.CLIENT_ID_AWS_INTEGRATION && appCfg.CLIENT_SECRET_AWS_INTEGRATION
|
||||
? {
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
Tag
|
||||
} from "aws-sdk/clients/secretsmanager";
|
||||
|
||||
import { CustomAWSHasher } from "@app/lib/aws/hashing";
|
||||
import { crypto } from "@app/lib/crypto";
|
||||
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
||||
import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums";
|
||||
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||
@@ -46,6 +48,8 @@ const getSecretsManagerClient = async (secretSync: TAwsSecretsManagerSyncWithCre
|
||||
|
||||
const secretsManagerClient = new SecretsManagerClient({
|
||||
region: config.region,
|
||||
useFipsEndpoint: crypto.isFipsModeEnabled(),
|
||||
sha256: CustomAWSHasher,
|
||||
credentials: config.credentials!
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { useCallback } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import { Badge, Button, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2";
|
||||
import { useServerConfig, useSubscription } from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
useGetServerRootKmsEncryptionDetails,
|
||||
@@ -29,6 +31,7 @@ export const EncryptionPageForm = () => {
|
||||
const { data: rootKmsDetails } = useGetServerRootKmsEncryptionDetails();
|
||||
|
||||
const { mutateAsync: updateEncryptionStrategy } = useUpdateServerEncryptionStrategy();
|
||||
const { config } = useServerConfig();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
@@ -117,14 +120,27 @@ export const EncryptionPageForm = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="mt-2"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Button
|
||||
className="mt-2"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
{config.fipsEnabled && (
|
||||
<Tooltip content="FIPS mode of operation is enabled for your instance. All cryptographic operations within the FIPS boundaries are validated to be FIPS compliant.">
|
||||
<div>
|
||||
<Badge className="flex items-center gap-2" variant="primary">
|
||||
FIPS Mode: Enabled
|
||||
<FontAwesomeIcon icon={faInfoCircle} />
|
||||
</Badge>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
|
||||
Reference in New Issue
Block a user