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

appsec/graphql: add support for Threat Monitoring #2309

Merged
merged 54 commits into from
Dec 19, 2023

Conversation

RomainMuller
Copy link
Contributor

@RomainMuller RomainMuller commented Oct 30, 2023

What does this PR do?

Adds a new in-app WAF integration for GraphQL to dyngo. Also adds a new contrib for github.com/grapgql-go/graphql to ensure the integration is not unnecessarily middleware-dependent.

The graphql.server.all_resolvers address can currently only be sent to the WAF after all resolvers have executed, as doing otherwise would require re-parsing the query and matching fields to variables, which is unnecessarily expensive.

Blocking capabilities will require further work (possibly dynamic instrumentation) as the tracing hooks provided by both supported middleware fail to allow altering the response flow (e.g: returning an error instead of invoking the field resolvers).

Motivation

This provides in-app WAF support for GraphQL operations.

Reviewer's Checklist

  • Changed code has unit tests for its functionality at or near 100% coverage.
  • There is a benchmark for any new code, or changes to existing code.
  • If this interacts with the agent in a new way, a system test has been added.

For Datadog employees:

  • If this PR touches code that handles credentials of any kind, such as Datadog API keys, I've requested a review from @DataDog/security-design-and-guidance.
  • This PR doesn't touch any of that.

Unsure? Have a question? Request a review!

Adds a new in-app WAF integration for GraphQL to `dyngo`. Also adds a
new contrib for `github.com/grapgql-go/graphql` to ensure the
integration is not unnecessarily middleware-dependent.

The `graphql.server.all_resolvers` address can currently only be sent
to the WAF after all resolvers have executed, as doing otherwise would
require re-parsing the query and matching fields to variables, which is
unnecessarily expensive.

Blocking capabilities will require further work (possibly dynamic
instrumentation) as the tracing hooks provided by both supported
middleware fail to allow altering the response flow (e.g: returning an
error instead of invoking the field resolvers).
contrib/graphql-go/graphql/graphql_test.go Show resolved Hide resolved
contrib/graphql-go/graphql/graphql_test.go Outdated Show resolved Hide resolved
contrib/graphql-go/graphql/graphql.go Outdated Show resolved Hide resolved
internal/appsec/waf.go Outdated Show resolved Hide resolved
contrib/graphql-go/graphql/graphql.go Outdated Show resolved Hide resolved
@pr-commenter
Copy link

pr-commenter bot commented Oct 30, 2023

Benchmarks

Benchmark execution time: 2023-12-19 10:16:39

Comparing candidate commit e0bb880 in PR branch romain.marcadier/graphql/APPSEC-11164 with baseline commit 337ded8 in branch main.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 40 metrics, 1 unstable metrics.

@RomainMuller RomainMuller force-pushed the romain.marcadier/graphql/APPSEC-11164 branch from ed9ae81 to 2f0d043 Compare October 31, 2023 11:07
…hql/APPSEC-11164

# Conflicts:
#	go.mod
#	go.sum
#	internal/appsec/dyngo/instrumentation/common.go
#	internal/appsec/remoteconfig_test.go
#	internal/appsec/waf.go
#	internal/appsec/waf_unit_test.go
Move dyngo listeners into per-protocol packages to make them easier to
maintain and to improve overall separation of concerns within dyngo
listener settings.

This is an initial step in preparing for the addition of GraphQL
support, and to make listener activation be contrib-driven instead of
being unconditional.
@RomainMuller RomainMuller changed the base branch from main to romain.marcadier/refactor-dyngo November 22, 2023 10:06
}
}

// TraceField traces a GraphQL field access.
func (t *Tracer) TraceField(ctx context.Context, _ string, typeName string, fieldName string, trivial bool, _ map[string]interface{}) (context.Context, trace.TraceFieldFinishFunc) {
func (t *Tracer) TraceField(ctx context.Context, label string, typeName string, fieldName string, trivial bool, arguments map[string]interface{}) (context.Context, trace.FieldFinishFunc) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci] reported by reviewdog 🐶
unused-parameter: parameter 'label' seems to be unused, consider removing or renaming it as _ (revive)

Copy link
Contributor

@Julio-Guerra Julio-Guerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass done - I reviewed everything on the dyngo side of things. I have all the contribs left to review.

internal/appsec/listener/graphqlsec/graphql.go Outdated Show resolved Hide resolved
internal/appsec/listener/graphqlsec/graphql.go Outdated Show resolved Hide resolved
internal/appsec/listener/graphqlsec/graphql.go Outdated Show resolved Hide resolved
internal/appsec/listener/graphqlsec/graphql.go Outdated Show resolved Hide resolved
Comment on lines 42 to 45
if span == nil {
// The span may be nil (e.g: in case of GraphQL subscriptions with certian contribs)
span = trace.BlackHoleTagSetter{}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about skipping them if in the end we don't have any data transport available?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, in Q1, we will start relying on the GLS so this case shouldn't happen anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GLS standing for Goroutine Local Storage? I don't think that'll change anything in this posture... The problem this works around is that the tracer INTENTIONALLY does not create a span for GraphQL Subscriptions because they may be open forever (preventing anything from being emitted).

internal/appsec/emitter/graphqlsec/execution.go Outdated Show resolved Hide resolved
internal/appsec/emitter/graphqlsec/execution.go Outdated Show resolved Hide resolved
internal/appsec/emitter/graphqlsec/result.go Outdated Show resolved Hide resolved
@Julio-Guerra Julio-Guerra added apm:ecosystem contrib/* related feature requests or bugs appsec labels Dec 12, 2023
@RomainMuller RomainMuller requested a review from a team as a code owner December 12, 2023 10:25
Copy link
Contributor

@ahmed-mez ahmed-mez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of review, mainly focused on the non-appsec code.
Does it make sense to add an integration test for the new contrib? Let's also add an example_test.go file for it.

I didn't add comments for style but please read the style guidelines of the repo. There are multiple places where the Stay compact rule wasn't respected.

@RomainMuller
Copy link
Contributor Author

@ahmed-mez: There are multiple places where the Stay compact rule wasn't respected.

Ah - you got me there... I guess I am aware of the style guide but the whole Stay Compact aspect I have a pretty strong disagreement with at this stage (and sadly, the style guide provides no motivation for this guideline, so I can't even say if I agree with the general principles of why it's there)... Nevertheless, my strong opinions aren't a justification to simply ignore guidelines (and this is also not the place to debate on said guidelines), so I'll go around and remove excessive blank lines 🫣

Copy link
Contributor

@ahmed-mez ahmed-mez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for tracing. If @rarguelloF or @zarirhamza could review on behalf of the FI team that would be great!

Copy link
Contributor

@zarirhamza zarirhamza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contrib looks good too, glad to see better graphQL instrumentation

Copy link
Contributor

@Julio-Guerra Julio-Guerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congrats - very amazing work and super happy to see such a good level of testing.

I left side notes only for the record, nothing to address immediately.

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

func TestAppSec(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

amazing test 👏

// returns a function to restore the previous environment state.
func enableAppSec(t *testing.T) func() {
const rules = `{
"version": "2.2",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: there's a helper function template somewhere in internal/appsec that you could leverage too.
The reason is that the rule format changed a few times so we created constructors instead at some point.

err = os.WriteFile(rulesFile, []byte(rules), 0644)
require.NoError(t, err)
t.Setenv("DD_APPSEC_ENABLED", "1")
t.Setenv("DD_APPSEC_RULES", rulesFile)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would we useful to accept inlined rules in the string value - worth adding this idea in the backlog:

@RomainMuller RomainMuller enabled auto-merge (squash) December 18, 2023 09:46
auto-merge was automatically disabled December 18, 2023 09:51

Pull Request is not mergeable

@RomainMuller RomainMuller enabled auto-merge (squash) December 19, 2023 10:01
auto-merge was automatically disabled December 19, 2023 10:04

Pull Request is not mergeable

@RomainMuller RomainMuller merged commit 3eafa89 into main Dec 19, 2023
334 of 335 checks passed
@RomainMuller RomainMuller deleted the romain.marcadier/graphql/APPSEC-11164 branch December 19, 2023 10:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
apm:ecosystem contrib/* related feature requests or bugs appsec
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants