diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTask.kt index 0715d43a8cc..614b3d7809c 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTask.kt @@ -59,7 +59,8 @@ abstract class ParseV2SupportedConfigurationsTask @Inject constructor( configMap["type"] as? String, configMap["default"] as? String, (configMap["aliases"] as? List) ?: emptyList(), - (configMap["propertyKeys"] as? List) ?: emptyList() + (configMap["propertyKeys"] as? List) ?: emptyList(), + configMap["sensitive"] as? Boolean ?: false ) } } @@ -81,6 +82,10 @@ abstract class ParseV2SupportedConfigurationsTask @Inject constructor( } }.toMap() + val sensitiveKeys: Set = supported.flatMap { (canonical, configList) -> + configList.filter { it.sensitive }.flatMap { listOf(canonical) + it.aliases } + }.toSet() + // Build the output .java path from the fully-qualified class name val pkgName = finalClassName.substringBeforeLast('.', "") val pkgPath = pkgName.replace('.', File.separatorChar) @@ -97,7 +102,8 @@ abstract class ParseV2SupportedConfigurationsTask @Inject constructor( aliases, aliasMapping, deprecated, - reversePropertyKeysMap + reversePropertyKeysMap, + sensitiveKeys ) } @@ -109,7 +115,8 @@ abstract class ParseV2SupportedConfigurationsTask @Inject constructor( aliases: Map>, aliasMapping: Map, deprecated: Map, - reversePropertyKeysMap: Map + reversePropertyKeysMap: Map, + sensitiveKeys: Set ) { val outFile = File(outputPath) outFile.parentFile?.mkdirs() @@ -118,13 +125,16 @@ abstract class ParseV2SupportedConfigurationsTask @Inject constructor( out.println("package $packageName;") out.println() out.println("import java.util.HashMap;") + out.println("import java.util.HashSet;") out.println("import java.util.List;") out.println("import java.util.Map;") + out.println("import java.util.Set;") out.println("import static java.util.Arrays.asList;") out.println("import static java.util.Collections.emptyList;") out.println("import static java.util.Collections.singletonList;") out.println("import static java.util.Collections.unmodifiableList;") out.println("import static java.util.Collections.unmodifiableMap;") + out.println("import static java.util.Collections.unmodifiableSet;") out.println() out.println("public final class $className {") out.println() @@ -138,12 +148,15 @@ abstract class ParseV2SupportedConfigurationsTask @Inject constructor( out.println() out.println(" public static final Map REVERSE_PROPERTY_KEYS_MAP;") out.println() + out.println(" public static final Set SENSITIVE_KEYS;") + out.println() out.println(" static {") out.println(" SUPPORTED = initSupported();") out.println(" ALIASES = initAliases();") out.println(" ALIAS_MAPPING = initAliasMapping();") out.println(" DEPRECATED = initDeprecated();") out.println(" REVERSE_PROPERTY_KEYS_MAP = initReversePropertyKeysMap();") + out.println(" SENSITIVE_KEYS = initSensitiveKeys();") out.println(" }") out.println() @@ -218,6 +231,16 @@ abstract class ParseV2SupportedConfigurationsTask @Inject constructor( } out.println(" return unmodifiableMap(reversePropertyKeysMapping);") out.println(" }") + out.println() + + // initSensitiveKeys() + out.println(" private static Set initSensitiveKeys() {") + out.println(" Set sensitiveKeys = new HashSet<>();") + for (key in sensitiveKeys.toSortedSet()) { + out.printf(" sensitiveKeys.add(\"%s\");\n", esc(key)) + } + out.println(" return unmodifiableSet(sensitiveKeys);") + out.println(" }") out.println("}") } } @@ -258,5 +281,6 @@ private data class SupportedConfigurationItem( val type: String?, val default: String?, val aliases: List, - val propertyKeys: List + val propertyKeys: List, + val sensitive: Boolean ) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt index f6583d4cdba..a71de75adbd 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/config/ParseV2SupportedConfigurationsTest.kt @@ -4,6 +4,7 @@ import datadog.gradle.plugin.GradleFixture import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.TaskOutcome import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import java.io.File @@ -26,6 +27,7 @@ class ParseV2SupportedConfigurationsTest : GradleFixture() { assertTrue(content.contains("public static final Map ALIAS_MAPPING;")) assertTrue(content.contains("public static final Map DEPRECATED;")) assertTrue(content.contains("public static final Map REVERSE_PROPERTY_KEYS_MAP;")) + assertTrue(content.contains("public static final Set SENSITIVE_KEYS;")) assertTrue(content.contains("private static Map> initSupported()")) assertTrue(content.contains("private static void initSupported1(Map> supportedMap)")) @@ -34,6 +36,7 @@ class ParseV2SupportedConfigurationsTest : GradleFixture() { assertTrue(content.contains("private static Map initAliasMapping()")) assertTrue(content.contains("private static Map initDeprecated()")) assertTrue(content.contains("private static Map initReversePropertyKeysMap()")) + assertTrue(content.contains("private static Set initSensitiveKeys()")) assertContainsSupportedConfig( content, @@ -41,7 +44,7 @@ class ParseV2SupportedConfigurationsTest : GradleFixture() { version = "A", type = "string", default = "null", - aliases = emptyList(), + aliases = listOf("DD_LEGACY_ACTION_EXECUTION_ID"), propertyKeys = listOf("property.key") ) @@ -63,7 +66,7 @@ class ParseV2SupportedConfigurationsTest : GradleFixture() { aliases = listOf("DD_ALIAS") ) - assertTrue(content.contains("""aliasesMap.put("DD_ACTION_EXECUTION_ID", emptyList())""")) + assertTrue(content.contains("""aliasesMap.put("DD_ACTION_EXECUTION_ID", singletonList("DD_LEGACY_ACTION_EXECUTION_ID"))""")) assertTrue(content.contains("""aliasesMap.put("DD_AGENTLESS_LOG_SUBMISSION_ENABLED", singletonList("DD_ALIAS"))""")) assertTrue(content.contains("""aliasMappingMap.put("DD_ALIAS", "DD_AGENTLESS_LOG_SUBMISSION_ENABLED")""")) @@ -72,6 +75,10 @@ class ParseV2SupportedConfigurationsTest : GradleFixture() { assertTrue(content.contains("""deprecatedMap.put("legacy.setting", "No longer supported")""")) assertTrue(content.contains("""reversePropertyKeysMapping.put("property.key", "DD_ACTION_EXECUTION_ID")""")) + + assertTrue(content.contains("""sensitiveKeys.add("DD_ACTION_EXECUTION_ID")""")) + assertTrue(content.contains("""sensitiveKeys.add("DD_LEGACY_ACTION_EXECUTION_ID")""")) + assertFalse(content.contains("""sensitiveKeys.add("DD_AGENTLESS_LOG_SUBMISSION_ENABLED")""")) } private fun runGradleTask(): Pair { @@ -85,8 +92,9 @@ class ParseV2SupportedConfigurationsTest : GradleFixture() { "version": "A", "type": "string", "default": null, - "aliases": [], - "propertyKeys": ["property.key"] + "aliases": ["DD_LEGACY_ACTION_EXECUTION_ID"], + "propertyKeys": ["property.key"], + "sensitive": true } ], "DD_AGENTLESS_LOG_SUBMISSION_ENABLED": [ 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..bbb88b6ef96 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/ConfigCollectorTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/ConfigCollectorTest.groovy @@ -39,7 +39,7 @@ class ConfigCollectorTest extends DDSpecification { // ConfigProvider.getStringNotEmpty AppSecConfig.APPSEC_AUTOMATED_USER_EVENTS_TRACKING | UserEventTrackingMode.EXTENDED.toString() // ConfigProvider.getStringExcludingSource - GeneralConfig.APPLICATION_KEY | "app-key" + DDTags.SERVICE | "my-service" // ConfigProvider.getBoolean TraceInstrumentationConfig.RESOLVER_USE_URL_CACHES | "true" // ConfigProvider.getInteger @@ -184,11 +184,11 @@ class ConfigCollectorTest extends DDSpecification { ConfigCollector.get().collect() when: - ConfigCollector.get().put('DD_API_KEY', 'sensitive data', ConfigOrigin.ENV, ABSENT_SEQ_ID) + ConfigCollector.get().put('api-key', 'sensitive data', ConfigOrigin.ENV, ABSENT_SEQ_ID) then: def collected = ConfigCollector.get().collect() - collected.get(ConfigOrigin.ENV).get('DD_API_KEY').stringValue() == '' + collected.get(ConfigOrigin.ENV).get('api-key').stringValue() == '' } def "collects common setting default values"() { diff --git a/metadata/supported-configurations.json b/metadata/supported-configurations.json index af0caea701f..49036685a57 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": [ @@ -206,7 +207,8 @@ "version": "A", "type": "string", "default": null, - "aliases": ["DD_APP_KEY"] + "aliases": ["DD_APP_KEY"], + "sensitive": true } ], "DD_APPLICATION_KEY_FILE": [ @@ -1022,7 +1024,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_CRASHTRACKING_PROXY_PORT": [ @@ -2398,7 +2401,8 @@ "version": "A", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_OTLP_LOGS_PROTOCOL": [ @@ -2534,7 +2538,8 @@ "version": "B", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_OTLP_METRICS_PROTOCOL": [ @@ -2582,7 +2587,8 @@ "version": "B", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_OTLP_TRACES_PROTOCOL": [ @@ -3342,7 +3348,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_PROFILING_PROXY_PORT": [ @@ -3726,7 +3733,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_RUM_DEFAULT_PRIVACY_LEVEL": [ @@ -4126,7 +4134,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "DD_TEST_FAILED_TEST_REPLAY_ENABLED": [ @@ -11870,7 +11879,8 @@ "version": "B", "type": "map", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "OTEL_EXPORTER_OTLP_PROTOCOL": [ @@ -11910,7 +11920,8 @@ "version": "A", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL": [ @@ -11950,7 +11961,8 @@ "version": "B", "type": "string", "default": null, - "aliases": [] + "aliases": [], + "sensitive": true } ], "OTEL_EXPORTER_OTLP_METRICS_PROTOCOL": [ @@ -12006,7 +12018,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 35f27491157..76f9bfcd5bd 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 @@ -1,14 +1,11 @@ package datadog.trace.api; -import static datadog.trace.util.ConfigStrings.propertyNameToEnvironmentVariableName; -import static datadog.trace.util.ConfigStrings.toEnvVar; +import static datadog.trace.util.ConfigStrings.toCanonicalEnvVar; -import java.util.Arrays; +import datadog.trace.config.inversion.GeneratedSupportedConfigurations; import java.util.BitSet; -import java.util.HashSet; import java.util.Map; import java.util.Objects; -import java.util.Set; public final class ConfigSetting { public static final int DEFAULT_SEQ_ID = 1; @@ -23,16 +20,6 @@ public final class ConfigSetting { /** The config ID associated with this setting, or {@code null} if not applicable. */ public final String configId; - private static final Set CONFIG_FILTER_LIST = - new HashSet<>( - Arrays.asList( - "DD_API_KEY", - "dd.api-key", - "dd.profiling.api-key", - "dd.profiling.apikey", - "test.agent.session.token", - "DD_TEST_AGENT_SESSION_TOKEN")); - public static ConfigSetting of(String key, Object value, ConfigOrigin origin) { return new ConfigSetting(key, value, origin, ABSENT_SEQ_ID, null); } @@ -51,20 +38,21 @@ public static ConfigSetting of( return new ConfigSetting(key, value, origin, seqId, configId); } + // Redact values of configs flagged "sensitive": true in metadata/supported-configurations.json. private ConfigSetting(String key, Object value, ConfigOrigin origin, int seqId, String configId) { this.key = key; - this.value = CONFIG_FILTER_LIST.contains(key) ? "" : value; + this.value = + (value != null + && GeneratedSupportedConfigurations.SENSITIVE_KEYS.contains(toCanonicalEnvVar(key))) + ? "" + : value; this.origin = origin; this.seqId = seqId; this.configId = configId; } public String normalizedKey() { - // OTel configurations should not be normalized with DD_ - if (key.startsWith("otel.") || key.startsWith("OTEL_")) { - return toEnvVar(key); - } - return propertyNameToEnvironmentVariableName(key); + return toCanonicalEnvVar(key); } public String stringValue() { diff --git a/utils/config-utils/src/main/java/datadog/trace/util/ConfigStrings.java b/utils/config-utils/src/main/java/datadog/trace/util/ConfigStrings.java index 2937e19a372..16307cdbea2 100644 --- a/utils/config-utils/src/main/java/datadog/trace/util/ConfigStrings.java +++ b/utils/config-utils/src/main/java/datadog/trace/util/ConfigStrings.java @@ -27,6 +27,26 @@ public static String propertyNameToEnvironmentVariableName(final String setting) return "DD_" + toEnvVar(setting); } + /** + * Converts a config key in any form into its canonical environment variable name, regardless of + * whether it arrived as a property name ({@code api-key}), a {@code dd.} system property ({@code + * dd.api-key}), or a raw environment variable ({@code DD_API_KEY}) -- all canonicalize to {@code + * DD_API_KEY}. Unlike {@link #propertyNameToEnvironmentVariableName(String)}, the {@code DD_} + * namespace is added only when not already present, so a key already in env-var form is not + * double-prefixed. {@code otel.}/{@code OTEL_} keys keep their own namespace. + * + * @param key The config key in any form + * @return The canonical environment variable name + */ + @Nonnull + public static String toCanonicalEnvVar(final String key) { + if (key.startsWith("otel.") || key.startsWith("OTEL_")) { + return toEnvVar(key); + } + final String env = toEnvVar(key); + return env.startsWith("DD_") ? env : "DD_" + env; + } + /** * Converts the property name, e.g. 'service.name' into a public system property name, e.g. * `dd.service.name`. 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 1d110be1287..ad2f23c491a 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 @@ -44,19 +44,32 @@ 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 | ", - "session token prop | test.agent.session.token | somevalue | ", - "session token env | DD_TEST_AGENT_SESSION_TOKEN | somevalue | ", - "some.other.key | some.other.key | somevalue | somevalue " + "scenario | key | value | filteredValue", + "api key property | api-key | somevalue | ", + "api key system prop | dd.api-key | somevalue | ", + "api key env var | DD_API_KEY | somevalue | ", + "application key | application-key | somevalue | ", + "application alias | DD_APP_KEY | somevalue | ", + "otlp traces headers | otlp.traces.headers | somevalue | ", + "proxy password | profiling.proxy.password | somevalue | ", + "session token prop | test.agent.session.token | somevalue | ", + "session token env | DD_TEST_AGENT_SESSION_TOKEN | somevalue | ", + "non-sensitive 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 | normalized ", + "property name | api-key | DD_API_KEY ", + "dd env var | DD_PROFILING_API_KEY | DD_PROFILING_API_KEY", + "otel env var | OTEL_SERVICE_NAME | OTEL_SERVICE_NAME " + }) + void normalizesKeyName(String key, String normalized) { + assertEquals(normalized, ConfigSetting.of(key, "v", ConfigOrigin.DEFAULT).normalizedKey()); + } + @TableTest({ "scenario | value | rendered", "null | | ", diff --git a/utils/config-utils/src/test/java/datadog/trace/util/ConfigStringsTest.java b/utils/config-utils/src/test/java/datadog/trace/util/ConfigStringsTest.java index c8b5acb8dfd..d87c5581426 100644 --- a/utils/config-utils/src/test/java/datadog/trace/util/ConfigStringsTest.java +++ b/utils/config-utils/src/test/java/datadog/trace/util/ConfigStringsTest.java @@ -5,6 +5,7 @@ import java.util.Locale; import org.junit.jupiter.api.Test; +import org.tabletest.junit.TableTest; class ConfigStringsTest { @@ -30,4 +31,19 @@ void toEnvVarUppercasesLowerIToAsciiIOnTurkishLocale() { Locale.setDefault(previousDefault); } } + + @TableTest({ + "scenario | key | canonical ", + "property name | api-key | DD_API_KEY ", + "dotted property | profiling.proxy.password | DD_PROFILING_PROXY_PASSWORD", + "dd system property | dd.api-key | DD_API_KEY ", + "raw env var | DD_API_KEY | DD_API_KEY ", + "alias env var | DD_APP_KEY | DD_APP_KEY ", + "otel property | otel.exporter.otlp.headers | OTEL_EXPORTER_OTLP_HEADERS ", + "otel env var | OTEL_EXPORTER_OTLP_HEADERS | OTEL_EXPORTER_OTLP_HEADERS ", + "non-dd env var | AWS_LAMBDA_FUNCTION_NAME | DD_AWS_LAMBDA_FUNCTION_NAME" + }) + void canonicalizesKeysToEnvVarForm(String key, String canonical) { + assertEquals(canonical, ConfigStrings.toCanonicalEnvVar(key)); + } }