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

Enable "Trusted IPs", a.k.a passlist with optional monitoring #3229

Merged
merged 1 commit into from
Nov 27, 2023

Conversation

lloeki
Copy link
Contributor

@lloeki lloeki commented Oct 26, 2023

What does this PR do?
Enable "Trusted IPs", a.k.a passlist with optional monitoring

Motivation:

Observe events for passlisted IPs but without blocking for those.

Additional Notes:

None

How to test the change?

For Datadog employees:

  • If this PR touches code that signs or publishes builds or packages, or handles
    credentials of any kind, 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!

@lloeki lloeki requested a review from a team as a code owner October 26, 2023 14:25
@lloeki lloeki marked this pull request as draft October 26, 2023 14:26
@github-actions github-actions bot added the appsec Application Security monitoring product label Oct 26, 2023
@codecov-commenter
Copy link

Codecov Report

❗ No coverage uploaded for pull request base (master@a59adba). Click here to learn what that means.
Report is 2 commits behind head on master.
The diff coverage is 96.42%.

@@            Coverage Diff            @@
##             master    #3229   +/-   ##
=========================================
  Coverage          ?   98.22%           
=========================================
  Files             ?     1253           
  Lines             ?    72360           
  Branches          ?     3361           
=========================================
  Hits              ?    71075           
  Misses            ?     1285           
  Partials          ?        0           
Files Coverage Δ
lib/datadog/appsec/component.rb 100.00% <100.00%> (ø)
lib/datadog/appsec/configuration/settings.rb 99.15% <100.00%> (ø)
lib/datadog/appsec/remote.rb 98.41% <100.00%> (ø)
...tadog/appsec/contrib/rack/integration_test_spec.rb 99.61% <100.00%> (ø)
spec/datadog/appsec/remote_spec.rb 100.00% <100.00%> (ø)
lib/datadog/appsec/processor/rule_loader.rb 94.11% <90.00%> (ø)

📣 Codecov offers a browser extension for seamless coverage viewing on GitHub. Try it in Chrome or Firefox today!

exclusions = []

exclusions << {
'conditions' => [
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of manually building up hashes like this, does it make sense to have some sort of exclusion object like a struct or class to ensure the hash is well formed? Or is that overkill in this situation?

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 would agree if it were pure Ruby as a consumer, but these will be passed to libddwaf's C land by converting them to ddwaf C objects via libddwaf-rb's ruby_to_object, which only understands Ruby core types.

That said, I could add an arbitrary object conversion protocol, as such:

  • non-core types such as a custom Struct would have a to_libddwaf method, to transform it to whatever libddwaf expects as a structure.
  • libddwaf-rb's ruby_to_object would check for to_libddwaf, e.g calling ruby_to_object(v.to_libddwaf).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment on lines +82 to +115
exclusions << {
'conditions' => [
{
'operator' => 'ip_match',
'parameters' => {
'inputs' => [
{
'address' => 'http.client_ip'
}
],
'list' => pass
}
}
],
'id' => SecureRandom.uuid,
}

exclusions << {
'conditions' => [
{
'operator' => 'ip_match',
'parameters' => {
'inputs' => [
{
'address' => 'http.client_ip'
}
],
'list' => monitor
}
}
],
'id' => SecureRandom.uuid,
'on_match' => 'monitor'
}
Copy link
Member

Choose a reason for hiding this comment

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

In line with @ekump's suggestion, maybe we can extract these into private methods, just to clean up the repetition a bit:

Suggested change
exclusions << {
'conditions' => [
{
'operator' => 'ip_match',
'parameters' => {
'inputs' => [
{
'address' => 'http.client_ip'
}
],
'list' => pass
}
}
],
'id' => SecureRandom.uuid,
}
exclusions << {
'conditions' => [
{
'operator' => 'ip_match',
'parameters' => {
'inputs' => [
{
'address' => 'http.client_ip'
}
],
'list' => monitor
}
}
],
'id' => SecureRandom.uuid,
'on_match' => 'monitor'
}
exclusions << build_exclusion(pass)
exclusions << build_exclusion(monitor).merge('on_match' => 'monitor')

It's fine to keep it as is too.

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 didn't want to go too far and crystallise an inherently incomplete design so I must admit I punted: indeed since load_exclusions/passlist_exclusions and load_data/denylist_data got added, it feels that RuleLoader should be split and/or refactored.

I mean, using this API - however internal - feels a bit awkward to me:

        def create_processor(settings)
          rules = AppSec::Processor::RuleLoader.load_rules(ruleset: settings.appsec.ruleset)
          return nil unless rules
          actions = rules['actions']
          AppSec::Processor::Actions.merge(actions) if actions

          data = AppSec::Processor::RuleLoader.load_data(
            ip_denylist: settings.appsec.ip_denylist,
            user_id_denylist: settings.appsec.user_id_denylist,
          )

          exclusions = AppSec::Processor::RuleLoader.load_exclusions(ip_passlist: settings.appsec.ip_passlist)

          ruleset = AppSec::Processor::RuleMerger.merge(
            rules: [rules],
            data: data,
            exclusions: exclusions,
          )

          processor = Processor.new(ruleset: ruleset)
          return nil unless processor.ready?
          processor
        end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lloeki lloeki marked this pull request as ready for review November 20, 2023 07:51
@lloeki lloeki requested a review from a team as a code owner November 20, 2023 07:51
@lloeki
Copy link
Contributor Author

lloeki commented Nov 27, 2023

Thanks for the reviews! I've tracked your suggestions internally as they fit into wider scoped changes.

@lloeki lloeki merged commit bec92bd into master Nov 27, 2023
218 checks passed
@lloeki lloeki deleted the enable-trusted-ips branch November 27, 2023 10:14
@github-actions github-actions bot added this to the 1.18.0 milestone Nov 27, 2023
@TonyCTHsu TonyCTHsu mentioned this pull request Dec 7, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
appsec Application Security monitoring product
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants