Skip to content

Commit

Permalink
contrib/gorilla/mux: add support for http.request.path_params address
Browse files Browse the repository at this point in the history
Add an AppSecParam field to router trace config to pass parameters
to AppSec wrap handler that can't be retrieved from request only.
This allows in this case to pass down path parameters.
Also add waf_test.go to check e2e soundness.
  • Loading branch information
Hellzy committed Dec 27, 2021
1 parent 00f6f88 commit 0b84c4e
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 3 deletions.
5 changes: 5 additions & 0 deletions contrib/gorilla/mux/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"

"github.com/gorilla/mux"
Expand Down Expand Up @@ -115,6 +116,7 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if r.config.headerTags {
spanopts = append(spanopts, headerTagsFromRequest(req))
}

resource := r.config.resourceNamer(r, req)
httputil.TraceAndServe(r.Router, &httputil.TraceConfig{
ResponseWriter: w,
Expand All @@ -124,6 +126,9 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
FinishOpts: r.config.finishOpts,
SpanOpts: spanopts,
QueryParams: r.config.queryParams,
AppSecParams: httpsec.AppSecParams{
PathParams: match.Vars,
},
})
}

Expand Down
68 changes: 68 additions & 0 deletions contrib/gorilla/mux/waf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// 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 2016 Datadog, Inc.

//go:build appsec
// +build appsec

package mux_test

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"

muxtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/gorilla/mux"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec"

"github.com/stretchr/testify/require"
)

func TestAppSec(t *testing.T) {

t.Run("path-params", func(t *testing.T) {
// Start the tracer along with the fake agent HTTP server
mt := mocktracer.Start()
defer mt.Stop()

appsec.Start()
defer appsec.Stop()

if !appsec.Enabled() {
t.Skip("appsec disabled")
}

// Start and trace an HTTP server
router := muxtrace.NewRouter()
router.Handle("/{pathParam}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK!\n"))
}))

srv := httptest.NewServer(router)
defer srv.Close()

// Forge HTTP request with path parameter
req, err := http.NewRequest("POST", srv.URL+"/appscan_fingerprint", nil)
if err != nil {
panic(err)
}

res, err := srv.Client().Do(req)
require.NoError(t, err)

// Check that the handler was properly called
b, err := ioutil.ReadAll(res.Body)
require.NoError(t, err)
require.Equal(t, "OK!\n", string(b))

spans := mt.FinishedSpans()
// The request should have the attack attempt event (appsec rule id crs-913-120).
event := spans[0].Tag("_dd.appsec.json")
require.NotNil(t, event)
require.True(t, strings.Contains(event.(string), "crs-913-120"))
})
}
3 changes: 2 additions & 1 deletion contrib/internal/httputil/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type TraceConfig struct {
QueryParams bool // specifies that request query parameters should be appended to http.url tag
FinishOpts []ddtrace.FinishOption // span finish options to be applied
SpanOpts []ddtrace.StartSpanOption // additional span options to be applied
AppSecParams httpsec.AppSecParams // extra parameters that AppSec may need while wrapping the request handler
}

// TraceAndServe will apply tracing to the given http.Handler using the passed tracer under the given service and resource.
Expand Down Expand Up @@ -55,7 +56,7 @@ func TraceAndServe(h http.Handler, cfg *TraceConfig) {
defer span.Finish(cfg.FinishOpts...)

if appsec.Enabled() {
h = httpsec.WrapHandler(h, span)
h = httpsec.WrapHandler(h, span, cfg.AppSecParams)
}
h.ServeHTTP(wrapResponseWriter(cfg.ResponseWriter, span), cfg.Request.WithContext(ctx))
}
Expand Down
10 changes: 8 additions & 2 deletions internal/appsec/dyngo/instrumentation/httpsec/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,23 @@ type (
// Status corresponds to the address `server.response.status`
Status int
}

// AppSecParams is the extra parameters that need to be passed over to the AppSec WrapHandler (i.e params
// not retrievable from the http.Request, such as path parameters)
AppSecParams struct {
PathParams map[string]string
}
)

// WrapHandler wraps the given HTTP handler with the abstract HTTP operation defined by HandlerOperationArgs and
// HandlerOperationRes.
func WrapHandler(handler http.Handler, span ddtrace.Span) http.Handler {
func WrapHandler(handler http.Handler, span ddtrace.Span, params AppSecParams) http.Handler {
// TODO(Julio-Guerra): move these to service entry tags
span.SetTag("_dd.appsec.enabled", 1)
span.SetTag("_dd.runtime_family", "go")

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
args := MakeHandlerOperationArgs(r, nil)
args := MakeHandlerOperationArgs(r, params.PathParams)
op := StartOperation(
args,
nil,
Expand Down

0 comments on commit 0b84c4e

Please sign in to comment.