Skip to content

Commit fa2afe1

Browse files
bm1549claude
andcommitted
[Configuration] Drive telemetry redaction from the sensitive registry flag
Capture the `sensitive` flag from supported-configurations.yaml through the source generator and emit a generated ConfigurationKeys.SensitiveKeys set (keys plus their aliases). ConfigurationTelemetry.Record gates recordValue on this set so a sensitive config's value is redacted regardless of the call site. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5909690 commit fa2afe1

9 files changed

Lines changed: 351 additions & 12 deletions

File tree

tracer/src/Datadog.Trace.SourceGenerators/Configuration/ConfigurationKeysGenerator.cs

Lines changed: 113 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,69 @@ private static void Execute(SourceProductionContext context, Result<Configuratio
8383
: $"{GeneratedClassName}.{productGroup.Key}.g.cs";
8484
context.AddSource(fileName, SourceText.From(productSource, Encoding.UTF8));
8585
}
86+
87+
// Emit the set of sensitive keys (plus their aliases) once on the main class.
88+
// Skip when there are no configurations so empty input produces no output.
89+
if (configData.Configurations.Count > 0)
90+
{
91+
var sensitiveSource = GenerateSensitiveKeysPartialClass(configData.Configurations);
92+
context.AddSource($"{GeneratedClassName}.Sensitive.g.cs", SourceText.From(sensitiveSource, Encoding.UTF8));
93+
}
94+
}
95+
96+
private static string GenerateSensitiveKeysPartialClass(Dictionary<string, ConfigEntry> configurations)
97+
{
98+
// Collect the key (and aliases) of every entry flagged sensitive, so a read via
99+
// either the primary key or an alias is gated. Sorted for deterministic output.
100+
var sensitiveKeys = new SortedSet<string>(StringComparer.Ordinal);
101+
foreach (var entry in configurations.Values)
102+
{
103+
if (!entry.Sensitive)
104+
{
105+
continue;
106+
}
107+
108+
sensitiveKeys.Add(entry.Key);
109+
if (entry.Aliases is not null)
110+
{
111+
foreach (var alias in entry.Aliases)
112+
{
113+
if (!string.IsNullOrEmpty(alias))
114+
{
115+
sensitiveKeys.Add(alias);
116+
}
117+
}
118+
}
119+
}
120+
121+
var sb = new StringBuilder();
122+
sb.Append(Constants.FileHeader);
123+
sb.AppendLine("using System.Collections.Generic;");
124+
sb.AppendLine();
125+
sb.Append("namespace ").Append(Namespace).AppendLine(";");
126+
sb.AppendLine();
127+
sb.AppendLine("/// <summary>");
128+
sb.AppendLine("/// String constants for standard Datadog configuration keys.");
129+
sb.AppendLine("/// Do not edit this file directly as it's auto-generated from supported-configurations.yaml");
130+
sb.AppendLine("/// For more info, see docs/development/Configuration/AddingConfigurationKeys.md");
131+
sb.AppendLine("/// </summary>");
132+
sb.AppendLine($"internal static partial class {GeneratedClassName}");
133+
sb.AppendLine("{");
134+
sb.AppendLine(" /// <summary>");
135+
sb.AppendLine(" /// The set of configuration keys (including aliases) marked <c>sensitive</c> in");
136+
sb.AppendLine(" /// supported-configurations.yaml. Telemetry redacts the value of any key in this set.");
137+
sb.AppendLine(" /// </summary>");
138+
sb.AppendLine(" public static readonly HashSet<string> SensitiveKeys = new()");
139+
sb.AppendLine(" {");
140+
foreach (var key in sensitiveKeys)
141+
{
142+
sb.AppendLine($" \"{key}\",");
143+
}
144+
145+
sb.AppendLine(" };");
146+
sb.AppendLine("}");
147+
148+
return sb.ToString();
86149
}
87150

88151
private static Result<YamlReader.ParsedConfigurationData> ParseYaml(string? content)
@@ -164,7 +227,9 @@ private static Result<ConfigurationData> ExtractConfigurationData(Result<YamlRea
164227
entry.Documentation ?? string.Empty,
165228
entry.Product ?? string.Empty,
166229
deprecationMessage,
167-
entry.ConstName);
230+
entry.ConstName,
231+
entry.Sensitive,
232+
entry.Aliases);
168233
}
169234

170235
return new Result<ConfigurationData>(new ConfigurationData(configurations), new EquatableArray<DiagnosticInfo>(diagnostics.ToArray()));
@@ -365,13 +430,15 @@ private static DiagnosticInfo CreateDiagnosticInfo(string id, string title, stri
365430

366431
private readonly struct ConfigEntry : IEquatable<ConfigEntry>
367432
{
368-
public ConfigEntry(string key, string documentation, string product, string? deprecationMessage = null, string? constName = null)
433+
public ConfigEntry(string key, string documentation, string product, string? deprecationMessage = null, string? constName = null, bool sensitive = false, string[]? aliases = null)
369434
{
370435
Key = key;
371436
Documentation = documentation;
372437
Product = product;
373438
DeprecationMessage = deprecationMessage;
374439
ConstName = constName;
440+
Sensitive = sensitive;
441+
Aliases = aliases;
375442
}
376443

377444
public string Key { get; }
@@ -384,11 +451,53 @@ public ConfigEntry(string key, string documentation, string product, string? dep
384451

385452
public string? ConstName { get; }
386453

387-
public bool Equals(ConfigEntry other) => Key == other.Key && Documentation == other.Documentation && Product == other.Product && DeprecationMessage == other.DeprecationMessage && ConstName == other.ConstName;
454+
public bool Sensitive { get; }
455+
456+
public string[]? Aliases { get; }
457+
458+
public bool Equals(ConfigEntry other)
459+
{
460+
if (Key != other.Key || Documentation != other.Documentation || Product != other.Product || DeprecationMessage != other.DeprecationMessage || ConstName != other.ConstName || Sensitive != other.Sensitive)
461+
{
462+
return false;
463+
}
464+
465+
if (ReferenceEquals(Aliases, other.Aliases))
466+
{
467+
return true;
468+
}
469+
470+
if (Aliases is null || other.Aliases is null || Aliases.Length != other.Aliases.Length)
471+
{
472+
return Aliases is null && other.Aliases is null;
473+
}
474+
475+
for (var i = 0; i < Aliases.Length; i++)
476+
{
477+
if (Aliases[i] != other.Aliases[i])
478+
{
479+
return false;
480+
}
481+
}
482+
483+
return true;
484+
}
388485

389486
public override bool Equals(object? obj) => obj is ConfigEntry other && Equals(other);
390487

391-
public override int GetHashCode() => HashCode.Combine(Key, Documentation, Product, DeprecationMessage, ConstName);
488+
public override int GetHashCode()
489+
{
490+
var hash = HashCode.Combine(Key, Documentation, Product, DeprecationMessage, ConstName, Sensitive);
491+
if (Aliases is not null)
492+
{
493+
foreach (var alias in Aliases)
494+
{
495+
hash = HashCode.Combine(hash, alias);
496+
}
497+
}
498+
499+
return hash;
500+
}
392501
}
393502

394503
private sealed class ConfigurationData : IEquatable<ConfigurationData>

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static ParsedConfigurationData ParseSupportedConfigurations(string yamlCo
2929
string? currentDocumentation = null;
3030
string? currentConstName = null;
3131
string[]? currentScope = null;
32+
var currentSensitive = false;
3233
var currentAliases = new List<string>();
3334
var inDocumentation = false;
3435
var inAliases = false;
@@ -63,7 +64,7 @@ public static ParsedConfigurationData ParseSupportedConfigurations(string yamlCo
6364
if (currentConfigKey != null)
6465
{
6566
var doc = inDocumentation ? documentationBuilder.ToString().TrimEnd() : currentDocumentation;
66-
configurations[currentConfigKey] = new ConfigurationEntry(currentConfigKey, currentProduct ?? string.Empty, doc, currentConstName, currentScope, currentAliases.Count > 0 ? currentAliases.ToArray() : null);
67+
configurations[currentConfigKey] = new ConfigurationEntry(currentConfigKey, currentProduct ?? string.Empty, doc, currentConstName, currentScope, currentAliases.Count > 0 ? currentAliases.ToArray() : null, currentSensitive);
6768
}
6869

6970
inSupportedConfigurations = false;
@@ -116,14 +117,15 @@ public static ParsedConfigurationData ParseSupportedConfigurations(string yamlCo
116117
if (currentConfigKey != null)
117118
{
118119
var doc = inDocumentation ? documentationBuilder.ToString().TrimEnd() : currentDocumentation;
119-
configurations[currentConfigKey] = new ConfigurationEntry(currentConfigKey, currentProduct ?? string.Empty, doc, currentConstName, currentScope, currentAliases.Count > 0 ? currentAliases.ToArray() : null);
120+
configurations[currentConfigKey] = new ConfigurationEntry(currentConfigKey, currentProduct ?? string.Empty, doc, currentConstName, currentScope, currentAliases.Count > 0 ? currentAliases.ToArray() : null, currentSensitive);
120121
}
121122

122123
currentConfigKey = potentialKey;
123124
currentProduct = null;
124125
currentDocumentation = null;
125126
currentConstName = null;
126127
currentScope = null;
128+
currentSensitive = false;
127129
currentAliases.Clear();
128130
inDocumentation = false;
129131
inAliases = false;
@@ -255,6 +257,9 @@ public static ParsedConfigurationData ParseSupportedConfigurations(string yamlCo
255257

256258
currentScope = scopeValues.ToArray();
257259
break;
260+
case "sensitive":
261+
currentSensitive = propValue.Equals("true", StringComparison.OrdinalIgnoreCase);
262+
break;
258263
case "aliases":
259264
inAliases = true;
260265
break;
@@ -301,7 +306,7 @@ public static ParsedConfigurationData ParseSupportedConfigurations(string yamlCo
301306
if (currentConfigKey != null)
302307
{
303308
var doc = inDocumentation ? documentationBuilder.ToString().TrimEnd() : currentDocumentation;
304-
configurations[currentConfigKey] = new ConfigurationEntry(currentConfigKey, currentProduct ?? string.Empty, doc, currentConstName, currentScope, currentAliases.Count > 0 ? currentAliases.ToArray() : null);
309+
configurations[currentConfigKey] = new ConfigurationEntry(currentConfigKey, currentProduct ?? string.Empty, doc, currentConstName, currentScope, currentAliases.Count > 0 ? currentAliases.ToArray() : null, currentSensitive);
305310
}
306311

307312
return new ParsedConfigurationData(configurations, deprecations);
@@ -369,14 +374,15 @@ public bool MoveNext()
369374
/// </summary>
370375
internal readonly struct ConfigurationEntry : IEquatable<ConfigurationEntry>
371376
{
372-
public ConfigurationEntry(string key, string? product, string? documentation, string? constName, string[]? scope, string[]? aliases = null)
377+
public ConfigurationEntry(string key, string? product, string? documentation, string? constName, string[]? scope, string[]? aliases = null, bool sensitive = false)
373378
{
374379
Key = key;
375380
Product = product;
376381
Documentation = documentation;
377382
ConstName = constName;
378383
Scope = scope is null ? default : new EquatableArray<string>(scope);
379384
Aliases = aliases is null ? default : new EquatableArray<string>(aliases);
385+
Sensitive = sensitive;
380386
}
381387

382388
public string Key { get; }
@@ -391,17 +397,20 @@ public ConfigurationEntry(string key, string? product, string? documentation, st
391397

392398
public EquatableArray<string> Aliases { get; }
393399

400+
public bool Sensitive { get; }
401+
394402
public bool Equals(ConfigurationEntry other)
395403
=> Key == other.Key
396404
&& Product == other.Product
397405
&& Documentation == other.Documentation
398406
&& ConstName == other.ConstName
399407
&& Scope == other.Scope
400-
&& Aliases == other.Aliases;
408+
&& Aliases == other.Aliases
409+
&& Sensitive == other.Sensitive;
401410

402411
public override bool Equals(object? obj) => obj is ConfigurationEntry other && Equals(other);
403412

404-
public override int GetHashCode() => HashCode.Combine(Key, Product, Documentation, ConstName, Scope, Aliases);
413+
public override int GetHashCode() => HashCode.Combine(Key, Product, Documentation, ConstName, Scope, Aliases, Sensitive);
405414
}
406415

407416
/// <summary>

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@ public enum ConfigurationTelemetryEntryType
2626
}
2727

2828
public void Record(string key, string? value, bool recordValue, ConfigurationOrigins origin, TelemetryErrorCode? error = null)
29-
=> _entries.Enqueue(
29+
{
30+
// The sensitive registry flag is authoritative: never record the value of a key
31+
// marked sensitive in supported-configurations.yaml, regardless of the call site.
32+
recordValue = recordValue && !ConfigurationKeys.SensitiveKeys.Contains(key);
33+
_entries.Enqueue(
3034
recordValue
3135
? ConfigurationTelemetryEntry.String(key, value, origin, error)
3236
: ConfigurationTelemetryEntry.Redacted(key, origin, error));
37+
}
3338

3439
public void Record(string key, bool value, ConfigurationOrigins origin, TelemetryErrorCode? error = null)
3540
=> _entries.Enqueue(ConfigurationTelemetryEntry.Bool(key, value, origin, error));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// <copyright company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
// <auto-generated/>
6+
7+
#nullable enable
8+
9+
using System.Collections.Generic;
10+
11+
namespace Datadog.Trace.Configuration;
12+
13+
/// <summary>
14+
/// String constants for standard Datadog configuration keys.
15+
/// Do not edit this file directly as it's auto-generated from supported-configurations.yaml
16+
/// For more info, see docs/development/Configuration/AddingConfigurationKeys.md
17+
/// </summary>
18+
internal static partial class ConfigurationKeys
19+
{
20+
/// <summary>
21+
/// The set of configuration keys (including aliases) marked <c>sensitive</c> in
22+
/// supported-configurations.yaml. Telemetry redacts the value of any key in this set.
23+
/// </summary>
24+
public static readonly HashSet<string> SensitiveKeys = new()
25+
{
26+
"OTEL_EXPORTER_OTLP_HEADERS",
27+
"OTEL_EXPORTER_OTLP_LOGS_HEADERS",
28+
"OTEL_EXPORTER_OTLP_METRICS_HEADERS",
29+
"OTEL_EXPORTER_OTLP_TRACES_HEADERS",
30+
};
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// <copyright company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
// <auto-generated/>
6+
7+
#nullable enable
8+
9+
using System.Collections.Generic;
10+
11+
namespace Datadog.Trace.Configuration;
12+
13+
/// <summary>
14+
/// String constants for standard Datadog configuration keys.
15+
/// Do not edit this file directly as it's auto-generated from supported-configurations.yaml
16+
/// For more info, see docs/development/Configuration/AddingConfigurationKeys.md
17+
/// </summary>
18+
internal static partial class ConfigurationKeys
19+
{
20+
/// <summary>
21+
/// The set of configuration keys (including aliases) marked <c>sensitive</c> in
22+
/// supported-configurations.yaml. Telemetry redacts the value of any key in this set.
23+
/// </summary>
24+
public static readonly HashSet<string> SensitiveKeys = new()
25+
{
26+
"OTEL_EXPORTER_OTLP_HEADERS",
27+
"OTEL_EXPORTER_OTLP_LOGS_HEADERS",
28+
"OTEL_EXPORTER_OTLP_METRICS_HEADERS",
29+
"OTEL_EXPORTER_OTLP_TRACES_HEADERS",
30+
};
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// <copyright company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
// <auto-generated/>
6+
7+
#nullable enable
8+
9+
using System.Collections.Generic;
10+
11+
namespace Datadog.Trace.Configuration;
12+
13+
/// <summary>
14+
/// String constants for standard Datadog configuration keys.
15+
/// Do not edit this file directly as it's auto-generated from supported-configurations.yaml
16+
/// For more info, see docs/development/Configuration/AddingConfigurationKeys.md
17+
/// </summary>
18+
internal static partial class ConfigurationKeys
19+
{
20+
/// <summary>
21+
/// The set of configuration keys (including aliases) marked <c>sensitive</c> in
22+
/// supported-configurations.yaml. Telemetry redacts the value of any key in this set.
23+
/// </summary>
24+
public static readonly HashSet<string> SensitiveKeys = new()
25+
{
26+
"OTEL_EXPORTER_OTLP_HEADERS",
27+
"OTEL_EXPORTER_OTLP_LOGS_HEADERS",
28+
"OTEL_EXPORTER_OTLP_METRICS_HEADERS",
29+
"OTEL_EXPORTER_OTLP_TRACES_HEADERS",
30+
};
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// <copyright company="Datadog">
2+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
4+
// </copyright>
5+
// <auto-generated/>
6+
7+
#nullable enable
8+
9+
using System.Collections.Generic;
10+
11+
namespace Datadog.Trace.Configuration;
12+
13+
/// <summary>
14+
/// String constants for standard Datadog configuration keys.
15+
/// Do not edit this file directly as it's auto-generated from supported-configurations.yaml
16+
/// For more info, see docs/development/Configuration/AddingConfigurationKeys.md
17+
/// </summary>
18+
internal static partial class ConfigurationKeys
19+
{
20+
/// <summary>
21+
/// The set of configuration keys (including aliases) marked <c>sensitive</c> in
22+
/// supported-configurations.yaml. Telemetry redacts the value of any key in this set.
23+
/// </summary>
24+
public static readonly HashSet<string> SensitiveKeys = new()
25+
{
26+
"OTEL_EXPORTER_OTLP_HEADERS",
27+
"OTEL_EXPORTER_OTLP_LOGS_HEADERS",
28+
"OTEL_EXPORTER_OTLP_METRICS_HEADERS",
29+
"OTEL_EXPORTER_OTLP_TRACES_HEADERS",
30+
};
31+
}

0 commit comments

Comments
 (0)