From 5dfc5e8d383874ff7c7a613abd60e4fefa20c825 Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 16 Jun 2024 21:35:53 +0100 Subject: [PATCH 01/18] bug fix --- sdk/trace/id_generator.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/sdk/trace/id_generator.go b/sdk/trace/id_generator.go index f9633d8c576..925bcf99305 100644 --- a/sdk/trace/id_generator.go +++ b/sdk/trace/id_generator.go @@ -41,7 +41,12 @@ func (gen *randomIDGenerator) NewSpanID(ctx context.Context, traceID trace.Trace gen.Lock() defer gen.Unlock() sid := trace.SpanID{} - _, _ = gen.randSource.Read(sid[:]) + for { + _, _ = gen.randSource.Read(sid[:]) + if sid.IsValid() { + break + } + } return sid } @@ -51,9 +56,19 @@ func (gen *randomIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace. gen.Lock() defer gen.Unlock() tid := trace.TraceID{} - _, _ = gen.randSource.Read(tid[:]) sid := trace.SpanID{} - _, _ = gen.randSource.Read(sid[:]) + for { + _, _ = gen.randSource.Read(tid[:]) + if tid.IsValid() { + break + } + } + for { + _, _ = gen.randSource.Read(sid[:]) + if sid.IsValid() { + break + } + } return tid, sid } From 1df95e643a5173ef7ab564bd3fd7a8f1e89e122f Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 16 Jun 2024 21:55:38 +0100 Subject: [PATCH 02/18] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 245f4cffa4b..03f313e8842 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed +- Fix IDGenerator may generate zero TraceId / SpanId. (#5514) - Log a warning to the OpenTelemetry internal logger when a `Record` in `go.opentelemetry.io/otel/sdk/log` drops an attribute due to a limit being reached. (#5376) - Identify the `Tracer` returned from the global `TracerProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426) - Identify the `Meter` returned from the global `MeterProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426) From ea40bd059ff1bf6bbad766ae7f0f949782020675 Mon Sep 17 00:00:00 2001 From: Fools <54661071+Charlie-lizhihan@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:13:09 +0100 Subject: [PATCH 03/18] Update CHANGELOG.md Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f313e8842..dc1d5e9b253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed -- Fix IDGenerator may generate zero TraceId / SpanId. (#5514) +- Retry trace and span ID generation if it generated an invalid one in `go.opentelemetry.io/otel/sdk/trace`. (#5514) - Log a warning to the OpenTelemetry internal logger when a `Record` in `go.opentelemetry.io/otel/sdk/log` drops an attribute due to a limit being reached. (#5376) - Identify the `Tracer` returned from the global `TracerProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426) - Identify the `Meter` returned from the global `MeterProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426) From 3b5fb78fdfe108ba7d5a18bec5ab48445a221d9b Mon Sep 17 00:00:00 2001 From: Fools <54661071+Charlie-lizhihan@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:29:24 +0100 Subject: [PATCH 04/18] Update sdk/trace/id_generator.go Co-authored-by: Damien Mathieu <42@dmathieu.com> --- sdk/trace/id_generator.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/trace/id_generator.go b/sdk/trace/id_generator.go index 925bcf99305..be899009a9c 100644 --- a/sdk/trace/id_generator.go +++ b/sdk/trace/id_generator.go @@ -44,10 +44,9 @@ func (gen *randomIDGenerator) NewSpanID(ctx context.Context, traceID trace.Trace for { _, _ = gen.randSource.Read(sid[:]) if sid.IsValid() { - break + return sid } } - return sid } // NewIDs returns a non-zero trace ID and a non-zero span ID from a From f1a0bf07f0521e01ae9ef7b810bbf527d5f56582 Mon Sep 17 00:00:00 2001 From: Fools Date: Tue, 18 Jun 2024 00:18:21 +0100 Subject: [PATCH 05/18] go idoms change --- sdk/trace/id_generator.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk/trace/id_generator.go b/sdk/trace/id_generator.go index 925bcf99305..7f887265dcd 100644 --- a/sdk/trace/id_generator.go +++ b/sdk/trace/id_generator.go @@ -66,10 +66,9 @@ func (gen *randomIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace. for { _, _ = gen.randSource.Read(sid[:]) if sid.IsValid() { - break + return tid, sid } } - return tid, sid } func defaultIDGenerator() IDGenerator { From 94d06af815b1ab59a85a5304abd9ed965554f768 Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Tue, 18 Jun 2024 15:03:37 +0200 Subject: [PATCH 06/18] Update sdk/trace/id_generator.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Pająk --- sdk/trace/id_generator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/trace/id_generator.go b/sdk/trace/id_generator.go index 6abe00ff9d8..be899009a9c 100644 --- a/sdk/trace/id_generator.go +++ b/sdk/trace/id_generator.go @@ -65,9 +65,10 @@ func (gen *randomIDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace. for { _, _ = gen.randSource.Read(sid[:]) if sid.IsValid() { - return tid, sid + break } } + return tid, sid } func defaultIDGenerator() IDGenerator { From ecef23c7900a273785fbcc2350e0deb154aa7228 Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Tue, 18 Jun 2024 15:03:42 +0200 Subject: [PATCH 07/18] Update sdk/trace/id_generator.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Pająk --- sdk/trace/id_generator.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/trace/id_generator.go b/sdk/trace/id_generator.go index be899009a9c..925bcf99305 100644 --- a/sdk/trace/id_generator.go +++ b/sdk/trace/id_generator.go @@ -44,9 +44,10 @@ func (gen *randomIDGenerator) NewSpanID(ctx context.Context, traceID trace.Trace for { _, _ = gen.randSource.Read(sid[:]) if sid.IsValid() { - return sid + break } } + return sid } // NewIDs returns a non-zero trace ID and a non-zero span ID from a From 41d321fcc1360b70be11c788ad8209ba1aac1495 Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 11 Aug 2024 16:58:46 +0100 Subject: [PATCH 08/18] bug fix 5623 --- CHANGELOG.md | 1 + .../internal/envconfig/envconfig.go | 36 +++++++++++--- .../internal/envconfig/envconfig_test.go | 47 +++++++++++++++++++ 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79dce02c17e..baff2c74366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed +- Fix parsing of OTLP exporter header environment variables, make it stop parsing keys and add key validation in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go`(#5704) - Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#5584) - Correct the `Tracer`, `Meter`, and `Logger` names used in `go.opentelemetry.io/otel/example/dice`. (#5612) - Correct the `Tracer` names used in `go.opentelemetry.io/otel/example/namedtracer`. (#5612) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go index 9513c0a57ca..cd78de0a4e0 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -163,15 +164,19 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.PathUnescape(n) - if err != nil { - global.Error(err, "escape header key", "key", n) + + trimmedName := strings.TrimSpace(n) + + // Validate the key + if !isValidHeaderKey(trimmedName) { + global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - trimmedName := strings.TrimSpace(name) - value, err := url.PathUnescape(v) + + // Only decode the value + value, err := url.QueryUnescape(v) if err != nil { - global.Error(err, "escape header value", "value", v) + global.Error(err, "unescape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) @@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go index 14543ea11eb..8f5e14ae73a 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go @@ -270,6 +270,53 @@ func TestEnvConfig(t *testing.T) { }, }, }, + { + name: "with percent-encoded headers", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "user%2Did=42,user%20name=alice%20smith" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "user%2Did": "42", + "user%20name": "alice smith", + }, + }, + }, + }, + { + name: "with invalid header key", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "valid-key=value,invalid key=value" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "valid-key": "value", + }, + }, + }, + }, { name: "with URL", reader: EnvOptionsReader{ From e7a107366a61fbdc79e3f7f4aef45f9c8ff79842 Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 11 Aug 2024 17:14:39 +0100 Subject: [PATCH 09/18] lint check and update the tmpl file --- .../internal/envconfig/envconfig.go | 36 +-- .../internal/envconfig/envconfig_test.go | 47 ---- .../otlptrace/otlpconfig/envconfig.go.tmpl | 245 +++++++++++------- 3 files changed, 153 insertions(+), 175 deletions(-) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go index cd78de0a4e0..9513c0a57ca 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go @@ -15,7 +15,6 @@ import ( "strconv" "strings" "time" - "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -164,19 +163,15 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - - trimmedName := strings.TrimSpace(n) - - // Validate the key - if !isValidHeaderKey(trimmedName) { - global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) + name, err := url.PathUnescape(n) + if err != nil { + global.Error(err, "escape header key", "key", n) continue } - - // Only decode the value - value, err := url.QueryUnescape(v) + trimmedName := strings.TrimSpace(name) + value, err := url.PathUnescape(v) if err != nil { - global.Error(err, "unescape header value", "value", v) + global.Error(err, "escape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) @@ -194,22 +189,3 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } - -func isValidHeaderKey(key string) bool { - if key == "" { - return false - } - for _, c := range key { - if !isTokenChar(c) { - return false - } - } - return true -} - -func isTokenChar(c rune) bool { - return c <= unicode.MaxASCII && (unicode.IsLetter(c) || - unicode.IsDigit(c) || - c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || - c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') -} diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go index 8f5e14ae73a..14543ea11eb 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go @@ -270,53 +270,6 @@ func TestEnvConfig(t *testing.T) { }, }, }, - { - name: "with percent-encoded headers", - reader: EnvOptionsReader{ - GetEnv: func(n string) string { - if n == "HELLO" { - return "user%2Did=42,user%20name=alice%20smith" - } - return "" - }, - }, - configs: []ConfigFn{ - WithHeaders("HELLO", func(v map[string]string) { - options = append(options, testOption{TestHeaders: v}) - }), - }, - expectedOptions: []testOption{ - { - TestHeaders: map[string]string{ - "user%2Did": "42", - "user%20name": "alice smith", - }, - }, - }, - }, - { - name: "with invalid header key", - reader: EnvOptionsReader{ - GetEnv: func(n string) string { - if n == "HELLO" { - return "valid-key=value,invalid key=value" - } - return "" - }, - }, - configs: []ConfigFn{ - WithHeaders("HELLO", func(v map[string]string) { - options = append(options, testOption{TestHeaders: v}) - }), - }, - expectedOptions: []testOption{ - { - TestHeaders: map[string]string{ - "valid-key": "value", - }, - }, - }, - }, { name: "with URL", reader: EnvOptionsReader{ diff --git a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl index f1fa3dc8450..9513c0a57ca 100644 --- a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl @@ -1,142 +1,191 @@ // Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl +// source: internal/shared/otlp/envconfig/envconfig.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package otlpconfig +package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig" import ( "crypto/tls" "crypto/x509" + "errors" + "fmt" "net/url" - "os" - "path" + "strconv" "strings" "time" - "{{ .envconfigImportPath }}" + "go.opentelemetry.io/otel/internal/global" ) -// DefaultEnvOptionsReader is the default environments reader. -var DefaultEnvOptionsReader = envconfig.EnvOptionsReader{ - GetEnv: os.Getenv, - ReadFile: os.ReadFile, - Namespace: "OTEL_EXPORTER_OTLP", +// ConfigFn is the generic function used to set a config. +type ConfigFn func(*EnvOptionsReader) + +// EnvOptionsReader reads the required environment variables. +type EnvOptionsReader struct { + GetEnv func(string) string + ReadFile func(string) ([]byte, error) + Namespace string } -// ApplyGRPCEnvConfigs applies the env configurations for gRPC. -func ApplyGRPCEnvConfigs(cfg Config) Config { - opts := getOptionsFromEnv() - for _, opt := range opts { - cfg = opt.ApplyGRPCOption(cfg) +// Apply runs every ConfigFn. +func (e *EnvOptionsReader) Apply(opts ...ConfigFn) { + for _, o := range opts { + o(e) } - return cfg } -// ApplyHTTPEnvConfigs applies the env configurations for HTTP. -func ApplyHTTPEnvConfigs(cfg Config) Config { - opts := getOptionsFromEnv() - for _, opt := range opts { - cfg = opt.ApplyHTTPOption(cfg) +// GetEnvValue gets an OTLP environment variable value of the specified key +// using the GetEnv function. +// This function prepends the OTLP specified namespace to all key lookups. +func (e *EnvOptionsReader) GetEnvValue(key string) (string, bool) { + v := strings.TrimSpace(e.GetEnv(keyWithNamespace(e.Namespace, key))) + return v, v != "" +} + +// WithString retrieves the specified config and passes it to ConfigFn as a string. +func WithString(n string, fn func(string)) func(e *EnvOptionsReader) { + return func(e *EnvOptionsReader) { + if v, ok := e.GetEnvValue(n); ok { + fn(v) + } + } +} + +// WithBool returns a ConfigFn that reads the environment variable n and if it exists passes its parsed bool value to fn. +func WithBool(n string, fn func(bool)) ConfigFn { + return func(e *EnvOptionsReader) { + if v, ok := e.GetEnvValue(n); ok { + b := strings.ToLower(v) == "true" + fn(b) + } } - return cfg } -func getOptionsFromEnv() []GenericOption { - opts := []GenericOption{} - - tlsConf := &tls.Config{} - DefaultEnvOptionsReader.Apply( - envconfig.WithURL("ENDPOINT", func(u *url.URL) { - opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { - cfg.Traces.Endpoint = u.Host - // For OTLP/HTTP endpoint URLs without a per-signal - // configuration, the passed endpoint is used as a base URL - // and the signals are sent to these paths relative to that. - cfg.Traces.URLPath = path.Join(u.Path, DefaultTracesPath) - return cfg - }, withEndpointForGRPC(u))) - }), - envconfig.WithURL("TRACES_ENDPOINT", func(u *url.URL) { - opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { - cfg.Traces.Endpoint = u.Host - // For endpoint URLs for OTLP/HTTP per-signal variables, the - // URL MUST be used as-is without any modification. The only - // exception is that if an URL contains no path part, the root - // path / MUST be used. - path := u.Path - if path == "" { - path = "/" - } - cfg.Traces.URLPath = path - return cfg - }, withEndpointForGRPC(u))) - }), - envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), - envconfig.WithCertPool("TRACES_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), - envconfig.WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }), - envconfig.WithClientCert("TRACES_CLIENT_CERTIFICATE", "TRACES_CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }), - withTLSConfig(tlsConf, func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }), - envconfig.WithBool("INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }), - envconfig.WithBool("TRACES_INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }), - envconfig.WithHeaders("HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }), - envconfig.WithHeaders("TRACES_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }), - WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), - WithEnvCompression("TRACES_COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), - envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), - envconfig.WithDuration("TRACES_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), - ) - - return opts +// WithDuration retrieves the specified config and passes it to ConfigFn as a duration. +func WithDuration(n string, fn func(time.Duration)) func(e *EnvOptionsReader) { + return func(e *EnvOptionsReader) { + if v, ok := e.GetEnvValue(n); ok { + d, err := strconv.Atoi(v) + if err != nil { + global.Error(err, "parse duration", "input", v) + return + } + fn(time.Duration(d) * time.Millisecond) + } + } } -func withEndpointScheme(u *url.URL) GenericOption { - switch strings.ToLower(u.Scheme) { - case "http", "unix": - return WithInsecure() - default: - return WithSecure() +// WithHeaders retrieves the specified config and passes it to ConfigFn as a map of HTTP headers. +func WithHeaders(n string, fn func(map[string]string)) func(e *EnvOptionsReader) { + return func(e *EnvOptionsReader) { + if v, ok := e.GetEnvValue(n); ok { + fn(stringToHeader(v)) + } } } -func withEndpointForGRPC(u *url.URL) func(cfg Config) Config { - return func(cfg Config) Config { - // For OTLP/gRPC endpoints, this is the target to which the - // exporter is going to send telemetry. - cfg.Traces.Endpoint = path.Join(u.Host, u.Path) - return cfg +// WithURL retrieves the specified config and passes it to ConfigFn as a net/url.URL. +func WithURL(n string, fn func(*url.URL)) func(e *EnvOptionsReader) { + return func(e *EnvOptionsReader) { + if v, ok := e.GetEnvValue(n); ok { + u, err := url.Parse(v) + if err != nil { + global.Error(err, "parse url", "input", v) + return + } + fn(u) + } } } -// WithEnvCompression retrieves the specified config and passes it to ConfigFn as a Compression. -func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOptionsReader) { - return func(e *envconfig.EnvOptionsReader) { +// WithCertPool returns a ConfigFn that reads the environment variable n as a filepath to a TLS certificate pool. If it exists, it is parsed as a crypto/x509.CertPool and it is passed to fn. +func WithCertPool(n string, fn func(*x509.CertPool)) ConfigFn { + return func(e *EnvOptionsReader) { if v, ok := e.GetEnvValue(n); ok { - cp := NoCompression - if v == "gzip" { - cp = GzipCompression + b, err := e.ReadFile(v) + if err != nil { + global.Error(err, "read tls ca cert file", "file", v) + return + } + c, err := createCertPool(b) + if err != nil { + global.Error(err, "create tls cert pool") + return } + fn(c) + } + } +} - fn(cp) +// WithClientCert returns a ConfigFn that reads the environment variable nc and nk as filepaths to a client certificate and key pair. If they exists, they are parsed as a crypto/tls.Certificate and it is passed to fn. +func WithClientCert(nc, nk string, fn func(tls.Certificate)) ConfigFn { + return func(e *EnvOptionsReader) { + vc, okc := e.GetEnvValue(nc) + vk, okk := e.GetEnvValue(nk) + if !okc || !okk { + return + } + cert, err := e.ReadFile(vc) + if err != nil { + global.Error(err, "read tls client cert", "file", vc) + return + } + key, err := e.ReadFile(vk) + if err != nil { + global.Error(err, "read tls client key", "file", vk) + return + } + crt, err := tls.X509KeyPair(cert, key) + if err != nil { + global.Error(err, "create tls client key pair") + return } + fn(crt) } } -// revive:disable-next-line:flag-parameter -func withInsecure(b bool) GenericOption { - if b { - return WithInsecure() +func keyWithNamespace(ns, key string) string { + if ns == "" { + return key } - return WithSecure() + return fmt.Sprintf("%s_%s", ns, key) } -func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOptionsReader) { - return func(e *envconfig.EnvOptionsReader) { - if c.RootCAs != nil || len(c.Certificates) > 0 { - fn(c) +func stringToHeader(value string) map[string]string { + headersPairs := strings.Split(value, ",") + headers := make(map[string]string) + + for _, header := range headersPairs { + n, v, found := strings.Cut(header, "=") + if !found { + global.Error(errors.New("missing '="), "parse headers", "input", header) + continue + } + name, err := url.PathUnescape(n) + if err != nil { + global.Error(err, "escape header key", "key", n) + continue } + trimmedName := strings.TrimSpace(name) + value, err := url.PathUnescape(v) + if err != nil { + global.Error(err, "escape header value", "value", v) + continue + } + trimmedValue := strings.TrimSpace(value) + + headers[trimmedName] = trimmedValue + } + + return headers +} + +func createCertPool(certBytes []byte) (*x509.CertPool, error) { + cp := x509.NewCertPool() + if ok := cp.AppendCertsFromPEM(certBytes); !ok { + return nil, errors.New("failed to append certificate to the cert pool") } + return cp, nil } From 999cd493a5a64b4b93e959c56833b502c7733969 Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 11 Aug 2024 17:30:25 +0100 Subject: [PATCH 10/18] lint check --- CHANGELOG.md | 1 - .../internal/envconfig/envconfig.go | 34 +++++++++++-- .../internal/envconfig/envconfig_test.go | 48 +++++++++++++++++++ .../otlptrace/otlpconfig/envconfig.go.tmpl | 36 +++++++++++--- 4 files changed, 107 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index baff2c74366..18f26f851d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,7 +73,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed -- Retry trace and span ID generation if it generated an invalid one in `go.opentelemetry.io/otel/sdk/trace`. (#5514) - Log a warning to the OpenTelemetry internal logger when a `Record` in `go.opentelemetry.io/otel/sdk/log` drops an attribute due to a limit being reached. (#5376) - Identify the `Tracer` returned from the global `TracerProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426) - Identify the `Meter` returned from the global `MeterProvider` in `go.opentelemetry.io/otel/global` with its schema URL. (#5426) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go index 9513c0a57ca..3c453e33d91 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -163,15 +164,19 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.PathUnescape(n) - if err != nil { - global.Error(err, "escape header key", "key", n) + + trimmedName := strings.TrimSpace(n) + + // Validate the key + if !isValidHeaderKey(trimmedName) { + global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - trimmedName := strings.TrimSpace(name) + + // Only decode the value value, err := url.PathUnescape(v) if err != nil { - global.Error(err, "escape header value", "value", v) + global.Error(err, "unescape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) @@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go index 14543ea11eb..53cb0fededd 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig_test.go @@ -270,6 +270,53 @@ func TestEnvConfig(t *testing.T) { }, }, }, + { + name: "with percent-encoded headers", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "user%2Did=42,user%20name=alice%20smith" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "user%2Did": "42", + "user%20name": "alice smith", + }, + }, + }, + }, + { + name: "with invalid header key", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "valid-key=value,invalid key=value" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "valid-key": "value", + }, + }, + }, + }, { name: "with URL", reader: EnvOptionsReader{ @@ -448,6 +495,7 @@ func TestStringToHeader(t *testing.T) { name: "invalid key", value: "%XX=missing,userId=alice", want: map[string]string{ + "%XX": "missing", "userId": "alice", }, }, diff --git a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl index 9513c0a57ca..bab7c4c0353 100644 --- a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -163,15 +164,19 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.PathUnescape(n) - if err != nil { - global.Error(err, "escape header key", "key", n) + + trimmedName := strings.TrimSpace(n) + + // Validate the key + if !isValidHeaderKey(trimmedName) { + global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - trimmedName := strings.TrimSpace(name) - value, err := url.PathUnescape(v) + + // Only decode the value + value, err := url.QueryUnescape(v) if err != nil { - global.Error(err, "escape header value", "value", v) + global.Error(err, "unescape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) @@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} \ No newline at end of file From 284d351d24771c80f3fc7b641446d723908dbe64 Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 11 Aug 2024 17:44:44 +0100 Subject: [PATCH 11/18] go.tmpl change --- .../shared/otlp/envconfig/envconfig.go.tmpl | 36 ++- .../otlptrace/otlpconfig/envconfig.go.tmpl | 269 +++++++----------- 2 files changed, 128 insertions(+), 177 deletions(-) diff --git a/internal/shared/otlp/envconfig/envconfig.go.tmpl b/internal/shared/otlp/envconfig/envconfig.go.tmpl index 6e0110b3344..3c453e33d91 100644 --- a/internal/shared/otlp/envconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/envconfig/envconfig.go.tmpl @@ -4,7 +4,7 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package envconfig +package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig" import ( "crypto/tls" @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -163,15 +164,19 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.PathUnescape(n) - if err != nil { - global.Error(err, "escape header key", "key", n) + + trimmedName := strings.TrimSpace(n) + + // Validate the key + if !isValidHeaderKey(trimmedName) { + global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - trimmedName := strings.TrimSpace(name) + + // Only decode the value value, err := url.PathUnescape(v) if err != nil { - global.Error(err, "escape header value", "value", v) + global.Error(err, "unescape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) @@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} diff --git a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl index bab7c4c0353..fa0bb195ce6 100644 --- a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl @@ -1,215 +1,142 @@ // Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/envconfig/envconfig.go.tmpl +// source: internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -package envconfig // import "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig" +package otlpconfig import ( "crypto/tls" "crypto/x509" - "errors" - "fmt" "net/url" - "strconv" + "os" + "path" "strings" "time" - "unicode" - "go.opentelemetry.io/otel/internal/global" + "{{ .envconfigImportPath }}" ) -// ConfigFn is the generic function used to set a config. -type ConfigFn func(*EnvOptionsReader) - -// EnvOptionsReader reads the required environment variables. -type EnvOptionsReader struct { - GetEnv func(string) string - ReadFile func(string) ([]byte, error) - Namespace string +// DefaultEnvOptionsReader is the default environments reader. +var DefaultEnvOptionsReader = envconfig.EnvOptionsReader{ + GetEnv: os.Getenv, + ReadFile: os.ReadFile, + Namespace: "OTEL_EXPORTER_OTLP", } -// Apply runs every ConfigFn. -func (e *EnvOptionsReader) Apply(opts ...ConfigFn) { - for _, o := range opts { - o(e) +// ApplyGRPCEnvConfigs applies the env configurations for gRPC. +func ApplyGRPCEnvConfigs(cfg Config) Config { + opts := getOptionsFromEnv() + for _, opt := range opts { + cfg = opt.ApplyGRPCOption(cfg) } + return cfg } -// GetEnvValue gets an OTLP environment variable value of the specified key -// using the GetEnv function. -// This function prepends the OTLP specified namespace to all key lookups. -func (e *EnvOptionsReader) GetEnvValue(key string) (string, bool) { - v := strings.TrimSpace(e.GetEnv(keyWithNamespace(e.Namespace, key))) - return v, v != "" -} - -// WithString retrieves the specified config and passes it to ConfigFn as a string. -func WithString(n string, fn func(string)) func(e *EnvOptionsReader) { - return func(e *EnvOptionsReader) { - if v, ok := e.GetEnvValue(n); ok { - fn(v) - } +// ApplyHTTPEnvConfigs applies the env configurations for HTTP. +func ApplyHTTPEnvConfigs(cfg Config) Config { + opts := getOptionsFromEnv() + for _, opt := range opts { + cfg = opt.ApplyHTTPOption(cfg) } + return cfg } -// WithBool returns a ConfigFn that reads the environment variable n and if it exists passes its parsed bool value to fn. -func WithBool(n string, fn func(bool)) ConfigFn { - return func(e *EnvOptionsReader) { - if v, ok := e.GetEnvValue(n); ok { - b := strings.ToLower(v) == "true" - fn(b) - } - } +func getOptionsFromEnv() []GenericOption { + opts := []GenericOption{} + + tlsConf := &tls.Config{} + DefaultEnvOptionsReader.Apply( + envconfig.WithURL("ENDPOINT", func(u *url.URL) { + opts = append(opts, withEndpointScheme(u)) + opts = append(opts, newSplitOption(func(cfg Config) Config { + cfg.Traces.Endpoint = u.Host + // For OTLP/HTTP endpoint URLs without a per-signal + // configuration, the passed endpoint is used as a base URL + // and the signals are sent to these paths relative to that. + cfg.Traces.URLPath = path.Join(u.Path, DefaultTracesPath) + return cfg + }, withEndpointForGRPC(u))) + }), + envconfig.WithURL("TRACES_ENDPOINT", func(u *url.URL) { + opts = append(opts, withEndpointScheme(u)) + opts = append(opts, newSplitOption(func(cfg Config) Config { + cfg.Traces.Endpoint = u.Host + // For endpoint URLs for OTLP/HTTP per-signal variables, the + // URL MUST be used as-is without any modification. The only + // exception is that if an URL contains no path part, the root + // path / MUST be used. + path := u.Path + if path == "" { + path = "/" + } + cfg.Traces.URLPath = path + return cfg + }, withEndpointForGRPC(u))) + }), + envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), + envconfig.WithCertPool("TRACES_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), + envconfig.WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }), + envconfig.WithClientCert("TRACES_CLIENT_CERTIFICATE", "TRACES_CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }), + withTLSConfig(tlsConf, func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }), + envconfig.WithBool("INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }), + envconfig.WithBool("TRACES_INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }), + envconfig.WithHeaders("HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }), + envconfig.WithHeaders("TRACES_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }), + WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), + WithEnvCompression("TRACES_COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), + envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), + envconfig.WithDuration("TRACES_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), + ) + + return opts } -// WithDuration retrieves the specified config and passes it to ConfigFn as a duration. -func WithDuration(n string, fn func(time.Duration)) func(e *EnvOptionsReader) { - return func(e *EnvOptionsReader) { - if v, ok := e.GetEnvValue(n); ok { - d, err := strconv.Atoi(v) - if err != nil { - global.Error(err, "parse duration", "input", v) - return - } - fn(time.Duration(d) * time.Millisecond) - } +func withEndpointScheme(u *url.URL) GenericOption { + switch strings.ToLower(u.Scheme) { + case "http", "unix": + return WithInsecure() + default: + return WithSecure() } } -// WithHeaders retrieves the specified config and passes it to ConfigFn as a map of HTTP headers. -func WithHeaders(n string, fn func(map[string]string)) func(e *EnvOptionsReader) { - return func(e *EnvOptionsReader) { - if v, ok := e.GetEnvValue(n); ok { - fn(stringToHeader(v)) - } +func withEndpointForGRPC(u *url.URL) func(cfg Config) Config { + return func(cfg Config) Config { + // For OTLP/gRPC endpoints, this is the target to which the + // exporter is going to send telemetry. + cfg.Traces.Endpoint = path.Join(u.Host, u.Path) + return cfg } } -// WithURL retrieves the specified config and passes it to ConfigFn as a net/url.URL. -func WithURL(n string, fn func(*url.URL)) func(e *EnvOptionsReader) { - return func(e *EnvOptionsReader) { +// WithEnvCompression retrieves the specified config and passes it to ConfigFn as a Compression. +func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOptionsReader) { + return func(e *envconfig.EnvOptionsReader) { if v, ok := e.GetEnvValue(n); ok { - u, err := url.Parse(v) - if err != nil { - global.Error(err, "parse url", "input", v) - return + cp := NoCompression + if v == "gzip" { + cp = GzipCompression } - fn(u) - } - } -} - -// WithCertPool returns a ConfigFn that reads the environment variable n as a filepath to a TLS certificate pool. If it exists, it is parsed as a crypto/x509.CertPool and it is passed to fn. -func WithCertPool(n string, fn func(*x509.CertPool)) ConfigFn { - return func(e *EnvOptionsReader) { - if v, ok := e.GetEnvValue(n); ok { - b, err := e.ReadFile(v) - if err != nil { - global.Error(err, "read tls ca cert file", "file", v) - return - } - c, err := createCertPool(b) - if err != nil { - global.Error(err, "create tls cert pool") - return - } - fn(c) - } - } -} -// WithClientCert returns a ConfigFn that reads the environment variable nc and nk as filepaths to a client certificate and key pair. If they exists, they are parsed as a crypto/tls.Certificate and it is passed to fn. -func WithClientCert(nc, nk string, fn func(tls.Certificate)) ConfigFn { - return func(e *EnvOptionsReader) { - vc, okc := e.GetEnvValue(nc) - vk, okk := e.GetEnvValue(nk) - if !okc || !okk { - return - } - cert, err := e.ReadFile(vc) - if err != nil { - global.Error(err, "read tls client cert", "file", vc) - return - } - key, err := e.ReadFile(vk) - if err != nil { - global.Error(err, "read tls client key", "file", vk) - return + fn(cp) } - crt, err := tls.X509KeyPair(cert, key) - if err != nil { - global.Error(err, "create tls client key pair") - return - } - fn(crt) } } -func keyWithNamespace(ns, key string) string { - if ns == "" { - return key +// revive:disable-next-line:flag-parameter +func withInsecure(b bool) GenericOption { + if b { + return WithInsecure() } - return fmt.Sprintf("%s_%s", ns, key) + return WithSecure() } -func stringToHeader(value string) map[string]string { - headersPairs := strings.Split(value, ",") - headers := make(map[string]string) - - for _, header := range headersPairs { - n, v, found := strings.Cut(header, "=") - if !found { - global.Error(errors.New("missing '="), "parse headers", "input", header) - continue - } - - trimmedName := strings.TrimSpace(n) - - // Validate the key - if !isValidHeaderKey(trimmedName) { - global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) - continue - } - - // Only decode the value - value, err := url.QueryUnescape(v) - if err != nil { - global.Error(err, "unescape header value", "value", v) - continue - } - trimmedValue := strings.TrimSpace(value) - - headers[trimmedName] = trimmedValue - } - - return headers -} - -func createCertPool(certBytes []byte) (*x509.CertPool, error) { - cp := x509.NewCertPool() - if ok := cp.AppendCertsFromPEM(certBytes); !ok { - return nil, errors.New("failed to append certificate to the cert pool") - } - return cp, nil -} - -func isValidHeaderKey(key string) bool { - if key == "" { - return false - } - for _, c := range key { - if !isTokenChar(c) { - return false +func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOptionsReader) { + return func(e *envconfig.EnvOptionsReader) { + if c.RootCAs != nil || len(c.Certificates) > 0 { + fn(c) } } - return true -} - -func isTokenChar(c rune) bool { - return c <= unicode.MaxASCII && (unicode.IsLetter(c) || - unicode.IsDigit(c) || - c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || - c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') } \ No newline at end of file From f96c597a58d43b6071f1bf69465211951e2acddb Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 11 Aug 2024 17:49:51 +0100 Subject: [PATCH 12/18] lint --- .../otlptrace/otlptracegrpc/internal/envconfig/envconfig.go | 2 +- internal/shared/otlp/envconfig/envconfig.go.tmpl | 2 +- internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go index 3c453e33d91..fbfcad0e87a 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go @@ -176,7 +176,7 @@ func stringToHeader(value string) map[string]string { // Only decode the value value, err := url.PathUnescape(v) if err != nil { - global.Error(err, "unescape header value", "value", v) + global.Error(err, "escape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) diff --git a/internal/shared/otlp/envconfig/envconfig.go.tmpl b/internal/shared/otlp/envconfig/envconfig.go.tmpl index 3c453e33d91..fbfcad0e87a 100644 --- a/internal/shared/otlp/envconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/envconfig/envconfig.go.tmpl @@ -176,7 +176,7 @@ func stringToHeader(value string) map[string]string { // Only decode the value value, err := url.PathUnescape(v) if err != nil { - global.Error(err, "unescape header value", "value", v) + global.Error(err, "escape header value", "value", v) continue } trimmedValue := strings.TrimSpace(value) diff --git a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl index fa0bb195ce6..f1fa3dc8450 100644 --- a/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/otlptrace/otlpconfig/envconfig.go.tmpl @@ -139,4 +139,4 @@ func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOpt fn(c) } } -} \ No newline at end of file +} From e180b3f7ddc0b412d7e0ddf96bef393493e78a72 Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 11 Aug 2024 18:15:21 +0100 Subject: [PATCH 13/18] update tmpl file --- .../otlp/envconfig/envconfig_test.go.tmpl | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/internal/shared/otlp/envconfig/envconfig_test.go.tmpl b/internal/shared/otlp/envconfig/envconfig_test.go.tmpl index 14543ea11eb..53cb0fededd 100644 --- a/internal/shared/otlp/envconfig/envconfig_test.go.tmpl +++ b/internal/shared/otlp/envconfig/envconfig_test.go.tmpl @@ -270,6 +270,53 @@ func TestEnvConfig(t *testing.T) { }, }, }, + { + name: "with percent-encoded headers", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "user%2Did=42,user%20name=alice%20smith" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "user%2Did": "42", + "user%20name": "alice smith", + }, + }, + }, + }, + { + name: "with invalid header key", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "valid-key=value,invalid key=value" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "valid-key": "value", + }, + }, + }, + }, { name: "with URL", reader: EnvOptionsReader{ @@ -448,6 +495,7 @@ func TestStringToHeader(t *testing.T) { name: "invalid key", value: "%XX=missing,userId=alice", want: map[string]string{ + "%XX": "missing", "userId": "alice", }, }, From 54b7c1c9714fd5a6bfd6cf6cd59e162a24be86bc Mon Sep 17 00:00:00 2001 From: Fools Date: Sun, 11 Aug 2024 21:53:13 +0100 Subject: [PATCH 14/18] make precommit --- .../internal/envconfig/envconfig.go | 32 +++++++++++-- .../internal/envconfig/envconfig_test.go | 48 +++++++++++++++++++ .../internal/envconfig/envconfig.go | 32 +++++++++++-- .../internal/envconfig/envconfig_test.go | 48 +++++++++++++++++++ .../internal/envconfig/envconfig.go | 32 +++++++++++-- .../internal/envconfig/envconfig_test.go | 48 +++++++++++++++++++ 6 files changed, 228 insertions(+), 12 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go index b2735ba923c..aed91bb3e9f 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -163,12 +164,16 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.PathUnescape(n) - if err != nil { - global.Error(err, "escape header key", "key", n) + + trimmedName := strings.TrimSpace(n) + + // Validate the key + if !isValidHeaderKey(trimmedName) { + global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - trimmedName := strings.TrimSpace(name) + + // Only decode the value value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v) @@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig_test.go index 14543ea11eb..53cb0fededd 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig_test.go @@ -270,6 +270,53 @@ func TestEnvConfig(t *testing.T) { }, }, }, + { + name: "with percent-encoded headers", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "user%2Did=42,user%20name=alice%20smith" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "user%2Did": "42", + "user%20name": "alice smith", + }, + }, + }, + }, + { + name: "with invalid header key", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "valid-key=value,invalid key=value" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "valid-key": "value", + }, + }, + }, + }, { name: "with URL", reader: EnvOptionsReader{ @@ -448,6 +495,7 @@ func TestStringToHeader(t *testing.T) { name: "invalid key", value: "%XX=missing,userId=alice", want: map[string]string{ + "%XX": "missing", "userId": "alice", }, }, diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go index 35885ba8a72..eb5d12c8ad9 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -163,12 +164,16 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.PathUnescape(n) - if err != nil { - global.Error(err, "escape header key", "key", n) + + trimmedName := strings.TrimSpace(n) + + // Validate the key + if !isValidHeaderKey(trimmedName) { + global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - trimmedName := strings.TrimSpace(name) + + // Only decode the value value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v) @@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig_test.go index 14543ea11eb..53cb0fededd 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig_test.go @@ -270,6 +270,53 @@ func TestEnvConfig(t *testing.T) { }, }, }, + { + name: "with percent-encoded headers", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "user%2Did=42,user%20name=alice%20smith" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "user%2Did": "42", + "user%20name": "alice smith", + }, + }, + }, + }, + { + name: "with invalid header key", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "valid-key=value,invalid key=value" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "valid-key": "value", + }, + }, + }, + }, { name: "with URL", reader: EnvOptionsReader{ @@ -448,6 +495,7 @@ func TestStringToHeader(t *testing.T) { name: "invalid key", value: "%XX=missing,userId=alice", want: map[string]string{ + "%XX": "missing", "userId": "alice", }, }, diff --git a/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go b/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go index 26a316d003d..ede155e502b 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go @@ -15,6 +15,7 @@ import ( "strconv" "strings" "time" + "unicode" "go.opentelemetry.io/otel/internal/global" ) @@ -163,12 +164,16 @@ func stringToHeader(value string) map[string]string { global.Error(errors.New("missing '="), "parse headers", "input", header) continue } - name, err := url.PathUnescape(n) - if err != nil { - global.Error(err, "escape header key", "key", n) + + trimmedName := strings.TrimSpace(n) + + // Validate the key + if !isValidHeaderKey(trimmedName) { + global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - trimmedName := strings.TrimSpace(name) + + // Only decode the value value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v) @@ -189,3 +194,22 @@ func createCertPool(certBytes []byte) (*x509.CertPool, error) { } return cp, nil } + +func isValidHeaderKey(key string) bool { + if key == "" { + return false + } + for _, c := range key { + if !isTokenChar(c) { + return false + } + } + return true +} + +func isTokenChar(c rune) bool { + return c <= unicode.MaxASCII && (unicode.IsLetter(c) || + unicode.IsDigit(c) || + c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || + c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~') +} diff --git a/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig_test.go b/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig_test.go index 14543ea11eb..53cb0fededd 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig_test.go +++ b/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig_test.go @@ -270,6 +270,53 @@ func TestEnvConfig(t *testing.T) { }, }, }, + { + name: "with percent-encoded headers", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "user%2Did=42,user%20name=alice%20smith" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "user%2Did": "42", + "user%20name": "alice smith", + }, + }, + }, + }, + { + name: "with invalid header key", + reader: EnvOptionsReader{ + GetEnv: func(n string) string { + if n == "HELLO" { + return "valid-key=value,invalid key=value" + } + return "" + }, + }, + configs: []ConfigFn{ + WithHeaders("HELLO", func(v map[string]string) { + options = append(options, testOption{TestHeaders: v}) + }), + }, + expectedOptions: []testOption{ + { + TestHeaders: map[string]string{ + "valid-key": "value", + }, + }, + }, + }, { name: "with URL", reader: EnvOptionsReader{ @@ -448,6 +495,7 @@ func TestStringToHeader(t *testing.T) { name: "invalid key", value: "%XX=missing,userId=alice", want: map[string]string{ + "%XX": "missing", "userId": "alice", }, }, From 361d9d0ffd028729ac48cf83587111c82983efd6 Mon Sep 17 00:00:00 2001 From: Zhihan Li <54661071+zhihali@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:15:41 +0100 Subject: [PATCH 15/18] Update CHANGELOG.md Co-authored-by: Damien Mathieu <42@dmathieu.com> --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b500108d991..d06ee257083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed -- Fix parsing of OTLP exporter header environment variables, make it stop parsing keys and add key validation in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go`(#5704) +- Stop percent encoding header environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` (#5705) +- Remove invalid environment variable header keys in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc`, `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`, `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc` and `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` (#5705) - Correct comments for the priority of the `WithEndpoint` and `WithEndpointURL` options and their corresponding environment variables in `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#5584) - Correct the `Tracer`, `Meter`, and `Logger` names used in `go.opentelemetry.io/otel/example/dice`. (#5612) - Correct the `Tracer` names used in `go.opentelemetry.io/otel/example/namedtracer`. (#5612) From 13298aafb7f3062640eb7bf7da647433e47d166b Mon Sep 17 00:00:00 2001 From: Zhihan Li <54661071+zhihali@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:47:14 +0100 Subject: [PATCH 16/18] Update internal/shared/otlp/envconfig/envconfig.go.tmpl Co-authored-by: Tyler Yahn --- internal/shared/otlp/envconfig/envconfig.go.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/shared/otlp/envconfig/envconfig.go.tmpl b/internal/shared/otlp/envconfig/envconfig.go.tmpl index fbfcad0e87a..eff09204c0e 100644 --- a/internal/shared/otlp/envconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/envconfig/envconfig.go.tmpl @@ -167,7 +167,7 @@ func stringToHeader(value string) map[string]string { trimmedName := strings.TrimSpace(n) - // Validate the key + // Validate the key. if !isValidHeaderKey(trimmedName) { global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue From 6e34625bb38d2be5596471782958669c73d09ebe Mon Sep 17 00:00:00 2001 From: Zhihan Li <54661071+zhihali@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:47:25 +0100 Subject: [PATCH 17/18] Update internal/shared/otlp/envconfig/envconfig.go.tmpl Co-authored-by: Tyler Yahn --- internal/shared/otlp/envconfig/envconfig.go.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/shared/otlp/envconfig/envconfig.go.tmpl b/internal/shared/otlp/envconfig/envconfig.go.tmpl index eff09204c0e..4abf48d1f62 100644 --- a/internal/shared/otlp/envconfig/envconfig.go.tmpl +++ b/internal/shared/otlp/envconfig/envconfig.go.tmpl @@ -173,7 +173,7 @@ func stringToHeader(value string) map[string]string { continue } - // Only decode the value + // Only decode the value. value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v) From 74361cded4c4410fbf73bbc44a0ab906f5faca35 Mon Sep 17 00:00:00 2001 From: Fools Date: Wed, 21 Aug 2024 15:18:18 +0100 Subject: [PATCH 18/18] make precommit --- .../otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go | 4 ++-- .../otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go | 4 ++-- .../otlptrace/otlptracegrpc/internal/envconfig/envconfig.go | 4 ++-- .../otlptrace/otlptracehttp/internal/envconfig/envconfig.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go index aed91bb3e9f..261f5502682 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig/envconfig.go @@ -167,13 +167,13 @@ func stringToHeader(value string) map[string]string { trimmedName := strings.TrimSpace(n) - // Validate the key + // Validate the key. if !isValidHeaderKey(trimmedName) { global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - // Only decode the value + // Only decode the value. value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go index eb5d12c8ad9..7ac42759f6c 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig/envconfig.go @@ -167,13 +167,13 @@ func stringToHeader(value string) map[string]string { trimmedName := strings.TrimSpace(n) - // Validate the key + // Validate the key. if !isValidHeaderKey(trimmedName) { global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - // Only decode the value + // Only decode the value. value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v) diff --git a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go index fbfcad0e87a..4abf48d1f62 100644 --- a/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlptrace/otlptracegrpc/internal/envconfig/envconfig.go @@ -167,13 +167,13 @@ func stringToHeader(value string) map[string]string { trimmedName := strings.TrimSpace(n) - // Validate the key + // Validate the key. if !isValidHeaderKey(trimmedName) { global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - // Only decode the value + // Only decode the value. value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v) diff --git a/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go b/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go index ede155e502b..f30bb66aeda 100644 --- a/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go +++ b/exporters/otlp/otlptrace/otlptracehttp/internal/envconfig/envconfig.go @@ -167,13 +167,13 @@ func stringToHeader(value string) map[string]string { trimmedName := strings.TrimSpace(n) - // Validate the key + // Validate the key. if !isValidHeaderKey(trimmedName) { global.Error(errors.New("invalid header key"), "parse headers", "key", trimmedName) continue } - // Only decode the value + // Only decode the value. value, err := url.PathUnescape(v) if err != nil { global.Error(err, "escape header value", "value", v)