aikido-zen 1.0.1.beta.4-x86_64-mingw-64 → 1.0.2.beta.1-x86_64-mingw-64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bc8f7df841d4cf2f47ce070838dfb1745ffa14b30b815e56ae4a7a86840cd5d
4
- data.tar.gz: d76417c56275f7ae782cf3589b15102917c1f34f5009063fe4910671cf09ea77
3
+ metadata.gz: 87a850ae8220f054a061b224178e250c7f8e8c734f8c3a4451a62f7ebf464377
4
+ data.tar.gz: c3801386633cec931748c69ba7f66ad219e21329c59ce7486585cc324a8b7125
5
5
  SHA512:
6
- metadata.gz: 5be18109dec6f530a320e690748ec2d7648e361381555288863f5ae19bbd5156b0b0a5fe6fd132ee95d40d8e21bb7196f17840690c3fb0ea11fa03bb07530ea8
7
- data.tar.gz: 855edd55388644b09be065b37788f48bb01c31c2ad2f7907fa60ee538dcfbbe27d6ae8de7d0cac6af6ceb273927c6faebc2f76ef9dc9f6a4bafc901bccc4a3aa
6
+ metadata.gz: ff8359526cba8c500244203a3d979592813140a2685c58f81702dd89fa80faed85d43f6e76f765db1aad48b27733fc4f79cfdd51bfb97f79b5dffd7e060b1888
7
+ data.tar.gz: e707a8623f2081c11031e47e1b22ba35d503ca29dfee2ea234e3379d1cbac194cc6147de5bc30a99f5534f0cfa170aea4ab9038510826c09ed3a36e7aab7415e
data/docs/rails.md CHANGED
@@ -2,19 +2,63 @@
2
2
 
3
3
  To install Zen, add the gem:
4
4
 
5
- ```
5
+ ```sh
6
6
  bundle add aikido-zen
7
7
  ```
8
8
 
9
+ And require it before `Bundler.require` in `config/application.rb`:
10
+
11
+ ```ruby
12
+ # config/application.rb
13
+ require_relative "boot"
14
+
15
+ require "rails/all"
16
+
17
+ require "aikido-zen"
18
+ Aikido::Zen.protect!
19
+
20
+ # Require the gems listed in Gemfile, including any gems
21
+ # you've limited to :test, :development, or :production.
22
+ Bundler.require(*Rails.groups)
23
+
24
+ ...
25
+ ```
26
+
9
27
  That's it! Zen will start to run inside your app when it starts getting
10
28
  requests.
11
29
 
30
+ ## Rate limiting and user blocking
31
+
32
+ If you want to add the rate limiting feature to your app, modify your code like this:
33
+
34
+ ```ruby
35
+ # app/controllers/application_controller.rb
36
+ class ApplicationController < ActionController::Base
37
+ private
38
+
39
+ def current_user
40
+ return unless session[:user_id]
41
+ User.find(session[:user_id])
42
+ end
43
+
44
+ def authenticate_user!
45
+ # Your authentication logic here
46
+ # ...
47
+ # Optional, if you want to use user based rate limiting or block specific users
48
+ Aikido::Zen.set_user(
49
+ id: current_user.id,
50
+ name: current_user.name
51
+ )
52
+ end
53
+ end
54
+ ```
55
+
12
56
  ## Configuration
13
57
 
14
58
  Zen exposes its configuration object to the Rails configuration, which you can
15
59
  modify in an initializer if desired:
16
60
 
17
- ``` ruby
61
+ ```ruby
18
62
  # config/initializers/zen.rb
19
63
  Rails.application.config.zen.api_timeouts = 20
20
64
  ```
@@ -30,7 +74,7 @@ If you're using Rails' [encrypted credentials][creds], and prefer not storing
30
74
  sensitive values in your env vars, you can easily configure Zen for it. For
31
75
  example, assuming the following credentials structure:
32
76
 
33
- ``` yaml
77
+ ```yaml
34
78
  # config/credentials.yml.enc
35
79
  zen:
36
80
  token: "AIKIDO_RUNTIME_..."
@@ -38,7 +82,7 @@ zen:
38
82
 
39
83
  You can just tell Zen to use it like so:
40
84
 
41
- ``` ruby
85
+ ```ruby
42
86
  # config/initializers/zen.rb
43
87
  Rails.application.config.zen.token = Rails.application.credentials.zen.token
44
88
  ```
@@ -61,7 +105,7 @@ way.
61
105
  By default, Zen will use the Rails logger, prefixing messages with `[aikido]`.
62
106
  You can redirect the log to a separate stream by overriding the logger:
63
107
 
64
- ```
108
+ ```ruby
65
109
  # config/initializers/zen.rb
66
110
  Rails.application.config.zen.logger = Logger.new(...)
67
111
  ```
@@ -64,7 +64,7 @@ module Aikido::Zen
64
64
  # @return [string] Path of the socket where the detached agent will listen.
65
65
  # By default, is stored under the root application path with file name
66
66
  # `aikido-detached-agent.sock`
67
- attr_reader :detached_agent_socket_path
67
+ attr_accessor :detached_agent_socket_path
68
68
 
69
69
  # @return [Boolean] is the agent in debugging mode?
70
70
  attr_accessor :debugging
@@ -222,9 +222,8 @@ module Aikido::Zen
222
222
  @api_timeouts.update(value)
223
223
  end
224
224
 
225
- def detached_agent_socket_path=(path)
226
- @detached_agent_socket_path = path
227
- @detached_agent_socket_path = "drbunix:" + @detached_agent_socket_path unless @detached_agent_socket_path.start_with?("drbunix:")
225
+ def detached_agent_socket_uri
226
+ "drbunix:" + @detached_agent_socket_path
228
227
  end
229
228
 
230
229
  private
@@ -32,7 +32,7 @@ module Aikido::Zen::DetachedAgent
32
32
  @polling_interval = polling_interval
33
33
  @worker = worker
34
34
  @collector = collector
35
- @detached_agent_front = DRbObject.new_with_uri(config.detached_agent_socket_path)
35
+ @detached_agent_front = DRbObject.new_with_uri(config.detached_agent_socket_uri)
36
36
  @has_forked = false
37
37
  schedule_tasks
38
38
  end
@@ -1,41 +1,78 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module Aikido::Zen::DetachedAgent
4
6
  class Server
7
+ # Initialize and start a detached agent server instance.
8
+ #
9
+ # @return [Aikido::Zen::DetachedAgent::Server]
10
+ def self.start(**opts)
11
+ new(**opts).tap(&:start!)
12
+ end
13
+
5
14
  def initialize(config: Aikido::Zen.config)
6
- @detached_agent_front = FrontObject.new
7
- @drb_server = DRb.start_service(config.detached_agent_socket_path, @detached_agent_front)
15
+ @started_at = nil
8
16
 
9
- # We don't want to see drb logs unless in debug mode
10
- @drb_server.verbose = config.logger.debug?
11
- end
17
+ @config = config
12
18
 
13
- def alive?
14
- @drb_server.alive?
19
+ @socket_path = config.detached_agent_socket_path
20
+ @socket_uri = config.detached_agent_socket_uri
15
21
  end
16
22
 
17
- def stop!
18
- @drb_server.stop_service
19
- DRb.stop_service
23
+ def started?
24
+ !!@started_at
20
25
  end
21
26
 
22
- class << self
23
- def start!
24
- Aikido::Zen.config.logger.debug("Starting DRb Server...")
25
- max_attempts = 10
26
- @server = new
27
-
28
- attempts = 0
29
- until @server.alive?
30
- Aikido::Zen.config.logger.info("DRb Server still not alive. #{max_attempts - attempts} attempts remaining")
31
- sleep 0.1
32
- attempts += 1
33
- raise Aikido::Zen::DetachedAgentError.new("Impossible to start the dRB server (socket=#{Aikido::Zen.config.detached_agent_socket_path})") \
34
- if attempts == max_attempts
35
- end
36
-
37
- @server
27
+ def start!
28
+ @config.logger.info("Starting DRb Server...")
29
+
30
+ # Try to ensure that the DRb service can start if the DRb service did
31
+ # not stop cleanly.
32
+ begin
33
+ # Check whether the Unix domain socket is in use by another process.
34
+ UNIXSocket.new(@socket_path).close
35
+ rescue Errno::ECONNREFUSED
36
+ @config.logger.debug("Removing residual Unix domain socket...")
37
+
38
+ # Remove the residual Unix domain socket.
39
+ FileUtils.rm_f(@socket_path)
40
+ rescue
41
+ # empty
42
+ end
43
+
44
+ @front = FrontObject.new
45
+
46
+ # If the Unix domain socket is in use by another process and/or the
47
+ # residual Unix domain socket could not be removed DRb will raise an
48
+ # appropriate error.
49
+ @drb_server = DRb.start_service(@socket_uri, @front)
50
+
51
+ # Only show DRb output in debug mode.
52
+ @drb_server.verbose = @config.logger.debug?
53
+
54
+ # Ensure that the DRb server is alive.
55
+ max_attempts = 10
56
+ attempts = 0
57
+ until @drb_server.alive?
58
+ @config.logger.info("DRb Server still not alive. #{max_attempts - attempts} attempts remaining")
59
+ sleep 0.1
60
+ attempts += 1
61
+ raise Aikido::Zen::DetachedAgentError.new("Impossible to start the dRB server (socket=#{Aikido::Zen.config.detached_agent_socket_path})") \
62
+ if attempts == max_attempts
38
63
  end
64
+
65
+ @started_at = Time.now.utc
66
+
67
+ at_exit { stop! if started? }
68
+ end
69
+
70
+ def stop!
71
+ @config.logger.info("Stopping DRb Server...")
72
+ @started_at = nil
73
+
74
+ @drb_server.stop_service if @drb_server.alive?
75
+ DRb.stop_service
39
76
  end
40
77
  end
41
78
  end
@@ -7,14 +7,6 @@ module Aikido::Zen
7
7
  module Sinks
8
8
  module Async
9
9
  module HTTP
10
- def self.load_sinks!
11
- if Aikido::Zen.satisfy "async-http", ">= 0.70.0"
12
- require "async/http"
13
-
14
- ::Async::HTTP::Client.prepend(Async::HTTP::ClientExtensions)
15
- end
16
- end
17
-
18
10
  SINK = Sinks.add("async-http", scanners: [
19
11
  Scanners::SSRFScanner,
20
12
  OutboundConnectionMonitor
@@ -30,48 +22,54 @@ module Aikido::Zen
30
22
  end
31
23
  end
32
24
 
33
- module ClientExtensions
34
- extend Sinks::DSL
25
+ def self.load_sinks!
26
+ if Aikido::Zen.satisfy "async-http", ">= 0.70.0"
27
+ require "async/http"
35
28
 
36
- sink_around :call do |super_call, request|
37
- uri = URI(format("%<scheme>s://%<authority>s%<path>s", {
38
- scheme: request.scheme || scheme,
39
- authority: request.authority || authority,
40
- path: request.path
41
- }))
29
+ ::Async::HTTP::Client.class_eval do
30
+ extend Sinks::DSL
42
31
 
43
- wrapped_request = Scanners::SSRFScanner::Request.new(
44
- verb: request.method,
45
- uri: uri,
46
- headers: request.headers.to_h,
47
- header_normalizer: ->(value) { Array(value).join(", ") }
48
- )
32
+ sink_around :call do |original_call, request|
33
+ uri = URI(format("%<scheme>s://%<authority>s%<path>s", {
34
+ scheme: request.scheme || scheme,
35
+ authority: request.authority || authority,
36
+ path: request.path
37
+ }))
49
38
 
50
- # Store the request information so the DNS sinks can pick it up.
51
- context = Aikido::Zen.current_context
52
- if context
53
- prev_request = context["ssrf.request"]
54
- context["ssrf.request"] = wrapped_request
55
- end
39
+ wrapped_request = Scanners::SSRFScanner::Request.new(
40
+ verb: request.method,
41
+ uri: uri,
42
+ headers: request.headers.to_h,
43
+ header_normalizer: ->(value) { Array(value).join(", ") }
44
+ )
56
45
 
57
- connection = OutboundConnection.from_uri(uri)
46
+ # Store the request information so the DNS sinks can pick it up.
47
+ context = Aikido::Zen.current_context
48
+ if context
49
+ prev_request = context["ssrf.request"]
50
+ context["ssrf.request"] = wrapped_request
51
+ end
58
52
 
59
- Helpers.scan(wrapped_request, connection, "request")
53
+ connection = OutboundConnection.from_uri(uri)
60
54
 
61
- response = super_call.call
55
+ Helpers.scan(wrapped_request, connection, "request")
62
56
 
63
- Scanners::SSRFScanner.track_redirects(
64
- request: wrapped_request,
65
- response: Scanners::SSRFScanner::Response.new(
66
- status: response.status,
67
- headers: response.headers.to_h,
68
- header_normalizer: ->(value) { Array(value).join(", ") }
69
- )
70
- )
57
+ response = original_call.call
58
+
59
+ Scanners::SSRFScanner.track_redirects(
60
+ request: wrapped_request,
61
+ response: Scanners::SSRFScanner::Response.new(
62
+ status: response.status,
63
+ headers: response.headers.to_h,
64
+ header_normalizer: ->(value) { Array(value).join(", ") }
65
+ )
66
+ )
71
67
 
72
- response
73
- ensure
74
- context["ssrf.request"] = prev_request if context
68
+ response
69
+ ensure
70
+ context["ssrf.request"] = prev_request if context
71
+ end
72
+ end
75
73
  end
76
74
  end
77
75
  end
@@ -6,14 +6,6 @@ require_relative "../outbound_connection_monitor"
6
6
  module Aikido::Zen
7
7
  module Sinks
8
8
  module Curl
9
- def self.load_sinks!
10
- if Aikido::Zen.satisfy "curb", ">= 0.2.3"
11
- require "curb"
12
-
13
- ::Curl::Easy.prepend(Curl::EasyExtensions)
14
- end
15
- end
16
-
17
9
  SINK = Sinks.add("curb", scanners: [
18
10
  Scanners::SSRFScanner,
19
11
  OutboundConnectionMonitor
@@ -53,59 +45,65 @@ module Aikido::Zen
53
45
  end
54
46
  end
55
47
 
56
- module EasyExtensions
57
- extend Sinks::DSL
58
-
59
- sink_around :perform do |super_call|
60
- wrapped_request = Helpers.wrap_request(self)
61
-
62
- # Store the request information so the DNS sinks can pick it up.
63
- context = Aikido::Zen.current_context
64
- if context
65
- prev_request = context["ssrf.request"]
66
- context["ssrf.request"] = wrapped_request
67
- end
68
-
69
- connection = OutboundConnection.from_uri(URI(url))
70
-
71
- Helpers.scan(wrapped_request, connection, "request")
72
-
73
- response = super_call.call
74
-
75
- Scanners::SSRFScanner.track_redirects(
76
- request: wrapped_request,
77
- response: Helpers.wrap_response(self)
78
- )
48
+ def self.load_sinks!
49
+ if Aikido::Zen.satisfy "curb", ">= 0.2.3"
50
+ require "curb"
79
51
 
80
- # When libcurl has follow_location set, it will handle redirections
81
- # internally, and expose the "last_effective_url" as the URI that was
82
- # last requested in the redirect chain.
83
- #
84
- # In this case, we can't actually stop the request from happening, but
85
- # we can scan again (now that we know another request happened), to
86
- # stop the response from being exposed to the user. This downgrades
87
- # the SSRF into a blind SSRF, which is better than doing nothing.
88
- if url != last_effective_url
89
- last_effective_request = Helpers.wrap_request(self, url: last_effective_url)
90
-
91
- # Code coverage is disabled here because the else clause is a no-op,
92
- # so there is nothing to cover.
93
- # :nocov:
94
- if context
95
- context["ssrf.request"] = last_effective_request
96
- else
97
- # empty
52
+ ::Curl::Easy.class_eval do
53
+ extend Sinks::DSL
54
+
55
+ sink_around :perform do |original_call|
56
+ wrapped_request = Helpers.wrap_request(self)
57
+
58
+ # Store the request information so the DNS sinks can pick it up.
59
+ context = Aikido::Zen.current_context
60
+ if context
61
+ prev_request = context["ssrf.request"]
62
+ context["ssrf.request"] = wrapped_request
63
+ end
64
+
65
+ connection = OutboundConnection.from_uri(URI(url))
66
+
67
+ Helpers.scan(wrapped_request, connection, "request")
68
+
69
+ response = original_call.call
70
+
71
+ Scanners::SSRFScanner.track_redirects(
72
+ request: wrapped_request,
73
+ response: Helpers.wrap_response(self)
74
+ )
75
+
76
+ # When libcurl has follow_location set, it will handle redirections
77
+ # internally, and expose the "last_effective_url" as the URI that was
78
+ # last requested in the redirect chain.
79
+ #
80
+ # In this case, we can't actually stop the request from happening, but
81
+ # we can scan again (now that we know another request happened), to
82
+ # stop the response from being exposed to the user. This downgrades
83
+ # the SSRF into a blind SSRF, which is better than doing nothing.
84
+ if url != last_effective_url
85
+ last_effective_request = Helpers.wrap_request(self, url: last_effective_url)
86
+
87
+ # Code coverage is disabled here because the else clause is a no-op,
88
+ # so there is nothing to cover.
89
+ # :nocov:
90
+ if context
91
+ context["ssrf.request"] = last_effective_request
92
+ else
93
+ # empty
94
+ end
95
+ # :nocov:
96
+
97
+ connection = OutboundConnection.from_uri(URI(last_effective_url))
98
+
99
+ Helpers.scan(last_effective_request, connection, "request")
100
+ end
101
+
102
+ response
103
+ ensure
104
+ context["ssrf.request"] = prev_request if context
98
105
  end
99
- # :nocov:
100
-
101
- connection = OutboundConnection.from_uri(URI(last_effective_url))
102
-
103
- Helpers.scan(last_effective_request, connection, "request")
104
106
  end
105
-
106
- response
107
- ensure
108
- context["ssrf.request"] = prev_request if context
109
107
  end
110
108
  end
111
109
  end
@@ -7,19 +7,6 @@ module Aikido::Zen
7
7
  module Sinks
8
8
  module EventMachine
9
9
  module HttpRequest
10
- def self.load_sinks!
11
- if Aikido::Zen.satisfy "em-http-request", ">= 1.0"
12
- require "em-http-request"
13
-
14
- ::EventMachine::HttpRequest.use(EventMachine::HttpRequest::Middleware)
15
-
16
- # NOTE: We can't use middleware to intercept requests as we want to ensure any
17
- # modifications to the request from user-supplied middleware are already applied
18
- # before we scan the request.
19
- ::EventMachine::HttpClient.prepend(EventMachine::HttpRequest::HttpClientExtensions)
20
- end
21
- end
22
-
23
10
  SINK = Sinks.add("em-http-request", scanners: [
24
11
  Scanners::SSRFScanner,
25
12
  OutboundConnectionMonitor
@@ -35,26 +22,37 @@ module Aikido::Zen
35
22
  end
36
23
  end
37
24
 
38
- module HttpClientExtensions
39
- extend Sinks::DSL
25
+ def self.load_sinks!
26
+ if Aikido::Zen.satisfy "em-http-request", ">= 1.0"
27
+ require "em-http-request"
40
28
 
41
- sink_before :send_request do
42
- wrapped_request = Scanners::SSRFScanner::Request.new(
43
- verb: req.method.to_s,
44
- uri: URI(req.uri),
45
- headers: req.headers
46
- )
29
+ ::EventMachine::HttpRequest.use(EventMachine::HttpRequest::Middleware)
47
30
 
48
- # Store the request information so the DNS sinks can pick it up.
49
- context = Aikido::Zen.current_context
50
- context["ssrf.request"] = wrapped_request if context
31
+ # NOTE: We can't use middleware to intercept requests as we want to ensure any
32
+ # modifications to the request from user-supplied middleware are already applied
33
+ # before we scan the request.
34
+ ::EventMachine::HttpClient.class_eval do
35
+ extend Sinks::DSL
51
36
 
52
- connection = OutboundConnection.new(
53
- host: req.host,
54
- port: req.port
55
- )
37
+ sink_before :send_request do
38
+ wrapped_request = Scanners::SSRFScanner::Request.new(
39
+ verb: req.method.to_s,
40
+ uri: URI(req.uri),
41
+ headers: req.headers
42
+ )
43
+
44
+ # Store the request information so the DNS sinks can pick it up.
45
+ context = Aikido::Zen.current_context
46
+ context["ssrf.request"] = wrapped_request if context
47
+
48
+ connection = OutboundConnection.new(
49
+ host: req.host,
50
+ port: req.port
51
+ )
56
52
 
57
- Helpers.scan(wrapped_request, connection, "request")
53
+ Helpers.scan(wrapped_request, connection, "request")
54
+ end
55
+ end
58
56
  end
59
57
  end
60
58