Skip to content

Commit

Permalink
Merge branch 'main' into mtoff/dogstatsd-host
Browse files Browse the repository at this point in the history
  • Loading branch information
mtoffl01 authored Jan 17, 2025
2 parents 48adfed + f5b85c2 commit 5ad10a1
Show file tree
Hide file tree
Showing 49 changed files with 1,761 additions and 2,320 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/apps/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/Masterminds/semver/v3 v3.3.1
github.com/stretchr/testify v1.10.0
golang.org/x/mod v0.22.0
gopkg.in/DataDog/dd-trace-go.v1 v1.70.1
gopkg.in/DataDog/dd-trace-go.v1 v1.70.3
)

require (
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/apps/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
gopkg.in/DataDog/dd-trace-go.v1 v1.70.1 h1:ZIRxAKlr3xr6xbMUDs3IDa6xq+ISv9zxyjaDCfwDjMY=
gopkg.in/DataDog/dd-trace-go.v1 v1.70.1/go.mod h1:PMOSkeY4VfXiuPvGodeNLCZCFYU2VfOvjVI6cX5bGrc=
gopkg.in/DataDog/dd-trace-go.v1 v1.70.3 h1:lXHrxMpQZjxNdA8mGRfgMtwF/O6qIut5QjL7LICUVJ4=
gopkg.in/DataDog/dd-trace-go.v1 v1.70.3/go.mod h1:CVUgctrrPGeB+OSjgyt56CNH5QxQwW3t11QU8R1LQjQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/system-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ jobs:
scenario: APPSEC_LOW_WAF_TIMEOUT
- weblog-variant: net-http
scenario: APPSEC_STANDALONE
- weblog-variant: net-http
scenario: APPSEC_META_STRUCT_DISABLED
- weblog-variant: net-http
scenario: APPSEC_CUSTOM_OBFUSCATION
# APM scenarios requiring specific environment settings
Expand Down
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ linters:
disable-all: true
enable:
- gofmt
- govet
- revive
- bodyclose
5 changes: 5 additions & 0 deletions contrib/IBM/sarama.v1/consumer_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package sarama
import (
"context"
"log"
"os"
"sync"
"testing"

Expand All @@ -20,6 +21,10 @@ import (
)

func TestWrapConsumerGroupHandler(t *testing.T) {
if _, ok := os.LookupEnv("INTEGRATION"); !ok {
t.Skip("🚧 Skipping integration test (INTEGRATION environment variable is not set)")
}

mt := mocktracer.Start()
defer mt.Stop()

Expand Down
8 changes: 8 additions & 0 deletions contrib/envoyproxy/go-control-plane/envoy.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,14 @@ func propagationRequestHeaderMutation(span ddtrace.Span) (*envoyextproc.Processi
func processResponseHeaders(res *envoyextproc.ProcessingRequest_ResponseHeaders, currentRequest *currentRequest) (*envoyextproc.ProcessingResponse, error) {
log.Debug("external_processing: received response headers: %v\n", res.ResponseHeaders)

if currentRequest == nil {
// Can happen when a malformed request is sent to Envoy (with no header), the request is never sent to the External Processor and directly passed to the server
// However the response of the server (which is valid) is sent to the External Processor and fail to be processed
log.Warn("external_processing: can't process the response: envoy never sent the beginning of the request, this is a known issue" +
" and can happen when a malformed request is sent to Envoy where the header Host is missing. See link to issue https://github.com/envoyproxy/envoy/issues/38022")
return nil, status.Errorf(codes.InvalidArgument, "Error processing response headers from ext_proc: can't process the response")
}

if err := createFakeResponseWriter(currentRequest.wrappedResponseWriter, res); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "Error processing response headers from ext_proc: %v", err)
}
Expand Down
47 changes: 47 additions & 0 deletions contrib/envoyproxy/go-control-plane/envoy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,53 @@ func TestXForwardedForHeaderClientIp(t *testing.T) {
})
}

func TestMalformedEnvoyProcessing(t *testing.T) {
appsec.Start()
defer appsec.Stop()
if !appsec.Enabled() {
t.Skip("appsec disabled")
}

setup := func() (envoyextproc.ExternalProcessorClient, mocktracer.Tracer, func()) {
rig, err := newEnvoyAppsecRig(t, false)
require.NoError(t, err)

mt := mocktracer.Start()

return rig.client, mt, func() {
rig.Close()
mt.Stop()
}
}

t.Run("response-received-without-request", func(t *testing.T) {
client, mt, cleanup := setup()
defer cleanup()

ctx := context.Background()
stream, err := client.Process(ctx)
require.NoError(t, err)

err = stream.Send(&envoyextproc.ProcessingRequest{
Request: &envoyextproc.ProcessingRequest_ResponseHeaders{
ResponseHeaders: &envoyextproc.HttpHeaders{
Headers: makeResponseHeaders(t, map[string]string{}, "400"),
},
},
})
require.NoError(t, err)

_, err = stream.Recv()
require.Error(t, err)
stream.Recv()

// No span created, the request is invalid.
// Span couldn't be created without request data
finished := mt.FinishedSpans()
require.Len(t, finished, 0)
})
}

func newEnvoyAppsecRig(t *testing.T, traceClient bool, interceptorOpts ...ddgrpc.Option) (*envoyAppsecRig, error) {
t.Helper()

Expand Down
8 changes: 4 additions & 4 deletions contrib/google.golang.org/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestBooks(t *testing.T) {
assert.Equal(t, "books.bookshelves.list", s0.Tag(ext.ResourceName))
assert.Equal(t, "400", s0.Tag(ext.HTTPCode))
assert.Equal(t, "GET", s0.Tag(ext.HTTPMethod))
assert.Equal(t, svc.BasePath+"books/v1/users/montana.banana/bookshelves", s0.Tag(ext.HTTPURL))
assert.Equal(t, svc.BasePath+"books/v1/users/montana.banana/bookshelves?alt=json&prettyPrint=false", s0.Tag(ext.HTTPURL))
assert.Equal(t, "google.golang.org/api", s0.Tag(ext.Component))
assert.Equal(t, ext.SpanKindClient, s0.Tag(ext.SpanKind))
}
Expand All @@ -88,7 +88,7 @@ func TestCivicInfo(t *testing.T) {
assert.Equal(t, "civicinfo.representatives.representativeInfoByAddress", s0.Tag(ext.ResourceName))
assert.Equal(t, "400", s0.Tag(ext.HTTPCode))
assert.Equal(t, "GET", s0.Tag(ext.HTTPMethod))
assert.Equal(t, svc.BasePath+"civicinfo/v2/representatives", s0.Tag(ext.HTTPURL))
assert.Equal(t, svc.BasePath+"civicinfo/v2/representatives?alt=json&prettyPrint=false", s0.Tag(ext.HTTPURL))
assert.Equal(t, "google.golang.org/api", s0.Tag(ext.Component))
assert.Equal(t, ext.SpanKindClient, s0.Tag(ext.SpanKind))
}
Expand All @@ -115,7 +115,7 @@ func TestURLShortener(t *testing.T) {
assert.Equal(t, "urlshortener.url.list", s0.Tag(ext.ResourceName))
assert.Equal(t, "400", s0.Tag(ext.HTTPCode))
assert.Equal(t, "GET", s0.Tag(ext.HTTPMethod))
assert.Equal(t, "https://www.googleapis.com/urlshortener/v1/url/history", s0.Tag(ext.HTTPURL))
assert.Equal(t, "https://www.googleapis.com/urlshortener/v1/url/history?alt=json&prettyPrint=false", s0.Tag(ext.HTTPURL))
assert.Equal(t, "google.golang.org/api", s0.Tag(ext.Component))
assert.Equal(t, ext.SpanKindClient, s0.Tag(ext.SpanKind))
}
Expand All @@ -140,7 +140,7 @@ func TestWithEndpointMetadataDisabled(t *testing.T) {
assert.Equal(t, "GET civicinfo.googleapis.com", s0.Tag(ext.ResourceName))
assert.Equal(t, "400", s0.Tag(ext.HTTPCode))
assert.Equal(t, "GET", s0.Tag(ext.HTTPMethod))
assert.Equal(t, svc.BasePath+"civicinfo/v2/representatives", s0.Tag(ext.HTTPURL))
assert.Equal(t, svc.BasePath+"civicinfo/v2/representatives?alt=json&prettyPrint=false", s0.Tag(ext.HTTPURL))
assert.Equal(t, "google.golang.org/api", s0.Tag(ext.Component))
assert.Equal(t, ext.SpanKindClient, s0.Tag(ext.SpanKind))
}
Expand Down
22 changes: 13 additions & 9 deletions contrib/internal/httptrace/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,33 @@ func ResetCfg() {
func newConfig() config {
c := config{
queryString: !internal.BoolEnv(envQueryStringDisabled, false),
queryStringRegexp: defaultQueryStringRegexp,
queryStringRegexp: QueryStringRegexp(),
traceClientIP: internal.BoolEnv(envTraceClientIPEnabled, false),
isStatusError: isServerError,
}
v := os.Getenv(envServerErrorStatuses)
if fn := GetErrorCodesFromInput(v); fn != nil {
c.isStatusError = fn
}
return c
}

func isServerError(statusCode int) bool {
return statusCode >= 500 && statusCode < 600
}

func QueryStringRegexp() *regexp.Regexp {
if s, ok := os.LookupEnv(envQueryStringRegexp); !ok {
return c
return defaultQueryStringRegexp
} else if s == "" {
c.queryStringRegexp = nil
log.Debug("%s is set but empty. Query string obfuscation will be disabled.", envQueryStringRegexp)
return nil
} else if r, err := regexp.Compile(s); err == nil {
c.queryStringRegexp = r
return r
} else {
log.Error("Could not compile regexp from %s. Using default regexp instead.", envQueryStringRegexp)
return defaultQueryStringRegexp
}
return c
}

func isServerError(statusCode int) bool {
return statusCode >= 500 && statusCode < 600
}

// GetErrorCodesFromInput parses a comma-separated string s to determine which codes are to be considered errors
Expand Down
39 changes: 20 additions & 19 deletions contrib/internal/httptrace/httptrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,27 @@ func StartRequestSpan(r *http.Request, opts ...ddtrace.StartSpanOption) (tracer.
}
nopts := make([]ddtrace.StartSpanOption, 0, len(opts)+1+len(ipTags))
nopts = append(nopts,
func(cfg *ddtrace.StartSpanConfig) {
if cfg.Tags == nil {
cfg.Tags = make(map[string]interface{})
func(ssCfg *ddtrace.StartSpanConfig) {
if ssCfg.Tags == nil {
ssCfg.Tags = make(map[string]interface{})
}
cfg.Tags[ext.SpanType] = ext.SpanTypeWeb
cfg.Tags[ext.HTTPMethod] = r.Method
cfg.Tags[ext.HTTPURL] = urlFromRequest(r)
cfg.Tags[ext.HTTPUserAgent] = r.UserAgent()
cfg.Tags["_dd.measured"] = 1
ssCfg.Tags[ext.SpanType] = ext.SpanTypeWeb
ssCfg.Tags[ext.HTTPMethod] = r.Method
ssCfg.Tags[ext.HTTPURL] = UrlFromRequest(r, cfg.queryString)
ssCfg.Tags[ext.HTTPUserAgent] = r.UserAgent()
ssCfg.Tags["_dd.measured"] = 1
if r.Host != "" {
cfg.Tags["http.host"] = r.Host
ssCfg.Tags["http.host"] = r.Host
}
if spanctx, err := tracer.Extract(tracer.HTTPHeadersCarrier(r.Header)); err == nil {
// If there are span links as a result of context extraction, add them as a StartSpanOption
if linksCtx, ok := spanctx.(ddtrace.SpanContextWithLinks); ok && linksCtx.SpanLinks() != nil {
tracer.WithSpanLinks(linksCtx.SpanLinks())(cfg)
tracer.WithSpanLinks(linksCtx.SpanLinks())(ssCfg)
}
tracer.ChildOf(spanctx)(cfg)
tracer.ChildOf(spanctx)(ssCfg)
}
for k, v := range ipTags {
cfg.Tags[k] = v
ssCfg.Tags[k] = v
}
})
nopts = append(nopts, opts...)
Expand Down Expand Up @@ -93,18 +93,19 @@ func FinishRequestSpan(s tracer.Span, status int, errorFn func(int) bool, opts .
s.Finish(opts...)
}

// urlFromRequest returns the full URL from the HTTP request. If query params are collected, they are obfuscated granted
// obfuscation is not disabled by the user (through DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP)
// See https://docs.datadoghq.com/tracing/configure_data_security#redacting-the-query-in-the-url for more information.
func urlFromRequest(r *http.Request) string {
// UrlFromRequest returns the full URL from the HTTP request. If queryString is true, params are collected and they are obfuscated either by the default query string obfuscator or the custom obfuscator provided by the user (through DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP)
// See https://docs.datadoghq.com/tracing/configure_data_security/?tab=net#redact-query-strings for more information.
func UrlFromRequest(r *http.Request, queryString bool) string {
// Quoting net/http comments about net.Request.URL on server requests:
// "For most requests, fields other than Path and RawQuery will be
// empty. (See RFC 7230, Section 5.3)"
// This is why we don't rely on url.URL.String(), url.URL.Host, url.URL.Scheme, etc...
// This is why we can't rely entirely on url.URL.String(), url.URL.Host, url.URL.Scheme, etc...
var url string
path := r.URL.EscapedPath()
scheme := "http"
if r.TLS != nil {
if s := r.URL.Scheme; s != "" {
scheme = s
} else if r.TLS != nil {
scheme = "https"
}
if r.Host != "" {
Expand All @@ -113,7 +114,7 @@ func urlFromRequest(r *http.Request) string {
url = path
}
// Collect the query string if we are allowed to report it and obfuscate it if possible/allowed
if cfg.queryString && r.URL.RawQuery != "" {
if queryString && r.URL.RawQuery != "" {
query := r.URL.RawQuery
if cfg.queryStringRegexp != nil {
query = cfg.queryStringRegexp.ReplaceAllLiteralString(query, "<redacted>")
Expand Down
2 changes: 1 addition & 1 deletion contrib/internal/httptrace/httptrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ func TestURLTag(t *testing.T) {
if tc.port != "" {
r.Host += ":" + tc.port
}
url := urlFromRequest(&r)
url := UrlFromRequest(&r, true)
require.Equal(t, tc.expectedURL, url)
})
}
Expand Down
2 changes: 1 addition & 1 deletion contrib/jackc/pgx.v5/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func ExampleConnect() {
}
}

func ExamplePool() {
func ExampleNewPool() {
ctx := context.TODO()

// The pgxpool uses the same tracer and is exposed the same way.
Expand Down
1 change: 0 additions & 1 deletion contrib/labstack/echo.v4/appsec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ func TestControlFlow(t *testing.T) {
},
handler: func(echo.Context) error {
panic("unexpected control flow")
return nil
},
test: func(t *testing.T, rec *httptest.ResponseRecorder, mt mocktracer.Tracer, err error) {
require.Error(t, err)
Expand Down
26 changes: 26 additions & 0 deletions contrib/net/http/get_pattern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025 Datadog, Inc.

//go:build go1.23

package http

import (
"net/http"
)

// getPattern returns the pattern associated with the request or the route if no wildcard is used
func getPattern(mux *http.ServeMux, r *http.Request) string {
if r.Pattern != "" { // Will not be available if the user uses NewServeMux
return r.Pattern
}

if mux == nil { // Will not be available if the user uses WrapHandler
return ""
}

_, pattern := mux.Handler(r)
return pattern
}
21 changes: 21 additions & 0 deletions contrib/net/http/get_pattern_go122.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2025 Datadog, Inc.

//go:build go1.22 && !go1.23

package http

import (
"net/http"
)

func getPattern(mux *http.ServeMux, r *http.Request) string {
if mux == nil { // Will not be available if the user uses WrapHandler
return ""
}

_, pattern := mux.Handler(r)
return pattern
}
Loading

0 comments on commit 5ad10a1

Please sign in to comment.