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

Add AppSec::ActionHandler to unify action handling #4300

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

y9v
Copy link
Member

@y9v y9v commented Jan 16, 2025

What does this PR do?
It adds Datadog::AppSec::ActionHandler module and unifies blocked response generation.

https://github.com/DataDog/libddwaf/blob/master/UPGRADING.md#action-semantics
https://github.com/DataDog/appsec-diagrams/blob/main/action-handling.md

Motivation:
We want to have a simple and unified way of handling libddwaf actions.

Change log entry
None.

Additional Notes:
Since Sinatra and Rails instrumentations always insert Datadog::AppSec::Contrib::Rack::RequestMiddleware, we don't need to catch interrupt signals anywhere other than in this Rack Middleware.

How to test the change?
CI and manual testing with app generator.

@y9v y9v self-assigned this Jan 16, 2025
@github-actions github-actions bot added integrations Involves tracing integrations appsec Application Security monitoring product labels Jan 16, 2025
case type
when 'block_request' then block_request(action_params)
when 'redirect_request' then redirect_request(action_params)
when 'generate_stack' then generate_stack(action_params)
Copy link
Contributor

Choose a reason for hiding this comment

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

Code Quality Violation

Suggested change
when 'generate_stack' then generate_stack(action_params)
when 'generate_stack'generate_stack(action_params)
Don't use `then` for multi-line if, unless, when, or in statements (...read more)

The then keyword is not necessary in multi-line if/unless/when/in statements in Ruby. When used in multi-line statements, it can make the code harder to read and understand. This is because then is typically associated with single-line conditional statements in Ruby, and its use in multi-line statements can be confusing.

Maintaining readability and clarity in your code is crucial for effective collaboration and debugging. It becomes even more important in larger codebases, where complex logic can become difficult to follow if not written clearly.

To avoid this issue, omit the then keyword in your multi-line if/unless/when/in statements. For single-line if/unless/when/in statements, using then is acceptable and can help improve readability. This practice keeps your code clean and easy to understand, following the principles of good coding practices.

View in Datadog  Leave us feedback  Documentation

when 'redirect_request' then redirect_request(action_params)
when 'generate_stack' then generate_stack(action_params)
when 'generate_schema' then generate_schema(action_params)
when 'monitor' then monitor(action_params)
Copy link
Contributor

Choose a reason for hiding this comment

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

Code Quality Violation

Suggested change
when 'monitor' then monitor(action_params)
when 'monitor'monitor(action_params)
Don't use `then` for multi-line if, unless, when, or in statements (...read more)

The then keyword is not necessary in multi-line if/unless/when/in statements in Ruby. When used in multi-line statements, it can make the code harder to read and understand. This is because then is typically associated with single-line conditional statements in Ruby, and its use in multi-line statements can be confusing.

Maintaining readability and clarity in your code is crucial for effective collaboration and debugging. It becomes even more important in larger codebases, where complex logic can become difficult to follow if not written clearly.

To avoid this issue, omit the then keyword in your multi-line if/unless/when/in statements. For single-line if/unless/when/in statements, using then is acceptable and can help improve readability. This practice keeps your code clean and easy to understand, following the principles of good coding practices.

View in Datadog  Leave us feedback  Documentation

when 'block_request' then block_request(action_params)
when 'redirect_request' then redirect_request(action_params)
when 'generate_stack' then generate_stack(action_params)
when 'generate_schema' then generate_schema(action_params)
Copy link
Contributor

Choose a reason for hiding this comment

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

Code Quality Violation

Suggested change
when 'generate_schema' then generate_schema(action_params)
when 'generate_schema'generate_schema(action_params)
Don't use `then` for multi-line if, unless, when, or in statements (...read more)

The then keyword is not necessary in multi-line if/unless/when/in statements in Ruby. When used in multi-line statements, it can make the code harder to read and understand. This is because then is typically associated with single-line conditional statements in Ruby, and its use in multi-line statements can be confusing.

Maintaining readability and clarity in your code is crucial for effective collaboration and debugging. It becomes even more important in larger codebases, where complex logic can become difficult to follow if not written clearly.

To avoid this issue, omit the then keyword in your multi-line if/unless/when/in statements. For single-line if/unless/when/in statements, using then is acceptable and can help improve readability. This practice keeps your code clean and easy to understand, following the principles of good coding practices.

View in Datadog  Leave us feedback  Documentation


def handle(type, action_params)
case type
when 'block_request' then block_request(action_params)
Copy link
Contributor

Choose a reason for hiding this comment

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

Code Quality Violation

Suggested change
when 'block_request' then block_request(action_params)
when 'block_request'block_request(action_params)
Don't use `then` for multi-line if, unless, when, or in statements (...read more)

The then keyword is not necessary in multi-line if/unless/when/in statements in Ruby. When used in multi-line statements, it can make the code harder to read and understand. This is because then is typically associated with single-line conditional statements in Ruby, and its use in multi-line statements can be confusing.

Maintaining readability and clarity in your code is crucial for effective collaboration and debugging. It becomes even more important in larger codebases, where complex logic can become difficult to follow if not written clearly.

To avoid this issue, omit the then keyword in your multi-line if/unless/when/in statements. For single-line if/unless/when/in statements, using then is acceptable and can help improve readability. This practice keeps your code clean and easy to understand, following the principles of good coding practices.

View in Datadog  Leave us feedback  Documentation

body << content(content_type)
def block_response(action_params, http_accept_header)
content_type = case action_params['type']
when nil, 'auto' then content_type(http_accept_header)
Copy link
Contributor

Choose a reason for hiding this comment

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

Code Quality Violation

Suggested change
when nil, 'auto' then content_type(http_accept_header)
when nil, 'auto'content_type(http_accept_header)
Don't use `then` for multi-line if, unless, when, or in statements (...read more)

The then keyword is not necessary in multi-line if/unless/when/in statements in Ruby. When used in multi-line statements, it can make the code harder to read and understand. This is because then is typically associated with single-line conditional statements in Ruby, and its use in multi-line statements can be confusing.

Maintaining readability and clarity in your code is crucial for effective collaboration and debugging. It becomes even more important in larger codebases, where complex logic can become difficult to follow if not written clearly.

To avoid this issue, omit the then keyword in your multi-line if/unless/when/in statements. For single-line if/unless/when/in statements, using then is acceptable and can help improve readability. This practice keeps your code clean and easy to understand, following the principles of good coding practices.

View in Datadog  Leave us feedback  Documentation

status: options['status_code']&.to_i || 403,
headers: { 'Content-Type' => content_type },
body: body,
status: (status_code >= 300 && status_code < 400 ? status_code : 303),
Copy link
Contributor

Choose a reason for hiding this comment

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

Code Quality Violation

Consider using ranges or between to simplify your comparison (...read more)

The rule "Prefer ranges/between over complex comparisons" advises developers to use the range or between? method for comparisons instead of complex conditional statements. This practice increases the readability and clarity of your code. Complex comparisons using logical operators can be difficult to understand and prone to errors.

This rule is important because it promotes cleaner, more efficient, and easier-to-read code. When code is easier to read, it's easier to maintain, debug, and less likely to contain hidden bugs. Using the range or between? method is a more concise way to check if a value falls within a specific range.

To adhere to this rule, replace complex comparison statements with the range or between? method. For example, instead of writing foo >= 42 && foo <= 99, you can write (42..99).include?(foo) or foo.between?(42, 99). These alternatives are more straightforward and visually cleaner, making your code easier to understand.

View in Datadog  Leave us feedback  Documentation

def handle(type, action_params)
case type
when 'block_request' then block_request(action_params)
when 'redirect_request' then redirect_request(action_params)
Copy link
Contributor

Choose a reason for hiding this comment

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

Code Quality Violation

Suggested change
when 'redirect_request' then redirect_request(action_params)
when 'redirect_request'redirect_request(action_params)
Don't use `then` for multi-line if, unless, when, or in statements (...read more)

The then keyword is not necessary in multi-line if/unless/when/in statements in Ruby. When used in multi-line statements, it can make the code harder to read and understand. This is because then is typically associated with single-line conditional statements in Ruby, and its use in multi-line statements can be confusing.

Maintaining readability and clarity in your code is crucial for effective collaboration and debugging. It becomes even more important in larger codebases, where complex logic can become difficult to follow if not written clearly.

To avoid this issue, omit the then keyword in your multi-line if/unless/when/in statements. For single-line if/unless/when/in statements, using then is acceptable and can help improve readability. This practice keeps your code clean and easy to understand, following the principles of good coding practices.

View in Datadog  Leave us feedback  Documentation


Response.new(
status: options['status_code']&.to_i || 403,
headers: { 'Content-Type' => content_type },
Copy link
Member Author

Choose a reason for hiding this comment

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

Content-Type header is not needed for a redirect response

@datadog-datadog-prod-us1
Copy link
Contributor

datadog-datadog-prod-us1 bot commented Jan 16, 2025

Datadog Report

Branch report: appsec-add-action-handler
Commit report: 03a451c
Test service: dd-trace-rb

✅ 0 Failed, 22070 Passed, 1477 Skipped, 5m 22.77s Total Time

@pr-commenter
Copy link

pr-commenter bot commented Jan 16, 2025

Benchmarks

Benchmark execution time: 2025-01-17 11:27:37

Comparing candidate commit 03a451c in PR branch appsec-add-action-handler with baseline commit 7e37aff in branch master.

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

@codecov-commenter
Copy link

codecov-commenter commented Jan 16, 2025

Codecov Report

Attention: Patch coverage is 95.00000% with 6 lines in your changes missing coverage. Please review.

Project coverage is 97.71%. Comparing base (7e37aff) to head (03a451c).
Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
lib/datadog/appsec/action_handler.rb 68.42% 6 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4300   +/-   ##
=======================================
  Coverage   97.70%   97.71%           
=======================================
  Files        1358     1358           
  Lines       82502    82446   -56     
  Branches     4223     4199   -24     
=======================================
- Hits        80610    80559   -51     
+ Misses       1892     1887    -5     

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

@y9v y9v marked this pull request as ready for review January 17, 2025 11:15
@y9v y9v requested review from a team as code owners January 17, 2025 11:15
# Returns an error * the number of queries so that the entire multiplex is blocked
if multiplex_response
blocked_event = multiplex_response.find { |action, _options| action == :block }
multiplex_return = AppSec::Response.graphql_response(gateway_multiplex) if blocked_event
Copy link
Contributor

Choose a reason for hiding this comment

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

If I understand correctly, this PR also removes the creation of responses at framework level (e.g. GraphQL) to handle them all at Rack level ?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, GraphQL instrumentation should only signal with INTERRUPT and leave response blocking to Rack

end

block = GraphQL::Reactive::Multiplex.publish(engine, gateway_multiplex)
GraphQL::Reactive::Multiplex.publish(engine, gateway_multiplex)
Copy link
Contributor

Choose a reason for hiding this comment

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

In the files contained in the Reactive folder of each contrib (e.g. appsec/contrib/graphql/reactive/multiplex.rb at the end of the subscribe methods, we still throw :block if any action is present, which is already wrong in itself. I wanted to fix it in the stacktrace PR but considering that this PR changes the way we handle actions, you may want to do it in this PR by deleting them ?

Copy link
Member Author

Choose a reason for hiding this comment

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

This would be done in a separate PR to reduce the amount of changes here. We want to fully get rid of the Reactive Engine, since it only has a instrumentation-local scope and didn't work as intended

@@ -39,13 +38,15 @@ def watch_multiplex(gateway = Instrumentation.gateway)

Datadog::AppSec::Event.tag_and_keep!(context, result)
context.events << event

result.actions.each do |action_type, action_params|
Copy link
Contributor

Choose a reason for hiding this comment

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

So what if the actions hash looks like {"block"=>{...}, "generate_stack"=>{...}} ? Do we still want to generate the stacktrace ? If so, I suppose that we should do a first catch in this each, save the block state, handle all the other actions, then throw block again

Copy link
Member Author

Choose a reason for hiding this comment

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

no, we don't need to throw block, we want to generate the stack trace and then throw INTERRUPT

Copy link
Member Author

Choose a reason for hiding this comment

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

but you are right, we don't handle the action precedence correctly now, I will think about it and fix it

Copy link
Member Author

Choose a reason for hiding this comment

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

done 1a5404d

Because we need to handle actions according their precedence.
@y9v y9v requested a review from vpellan January 17, 2025 15:12
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.

3 participants