Skip to content

Commit

Permalink
Merge pull request DataDog#3229 from DataDog/enable-trusted-ips
Browse files Browse the repository at this point in the history
Enable "Trusted IPs", a.k.a passlist with optional monitoring
  • Loading branch information
lloeki committed Nov 27, 2023
2 parents adc07c4 + 92c2866 commit bec92bd
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 11 deletions.
5 changes: 4 additions & 1 deletion lib/datadog/appsec/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ def create_processor(settings)

data = AppSec::Processor::RuleLoader.load_data(
ip_denylist: settings.appsec.ip_denylist,
user_id_denylist: settings.appsec.user_id_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)
Expand Down
4 changes: 4 additions & 0 deletions lib/datadog/appsec/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ def self.add_settings!(base)
o.default :recommended
end

option :ip_passlist do |o|
o.default []
end

option :ip_denylist do |o|
o.type :array
o.default []
Expand Down
60 changes: 60 additions & 0 deletions lib/datadog/appsec/processor/rule_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ def load_data(ip_denylist: [], user_id_denylist: [])
data
end

def load_exclusions(ip_passlist: [])
exclusions = []
exclusions << [passlist_exclusions(ip_passlist)] if ip_passlist.any?

exclusions
end

private

def denylist_data(id, denylist)
Expand All @@ -56,6 +63,59 @@ def denylist_data(id, denylist)
'data' => denylist.map { |v| { 'value' => v.to_s, 'expiration' => 2**63 } }
}
end

def passlist_exclusions(ip_passlist) # rubocop:disable Metrics/MethodLength
case ip_passlist
when Array
pass = ip_passlist
monitor = []
when Hash
pass = ip_passlist[:pass]
monitor = ip_passlist[:monitor]
else
pass = []
monitor = []
end

exclusions = []

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
end
end
end
end
Expand Down
21 changes: 12 additions & 9 deletions lib/datadog/appsec/remote.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ class ReadError < StandardError; end
class NoRulesError < StandardError; end

class << self
CAP_ASM_ACTIVATION = 1 << 1 # Remote activation via ASM_FEATURES product
CAP_ASM_IP_BLOCKING = 1 << 2 # accept IP blocking data from ASM_DATA product
CAP_ASM_DD_RULES = 1 << 3 # read ASM rules from ASM_DD product
CAP_ASM_EXCLUSIONS = 1 << 4 # exclusion filters (passlist) via ASM product
CAP_ASM_REQUEST_BLOCKING = 1 << 5 # can block on request info
CAP_ASM_RESPONSE_BLOCKING = 1 << 6 # can block on response info
CAP_ASM_USER_BLOCKING = 1 << 7 # accept user blocking data from ASM_DATA product
CAP_ASM_CUSTOM_RULES = 1 << 8 # accept custom rules
CAP_ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9 # supports custom http code or redirect sa blocking response
CAP_ASM_RESERVED_1 = 1 << 0 # RESERVED
CAP_ASM_ACTIVATION = 1 << 1 # Remote activation via ASM_FEATURES product
CAP_ASM_IP_BLOCKING = 1 << 2 # accept IP blocking data from ASM_DATA product
CAP_ASM_DD_RULES = 1 << 3 # read ASM rules from ASM_DD product
CAP_ASM_EXCLUSIONS = 1 << 4 # exclusion filters (passlist) via ASM product
CAP_ASM_REQUEST_BLOCKING = 1 << 5 # can block on request info
CAP_ASM_RESPONSE_BLOCKING = 1 << 6 # can block on response info
CAP_ASM_USER_BLOCKING = 1 << 7 # accept user blocking data from ASM_DATA product
CAP_ASM_CUSTOM_RULES = 1 << 8 # accept custom rules
CAP_ASM_CUSTOM_BLOCKING_RESPONSE = 1 << 9 # supports custom http code or redirect sa blocking response
CAP_ASM_TRUSTED_IPS = 1 << 10 # supports trusted ip

# TODO: we need to dynamically add CAP_ASM_ACTIVATION once we support it
ASM_CAPABILITIES = [
Expand All @@ -32,6 +34,7 @@ class << self
CAP_ASM_DD_RULES,
CAP_ASM_CUSTOM_RULES,
CAP_ASM_CUSTOM_BLOCKING_RESPONSE,
CAP_ASM_TRUSTED_IPS,
].freeze

ASM_PRODUCTS = [
Expand Down
1 change: 1 addition & 0 deletions sig/datadog/appsec/processor/rule_loader.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Datadog
private

def self.denylist_data: (String id, Array[String] denylist) -> ::Hash[::String, untyped]
def self.passlist_exclusions: (Array[String] | Hash[Symbol, Array[String]] passlist) -> ::Array[::Hash[::String, untyped]]
end
end
end
Expand Down
4 changes: 4 additions & 0 deletions sig/datadog/appsec/remote.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ module Datadog
class NoRulesError < StandardError
end

CAP_ASM_RESERVED_1: Integer

CAP_ASM_ACTIVATION: Integer

CAP_ASM_IP_BLOCKING: Integer
Expand All @@ -25,6 +27,8 @@ module Datadog

CAP_ASM_CUSTOM_BLOCKING_RESPONSE: Integer

CAP_ASM_TRUSTED_IPS: Integer

ASM_CAPABILITIES: Array[Integer]

ASM_PRODUCTS: ::Array[String]
Expand Down
26 changes: 26 additions & 0 deletions spec/datadog/appsec/contrib/rack/integration_test_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

let(:appsec_enabled) { true }
let(:tracing_enabled) { true }
let(:appsec_ip_passlist) { [] }
let(:appsec_ip_denylist) { [] }
let(:appsec_user_id_denylist) { [] }
let(:appsec_ruleset) { :recommended }
Expand Down Expand Up @@ -135,6 +136,7 @@
c.appsec.enabled = appsec_enabled
c.appsec.waf_timeout = 10_000_000 # in us
c.appsec.instrument :rack
c.appsec.ip_passlist = appsec_ip_passlist
c.appsec.ip_denylist = appsec_ip_denylist
c.appsec.user_id_denylist = appsec_user_id_denylist
c.appsec.ruleset = appsec_ruleset
Expand Down Expand Up @@ -271,6 +273,30 @@
it_behaves_like 'a trace with AppSec tags'
it_behaves_like 'a trace with AppSec events', { blocking: true }
it_behaves_like 'a trace with AppSec api security tags'

context 'and a passlist' do
let(:client_ip) { '1.2.3.4' }
let(:appsec_ip_passlist) { [client_ip] }
let(:headers) { { 'HTTP_X_FORWARDED_FOR' => client_ip } }

it_behaves_like 'normal with tracing disable'
it_behaves_like 'a GET 200 span'
it_behaves_like 'a trace with AppSec tags'
it_behaves_like 'a trace without AppSec events'
it_behaves_like 'a trace with AppSec api security tags'
end

context 'and a monitoring passlist' do
let(:client_ip) { '1.2.3.4' }
let(:appsec_ip_passlist) { { monitor: [client_ip] } }
let(:headers) { { 'HTTP_X_FORWARDED_FOR' => client_ip } }

it_behaves_like 'normal with tracing disable'
it_behaves_like 'a GET 200 span'
it_behaves_like 'a trace with AppSec tags'
it_behaves_like 'a trace with AppSec events'
it_behaves_like 'a trace with AppSec api security tags'
end
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/datadog/appsec/remote_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
end

it 'returns capabilities' do
expect(described_class.capabilities).to eq([4, 128, 16, 32, 64, 8, 256, 512])
expect(described_class.capabilities).to eq([4, 128, 16, 32, 64, 8, 256, 512, 1024])
end
end
end
Expand Down

0 comments on commit bec92bd

Please sign in to comment.