diff --git a/CHANGELOG.md b/CHANGELOG.md index 46684e0b29a..638013b3e3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed -- Added `WithoutSubSpans` option to `NewClientTrace` in the `instrumentation/net/http/httptrace/otelhttptrace` package (#879) +- Added `WithoutSubSpans`, `WithRedactedHeaders`, and `WithoutHeaders` options to `NewClientTrace` in the `instrumentation/net/http/httptrace/otelhttptrace` package (#879) ## [0.21.0] - 2021-06-18 diff --git a/instrumentation/net/http/httptrace/otelhttptrace/clienttrace.go b/instrumentation/net/http/httptrace/otelhttptrace/clienttrace.go index c5a66469d30..02aba7db5a7 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/clienttrace.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/clienttrace.go @@ -73,22 +73,53 @@ func WithoutSubSpans() ClientTraceOption { } } +// WithRedactedHeaders will be replaced by fixed '****' values for the header +// names provided. These are in addition to the sensitive headers already +// redacted by default: Authorization, WWW-Authenticate, Proxy-Authenticate +// Proxy-Authorization, Cookie, Set-Cookie +func WithRedactedHeaders(headers ...string) ClientTraceOption { + return func(ct *clientTracer) { + for _, header := range headers { + ct.redactedHeaders[strings.ToLower(header)] = struct{}{} + } + } +} + +// WithoutHeaders will disable adding span annotations for the http headers +// and values. +func WithoutHeaders() ClientTraceOption { + return func(ct *clientTracer) { + ct.addHeaders = false + } +} + type clientTracer struct { context.Context tr trace.Tracer - activeHooks map[string]context.Context - root trace.Span - mtx sync.Mutex - useSpans bool + activeHooks map[string]context.Context + root trace.Span + mtx sync.Mutex + redactedHeaders map[string]struct{} + addHeaders bool + useSpans bool } func NewClientTrace(ctx context.Context, opts ...ClientTraceOption) *httptrace.ClientTrace { ct := &clientTracer{ Context: ctx, activeHooks: make(map[string]context.Context), - useSpans: true, + redactedHeaders: map[string]struct{}{ + "authorization": {}, + "www-authenticate": {}, + "proxy-authenticate": {}, + "proxy-authorization": {}, + "cookie": {}, + "set-cookie": {}, + }, + addHeaders: true, + useSpans: true, } for _, opt := range opts { opt(ct) @@ -259,7 +290,15 @@ func (ct *clientTracer) wroteHeaderField(k string, v []string) { if ct.useSpans && ct.span("http.headers") == nil { ct.start("http.headers", "http.headers") } - ct.root.SetAttributes(attribute.String("http."+strings.ToLower(k), sliceToString(v))) + if !ct.addHeaders { + return + } + k = strings.ToLower(k) + value := sliceToString(v) + if _, ok := ct.redactedHeaders[k]; ok { + value = "****" + } + ct.root.SetAttributes(attribute.String("http."+k, value)) } func (ct *clientTracer) wroteHeaders() { diff --git a/instrumentation/net/http/httptrace/otelhttptrace/clienttrace_test.go b/instrumentation/net/http/httptrace/otelhttptrace/clienttrace_test.go index 7bf19aac5b1..3e97b106153 100644 --- a/instrumentation/net/http/httptrace/otelhttptrace/clienttrace_test.go +++ b/instrumentation/net/http/httptrace/otelhttptrace/clienttrace_test.go @@ -293,6 +293,7 @@ func TestWithoutSubSpans(t *testing.T) { ) req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) req.Header.Set("User-Agent", "oteltest/1.1") + req.Header.Set("Authorization", "Bearer token123") require.NoError(t, err) resp, err = ts.Client().Do(req) require.NoError(t, err) @@ -316,7 +317,7 @@ func TestWithoutSubSpans(t *testing.T) { assert.Equal(t, expectedEventNames, gotEventNames) gotAttributes := recSpan.Attributes() - require.Len(t, gotAttributes, 8) + require.Len(t, gotAttributes, 9) assert.Equal(t, attribute.StringValue("gzip"), gotAttributes[attribute.Key("http.accept-encoding")], @@ -325,6 +326,11 @@ func TestWithoutSubSpans(t *testing.T) { attribute.StringValue("oteltest/1.1"), gotAttributes[attribute.Key("http.user-agent")], ) + // verify redacted auth headers + assert.Equal(t, + attribute.StringValue("****"), + gotAttributes[attribute.Key("http.authorization")], + ) // value is dynamic, just verify we have the attribute assert.Contains(t, gotAttributes, attribute.Key("http.conn.idletime")) assert.Equal(t,