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

Introduce AppSec::Instrumentation::Gateway::Argument #2648

Merged
merged 7 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions lib/datadog/appsec/contrib/rails/gateway/request.rb
Original file line number Diff line number Diff line change
@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to inherit from Contrib::Rack::Gateway::Request?

Copy link
Member Author

Choose a reason for hiding this comment

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

At first, I thought about, but since I saw we use the Rack::RequestBody directly in the Sinatra contrib folder I thought it would be ok.

I'm happy to change it.

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<String,String> but can be a more complex
# Hash<String,String||Array||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
10 changes: 5 additions & 5 deletions lib/datadog/appsec/contrib/rails/gateway/watcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}

Expand All @@ -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 ||= []
Expand Down
4 changes: 3 additions & 1 deletion lib/datadog/appsec/contrib/rails/patcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions lib/datadog/appsec/contrib/rails/reactive/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions sig/datadog/appsec/contrib/rails/gateway/request.rbs
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion sig/datadog/appsec/contrib/rails/reactive/action.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 8 additions & 5 deletions spec/datadog/appsec/contrib/rails/reactive/action_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down