aikido-zen 1.6.0.beta.2-arm64-linux → 1.7.0-arm64-linux
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 +4 -4
- data/lib/aikido/zen/agent.rb +68 -30
- data/lib/aikido/zen/api_cache.rb +55 -2
- data/lib/aikido/zen/api_stream.rb +6 -0
- data/lib/aikido/zen/config.rb +5 -0
- data/lib/aikido/zen/runtime_settings/ip_list.rb +12 -7
- data/lib/aikido/zen/runtime_settings.rb +35 -1
- data/lib/aikido/zen/sinks/httpx.rb +4 -0
- data/lib/aikido/zen/version.rb +1 -1
- data/lib/aikido/zen/worker_process/agent/client.rb +47 -18
- data/lib/aikido/zen/worker_process/agent/server.rb +16 -7
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0ffb379e7423fa38477a59c69810a488e9558d2b4e9eabf55d8dbaee38972aa2
|
|
4
|
+
data.tar.gz: 0d87bf012fe98b6fda0e4e5a8914c4963e79bd408e450766206cd0e30ead389f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5174789571ad048204dfe47ab72f398991bca3a6000362e0165deb896adadf8449557d0295a1270a36f5d02eba600096a14f2e75d4a2a18de0546d0d0d3d6167
|
|
7
|
+
data.tar.gz: 849de375d72d7ccd824e3d751257a9ac1854f91793db0dc0c36a8155e0c473efa02aa39120ea5f69bfefdad30cbd9a0d29383eb427abaf82e239ccaf0c0022d6
|
data/lib/aikido/zen/agent.rb
CHANGED
|
@@ -18,12 +18,14 @@ module Aikido::Zen
|
|
|
18
18
|
|
|
19
19
|
def initialize(
|
|
20
20
|
config: Aikido::Zen.config,
|
|
21
|
+
settings: Aikido::Zen.runtime_settings,
|
|
21
22
|
collector: Aikido::Zen.collector,
|
|
22
23
|
worker: Aikido::Zen::Worker.new(config: config),
|
|
23
24
|
api_client: Aikido::Zen::APIClient.new(config: config),
|
|
24
25
|
api_stream: Aikido::Zen::APIStream.new(config: config)
|
|
25
26
|
)
|
|
26
27
|
@config = config
|
|
28
|
+
@settings = settings
|
|
27
29
|
@collector = collector
|
|
28
30
|
@worker = worker
|
|
29
31
|
@api_client = api_client
|
|
@@ -59,29 +61,39 @@ module Aikido::Zen
|
|
|
59
61
|
return
|
|
60
62
|
end
|
|
61
63
|
|
|
64
|
+
# Code coverage is disabled here because the block is only called when
|
|
65
|
+
# the process exits.
|
|
66
|
+
# :nocov:
|
|
62
67
|
at_exit { stop! if started? }
|
|
68
|
+
# :nocov:
|
|
63
69
|
|
|
64
70
|
report(Events::Started.new(time: @started_at)) do |response|
|
|
65
|
-
if update_settings_from_runtime_config!(response)
|
|
71
|
+
if update_settings_from_runtime_config!(response, reason: "after start")
|
|
66
72
|
updated_settings!
|
|
67
|
-
|
|
73
|
+
|
|
74
|
+
if @config.realtime_settings_updates_enabled? || @settings.realtime_settings_updates_enabled?
|
|
75
|
+
@api_stream.handle("config-updated") do |event|
|
|
76
|
+
@config.logger.debug("Received server-sent event: config-updated")
|
|
77
|
+
settings_updated(event)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@api_stream.start!
|
|
81
|
+
end
|
|
82
|
+
# :nocov:
|
|
83
|
+
else
|
|
84
|
+
# empty
|
|
68
85
|
end
|
|
86
|
+
# :nocov:
|
|
69
87
|
rescue => err
|
|
70
88
|
@config.logger.error(err.message)
|
|
71
89
|
end
|
|
72
90
|
|
|
73
91
|
begin
|
|
74
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
75
|
-
@config.logger.info("Updated runtime firewall list")
|
|
92
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after start")
|
|
76
93
|
rescue => err
|
|
77
94
|
@config.logger.error(err.message)
|
|
78
95
|
end
|
|
79
96
|
|
|
80
|
-
if @config.realtime_settings_updates_enabled?
|
|
81
|
-
@api_stream.handle("config-updated") { |event| settings_updated(event) }
|
|
82
|
-
@api_stream.start!
|
|
83
|
-
end
|
|
84
|
-
|
|
85
97
|
poll_for_setting_updates
|
|
86
98
|
|
|
87
99
|
@config.initial_heartbeat_delays.each do |heartbeat_delay|
|
|
@@ -167,13 +179,15 @@ module Aikido::Zen
|
|
|
167
179
|
|
|
168
180
|
heartbeat = @collector.flush
|
|
169
181
|
report(heartbeat) do |response|
|
|
170
|
-
if update_settings_from_runtime_config!(response)
|
|
182
|
+
if update_settings_from_runtime_config!(response, reason: "after heartbeat")
|
|
171
183
|
updated_settings!
|
|
172
|
-
@config.logger.info("Updated runtime settings after heartbeat")
|
|
173
184
|
|
|
174
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
175
|
-
|
|
185
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after heartbeat")
|
|
186
|
+
# :nocov:
|
|
187
|
+
else
|
|
188
|
+
# empty
|
|
176
189
|
end
|
|
190
|
+
# :nocov:
|
|
177
191
|
end
|
|
178
192
|
end
|
|
179
193
|
|
|
@@ -185,15 +199,17 @@ module Aikido::Zen
|
|
|
185
199
|
# @return [void]
|
|
186
200
|
# @see Aikido::Zen::RuntimeSettings
|
|
187
201
|
def poll_for_setting_updates
|
|
188
|
-
@worker.every(@config.polling_interval) do
|
|
202
|
+
@worker.every(@config.polling_interval, run_now: true) do
|
|
189
203
|
if @api_client.should_fetch_settings?
|
|
190
|
-
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
|
|
204
|
+
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after polling")
|
|
191
205
|
updated_settings!
|
|
192
|
-
|
|
206
|
+
# :nocov:
|
|
207
|
+
else
|
|
208
|
+
# empty
|
|
193
209
|
end
|
|
210
|
+
# :nocov:
|
|
194
211
|
|
|
195
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
196
|
-
@config.logger.info("Updated runtime firewall list after polling")
|
|
212
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after polling")
|
|
197
213
|
end
|
|
198
214
|
end
|
|
199
215
|
end
|
|
@@ -204,13 +220,15 @@ module Aikido::Zen
|
|
|
204
220
|
updated_at = Time.at(event[:data]["configUpdatedAt"].to_i)
|
|
205
221
|
|
|
206
222
|
if should_fetch_settings?(updated_at)
|
|
207
|
-
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
|
|
223
|
+
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after server-sent event")
|
|
208
224
|
updated_settings!
|
|
209
|
-
@config.logger.info("Updated runtime settings after server-side event")
|
|
210
225
|
|
|
211
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
212
|
-
|
|
226
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after server-sent event")
|
|
227
|
+
# :nocov:
|
|
228
|
+
else
|
|
229
|
+
# empty
|
|
213
230
|
end
|
|
231
|
+
# :nocov:
|
|
214
232
|
end
|
|
215
233
|
end
|
|
216
234
|
|
|
@@ -229,24 +247,44 @@ module Aikido::Zen
|
|
|
229
247
|
end
|
|
230
248
|
|
|
231
249
|
# @param data [Hash]
|
|
232
|
-
# @
|
|
233
|
-
|
|
250
|
+
# @param reason [String] context appended to the log message when logged
|
|
251
|
+
# @return [Boolean] whether the settings were updated; or nil if another
|
|
252
|
+
# thread is already applying an update.
|
|
253
|
+
def update_settings_from_runtime_config!(data, reason:)
|
|
254
|
+
# :nocov:
|
|
234
255
|
return unless @runtime_config_update_mutex.try_lock
|
|
256
|
+
# :nocov:
|
|
235
257
|
begin
|
|
236
|
-
Aikido::Zen.api_cache.
|
|
237
|
-
|
|
258
|
+
return false unless Aikido::Zen.api_cache.update_runtime_config(data)
|
|
259
|
+
|
|
260
|
+
if Aikido::Zen.runtime_settings.update_from_runtime_config_json(data)
|
|
261
|
+
@config.logger.info("Updated runtime settings #{reason}")
|
|
262
|
+
true
|
|
263
|
+
else
|
|
264
|
+
false
|
|
265
|
+
end
|
|
238
266
|
ensure
|
|
239
267
|
@runtime_config_update_mutex.unlock
|
|
240
268
|
end
|
|
241
269
|
end
|
|
242
270
|
|
|
243
271
|
# @param data [Hash]
|
|
244
|
-
# @
|
|
245
|
-
|
|
272
|
+
# @param reason [String] context appended to the log message when logged
|
|
273
|
+
# @return [Boolean] whether the settings were updated; or nil if another
|
|
274
|
+
# thread is already applying an update.
|
|
275
|
+
def update_settings_from_runtime_firewall_lists!(data, reason:)
|
|
276
|
+
# :nocov:
|
|
246
277
|
return unless @runtime_firewall_lists_update_mutex.try_lock
|
|
278
|
+
# :nocov:
|
|
247
279
|
begin
|
|
248
|
-
Aikido::Zen.api_cache.
|
|
249
|
-
|
|
280
|
+
return false unless Aikido::Zen.api_cache.update_runtime_firewall_lists(data)
|
|
281
|
+
|
|
282
|
+
if Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(data)
|
|
283
|
+
@config.logger.info("Updated runtime firewall list #{reason}")
|
|
284
|
+
true
|
|
285
|
+
else
|
|
286
|
+
false
|
|
287
|
+
end
|
|
250
288
|
ensure
|
|
251
289
|
@runtime_firewall_lists_update_mutex.unlock
|
|
252
290
|
end
|
data/lib/aikido/zen/api_cache.rb
CHANGED
|
@@ -2,7 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
module Aikido::Zen
|
|
4
4
|
class APICache
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
attr_reader :runtime_config
|
|
6
|
+
attr_reader :runtime_firewall_lists
|
|
7
|
+
attr_reader :runtime_config_generation
|
|
8
|
+
attr_reader :runtime_firewall_lists_generation
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
@runtime_config_generation = 0
|
|
12
|
+
@runtime_firewall_lists_generation = 0
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def runtime_config=(value)
|
|
16
|
+
update_runtime_config(value)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def runtime_firewall_lists=(value)
|
|
20
|
+
update_runtime_firewall_lists(value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param value [Hash, nil]
|
|
24
|
+
# @return [Boolean] whether the value actually changed
|
|
25
|
+
def update_runtime_config(value)
|
|
26
|
+
return false if value == @runtime_config
|
|
27
|
+
|
|
28
|
+
@runtime_config_generation += 1
|
|
29
|
+
@runtime_config = value
|
|
30
|
+
|
|
31
|
+
true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param value [Hash, nil]
|
|
35
|
+
# @return [Boolean] whether the value actually changed
|
|
36
|
+
def update_runtime_firewall_lists(value)
|
|
37
|
+
return false if value == @runtime_firewall_lists
|
|
38
|
+
|
|
39
|
+
@runtime_firewall_lists_generation += 1
|
|
40
|
+
@runtime_firewall_lists = value
|
|
41
|
+
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param known_generation [Integer, nil]
|
|
46
|
+
# @return [Array(Object, Integer), nil] the current value and generation,
|
|
47
|
+
# or nil if the known generation is already current.
|
|
48
|
+
def config_if_changed(known_generation)
|
|
49
|
+
return nil if known_generation == runtime_config_generation
|
|
50
|
+
[runtime_config, runtime_config_generation]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @param known_generation [Integer, nil]
|
|
54
|
+
# @return [Array(Object, Integer), nil] the current value and generation,
|
|
55
|
+
# or nil if the known generation is already current.
|
|
56
|
+
def firewall_lists_if_changed(known_generation)
|
|
57
|
+
return nil if known_generation == runtime_firewall_lists_generation
|
|
58
|
+
[runtime_firewall_lists, runtime_firewall_lists_generation]
|
|
59
|
+
end
|
|
7
60
|
end
|
|
8
61
|
end
|
|
@@ -116,12 +116,18 @@ module Aikido::Zen
|
|
|
116
116
|
http.open_timeout = @open_timeout
|
|
117
117
|
http.write_timeout = @write_timeout
|
|
118
118
|
http.read_timeout = @read_timeout
|
|
119
|
+
# Working around Net::HTTP cleverness; retries GET requests once by default.
|
|
119
120
|
http.max_retries = 0
|
|
120
121
|
|
|
121
122
|
request = Net::HTTP::Get.new("/api/runtime/stream")
|
|
122
123
|
request["Authorization"] = @token
|
|
123
124
|
request["Accept"] = "text/event-stream"
|
|
124
125
|
request["Cache-Control"] = "no-cache"
|
|
126
|
+
request["X-Agent-Platform"] = "ruby"
|
|
127
|
+
request["X-Agent-Version"] = Aikido::Zen::VERSION
|
|
128
|
+
# Working around Net::HTTP cleverness; auto-negotiates gzip and buffers
|
|
129
|
+
# decompressed output internally.
|
|
130
|
+
request["Accept-Encoding"] = "identity"
|
|
125
131
|
|
|
126
132
|
@config.logger.debug("API stream connecting")
|
|
127
133
|
http.start
|
data/lib/aikido/zen/config.rb
CHANGED
|
@@ -60,6 +60,10 @@ module Aikido::Zen
|
|
|
60
60
|
# poll the parent process for updated runtime settings. Defaults to 10 seconds.
|
|
61
61
|
attr_accessor :worker_process_polling_interval
|
|
62
62
|
|
|
63
|
+
# @return [Integer] the maximum random delay in seconds before a forked
|
|
64
|
+
# worker process starts polling. Defaults to 10 seconds.
|
|
65
|
+
attr_accessor :worker_process_polling_jitter
|
|
66
|
+
|
|
63
67
|
# @return [Integer] the interval in seconds at which forked worker processes
|
|
64
68
|
# flush their collected stats to the parent process. Defaults to 10 seconds.
|
|
65
69
|
attr_accessor :worker_process_heartbeat_interval
|
|
@@ -237,6 +241,7 @@ module Aikido::Zen
|
|
|
237
241
|
self.polling_interval = 60 # 1 min
|
|
238
242
|
self.initial_heartbeat_delays = [30, 60 * 2] # 30 sec, 2 min
|
|
239
243
|
self.worker_process_polling_interval = 10
|
|
244
|
+
self.worker_process_polling_jitter = 10
|
|
240
245
|
self.worker_process_heartbeat_interval = 10
|
|
241
246
|
self.json_encoder = DEFAULT_JSON_ENCODER
|
|
242
247
|
self.json_decoder = DEFAULT_JSON_DECODER
|
|
@@ -29,10 +29,13 @@ module Aikido::Zen
|
|
|
29
29
|
@ipv6_ranges = []
|
|
30
30
|
|
|
31
31
|
ips.each do |ip|
|
|
32
|
+
range = ip.to_range
|
|
33
|
+
ip_int_range = (range.begin.to_i..range.end.to_i)
|
|
34
|
+
|
|
32
35
|
if ip.ipv4?
|
|
33
|
-
@ipv4_ranges <<
|
|
36
|
+
@ipv4_ranges << ip_int_range
|
|
34
37
|
elsif ip.ipv6?
|
|
35
|
-
@ipv6_ranges <<
|
|
38
|
+
@ipv6_ranges << ip_int_range
|
|
36
39
|
else
|
|
37
40
|
raise ArgumentError, "Unsupported IP address family: #{ip.inspect}"
|
|
38
41
|
end
|
|
@@ -50,10 +53,12 @@ module Aikido::Zen
|
|
|
50
53
|
native_ip = nativize_ip(ip)
|
|
51
54
|
return false if native_ip.nil?
|
|
52
55
|
|
|
56
|
+
ip_int = native_ip.to_i
|
|
57
|
+
|
|
53
58
|
if native_ip.ipv4?
|
|
54
|
-
ranges_cover?(@ipv4_ranges,
|
|
59
|
+
ranges_cover?(@ipv4_ranges, ip_int)
|
|
55
60
|
elsif native_ip.ipv6?
|
|
56
|
-
ranges_cover?(@ipv6_ranges,
|
|
61
|
+
ranges_cover?(@ipv6_ranges, ip_int)
|
|
57
62
|
else
|
|
58
63
|
raise ArgumentError, "Unsupported IP address family: #{ip.inspect}"
|
|
59
64
|
end
|
|
@@ -78,12 +83,12 @@ module Aikido::Zen
|
|
|
78
83
|
end
|
|
79
84
|
end
|
|
80
85
|
|
|
81
|
-
def ranges_cover?(ranges,
|
|
82
|
-
index = ranges.bsearch_index { |range| range.begin >
|
|
86
|
+
def ranges_cover?(ranges, ip_int)
|
|
87
|
+
index = ranges.bsearch_index { |range| range.begin > ip_int }
|
|
83
88
|
index = index ? index - 1 : ranges.size - 1
|
|
84
89
|
return false if index < 0
|
|
85
90
|
|
|
86
|
-
ranges[index].cover?(
|
|
91
|
+
ranges[index].cover?(ip_int)
|
|
87
92
|
end
|
|
88
93
|
end
|
|
89
94
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
3
5
|
module Aikido::Zen
|
|
4
6
|
# Stores the firewall configuration sourced from the Aikido dashboard. This
|
|
5
7
|
# object is updated by the Agent regularly.
|
|
@@ -11,7 +13,25 @@ module Aikido::Zen
|
|
|
11
13
|
#
|
|
12
14
|
# You can subscribe to changes with +#add_observer(object, func_name)+, which
|
|
13
15
|
# will call the function passing the settings as an argument
|
|
14
|
-
RuntimeSettings = Struct.new(
|
|
16
|
+
RuntimeSettings = Struct.new(
|
|
17
|
+
:updated_at,
|
|
18
|
+
:heartbeat_interval,
|
|
19
|
+
:endpoints,
|
|
20
|
+
:blocked_user_ids,
|
|
21
|
+
:bypassed_ips,
|
|
22
|
+
:received_any_stats,
|
|
23
|
+
:blocking_mode,
|
|
24
|
+
:blocked_user_agent_regexp,
|
|
25
|
+
:monitored_user_agent_regexp,
|
|
26
|
+
:user_agent_details,
|
|
27
|
+
:blocked_ip_lists,
|
|
28
|
+
:allowed_ip_lists,
|
|
29
|
+
:monitored_ip_lists,
|
|
30
|
+
:block_new_outbound,
|
|
31
|
+
:domains,
|
|
32
|
+
:excluded_user_ids_from_rate_limiting,
|
|
33
|
+
:enabled_features
|
|
34
|
+
) do
|
|
15
35
|
def initialize(*)
|
|
16
36
|
super
|
|
17
37
|
self.endpoints ||= RuntimeSettings::Endpoints.new
|
|
@@ -20,6 +40,7 @@ module Aikido::Zen
|
|
|
20
40
|
self.allowed_ip_lists ||= []
|
|
21
41
|
self.monitored_ip_lists ||= []
|
|
22
42
|
self.domains ||= RuntimeSettings::Domains.new
|
|
43
|
+
self.enabled_features ||= Set.new
|
|
23
44
|
end
|
|
24
45
|
|
|
25
46
|
# @!attribute [rw] updated_at
|
|
@@ -73,6 +94,9 @@ module Aikido::Zen
|
|
|
73
94
|
# @return [Array<String>, nil] the user IDs that should be skipped from
|
|
74
95
|
# rate limiting entirely.
|
|
75
96
|
|
|
97
|
+
# @!attribute [rw] enabled_features
|
|
98
|
+
# @return [Set<String>]
|
|
99
|
+
|
|
76
100
|
# Parse and interpret the JSON response from the core API with updated
|
|
77
101
|
# runtime settings, and apply the changes.
|
|
78
102
|
#
|
|
@@ -97,6 +121,8 @@ module Aikido::Zen
|
|
|
97
121
|
|
|
98
122
|
self.excluded_user_ids_from_rate_limiting = data["excludedUserIdsFromRateLimiting"]
|
|
99
123
|
|
|
124
|
+
self.enabled_features = Set.new(data["enabledFeatures"])
|
|
125
|
+
|
|
100
126
|
updated_at != last_updated_at
|
|
101
127
|
end
|
|
102
128
|
|
|
@@ -223,6 +249,14 @@ module Aikido::Zen
|
|
|
223
249
|
|
|
224
250
|
(!domain.nil? && domain.block?) || (domain.nil? && block_new_outbound)
|
|
225
251
|
end
|
|
252
|
+
|
|
253
|
+
private def enabled_feature?(feature)
|
|
254
|
+
enabled_features.include?(feature)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def realtime_settings_updates_enabled?
|
|
258
|
+
enabled_feature?("realtime_updates")
|
|
259
|
+
end
|
|
226
260
|
end
|
|
227
261
|
end
|
|
228
262
|
|
|
@@ -70,6 +70,10 @@ module Aikido::Zen
|
|
|
70
70
|
Helpers.scan(wrapped_request, connection, "request")
|
|
71
71
|
|
|
72
72
|
request.on(:response) do |response|
|
|
73
|
+
# HTTPX emits an ErrorResponse (no status/headers) through this
|
|
74
|
+
# same callback when the request fails at the transport level.
|
|
75
|
+
next if response.is_a?(::HTTPX::ErrorResponse)
|
|
76
|
+
|
|
73
77
|
Scanners::SSRFScanner.track_redirects(
|
|
74
78
|
request: wrapped_request,
|
|
75
79
|
response: Helpers.wrap_response(response)
|
data/lib/aikido/zen/version.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Aikido::Zen::WorkerProcess
|
|
|
16
16
|
worker: Aikido::Zen::Worker.new(config: config),
|
|
17
17
|
keepalive_worker: Aikido::Zen::Worker.new(config: config),
|
|
18
18
|
polling_interval: config.worker_process_polling_interval,
|
|
19
|
+
polling_jitter: config.worker_process_polling_jitter,
|
|
19
20
|
heartbeat_interval: config.worker_process_heartbeat_interval,
|
|
20
21
|
collector: Aikido::Zen.collector
|
|
21
22
|
)
|
|
@@ -23,10 +24,14 @@ module Aikido::Zen::WorkerProcess
|
|
|
23
24
|
@worker = worker
|
|
24
25
|
@keepalive_worker = keepalive_worker
|
|
25
26
|
@polling_interval = polling_interval
|
|
27
|
+
@polling_jitter = polling_jitter
|
|
26
28
|
@heartbeat_interval = heartbeat_interval
|
|
27
29
|
@collector = collector
|
|
28
30
|
|
|
29
31
|
@rpc_client = Aikido::Zen::RPC::Client.new(secret, host, port, reconnect: true)
|
|
32
|
+
|
|
33
|
+
@known_config_generation = nil
|
|
34
|
+
@known_firewall_lists_generation = nil
|
|
30
35
|
end
|
|
31
36
|
|
|
32
37
|
def close
|
|
@@ -34,14 +39,7 @@ module Aikido::Zen::WorkerProcess
|
|
|
34
39
|
end
|
|
35
40
|
|
|
36
41
|
def start
|
|
37
|
-
@rpc_client.start
|
|
38
|
-
@worker.perform do
|
|
39
|
-
update_settings(updated_settings)
|
|
40
|
-
rescue => err
|
|
41
|
-
@config.logger.error("Forked worker process #{Process.pid}: failed to get settings after connecting to parent: #{err.message}")
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
42
|
+
@rpc_client.start
|
|
45
43
|
schedule_tasks
|
|
46
44
|
end
|
|
47
45
|
|
|
@@ -73,35 +71,66 @@ module Aikido::Zen::WorkerProcess
|
|
|
73
71
|
private
|
|
74
72
|
|
|
75
73
|
def updated_settings
|
|
76
|
-
@rpc_client.invoke(
|
|
74
|
+
@rpc_client.invoke(
|
|
75
|
+
"updated_settings",
|
|
76
|
+
KEEPALIVE_INTERVAL,
|
|
77
|
+
@known_config_generation,
|
|
78
|
+
@known_firewall_lists_generation
|
|
79
|
+
)
|
|
77
80
|
end
|
|
78
81
|
|
|
79
|
-
def update_settings
|
|
80
|
-
|
|
82
|
+
def update_settings
|
|
83
|
+
settings = updated_settings
|
|
84
|
+
return false unless settings
|
|
85
|
+
return false unless settings["config"] || settings["firewall_lists"]
|
|
86
|
+
|
|
87
|
+
@config.logger.debug("Forked worker process #{Process.pid}: starting runtime settings update")
|
|
81
88
|
|
|
82
89
|
if settings["config"]
|
|
90
|
+
@config.logger.debug("Forked worker process #{Process.pid}: starting config update")
|
|
83
91
|
Aikido::Zen.runtime_settings.update_from_runtime_config_json(settings["config"])
|
|
92
|
+
@known_config_generation = settings["config_generation"]
|
|
93
|
+
@config.logger.debug("Forked worker process #{Process.pid}: finished config update")
|
|
84
94
|
end
|
|
85
95
|
|
|
86
96
|
if settings["firewall_lists"]
|
|
97
|
+
@config.logger.debug("Forked worker process #{Process.pid}: starting firewall_lists update")
|
|
87
98
|
Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(settings["firewall_lists"])
|
|
99
|
+
@known_firewall_lists_generation = settings["firewall_lists_generation"]
|
|
100
|
+
@config.logger.debug("Forked worker process #{Process.pid}: finished firewall_lists update")
|
|
88
101
|
end
|
|
102
|
+
|
|
103
|
+
@config.logger.debug("Forked worker process #{Process.pid}: finished runtime settings update")
|
|
104
|
+
|
|
105
|
+
true
|
|
106
|
+
rescue => err
|
|
107
|
+
@config.logger.error("Forked worker process #{Process.pid}: failed to get settings from parent: #{err.message}")
|
|
108
|
+
false
|
|
89
109
|
end
|
|
90
110
|
|
|
91
111
|
def schedule_tasks
|
|
92
112
|
@keepalive_worker.every(KEEPALIVE_INTERVAL, run_now: false) { keepalive }
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
rescue => err
|
|
99
|
-
@config.logger.error("Forked worker process #{Process.pid}: failed to get settings from parent: #{err.message}")
|
|
100
|
-
end
|
|
114
|
+
# Delay start polling to reduce the chance of workers polling in lockstep.
|
|
115
|
+
delay = polling_start_delay
|
|
116
|
+
@config.logger.debug("Forked worker process #{Process.pid}: will start polling in #{delay}s")
|
|
117
|
+
@worker.delay(delay) { start_polling }
|
|
101
118
|
|
|
102
119
|
@worker.every(@heartbeat_interval, run_now: false) { send_collector_events }
|
|
103
120
|
end
|
|
104
121
|
|
|
122
|
+
def start_polling
|
|
123
|
+
@config.logger.debug("Forked worker process #{Process.pid}: started polling")
|
|
124
|
+
@worker.every(@polling_interval, run_now: true) { update_settings }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def polling_start_delay
|
|
128
|
+
return 0 if @polling_jitter < 1
|
|
129
|
+
|
|
130
|
+
# Seed by pid because forked processes inherit identical PRNG state.
|
|
131
|
+
Random.new(Process.pid).rand(1..@polling_jitter)
|
|
132
|
+
end
|
|
133
|
+
|
|
105
134
|
def keepalive
|
|
106
135
|
@rpc_client.invoke("ping", KEEPALIVE_INTERVAL)
|
|
107
136
|
rescue => err
|
|
@@ -12,8 +12,8 @@ module Aikido::Zen::WorkerProcess
|
|
|
12
12
|
respond.call(nil)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
@rpc_server.handle("updated_settings") do |respond|
|
|
16
|
-
respond.call(updated_settings)
|
|
15
|
+
@rpc_server.handle("updated_settings") do |respond, known_config_generation = nil, known_firewall_lists_generation = nil|
|
|
16
|
+
respond.call(updated_settings(known_config_generation, known_firewall_lists_generation))
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
@rpc_server.handle("send_collector_events") do |respond, events_data|
|
|
@@ -64,11 +64,20 @@ module Aikido::Zen::WorkerProcess
|
|
|
64
64
|
# Visible for testing.
|
|
65
65
|
RequestKind = Struct.new(:route, :schema, :client_ip, :actor)
|
|
66
66
|
|
|
67
|
-
def updated_settings
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
def updated_settings(known_config_generation = nil, known_firewall_lists_generation = nil)
|
|
68
|
+
result = {}
|
|
69
|
+
|
|
70
|
+
config_change = Aikido::Zen.api_cache.config_if_changed(known_config_generation)
|
|
71
|
+
if config_change
|
|
72
|
+
result["config"], result["config_generation"] = config_change
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
firewall_lists_change = Aikido::Zen.api_cache.firewall_lists_if_changed(known_firewall_lists_generation)
|
|
76
|
+
if firewall_lists_change
|
|
77
|
+
result["firewall_lists"], result["firewall_lists_generation"] = firewall_lists_change
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
result
|
|
72
81
|
end
|
|
73
82
|
|
|
74
83
|
def send_collector_events(events_data)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aikido-zen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: arm64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Aikido Security
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: concurrent-ruby
|