Skip to content

Extract API Key from Secrets Manager when using the metrics API client #609

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 8 commits into from
Jan 23, 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
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -76,6 +77,7 @@ export type Config = MetricsConfig & TraceConfig & GlobalConfig;
export const defaultConfig: Config = {
apiKey: "",
apiKeyKMS: "",
apiKeySecretARN: "",
autoPatchHTTP: true,
captureLambdaPayload: false,
captureLambdaPayloadMaxDepth: 10,
Expand Down Expand Up @@ -344,6 +346,10 @@ function getConfig(userConfig?: Partial<Config>): 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";
Expand Down
41 changes: 41 additions & 0 deletions src/metrics/listener.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ 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().mockReturnValue({
promise: jest.fn().mockResolvedValue({
SecretString: "api-key-secret",
}),
}),
}));
});

const siteURL = "example.com";

class MockKMS {
Expand All @@ -34,6 +44,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,
Expand All @@ -54,6 +65,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,
Expand All @@ -67,11 +79,35 @@ 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, {
apiKey: "",
apiKeyKMS: "kms-api-key-encrypted",
apiKeySecretARN: "api-key-secret-arn",
enhancedMetrics: false,
logForwarding: false,
shouldRetryMetrics: false,
Expand All @@ -91,6 +127,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,
Expand Down Expand Up @@ -124,6 +161,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,
Expand Down Expand Up @@ -159,6 +197,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,
Expand Down Expand Up @@ -198,6 +237,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,
Expand All @@ -219,6 +259,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,
Expand Down
15 changes: 15 additions & 0 deletions src/metrics/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -215,6 +219,17 @@ export class MetricsListener {
logError("couldn't decrypt kms api key", error as Error);
}
}

if (config.apiKeySecretARN !== "") {
try {
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) {
logError("couldn't get secrets manager api key", error as Error);
}
}
return "";
}

Expand Down
Loading