From fab8446facddfdd8fd283ba16ba8568fc9fa7959 Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Wed, 22 Jan 2025 11:16:29 -0500 Subject: [PATCH 1/8] init get secret --- src/index.ts | 6 ++++++ src/metrics/listener.spec.ts | 8 ++++++++ src/metrics/listener.ts | 15 +++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/index.ts b/src/index.ts index 5e107006..92746348 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import { SpanOptions, TracerWrapper } from "./trace/tracer-wrapper"; export { DatadogTraceHeaders as TraceHeaders } from "./trace/context/extractor"; export const apiKeyEnvVar = "DD_API_KEY"; export const apiKeyKMSEnvVar = "DD_KMS_API_KEY"; +export const apiKeySecretARNEnvVar = "DD_API_KEY_SECRET_ARN" export const captureLambdaPayloadEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD"; export const captureLambdaPayloadMaxDepthEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH"; export const traceManagedServicesEnvVar = "DD_TRACE_MANAGED_SERVICES"; @@ -76,6 +77,7 @@ export type Config = MetricsConfig & TraceConfig & GlobalConfig; export const defaultConfig: Config = { apiKey: "", apiKeyKMS: "", + apiKeySecretARN: "", autoPatchHTTP: true, captureLambdaPayload: false, captureLambdaPayloadMaxDepth: 10, @@ -344,6 +346,10 @@ function getConfig(userConfig?: Partial): Config { config.apiKeyKMS = getEnvValue(apiKeyKMSEnvVar, ""); } + if (config.apiKeySecretARN === "") { + config.apiKeySecretARN = getEnvValue(apiKeySecretARNEnvVar, ""); + } + if (userConfig === undefined || userConfig.injectLogContext === undefined) { const result = getEnvValue(logInjectionEnvVar, "true").toLowerCase(); config.injectLogContext = result === "true"; diff --git a/src/metrics/listener.spec.ts b/src/metrics/listener.spec.ts index abf6771f..98f745c0 100644 --- a/src/metrics/listener.spec.ts +++ b/src/metrics/listener.spec.ts @@ -34,6 +34,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "api-key", apiKeyKMS: "kms-api-key-encrypted", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: false, shouldRetryMetrics: false, @@ -54,6 +55,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "", apiKeyKMS: "kms-api-key-encrypted", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: false, shouldRetryMetrics: false, @@ -72,6 +74,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "", apiKeyKMS: "kms-api-key-encrypted", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: false, shouldRetryMetrics: false, @@ -91,6 +94,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "api-key", apiKeyKMS: "kms-api-key-encrypted", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: true, shouldRetryMetrics: false, @@ -124,6 +128,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "api-key", apiKeyKMS: "", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: true, shouldRetryMetrics: false, @@ -159,6 +164,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "api-key", apiKeyKMS: "", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: false, shouldRetryMetrics: false, @@ -198,6 +204,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "api-key", apiKeyKMS: "", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: false, shouldRetryMetrics: false, @@ -219,6 +226,7 @@ describe("MetricsListener", () => { const listener = new MetricsListener(kms as any, { apiKey: "api-key", apiKeyKMS: "kms-api-key-encrypted", + apiKeySecretARN: "api-key-secret-arn", enhancedMetrics: false, logForwarding: true, shouldRetryMetrics: false, diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index 014822cc..edd49336 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -28,6 +28,10 @@ export interface MetricsConfig { * be decrypted before any metrics are sent. */ apiKeyKMS: string; + /** + * An api key stored in secrets manager used to talk to the Datadog API. + */ + apiKeySecretARN: string; /** * The site of the Datadog URL to send to. This should either be 'datadoghq.com', (default), * or 'datadoghq.eu', for customers in the eu. @@ -215,6 +219,17 @@ export class MetricsListener { logError("couldn't decrypt kms api key", error as Error); } } + + if (config.apiKeySecretARN !== "") { + try { + const secretsClient = require("aws-sdk/clients/secretsmanager"); + const secretsManager = new secretsClient(); + const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }); + return secret.SecretString; + } catch (error) { + logError("couldn't get secrets api key", error as Error); + } + } return ""; } From 7021e2201806a165f06c7406fa1b710adf7fa31c Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Wed, 22 Jan 2025 13:08:22 -0500 Subject: [PATCH 2/8] cleanup w/ test --- src/index.ts | 2 +- src/metrics/listener.spec.ts | 31 +++++++++++++++++++++++++++++++ src/metrics/listener.ts | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 92746348..80250755 100644 --- a/src/index.ts +++ b/src/index.ts @@ -31,7 +31,7 @@ import { SpanOptions, TracerWrapper } from "./trace/tracer-wrapper"; export { DatadogTraceHeaders as TraceHeaders } from "./trace/context/extractor"; export const apiKeyEnvVar = "DD_API_KEY"; export const apiKeyKMSEnvVar = "DD_KMS_API_KEY"; -export const apiKeySecretARNEnvVar = "DD_API_KEY_SECRET_ARN" +export const apiKeySecretARNEnvVar = "DD_API_KEY_SECRET_ARN"; export const captureLambdaPayloadEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD"; export const captureLambdaPayloadMaxDepthEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH"; export const traceManagedServicesEnvVar = "DD_TRACE_MANAGED_SERVICES"; diff --git a/src/metrics/listener.spec.ts b/src/metrics/listener.spec.ts index 98f745c0..76c6ccc4 100644 --- a/src/metrics/listener.spec.ts +++ b/src/metrics/listener.spec.ts @@ -9,6 +9,14 @@ import StatsDClient from "hot-shots"; import { Context } from "aws-lambda"; jest.mock("hot-shots"); +jest.mock("aws-sdk/clients/secretsmanager", () => { + return jest.fn().mockImplementation(() => ({ + getSecretValue: jest.fn().mockResolvedValue({ + SecretString: "api-key-secret", + }), + })); +}); + const siteURL = "example.com"; class MockKMS { @@ -69,6 +77,29 @@ describe("MetricsListener", () => { expect(nock.isDone()).toBeTruthy(); }); + + it("extracts the API Key from the secret manager to send a metric", async () => { + nock("https://api.example.com").post("/api/v1/distribution_points?api_key=api-key-secret").reply(200, {}); + + const kms = new MockKMS("kms-api-key-decrypted"); + const listener = new MetricsListener(kms as any, { + apiKey: "", + apiKeyKMS: "", + apiKeySecretARN: "api-key-secret-arn", + enhancedMetrics: false, + logForwarding: false, + shouldRetryMetrics: false, + localTesting: false, + siteURL, + }); + + await listener.onStartInvocation({}); + listener.sendDistributionMetricWithDate("my-metric", 10, new Date(), false, "tag:a", "tag:b"); + await listener.onCompleteInvocation(); + + expect(nock.isDone()).toBeTruthy(); + }); + it("doesn't throw an error if it can't get a valid apiKey", async () => { const kms = new MockKMS("kms-api-key-decrypted", new Error("The error")); const listener = new MetricsListener(kms as any, { diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index edd49336..29913f0c 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -227,7 +227,7 @@ export class MetricsListener { const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }); return secret.SecretString; } catch (error) { - logError("couldn't get secrets api key", error as Error); + logError("couldn't get secrets manager api key", error as Error); } } return ""; From 80fa39f20e7c53dc2d96586827df894fa525d68e Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Wed, 22 Jan 2025 14:31:14 -0500 Subject: [PATCH 3/8] testing logic --- src/metrics/listener.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index 29913f0c..e7c883ed 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -224,7 +224,11 @@ export class MetricsListener { try { const secretsClient = require("aws-sdk/clients/secretsmanager"); const secretsManager = new secretsClient(); - const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }); + const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }).promise(); + if (secret === undefined || secret.SecretString === undefined) { + console.log("=================== TODO DYLAN delete") + console.log({secret}) + } return secret.SecretString; } catch (error) { logError("couldn't get secrets manager api key", error as Error); From d71788c15e58999cb72ca48202443271c5604cd8 Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Wed, 22 Jan 2025 14:34:42 -0500 Subject: [PATCH 4/8] pretty --- src/metrics/listener.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index e7c883ed..005b75a1 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -226,8 +226,8 @@ export class MetricsListener { const secretsManager = new secretsClient(); const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }).promise(); if (secret === undefined || secret.SecretString === undefined) { - console.log("=================== TODO DYLAN delete") - console.log({secret}) + console.log("=================== TODO DYLAN delete"); + console.log({ secret }); } return secret.SecretString; } catch (error) { From 5c9f618a20667315aee0583d70aa5d2a691cc3e1 Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Wed, 22 Jan 2025 14:44:18 -0500 Subject: [PATCH 5/8] update test --- src/metrics/listener.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/metrics/listener.spec.ts b/src/metrics/listener.spec.ts index 76c6ccc4..24e2417a 100644 --- a/src/metrics/listener.spec.ts +++ b/src/metrics/listener.spec.ts @@ -11,8 +11,10 @@ jest.mock("hot-shots"); jest.mock("aws-sdk/clients/secretsmanager", () => { return jest.fn().mockImplementation(() => ({ - getSecretValue: jest.fn().mockResolvedValue({ - SecretString: "api-key-secret", + getSecretValue: jest.fn().mockReturnValue({ + promise: jest.fn().mockResolvedValue({ + SecretString: "api-key-secret", + }), }), })); }); From c6eaac809d07e0b4e6f48fa90fc949cfa14a85e3 Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Wed, 22 Jan 2025 15:01:08 -0500 Subject: [PATCH 6/8] rm extra --- src/metrics/listener.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index 005b75a1..fa994162 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -225,10 +225,6 @@ export class MetricsListener { const secretsClient = require("aws-sdk/clients/secretsmanager"); const secretsManager = new secretsClient(); const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }).promise(); - if (secret === undefined || secret.SecretString === undefined) { - console.log("=================== TODO DYLAN delete"); - console.log({ secret }); - } return secret.SecretString; } catch (error) { logError("couldn't get secrets manager api key", error as Error); From 7a274631b7ea9038ecf371dc67e898cde1c97e1f Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Thu, 23 Jan 2025 09:38:05 -0500 Subject: [PATCH 7/8] appease code quality violation check --- src/metrics/listener.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index fa994162..a9c651f8 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -222,10 +222,10 @@ export class MetricsListener { if (config.apiKeySecretARN !== "") { try { - const secretsClient = require("aws-sdk/clients/secretsmanager"); - const secretsManager = new secretsClient(); + const { default: SecretsManager } = await import("aws-sdk/clients/secretsmanager"); + const secretsManager = new SecretsManager(); const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }).promise(); - return secret.SecretString; + return secret?.SecretString ?? ""; } catch (error) { logError("couldn't get secrets manager api key", error as Error); } From 5781cac096ed01561fcadd4db1e9808a9f068a1e Mon Sep 17 00:00:00 2001 From: Dylan Yang Date: Thu, 23 Jan 2025 09:51:22 -0500 Subject: [PATCH 8/8] lint --- src/metrics/listener.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/metrics/listener.ts b/src/metrics/listener.ts index a9c651f8..b59bae08 100644 --- a/src/metrics/listener.ts +++ b/src/metrics/listener.ts @@ -222,8 +222,8 @@ export class MetricsListener { if (config.apiKeySecretARN !== "") { try { - const { default: SecretsManager } = await import("aws-sdk/clients/secretsmanager"); - const secretsManager = new SecretsManager(); + const { default: secretsClient } = await import("aws-sdk/clients/secretsmanager"); + const secretsManager = new secretsClient(); const secret = await secretsManager.getSecretValue({ SecretId: config.apiKeySecretARN }).promise(); return secret?.SecretString ?? ""; } catch (error) {