Skip to content

Commit

Permalink
Merge branch 'v1' into mackjmr/echo-no-debug-stack
Browse files Browse the repository at this point in the history
  • Loading branch information
knusbaum committed Jan 14, 2022
2 parents cb528da + a4018e0 commit 50dadd2
Show file tree
Hide file tree
Showing 30 changed files with 1,074 additions and 53 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Expand Up @@ -14,3 +14,4 @@

# appsec
/internal/appsec @DataDog/appsec-go
/contrib/**/appsec.go @DataDog/appsec-go
4 changes: 4 additions & 0 deletions contrib/go-chi/chi.v5/chi.go
Expand Up @@ -31,6 +31,10 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler {
log.Debug("contrib/go-chi/chi.v5: Configuring Middleware: %#v", cfg)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.ignoreRequest(r) {
next.ServeHTTP(w, r)
return
}
opts := []ddtrace.StartSpanOption{
tracer.SpanType(ext.SpanTypeWeb),
tracer.ServiceName(cfg.serviceName),
Expand Down
31 changes: 31 additions & 0 deletions contrib/go-chi/chi.v5/chi_test.go
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
Expand Down Expand Up @@ -270,3 +271,33 @@ func TestAnalyticsSettings(t *testing.T) {
assertRate(t, mt, 0.23, WithAnalyticsRate(0.23))
})
}

func TestIgnoreRequest(t *testing.T) {
router := chi.NewRouter()
router.Use(Middleware(
WithIgnoreRequest(func(r *http.Request) bool {
return strings.HasPrefix(r.URL.Path, "/skip")
}),
))

router.Get("/ok", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})

router.Get("/skip", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("skip"))
})

for path, shouldSkip := range map[string]bool{
"/ok": false,
"/skip": true,
"/skipfoo": true,
} {
mt := mocktracer.Start()
defer mt.Reset()

r := httptest.NewRequest("GET", "http://localhost"+path, nil)
router.ServeHTTP(httptest.NewRecorder(), r)
assert.Equal(t, shouldSkip, len(mt.FinishedSpans()) == 0)
}
}
11 changes: 11 additions & 0 deletions contrib/go-chi/chi.v5/option.go
Expand Up @@ -7,6 +7,7 @@ package chi

import (
"math"
"net/http"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
Expand All @@ -18,6 +19,7 @@ type config struct {
spanOpts []ddtrace.StartSpanOption // additional span options to be applied
analyticsRate float64
isStatusError func(statusCode int) bool
ignoreRequest func(r *http.Request) bool
}

// Option represents an option that can be passed to NewRouter.
Expand All @@ -34,6 +36,7 @@ func defaults(cfg *config) {
cfg.analyticsRate = globalconfig.AnalyticsRate()
}
cfg.isStatusError = isServerError
cfg.ignoreRequest = func(_ *http.Request) bool { return false }
}

// WithServiceName sets the given service name for the router.
Expand Down Expand Up @@ -85,3 +88,11 @@ func WithStatusCheck(fn func(statusCode int) bool) Option {
func isServerError(statusCode int) bool {
return statusCode >= 500 && statusCode < 600
}

// WithIgnoreRequest specifies a function to use for determining if the
// incoming HTTP request tracing should be skipped.
func WithIgnoreRequest(fn func(r *http.Request) bool) Option {
return func(cfg *config) {
cfg.ignoreRequest = fn
}
}
4 changes: 4 additions & 0 deletions contrib/go-chi/chi/chi.go
Expand Up @@ -31,6 +31,10 @@ func Middleware(opts ...Option) func(next http.Handler) http.Handler {
log.Debug("contrib/go-chi/chi: Configuring Middleware: %#v", cfg)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg.ignoreRequest(r) {
next.ServeHTTP(w, r)
return
}
opts := []ddtrace.StartSpanOption{
tracer.SpanType(ext.SpanTypeWeb),
tracer.ServiceName(cfg.serviceName),
Expand Down
31 changes: 31 additions & 0 deletions contrib/go-chi/chi/chi_test.go
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/ext"
Expand Down Expand Up @@ -270,3 +271,33 @@ func TestAnalyticsSettings(t *testing.T) {
assertRate(t, mt, 0.23, WithAnalyticsRate(0.23))
})
}

func TestIgnoreRequest(t *testing.T) {
router := chi.NewRouter()
router.Use(Middleware(
WithIgnoreRequest(func(r *http.Request) bool {
return strings.HasPrefix(r.URL.Path, "/skip")
}),
))

router.Get("/ok", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})

router.Get("/skip", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("skip"))
})

for path, shouldSkip := range map[string]bool{
"/ok": false,
"/skip": true,
"/skipfoo": true,
} {
mt := mocktracer.Start()
defer mt.Reset()

r := httptest.NewRequest("GET", "http://localhost"+path, nil)
router.ServeHTTP(httptest.NewRecorder(), r)
assert.Equal(t, shouldSkip, len(mt.FinishedSpans()) == 0)
}
}
11 changes: 11 additions & 0 deletions contrib/go-chi/chi/option.go
Expand Up @@ -7,6 +7,7 @@ package chi

import (
"math"
"net/http"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/internal"
Expand All @@ -18,6 +19,7 @@ type config struct {
spanOpts []ddtrace.StartSpanOption // additional span options to be applied
analyticsRate float64
isStatusError func(statusCode int) bool
ignoreRequest func(r *http.Request) bool
}

// Option represents an option that can be passed to NewRouter.
Expand All @@ -34,6 +36,7 @@ func defaults(cfg *config) {
cfg.analyticsRate = globalconfig.AnalyticsRate()
}
cfg.isStatusError = isServerError
cfg.ignoreRequest = func(_ *http.Request) bool { return false }
}

// WithServiceName sets the given service name for the router.
Expand Down Expand Up @@ -85,3 +88,11 @@ func WithStatusCheck(fn func(statusCode int) bool) Option {
func isServerError(statusCode int) bool {
return statusCode >= 500 && statusCode < 600
}

// WithIgnoreRequest specifies a function to use for determining if the
// incoming HTTP request tracing should be skipped.
func WithIgnoreRequest(fn func(r *http.Request) bool) Option {
return func(cfg *config) {
cfg.ignoreRequest = fn
}
}
78 changes: 78 additions & 0 deletions contrib/google.golang.org/grpc/appsec.go
@@ -0,0 +1,78 @@
// 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.

package grpc

import (
"encoding/json"
"net"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/grpcsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/dyngo/instrumentation/httpsec"

"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)

// UnaryHandler wrapper to use when AppSec is enabled to monitor its execution.
func appsecUnaryHandlerMiddleware(span ddtrace.Span, handler grpc.UnaryHandler) grpc.UnaryHandler {
httpsec.SetAppSecTags(span)
return func(ctx context.Context, req interface{}) (interface{}, error) {
op := grpcsec.StartHandlerOperation(grpcsec.HandlerOperationArgs{}, nil)
defer func() {
events := op.Finish(grpcsec.HandlerOperationRes{})
if len(events) == 0 {
return
}
setAppSecTags(ctx, span, events)
}()
defer grpcsec.StartReceiveOperation(grpcsec.ReceiveOperationArgs{}, op).Finish(grpcsec.ReceiveOperationRes{Message: req})
return handler(ctx, req)
}
}

// StreamHandler wrapper to use when AppSec is enabled to monitor its execution.
func appsecStreamHandlerMiddleware(span ddtrace.Span, handler grpc.StreamHandler) grpc.StreamHandler {
httpsec.SetAppSecTags(span)
return func(srv interface{}, stream grpc.ServerStream) error {
op := grpcsec.StartHandlerOperation(grpcsec.HandlerOperationArgs{}, nil)
defer func() {
events := op.Finish(grpcsec.HandlerOperationRes{})
if len(events) == 0 {
return
}
setAppSecTags(stream.Context(), span, events)
}()
return handler(srv, appsecServerStream{ServerStream: stream, handlerOperation: op})
}
}

type appsecServerStream struct {
grpc.ServerStream
handlerOperation *grpcsec.HandlerOperation
}

// RecvMsg implements grpc.ServerStream interface method to monitor its
// execution with AppSec.
func (ss appsecServerStream) RecvMsg(m interface{}) error {
op := grpcsec.StartReceiveOperation(grpcsec.ReceiveOperationArgs{}, ss.handlerOperation)
defer func() {
op.Finish(grpcsec.ReceiveOperationRes{Message: m})
}()
return ss.ServerStream.RecvMsg(m)
}

// Set the AppSec tags when security events were found.
func setAppSecTags(ctx context.Context, span ddtrace.Span, events []json.RawMessage) {
md, _ := metadata.FromIncomingContext(ctx)
var addr net.Addr
if p, ok := peer.FromContext(ctx); ok {
addr = p.Addr
}
grpcsec.SetSecurityEventTags(span, events, addr, md)
}
91 changes: 91 additions & 0 deletions contrib/google.golang.org/grpc/appsec_test.go
@@ -0,0 +1,91 @@
// 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.

package grpc

import (
"context"
"strings"
"testing"

"github.com/stretchr/testify/require"

"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/mocktracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec"
)

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

rig, err := newRig(false)
require.NoError(t, err)
defer rig.Close()

client := rig.client

t.Run("unary", func(t *testing.T) {
mt := mocktracer.Start()
defer mt.Stop()

// Send a XSS attack
res, err := client.Ping(context.Background(), &FixtureRequest{Name: "<script>alert('xss');</script>"})
// Check that the handler was properly called
require.NoError(t, err)
require.Equal(t, "passed", res.Message)

finished := mt.FinishedSpans()
require.Len(t, finished, 1)

// The request should have the XSS attack attempt event (appsec rule id crs-941-100).
event := finished[0].Tag("_dd.appsec.json")
require.NotNil(t, event)
require.True(t, strings.Contains(event.(string), "crs-941-100"))
})

t.Run("stream", func(t *testing.T) {
mt := mocktracer.Start()
defer mt.Stop()

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

// Send a XSS attack
err = stream.Send(&FixtureRequest{Name: "<script>alert('xss');</script>"})
require.NoError(t, err)

// Check that the handler was properly called
res, err := stream.Recv()
require.Equal(t, "passed", res.Message)
require.NoError(t, err)

// Send a SQLi attack
err = stream.Send(&FixtureRequest{Name: "something UNION SELECT * from users"})
require.NoError(t, err)

// Check that the handler was properly called
res, err = stream.Recv()
require.Equal(t, "passed", res.Message)
require.NoError(t, err)

err = stream.CloseSend()
require.NoError(t, err)
// to flush the spans
stream.Recv()

finished := mt.FinishedSpans()
require.Len(t, finished, 6)

// The request should both attacks: the XSS and SQLi attack attempt
// events (appsec rule id crs-941-100, crs-942-100).
event := finished[5].Tag("_dd.appsec.json")
require.NotNil(t, event)
require.True(t, strings.Contains(event.(string), "crs-941-100"))
require.True(t, strings.Contains(event.(string), "crs-942-100"))
})
}

0 comments on commit 50dadd2

Please sign in to comment.