Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal/appsec: refactor listeners #2862

Merged
merged 25 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions appsec/appsec.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/httpsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/sharedsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/usersec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
)

Expand Down Expand Up @@ -60,7 +60,7 @@ 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 sharedsec.MonitorUser(ctx, id)
return usersec.MonitorUser(ctx, id)
}

// TrackUserLoginSuccessEvent sets a successful user login event, with the given
Expand Down
54 changes: 13 additions & 41 deletions contrib/99designs/gqlgen/appsec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,29 +58,20 @@ func TestAppSec(t *testing.T) {
testCases := map[string]struct {
query string
variables map[string]any
events map[string]string
}{
"basic": {
query: `query TestQuery($topLevelId: String!, $nestedId: String!) { topLevel(id: $topLevelId) { nested(id: $nestedId) } }`,
variables: map[string]any{
"topLevelId": topLevelAttack,
"nestedId": nestedAttack,
},
events: map[string]string{
"test-rule-001": "graphql.resolve(topLevel)",
"test-rule-002": "graphql.resolve(nested)",
},
},
"with-default-parameter": {
query: fmt.Sprintf(`query TestQuery($topLevelId: String = %#v, $nestedId: String!) { topLevel(id: $topLevelId) { nested(id: $nestedId) } }`, topLevelAttack),
variables: map[string]any{
// "topLevelId" omitted (default value used)
"nestedId": nestedAttack,
},
events: map[string]string{
"test-rule-001": "graphql.resolve(topLevel)",
"test-rule-002": "graphql.resolve(nested)",
},
},
"embedded-variable": {
query: `query TestQuery($topLevelId: String!, $nestedId: String!) {
Expand All @@ -92,10 +83,6 @@ func TestAppSec(t *testing.T) {
"topLevelId": topLevelAttack,
"nestedId": nestedAttack,
},
events: map[string]string{
"test-rule-001": "graphql.resolve(topLevelMapped)",
"test-rule-002": "graphql.resolve(nested)",
},
},
}
for name, tc := range testCases {
Expand All @@ -118,9 +105,9 @@ func TestAppSec(t *testing.T) {
require.NotEmpty(t, spans)

// The last finished span (which is GraphQL entry) should have the "_dd.appsec.enabled" tag.
require.Equal(t, 1, spans[len(spans)-1].Tag("_dd.appsec.enabled"))
span := spans[len(spans)-1]
require.Equal(t, 1, span.Tag("_dd.appsec.enabled"))

events := make(map[string]string)
type ddAppsecJSON struct {
Triggers []struct {
Rule struct {
Expand All @@ -129,34 +116,19 @@ func TestAppSec(t *testing.T) {
} `json:"triggers"`
}

// Search for AppSec events in the set of spans
for _, span := range spans {
jsonText, ok := span.Tag("_dd.appsec.json").(string)
if !ok || jsonText == "" {
continue
}
var parsed ddAppsecJSON
err := json.Unmarshal([]byte(jsonText), &parsed)
require.NoError(t, err)
jsonText, ok := span.Tag("_dd.appsec.json").(string)
require.True(t, ok, "expected _dd.appsec.json tag on span")

require.Len(t, parsed.Triggers, 1, "expected exactly 1 trigger on %s span", span.OperationName())
ruleID := parsed.Triggers[0].Rule.ID
_, duplicate := events[ruleID]
require.False(t, duplicate, "found duplicated hit for rule %s", ruleID)
var origin string
switch name := span.OperationName(); name {
case "graphql.field":
field := span.Tag(tagGraphqlField).(string)
origin = fmt.Sprintf("%s(%s)", "graphql.resolve", field)
case "graphql.query":
origin = "graphql.execute"
default:
require.Fail(t, "rule trigger recorded on unecpected span", "rule %s recorded a hit on unexpected span %s", ruleID, name)
}
events[ruleID] = origin
var parsed ddAppsecJSON
err = json.Unmarshal([]byte(jsonText), &parsed)
require.NoError(t, err)

ids := make([]string, 0, len(parsed.Triggers))
for _, trigger := range parsed.Triggers {
ids = append(ids, trigger.Rule.ID)
}
// Ensure they match the expected outcome
require.Equal(t, tc.events, events)

require.ElementsMatch(t, ids, []string{"test-rule-001", "test-rule-002"})
})
}
})
Expand Down
13 changes: 6 additions & 7 deletions contrib/99designs/gqlgen/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ import (
"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/emitter/graphqlsec"
"gopkg.in/DataDog/dd-trace-go.v1/internal/appsec/emitter/graphqlsec/types"
"gopkg.in/DataDog/dd-trace-go.v1/internal/namingschema"
"gopkg.in/DataDog/dd-trace-go.v1/internal/telemetry"

Expand Down Expand Up @@ -104,12 +103,12 @@ func (t *gqlTracer) Validate(_ graphql.ExecutableSchema) error {
func (t *gqlTracer) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
opCtx := graphql.GetOperationContext(ctx)
span, ctx := t.createRootSpan(ctx, opCtx)
ctx, req := graphqlsec.StartRequestOperation(ctx, span, types.RequestOperationArgs{
ctx, req := graphqlsec.StartRequestOperation(ctx, graphqlsec.RequestOperationArgs{
RawQuery: opCtx.RawQuery,
OperationName: opCtx.OperationName,
Variables: opCtx.Variables,
})
ctx, query := graphqlsec.StartExecutionOperation(ctx, span, types.ExecutionOperationArgs{
ctx, query := graphqlsec.StartExecutionOperation(ctx, graphqlsec.ExecutionOperationArgs{
Query: opCtx.RawQuery,
OperationName: opCtx.OperationName,
Variables: opCtx.Variables,
Expand All @@ -124,11 +123,11 @@ func (t *gqlTracer) InterceptOperation(ctx context.Context, next graphql.Operati
}
defer span.Finish(tracer.WithError(err))
}
query.Finish(types.ExecutionOperationRes{
query.Finish(graphqlsec.ExecutionOperationRes{
Data: response.Data, // NB - This is raw data, but rather not parse it (possibly expensive).
Error: response.Errors,
})
req.Finish(types.RequestOperationRes{
req.Finish(span, graphqlsec.RequestOperationRes{
Data: response.Data, // NB - This is raw data, but rather not parse it (possibly expensive).
Error: response.Errors,
})
Expand Down Expand Up @@ -167,13 +166,13 @@ func (t *gqlTracer) InterceptField(ctx context.Context, next graphql.Resolver) (

span, ctx := tracer.StartSpanFromContext(ctx, fieldOp, opts...)
defer func() { span.Finish(tracer.WithError(err)) }()
ctx, op := graphqlsec.StartResolveOperation(ctx, span, types.ResolveOperationArgs{
ctx, op := graphqlsec.StartResolveOperation(ctx, graphqlsec.ResolveOperationArgs{
Arguments: fieldCtx.Args,
TypeName: fieldCtx.Object,
FieldName: fieldCtx.Field.Name,
Trivial: isTrivial,
})
defer func() { op.Finish(types.ResolveOperationRes{Data: res, Error: err}) }()
defer func() { op.Finish(graphqlsec.ResolveOperationRes{Data: res, Error: err}) }()

res, err = next(ctx)
return
Expand Down
Loading
Loading