aikido-zen 1.1.0 → 1.1.2

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/docs/rails.md +1 -1
  3. data/lib/aikido/zen/actor.rb +2 -2
  4. data/lib/aikido/zen/agent.rb +3 -0
  5. data/lib/aikido/zen/attack_wave/helpers.rb +1 -1
  6. data/lib/aikido/zen/attack_wave.rb +69 -7
  7. data/lib/aikido/zen/cache.rb +8 -2
  8. data/lib/aikido/zen/collector/event.rb +44 -1
  9. data/lib/aikido/zen/collector/stats.rb +18 -1
  10. data/lib/aikido/zen/collector.rb +27 -2
  11. data/lib/aikido/zen/config.rb +43 -9
  12. data/lib/aikido/zen/current_context.rb +27 -0
  13. data/lib/aikido/zen/detached_agent/agent.rb +2 -2
  14. data/lib/aikido/zen/detached_agent/front_object.rb +1 -1
  15. data/lib/aikido/zen/detached_agent/server.rb +2 -2
  16. data/lib/aikido/zen/errors.rb +6 -0
  17. data/lib/aikido/zen/event.rb +0 -17
  18. data/lib/aikido/zen/middleware/allowed_address_checker.rb +12 -4
  19. data/lib/aikido/zen/middleware/attack_protector.rb +2 -3
  20. data/lib/aikido/zen/middleware/attack_wave_protector.rb +21 -3
  21. data/lib/aikido/zen/middleware/ip_list_checker.rb +47 -0
  22. data/lib/aikido/zen/middleware/rack_throttler.rb +6 -2
  23. data/lib/aikido/zen/middleware/request_tracker.rb +2 -3
  24. data/lib/aikido/zen/middleware/user_agent_checker.rb +1 -2
  25. data/lib/aikido/zen/outbound_connection.rb +1 -1
  26. data/lib/aikido/zen/payload.rb +1 -1
  27. data/lib/aikido/zen/rails_engine.rb +1 -0
  28. data/lib/aikido/zen/rate_limiter/bucket.rb +21 -3
  29. data/lib/aikido/zen/rate_limiter.rb +22 -15
  30. data/lib/aikido/zen/request/schema/auth_schemas.rb +8 -7
  31. data/lib/aikido/zen/request/schema/definition.rb +32 -2
  32. data/lib/aikido/zen/request/schema.rb +23 -7
  33. data/lib/aikido/zen/runtime_settings/domain_settings.rb +27 -0
  34. data/lib/aikido/zen/runtime_settings/domains.rb +41 -0
  35. data/lib/aikido/zen/runtime_settings/endpoints.rb +65 -14
  36. data/lib/aikido/zen/runtime_settings/ip_list.rb +34 -0
  37. data/lib/aikido/zen/runtime_settings/ip_set.rb +21 -1
  38. data/lib/aikido/zen/runtime_settings/rate_limit_settings.rb +11 -0
  39. data/lib/aikido/zen/runtime_settings.rb +92 -7
  40. data/lib/aikido/zen/scanners/path_traversal/helpers.rb +17 -4
  41. data/lib/aikido/zen/scanners/shell_injection/helpers.rb +2 -2
  42. data/lib/aikido/zen/sinks/action_controller.rb +6 -2
  43. data/lib/aikido/zen/sinks/async_http.rb +15 -3
  44. data/lib/aikido/zen/sinks/curb.rb +15 -3
  45. data/lib/aikido/zen/sinks/em_http.rb +15 -3
  46. data/lib/aikido/zen/sinks/excon.rb +15 -3
  47. data/lib/aikido/zen/sinks/http.rb +15 -3
  48. data/lib/aikido/zen/sinks/httpclient.rb +15 -3
  49. data/lib/aikido/zen/sinks/httpx.rb +15 -3
  50. data/lib/aikido/zen/sinks/net_http.rb +29 -9
  51. data/lib/aikido/zen/sinks/patron.rb +15 -3
  52. data/lib/aikido/zen/sinks/typhoeus.rb +19 -5
  53. data/lib/aikido/zen/sinks.rb +5 -0
  54. data/lib/aikido/zen/version.rb +2 -2
  55. data/lib/aikido/zen.rb +20 -6
  56. metadata +21 -3
  57. data/lib/aikido/zen/outbound_connection_monitor.rb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00d07a2e96b782a13c2ab2ec94582f318660ce4e2a3b77ff896490c5382bea47
4
- data.tar.gz: a9b4db0b7157206284d78f35e6530ccb5bdf2e851ea28152788a7d195406b7a3
3
+ metadata.gz: 22a0e7e94d5a020957f061bf7139e15f67d599a8a479ebed19d41b3b53893b44
4
+ data.tar.gz: 249cd01e1fb339ff5cfb87dcb6944247e7ee31e990643ca815f1bdcae34a1961
5
5
  SHA512:
6
- metadata.gz: 3e7229c3918e282d565ef0d25841e641e9133af719769748ee3626d6c7ad70573d899c33f46e290f8a5fdd56118d569c282d62ba6dab006970426bbccc33febb
7
- data.tar.gz: f1388b4b07679712ee999f245db8faf4c811f76c08ac7ca67e68df06ca8e5d74707cf53f35ca6e25fe195d31d41ba13ef51145804ae7f60a6e451f194672f670
6
+ metadata.gz: d18a0dba5e3a68b979f6a8f628f9cdac035786d9abd05fc917c45ffd391c7e9a82d8c844a738c933865d28133fedd74e7696addbd30901a2831c1c7680445a26
7
+ data.tar.gz: ecf92bcb0a52fb3949abccd325d32b81faad3053ee2e92686be5d3315c1b1e157a2fb2ae7cbce68017ca77045acb66edacb4f96e3dfd4ce928c4f3db5cdfc9f7
data/docs/rails.md CHANGED
@@ -6,7 +6,7 @@ To install Zen, add the gem:
6
6
  bundle add aikido-zen
7
7
  ```
8
8
 
9
- And require it before `Bundler.require` in `config/application.rb`:
9
+ And be sure to **require it before** `Bundler.require` in `config/application.rb`:
10
10
 
11
11
  ```ruby
12
12
  # config/application.rb
@@ -65,7 +65,7 @@ module Aikido::Zen
65
65
  def initialize(
66
66
  id:,
67
67
  name: nil,
68
- ip: Aikido::Zen.current_context&.request&.ip,
68
+ ip: Aikido::Zen.current_context&.request&.client_ip,
69
69
  first_seen_at: Time.now.utc,
70
70
  last_seen_at: first_seen_at
71
71
  )
@@ -96,7 +96,7 @@ module Aikido::Zen
96
96
  # always keep the most recent time if this conflicts with the current
97
97
  # value.
98
98
  # @return [void]
99
- def update(seen_at: Time.now.utc, ip: Aikido::Zen.current_context&.request&.ip)
99
+ def update(seen_at: Time.now.utc, ip: Aikido::Zen.current_context&.request&.client_ip)
100
100
  @last_seen_at.try_update { |last_seen_at| [last_seen_at, seen_at].max }
101
101
  @ip.try_update { |last_ip| [ip, last_ip].compact.first }
102
102
  end
@@ -160,6 +160,9 @@ module Aikido::Zen
160
160
  if Aikido::Zen.runtime_settings.update_from_runtime_config_json(response)
161
161
  updated_settings!
162
162
  @config.logger.info("Updated runtime settings after heartbeat")
163
+
164
+ Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(@api_client.fetch_runtime_firewall_lists)
165
+ @config.logger.info("Updated runtime firewall list after heartbeat")
163
166
  end
164
167
  end
165
168
  end
@@ -80,7 +80,7 @@ module Aikido::Zen
80
80
  ".dbus",
81
81
  ".docker",
82
82
  ".drush",
83
- ".TODO: gem",
83
+ ".gem",
84
84
  ".git",
85
85
  ".github",
86
86
  ".gnupg",
@@ -6,12 +6,19 @@ require_relative "attack_wave/helpers"
6
6
  module Aikido::Zen
7
7
  module AttackWave
8
8
  class Detector
9
+ # @return [Aikido::Zen::CappedSet]
10
+ attr_reader :samples
11
+
9
12
  def initialize(config: Aikido::Zen.config, clock: nil)
10
13
  @config = config
11
14
 
12
15
  @event_times = Cache.new(@config.attack_wave_max_cache_entries, ttl: @config.attack_wave_min_time_between_events, clock: clock)
13
16
 
14
17
  @request_counts = Cache.new(@config.attack_wave_max_cache_entries, 0, ttl: @config.attack_wave_min_time_between_requests, clock: clock)
18
+
19
+ @samples = Cache.new(@config.attack_wave_max_cache_entries, ttl: @config.attack_wave_min_time_between_requests, clock: clock) do
20
+ CappedSet.new(@config.attack_wave_max_cache_samples)
21
+ end
15
22
  end
16
23
 
17
24
  def attack_wave?(context)
@@ -25,6 +32,13 @@ module Aikido::Zen
25
32
 
26
33
  request_count = @request_counts[client_ip] += 1
27
34
 
35
+ context.request.then do |request|
36
+ @samples[client_ip] <<= Sample.new(
37
+ verb: request.request_method,
38
+ path: request.fullpath
39
+ )
40
+ end
41
+
28
42
  return false if request_count < @config.attack_wave_threshold
29
43
 
30
44
  @event_times[client_ip] = Time.now.utc
@@ -60,29 +74,77 @@ module Aikido::Zen
60
74
  source: @source
61
75
  }.compact
62
76
  end
77
+
78
+ def ==(other)
79
+ other.is_a?(self.class) &&
80
+ other.ip_address == ip_address &&
81
+ other.user_agent == user_agent &&
82
+ other.source == source
83
+ end
84
+ alias_method :eql?, :==
63
85
  end
64
86
 
65
87
  class Attack
66
- # @return [Hash<String, String>]
67
- attr_reader :metadata
88
+ # @return [Aikido::Zen::AttackWave::Sample]
89
+ attr_reader :samples
68
90
 
69
91
  # @return [Aikido::Zen::Actor]
70
92
  attr_reader :user
71
93
 
72
- # @param metadata [Hash<String, String>]
73
- # @param metadata [Aikido::Zen::Actor]
94
+ # @param samples [Aikido::Zen::AttackWave::Sample]
95
+ # @param user [Aikido::Zen::Actor]
74
96
  # @return [Aikido::Zen::AttackWave::Attack]
75
- def initialize(metadata:, user:)
76
- @metadata = metadata
97
+ def initialize(samples:, user:)
98
+ @samples = samples
77
99
  @user = user
78
100
  end
79
101
 
80
102
  def as_json
81
103
  {
82
- metadata: @metadata.as_json,
104
+ metadata: {
105
+ samples: @samples.as_json.to_json # The API only accepts string values in metadata
106
+ },
83
107
  user: @user.as_json
84
108
  }.compact
85
109
  end
110
+
111
+ def ==(other)
112
+ other.is_a?(self.class) &&
113
+ other.samples == samples &&
114
+ other.user == user
115
+ end
116
+ alias_method :eql?, :==
117
+ end
118
+
119
+ class Sample
120
+ # @return [String]
121
+ attr_reader :verb
122
+
123
+ # @return [String]
124
+ attr_reader :path
125
+
126
+ def initialize(verb:, path:)
127
+ @verb = verb
128
+ @path = path
129
+ end
130
+
131
+ def as_json
132
+ {
133
+ method: @verb.as_json,
134
+ url: @path.as_json
135
+ }.compact
136
+ end
137
+
138
+ def ==(other)
139
+ other.is_a?(self.class) &&
140
+ other.verb == verb &&
141
+ other.path == path
142
+ end
143
+ alias_method :eql?, :==
144
+
145
+ def hash
146
+ [verb, path].hash
147
+ end
86
148
  end
87
149
  end
88
150
  end
@@ -9,11 +9,13 @@ module Aikido::Zen
9
9
  def_delegators :@data,
10
10
  :size, :empty?
11
11
 
12
- def initialize(capacity, default_value = nil, ttl:, clock: nil)
12
+ def initialize(capacity, default_value = nil, ttl:, clock: nil, &block)
13
13
  @default_value = default_value
14
14
  @ttl = ttl
15
15
  @clock = clock
16
16
 
17
+ @initialize_block = block
18
+
17
19
  @data = CappedMap.new(capacity, mode: :lru)
18
20
  end
19
21
 
@@ -38,7 +40,7 @@ module Aikido::Zen
38
40
  if key?(key)
39
41
  @data[key].value
40
42
  else
41
- @default_value
43
+ default_value
42
44
  end
43
45
  end
44
46
 
@@ -62,6 +64,10 @@ module Aikido::Zen
62
64
  def to_h
63
65
  to_a.to_h
64
66
  end
67
+
68
+ private def default_value
69
+ @default_value || @initialize_block&.call
70
+ end
65
71
  end
66
72
 
67
73
  class CacheEntry
@@ -39,7 +39,7 @@ module Aikido::Zen
39
39
  class TrackRequest < Event
40
40
  register "track_request"
41
41
 
42
- def self.from_json(data)
42
+ def self.from_json(_data)
43
43
  new
44
44
  end
45
45
 
@@ -52,6 +52,22 @@ module Aikido::Zen
52
52
  end
53
53
  end
54
54
 
55
+ class TrackRateLimitedRequest < Event
56
+ register "track_rate_limited_request"
57
+
58
+ def self.from_json(_data)
59
+ new
60
+ end
61
+
62
+ def handle(collector)
63
+ collector.handle_track_rate_limited_request
64
+ end
65
+
66
+ def inspect
67
+ "#<#{self.class.name}>"
68
+ end
69
+ end
70
+
55
71
  class TrackUserAgent < Event
56
72
  register "track_user_agent"
57
73
 
@@ -79,6 +95,33 @@ module Aikido::Zen
79
95
  end
80
96
  end
81
97
 
98
+ class TrackIPList < Event
99
+ register "track_ip_list"
100
+
101
+ def self.from_json(data)
102
+ new(data[:ip_list_keys])
103
+ end
104
+
105
+ def initialize(ip_list_keys)
106
+ super()
107
+ @ip_list_keys = ip_list_keys
108
+ end
109
+
110
+ def as_json
111
+ super.update({
112
+ ip_list_keys: @ip_list_keys
113
+ })
114
+ end
115
+
116
+ def handle(collector)
117
+ collector.handle_track_ip_list(@ip_list_keys)
118
+ end
119
+
120
+ def inspect
121
+ "#<#{self.class.name} #{@ip_list_keys.inspect}>"
122
+ end
123
+ end
124
+
82
125
  class TrackAttackWave < Event
83
126
  register "track_attack_wave"
84
127
 
@@ -8,7 +8,7 @@ module Aikido::Zen
8
8
  # Tracks information about how the Aikido Agent is used in the app.
9
9
  class Collector::Stats
10
10
  # @!visibility private
11
- attr_reader :started_at, :ended_at, :requests, :aborted_requests, :user_agents, :attack_waves, :blocked_attack_waves, :sinks
11
+ attr_reader :started_at, :ended_at, :requests, :aborted_requests, :rate_limited_requests, :user_agents, :ip_lists, :attack_waves, :blocked_attack_waves, :sinks
12
12
 
13
13
  # @!visibility private
14
14
  attr_writer :ended_at
@@ -20,7 +20,9 @@ module Aikido::Zen
20
20
  @started_at = @ended_at = nil
21
21
  @requests = 0
22
22
  @aborted_requests = 0
23
+ @rate_limited_requests = 0
23
24
  @user_agents = Hash.new { |h, k| h[k] = 0 }
25
+ @ip_lists = Hash.new { |h, k| h[k] = 0 }
24
26
  @attack_waves = 0
25
27
  @blocked_attack_waves = 0
26
28
  @sinks = Hash.new { |h, k| h[k] = Collector::SinkStats.new(k, @config) }
@@ -67,12 +69,23 @@ module Aikido::Zen
67
69
  @requests += 1
68
70
  end
69
71
 
72
+ # @return [void]
73
+ def add_rate_limited_request
74
+ @rate_limited_requests += 1
75
+ end
76
+
70
77
  # @param user_agent_keys [Array<String>] the user agent keys
71
78
  # @return [void]
72
79
  def add_user_agent(user_agent_keys)
73
80
  user_agent_keys&.each { |user_agent_key| @user_agents[user_agent_key] += 1 }
74
81
  end
75
82
 
83
+ # @param user_agent_keys [Array<String>] the user agent keys
84
+ # @return [void]
85
+ def add_ip_list(ip_list_keys)
86
+ ip_list_keys&.each { |ip_list_key| @ip_lists[ip_list_key] += 1 }
87
+ end
88
+
76
89
  # @param being_blocked [Boolean] whether the Agent blocked the request
77
90
  # @return [void]
78
91
  def add_attack_wave(being_blocked:)
@@ -109,6 +122,7 @@ module Aikido::Zen
109
122
  requests: {
110
123
  total: @requests,
111
124
  aborted: @aborted_requests,
125
+ rateLimited: @rate_limited_requests,
112
126
  attacksDetected: {
113
127
  total: total_attacks,
114
128
  blocked: total_blocked
@@ -120,6 +134,9 @@ module Aikido::Zen
120
134
  },
121
135
  userAgents: {
122
136
  breakdown: @user_agents
137
+ },
138
+ ipAddresses: {
139
+ breakdown: @ip_lists
123
140
  }
124
141
  }
125
142
  end
@@ -88,12 +88,23 @@ module Aikido::Zen
88
88
  synchronize(@stats) { |stats| stats.add_request }
89
89
  end
90
90
 
91
+ # Track stats about the rate_limited_requests
92
+ #
93
+ # @return [void]
94
+ def track_rate_limited_request
95
+ add_event(Events::TrackRateLimitedRequest.new)
96
+ end
97
+
98
+ def handle_track_rate_limited_request
99
+ synchronize(@stats) { |stats| stats.add_rate_limited_request }
100
+ end
101
+
91
102
  # Track stats about monitored and blocked user agents
92
103
  #
93
- # @param [Array<String>, nil] the user agent keys
104
+ # @param user_agent_keys [Array<String>, nil] the user agent keys
94
105
  # @return [void]
95
106
  def track_user_agent(user_agent_keys)
96
- return if user_agent_keys.nil?
107
+ return if user_agent_keys.nil? || user_agent_keys.empty?
97
108
 
98
109
  add_event(Events::TrackUserAgent.new(user_agent_keys))
99
110
  end
@@ -102,6 +113,20 @@ module Aikido::Zen
102
113
  synchronize(@stats) { |stats| stats.add_user_agent(user_agent_keys) }
103
114
  end
104
115
 
116
+ # Track stats about blocked and monitored IP lists
117
+ #
118
+ # @param ip_list_keys [Array<String>, nil]
119
+ # @return [void]
120
+ def track_ip_list(ip_list_keys)
121
+ return if ip_list_keys.nil? || ip_list_keys.empty?
122
+
123
+ add_event(Events::TrackIPList.new(ip_list_keys))
124
+ end
125
+
126
+ def handle_track_ip_list(ip_list_keys)
127
+ synchronize(@stats) { |stats| stats.add_ip_list(ip_list_keys) }
128
+ end
129
+
105
130
  # Track stats about an attack detected by our scanners.
106
131
  #
107
132
  # @param attack [Aikido::Zen::Events::AttackWave]
@@ -3,6 +3,7 @@
3
3
  require "uri"
4
4
  require "json"
5
5
  require "logger"
6
+ require "digest"
6
7
 
7
8
  require_relative "context"
8
9
 
@@ -11,7 +12,7 @@ module Aikido::Zen
11
12
  # @return [Class, Integer, nil] The Rack middleware class or index after which
12
13
  # the Zen middleware should be inserted. When set to nil, the middleware is
13
14
  # inserted before the first middleware in the then-current middleware stack.
14
- # Defaults to ::ActionDispatch::Executor.
15
+ # Defaults to ::ActionDispatch::RemoteIp.
15
16
  attr_accessor :insert_middleware_after
16
17
 
17
18
  # @return [Boolean] whether Aikido should be turned completely off (no
@@ -62,8 +63,8 @@ module Aikido::Zen
62
63
  attr_reader :logger
63
64
 
64
65
  # @return [String] Path of the socket where the detached agent will listen.
65
- # By default, is stored under the root application path with file name
66
- # `aikido-detached-agent.sock`
66
+ # By default, the socket file is created in the current working directory.
67
+ # Defaults to `aikido-detached-agent.sock`.
67
68
  attr_accessor :detached_agent_socket_path
68
69
 
69
70
  # @return [Boolean] is the agent in debugging mode?
@@ -94,7 +95,7 @@ module Aikido::Zen
94
95
  # the oldest seen users.
95
96
  attr_accessor :max_users_tracked
96
97
 
97
- # @return [Proc{(Aikido::Zen::Request, Symbol) => Array(Integer, Hash, #each)}]
98
+ # @return [Proc{(Aikido::Zen::Request, Symbol, reason: String=nil) => Array(Integer, Hash, #each)}]
98
99
  # Rack handler used to respond to requests from IPs, users or others blocked in the Aikido
99
100
  # dashboard.
100
101
  attr_accessor :blocked_responder
@@ -183,8 +184,12 @@ module Aikido::Zen
183
184
  # Defaults to 10,000 entries.
184
185
  attr_accessor :attack_wave_max_cache_entries
185
186
 
187
+ # @return [Integer] the maximum number of samples in the LRU cache.
188
+ # Defaults to 15 entries.
189
+ attr_accessor :attack_wave_max_cache_samples
190
+
186
191
  def initialize
187
- self.insert_middleware_after = ::ActionDispatch::Executor
192
+ self.insert_middleware_after = ::ActionDispatch::RemoteIp
188
193
  self.disabled = read_boolean_from_env(ENV.fetch("AIKIDO_DISABLE", false)) || read_boolean_from_env(ENV.fetch("AIKIDO_DISABLED", false))
189
194
  self.blocking_mode = read_boolean_from_env(ENV.fetch("AIKIDO_BLOCK", false))
190
195
  self.api_timeouts = 10
@@ -221,6 +226,7 @@ module Aikido::Zen
221
226
  self.attack_wave_min_time_between_requests = 60 * 1000 # 1 min (ms)
222
227
  self.attack_wave_min_time_between_events = 20 * 60 * 1000 # 20 min (ms)
223
228
  self.attack_wave_max_cache_entries = 10_000
229
+ self.attack_wave_max_cache_samples = 15
224
230
  end
225
231
 
226
232
  # Set the base URL for API requests.
@@ -263,12 +269,32 @@ module Aikido::Zen
263
269
  @api_timeouts.update(value)
264
270
  end
265
271
 
272
+ def api_token_hash
273
+ return unless api_token
274
+
275
+ @api_token_hash ||= Digest::SHA1.hexdigest(api_token)[0, 7]
276
+ end
277
+
266
278
  def detached_agent_socket_uri
267
279
  "drbunix:" + @detached_agent_socket_path
268
280
  end
269
281
 
282
+ def expanded_detached_agent_socket_path
283
+ @exanded_detached_agent_path ||= expand_socket_path(detached_agent_socket_path)
284
+ end
285
+
286
+ def expanded_detached_agent_socket_uri
287
+ @exanded_detached_agent_uri ||= expand_socket_path(detached_agent_socket_uri)
288
+ end
289
+
270
290
  private
271
291
 
292
+ def expand_socket_path(socket_path)
293
+ socket_path = socket_path.dup
294
+ socket_path.gsub!("%h", api_token_hash) if api_token_hash
295
+ socket_path
296
+ end
297
+
272
298
  def read_boolean_from_env(value)
273
299
  return value unless value.respond_to?(:to_str)
274
300
 
@@ -293,13 +319,21 @@ module Aikido::Zen
293
319
  DEFAULT_JSON_DECODER = JSON.method(:parse)
294
320
 
295
321
  # @!visibility private
296
- DEFAULT_DETACHED_AGENT_SOCKET_PATH = "aikido-detached-agent.sock"
322
+ DEFAULT_DETACHED_AGENT_SOCKET_PATH = "aikido-detached-agent.%h.sock"
297
323
 
298
324
  # @!visibility private
299
- DEFAULT_BLOCKED_RESPONDER = ->(request, blocking_type) do
325
+ DEFAULT_BLOCKED_RESPONDER = ->(request, blocking_type, reason = nil) do
300
326
  message = case blocking_type
301
327
  when :ip
302
- format("Your IP address is not allowed to access this resource. (Your IP: %s)", request.ip)
328
+ "Your IP address is not allowed to access this resource. (Your IP: #{request.ip})"
329
+ when :ip_allowed_list
330
+ "Your IP address is not allowed to access this resource. (Your IP: #{request.client_ip})"
331
+ when :ip_blocked_list
332
+ if reason.nil?
333
+ "Your IP is blocked."
334
+ else
335
+ "Your IP is blocked due to #{reason}."
336
+ end
303
337
  when :user_agent
304
338
  "You are not allowed to access this resource because you have been identified as a bot."
305
339
  else
@@ -315,7 +349,7 @@ module Aikido::Zen
315
349
 
316
350
  # @!visibility private
317
351
  DEFAULT_RATE_LIMITING_DISCRIMINATOR = ->(request) {
318
- request.actor ? "actor:#{request.actor.id}" : request.ip
352
+ request.actor ? "actor:#{request.actor.id}" : request.client_ip
319
353
  }
320
354
  end
321
355
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The current context is stored in an additional Fiber instance variable and
4
+ # is though the aikido_current_context accessor methods.
5
+
6
+ class Fiber
7
+ # @api private
8
+ attr_accessor :aikido_current_context
9
+ end
10
+
11
+ # When a new Fiber is instantiated the current context of the Fiber that is
12
+ # creating the new Fiber is copied into the new Fiber.
13
+
14
+ class << Fiber
15
+ # @api private
16
+ alias_method :new__internal_for_aikido_zen, :new
17
+
18
+ def new(*args, **kwargs, &blk)
19
+ context = Fiber.current.aikido_current_context
20
+
21
+ new__internal_for_aikido_zen(*args, **kwargs) do |*args|
22
+ Fiber.current.aikido_current_context = context
23
+
24
+ blk.call(*args)
25
+ end
26
+ end
27
+ end
@@ -34,7 +34,7 @@ module Aikido::Zen::DetachedAgent
34
34
 
35
35
  @collector = collector
36
36
 
37
- @front_object = DRbObject.new_with_uri(config.detached_agent_socket_uri)
37
+ @front_object = DRbObject.new_with_uri(config.expanded_detached_agent_socket_uri)
38
38
 
39
39
  @has_forked = false
40
40
  schedule_tasks
@@ -46,7 +46,7 @@ module Aikido::Zen::DetachedAgent
46
46
  end
47
47
 
48
48
  def calculate_rate_limits(request)
49
- @front_object.calculate_rate_limits(request.route.as_json, request.ip, request.actor.as_json)
49
+ @front_object.calculate_rate_limits(request.route.as_json, request.client_ip, request.actor.as_json)
50
50
  end
51
51
 
52
52
  # Every time a fork occurs (a new child process is created), we need to start
@@ -17,7 +17,7 @@ module Aikido::Zen::DetachedAgent
17
17
  @rate_limiter = rate_limiter
18
18
  end
19
19
 
20
- RequestKind = Struct.new(:route, :schema, :ip, :actor)
20
+ RequestKind = Struct.new(:route, :schema, :client_ip, :actor)
21
21
 
22
22
  def send_collector_events(events_data)
23
23
  events_data.each do |event_data|
@@ -16,8 +16,8 @@ module Aikido::Zen::DetachedAgent
16
16
 
17
17
  @config = config
18
18
 
19
- @socket_path = config.detached_agent_socket_path
20
- @socket_uri = config.detached_agent_socket_uri
19
+ @socket_path = config.expanded_detached_agent_socket_path
20
+ @socket_uri = config.expanded_detached_agent_socket_uri
21
21
  end
22
22
 
23
23
  def started?
@@ -114,5 +114,11 @@ module Aikido
114
114
  super
115
115
  end
116
116
  end
117
+
118
+ class OutboundConnectionBlockedError < StandardError
119
+ def initialize(connection)
120
+ super("Zen blocked an outbound connection to #{connection.host}.")
121
+ end
122
+ end
117
123
  end
118
124
  end
@@ -71,23 +71,6 @@ module Aikido::Zen
71
71
  end
72
72
 
73
73
  class AttackWave < Event
74
- # @param [Aikido::Zen::Context] a context
75
- # @return [Aikido::Zen::Events::AttackWave] an attack wave event
76
- def self.from_context(context)
77
- request = Aikido::Zen::AttackWave::Request.new(
78
- ip_address: context.request.client_ip,
79
- user_agent: context.request.user_agent,
80
- source: context.request.framework
81
- )
82
-
83
- attack = Aikido::Zen::AttackWave::Attack.new(
84
- metadata: {}, # not used yet
85
- user: context.request.actor
86
- )
87
-
88
- new(request: request, attack: attack)
89
- end
90
-
91
74
  # @return [Aikido::Zen::AttackWave::Request]
92
75
  attr_reader :request
93
76
 
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Aikido::Zen
4
4
  module Middleware
5
- # Middleware that rejects requests from IPs blocked in the Aikido dashboard.
5
+ # Middleware that only allows allowed IPs when allowed IPs are configured for
6
+ # any matching route in the Aikido dashboard.
6
7
  class AllowedAddressChecker
7
8
  def initialize(app, config: Aikido::Zen.config, settings: Aikido::Zen.runtime_settings)
8
9
  @app = app
@@ -13,14 +14,21 @@ module Aikido::Zen
13
14
  def call(env)
14
15
  request = Aikido::Zen::Middleware.request_from(env)
15
16
 
16
- allowed_ips = @settings.endpoints[request.route].allowed_ips
17
-
18
- if allowed_ips.empty? || allowed_ips.include?(request.ip)
17
+ if allowed?(request)
19
18
  @app.call(env)
20
19
  else
21
20
  @config.blocked_responder.call(request, :ip)
22
21
  end
23
22
  end
23
+
24
+ private def allowed?(request)
25
+ return true if @settings.bypassed_ips.include?(request.client_ip)
26
+
27
+ matches = @settings.endpoints.matched_settings(request.route)
28
+
29
+ matches.all? { |settings| settings.allowed_ips.empty? } ||
30
+ matches.any? { |settings| settings.allowed_ips.include?(request.ip) }
31
+ end
24
32
  end
25
33
  end
26
34
  end
@@ -19,10 +19,9 @@ module Aikido::Zen
19
19
  end
20
20
 
21
21
  private def protection_disabled?(request)
22
- # Bypass attack protection for allowed IPs
23
- return true if @settings.allowed_ips.include?(request.ip)
22
+ return true if @settings.bypassed_ips.include?(request.client_ip)
24
23
 
25
- !@settings.endpoints.match(request.route).all?(&:protected?)
24
+ !@settings.endpoints.matched_settings(request.route).all?(&:protected?)
26
25
  end
27
26
  end
28
27
  end