Skip to content

Commit d3df1fa

Browse files
bm1549claude
andcommitted
[Configuration] Redact OTLP header configurations in configuration telemetry
Redact the OTEL_EXPORTER_OTLP_HEADERS family (base, METRICS, TRACES, LOGS) in instrumentation configuration telemetry. The values are now recorded as <redacted> in the configuration array of app-started and app-client-configuration-change events. - Switch the OTLP header reads in ExporterSettings to AsRedactedString(). - Add AsRedactedDictionaryResult and thread a recordValue flag through the dictionary configuration sources so the OTLP metrics/logs header reads in TracerSettings are recorded as redacted. - Mark the four OTLP header variants with sensitive: true in supported-configurations.yaml and accept the attribute in the YAML reader. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4c9508f commit d3df1fa

14 files changed

Lines changed: 182 additions & 27 deletions

tracer/src/Datadog.Trace.SourceGenerators/Helpers/YamlReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public static ParsedConfigurationData ParseSupportedConfigurations(string yamlCo
140140
if (propColonIdx > 0)
141141
{
142142
var propName = trimmedLine.Substring(0, propColonIdx);
143-
if (propName is "const_name" or "product" or "implementation" or "type" or "default" or "aliases" or "deprecation_message")
143+
if (propName is "const_name" or "product" or "implementation" or "type" or "default" or "aliases" or "deprecation_message" or "sensitive")
144144
{
145145
// End of documentation, process this property
146146
inDocumentation = false;

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/CompositeConfigurationSource.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,14 @@ public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key
192192
=> GetDictionary(key, telemetry, validator, parser: null, allowOptionalMappings: false, separator: null);
193193

194194
/// <inheritdoc />
195-
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
196-
=> GetDictionary(key, telemetry, validator, parser: null, allowOptionalMappings, separator);
195+
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator, bool recordValue = true)
196+
=> GetDictionary(key, telemetry, validator, parser: null, allowOptionalMappings, separator, recordValue);
197197

198198
/// <inheritdoc />
199199
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, Func<string, IDictionary<string, string>> parser)
200200
=> GetDictionary(key, telemetry, validator, parser, allowOptionalMappings: false, separator: null);
201201

202-
private ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, Func<string, IDictionary<string, string>>? parser, bool allowOptionalMappings, char? separator)
202+
private ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, Func<string, IDictionary<string, string>>? parser, bool allowOptionalMappings, char? separator, bool recordValue = true)
203203
{
204204
// We iterate in reverse order, and keep the last successful value
205205
// because we need to record the data for all the sources in telemetry
@@ -216,7 +216,7 @@ private ConfigurationResult<IDictionary<string, string>> GetDictionary(string ke
216216
}
217217
else if (separator.HasValue)
218218
{
219-
value = source.GetDictionary(key, telemetry, validator, allowOptionalMappings, separator.Value);
219+
value = source.GetDictionary(key, telemetry, validator, allowOptionalMappings, separator.Value, recordValue);
220220
}
221221
else
222222
{
@@ -238,7 +238,7 @@ private ConfigurationResult<IDictionary<string, string>> GetDictionary(string ke
238238
if (result.IsValid && !isLastFound)
239239
{
240240
// there should always be a telemetry override by convention, so just record a sentinel for now if there's not for some reason
241-
telemetry.Record(key, result.TelemetryOverride ?? "<MISSING>", recordValue: true, origin);
241+
telemetry.Record(key, result.TelemetryOverride ?? "<MISSING>", recordValue, origin);
242242
}
243243

244244
return result;

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/DictionaryObjectConfigurationSource.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,13 @@ public ConfigurationResult<bool> GetBool(string key, IConfigurationTelemetry tel
128128
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator)
129129
=> GetDictionary(key, telemetry, validator, allowOptionalMappings: false, separator: ':');
130130

131-
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
131+
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator, bool recordValue = true)
132132
{
133133
if (TryGetValue(key, out var objValue) && objValue is not null)
134134
{
135135
if (objValue is not IDictionary<string, string> value)
136136
{
137-
telemetry.Record(key, objValue.ToString(), recordValue: true, Origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource);
137+
telemetry.Record(key, objValue.ToString(), recordValue, Origin, TelemetryErrorCode.UnexpectedTypeInConfigurationSource);
138138
return ConfigurationResult<IDictionary<string, string>>.ParseFailure();
139139
}
140140

@@ -157,11 +157,11 @@ public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key
157157

158158
if (validator is null || validator(value))
159159
{
160-
telemetry.Record(key, dictAsString, recordValue: true, Origin);
160+
telemetry.Record(key, dictAsString, recordValue, Origin);
161161
return ConfigurationResult<IDictionary<string, string>>.Valid(value, dictAsString);
162162
}
163163

164-
telemetry.Record(key, dictAsString, recordValue: true, Origin, TelemetryErrorCode.FailedValidation);
164+
telemetry.Record(key, dictAsString, recordValue, Origin, TelemetryErrorCode.FailedValidation);
165165
return ConfigurationResult<IDictionary<string, string>>.Invalid(value);
166166
}
167167

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/IConfigurationSource.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,9 @@ ConfigurationResult<string> GetString(
9393
/// a successfully extracted value to determine if it should be accepted</param>
9494
/// <param name="allowOptionalMappings">Determines whether to create dictionary entries when the input has no value mapping</param>
9595
/// <param name="separator">Sets the character that separates keys and values in the input</param>
96+
/// <param name="recordValue">If <c>true</c> the value should be recorded in telemetry. If not, the source value should be redacted</param>
9697
/// <returns>The value of the setting, or <c>null</c> if not found.</returns>
97-
ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator);
98+
ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator, bool recordValue = true);
9899

99100
/// <summary>
100101
/// Gets the <see cref="IDictionary{TKey, TValue}"/> value of

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/JsonConfigurationSource.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ public ConfigurationResult<T> GetAs<T>(string key, IConfigurationTelemetry telem
266266
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator)
267267
=> GetDictionary(key, telemetry, validator, allowOptionalMappings: false, separator: ':');
268268

269-
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
269+
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator, bool recordValue = true)
270270
{
271271
var token = SelectToken(key);
272272
if (token == null)
@@ -301,7 +301,7 @@ public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key
301301
catch (Exception e)
302302
{
303303
Log.Error(e, "Unable to parse configuration value for {ConfigurationKey} as key-value pairs of strings.", key);
304-
telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.JsonStringError);
304+
telemetry.Record(key, tokenAsString, recordValue, Origin, TelemetryErrorCode.JsonStringError);
305305
return ConfigurationResult<IDictionary<string, string>>.ParseFailure();
306306
}
307307
}
@@ -311,19 +311,19 @@ public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key
311311
}
312312
catch (InvalidCastException)
313313
{
314-
telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.JsonStringError);
314+
telemetry.Record(key, tokenAsString, recordValue, Origin, TelemetryErrorCode.JsonStringError);
315315
throw; // Exising behaviour
316316
}
317317

318318
ConfigurationResult<IDictionary<string, string>> Validate(IDictionary<string, string> dictionary)
319319
{
320320
if (validator is null || validator(dictionary))
321321
{
322-
telemetry.Record(key, tokenAsString, recordValue: true, Origin);
322+
telemetry.Record(key, tokenAsString, recordValue, Origin);
323323
return ConfigurationResult<IDictionary<string, string>>.Valid(dictionary, tokenAsString);
324324
}
325325

326-
telemetry.Record(key, tokenAsString, recordValue: true, Origin, TelemetryErrorCode.FailedValidation);
326+
telemetry.Record(key, tokenAsString, recordValue, Origin, TelemetryErrorCode.FailedValidation);
327327
return ConfigurationResult<IDictionary<string, string>>.Invalid(dictionary);
328328
}
329329
}

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/NullConfigurationSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public ConfigurationResult<bool> GetBool(string key, IConfigurationTelemetry tel
3333
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator)
3434
=> ConfigurationResult<IDictionary<string, string>>.NotFound();
3535

36-
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
36+
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator, bool recordValue = true)
3737
=> ConfigurationResult<IDictionary<string, string>>.NotFound();
3838

3939
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, Func<string, IDictionary<string, string>> parser)

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/StringConfigurationSource.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key
233233
=> GetDictionary(key, telemetry, validator, allowOptionalMappings: false, separator: ':');
234234

235235
/// <inheritdoc />
236-
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator)
236+
public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key, IConfigurationTelemetry telemetry, Func<IDictionary<string, string>, bool>? validator, bool allowOptionalMappings, char separator, bool recordValue = true)
237237
{
238238
var value = GetString(key);
239239

@@ -249,11 +249,11 @@ public ConfigurationResult<IDictionary<string, string>> GetDictionary(string key
249249

250250
if (validator is null || validator(result))
251251
{
252-
telemetry.Record(key, value, recordValue: true, Origin);
252+
telemetry.Record(key, value, recordValue, Origin);
253253
return ConfigurationResult<IDictionary<string, string>>.Valid(result, value);
254254
}
255255

256-
telemetry.Record(key, value, recordValue: true, Origin, TelemetryErrorCode.FailedValidation);
256+
telemetry.Record(key, value, recordValue, Origin, TelemetryErrorCode.FailedValidation);
257257
return ConfigurationResult<IDictionary<string, string>>.Invalid(result);
258258
}
259259

tracer/src/Datadog.Trace/Configuration/ConfigurationSources/Telemetry/ConfigurationBuilder.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,9 @@ public ClassConfigurationResultWithKey<IDictionary<string, string>> AsDictionary
468468
public ClassConfigurationResultWithKey<IDictionary<string, string>> AsDictionaryResult(bool allowOptionalMappings, char separator)
469469
=> new(Telemetry, Key, recordValue: true, configurationResult: GetDictionaryResult(allowOptionalMappings, separator));
470470

471+
public ClassConfigurationResultWithKey<IDictionary<string, string>> AsRedactedDictionaryResult(char separator)
472+
=> new(Telemetry, Key, recordValue: false, configurationResult: GetDictionaryResult(allowOptionalMappings: false, separator, recordValue: false));
473+
471474
public ClassConfigurationResultWithKey<IDictionary<string, string>> AsDictionaryResult(Func<string, IDictionary<string, string>> parser)
472475
=> new(Telemetry, Key, recordValue: true, configurationResult: GetDictionaryResult(parser));
473476

@@ -514,11 +517,11 @@ private ConfigurationResult<T> GetAs<T>(Func<T, bool>? validator, Func<string, P
514517
return GetResultWithFallback(key => source.GetAs(key, telemetry, converter, validator, recordValue: true));
515518
}
516519

517-
private ConfigurationResult<IDictionary<string, string>> GetDictionaryResult(bool allowOptionalMappings, char separator)
520+
private ConfigurationResult<IDictionary<string, string>> GetDictionaryResult(bool allowOptionalMappings, char separator, bool recordValue = true)
518521
{
519522
var source = Source;
520523
var telemetry = Telemetry;
521-
return GetResultWithFallback(key => source.GetDictionary(key, telemetry, validator: null, allowOptionalMappings, separator));
524+
return GetResultWithFallback(key => source.GetDictionary(key, telemetry, validator: null, allowOptionalMappings, separator, recordValue));
522525
}
523526

524527
private ConfigurationResult<IDictionary<string, string>> GetDictionaryResult(Func<string, IDictionary<string, string>> parser)

tracer/src/Datadog.Trace/Configuration/ExporterSettings.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -587,23 +587,23 @@ public Raw(IConfigurationSource source, IConfigurationTelemetry telemetry)
587587
.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpTimeoutMs)
588588
.AsInt32(10_000, value => value > 0)
589589
.Value;
590-
OtlpHeaders = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpHeaders).AsString()?.Trim();
590+
OtlpHeaders = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpHeaders).AsRedactedString()?.Trim();
591591

592592
OtlpMetricsProtocol = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpMetricsProtocol).AsString()?.Trim();
593593
OtlpMetricsEndpoint = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpMetricsEndpoint).AsString()?.Trim();
594594
OtlpMetricsTimeoutMs = config
595595
.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpMetricsTimeoutMs)
596596
.AsInt32(OtlpTimeoutMs, value => value > 0)
597597
.Value;
598-
OtlpMetricsHeaders = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpMetricsHeaders).AsString()?.Trim();
598+
OtlpMetricsHeaders = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpMetricsHeaders).AsRedactedString()?.Trim();
599599

600600
OtlpTracesProtocol = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpTracesProtocol).AsString()?.Trim();
601601
OtlpTracesEndpoint = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpTracesEndpoint).AsString()?.Trim();
602602
OtlpTracesTimeoutMs = config
603603
.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpTracesTimeoutMs)
604604
.AsInt32(OtlpTimeoutMs, value => value > 0)
605605
.Value;
606-
OtlpTracesHeaders = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpTracesHeaders).AsString()?.Trim();
606+
OtlpTracesHeaders = config.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpTracesHeaders).AsRedactedString()?.Trim();
607607
}
608608

609609
/// <summary>

tracer/src/Datadog.Trace/Configuration/TracerSettings.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ not null when string.Equals(x, "http/json", StringComparison.OrdinalIgnoreCase)
280280

281281
OtlpMetricsHeaders = config
282282
.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpMetricsHeaders)
283-
.AsDictionaryResult(separator: '=')
283+
.AsRedactedDictionaryResult(separator: '=')
284284
.WithDefault(new DefaultResult<IDictionary<string, string>>(new Dictionary<string, string>(), "[]"))
285285
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key))
286286
.ToDictionary(kvp => kvp.Key.Trim(), kvp => kvp.Value?.Trim() ?? string.Empty);
@@ -330,7 +330,7 @@ not null when string.Equals(x, "http/protobuf", StringComparison.OrdinalIgnoreCa
330330

331331
OtlpLogsHeaders = config
332332
.WithKeys(ConfigurationKeys.OpenTelemetry.ExporterOtlpLogsHeaders)
333-
.AsDictionaryResult(separator: '=')
333+
.AsRedactedDictionaryResult(separator: '=')
334334
.WithDefault(new DefaultResult<IDictionary<string, string>>(new Dictionary<string, string>(), "[]"))
335335
.Where(kvp => !string.IsNullOrWhiteSpace(kvp.Key))
336336
.ToDictionary(kvp => kvp.Key.Trim(), kvp => kvp.Value?.Trim() ?? string.Empty);

0 commit comments

Comments
 (0)