diff --git a/lib/datadog/appsec/contrib/rack/gateway/request.rb b/lib/datadog/appsec/contrib/rack/gateway/request.rb new file mode 100644 index 00000000000..31d6c8a45e2 --- /dev/null +++ b/lib/datadog/appsec/contrib/rack/gateway/request.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require_relative '../../../instrumentation/gateway/argument' +require_relative '../../../../tracing/client_ip' +require_relative '../../../../tracing/contrib/rack/header_collection' + +module Datadog + module AppSec + module Contrib + module Rack + module Gateway + # Gateway Request argument. Normalized extration of data from Rack::Request + class Request < Instrumentation::Gateway::Argument + attr_reader :env + + def initialize(env) + super + @env = env + end + + def request + @request ||= ::Rack::Request.new(env) + end + + def query + # Downstream libddwaf expects keys and values to be extractable + # separately so we can't use [[k, v], ...]. We also want to allow + # duplicate keys, so we use [{k, v}, ...] instead. + request.query_string.split('&').map do |e| + k, v = e.split('=').map { |s| CGI.unescape(s) } + + { k => v } + end + end + + # Rack < 2.0 does not have :each_header + # TODO: We need access to Rack here. We must make sure we are able to load AppSec without Rack, + # TODO: while still ensure correctness in ths code path. + if defined?(::Rack) && ::Rack::Request.instance_methods.include?(:each_header) + def headers + request.each_header.each_with_object({}) do |(k, v), h| + h[k.gsub(/^HTTP_/, '').downcase.tr('_', '-')] = v if k =~ /^HTTP_/ + end + end + else + def headers + request.env.each_with_object({}) do |(k, v), h| + h[k.gsub(/^HTTP_/, '').downcase.tr('_', '-')] = v if k =~ /^HTTP_/ + end + end + end + + def body + request.body.read.tap { request.body.rewind } + end + + def url + request.url + end + + def cookies + request.cookies + end + + def host + request.host + end + + def user_agent + request.user_agent + end + + def remote_addr + env['REMOTE_ADDR'] + end + + def form_hash + # force form data processing + request.POST if request.form_data? + + # usually Hash but can be a more complex + # Hash when e.g coming from JSON + env['rack.request.form_hash'] + end + + def client_ip + remote_ip = remote_addr + headers = Datadog::Tracing::Contrib::Rack::Header::RequestHeaderCollection.new(env) + + result = Datadog::Tracing::ClientIp.raw_ip_from_request(headers, remote_ip) + + if result.raw_ip + ip = Datadog::Tracing::ClientIp.strip_decorations(result.raw_ip) + return unless Datadog::Tracing::ClientIp.valid_ip?(ip) + + ip + end + end + end + end + end + end + end +end diff --git a/lib/datadog/appsec/contrib/rack/gateway/response.rb b/lib/datadog/appsec/contrib/rack/gateway/response.rb new file mode 100644 index 00000000000..edd5fe08687 --- /dev/null +++ b/lib/datadog/appsec/contrib/rack/gateway/response.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative '../../../instrumentation/gateway/argument' + +module Datadog + module AppSec + module Contrib + module Rack + module Gateway + # Gateway Response argument. + class Response < Instrumentation::Gateway::Argument + attr_reader :body, :status, :headers, :active_context + + def initialize(body, status, headers, active_context:) + super + @body = body + @status = status + @headers = headers.each_with_object({}) { |(k, v), h| h[k.downcase] = v } + @active_context = active_context + end + + def response + @response ||= ::Rack::Response.new(body, status, headers) + end + end + end + end + end + end +end diff --git a/lib/datadog/appsec/contrib/rack/gateway/watcher.rb b/lib/datadog/appsec/contrib/rack/gateway/watcher.rb index e870ce35393..273c5144606 100644 --- a/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +++ b/lib/datadog/appsec/contrib/rack/gateway/watcher.rb @@ -22,10 +22,10 @@ def watch end def watch_request(gateway = Instrumentation.gateway) - gateway.watch('rack.request', :appsec) do |stack, request| + gateway.watch('rack.request', :appsec) do |stack, gateway_request| block = false event = nil - waf_context = request.env['datadog.waf.context'] + waf_context = gateway_request.env['datadog.waf.context'] AppSec::Reactive::Operation.new('rack.request') do |op| trace = active_trace @@ -38,7 +38,7 @@ def watch_request(gateway = Instrumentation.gateway) waf_result: result, trace: trace, span: span, - request: request, + request: gateway_request, actions: result.actions } @@ -48,12 +48,12 @@ def watch_request(gateway = Instrumentation.gateway) end end - _result, block = Rack::Reactive::Request.publish(op, request) + _result, block = Rack::Reactive::Request.publish(op, gateway_request) end next [nil, [[:block, event]]] if block - ret, res = stack.call(request) + ret, res = stack.call(gateway_request.request) if event res ||= [] @@ -65,10 +65,10 @@ def watch_request(gateway = Instrumentation.gateway) end def watch_response(gateway = Instrumentation.gateway) - gateway.watch('rack.response', :appsec) do |stack, response| + gateway.watch('rack.response', :appsec) do |stack, gateway_response| block = false event = nil - waf_context = response.instance_eval { @waf_context } + waf_context = gateway_response.active_context AppSec::Reactive::Operation.new('rack.response') do |op| trace = active_trace @@ -81,7 +81,7 @@ def watch_response(gateway = Instrumentation.gateway) waf_result: result, trace: trace, span: span, - response: response, + response: gateway_response, actions: result.actions } @@ -91,12 +91,12 @@ def watch_response(gateway = Instrumentation.gateway) end end - _result, block = Rack::Reactive::Response.publish(op, response) + _result, block = Rack::Reactive::Response.publish(op, gateway_response) end next [nil, [[:block, event]]] if block - ret, res = stack.call(response) + ret, res = stack.call(gateway_response.response) if event res ||= [] @@ -108,10 +108,10 @@ def watch_response(gateway = Instrumentation.gateway) end def watch_request_body(gateway = Instrumentation.gateway) - gateway.watch('rack.request.body', :appsec) do |stack, request| + gateway.watch('rack.request.body', :appsec) do |stack, gateway_request| block = false event = nil - waf_context = request.env['datadog.waf.context'] + waf_context = gateway_request.env['datadog.waf.context'] AppSec::Reactive::Operation.new('rack.request.body') do |op| trace = active_trace @@ -124,7 +124,7 @@ def watch_request_body(gateway = Instrumentation.gateway) waf_result: result, trace: trace, span: span, - request: request, + request: gateway_request, actions: result.actions } @@ -134,12 +134,12 @@ def watch_request_body(gateway = Instrumentation.gateway) end end - _result, block = Rack::Reactive::RequestBody.publish(op, request) + _result, block = Rack::Reactive::RequestBody.publish(op, gateway_request) end next [nil, [[:block, event]]] if block - ret, res = stack.call(request) + ret, res = stack.call(gateway_request.request) if event res ||= [] diff --git a/lib/datadog/appsec/contrib/rack/reactive/request.rb b/lib/datadog/appsec/contrib/rack/reactive/request.rb index 0633e77a93a..d7a4e49162a 100644 --- a/lib/datadog/appsec/contrib/rack/reactive/request.rb +++ b/lib/datadog/appsec/contrib/rack/reactive/request.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../request' - module Datadog module AppSec module Contrib @@ -18,13 +16,13 @@ module Request ].freeze private_constant :ADDRESSES - def self.publish(op, request) + def self.publish(op, gateway_request) catch(:block) do - op.publish('request.query', Rack::Request.query(request)) - op.publish('request.headers', Rack::Request.headers(request)) - op.publish('request.uri.raw', Rack::Request.url(request)) - op.publish('request.cookies', Rack::Request.cookies(request)) - op.publish('request.client_ip', Rack::Request.client_ip(request)) + op.publish('request.query', gateway_request.query) + op.publish('request.headers', gateway_request.headers) + op.publish('request.uri.raw', gateway_request.url) + op.publish('request.cookies', gateway_request.cookies) + op.publish('request.client_ip', gateway_request.client_ip) nil end diff --git a/lib/datadog/appsec/contrib/rack/reactive/request_body.rb b/lib/datadog/appsec/contrib/rack/reactive/request_body.rb index c8872354f41..1316df98e3f 100644 --- a/lib/datadog/appsec/contrib/rack/reactive/request_body.rb +++ b/lib/datadog/appsec/contrib/rack/reactive/request_body.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../request' - module Datadog module AppSec module Contrib @@ -14,10 +12,10 @@ module RequestBody ].freeze private_constant :ADDRESSES - def self.publish(op, request) + def self.publish(op, gateway_request) catch(:block) do # params have been parsed from the request body - op.publish('request.body', Rack::Request.form_hash(request)) + op.publish('request.body', gateway_request.form_hash) nil end diff --git a/lib/datadog/appsec/contrib/rack/reactive/response.rb b/lib/datadog/appsec/contrib/rack/reactive/response.rb index 46566567f0b..fc1fd08fa7a 100644 --- a/lib/datadog/appsec/contrib/rack/reactive/response.rb +++ b/lib/datadog/appsec/contrib/rack/reactive/response.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative '../response' - module Datadog module AppSec module Contrib @@ -14,9 +12,9 @@ module Response ].freeze private_constant :ADDRESSES - def self.publish(op, response) + def self.publish(op, gateway_response) catch(:block) do - op.publish('response.status', Rack::Response.status(response)) + op.publish('response.status', gateway_response.status) nil end diff --git a/lib/datadog/appsec/contrib/rack/request.rb b/lib/datadog/appsec/contrib/rack/request.rb deleted file mode 100644 index 99d42bbf978..00000000000 --- a/lib/datadog/appsec/contrib/rack/request.rb +++ /dev/null @@ -1,76 +0,0 @@ -require_relative '../../../tracing/client_ip' -require_relative '../../../tracing/contrib/rack/header_collection' - -module Datadog - module AppSec - module Contrib - module Rack - # Normalized extration of data from Rack::Request - module Request - def self.query(request) - # Downstream libddwaf expects keys and values to be extractable - # separately so we can't use [[k, v], ...]. We also want to allow - # duplicate keys, so we use [{k, v}, ...] instead. - request.query_string.split('&').map do |e| - k, v = e.split('=').map { |s| CGI.unescape(s) } - - { k => v } - end - end - - # Rack < 2.0 does not have :each_header - # TODO: We need access to Rack here. We must make sure we are able to load AppSec without Rack, - # TODO: while still ensure correctness in ths code path. - if defined?(::Rack) && ::Rack::Request.instance_methods.include?(:each_header) - def self.headers(request) - request.each_header.each_with_object({}) do |(k, v), h| - h[k.gsub(/^HTTP_/, '').downcase.tr('_', '-')] = v if k =~ /^HTTP_/ - end - end - else - def self.headers(request) - request.env.each_with_object({}) do |(k, v), h| - h[k.gsub(/^HTTP_/, '').downcase.tr('_', '-')] = v if k =~ /^HTTP_/ - end - end - end - - def self.body(request) - request.body.read.tap { request.body.rewind } - end - - def self.url(request) - request.url - end - - def self.cookies(request) - request.cookies - end - - def self.form_hash(request) - # force form data processing - request.POST if request.form_data? - - # usually Hash but can be a more complex - # Hash when e.g coming from JSON - request.env['rack.request.form_hash'] - end - - def self.client_ip(request) - remote_ip = request.env['REMOTE_ADDR'] - headers = Datadog::Tracing::Contrib::Rack::Header::RequestHeaderCollection.new(request.env) - - result = Datadog::Tracing::ClientIp.raw_ip_from_request(headers, remote_ip) - - if result.raw_ip - ip = Datadog::Tracing::ClientIp.strip_decorations(result.raw_ip) - return unless Datadog::Tracing::ClientIp.valid_ip?(ip) - - ip - end - end - end - end - end - end -end diff --git a/lib/datadog/appsec/contrib/rack/request_body_middleware.rb b/lib/datadog/appsec/contrib/rack/request_body_middleware.rb index aa7a15e6cf2..e51e8326208 100644 --- a/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +++ b/lib/datadog/appsec/contrib/rack/request_body_middleware.rb @@ -1,3 +1,6 @@ +# frozen_string_literal: true + +require_relative 'gateway/request' require_relative '../../instrumentation/gateway' require_relative '../../response' @@ -20,9 +23,10 @@ def call(env) # TODO: handle exceptions, except for @app.call - request = ::Rack::Request.new(env) - - request_return, request_response = Instrumentation.gateway.push('rack.request.body', request) do + request_return, request_response = Instrumentation.gateway.push( + 'rack.request.body', + Gateway::Request.new(env) + ) do @app.call(env) end diff --git a/lib/datadog/appsec/contrib/rack/request_middleware.rb b/lib/datadog/appsec/contrib/rack/request_middleware.rb index 16a9e257169..b682dd5d319 100644 --- a/lib/datadog/appsec/contrib/rack/request_middleware.rb +++ b/lib/datadog/appsec/contrib/rack/request_middleware.rb @@ -1,5 +1,7 @@ require 'json' +require_relative 'gateway/request' +require_relative 'gateway/response' require_relative '../../ext' require_relative '../../instrumentation/gateway' require_relative '../../processor' @@ -33,12 +35,12 @@ def call(env) context = processor.activate_context env['datadog.waf.context'] = context - request = ::Rack::Request.new(env) + gateway_request = Gateway::Request.new(env) add_appsec_tags(processor, active_trace, active_span, env) request_return, request_response = catch(::Datadog::AppSec::Ext::INTERRUPT) do - Instrumentation.gateway.push('rack.request', request) do + Instrumentation.gateway.push('rack.request', gateway_request) do @app.call(env) end end @@ -47,16 +49,18 @@ def call(env) request_return = AppSec::Response.negotiate(env).to_rack end - response = ::Rack::Response.new(request_return[2], request_return[0], request_return[1]) - response.instance_eval do - @waf_context = context - end + gateway_response = Gateway::Response.new( + request_return[2], + request_return[0], + request_return[1], + active_context: context + ) - _response_return, response_response = Instrumentation.gateway.push('rack.response', response) + _response_return, response_response = Instrumentation.gateway.push('rack.response', gateway_response) context.events.each do |e| - e[:response] ||= response - e[:request] ||= request + e[:response] ||= gateway_response + e[:request] ||= gateway_request end AppSec::Event.record(*context.events) diff --git a/lib/datadog/appsec/contrib/rack/response.rb b/lib/datadog/appsec/contrib/rack/response.rb deleted file mode 100644 index 9df6af65bcd..00000000000 --- a/lib/datadog/appsec/contrib/rack/response.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Datadog - module AppSec - module Contrib - module Rack - # Normalized extration of data from Rack::Response - module Response - def self.headers(response) - response.headers.each_with_object({}) { |(k, v), h| h[k.downcase] = v } - end - - def self.cookies(response) - response.cookies - end - - def self.status(response) - response.status - end - end - end - end - end -end diff --git a/lib/datadog/appsec/contrib/rails/gateway/request.rb b/lib/datadog/appsec/contrib/rails/gateway/request.rb new file mode 100644 index 00000000000..f705a7fd4cc --- /dev/null +++ b/lib/datadog/appsec/contrib/rails/gateway/request.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require_relative '../../../instrumentation/gateway/argument' + +module Datadog + module AppSec + module Contrib + module Rails + module Gateway + # Gateway Request argument. Normalized extration of data from ActionDispatch::Request + class Request < Instrumentation::Gateway::Argument + attr_reader :request + + def initialize(request) + super + @request = request + end + + def env + request.env + end + + def headers + request.headers + end + + def host + request.host + end + + def user_agent + request.user_agent + end + + def remote_addr + request.remote_addr + end + + def parsed_body + # force body parameter parsing, which is done lazily by Rails + request.parameters + + # usually Hash but can be a more complex + # Hash when e.g coming from JSON or + # with Rails advanced param square bracket parsing + body = request.env['action_dispatch.request.request_parameters'] + + return if body.nil? + + body.reject do |k, _v| + request.env['action_dispatch.request.path_parameters'].key?(k) + end + end + + def route_params + excluded = [:controller, :action] + + request.env['action_dispatch.request.path_parameters'].reject do |k, _v| + excluded.include?(k) + end + end + end + end + end + end + end +end diff --git a/lib/datadog/appsec/contrib/rails/gateway/watcher.rb b/lib/datadog/appsec/contrib/rails/gateway/watcher.rb index fbbc2e1cca7..8b6024b1b54 100644 --- a/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +++ b/lib/datadog/appsec/contrib/rails/gateway/watcher.rb @@ -18,10 +18,10 @@ def watch end def watch_request_action(gateway = Instrumentation.gateway) - gateway.watch('rails.request.action', :appsec) do |stack, request| + gateway.watch('rails.request.action', :appsec) do |stack, gateway_request| block = false event = nil - waf_context = request.env['datadog.waf.context'] + waf_context = gateway_request.env['datadog.waf.context'] AppSec::Reactive::Operation.new('rails.request.action') do |op| trace = active_trace @@ -34,7 +34,7 @@ def watch_request_action(gateway = Instrumentation.gateway) waf_result: result, trace: trace, span: span, - request: request, + request: gateway_request, actions: result.actions } @@ -44,12 +44,12 @@ def watch_request_action(gateway = Instrumentation.gateway) end end - _result, block = Rails::Reactive::Action.publish(op, request) + _result, block = Rails::Reactive::Action.publish(op, gateway_request) end next [nil, [[:block, event]]] if block - ret, res = stack.call(request) + ret, res = stack.call(gateway_request.request) if event res ||= [] diff --git a/lib/datadog/appsec/contrib/rails/patcher.rb b/lib/datadog/appsec/contrib/rails/patcher.rb index 406ad0b3c28..c839d71f974 100644 --- a/lib/datadog/appsec/contrib/rails/patcher.rb +++ b/lib/datadog/appsec/contrib/rails/patcher.rb @@ -6,6 +6,7 @@ require_relative '../rack/request_middleware' require_relative '../rack/request_body_middleware' require_relative 'gateway/watcher' +require_relative 'gateway/request' require_relative '../../../tracing/contrib/rack/middlewares' @@ -77,7 +78,8 @@ def process_action(*args) # TODO: handle exceptions, except for super - request_return, request_response = Instrumentation.gateway.push('rails.request.action', request) do + gateway_request = Gateway::Request.new(request) + request_return, request_response = Instrumentation.gateway.push('rails.request.action', gateway_request) do super end diff --git a/lib/datadog/appsec/contrib/rails/reactive/action.rb b/lib/datadog/appsec/contrib/rails/reactive/action.rb index 2f5f73e4632..9dd13a4c982 100644 --- a/lib/datadog/appsec/contrib/rails/reactive/action.rb +++ b/lib/datadog/appsec/contrib/rails/reactive/action.rb @@ -15,11 +15,11 @@ module Action ].freeze private_constant :ADDRESSES - def self.publish(op, request) + def self.publish(op, gateway_request) catch(:block) do # params have been parsed from the request body - op.publish('rails.request.body', Rails::Request.parsed_body(request)) - op.publish('rails.request.route_params', Rails::Request.route_params(request)) + op.publish('rails.request.body', gateway_request.parsed_body) + op.publish('rails.request.route_params', gateway_request.route_params) nil end diff --git a/lib/datadog/appsec/contrib/sinatra/gateway/request.rb b/lib/datadog/appsec/contrib/sinatra/gateway/request.rb new file mode 100644 index 00000000000..cf224ef0125 --- /dev/null +++ b/lib/datadog/appsec/contrib/sinatra/gateway/request.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_relative '../../rack/gateway/request' + +module Datadog + module AppSec + module Contrib + module Sinatra + module Gateway + # Gateway Request argument. Normalized extration of data from Rack::Request + class Request < Rack::Gateway::Request + end + end + end + end + end +end diff --git a/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb b/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb new file mode 100644 index 00000000000..58e4928ac2b --- /dev/null +++ b/lib/datadog/appsec/contrib/sinatra/gateway/route_params.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require_relative '../../../instrumentation/gateway/argument' + +module Datadog + module AppSec + module Contrib + module Sinatra + module Gateway + # Gateway Route Params argument. + class RouteParams < Instrumentation::Gateway::Argument + attr_reader :params + + def initialize(params) + super + @params = params + end + end + end + end + end + end +end diff --git a/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb b/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb index 00de01232d0..4ffba374c17 100644 --- a/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +++ b/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb @@ -20,10 +20,10 @@ def watch end def watch_request_dispatch(gateway = Instrumentation.gateway) - gateway.watch('sinatra.request.dispatch', :appsec) do |stack, request| + gateway.watch('sinatra.request.dispatch', :appsec) do |stack, gateway_request| block = false event = nil - waf_context = request.env['datadog.waf.context'] + waf_context = gateway_request.env['datadog.waf.context'] AppSec::Reactive::Operation.new('sinatra.request.dispatch') do |op| trace = active_trace @@ -36,7 +36,7 @@ def watch_request_dispatch(gateway = Instrumentation.gateway) waf_result: result, trace: trace, span: span, - request: request, + request: gateway_request, actions: result.actions } @@ -46,12 +46,12 @@ def watch_request_dispatch(gateway = Instrumentation.gateway) end end - _result, block = Rack::Reactive::RequestBody.publish(op, request) + _result, block = Rack::Reactive::RequestBody.publish(op, gateway_request) end next [nil, [[:block, event]]] if block - ret, res = stack.call(request) + ret, res = stack.call(gateway_request.request) if event res ||= [] @@ -63,10 +63,10 @@ def watch_request_dispatch(gateway = Instrumentation.gateway) end def watch_request_routed(gateway = Instrumentation.gateway) - gateway.watch('sinatra.request.routed', :appsec) do |stack, (request, route_params)| + gateway.watch('sinatra.request.routed', :appsec) do |stack, (gateway_request, gateway_route_params)| block = false event = nil - waf_context = request.env['datadog.waf.context'] + waf_context = gateway_request.env['datadog.waf.context'] AppSec::Reactive::Operation.new('sinatra.request.routed') do |op| trace = active_trace @@ -79,7 +79,7 @@ def watch_request_routed(gateway = Instrumentation.gateway) waf_result: result, trace: trace, span: span, - request: request, + request: gateway_request, actions: result.actions } @@ -89,12 +89,12 @@ def watch_request_routed(gateway = Instrumentation.gateway) end end - _result, block = Sinatra::Reactive::Routed.publish(op, [request, route_params]) + _result, block = Sinatra::Reactive::Routed.publish(op, [gateway_request, gateway_route_params]) end next [nil, [[:block, event]]] if block - ret, res = stack.call(request) + ret, res = stack.call(gateway_request.request) if event res ||= [] diff --git a/lib/datadog/appsec/contrib/sinatra/patcher.rb b/lib/datadog/appsec/contrib/sinatra/patcher.rb index 4683c681f95..7617432fe5b 100644 --- a/lib/datadog/appsec/contrib/sinatra/patcher.rb +++ b/lib/datadog/appsec/contrib/sinatra/patcher.rb @@ -5,6 +5,8 @@ require_relative '../rack/request_middleware' require_relative 'framework' require_relative 'gateway/watcher' +require_relative 'gateway/route_params' +require_relative 'gateway/request' require_relative '../../../tracing/contrib/sinatra/framework' module Datadog @@ -55,7 +57,9 @@ def dispatch! # TODO: handle exceptions, except for super - request_return, request_response = Instrumentation.gateway.push('sinatra.request.dispatch', request) do + gateway_request = Gateway::Request.new(env) + + request_return, request_response = Instrumentation.gateway.push('sinatra.request.dispatch', gateway_request) do # handle process_route interruption catch(Ext::ROUTE_INTERRUPT) { super } end @@ -90,7 +94,13 @@ def process_route(*) # At this point params has both route params and normal params. route_params = params.each.with_object({}) { |(k, v), h| h[k] = v unless base_params.key?(k) } - _, request_response = Instrumentation.gateway.push('sinatra.request.routed', [request, route_params]) + gateway_request = Gateway::Request.new(env) + gateway_route_params = Gateway::RouteParams.new(route_params) + + _, request_response = Instrumentation.gateway.push( + 'sinatra.request.routed', + [gateway_request, gateway_route_params] + ) if request_response && request_response.any? { |action, _event| action == :block } self.response = AppSec::Response.negotiate(env).to_sinatra_response diff --git a/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb b/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb index 82eb80657b9..292a927090f 100644 --- a/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb +++ b/lib/datadog/appsec/contrib/sinatra/reactive/routed.rb @@ -16,7 +16,7 @@ def self.publish(op, data) _request, route_params = data catch(:block) do - op.publish('sinatra.request.route_params', route_params) + op.publish('sinatra.request.route_params', route_params.params) nil end diff --git a/lib/datadog/appsec/event.rb b/lib/datadog/appsec/event.rb index 241e2bf81ae..dd573242bce 100644 --- a/lib/datadog/appsec/event.rb +++ b/lib/datadog/appsec/event.rb @@ -1,7 +1,5 @@ require 'json' -require_relative 'contrib/rack/request' -require_relative 'contrib/rack/response' require_relative 'rate_limiter' module Datadog @@ -50,7 +48,7 @@ def self.record(*events) end # rubocop:disable Metrics/MethodLength - def self.record_via_span(*events) # rubocop:disable Metrics/AbcSize + def self.record_via_span(*events) events.group_by { |e| e[:trace] }.each do |trace, event_group| unless trace Datadog.logger.debug { "{ error: 'no trace: cannot record', event_group: #{event_group.inspect}}" } @@ -68,7 +66,7 @@ def self.record_via_span(*events) # rubocop:disable Metrics/AbcSize # TODO: assume HTTP request context for now if (request = event[:request]) - request_headers = AppSec::Contrib::Rack::Request.headers(request).select do |k, _| + request_headers = request.headers.select do |k, _| ALLOWED_REQUEST_HEADERS.include?(k.downcase) end @@ -78,11 +76,11 @@ def self.record_via_span(*events) # rubocop:disable Metrics/AbcSize tags['http.host'] = request.host tags['http.useragent'] = request.user_agent - tags['network.client.ip'] = request.env['REMOTE_ADDR'] if request.env['REMOTE_ADDR'] + tags['network.client.ip'] = request.remote_addr end if (response = event[:response]) - response_headers = AppSec::Contrib::Rack::Response.headers(response).select do |k, _| + response_headers = response.headers.select do |k, _| ALLOWED_RESPONSE_HEADERS.include?(k.downcase) end diff --git a/lib/datadog/appsec/instrumentation.rb b/lib/datadog/appsec/instrumentation.rb new file mode 100644 index 00000000000..583b0cf24bb --- /dev/null +++ b/lib/datadog/appsec/instrumentation.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Datadog + module AppSec + # Instrumentation for AppSec + module Instrumentation + end + end +end diff --git a/lib/datadog/appsec/instrumentation/gateway/argument.rb b/lib/datadog/appsec/instrumentation/gateway/argument.rb new file mode 100644 index 00000000000..c4e11b10686 --- /dev/null +++ b/lib/datadog/appsec/instrumentation/gateway/argument.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Datadog + module AppSec + module Instrumentation + class Gateway + # Base class for Gateway Arguments + class Argument + def initialize(*); end + end + + # Gateway User argument + class User < Argument + attr_reader :id + + def initialize(id) + super + @id = id + end + end + end + end + end +end diff --git a/lib/datadog/kit/identity.rb b/lib/datadog/kit/identity.rb index b2bd8115e50..8f9ccdafe20 100644 --- a/lib/datadog/kit/identity.rb +++ b/lib/datadog/kit/identity.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative '../appsec/instrumentation/gateway/argument' + module Datadog module Kit # Tracking identity via traces @@ -57,7 +59,7 @@ def self.set_user(trace, id:, email: nil, name: nil, session_id: nil, role: nil, end if Datadog.configuration.appsec.enabled - user = OpenStruct.new(id: id) + user = ::Datadog::AppSec::Instrumentation::Gateway::User.new(id) ::Datadog::AppSec::Instrumentation.gateway.push('identity.set_user', user) end end diff --git a/sig/datadog/appsec/contrib/rack/gateway/request.rbs b/sig/datadog/appsec/contrib/rack/gateway/request.rbs new file mode 100644 index 00000000000..882464cf096 --- /dev/null +++ b/sig/datadog/appsec/contrib/rack/gateway/request.rbs @@ -0,0 +1,37 @@ +module Datadog + module AppSec + module Contrib + module Rack + module Gateway + class Request < Instrumentation::Gateway::Argument + attr_reader env: untyped + + def initialize: (untyped env) -> void + + def request: () -> untyped + + def query: () -> untyped + + def headers: () -> untyped + + def body: () -> untyped + + def url: () -> untyped + + def cookies: () -> untyped + + def host: () -> untyped + + def user_agent: () -> untyped + + def remote_addr: () -> untyped + + def form_hash: () -> untyped + + def client_ip: () -> (nil | untyped) + end + end + end + end + end +end diff --git a/sig/datadog/appsec/contrib/rack/gateway/response.rbs b/sig/datadog/appsec/contrib/rack/gateway/response.rbs new file mode 100644 index 00000000000..717b896bcee --- /dev/null +++ b/sig/datadog/appsec/contrib/rack/gateway/response.rbs @@ -0,0 +1,23 @@ +module Datadog + module AppSec + module Contrib + module Rack + module Gateway + class Response < Instrumentation::Gateway::Argument + attr_reader body: String + + attr_reader status: untyped + + attr_reader headers: untyped + + attr_reader active_context: Datadog::AppSec::Processor::Context + + def initialize: (untyped body, untyped status, untyped headers, active_context: Datadog::AppSec::Processor::Context) -> void + + def response: () -> untyped + end + end + end + end + end +end diff --git a/sig/datadog/appsec/contrib/rack/reactive/request.rbs b/sig/datadog/appsec/contrib/rack/reactive/request.rbs index b301bdfefab..7163eff15e3 100644 --- a/sig/datadog/appsec/contrib/rack/reactive/request.rbs +++ b/sig/datadog/appsec/contrib/rack/reactive/request.rbs @@ -6,7 +6,7 @@ module Datadog module Request ADDRESSES: ::Array[::String] - def self.publish: (untyped op, untyped request) -> untyped + def self.publish: (untyped op, Datadog::AppSec::Contrib::Rack::Gateway::Request request) -> untyped def self.subscribe: (untyped op, untyped waf_context) { (untyped) -> untyped } -> untyped end diff --git a/sig/datadog/appsec/contrib/rack/reactive/request_body.rbs b/sig/datadog/appsec/contrib/rack/reactive/request_body.rbs index 7c8e37935ae..3d04cb7c5ed 100644 --- a/sig/datadog/appsec/contrib/rack/reactive/request_body.rbs +++ b/sig/datadog/appsec/contrib/rack/reactive/request_body.rbs @@ -6,7 +6,7 @@ module Datadog module RequestBody ADDRESSES: ::Array[::String] - def self.publish: (untyped op, untyped request) -> untyped + def self.publish: (untyped op, Datadog::AppSec::Contrib::Rack::Gateway::Response request) -> untyped def self.subscribe: (untyped op, untyped waf_context) { (untyped) -> untyped } -> untyped end diff --git a/sig/datadog/appsec/contrib/rack/reactive/response.rbs b/sig/datadog/appsec/contrib/rack/reactive/response.rbs index 5dbbfae442d..2d2a9edddc1 100644 --- a/sig/datadog/appsec/contrib/rack/reactive/response.rbs +++ b/sig/datadog/appsec/contrib/rack/reactive/response.rbs @@ -6,7 +6,7 @@ module Datadog module Response ADDRESSES: ::Array[::String] - def self.publish: (untyped op, untyped response) -> untyped + def self.publish: (untyped op, Datadog::AppSec::Contrib::Rack::Gateway::Response response) -> untyped def self.subscribe: (untyped op, untyped waf_context) { (untyped) -> untyped } -> untyped end diff --git a/sig/datadog/appsec/contrib/rack/request.rbs b/sig/datadog/appsec/contrib/rack/request.rbs deleted file mode 100644 index 5db94c42c77..00000000000 --- a/sig/datadog/appsec/contrib/rack/request.rbs +++ /dev/null @@ -1,23 +0,0 @@ -module Datadog - module AppSec - module Contrib - module Rack - module Request - def self.query: (untyped request) -> untyped - - def self.headers: (untyped request) -> untyped - - def self.body: (untyped request) -> untyped - - def self.url: (untyped request) -> untyped - - def self.cookies: (untyped request) -> untyped - - def self.form_hash: (untyped request) -> untyped - - def self.client_ip: (untyped request) -> (nil | untyped) - end - end - end - end -end diff --git a/sig/datadog/appsec/contrib/rack/response.rbs b/sig/datadog/appsec/contrib/rack/response.rbs deleted file mode 100644 index 6f4954e68a4..00000000000 --- a/sig/datadog/appsec/contrib/rack/response.rbs +++ /dev/null @@ -1,15 +0,0 @@ -module Datadog - module AppSec - module Contrib - module Rack - module Response - def self.headers: (untyped response) -> untyped - - def self.cookies: (untyped response) -> untyped - - def self.status: (untyped response) -> untyped - end - end - end - end -end diff --git a/sig/datadog/appsec/contrib/rails/gateway/request.rbs b/sig/datadog/appsec/contrib/rails/gateway/request.rbs new file mode 100644 index 00000000000..9c0f261930a --- /dev/null +++ b/sig/datadog/appsec/contrib/rails/gateway/request.rbs @@ -0,0 +1,29 @@ +module Datadog + module AppSec + module Contrib + module Rails + module Gateway + class Request < Instrumentation::Gateway::Argument + attr_reader request: untyped + + def initialize: (untyped request) -> void + + def env: () -> untyped + + def headers: () -> untyped + + def host: () -> untyped + + def user_agent: () -> untyped + + def remote_addr: () -> untyped + + def parsed_body: () -> (nil | untyped) + + def route_params: () -> untyped + end + end + end + end + end +end diff --git a/sig/datadog/appsec/contrib/rails/reactive/action.rbs b/sig/datadog/appsec/contrib/rails/reactive/action.rbs index 20235a22a32..f24f3c62ff5 100644 --- a/sig/datadog/appsec/contrib/rails/reactive/action.rbs +++ b/sig/datadog/appsec/contrib/rails/reactive/action.rbs @@ -6,7 +6,7 @@ module Datadog module Action ADDRESSES: ::Array[::String] - def self.publish: (untyped op, untyped request) -> untyped + def self.publish: (untyped op, Datadog::AppSec::Contrib::Rails::Gateway::Request request) -> untyped def self.subscribe: (untyped op, untyped waf_context) { (untyped) -> untyped } -> untyped end diff --git a/sig/datadog/appsec/contrib/sinatra/gateway/route_params.rbs b/sig/datadog/appsec/contrib/sinatra/gateway/route_params.rbs new file mode 100644 index 00000000000..7eaa17d233c --- /dev/null +++ b/sig/datadog/appsec/contrib/sinatra/gateway/route_params.rbs @@ -0,0 +1,15 @@ +module Datadog + module AppSec + module Contrib + module Sinatra + module Gateway + class RouteParams < Instrumentation::Gateway::Argument + attr_reader params: untyped + + def initialize: (untyped params) -> void + end + end + end + end + end +end diff --git a/sig/datadog/appsec/contrib/sinatra/reactive/routed.rbs b/sig/datadog/appsec/contrib/sinatra/reactive/routed.rbs index f74eacb211b..de3fa0a0919 100644 --- a/sig/datadog/appsec/contrib/sinatra/reactive/routed.rbs +++ b/sig/datadog/appsec/contrib/sinatra/reactive/routed.rbs @@ -6,7 +6,7 @@ module Datadog module Routed ADDRESSES: ::Array[::String] - def self.publish: (untyped op, untyped data) -> untyped + def self.publish: (untyped op, ::Array[Datadog::AppSec::Instrumentation::Gateway::Argument] data) -> untyped def self.subscribe: (untyped op, untyped waf_context) { (untyped) -> untyped } -> untyped end diff --git a/sig/datadog/appsec/instrumentation/gateway.rbs b/sig/datadog/appsec/instrumentation/gateway.rbs index b8dc97f5038..eee8075d1c9 100644 --- a/sig/datadog/appsec/instrumentation/gateway.rbs +++ b/sig/datadog/appsec/instrumentation/gateway.rbs @@ -6,7 +6,6 @@ module Datadog type final_call_result = untyped type final_call = ^() -> final_call_result type stack = ::Proc - type env = untyped # TODO: this should be a struct type stack_result = ::Array[nil | middleware_result | final_call_result] @@ -15,15 +14,15 @@ module Datadog attr_reader key: ::Symbol attr_reader block: ::Proc - def initialize: (::Symbol key) { (stack next, env env) -> stack_result } -> void - def call: (stack next, env env) -> stack_result + def initialize: (::Symbol key) { (stack next, Instrumentation::Gateway::Argument env) -> stack_result } -> void + def call: (stack next, Gateway::Argument? env) -> stack_result end @middlewares: ::Hash[::String, ::Array[Middleware]] def initialize: () -> void - def push: (::String name, env env) { () -> final_call_result } -> stack_result - def watch: (::String name, ::Symbol key) { (stack next, env env) -> stack_result } -> void + def push: (::String name, Gateway::Argument env) ?{ () -> final_call_result } -> stack_result + def watch: (::String name, ::Symbol key) { (stack next, Gateway::Argument env) -> stack_result } -> void private diff --git a/sig/datadog/appsec/instrumentation/gateway/argument.rbs b/sig/datadog/appsec/instrumentation/gateway/argument.rbs new file mode 100644 index 00000000000..b27314102fe --- /dev/null +++ b/sig/datadog/appsec/instrumentation/gateway/argument.rbs @@ -0,0 +1,17 @@ +module Datadog + module AppSec + module Instrumentation + class Gateway + class Argument + def initialize: (*untyped) -> void + end + + class User < Argument + attr_reader id: String + + def initialize: (String id) -> void + end + end + end + end +end diff --git a/sig/datadog/appsec/monitor/reactive/set_user.rbs b/sig/datadog/appsec/monitor/reactive/set_user.rbs index 529d31fe2d2..49fa18ff860 100644 --- a/sig/datadog/appsec/monitor/reactive/set_user.rbs +++ b/sig/datadog/appsec/monitor/reactive/set_user.rbs @@ -5,7 +5,7 @@ module Datadog module SetUser ADDRESSES: ::Array[::String] - def self.publish: (untyped op, untyped user) -> untyped + def self.publish: (untyped op, Datadog::AppSec::Instrumentation::Gateway::User user) -> untyped def self.subscribe: (untyped op, untyped waf_context) { (untyped) -> untyped } -> untyped end diff --git a/spec/datadog/appsec/contrib/rack/gateway/request_spec.rb b/spec/datadog/appsec/contrib/rack/gateway/request_spec.rb new file mode 100644 index 00000000000..1ee715a1414 --- /dev/null +++ b/spec/datadog/appsec/contrib/rack/gateway/request_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'datadog/appsec/spec_helper' +require 'datadog/appsec/contrib/rack/gateway/request' +require 'rack' + +RSpec.describe Datadog::AppSec::Contrib::Rack::Gateway::Request do + let(:request) do + described_class.new( + Rack::MockRequest.env_for( + 'http://example.com:8080/?a=foo', + { 'REMOTE_ADDR' => '10.10.10.10', 'HTTP_CONTENT_TYPE' => 'text/html', 'HTTP_COOKIE' => 'foo=bar', + 'HTTP_USER_AGENT' => 'WebKit' } + ) + ) + end + + describe '#query' do + it 'returns URL query information' do + expect(request.query).to eq([{ 'a' => 'foo' }]) + end + end + + describe '#headers' do + it 'returns the header information and strip the HTTP_ prefix' do + expected_headers = { 'content-type' => 'text/html', 'cookie' => 'foo=bar', 'user-agent' => 'WebKit' } + expect(request.headers).to eq(expected_headers) + end + end + + describe '#body' do + it 'returns the body' do + expect(request.body).to eq('') + end + end + + describe '#url' do + it 'returns the url' do + expect(request.url).to eq('http://example.com:8080/?a=foo') + end + end + + describe '#host' do + it 'returns the host' do + expect(request.host).to eq('example.com') + end + end + + describe '#cookies' do + it 'returns the cookie information' do + expect(request.cookies).to eq({ 'foo' => 'bar' }) + end + end + + describe '#user_agent' do + it 'returns the user agnet information' do + expect(request.user_agent).to eq('WebKit') + end + end + + describe '#remote_addr' do + it 'returns the remote address information' do + expect(request.remote_addr).to eq('10.10.10.10') + end + end + + describe '#client_ip' do + it 'returns the client_ip' do + expect(request.client_ip).to eq('10.10.10.10') + end + end + + describe '#form_hash' do + context 'GET request' do + it 'returns nil' do + expect(request.form_hash).to be_nil + end + end + + context 'POST request' do + let(:request) do + described_class.new( + Rack::MockRequest.env_for( + 'http://example.com:8080/?a=foo', + { method: 'POST', input: 'name=john', 'REMOTE_ADDR' => '10.10.10.10' } + ) + ) + end + + it 'returns information' do + expect(request.form_hash).to eq({ 'name' => 'john' }) + end + end + end +end diff --git a/spec/datadog/appsec/contrib/rack/gateway/response_spec.rb b/spec/datadog/appsec/contrib/rack/gateway/response_spec.rb new file mode 100644 index 00000000000..b42b30bf532 --- /dev/null +++ b/spec/datadog/appsec/contrib/rack/gateway/response_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'datadog/appsec/spec_helper' +require 'datadog/appsec/contrib/rack/gateway/request' +require 'datadog/appsec/processor' +require 'rack' + +RSpec.describe Datadog::AppSec::Contrib::Rack::Gateway::Response do + let(:response) do + described_class.new( + 'Ok', + 200, + { 'Content-Type' => 'text/html' }, + active_context: instance_double(Datadog::AppSec::Processor::Context) + ) + end + + describe '#body' do + it 'returns the body' do + expect(response.body).to eq('Ok') + end + end + + describe '#status' do + it 'returns the status' do + expect(response.status).to eq(200) + end + end + + describe '#headers' do + it 'returns the headers' do + expect(response.headers).to eq({ 'content-type' => 'text/html' }) + end + end + + describe '#response' do + it 'returns a rack response object' do + expect(response.response).to be_a(Rack::Response) + end + end +end diff --git a/spec/datadog/appsec/contrib/rack/reactive/request_body_spec.rb b/spec/datadog/appsec/contrib/rack/reactive/request_body_spec.rb index fdc9a937402..69f4a2837b1 100644 --- a/spec/datadog/appsec/contrib/rack/reactive/request_body_spec.rb +++ b/spec/datadog/appsec/contrib/rack/reactive/request_body_spec.rb @@ -2,13 +2,14 @@ require 'datadog/appsec/spec_helper' require 'datadog/appsec/reactive/operation' +require 'datadog/appsec/contrib/rack/gateway/request' require 'datadog/appsec/contrib/rack/reactive/request_body' require 'rack' RSpec.describe Datadog::AppSec::Contrib::Rack::Reactive::RequestBody do let(:operation) { Datadog::AppSec::Reactive::Operation.new('test') } let(:request) do - Rack::Request.new( + Datadog::AppSec::Contrib::Rack::Gateway::Request.new( Rack::MockRequest.env_for( 'http://example.com:8080/?a=foo', { method: 'POST', params: { 'foo' => 'bar' } } diff --git a/spec/datadog/appsec/contrib/rack/reactive/request_spec.rb b/spec/datadog/appsec/contrib/rack/reactive/request_spec.rb index 2f32aefdf03..65dafa16735 100644 --- a/spec/datadog/appsec/contrib/rack/reactive/request_spec.rb +++ b/spec/datadog/appsec/contrib/rack/reactive/request_spec.rb @@ -2,13 +2,14 @@ require 'datadog/appsec/spec_helper' require 'datadog/appsec/reactive/operation' +require 'datadog/appsec/contrib/rack/gateway/request' require 'datadog/appsec/contrib/rack/reactive/request' require 'rack' RSpec.describe Datadog::AppSec::Contrib::Rack::Reactive::Request do let(:operation) { Datadog::AppSec::Reactive::Operation.new('test') } let(:request) do - Rack::Request.new( + Datadog::AppSec::Contrib::Rack::Gateway::Request.new( Rack::MockRequest.env_for( 'http://example.com:8080/?a=foo', { 'REMOTE_ADDR' => '10.10.10.10', 'HTTP_CONTENT_TYPE' => 'text/html' } diff --git a/spec/datadog/appsec/contrib/rack/reactive/response_spec.rb b/spec/datadog/appsec/contrib/rack/reactive/response_spec.rb index 9f11a1046b8..8b027c79ff2 100644 --- a/spec/datadog/appsec/contrib/rack/reactive/response_spec.rb +++ b/spec/datadog/appsec/contrib/rack/reactive/response_spec.rb @@ -1,14 +1,17 @@ # frozen_string_literal: true require 'datadog/appsec/spec_helper' +require 'datadog/appsec/processor' require 'datadog/appsec/reactive/operation' +require 'datadog/appsec/contrib/rack/gateway/response' require 'datadog/appsec/contrib/rack/reactive/response' -require 'rack' RSpec.describe Datadog::AppSec::Contrib::Rack::Reactive::Response do let(:operation) { Datadog::AppSec::Reactive::Operation.new('test') } + let(:waf_context) { instance_double(Datadog::AppSec::Processor::Context) } + let(:response) do - Rack::Response.new + Datadog::AppSec::Contrib::Rack::Gateway::Response.new('Ok', 200, {}, active_context: waf_context) end describe '.publish' do @@ -20,8 +23,6 @@ end describe '.subscribe' do - let(:waf_context) { double(:waf_context) } - context 'not all addresses have been published' do it 'does not call the waf context' do expect(operation).to receive(:subscribe).with('response.status').and_call_original diff --git a/spec/datadog/appsec/contrib/rails/reactive/action_spec.rb b/spec/datadog/appsec/contrib/rails/reactive/action_spec.rb index a68f9a1b174..b2c604d3463 100644 --- a/spec/datadog/appsec/contrib/rails/reactive/action_spec.rb +++ b/spec/datadog/appsec/contrib/rails/reactive/action_spec.rb @@ -1,6 +1,7 @@ require 'datadog/appsec/spec_helper' require 'datadog/appsec/reactive/operation' require 'datadog/appsec/contrib/rails/reactive/action' +require 'datadog/appsec/contrib/rails/gateway/request' require 'action_dispatch' RSpec.describe Datadog::AppSec::Contrib::Rails::Reactive::Action do @@ -11,11 +12,13 @@ { method: 'POST', params: { 'foo' => 'bar' }, 'action_dispatch.request.path_parameters' => { id: '1234' } } ) - if ActionDispatch::TestRequest.respond_to?(:create) - ActionDispatch::TestRequest.create(request_env) - else - ActionDispatch::TestRequest.new(request_env) - end + rails_request = if ActionDispatch::TestRequest.respond_to?(:create) + ActionDispatch::TestRequest.create(request_env) + else + ActionDispatch::TestRequest.new(request_env) + end + + Datadog::AppSec::Contrib::Rails::Gateway::Request.new(rails_request) end describe '.publish' do diff --git a/spec/datadog/appsec/contrib/sinatra/reactive/routed_spec.rb b/spec/datadog/appsec/contrib/sinatra/reactive/routed_spec.rb index c066866e8f8..32a0530e377 100644 --- a/spec/datadog/appsec/contrib/sinatra/reactive/routed_spec.rb +++ b/spec/datadog/appsec/contrib/sinatra/reactive/routed_spec.rb @@ -3,24 +3,27 @@ require 'datadog/appsec/spec_helper' require 'datadog/appsec/reactive/operation' require 'datadog/appsec/contrib/sinatra/reactive/routed' +require 'datadog/appsec/contrib/rack/gateway/request' +require 'datadog/appsec/contrib/sinatra/gateway/route_params' require 'rack' RSpec.describe Datadog::AppSec::Contrib::Sinatra::Reactive::Routed do let(:operation) { Datadog::AppSec::Reactive::Operation.new('test') } let(:request) do - Rack::Request.new( + Datadog::AppSec::Contrib::Rack::Gateway::Request.new( Rack::MockRequest.env_for( 'http://example.com:8080/?a=foo', { 'REMOTE_ADDR' => '10.10.10.10', 'HTTP_CONTENT_TYPE' => 'text/html' } ) ) end + let(:routed_params) { Datadog::AppSec::Contrib::Sinatra::Gateway::RouteParams.new({ id: '1234' }) } describe '.publish' do it 'propagates routed params attributes to the operation' do expect(operation).to receive(:publish).with('sinatra.request.route_params', { id: '1234' }) - described_class.publish(operation, [request, { id: '1234' }]) + described_class.publish(operation, [request, routed_params]) end end @@ -49,7 +52,7 @@ Datadog::AppSec.settings.waf_timeout ).and_return(waf_result) described_class.subscribe(operation, waf_context) - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end end @@ -64,7 +67,7 @@ expect(result).to eq(waf_result) expect(block).to eq(false) end - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end @@ -77,7 +80,7 @@ expect(result).to eq(waf_result) expect(block).to eq(true) end - publish_result, publish_block = described_class.publish(operation, [request, { id: '1234' }]) + publish_result, publish_block = described_class.publish(operation, [request, routed_params]) expect(publish_result).to eq(waf_result) expect(publish_block).to eq(true) end @@ -90,7 +93,7 @@ waf_result = double(:waf_result, status: :ok, timeout: false) expect(waf_context).to receive(:run).and_return(waf_result) expect { |b| described_class.subscribe(operation, waf_context, &b) }.not_to yield_control - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end end @@ -102,7 +105,7 @@ waf_result = double(:waf_result, status: :invalid_call, timeout: false) expect(waf_context).to receive(:run).and_return(waf_result) expect { |b| described_class.subscribe(operation, waf_context, &b) }.not_to yield_control - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end end @@ -114,7 +117,7 @@ waf_result = double(:waf_result, status: :invalid_rule, timeout: false) expect(waf_context).to receive(:run).and_return(waf_result) expect { |b| described_class.subscribe(operation, waf_context, &b) }.not_to yield_control - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end end @@ -126,7 +129,7 @@ waf_result = double(:waf_result, status: :invalid_flow, timeout: false) expect(waf_context).to receive(:run).and_return(waf_result) expect { |b| described_class.subscribe(operation, waf_context, &b) }.not_to yield_control - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end end @@ -138,7 +141,7 @@ waf_result = double(:waf_result, status: :no_rule, timeout: false) expect(waf_context).to receive(:run).and_return(waf_result) expect { |b| described_class.subscribe(operation, waf_context, &b) }.not_to yield_control - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end end @@ -150,7 +153,7 @@ waf_result = double(:waf_result, status: :foo, timeout: false) expect(waf_context).to receive(:run).and_return(waf_result) expect { |b| described_class.subscribe(operation, waf_context, &b) }.not_to yield_control - result = described_class.publish(operation, [request, { id: '1234' }]) + result = described_class.publish(operation, [request, routed_params]) expect(result).to be_nil end end diff --git a/spec/datadog/appsec/event_spec.rb b/spec/datadog/appsec/event_spec.rb index 798ff67cbd9..5ce108e1525 100644 --- a/spec/datadog/appsec/event_spec.rb +++ b/spec/datadog/appsec/event_spec.rb @@ -18,18 +18,13 @@ allow(dbl).to receive(:host).and_return('example.com') allow(dbl).to receive(:user_agent).and_return('Ruby/0.0') - allow(dbl).to receive(:ip).and_return('127.0.0.1') + allow(dbl).to receive(:remote_addr).and_return('127.0.0.1') - allow(dbl).to receive(:each_header).and_return [ - ['HTTP_USER_AGENT', 'Ruby/0.0'], + allow(dbl).to receive(:headers).and_return [ + ['user-agent', 'Ruby/0.0'], ['SERVER_NAME', 'example.com'], ['REMOTE_ADDR', '127.0.0.1'] ] - allow(dbl).to receive(:env).and_return( - 'HTTP_USER_AGENT' => 'Ruby/0.0', - 'SERVER_NAME' => 'example.com', - 'REMOTE_ADDR' => '127.0.0.1' - ) dbl end diff --git a/spec/datadog/kit/identity_spec.rb b/spec/datadog/kit/identity_spec.rb index b270f7c2755..eef36e492c3 100644 --- a/spec/datadog/kit/identity_spec.rb +++ b/spec/datadog/kit/identity_spec.rb @@ -235,10 +235,9 @@ context 'when is enabled' do it 'instruments the user information to appsec' do Datadog.configuration.appsec.enabled = true - user = OpenStruct.new(id: '42') expect_any_instance_of(Datadog::AppSec::Instrumentation::Gateway).to receive(:push).with( 'identity.set_user', - user + instance_of(Datadog::AppSec::Instrumentation::Gateway::User) ) described_class.set_user(trace_op, id: '42') end