From 9639e058de8180b0bf397d9c0dda7e135d22dd60 Mon Sep 17 00:00:00 2001 From: Brian Marks Date: Fri, 5 Jun 2026 17:22:12 -0400 Subject: [PATCH] Redact OTLP header and Datadog key configurations from configuration telemetry Add the OTLP exporter header configurations and the Datadog API key and application key configurations to the telemetry configuration filter list so their values are reported as "" in the configuration telemetry: - OTEL_EXPORTER_OTLP_HEADERS - OTEL_EXPORTER_OTLP_TRACES_HEADERS - OTEL_EXPORTER_OTLP_METRICS_HEADERS - OTEL_EXPORTER_OTLP_LOGS_HEADERS - DD_API_KEY - DD_APPLICATION_KEY (and its DD_APP_KEY alias) For each configuration, every form that can reach ConfigSetting is covered: the dotted configuration names (otlp.traces.headers, otlp.metrics.headers, otlp.logs.headers, application-key, app-key) and the environment-variable names. Mark these configurations, DD_API_KEY, and DD_APPLICATION_KEY with "sensitive: true" in metadata/supported-configurations.json. Migrate ConfigSettingTest to JUnit 5 and extend it to cover the OTLP header and application key configurations, including an assertion that the configured value is not present in the reported telemetry value. Update ConfigCollectorTest so the application key collected through the ConfigCollector pipeline is asserted to render as "". Co-Authored-By: Claude Opus 4.8 --- .../trace/api/ConfigCollectorTest.groovy | 42 ++++++++++-------- metadata/supported-configurations.json | 27 ++++++++---- .../java/datadog/trace/api/ConfigSetting.java | 21 ++++++++- .../datadog/trace/api/ConfigSettingTest.java | 43 ++++++++++++++++--- 4 files changed, 98 insertions(+), 35 deletions(-) diff --git a/internal-api/src/test/groovy/datadog/trace/api/ConfigCollectorTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/ConfigCollectorTest.groovy index 7ac922c028c..7e25eeb5985 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/ConfigCollectorTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/ConfigCollectorTest.groovy @@ -27,43 +27,45 @@ class ConfigCollectorTest extends DDSpecification { expect: def envConfigByKey = ConfigCollector.get().collect().get(ConfigOrigin.ENV) def config = envConfigByKey.get(configKey) - config.stringValue() == configValue + config.stringValue() == expectedValue config.origin == ConfigOrigin.ENV where: - configKey | configValue + // expectedValue equals configValue for every setting except those redacted from configuration + // telemetry (e.g. the application key), where the collected value is rendered as "". + configKey | configValue | expectedValue // ConfigProvider.getEnum - IastConfig.IAST_TELEMETRY_VERBOSITY | Verbosity.DEBUG.toString() + IastConfig.IAST_TELEMETRY_VERBOSITY | Verbosity.DEBUG.toString() | configValue // ConfigProvider.getString - TracerConfig.TRACE_SPAN_ATTRIBUTE_SCHEMA | "v1" + TracerConfig.TRACE_SPAN_ATTRIBUTE_SCHEMA | "v1" | configValue // ConfigProvider.getStringNotEmpty - AppSecConfig.APPSEC_AUTOMATED_USER_EVENTS_TRACKING | UserEventTrackingMode.EXTENDED.toString() + AppSecConfig.APPSEC_AUTOMATED_USER_EVENTS_TRACKING | UserEventTrackingMode.EXTENDED.toString() | configValue // ConfigProvider.getStringExcludingSource - GeneralConfig.APPLICATION_KEY | "app-key" + GeneralConfig.APPLICATION_KEY | "app-key" | "" // ConfigProvider.getBoolean - TraceInstrumentationConfig.RESOLVER_USE_URL_CACHES | "true" + TraceInstrumentationConfig.RESOLVER_USE_URL_CACHES | "true" | configValue // ConfigProvider.getInteger - JmxFetchConfig.JMX_FETCH_CHECK_PERIOD | "60" + JmxFetchConfig.JMX_FETCH_CHECK_PERIOD | "60" | configValue // ConfigProvider.getLong - CiVisibilityConfig.CIVISIBILITY_GIT_COMMAND_TIMEOUT_MILLIS | "450273" + CiVisibilityConfig.CIVISIBILITY_GIT_COMMAND_TIMEOUT_MILLIS | "450273" | configValue // ConfigProvider.getFloat - GeneralConfig.TELEMETRY_HEARTBEAT_INTERVAL | "1.5" + GeneralConfig.TELEMETRY_HEARTBEAT_INTERVAL | "1.5" | configValue // ConfigProvider.getDouble - TracerConfig.TRACE_SAMPLE_RATE | "2.2" + TracerConfig.TRACE_SAMPLE_RATE | "2.2" | configValue // ConfigProvider.getList - TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS | "someTopic,otherTopic" + TraceInstrumentationConfig.JMS_PROPAGATION_DISABLED_TOPICS | "someTopic,otherTopic" | configValue // ConfigProvider.getSet - IastConfig.IAST_WEAK_HASH_ALGORITHMS | "SHA1,SHA-1" + IastConfig.IAST_WEAK_HASH_ALGORITHMS | "SHA1,SHA-1" | configValue // ConfigProvider.getSpacedList - TracerConfig.PROXY_NO_PROXY | "a b c" + TracerConfig.PROXY_NO_PROXY | "a b c" | configValue // ConfigProvider.getMergedMap - TracerConfig.TRACE_PEER_SERVICE_MAPPING | "service1:best_service,userService:my_service" + TracerConfig.TRACE_PEER_SERVICE_MAPPING | "service1:best_service,userService:my_service" | configValue // ConfigProvider.getOrderedMap - TracerConfig.TRACE_HTTP_SERVER_PATH_RESOURCE_NAME_MAPPING | "/asdf/*:/test" + TracerConfig.TRACE_HTTP_SERVER_PATH_RESOURCE_NAME_MAPPING | "/asdf/*:/test" | configValue // ConfigProvider.getMergedMapWithOptionalMappings - TracerConfig.HEADER_TAGS | "e:five" + TracerConfig.HEADER_TAGS | "e:five" | configValue // ConfigProvider.getIntegerRange - TracerConfig.TRACE_HTTP_CLIENT_ERROR_STATUSES | "400-402" + TracerConfig.TRACE_HTTP_CLIENT_ERROR_STATUSES | "400-402" | configValue } def "should collect merged data from multiple sources"() { @@ -131,8 +133,10 @@ class ConfigCollectorTest extends DDSpecification { cs.origin == ConfigOrigin.DEFAULT where: + // GeneralConfig.APPLICATION_KEY is redacted from configuration telemetry, so its collected + // value is rendered as "" rather than null; that redaction is verified in the + // "non-default config settings get collected" feature above. configKey << [ - GeneralConfig.APPLICATION_KEY, TraceInstrumentationConfig.RESOLVER_USE_URL_CACHES, JmxFetchConfig.JMX_FETCH_CHECK_PERIOD, CiVisibilityConfig.CIVISIBILITY_DEBUG_PORT, diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index 93e70b13a35..e0fd003789f 100644 --- a/metadata/supported-configurations.json +++ b/metadata/supported-configurations.json @@ -118,7 +118,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_API_KEY_FILE": [ @@ -198,7 +199,8 @@ "version": "A", "type": "string", "default": null, - "aliases": ["DD_APP_KEY"] + "aliases": ["DD_APP_KEY"], + "sensitive": true } ], "DD_APPLICATION_KEY_FILE": [ @@ -2310,7 +2312,8 @@ "version": "A", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_OTLP_LOGS_PROTOCOL": [ @@ -2454,7 +2457,8 @@ "version": "B", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_OTLP_METRICS_PROTOCOL": [ @@ -2502,7 +2506,8 @@ "version": "B", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_OTLP_TRACES_PROTOCOL": [ @@ -11646,7 +11651,8 @@ "version": "B", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "OTEL_EXPORTER_OTLP_PROTOCOL": [ @@ -11686,7 +11692,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL": [ @@ -11726,7 +11733,8 @@ "version": "B", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": [ @@ -11782,7 +11790,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL": [ diff --git a/utils/config-utils/src/main/java/datadog/trace/api/ConfigSetting.java b/utils/config-utils/src/main/java/datadog/trace/api/ConfigSetting.java index cf77e3bfb35..f6160db5804 100644 --- a/utils/config-utils/src/main/java/datadog/trace/api/ConfigSetting.java +++ b/utils/config-utils/src/main/java/datadog/trace/api/ConfigSetting.java @@ -23,9 +23,28 @@ public final class ConfigSetting { /** The config ID associated with this setting, or {@code null} if not applicable. */ public final String configId; + // Configuration keys whose values are excluded from configuration telemetry by replacing them + // with "". Keys are listed in every form that may reach this constructor: the dotted + // configuration name (used by ConfigProvider) and the environment-variable name. private static final Set CONFIG_FILTER_LIST = new HashSet<>( - Arrays.asList("DD_API_KEY", "dd.api-key", "dd.profiling.api-key", "dd.profiling.apikey")); + Arrays.asList( + "DD_API_KEY", + "dd.api-key", + "dd.profiling.api-key", + "dd.profiling.apikey", + "application-key", + "dd.application-key", + "DD_APPLICATION_KEY", + "app-key", + "dd.app-key", + "otlp.traces.headers", + "otlp.metrics.headers", + "otlp.logs.headers", + "OTEL_EXPORTER_OTLP_HEADERS", + "OTEL_EXPORTER_OTLP_TRACES_HEADERS", + "OTEL_EXPORTER_OTLP_METRICS_HEADERS", + "OTEL_EXPORTER_OTLP_LOGS_HEADERS")); public static ConfigSetting of(String key, Object value, ConfigOrigin origin) { return new ConfigSetting(key, value, origin, ABSENT_SEQ_ID, null); diff --git a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java index ad086241a6d..3ed0c933f0c 100644 --- a/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java +++ b/utils/config-utils/src/test/java/datadog/trace/api/ConfigSettingTest.java @@ -1,6 +1,7 @@ package datadog.trace.api; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import datadog.trace.junit.utils.tabletest.BoxedValueConverter; @@ -42,17 +43,47 @@ void supportsEqualityCheck( } @TableTest({ - "scenario | key | value | filteredValue", - "DD_API_KEY | DD_API_KEY | somevalue | ", - "dd.api-key | dd.api-key | somevalue | ", - "dd.profiling.api-key | dd.profiling.api-key | somevalue | ", - "dd.profiling.apikey | dd.profiling.apikey | somevalue | ", - "some.other.key | some.other.key | somevalue | somevalue " + "scenario | key | value | filteredValue", + "dd api key env | DD_API_KEY | somevalue | ", + "dd api key prop | dd.api-key | somevalue | ", + "profiling api key | dd.profiling.api-key | somevalue | ", + "profiling apikey | dd.profiling.apikey | somevalue | ", + "application key name | application-key | somevalue | ", + "application key prop | dd.application-key | somevalue | ", + "application key env | DD_APPLICATION_KEY | somevalue | ", + "app key alias name | app-key | somevalue | ", + "app key alias prop | dd.app-key | somevalue | ", + "otlp traces headers | otlp.traces.headers | somevalue | ", + "otlp metrics headers | otlp.metrics.headers | somevalue | ", + "otlp logs headers | otlp.logs.headers | somevalue | ", + "otel otlp headers | OTEL_EXPORTER_OTLP_HEADERS | somevalue | ", + "otel traces headers | OTEL_EXPORTER_OTLP_TRACES_HEADERS | somevalue | ", + "otel metrics headers | OTEL_EXPORTER_OTLP_METRICS_HEADERS | somevalue | ", + "otel logs headers | OTEL_EXPORTER_OTLP_LOGS_HEADERS | somevalue | ", + "other key | some.other.key | somevalue | somevalue " }) void filtersKeyValues(String key, String value, String filteredValue) { assertEquals(filteredValue, ConfigSetting.of(key, value, ConfigOrigin.DEFAULT).stringValue()); } + @TableTest({ + "scenario | key | value ", + "otlp traces | otlp.traces.headers | dd-api-key=secret-traces ", + "otlp metrics | otlp.metrics.headers | dd-api-key=secret-metrics", + "otlp logs | otlp.logs.headers | dd-api-key=secret-logs ", + "otel base | OTEL_EXPORTER_OTLP_HEADERS | dd-api-key=secret-base ", + "otel traces | OTEL_EXPORTER_OTLP_TRACES_HEADERS | dd-api-key=secret-traces ", + "otel metrics | OTEL_EXPORTER_OTLP_METRICS_HEADERS | dd-api-key=secret-metrics", + "otel logs | OTEL_EXPORTER_OTLP_LOGS_HEADERS | dd-api-key=secret-logs ", + "dd api key | DD_API_KEY | secret-api-key " + }) + void doesNotExposeSensitiveValues(String key, String value) { + String rendered = ConfigSetting.of(key, value, ConfigOrigin.ENV).stringValue(); + assertEquals("", rendered); + assertFalse( + rendered.contains(value), "rendered telemetry value must not contain the configured value"); + } + @TableTest({ "scenario | value | rendered", "null | | ",