8000 [breaking] FIPS compliant metrics + secrets management by nhulston · Pull Request #649 · DataDog/datadog-lambda-js · GitHub
[go: up one dir, main page]

Skip to content

[breaking] FIPS compliant metrics + secrets management #649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions src/metrics/kms-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});
Expand Down
11 changes: 4 additions & 7 deletions src/metrics/kms-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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`,
};
}

Expand Down
34 changes: 2 additions & 32 deletions src/metrics/listener.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,41 +123,11 @@ describe("MetricsListener", () => {
await expect(listener.onCompleteInvocation()).resolves.toEqual(undefined);
});

it("configures FIPS endpoint for GovCloud regions", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we keep a version of this test like configures FIPS endpoint for FIPS_MODE_ENABLED?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved those tests to fips.spec.ts; since we're moving the FIPS enabled/disabled logic to there, this was a little redundant

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");

Expand Down
12 changes: 9 additions & 3 deletions src/metrics/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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,
628C });
const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN });
Expand Down
44 changes: 44 additions & 0 deletions src/utils/fips.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
13 changes: 13 additions & 0 deletions src/utils/fips.ts
Original file line number Diff line number Diff line change
@@ -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"}.`);
}
Loading
0