diff --git a/src/metrics/kms-service.spec.ts b/src/metrics/kms-service.spec.ts index a12e6106..6892884e 100644 --- a/src/metrics/kms-service.spec.ts +++ b/src/metrics/kms-service.spec.ts @@ -100,7 +100,7 @@ describe("KMSService", () => { }); const kmsService = new KMSService(); - const result = await kmsService.decryptV3(Buffer.from(ENCRYPTED_KEY, "base64")); + const result = await kmsService.decryptV3(Buffer.from(ENCRYPTED_KEY, "base64"), {}); expect(Buffer.from(result).toString("ascii")).toEqual(EXPECTED_RESULT); fakeKmsCall.done(); }); @@ -116,8 +116,34 @@ describe("KMSService", () => { }); const kmsService = new KMSService(); - const result = await kmsService.decryptV3(Buffer.from(ENCRYPTED_KEY, "base64")); + const result = await kmsService.decryptV3(Buffer.from(ENCRYPTED_KEY, "base64"), {}); expect(Buffer.from(result).toString("ascii")).toEqual(EXPECTED_RESULT); fakeKmsCall.done(); }); + + it("configures FIPS endpoint for GovCloud regions", async () => { + try { + process.env.AWS_REGION = "us-gov-west-1"; + + const mockKmsConstructor = jest.fn(); + jest.mock("aws-sdk/clients/kms", () => mockKmsConstructor); + mockKmsConstructor.mockImplementation(() => ({ + decrypt: () => ({ + promise: () => Promise.resolve({ Plaintext: Buffer.from(EXPECTED_RESULT) }), + }), + })); + + // Create service and call decrypt + 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", + }); + } finally { + process.env.AWS_REGION = "us-east-1"; + jest.restoreAllMocks(); + } + }); }); diff --git a/src/metrics/kms-service.ts b/src/metrics/kms-service.ts index f4aab9b4..d10950f4 100644 --- a/src/metrics/kms-service.ts +++ b/src/metrics/kms-service.ts @@ -1,6 +1,8 @@ // In order to avoid the layer adding the 40mb aws-sdk to a deployment, (which is always available // in the Lambda environment anyway), we use require to import the SDK. +import { logDebug } from "../utils"; + export class KMSService { private encryptionContext; @@ -12,6 +14,19 @@ 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) { + // Endpoints: https://docs.aws.amazon.com/general/latest/gr/kms.html + kmsClientParams = { + endpoint: `https://kms-fips.${region}.amazonaws.com`, + }; + } + // Explicitly try/catch this require to appease esbuild and ts compiler // otherwise users would need to mark this as `external` // see https://github.com/DataDog/datadog-lambda-js/pull/409 @@ -20,11 +35,12 @@ export class KMSService { } catch (err) { if ((err as any).code === "MODULE_NOT_FOUND") { // Node 18 - return this.decryptV3(buffer); + return this.decryptV3(buffer, kmsClientParams); } } try { - const kmsClient = new kms(); + // Configure KMS client to use FIPS endpoint + const kmsClient = new kms(kmsClientParams); // When the API key is encrypted using the AWS console, the function name is added as an encryption context. // When the API key is encrypted using the AWS CLI, no encryption context is added. @@ -50,7 +66,7 @@ export class KMSService { } // Node 18 or AWS SDK V3 - public async decryptV3(buffer: Buffer): Promise { + public async decryptV3(buffer: Buffer, kmsClientParams: any): Promise { // tslint:disable-next-line: variable-name one-variable-per-declaration let KMSClient, DecryptCommand; // Explicitly try/catch this require to appease esbuild and ts compiler @@ -61,7 +77,8 @@ export class KMSService { } catch (e) { throw Error("Can't load AWS SDK v2 or v3 to decrypt KMS key, custom metrics may not be sent"); } - const kmsClient = new KMSClient(); + + const kmsClient = new KMSClient(kmsClientParams); let result; try { const decryptCommand = new DecryptCommand({ CiphertextBlob: buffer });