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

[APPSEC-56188] Replace Scope with Context #4277

Merged
merged 11 commits into from
Jan 13, 2025
6 changes: 3 additions & 3 deletions lib/datadog/appsec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

require_relative 'appsec/configuration'
require_relative 'appsec/extensions'
require_relative 'appsec/scope'
require_relative 'appsec/context'
require_relative 'appsec/ext'
require_relative 'appsec/utils'

Expand All @@ -14,8 +14,8 @@ def enabled?
Datadog.configuration.appsec.enabled
end

def active_scope
Datadog::AppSec::Scope.active_scope
def active_context
Datadog::AppSec::Context.active
end

def processor
Expand Down
54 changes: 54 additions & 0 deletions lib/datadog/appsec/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

module Datadog
module AppSec
# This class accumulates the context over the request life-cycle and exposes
# interface sufficient for instrumentation to perform threat detection.
class Context
ActiveContextError = Class.new(StandardError)

attr_reader :trace, :span

# NOTE: This is an intermediate state and will be changed
attr_reader :waf_runner

class << self
def activate(context)
raise ArgumentError, 'not a Datadog::AppSec::Context' unless context.instance_of?(Context)
raise ActiveContextError, 'another context is active, nested contexts are not supported' if active

Thread.current[Ext::ACTIVE_CONTEXT_KEY] = context
end

def deactivate
active&.finalize
ensure
Thread.current[Ext::ACTIVE_CONTEXT_KEY] = nil
end

def active
Thread.current[Ext::ACTIVE_CONTEXT_KEY]
end
end

def initialize(trace, span, security_engine)
@trace = trace
@span = span
@security_engine = security_engine
@waf_runner = security_engine.new_context
end

def run_waf(persistent_data, ephemeral_data, timeout = WAF::LibDDWAF::DDWAF_RUN_TIMEOUT)
@waf_runner.run(persistent_data, ephemeral_data, timeout)
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 finalize
@waf_runner.finalize
end
end
end
end
14 changes: 7 additions & 7 deletions lib/datadog/appsec/contrib/active_record/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ module Instrumentation
module_function

def detect_sql_injection(sql, adapter_name)
scope = AppSec.active_scope
return unless scope
context = AppSec.active_context
return unless context

# libddwaf expects db system to be lowercase,
# in case of sqlite adapter, libddwaf expects 'sqlite' as db system
Expand All @@ -23,19 +23,19 @@ def detect_sql_injection(sql, adapter_name)
}

waf_timeout = Datadog.configuration.appsec.waf_timeout
result = scope.processor_context.run({}, ephemeral_data, waf_timeout)
result = context.run_rasp(Ext::RASP_SQLI, {}, ephemeral_data, waf_timeout)

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

event = {
waf_result: result,
trace: scope.trace,
span: scope.service_entry_span,
trace: context.trace,
span: context.span,
sql: sql,
actions: result.actions
}
scope.processor_context.events << event
context.waf_runner.events << event
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ def validate(resource, &block)

automated_track_user_events_mode = track_user_events_configuration.mode

appsec_scope = Datadog::AppSec.active_scope
appsec_context = Datadog::AppSec.active_context

return result unless appsec_scope
return result unless appsec_context

devise_resource = resource ? Resource.new(resource) : nil

Expand All @@ -39,8 +39,8 @@ def validate(resource, &block)
end

Tracking.track_login_success(
appsec_scope.trace,
appsec_scope.service_entry_span,
appsec_context.trace,
appsec_context.span,
user_id: event_information.user_id,
**event_information.to_h
)
Expand All @@ -59,8 +59,8 @@ def validate(resource, &block)
end

Tracking.track_login_failure(
appsec_scope.trace,
appsec_scope.service_entry_span,
appsec_context.trace,
appsec_context.span,
user_id: event_information.user_id,
user_exists: user_exists,
**event_information.to_h
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def create

automated_track_user_events_mode = track_user_events_configuration.mode

appsec_scope = Datadog::AppSec.active_scope
return super unless appsec_scope
appsec_context = Datadog::AppSec.active_context
return super unless appsec_context

super do |resource|
if resource.persisted?
Expand All @@ -36,8 +36,8 @@ def create
end

Tracking.track_signup(
appsec_scope.trace,
appsec_scope.service_entry_span,
appsec_context.trace,
appsec_context.span,
user_id: event_information.user_id,
**event_information.to_h
)
Expand Down
14 changes: 7 additions & 7 deletions lib/datadog/appsec/contrib/graphql/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@ def watch_multiplex(gateway = Instrumentation.gateway)
gateway.watch('graphql.multiplex', :appsec) do |stack, gateway_multiplex|
block = false
event = nil
scope = AppSec::Scope.active_scope
context = AppSec::Context.active
engine = AppSec::Reactive::Engine.new

if scope
GraphQL::Reactive::Multiplex.subscribe(engine, scope.processor_context) do |result|
if context
GraphQL::Reactive::Multiplex.subscribe(engine, context) do |result|
event = {
waf_result: result,
trace: scope.trace,
span: scope.service_entry_span,
trace: context.trace,
span: context.span,
multiplex: gateway_multiplex,
actions: result.actions
}

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

block = GraphQL::Reactive::Multiplex.publish(engine, gateway_multiplex)
Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/appsec/contrib/graphql/reactive/multiplex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def self.publish(engine, gateway_multiplex)
end
end

def self.subscribe(engine, waf_context)
def self.subscribe(engine, context)
engine.subscribe(*ADDRESSES) do |*values|
Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
arguments = values[0]
Expand All @@ -30,7 +30,7 @@ def self.subscribe(engine, waf_context)
}

waf_timeout = Datadog.configuration.appsec.waf_timeout
result = waf_context.run(persistent_data, {}, waf_timeout)
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match

Expand Down
6 changes: 3 additions & 3 deletions lib/datadog/appsec/contrib/rack/gateway/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ module Rack
module Gateway
# Gateway Response argument.
class Response < Instrumentation::Gateway::Argument
attr_reader :body, :status, :headers, :scope
attr_reader :body, :status, :headers, :context

def initialize(body, status, headers, scope:)
def initialize(body, status, headers, context:)
super()
@body = body
@status = status
@headers = headers.each_with_object({}) { |(k, v), h| h[k.downcase] = v }
@scope = scope
@context = context
end

def response
Expand Down
42 changes: 21 additions & 21 deletions lib/datadog/appsec/contrib/rack/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,24 @@ def watch
def watch_request(gateway = Instrumentation.gateway)
gateway.watch('rack.request', :appsec) do |stack, gateway_request|
event = nil
scope = gateway_request.env[Datadog::AppSec::Ext::SCOPE_KEY]
context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
engine = AppSec::Reactive::Engine.new

Rack::Reactive::Request.subscribe(engine, scope.processor_context) do |result|
Rack::Reactive::Request.subscribe(engine, context) do |result|
if result.status == :match
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
trace: scope.trace,
span: scope.service_entry_span,
trace: context.trace,
span: context.span,
request: gateway_request,
actions: result.actions
}

# We want to keep the trace in case of security event
scope.trace.keep! if scope.trace
Datadog::AppSec::Event.tag_and_keep!(scope, result)
scope.processor_context.events << event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
end
end

Expand All @@ -57,24 +57,24 @@ def watch_request(gateway = Instrumentation.gateway)
def watch_response(gateway = Instrumentation.gateway)
gateway.watch('rack.response', :appsec) do |stack, gateway_response|
event = nil
scope = gateway_response.scope
context = gateway_response.context
engine = AppSec::Reactive::Engine.new

Rack::Reactive::Response.subscribe(engine, scope.processor_context) do |result|
Rack::Reactive::Response.subscribe(engine, context) do |result|
if result.status == :match
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
trace: scope.trace,
span: scope.service_entry_span,
trace: context.trace,
span: context.span,
response: gateway_response,
actions: result.actions
}

# We want to keep the trace in case of security event
scope.trace.keep! if scope.trace
Datadog::AppSec::Event.tag_and_keep!(scope, result)
scope.processor_context.events << event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
end
end

Expand All @@ -88,24 +88,24 @@ def watch_response(gateway = Instrumentation.gateway)
def watch_request_body(gateway = Instrumentation.gateway)
gateway.watch('rack.request.body', :appsec) do |stack, gateway_request|
event = nil
scope = gateway_request.env[Datadog::AppSec::Ext::SCOPE_KEY]
context = gateway_request.env[Datadog::AppSec::Ext::CONTEXT_KEY]
engine = AppSec::Reactive::Engine.new

Rack::Reactive::RequestBody.subscribe(engine, scope.processor_context) do |result|
Rack::Reactive::RequestBody.subscribe(engine, context) do |result|
if result.status == :match
# TODO: should this hash be an Event instance instead?
event = {
waf_result: result,
trace: scope.trace,
span: scope.service_entry_span,
trace: context.trace,
span: context.span,
request: gateway_request,
actions: result.actions
}

# We want to keep the trace in case of security event
scope.trace.keep! if scope.trace
Datadog::AppSec::Event.tag_and_keep!(scope, result)
scope.processor_context.events << event
context.trace.keep! if context.trace
Datadog::AppSec::Event.tag_and_keep!(context, result)
context.waf_runner.events << event
end
end

Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/appsec/contrib/rack/reactive/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def self.publish(engine, gateway_request)
end
end

def self.subscribe(engine, waf_context)
def self.subscribe(engine, context)
engine.subscribe(*ADDRESSES) do |*values|
Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }

Expand All @@ -53,7 +53,7 @@ def self.subscribe(engine, waf_context)
}

waf_timeout = Datadog.configuration.appsec.waf_timeout
result = waf_context.run(persistent_data, {}, waf_timeout)
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match

Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/appsec/contrib/rack/reactive/request_body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def self.publish(engine, gateway_request)
end
end

def self.subscribe(engine, waf_context)
def self.subscribe(engine, context)
engine.subscribe(*ADDRESSES) do |*values|
Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }
body = values[0]
Expand All @@ -31,7 +31,7 @@ def self.subscribe(engine, waf_context)
}

waf_timeout = Datadog.configuration.appsec.waf_timeout
result = waf_context.run(persistent_data, {}, waf_timeout)
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match

Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/appsec/contrib/rack/reactive/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def self.publish(engine, gateway_response)
end
end

def self.subscribe(engine, waf_context)
def self.subscribe(engine, context)
engine.subscribe(*ADDRESSES) do |*values|
Datadog.logger.debug { "reacted to #{ADDRESSES.inspect}: #{values.inspect}" }

Expand All @@ -37,7 +37,7 @@ def self.subscribe(engine, waf_context)
}

waf_timeout = Datadog.configuration.appsec.waf_timeout
result = waf_context.run(persistent_data, {}, waf_timeout)
result = context.run_waf(persistent_data, {}, waf_timeout)

next if result.status != :match

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(app, opt = {})
end

def call(env)
context = env[Datadog::AppSec::Ext::SCOPE_KEY]
context = env[Datadog::AppSec::Ext::CONTEXT_KEY]

return @app.call(env) unless context

Expand Down
Loading
Loading