diff --git a/src/metrics/kms-service.spec.ts b/src/metrics/kms-service.spec.ts index 6892884e..ffbe7ec7 100644 --- a/src/metrics/kms-service.spec.ts +++ b/src/metrics/kms-service.spec.ts @@ -122,22 +122,27 @@ describe("KMSService", () => { }); it("configures FIPS endpoint for GovCloud regions", async () => { - try { - process.env.AWS_REGION = "us-gov-west-1"; + jest.resetModules(); + + jest.mock("../utils/fips", () => ({ + AWS_REGION: "us-gov-west-1", + FIPS_MODE_ENABLED: true, + })); - const mockKmsConstructor = jest.fn(); - jest.mock("aws-sdk/clients/kms", () => mockKmsConstructor); - mockKmsConstructor.mockImplementation(() => ({ - decrypt: () => ({ - promise: () => Promise.resolve({ Plaintext: Buffer.from(EXPECTED_RESULT) }), - }), - })); + const mockKmsConstructor = jest.fn().mockImplementation(() => ({ + decrypt: () => ({ + promise: () => Promise.resolve({ Plaintext: Buffer.from(EXPECTED_RESULT) }), + }), + })); + jest.mock("aws-sdk/clients/kms", () => mockKmsConstructor); - // Create service and call decrypt + // tslint:disable-next-line:no-shadowed-variable + const { KMSService } = require("./kms-service"); + + try { const kmsService = new KMSService(); await kmsService.decrypt(ENCRYPTED_KEY); - // Verify FIPS endpoint was used expect(mockKmsConstructor).toHaveBeenCalledWith({ endpoint: "https://kms-fips.us-gov-west-1.amazonaws.com", }); diff --git a/src/metrics/kms-service.ts b/src/metrics/kms-service.ts index d10950f4..9094d388 100644 --- a/src/metrics/kms-service.ts +++ b/src/metrics/kms-service.ts @@ -2,6 +2,7 @@ // in the Lambda environment anyway), we use require to import the SDK. import { logDebug } from "../utils"; +import { AWS_REGION, FIPS_MODE_ENABLED } from "../utils/fips"; export class KMSService { private encryptionContext; @@ -14,16 +15,12 @@ export class KMSService { const buffer = Buffer.from(ciphertext, "base64"); let kms; - const region = process.env.AWS_REGION; - const isGovRegion = region !== undefined && region.startsWith("us-gov-"); - if (isGovRegion) { - logDebug("Govcloud region detected. Using FIPs endpoints for secrets management."); - } let kmsClientParams = {}; - if (isGovRegion) { + if (FIPS_MODE_ENABLED) { + logDebug("FIPS mode is enabled, Using FIPS endpoints for secrets management."); // Endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html kmsClientParams = { - endpoint: `https://kms-fips.${region}.amazonaws.com`, + endpoint: `https://kms-fips.${AWS_REGION}.amazonaws.com`, }; } diff --git a/src/metrics/listener.spec.ts b/src/metrics/listener.spec.ts index 8cb9b1b8..506b720c 100644 --- a/src/metrics/listener.spec.ts +++ b/src/metrics/listener.spec.ts @@ -123,41 +123,11 @@ describe("MetricsListener", () => { await expect(listener.onCompleteInvocation()).resolves.toEqual(undefined); }); - it("configures FIPS endpoint for GovCloud regions", async () => { - try { - process.env.AWS_REGION = "us-gov-west-1"; - const secretsManagerModule = require("@aws-sdk/client-secrets-manager"); - const secretsManagerSpy = jest.spyOn(secretsManagerModule, "SecretsManager"); - - const kms = new MockKMS("kms-api-key-decrypted"); - const listener = new MetricsListener(kms as any, { - apiKey: "", - apiKeyKMS: "", - apiKeySecretARN: "arn:aws:secretsmanager:us-gov-west-1:1234567890:secret:key-name-123ABC", - enhancedMetrics: false, - logForwarding: false, - shouldRetryMetrics: false, - localTesting: false, - siteURL, - }); - - await listener.onStartInvocation({}); - await listener.onCompleteInvocation(); - - expect(secretsManagerSpy).toHaveBeenCalledWith({ - useFipsEndpoint: true, - region: "us-gov-west-1", - }); - - secretsManagerSpy.mockRestore(); - } finally { - process.env.AWS_REGION = "us-east-1"; - } - }); - it("uses correct secrets region", async () => { try { process.env.AWS_REGION = "us-east-1"; + + // tslint:disable-next-line:no-shadowed-variable const secretsManagerModule = require("@aws-sdk/client-secrets-manager"); const secretsManagerSpy = jest.spyOn(secretsManagerModule, "SecretsManager"); diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index bc349f0b..26c60d2b 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -7,6 +7,7 @@ import { Distribution } from "./model"; import { Context } from "aws-lambda"; import { getEnhancedMetricTags } from "./enhanced-metrics"; import { LambdaDogStatsD } from "./dogstatsd"; +import { FIPS_MODE_ENABLED } from "../utils/fips"; const METRICS_BATCH_SEND_INTERVAL = 10000; // 10 seconds const HISTORICAL_METRICS_THRESHOLD_HOURS = 4 * 60 * 60 * 1000; // 4 hours @@ -154,6 +155,13 @@ export class MetricsListener { } // Otherwise, send directly to DD API (not FIPs compliant!) + if (FIPS_MODE_ENABLED) { + logDebug( + "FIPS mode is enabled, so sending metrics via the Datadog API is not possible. (1) Install the extension, (2) enable log forwarding, or (3) disable FIPS mode.", + ); + return; + } + // Add global tags to metrics sent to the API if (this.globalTags !== undefined && this.globalTags.length > 0) { tags = [...tags, ...this.globalTags]; @@ -210,10 +218,8 @@ export class MetricsListener { try { const { SecretsManager } = await import("@aws-sdk/client-secrets-manager"); const secretRegion = config.apiKeySecretARN.split(":")[3]; - const lambdaRegion = process.env.AWS_REGION; - const isGovRegion = lambdaRegion !== undefined && lambdaRegion.startsWith("us-gov-"); const secretsManager = new SecretsManager({ - useFipsEndpoint: isGovRegion, + useFipsEndpoint: FIPS_MODE_ENABLED, region: secretRegion, }); const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }); diff --git a/src/utils/fips.spec.ts b/src/utils/fips.spec.ts new file mode 100644 index 00000000..f46ca21e --- /dev/null +++ b/src/utils/fips.spec.ts @@ -0,0 +1,44 @@ +describe("fips.ts", () => { + const ORIGINAL_ENV = process.env; + + beforeEach(() => { + jest.resetModules(); + process.env = { ...ORIGINAL_ENV }; + }); + + afterAll(() => { + process.env = ORIGINAL_ENV; + }); + + it("enables FIPS mode in GovCloud by default", () => { + process.env.AWS_REGION = "us-gov-west-1"; + delete process.env.DD_LAMBDA_FIPS_MODE; + + const { FIPS_MODE_ENABLED } = require("./fips"); + expect(FIPS_MODE_ENABLED).toBe(true); + }); + + it("disables FIPS mode in standard region by default", () => { + process.env.AWS_REGION = "us-east-1"; + delete process.env.DD_LAMBDA_FIPS_MODE; + + const { FIPS_MODE_ENABLED } = require("./fips"); + expect(FIPS_MODE_ENABLED).toBe(false); + }); + + it("enables FIPS mode when env var is set to true in a standard region", () => { + process.env.AWS_REGION = "us-east-1"; + process.env.DD_LAMBDA_FIPS_MODE = "true"; + + const { FIPS_MODE_ENABLED } = require("./fips"); + expect(FIPS_MODE_ENABLED).toBe(true); + }); + + it("disables FIPS mode when DD_LAMBDA_FIPS_MODE=false in GovCloud region", () => { + process.env.AWS_REGION = "us-gov-east-1"; + process.env.DD_LAMBDA_FIPS_MODE = "false"; + + const { FIPS_MODE_ENABLED } = require("./fips"); + expect(FIPS_MODE_ENABLED).toBe(false); + }); +}); diff --git a/src/utils/fips.ts b/src/utils/fips.ts new file mode 100644 index 00000000..09d130b2 --- /dev/null +++ b/src/utils/fips.ts @@ -0,0 +1,13 @@ +import { logDebug } from "./log"; + +export const AWS_REGION = process.env.AWS_REGION ?? ""; +const isGovRegion = AWS_REGION.startsWith("us-gov-"); + +// Determine FIPS mode default (enabled in Gov regions) and override via env var +const defaultFips = isGovRegion ? "true" : "false"; +const rawFipsEnv = process.env.DD_LAMBDA_FIPS_MODE ?? defaultFips; +export const FIPS_MODE_ENABLED = rawFipsEnv.toLowerCase() === "true"; + +if (isGovRegion || FIPS_MODE_ENABLED) { + logDebug(`Node Lambda Layer FIPS mode is ${FIPS_MODE_ENABLED ? "enabled" : "not enabled"}.`); +}