Skip to content

Commit 89e2082

Browse files
authored
Merge pull request #4391 from DataDog/appsec-add-faraday-instrumentation
Add Faraday instrumentation for AppSec
2 parents bcf28ce + 0158995 commit 89e2082

File tree

18 files changed

+473
-1
lines changed

18 files changed

+473
-1
lines changed

Matrixfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,10 @@
296296
'graphql-2.0' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
297297
'graphql-1.13' => '❌ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
298298
},
299+
'appsec:http' => {
300+
'http' => '✅ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby',
301+
'contrib-old' => '❌ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ✅ jruby'
302+
},
299303
'di:active_record' => {
300304
'rails61-mysql2' => '❌ 2.5 / ✅ 2.6 / ✅ 2.7 / ✅ 3.0 / ✅ 3.1 / ✅ 3.2 / ✅ 3.3 / ✅ 3.4 / ❌ jruby',
301305
},

Rakefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ namespace :spec do
276276
end
277277

278278
namespace :appsec do
279-
task all: [:main, :active_record, :rack, :rails, :sinatra, :devise, :graphql, :integration]
279+
task all: [:main, :active_record, :rack, :rails, :sinatra, :devise, :graphql, :http, :integration]
280280

281281
# Datadog AppSec main specs
282282
desc '' # "Explicitly hiding from `rake -T`"
@@ -302,6 +302,7 @@ namespace :spec do
302302
:rails,
303303
:devise,
304304
:graphql,
305+
:http
305306
].each do |contrib|
306307
desc '' # "Explicitly hiding from `rake -T`"
307308
RSpec::Core::RakeTask.new(contrib) do |t, args|

appraisal/ruby-3.3.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188

189189
appraise 'rails-app' do
190190
gem 'devise', '~> 4.9'
191+
gem 'faraday', '~> 2.0'
191192
gem 'rack', '~> 2'
192193
gem 'rack-contrib', '~> 2'
193194
gem 'rack-test' # Dev dependencies for testing rack-based code

gemfiles/ruby_3.3_rails_app.gemfile

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gemfiles/ruby_3.3_rails_app.gemfile.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/datadog/appsec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ def components
6868
require_relative 'appsec/contrib/active_record/integration'
6969
require_relative 'appsec/contrib/devise/integration'
7070
require_relative 'appsec/contrib/graphql/integration'
71+
require_relative 'appsec/contrib/faraday/integration'
7172

7273
require_relative 'appsec/autoload'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
module Datadog
4+
module AppSec
5+
module Contrib
6+
module Faraday
7+
# Handles installation of our middleware if the user has *not*
8+
# already explicitly configured our middleware for this correction.
9+
#
10+
# Wraps Faraday::Connection#initialize:
11+
# https://github.com/lostisland/faraday/blob/ff9dc1d1219a1bbdba95a9a4cf5d135b97247ee2/lib/faraday/connection.rb#L62-L92
12+
module ConnectionPatch
13+
def initialize(*args, &block)
14+
super.tap do
15+
use(:datadog_appsec) unless builder.handlers.any? { |h| h.klass == SSRFDetectionMiddleware }
16+
end
17+
end
18+
end
19+
end
20+
end
21+
end
22+
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../integration'
4+
5+
require_relative 'patcher'
6+
7+
module Datadog
8+
module AppSec
9+
module Contrib
10+
module Faraday
11+
# This class provides helper methods that are used when patching Faraday
12+
class Integration
13+
include Datadog::AppSec::Contrib::Integration
14+
15+
MINIMUM_VERSION = Gem::Version.new('0.14.0')
16+
17+
register_as :faraday, auto_patch: true
18+
19+
def self.version
20+
Gem.loaded_specs['faraday'] && Gem.loaded_specs['faraday'].version
21+
end
22+
23+
def self.loaded?
24+
!defined?(::Faraday).nil?
25+
end
26+
27+
def self.compatible?
28+
super && version >= MINIMUM_VERSION
29+
end
30+
31+
def self.auto_instrument?
32+
true
33+
end
34+
35+
def patcher
36+
Patcher
37+
end
38+
end
39+
end
40+
end
41+
end
42+
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
3+
module Datadog
4+
module AppSec
5+
module Contrib
6+
module Faraday
7+
# Patcher for Faraday
8+
module Patcher
9+
module_function
10+
11+
def patched?
12+
Patcher.instance_variable_get(:@patched)
13+
end
14+
15+
def target_version
16+
Integration.version
17+
end
18+
19+
def patch
20+
require_relative 'ssrf_detection_middleware'
21+
require_relative 'connection_patch'
22+
require_relative 'rack_builder_patch'
23+
24+
::Faraday::Middleware.register_middleware(datadog_appsec: SSRFDetectionMiddleware)
25+
configure_default_faraday_connection
26+
27+
Patcher.instance_variable_set(:@patched, true)
28+
end
29+
30+
def configure_default_faraday_connection
31+
if target_version >= Gem::Version.new('1.0.0')
32+
# Patch the default connection (e.g. +Faraday.get+)
33+
::Faraday.default_connection.use(:datadog_appsec)
34+
35+
# Patch new connection instances (e.g. +Faraday.new+)
36+
::Faraday::Connection.prepend(ConnectionPatch)
37+
else
38+
# Patch the default connection (e.g. +Faraday.get+)
39+
#
40+
# We insert our middleware before the 'adapter', which is
41+
# always the last handler.
42+
idx = ::Faraday.default_connection.builder.handlers.size - 1
43+
::Faraday.default_connection.builder.insert(idx, SSRFDetectionMiddleware)
44+
45+
# Patch new connection instances (e.g. +Faraday.new+)
46+
::Faraday::RackBuilder.prepend(RackBuilderPatch)
47+
end
48+
end
49+
end
50+
end
51+
end
52+
end
53+
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
module Datadog
4+
module AppSec
5+
module Contrib
6+
module Faraday
7+
# Handles installation of our middleware if the user has *not*
8+
# already explicitly configured it for this correction.
9+
#
10+
# RackBuilder class was introduced in faraday 0.9.0:
11+
# https://github.com/lostisland/faraday/commit/77d7546d6d626b91086f427c56bc2cdd951353b3
12+
module RackBuilderPatch
13+
def adapter(*args)
14+
use(:datadog_appsec) unless @handlers.any? { |h| h.klass == SSRFDetectionMiddleware }
15+
16+
super
17+
end
18+
end
19+
end
20+
end
21+
end
22+
end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# rubocop:disable Naming/FileName
2+
# frozen_string_literal: true
3+
4+
module Datadog
5+
module AppSec
6+
module Contrib
7+
module Faraday
8+
# AppSec SSRF detection Middleware for Faraday
9+
class SSRFDetectionMiddleware < ::Faraday::Middleware
10+
def call(request_env)
11+
context = AppSec.active_context
12+
13+
return @app.call(request_env) unless context && AppSec.rasp_enabled?
14+
15+
ephemeral_data = {
16+
'server.io.net.url' => request_env.url.to_s
17+
}
18+
19+
result = context.run_rasp(Ext::RASP_SSRF, {}, ephemeral_data, Datadog.configuration.appsec.waf_timeout)
20+
21+
if result.match?
22+
Datadog::AppSec::Event.tag_and_keep!(context, result)
23+
24+
context.events << {
25+
waf_result: result,
26+
trace: context.trace,
27+
span: context.span,
28+
request_url: request_env.url,
29+
actions: result.actions
30+
}
31+
32+
ActionsHandler.handle(result.actions)
33+
end
34+
35+
@app.call(request_env)
36+
end
37+
end
38+
end
39+
end
40+
end
41+
end
42+
# rubocop:enable Naming/FileName
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module Datadog
2+
module AppSec
3+
module Contrib
4+
module Faraday
5+
class ConnectionPatch
6+
def initialize: (*untyped args) ?{ () -> untyped } -> void
7+
end
8+
end
9+
end
10+
end
11+
end
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module Datadog
2+
module AppSec
3+
module Contrib
4+
module Faraday
5+
class Integration
6+
include Datadog::AppSec::Contrib::Integration
7+
8+
MINIMUM_VERSION: ::Gem::Version
9+
10+
def self.version: () -> ::Gem::Version?
11+
12+
def self.loaded?: () -> bool
13+
14+
def self.compatible?: () -> bool
15+
16+
def self.auto_instrument?: () -> bool
17+
18+
def patcher: () -> Datadog::AppSec::Contrib::GraphQL::Patcher
19+
end
20+
end
21+
end
22+
end
23+
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module Datadog
2+
module AppSec
3+
module Contrib
4+
module Faraday
5+
module Patcher
6+
def self?.patched?: () -> bool
7+
8+
def self?.target_version: () -> ::Gem::Version
9+
10+
def self?.patch: () -> bool
11+
12+
def self?.register_middleware!: () -> void
13+
14+
def self?.add_default_middleware!: () -> void
15+
end
16+
end
17+
end
18+
end
19+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module Datadog
2+
module AppSec
3+
module Contrib
4+
module Faraday
5+
module RackBuilder
6+
def adapter: (*untyped args) -> untyped
7+
end
8+
end
9+
end
10+
end
11+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Datadog
2+
module AppSec
3+
module Contrib
4+
module Faraday
5+
class SSRFDetectionMiddleware < ::Faraday::Middleware
6+
def call: (untyped env) -> void
7+
8+
private
9+
10+
attr_reader app: untyped
11+
end
12+
end
13+
end
14+
end
15+
end

0 commit comments

Comments
 (0)