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

GraphQL threats detection and protection #3769

Merged
merged 36 commits into from
Jul 24, 2024

Conversation

vpellan
Copy link
Contributor

@vpellan vpellan commented Jul 9, 2024

What does this PR do?

This PR adds threats detection and protection for GraphQL using libddwaf, the reactive engine and a GraphQL-Ruby (fake) tracer acting as a middleware for the method we want to instrument.
Instead of instrumenting execute_query and verifying variables and arguments there, we go through every query's AST in execute_multiplex, which enable us to block all queries if a threat is detected, and not just the one where the threat is actually located.

Motivation:

There has been some customer demand about that feature. The goal is to dogfood this on Datadog's GitLab.

Additional Notes:

There are complementary changes on this PR :

  • Added support for GraphQL 2.3, and added Rails in GraphQL appraisals to do integration tests on a full rails app.

    • This means that a lot of changed files are just gemfiles and gemfile locks.
  • Refactored waf result specs that were in:

    • appsec/contrib/rack/reactive/*
    • appsec/contrib/rails/reactive/*
    • appsec/contrib/sinatra/reactive/*
    • appsec/monitor/reactive/*

    in appsec/reactive/shared_examples.rb

  • Fixed a typo in fetch_configuration in:

    • appsec/processor/actions.rb and associated sig & spec
    • appsec/response.rb
  • Fixed indentation in docs/GettingStarted.md

  • The files that are directly connected to GraphQL Threats detection are:

    • Rakefile
    • Matrixfile
    • lib/datadog/appsec.rb
    • lib/datadog/appsec/response.rb
    • lib/datadog/appsec/contrib/graphql/*
    • sig/datadog/appsec/contrib/graphql/*
    • spec/datadog/appsec/contrib/graphql/*
    • spec/datadog/tracing/contrib/graphql/support/*
    • spec/datadog/tracing/contrib/rails/support/*

How to test the change?

bundle exec appraisal ruby-X.X-graphql-X.X rake spec:appsec:graphql

@vpellan vpellan requested review from a team as code owners July 9, 2024 13:56
@github-actions github-actions bot added appsec Application Security monitoring product integrations Involves tracing integrations labels Jul 9, 2024
@vpellan vpellan self-assigned this Jul 9, 2024
Copy link
Contributor

@maycmlee maycmlee left a comment

Choose a reason for hiding this comment

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

👍 for docs

@vpellan vpellan requested a review from lloeki July 10, 2024 11:38
Copy link
Contributor

@lloeki lloeki left a comment

Choose a reason for hiding this comment

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

Overall it looks good.

I see a factorisation opportunity, plus a few small notes+questions for clarification.

Comment on lines 28 to 34
multiplex_return = []
gateway_multiplex.queries.each do |query|
query_result = ::GraphQL::Query::Result.new(
query: query,
values: JSON.parse(AppSec::Response.content_json)
)
multiplex_return << query_result
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this be extracted into AppSec::Response.negotiate?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could extract it but I believe it would become un-necessarily more complex. AppSec::Response.negotiate was made for HTTP-level frameworks, selecting the type of response the client configured (JSON, HTML or Plain text). But GraphQL only return JSON, so we'd have to make a special case to always enforce JSON when it is GraphQL, and also duplicate the resulting JSON times the number of queries in the multiplex.

Copy link
Contributor

@lloeki lloeki Jul 15, 2024

Choose a reason for hiding this comment

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

Maybe this then?

multiplex_return = appsec_response(some, args)

and

private

def appsec_response(some, args)
  gateway_multiplex.queries.map do |query|
    query_result = ::GraphQL::Query::Result.new(
      query: query,
      values: JSON.parse(AppSec::Response.content_json)
    )
  end
end

To split concerns

module GraphQL
# GraphQL integration constants
# @public_api Changing resource names, tag names, or environment variables creates breaking changes.
module Ext
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this Ext empty, you might as well remove the file.

private

def create_arguments_hash
require 'graphql/language/nodes'
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason why this require is dynamic instead of top-level?

Also, if it needs to be dynamic for some reason, it might be worth doing the require out of a hot code path.

bits = schema.execute('query test{ user(id: 1) { name } }')
expect(bits.to_h).to eq({ 'data' => { 'user' => { 'name' => 'Bits' } } })

caniche = schema.execute('query test{ user(id: 10) { name } }')
Copy link
Contributor

Choose a reason for hiding this comment

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

😆

Suggested change
caniche = schema.execute('query test{ user(id: 10) { name } }')
poodle = schema.execute('query test{ user(id: 10) { name } }')

(j/k)

| `schemas` | | `Array` | Array of `GraphQL::Schema` objects (that support class-based schema only) to trace. If you do not provide any, then tracing will applied to all the schemas. | `[]` |
| `with_unified_tracer` | | `Bool` | Enable to instrument with `UnifiedTrace` tracer, enabling support for API Catalog. `with_deprecated_tracer` has priority over this. Default is `false`, using `GraphQL::Tracing::DataDogTrace` (Added in v2.2) | `false` |
| `with_deprecated_tracer` | | `Bool` | Enable to instrument with deprecated `GraphQL::Tracing::DataDogTracing`. This has priority over `with_unified_tracer`. Default is `false`, using `GraphQL::Tracing::DataDogTrace` | `false` |
| `service_name` | | `String` | Service name used for graphql instrumentation | `'ruby-graphql'` |
Copy link
Contributor

Choose a reason for hiding this comment

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

'ruby-graphql'

The ruby- prefix seems odd. Shouldn't it simply be graphql? (Really I don't know)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree but this has been added 6 years ago, maybe we should create a new PR about it ?

@codecov-commenter
Copy link

codecov-commenter commented Jul 11, 2024

Codecov Report

Attention: Patch coverage is 96.81021% with 20 lines in your changes missing coverage. Please review.

Project coverage is 97.89%. Comparing base (ca006e9) to head (9295cad).
Report is 45 commits behind head on master.

Files Patch % Lines
...dog/tracing/contrib/graphql/support/application.rb 90.90% 7 Missing ⚠️
lib/datadog/appsec/contrib/graphql/appsec_trace.rb 83.33% 4 Missing ⚠️
...cing/contrib/graphql/support/application_schema.rb 90.90% 4 Missing ⚠️
...atadog/appsec/contrib/graphql/gateway/multiplex.rb 97.14% 1 Missing ⚠️
.../datadog/appsec/contrib/graphql/gateway/watcher.rb 97.29% 1 Missing ⚠️
lib/datadog/appsec/contrib/graphql/integration.rb 95.00% 1 Missing ⚠️
lib/datadog/appsec/contrib/graphql/patcher.rb 94.44% 1 Missing ⚠️
...ec/datadog/tracing/contrib/rails/support/models.rb 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3769      +/-   ##
==========================================
- Coverage   97.91%   97.89%   -0.02%     
==========================================
  Files        1243     1256      +13     
  Lines       74763    74983     +220     
  Branches     3608     3667      +59     
==========================================
+ Hits        73205    73408     +203     
- Misses       1558     1575      +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@vpellan vpellan merged commit 26a5abf into master Jul 24, 2024
170 checks passed
@vpellan vpellan deleted the vpellan/graphql-threat-detection branch July 24, 2024 08:34
@github-actions github-actions bot added this to the 2.3.0 milestone Jul 24, 2024
@TonyCTHsu TonyCTHsu mentioned this pull request Aug 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
appsec Application Security monitoring product integrations Involves tracing integrations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants