Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: OTLP exporters should not percent decode the key when parsing HEADERS env vars #5705

Merged
merged 29 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5dfc5e8
bug fix
zhihali Jun 16, 2024
f6d6216
Merge branch 'open-telemetry:main' into main
zhihali Jun 16, 2024
1df95e6
update changelog
zhihali Jun 16, 2024
ea40bd0
Update CHANGELOG.md
zhihali Jun 17, 2024
3b5fb78
Update sdk/trace/id_generator.go
zhihali Jun 17, 2024
70cba80
Merge branch 'main' into main
zhihali Jun 17, 2024
f1a0bf0
go idoms change
zhihali Jun 17, 2024
c8cd196
Merge pull request #1 from Charlie-lizhihan/dev
zhihali Jun 17, 2024
0bce352
Merge branch 'main' into main
zhihali Jun 18, 2024
94d06af
Update sdk/trace/id_generator.go
dmathieu Jun 18, 2024
ecef23c
Update sdk/trace/id_generator.go
dmathieu Jun 18, 2024
9124208
Merge branch 'main' into main
zhihali Jun 18, 2024
215498e
Merge remote-tracking branch 'origin/main'
zhihali Aug 11, 2024
41d321f
bug fix 5623
zhihali Aug 11, 2024
e7a1073
lint check and update the tmpl file
zhihali Aug 11, 2024
999cd49
lint check
zhihali Aug 11, 2024
284d351
go.tmpl change
zhihali Aug 11, 2024
f96c597
lint
zhihali Aug 11, 2024
e180b3f
update tmpl file
zhihali Aug 11, 2024
54b7c1c
make precommit
zhihali Aug 11, 2024
915aa00
Merge branch 'main' into bugfix-5623
zhihali Aug 14, 2024
574acfd
Merge branch 'main' into bugfix-5623
zhihali Aug 17, 2024
e691e42
Merge branch 'main' into bugfix-5623
zhihali Aug 19, 2024
361d9d0
Update CHANGELOG.md
zhihali Aug 20, 2024
4ac2ba1
Merge branch 'main' into bugfix-5623
zhihali Aug 20, 2024
76d5163
Merge branch 'main' into bugfix-5623
hanyuancheung Aug 21, 2024
13298aa
Update internal/shared/otlp/envconfig/envconfig.go.tmpl
zhihali Aug 21, 2024
6e34625
Update internal/shared/otlp/envconfig/envconfig.go.tmpl
zhihali Aug 21, 2024
74361cd
make precommit
zhihali Aug 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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)
zhihali marked this conversation as resolved.
Show resolved Hide resolved
- 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"strconv"
"strings"
"time"
"unicode"

"go.opentelemetry.io/otel/internal/global"
)
Expand Down Expand Up @@ -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)
Expand All @@ -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 == '~')
}
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"strconv"
"strings"
"time"
"unicode"

"go.opentelemetry.io/otel/internal/global"
)
Expand Down Expand Up @@ -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)
Expand All @@ -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 == '~')
}
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"strconv"
"strings"
"time"
"unicode"

"go.opentelemetry.io/otel/internal/global"
)
Expand Down Expand Up @@ -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)
Expand All @@ -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 == '~')
}
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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",
},
},
Expand Down
Loading
Loading