Skip to content

Commit

Permalink
Merge branch 'main' into mtoff/trace-log-directory
Browse files Browse the repository at this point in the history
  • Loading branch information
mtoffl01 authored Oct 7, 2024
2 parents a6ec3f9 + 2a34b15 commit a9d646d
Show file tree
Hide file tree
Showing 53 changed files with 1,889 additions and 324 deletions.
6 changes: 2 additions & 4 deletions .github/workflows/system-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,8 @@ jobs:
DD_API_KEY: ${{ secrets.DD_API_KEY }}
SYSTEM_TESTS_E2E_DD_API_KEY: ${{ secrets.SYSTEM_TESTS_E2E_DD_API_KEY }}
SYSTEM_TESTS_E2E_DD_APP_KEY: ${{ secrets.SYSTEM_TESTS_E2E_DD_APP_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.SYSTEM_TESTS_IDM_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SYSTEM_TESTS_IDM_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: us-east-1
AWS_DEFAULT_REGION: us-east-1 # AWS services should use `AWS_REGION`, but some still use the older `AWS_DEFAULT_REGION`
SYSTEM_TESTS_AWS_ACCESS_KEY_ID: ${{ secrets.SYSTEM_TESTS_IDM_AWS_ACCESS_KEY_ID }}
SYSTEM_TESTS_AWS_SECRET_ACCESS_KEY: ${{ secrets.SYSTEM_TESTS_IDM_AWS_SECRET_ACCESS_KEY }}
name: Test (${{ matrix.weblog-variant }}, ${{ matrix.scenario }})
steps:
- name: Checkout system tests
Expand Down
51 changes: 30 additions & 21 deletions appsec/appsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var appsecDisabledLog sync.Once
// Note that passing the raw bytes of the HTTP request body is not expected and would
// result in inaccurate attack detection.
// This function always returns nil when appsec is disabled.
func MonitorParsedHTTPBody(ctx context.Context, body interface{}) error {
func MonitorParsedHTTPBody(ctx context.Context, body any) error {
if !appsec.Enabled() {
appsecDisabledLog.Do(func() { log.Warn("appsec: not enabled. Body blocking checks won't be performed.") })
return nil
Expand All @@ -60,7 +60,15 @@ func SetUser(ctx context.Context, id string, opts ...tracer.UserMonitoringOption
appsecDisabledLog.Do(func() { log.Warn("appsec: not enabled. User blocking checks won't be performed.") })
return nil
}
return usersec.MonitorUser(ctx, id)

op, errPtr := usersec.StartUserLoginOperation(ctx, usersec.UserLoginOperationArgs{})
op.Finish(usersec.UserLoginOperationRes{
UserID: id,
SessionID: getSessionID(opts...),
Success: true,
})

return *errPtr
}

// TrackUserLoginSuccessEvent sets a successful user login event, with the given
Expand All @@ -76,17 +84,7 @@ func SetUser(ctx context.Context, id string, opts ...tracer.UserMonitoringOption
// Take-Over (ATO) monitoring, ultimately blocking the IP address and/or user id
// associated to them.
func TrackUserLoginSuccessEvent(ctx context.Context, uid string, md map[string]string, opts ...tracer.UserMonitoringOption) error {
span := getRootSpan(ctx)
if span == nil {
return nil
}

const tagPrefix = "appsec.events.users.login.success."
span.SetTag(tagPrefix+"track", true)
for k, v := range md {
span.SetTag(tagPrefix+k, v)
}
span.SetTag(ext.SamplingPriority, ext.PriorityUserKeep)
TrackCustomEvent(ctx, "users.login.success", md)
return SetUser(ctx, uid, opts...)
}

Expand All @@ -106,14 +104,15 @@ func TrackUserLoginFailureEvent(ctx context.Context, uid string, exists bool, md
return
}

const tagPrefix = "appsec.events.users.login.failure."
span.SetTag(tagPrefix+"track", true)
span.SetTag(tagPrefix+"usr.id", uid)
span.SetTag(tagPrefix+"usr.exists", exists)
for k, v := range md {
span.SetTag(tagPrefix+k, v)
}
span.SetTag(ext.SamplingPriority, ext.PriorityUserKeep)
// We need to do the first call to SetTag ourselves because the map taken by TrackCustomEvent is map[string]string
// and not map [string]any, so the `exists` boolean variable does not fit int
span.SetTag("appsec.events.users.login.failure.usr.exists", exists)
span.SetTag("appsec.events.users.login.failure.usr.id", uid)

TrackCustomEvent(ctx, "users.login.failure", md)

op, _ := usersec.StartUserLoginOperation(ctx, usersec.UserLoginOperationArgs{})
op.Finish(usersec.UserLoginOperationRes{UserID: uid, Success: false})
}

// TrackCustomEvent sets a custom event as service entry span tags. This span is
Expand Down Expand Up @@ -153,3 +152,13 @@ func getRootSpan(ctx context.Context) tracer.Span {
log.Error("appsec: could not access the root span")
return nil
}

func getSessionID(opts ...tracer.UserMonitoringOption) string {
cfg := &tracer.UserMonitoringConfig{
Metadata: make(map[string]string),
}
for _, opt := range opts {
opt(cfg)
}
return cfg.SessionID
}
73 changes: 39 additions & 34 deletions contrib/google.golang.org/grpc/appsec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net"
"testing"

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

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
Expand All @@ -24,26 +26,27 @@ import (
)

func TestAppSec(t *testing.T) {
t.Setenv("DD_APPSEC_WAF_TIMEOUT", "1h") // Functionally unlimited
appsec.Start()
defer appsec.Stop()
if !appsec.Enabled() {
t.Skip("appsec disabled")
}

setup := func() (FixtureClient, mocktracer.Tracer, func()) {
rig, err := newAppsecRig(false)
setup := func(t *testing.T) (FixtureClient, mocktracer.Tracer, func()) {
rig, err := newAppsecRig(t, false)
require.NoError(t, err)

mt := mocktracer.Start()

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

t.Run("unary", func(t *testing.T) {
client, mt, cleanup := setup()
client, mt, cleanup := setup(t)
defer cleanup()

// Send a XSS attack in the payload along with the canary value in the RPC metadata
Expand All @@ -64,7 +67,7 @@ func TestAppSec(t *testing.T) {
})

t.Run("stream", func(t *testing.T) {
client, mt, cleanup := setup()
client, mt, cleanup := setup(t)
defer cleanup()

// Send a XSS attack in the payload along with the canary value in the RPC metadata
Expand All @@ -73,8 +76,9 @@ func TestAppSec(t *testing.T) {
require.NoError(t, err)

// Send a XSS attack
err = stream.Send(&FixtureRequest{Name: "<script>window.location;</script>"})
require.NoError(t, err)
if err := stream.Send(&FixtureRequest{Name: "<script>window.location;</script>"}); err != io.EOF {
require.NoError(t, err)
}

// Check that the handler was properly called
res, err := stream.Recv()
Expand All @@ -83,8 +87,9 @@ func TestAppSec(t *testing.T) {

for i := 0; i < 5; i++ { // Fire multiple times, each time should result in a detected event
// Send a SQLi attack
err = stream.Send(&FixtureRequest{Name: fmt.Sprintf("-%[1]d' and %[1]d=%[1]d union select * from users--", i)})
require.NoError(t, err)
if err := stream.Send(&FixtureRequest{Name: fmt.Sprintf("-%[1]d' and %[1]d=%[1]d union select * from users--", i)}); err != io.EOF {
require.NoError(t, err)
}

// Check that the handler was properly called
res, err = stream.Recv()
Expand Down Expand Up @@ -121,9 +126,9 @@ func TestAppSec(t *testing.T) {
histogram[tr.Rule.ID]++
}

require.EqualValues(t, 1, histogram["crs-941-180"]) // XSS attack attempt
require.EqualValues(t, 5, histogram["crs-942-270"]) // SQL-injection attack attempt
require.EqualValues(t, 1, histogram["ua0-600-55x"]) // canary rule attack attempt
assert.EqualValues(t, 1, histogram["crs-941-180"]) // XSS attack attempt
assert.EqualValues(t, 5, histogram["crs-942-270"]) // SQL-injection attack attempt
assert.EqualValues(t, 1, histogram["ua0-600-55x"]) // canary rule attack attempt

require.Len(t, histogram, 3)
})
Expand All @@ -139,13 +144,13 @@ func TestBlocking(t *testing.T) {
}

setup := func() (FixtureClient, mocktracer.Tracer, func()) {
rig, err := newAppsecRig(false)
rig, err := newAppsecRig(t, false)
require.NoError(t, err)

mt := mocktracer.Start()

return rig.client, mt, func() {
rig.Close()
assert.NoError(t, rig.Close())
mt.Stop()
}
}
Expand Down Expand Up @@ -183,7 +188,7 @@ func TestBlocking(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
// Helper assertion function to run for the unary and stream tests
assert := func(t *testing.T, do func(client FixtureClient)) {
withClient := func(t *testing.T, do func(client FixtureClient)) {
client, mt, cleanup := setup()
defer cleanup()

Expand All @@ -204,7 +209,7 @@ func TestBlocking(t *testing.T) {
}

t.Run("unary", func(t *testing.T) {
assert(t, func(client FixtureClient) {
withClient(t, func(client FixtureClient) {
ctx := metadata.NewOutgoingContext(context.Background(), tc.md)
reply, err := client.Ping(ctx, &FixtureRequest{Name: tc.message})
require.Nil(t, reply)
Expand All @@ -213,19 +218,18 @@ func TestBlocking(t *testing.T) {
})

t.Run("stream", func(t *testing.T) {
assert(t, func(client FixtureClient) {
withClient(t, func(client FixtureClient) {
ctx := metadata.NewOutgoingContext(context.Background(), tc.md)

// Open the stream
stream, err := client.StreamPing(ctx)
require.NoError(t, err)
defer func() {
require.NoError(t, stream.CloseSend())
}()
defer func() { assert.NoError(t, stream.CloseSend()) }()

// Send a message
err = stream.Send(&FixtureRequest{Name: tc.message})
require.NoError(t, err)
if err := stream.Send(&FixtureRequest{Name: tc.message}); err != io.EOF {
require.NoError(t, err)
}

// Receive a message
reply, err := stream.Recv()
Expand All @@ -249,20 +253,20 @@ func TestPasslist(t *testing.T) {
t.Skip("appsec disabled")
}

setup := func() (FixtureClient, mocktracer.Tracer, func()) {
rig, err := newAppsecRig(false)
setup := func(t *testing.T) (FixtureClient, mocktracer.Tracer, func()) {
rig, err := newAppsecRig(t, false)
require.NoError(t, err)

mt := mocktracer.Start()

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

t.Run("unary", func(t *testing.T) {
client, mt, cleanup := setup()
client, mt, cleanup := setup(t)
defer cleanup()

// Send the payload triggering the sec event thanks to the "zouzou" value in the RPC metadata
Expand All @@ -284,7 +288,7 @@ func TestPasslist(t *testing.T) {
})

t.Run("stream", func(t *testing.T) {
client, mt, cleanup := setup()
client, mt, cleanup := setup(t)
defer cleanup()

// Open the steam triggering the sec event thanks to the "zouzou" value in the RPC metadata
Expand All @@ -294,8 +298,9 @@ func TestPasslist(t *testing.T) {

// Send some messages
for i := 0; i < 5; i++ {
err = stream.Send(&FixtureRequest{Name: "hello"})
require.NoError(t, err)
if err := stream.Send(&FixtureRequest{Name: "hello"}); err != io.EOF {
require.NoError(t, err)
}

// Check that the handler was properly called
res, err := stream.Recv()
Expand All @@ -319,7 +324,7 @@ func TestPasslist(t *testing.T) {
})
}

func newAppsecRig(traceClient bool, interceptorOpts ...Option) (*appsecRig, error) {
func newAppsecRig(t *testing.T, traceClient bool, interceptorOpts ...Option) (*appsecRig, error) {
interceptorOpts = append([]InterceptorOption{WithServiceName("grpc")}, interceptorOpts...)

server := grpc.NewServer(
Expand All @@ -336,7 +341,7 @@ func newAppsecRig(traceClient bool, interceptorOpts ...Option) (*appsecRig, erro
}
_, port, _ := net.SplitHostPort(li.Addr().String())
// start our test fixtureServer.
go server.Serve(li)
go func() { assert.NoError(t, server.Serve(li)) }()

opts := []grpc.DialOption{grpc.WithInsecure()}
if traceClient {
Expand Down Expand Up @@ -370,9 +375,9 @@ type appsecRig struct {
client FixtureClient
}

func (r *appsecRig) Close() {
r.server.Stop()
r.conn.Close()
func (r *appsecRig) Close() error {
defer r.server.GracefulStop()
return r.conn.Close()
}

type appsecFixtureServer struct {
Expand Down
Loading

0 comments on commit a9d646d

Please sign in to comment.