Skip to content

Commit

Permalink
Merge branch 'master' into debug-dep
Browse files Browse the repository at this point in the history
  • Loading branch information
p-datadog authored Jan 16, 2025
2 parents 4e43798 + b5cf594 commit 905f888
Show file tree
Hide file tree
Showing 51 changed files with 897 additions and 696 deletions.
11 changes: 11 additions & 0 deletions integration/apps/rails-seven/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,14 @@ end
# Gems no longer included in Ruby 3.4
gem 'bigdecimal'
gem 'mutex_m'

# concurrent-ruby 1.3.5 removed dependency on logger, see:
# https://github.com/ruby-concurrency/concurrent-ruby/commit/d7ce956dacd0b772273d39b8ed31a30cff7ecf38
# Unfortunately this broke Rails because ActiveSupport used Logger
# before requiring logger.
# Since the failure happens rather early in rails bootstrapping,
# patching it is difficult, thus downgrade concurrent-ruby.
# The issue is fixed in 7-0-stable and should be shipped in the release
# after 7.0.8.7, at which point the pin of concurrent-ruby should be removed.
# See https://github.com/rails/rails/issues/54263
gem 'concurrent-ruby', '1.3.4'
10 changes: 10 additions & 0 deletions integration/apps/rails-six/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,13 @@ end

# Gem no longer included in Ruby 3.4
gem 'mutex_m'

# concurrent-ruby 1.3.5 removed dependency on logger, see:
# https://github.com/ruby-concurrency/concurrent-ruby/commit/d7ce956dacd0b772273d39b8ed31a30cff7ecf38
# Unfortunately this broke Rails because ActiveSupport used Logger
# before requiring logger.
# Since the failure happens rather early in rails bootstrapping,
# patching it is difficult, thus downgrade concurrent-ruby.
# The issue affects Rails 6.1 where apparently it will be never fixed
# (unlike Rails 7.0 which is fixed in https://github.com/rails/rails/issues/54263).
gem 'concurrent-ruby', '1.3.4'
5 changes: 5 additions & 0 deletions lib/datadog/appsec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ def reconfigure_lock(&block)
appsec_component.reconfigure_lock(&block)
end

def api_security_enabled?
Datadog.configuration.appsec.api_security.enabled &&
Datadog.configuration.appsec.api_security.sample_rate.sample?
end

private

def components
Expand Down
25 changes: 19 additions & 6 deletions lib/datadog/appsec/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ module AppSec
# interface sufficient for instrumentation to perform threat detection.
class Context
ActiveContextError = Class.new(StandardError)
WAFMetrics = Struct.new(:timeouts, :duration_ns, :duration_ext_ns, keyword_init: true)

attr_reader :trace, :span

# NOTE: This is an intermediate state and will be changed
attr_reader :waf_runner
attr_reader :trace, :span, :events, :waf_metrics

class << self
def activate(context)
Expand All @@ -34,18 +32,33 @@ def active
def initialize(trace, span, security_engine)
@trace = trace
@span = span
@events = []
@security_engine = security_engine
@waf_runner = security_engine.new_context
@waf_runner = security_engine.new_runner
@waf_metrics = WAFMetrics.new(timeouts: 0, duration_ns: 0, duration_ext_ns: 0)
@mutex = Mutex.new
end

def run_waf(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
@waf_runner.run(persistent_data, ephemeral_data, timeout)
result = @waf_runner.run(persistent_data, ephemeral_data, timeout)

@mutex.synchronize do
@waf_metrics.timeouts += 1 if result.timeout?
@waf_metrics.duration_ns += result.duration_ns
@waf_metrics.duration_ext_ns += result.duration_ext_ns
end

result
end

def run_rasp(_type, persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
@waf_runner.run(persistent_data, ephemeral_data, timeout)
end

def extract_schema
@waf_runner.run({ 'waf.context.processor' => { 'extract-schema' => true } }, {})
end

def finalize
@waf_runner.finalize
end
Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/appsec/contrib/active_record/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def detect_sql_injection(sql, adapter_name)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_rasp(Ext::RASP_SQLI, {}, ephemeral_data, waf_timeout)

if result.status == :match
if result.match?
Datadog::AppSec::Event.tag_and_keep!(context, result)

event = {
Expand All @@ -35,7 +35,7 @@ def detect_sql_injection(sql, adapter_name)
sql: sql,
actions: result.actions
}
context.waf_runner.events << event
context.events << event
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/contrib/graphql/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def watch_multiplex(gateway = Instrumentation.gateway)
}

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

block = GraphQL::Reactive::Multiplex.publish(engine, gateway_multiplex)
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def self.subscribe(engine, context)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match
next unless result.match?

yield result
throw(:block, true) unless result.actions.empty?
Expand Down
12 changes: 6 additions & 6 deletions lib/datadog/appsec/contrib/rack/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def watch_request(gateway = Instrumentation.gateway)
engine = AppSec::Reactive::Engine.new

Rack::Reactive::Request.subscribe(engine, context) do |result|
if result.status == :match
if result.match?
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
Expand All @@ -43,7 +43,7 @@ def watch_request(gateway = Instrumentation.gateway)
# We want to keep the trace in case of security event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
context.events << event
end
end

Expand All @@ -61,7 +61,7 @@ def watch_response(gateway = Instrumentation.gateway)
engine = AppSec::Reactive::Engine.new

Rack::Reactive::Response.subscribe(engine, context) do |result|
if result.status == :match
if result.match?
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
Expand All @@ -74,7 +74,7 @@ def watch_response(gateway = Instrumentation.gateway)
# We want to keep the trace in case of security event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
context.events << event
end
end

Expand All @@ -92,7 +92,7 @@ def watch_request_body(gateway = Instrumentation.gateway)
engine = AppSec::Reactive::Engine.new

Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
if result.status == :match
if result.match?
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
Expand All @@ -105,7 +105,7 @@ def watch_request_body(gateway = Instrumentation.gateway)
# We want to keep the trace in case of security event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
context.events << event
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/contrib/rack/reactive/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def self.subscribe(engine, context)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match
next unless result.match?

yield result
throw(:block, true) unless result.actions.empty?
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/contrib/rack/reactive/request_body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ def self.subscribe(engine, context)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match
next unless result.match?

yield result
throw(:block, true) unless result.actions.empty?
Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/contrib/rack/reactive/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def self.subscribe(engine, context)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match
next unless result.match?

yield result
throw(:block, true) unless result.actions.empty?
Expand Down
20 changes: 9 additions & 11 deletions lib/datadog/appsec/contrib/rack/request_middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,20 @@ def call(env)

http_response = AppSec::Response.negotiate(env, block_actions).to_rack if block_actions

if (result = ctx.waf_runner.extract_schema)
ctx.waf_runner.events << {
if AppSec.api_security_enabled?
ctx.events << {
trace: ctx.trace,
span: ctx.span,
waf_result: result,
waf_result: ctx.extract_schema,
}
end

ctx.waf_runner.events.each do |e|
ctx.events.each do |e|
e[:response] ||= gateway_response
e[:request] ||= gateway_request
end

AppSec::Event.record(ctx.span, *ctx.waf_runner.events)
AppSec::Event.record(ctx.span, *ctx.events)

http_response
ensure
Expand Down Expand Up @@ -200,15 +200,13 @@ def add_request_tags(context, env)

def add_waf_runtime_tags(context)
span = context.span
context = context.waf_runner

return unless span && context
return unless span

span.set_tag('_dd.appsec.waf.timeouts', context.timeouts)
span.set_tag('_dd.appsec.waf.timeouts', context.waf_metrics.timeouts)

# these tags expect time in us
span.set_tag('_dd.appsec.waf.duration', context.time_ns / 1000.0)
span.set_tag('_dd.appsec.waf.duration_ext', context.time_ext_ns / 1000.0)
span.set_tag('_dd.appsec.waf.duration', context.waf_metrics.duration_ns / 1000.0)
span.set_tag('_dd.appsec.waf.duration_ext', context.waf_metrics.duration_ext_ns / 1000.0)
end

def to_rack_header(header)
Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/appsec/contrib/rails/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def watch_request_action(gateway = Instrumentation.gateway)
engine = AppSec::Reactive::Engine.new

Rails::Reactive::Action.subscribe(engine, context) do |result|
if result.status == :match
if result.match?
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
Expand All @@ -39,7 +39,7 @@ def watch_request_action(gateway = Instrumentation.gateway)
# We want to keep the trace in case of security event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
context.events << event
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/contrib/rails/reactive/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def self.subscribe(engine, context)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match
next unless result.match?

yield result
throw(:block, true) unless result.actions.empty?
Expand Down
8 changes: 4 additions & 4 deletions lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def watch_request_dispatch(gateway = Instrumentation.gateway)
engine = AppSec::Reactive::Engine.new

Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
if result.status == :match
if result.match?
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
Expand All @@ -41,7 +41,7 @@ def watch_request_dispatch(gateway = Instrumentation.gateway)
# We want to keep the trace in case of security event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
context.events << event
end
end

Expand All @@ -59,7 +59,7 @@ def watch_request_routed(gateway = Instrumentation.gateway)
engine = AppSec::Reactive::Engine.new

Sinatra::Reactive::Routed.subscribe(engine, context) do |result|
if result.status == :match
if result.match?
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
Expand All @@ -72,7 +72,7 @@ def watch_request_routed(gateway = Instrumentation.gateway)
# We want to keep the trace in case of security event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
context.events << event
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/contrib/sinatra/reactive/routed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def self.subscribe(engine, context)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match
next unless result.match?

yield result
throw(:block, true) unless result.actions.empty?
Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/appsec/monitor/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def watch_user_id(gateway = Instrumentation.gateway)
engine = AppSec::Reactive::Engine.new

Monitor::Reactive::SetUser.subscribe(engine, context) do |result|
if result.status == :match
if result.match?
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
Expand All @@ -37,7 +37,7 @@ def watch_user_id(gateway = Instrumentation.gateway)
# We want to keep the trace in case of security event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
context.events << event
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/datadog/appsec/monitor/reactive/set_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def self.subscribe(engine, context)
waf_timeout = Datadog.configuration.appsec.waf_timeout
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match
next unless result.match?

yield result
throw(:block, true) unless result.actions.empty?
Expand Down
7 changes: 4 additions & 3 deletions lib/datadog/appsec/processor.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# frozen_string_literal: true

require_relative 'processor/context'
require_relative 'security_engine/runner'

module Datadog
module AppSec
# Processor integrates libddwaf into datadog/appsec
# NOTE: This class will be moved under AppSec::SecurityEngine namespace
class Processor
attr_reader :diagnostics, :addresses

Expand All @@ -29,8 +30,8 @@ def finalize
@handle.finalize
end

def new_context
Context.new(@handle, telemetry: @telemetry)
def new_runner
SecurityEngine::Runner.new(@handle, telemetry: @telemetry)
end

private
Expand Down
Loading

0 comments on commit 905f888

Please sign in to comment.