Skip to content

Commit 1cfa8ea

Browse files
authored
Strengthen Config v2 conversion boundaries (#2700)
1 parent 9be8a28 commit 1cfa8ea

14 files changed

Lines changed: 996 additions & 149 deletions

File tree

devdocs/config/version-2.0/examples/default-configuration.yaml

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -600,28 +600,12 @@ extensions:
600600
source_labels:
601601
service_name: ""
602602
service_namespace: ""
603-
# DNS enricher runtime configuration.
604-
dns:
605-
enabled: true
606603
# Service identity enrichment policy from configured enrichers.
607604
service_name:
608605
# Shared cache for peer/host service-name lookups.
609606
cache:
610607
size: 1024
611608
ttl: 5m0s
612-
# Rule-based service identity mapping.
613-
# Rule order is the precedence model: earlier rules win when they set the same target field.
614-
rules:
615-
- id: k8s-default
616-
from: kubernetes
617-
description: Default Kubernetes label mapping into canonical service identity.
618-
map:
619-
service.name:
620-
- app.kubernetes.io/name
621-
service.namespace:
622-
- app.kubernetes.io/part-of
623-
service.version:
624-
- app.kubernetes.io/version
625609
# Fallback names when peer/host resolution fails.
626610
unresolved_hosts:
627611
names:
@@ -630,35 +614,6 @@ extensions:
630614
incoming: incoming
631615
# Attribute enrichment/selection controls.
632616
attributes:
633-
# Rule order is the precedence model: earlier rules win when they set the same target attribute.
634-
rules:
635-
- id: k8s-default-attributes
636-
from: kubernetes
637-
description: Default Kubernetes-derived attribute enrichment and selection.
638-
add:
639-
# Explicit attribute mapping policy for Kubernetes metadata.
640-
map:
641-
k8s.namespace.name:
642-
- kubernetes.namespace
643-
k8s.pod.name:
644-
- kubernetes.pod.name
645-
k8s.deployment.name:
646-
- kubernetes.workload.deployment
647-
k8s.node.name:
648-
- kubernetes.node.name
649-
- id: dns-default-attributes
650-
from: dns
651-
description: Default DNS-based host and instance identity enrichment.
652-
add:
653-
# Explicit attribute mapping policy for DNS metadata.
654-
map:
655-
host.name:
656-
- dns.host_name
657-
- dns.ptr_name
658-
service.instance.id:
659-
- dns.host_name
660-
server.address:
661-
- dns.resolved_ip
662617

663618
# correlation propagates OBI trace context into external streams.
664619
# Standalone-mode only: not valid in Collector receiver deployments.

internal/config/convert/export.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -715,12 +715,12 @@ func daemon(cfg *obi.Config) *schema.Daemon {
715715
}
716716

717717
func logLevel(level obi.LogLevel) otelconfx.SeverityNumber {
718-
switch level {
719-
case obi.LogLevelDebug:
718+
switch strings.ToUpper(string(level)) {
719+
case string(obi.LogLevelDebug):
720720
return otelconfx.SeverityNumberDebug
721-
case obi.LogLevelWarn:
721+
case string(obi.LogLevelWarn):
722722
return otelconfx.SeverityNumberWarn
723-
case obi.LogLevelError:
723+
case string(obi.LogLevelError):
724724
return otelconfx.SeverityNumberError
725725
default:
726726
return otelconfx.SeverityNumberInfo

internal/config/convert/export_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ func TestRuntimeToV2DocumentParsesAsStandaloneV2(t *testing.T) {
865865
require.NotContains(t, string(data), "rules: null")
866866
require.NotContains(t, string(data), "telemetry: null")
867867
require.NotContains(t, string(data), "refine: {}")
868+
require.NotContains(t, string(data), "additionalproperties")
868869

869870
parsedDoc, parsedExt, err := schema.ParseStandaloneYAML(data)
870871
require.NoError(t, err)

internal/config/convert/import.go

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,24 @@ func V2ToRuntime(src *schema.Extension) (*obi.Config, error) {
3838
if err := schema.ValidateStandalone(src); err != nil {
3939
return nil, err
4040
}
41+
if err := validateV2MatchOrder(src.Capture.Policy.MatchOrder, src.Capture.Rules); err != nil {
42+
return nil, err
43+
}
4144
if err := validateV2RulePatterns(src.Capture.Rules); err != nil {
4245
return nil, err
4346
}
47+
if err := validateV2RuleSelectorFamilies(src.Capture.Rules); err != nil {
48+
return nil, err
49+
}
4450
if err := validateV2HTTPFilters(src.Capture.Instrumentation.HTTP.Filters); err != nil {
4551
return nil, err
4652
}
4753
if err := validateV2HTTPPayloadExtraction(src.Capture.Instrumentation.HTTP.PayloadExtraction); err != nil {
4854
return nil, err
4955
}
56+
if err := validateUnsupportedV2Fields(src); err != nil {
57+
return nil, err
58+
}
5059

5160
cfg := runtimeConfigDefaults()
5261
applyV2Capture(&cfg, src)
@@ -57,6 +66,57 @@ func V2ToRuntime(src *schema.Extension) (*obi.Config, error) {
5766
return &cfg, nil
5867
}
5968

69+
func validateUnsupportedV2Fields(src *schema.Extension) error {
70+
runtimeFilters := []struct {
71+
path string
72+
filters schema.AttributeFilters
73+
}{
74+
{path: "capture.runtimes.go.filter", filters: src.Capture.Runtimes.Go.Filter},
75+
{path: "capture.runtimes.nodejs.filter", filters: src.Capture.Runtimes.NodeJS.Filter},
76+
{path: "capture.runtimes.java.filter", filters: src.Capture.Runtimes.Java.Filter},
77+
}
78+
for _, runtimeFilter := range runtimeFilters {
79+
if len(runtimeFilter.filters) != 0 {
80+
return fmt.Errorf("%s is not supported", runtimeFilter.path)
81+
}
82+
}
83+
84+
if src.Enrich != nil {
85+
for _, enrichmentProperties := range []struct {
86+
path string
87+
properties map[string]any
88+
}{
89+
{path: "enrich", properties: src.Enrich.AdditionalProperties},
90+
{path: "enrich.enrichers", properties: src.Enrich.Enrichers.AdditionalProperties},
91+
{path: "enrich.enrichers.kubernetes", properties: src.Enrich.Enrichers.Kubernetes.AdditionalProperties},
92+
{path: "enrich.service_name", properties: src.Enrich.ServiceName.AdditionalProperties},
93+
{path: "enrich.attributes", properties: src.Enrich.Attributes.AdditionalProperties},
94+
} {
95+
if err := rejectUnsupportedProperties(enrichmentProperties.path, enrichmentProperties.properties); err != nil {
96+
return err
97+
}
98+
}
99+
}
100+
101+
if src.Correlation != nil && len(src.Correlation.LogTraceAnnotation.Filter) != 0 {
102+
return errors.New("correlation.log_trace_annotation.filter is not supported")
103+
}
104+
return nil
105+
}
106+
107+
func rejectUnsupportedProperties(path string, properties map[string]any) error {
108+
if len(properties) == 0 {
109+
return nil
110+
}
111+
112+
keys := make([]string, 0, len(properties))
113+
for key := range properties {
114+
keys = append(keys, key)
115+
}
116+
slices.Sort(keys)
117+
return fmt.Errorf("%s.%s is not supported", path, keys[0])
118+
}
119+
60120
func runtimeConfigDefaults() obi.Config {
61121
cfg := obi.DefaultConfig
62122
if cfg.Routes != nil {
@@ -72,7 +132,7 @@ func runtimeConfigDefaults() obi.Config {
72132

73133
func applyV2Capture(cfg *obi.Config, src *schema.Extension) {
74134
applyV2Policy(cfg, src.Capture.Policy, completePolicy(src.Capture.Policy))
75-
applyV2Rules(cfg, src.Capture.Rules)
135+
applyV2Rules(cfg, src.Capture.Rules, src.Capture.Policy.DefaultAction)
76136
applyV2Limits(cfg, src.Capture.Limits, completeLimits(src.Capture.Limits))
77137
applyV2Safety(cfg, src.Capture.Safety, !zeroValue(src.Capture.Safety))
78138
applyV2Channels(cfg, src.Capture.Channels, completeChannels(src.Capture.Channels))
@@ -112,12 +172,30 @@ type runtimeDiscoveryRules struct {
112172
defaultOTLPGRPCPort int
113173
}
114174

115-
func applyV2Rules(cfg *obi.Config, rules []schema.Rule) {
175+
func applyV2Rules(cfg *obi.Config, rules []schema.Rule, defaultAction schema.CaptureAction) {
176+
includeByDefault := defaultAction != schema.CaptureActionExclude
116177
if rules == nil {
178+
if includeByDefault {
179+
cfg.Discovery.Instrument = services.GlobDefinitionCriteria{
180+
{Path: services.NewGlob("*")},
181+
}
182+
}
117183
return
118184
}
119185

120-
applyRuntimeDiscoveryRules(cfg, runtimeDiscoveryRulesFromV2(rules))
186+
converted := runtimeDiscoveryRulesFromV2(rules)
187+
if includeByDefault {
188+
if len(converted.includeRegex) > 0 || len(converted.excludeRegex) > 0 {
189+
converted.includeRegex = append(converted.includeRegex, services.RegexSelector{
190+
Path: services.NewRegexp(".*"),
191+
})
192+
} else {
193+
converted.includeGlobs = append(converted.includeGlobs, services.GlobAttributes{
194+
Path: services.NewGlob("*"),
195+
})
196+
}
197+
}
198+
applyRuntimeDiscoveryRules(cfg, converted)
121199
}
122200

123201
func runtimeDiscoveryRulesFromV2(rules []schema.Rule) runtimeDiscoveryRules {
@@ -195,6 +273,69 @@ func validateV2RulePatterns(rules []schema.Rule) error {
195273
return nil
196274
}
197275

276+
func validateV2RuleSelectorFamilies(rules []schema.Rule) error {
277+
selectorFamily := ""
278+
for i, rule := range rules {
279+
if rule.Match.Process.ExportsOTLP != nil || ruleMatchEmpty(rule.Match) {
280+
continue
281+
}
282+
283+
ruleSelectorFamily := "glob"
284+
if ruleUsesRegex(rule.Match) {
285+
if ruleUsesGlob(rule.Match) {
286+
return fmt.Errorf(
287+
"capture.rules[%d].match: mixing glob and regex selectors is not supported",
288+
i,
289+
)
290+
}
291+
ruleSelectorFamily = "regex"
292+
}
293+
294+
if selectorFamily != "" && selectorFamily != ruleSelectorFamily {
295+
return fmt.Errorf(
296+
"capture.rules[%d].match: mixing glob and regex selectors is not supported",
297+
i,
298+
)
299+
}
300+
selectorFamily = ruleSelectorFamily
301+
}
302+
return nil
303+
}
304+
305+
func validateV2MatchOrder(matchOrder schema.MatchOrder, rules []schema.Rule) error {
306+
if matchOrder == schema.MatchOrderLastMatchWins {
307+
return errors.New("capture.policy.match_order: last_match_wins is not supported")
308+
}
309+
310+
includeSeen := false
311+
for i, rule := range rules {
312+
if !ruleAffectsV2Selection(rule) {
313+
continue
314+
}
315+
316+
switch rule.Action {
317+
case schema.CaptureActionInclude:
318+
includeSeen = true
319+
case schema.CaptureActionExclude:
320+
if includeSeen {
321+
return fmt.Errorf(
322+
"capture.rules[%d]: exclude rules must precede include rules for first_match_wins",
323+
i,
324+
)
325+
}
326+
}
327+
}
328+
329+
return nil
330+
}
331+
332+
func ruleAffectsV2Selection(rule schema.Rule) bool {
333+
if rule.Match.Process.ExportsOTLP != nil || ruleMatchEmpty(rule.Match) {
334+
return false
335+
}
336+
return !ruleUsesRegex(rule.Match) || !ruleUsesGlob(rule.Match)
337+
}
338+
198339
func validateV2HTTPFilters(filters schema.SignalFilters) error {
199340
if len(filters.Traces) == 0 || len(filters.Metrics) == 0 {
200341
return nil

internal/config/convert/import_document.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ func DocumentToRuntime(src *schema.Document) (*obi.Config, error) {
2525
if src == nil {
2626
return nil, errors.New("missing OBI document")
2727
}
28+
if fields := src.OpenTelemetryExtensionFields(); len(fields) != 0 {
29+
return nil, fmt.Errorf(
30+
"OpenTelemetry extension fields are not supported: %s",
31+
strings.Join(fields, ", "),
32+
)
33+
}
2834

2935
cfg, err := V2ToRuntime(src.Extensions.OBI)
3036
if err != nil {

internal/config/convert/import_document_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,28 @@ extensions:
175175
require.Contains(t, err.Error(), "verbose")
176176
}
177177

178+
func TestDocumentToRuntimeRejectsOpenTelemetryExtensionFields(t *testing.T) {
179+
t.Parallel()
180+
181+
doc, _, err := schema.ParseStandaloneYAML([]byte(`
182+
file_format: "1.0"
183+
tracer_provider:
184+
sampler:
185+
vendor_sampler: {}
186+
extensions:
187+
obi:
188+
version: "2.0"
189+
`))
190+
require.NoError(t, err)
191+
192+
_, err = DocumentToRuntime(doc)
193+
require.ErrorContains(
194+
t,
195+
err,
196+
"OpenTelemetry extension fields are not supported: tracer_provider.sampler.vendor_sampler",
197+
)
198+
}
199+
178200
func TestDocumentToRuntimeSkipsUnsupportedMetricReaderShapes(t *testing.T) {
179201
t.Parallel()
180202

0 commit comments

Comments
 (0)