Skip to content

Commit 8753841

Browse files
committed
contrib/{aws/*,net/http}: fix encoding of http.url (#1662)
This PR ensures auth strings are not present in URLs before attaching them to spans in the `http.url` tag
1 parent 7fd5e0d commit 8753841

File tree

6 files changed

+191
-3
lines changed

6 files changed

+191
-3
lines changed

contrib/aws/aws-sdk-go-v2/aws/aws.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,11 @@ func (mw *traceMiddleware) deserializeTraceMiddleware(stack *middleware.Stack) e
104104

105105
// Get values out of the request.
106106
if req, ok := in.Request.(*smithyhttp.Request); ok {
107+
// Make a copy of the URL so we don't modify the outgoing request
108+
url := *req.URL
109+
url.User = nil // Do not include userinfo in the HTTPURL tag.
107110
span.SetTag(ext.HTTPMethod, req.Method)
108-
span.SetTag(ext.HTTPURL, req.URL.String())
111+
span.SetTag(ext.HTTPURL, url.String())
109112
span.SetTag(tagAWSAgent, req.Header.Get("User-Agent"))
110113
}
111114

contrib/aws/aws-sdk-go-v2/aws/aws_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ package aws
77

88
import (
99
"context"
10+
"encoding/base64"
1011
"net/http"
1112
"net/http/httptest"
13+
"net/url"
14+
"strings"
1215
"testing"
1316

1417
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
@@ -17,6 +20,7 @@ import (
1720
"github.com/aws/aws-sdk-go-v2/aws"
1821
"github.com/aws/aws-sdk-go-v2/service/sqs"
1922
"github.com/stretchr/testify/assert"
23+
"github.com/stretchr/testify/require"
2024
)
2125

2226
func TestAppendMiddleware(t *testing.T) {
@@ -199,3 +203,58 @@ func TestAppendMiddleware_WithOpts(t *testing.T) {
199203
})
200204
}
201205
}
206+
207+
func TestHTTPCredentials(t *testing.T) {
208+
mt := mocktracer.Start()
209+
defer mt.Stop()
210+
211+
var auth string
212+
213+
server := httptest.NewServer(http.HandlerFunc(
214+
func(w http.ResponseWriter, r *http.Request) {
215+
if enc, ok := r.Header["Authorization"]; ok {
216+
encoded := strings.TrimPrefix(enc[0], "Basic ")
217+
if b64, err := base64.StdEncoding.DecodeString(encoded); err == nil {
218+
auth = string(b64)
219+
}
220+
}
221+
222+
w.Header().Set("X-Amz-RequestId", "test_req")
223+
w.WriteHeader(200)
224+
w.Write([]byte(`{}`))
225+
}))
226+
defer server.Close()
227+
228+
u, err := url.Parse(server.URL)
229+
require.NoError(t, err)
230+
u.User = url.UserPassword("myuser", "mypassword")
231+
232+
resolver := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
233+
return aws.Endpoint{
234+
PartitionID: "aws",
235+
URL: u.String(),
236+
SigningRegion: "eu-west-1",
237+
}, nil
238+
})
239+
240+
awsCfg := aws.Config{
241+
Region: "eu-west-1",
242+
Credentials: aws.AnonymousCredentials{},
243+
EndpointResolver: resolver,
244+
}
245+
246+
AppendMiddleware(&awsCfg)
247+
248+
sqsClient := sqs.NewFromConfig(awsCfg)
249+
sqsClient.ListQueues(context.Background(), &sqs.ListQueuesInput{})
250+
251+
spans := mt.FinishedSpans()
252+
253+
s := spans[0]
254+
assert.Equal(t, server.URL+"/", s.Tag(ext.HTTPURL))
255+
assert.NotContains(t, s.Tag(ext.HTTPURL), "mypassword")
256+
assert.NotContains(t, s.Tag(ext.HTTPURL), "myuser")
257+
// Make sure we haven't modified the outgoing request, and the server still
258+
// receives the auth request.
259+
assert.Equal(t, auth, "myuser:mypassword")
260+
}

contrib/aws/aws-sdk-go/aws/aws.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ func (h *handlers) Send(req *request.Request) {
6060
if req.RetryCount != 0 {
6161
return
6262
}
63+
// Make a copy of the URL so we don't modify the outgoing request
64+
url := *req.HTTPRequest.URL
65+
url.User = nil // Do not include userinfo in the HTTPURL tag.
6366
opts := []ddtrace.StartSpanOption{
6467
tracer.SpanType(ext.SpanTypeHTTP),
6568
tracer.ServiceName(h.serviceName(req)),
@@ -68,7 +71,7 @@ func (h *handlers) Send(req *request.Request) {
6871
tracer.Tag(tagAWSOperation, h.awsOperation(req)),
6972
tracer.Tag(tagAWSRegion, h.awsRegion(req)),
7073
tracer.Tag(ext.HTTPMethod, req.Operation.HTTPMethod),
71-
tracer.Tag(ext.HTTPURL, req.HTTPRequest.URL.String()),
74+
tracer.Tag(ext.HTTPURL, url.String()),
7275
tracer.Tag(ext.Component, "aws/aws-sdk-go/aws"),
7376
tracer.Tag(ext.SpanKind, ext.SpanKindClient),
7477
}

contrib/aws/aws-sdk-go/aws/aws_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@ package aws
77

88
import (
99
"context"
10+
"encoding/base64"
1011
"errors"
12+
"net/http"
13+
"net/http/httptest"
14+
"net/url"
15+
"strings"
1116
"testing"
1217

1318
"github.com/aws/aws-sdk-go/aws"
1419
"github.com/aws/aws-sdk-go/aws/credentials"
20+
"github.com/aws/aws-sdk-go/aws/endpoints"
1521
"github.com/aws/aws-sdk-go/aws/request"
1622
"github.com/aws/aws-sdk-go/aws/session"
1723
"github.com/aws/aws-sdk-go/service/ec2"
1824
"github.com/aws/aws-sdk-go/service/s3"
1925
"github.com/stretchr/testify/assert"
26+
"github.com/stretchr/testify/require"
2027

2128
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
2229
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
@@ -182,3 +189,65 @@ func TestRetries(t *testing.T) {
182189
assert.Len(t, mt.FinishedSpans(), 1)
183190
assert.Equal(t, mt.FinishedSpans()[0].Tag(tagAWSRetryCount), 3)
184191
}
192+
193+
func TestHTTPCredentials(t *testing.T) {
194+
mt := mocktracer.Start()
195+
defer mt.Stop()
196+
197+
var auth string
198+
199+
server := httptest.NewServer(http.HandlerFunc(
200+
func(w http.ResponseWriter, r *http.Request) {
201+
if enc, ok := r.Header["Authorization"]; ok {
202+
encoded := strings.TrimPrefix(enc[0], "Basic ")
203+
if b64, err := base64.StdEncoding.DecodeString(encoded); err == nil {
204+
auth = string(b64)
205+
}
206+
}
207+
208+
w.Header().Set("X-Amz-RequestId", "test_req")
209+
w.WriteHeader(200)
210+
w.Write([]byte(`{}`))
211+
}))
212+
defer server.Close()
213+
214+
u, err := url.Parse(server.URL)
215+
require.NoError(t, err)
216+
u.User = url.UserPassword("myuser", "mypassword")
217+
218+
resolver := endpoints.ResolverFunc(func(service, region string, opts ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
219+
return endpoints.ResolvedEndpoint{
220+
PartitionID: "aws",
221+
URL: u.String(),
222+
SigningRegion: "eu-west-1",
223+
}, nil
224+
})
225+
226+
region := "eu-west-1"
227+
awsCfg := aws.Config{
228+
Region: &region,
229+
Credentials: credentials.AnonymousCredentials,
230+
EndpointResolver: resolver,
231+
}
232+
session := WrapSession(session.Must(session.NewSession(&awsCfg)))
233+
234+
ctx := context.Background()
235+
s3api := s3.New(session)
236+
req, _ := s3api.GetObjectRequest(&s3.GetObjectInput{
237+
Bucket: aws.String("BUCKET"),
238+
Key: aws.String("KEY"),
239+
})
240+
req.SetContext(ctx)
241+
err = req.Send()
242+
require.NoError(t, err)
243+
244+
spans := mt.FinishedSpans()
245+
246+
s := spans[0]
247+
assert.Equal(t, server.URL+"/BUCKET/KEY", s.Tag(ext.HTTPURL))
248+
assert.NotContains(t, s.Tag(ext.HTTPURL), "mypassword")
249+
assert.NotContains(t, s.Tag(ext.HTTPURL), "myuser")
250+
// Make sure we haven't modified the outgoing request, and the server still
251+
// receives the auth request.
252+
assert.Equal(t, auth, "myuser:mypassword")
253+
}

contrib/net/http/roundtripper.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ func (rt *roundTripper) RoundTrip(req *http.Request) (res *http.Response, err er
2727
return rt.base.RoundTrip(req)
2828
}
2929
resourceName := rt.cfg.resourceNamer(req)
30+
// Make a copy of the URL so we don't modify the outgoing request
31+
url := *req.URL
32+
url.User = nil // Do not include userinfo in the HTTPURL tag.
3033
opts := []ddtrace.StartSpanOption{
3134
tracer.SpanType(ext.SpanTypeHTTP),
3235
tracer.ResourceName(resourceName),
3336
tracer.Tag(ext.HTTPMethod, req.Method),
34-
tracer.Tag(ext.HTTPURL, req.URL.String()),
37+
tracer.Tag(ext.HTTPURL, url.String()),
3538
tracer.Tag(ext.Component, "net/http"),
3639
tracer.Tag(ext.SpanKind, ext.SpanKindClient),
3740
}

contrib/net/http/roundtripper_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66
package http
77

88
import (
9+
"encoding/base64"
910
"fmt"
1011
"net/http"
1112
"net/http/httptest"
13+
"net/url"
14+
"strings"
1215
"testing"
1316
"time"
1417

1518
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/require"
1620

1721
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
1822
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
@@ -179,6 +183,53 @@ func TestRoundTripperNetworkError(t *testing.T) {
179183
assert.Equal(t, "net/http", s0.Tag(ext.Component))
180184
}
181185

186+
func TestRoundTripperCredentials(t *testing.T) {
187+
mt := mocktracer.Start()
188+
defer mt.Stop()
189+
190+
var auth string
191+
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
192+
if enc, ok := r.Header["Authorization"]; ok {
193+
encoded := strings.TrimPrefix(enc[0], "Basic ")
194+
if b64, err := base64.StdEncoding.DecodeString(encoded); err == nil {
195+
auth = string(b64)
196+
}
197+
}
198+
199+
}))
200+
defer s.Close()
201+
202+
rt := WrapRoundTripper(http.DefaultTransport,
203+
WithBefore(func(req *http.Request, span ddtrace.Span) {
204+
span.SetTag("CalledBefore", true)
205+
}),
206+
WithAfter(func(res *http.Response, span ddtrace.Span) {
207+
span.SetTag("CalledAfter", true)
208+
}))
209+
210+
client := &http.Client{
211+
Transport: rt,
212+
}
213+
214+
u, err := url.Parse(s.URL)
215+
require.NoError(t, err)
216+
u.User = url.UserPassword("myuser", "mypassword")
217+
218+
client.Get(u.String() + "/hello/world")
219+
220+
spans := mt.FinishedSpans()
221+
require.Len(t, spans, 1)
222+
223+
s1 := spans[0]
224+
225+
assert.Equal(t, s.URL+"/hello/world", s1.Tag(ext.HTTPURL))
226+
assert.NotContains(t, s1.Tag(ext.HTTPURL), "mypassword")
227+
assert.NotContains(t, s1.Tag(ext.HTTPURL), "myuser")
228+
// Make sure we haven't modified the outgoing request, and the server still
229+
// receives the auth request.
230+
assert.Equal(t, auth, "myuser:mypassword")
231+
}
232+
182233
func TestWrapClient(t *testing.T) {
183234
c := WrapClient(http.DefaultClient)
184235
assert.Equal(t, c, http.DefaultClient)

0 commit comments

Comments
 (0)