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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 605bf12f73582ee321281c9992109368292fe8fd8b892845a67a3f61a9b18fe9
4
- data.tar.gz: de9ac54f227bf9b62cdd6bdf96b00d4cfae79880d0bbb7bd4054a48312f9296f
3
+ metadata.gz: 0ffb379e7423fa38477a59c69810a488e9558d2b4e9eabf55d8dbaee38972aa2
4
+ data.tar.gz: 0d87bf012fe98b6fda0e4e5a8914c4963e79bd408e450766206cd0e30ead389f
5
5
  SHA512:
6
- metadata.gz: e71903eda24b6507bb187bb20bbf45644514d51d2419626b7dd9010086000d5246eb379c2bc41cc78bc462ae0e0a66f025417bc22029396ebcda716cfd51ec29
7
- data.tar.gz: 00d0a9d8df86a856fe7c219732012944c8269f357abfaf1d82b905352ec1bde853b8efb97e2d724db08b530d65d0e5e86fc16f6cb94a3af5b9095d9c800dd152
6
+ metadata.gz: 5174789571ad048204dfe47ab72f398991bca3a6000362e0165deb896adadf8449557d0295a1270a36f5d02eba600096a14f2e75d4a2a18de0546d0d0d3d6167
7
+ data.tar.gz: 849de375d72d7ccd824e3d751257a9ac1854f91793db0dc0c36a8155e0c473efa02aa39120ea5f69bfefdad30cbd9a0d29383eb427abaf82e239ccaf0c0022d6
@@ -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
- @config.logger.info("Updated runtime settings")
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
- @config.logger.info("Updated runtime firewall list after heartbeat")
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
- @config.logger.info("Updated runtime settings after polling")
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
- @config.logger.info("Updated runtime firewall list after server-side event")
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
- # @return [Boolean]
233
- def update_settings_from_runtime_config!(data)
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.runtime_config = data
237
- Aikido::Zen.runtime_settings.update_from_runtime_config_json(data)
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
- # @return [Boolean]
245
- def update_settings_from_runtime_firewall_lists!(data)
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.runtime_firewall_lists = data
249
- Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(data)
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
@@ -2,7 +2,60 @@
2
2
 
3
3
  module Aikido::Zen
4
4
  class APICache
5
- attr_accessor :runtime_config
6
- attr_accessor :runtime_firewall_lists
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
@@ -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 << ip.to_range
36
+ @ipv4_ranges << ip_int_range
34
37
  elsif ip.ipv6?
35
- @ipv6_ranges << ip.to_range
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, native_ip)
59
+ ranges_cover?(@ipv4_ranges, ip_int)
55
60
  elsif native_ip.ipv6?
56
- ranges_cover?(@ipv6_ranges, native_ip)
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, ip)
82
- index = ranges.bsearch_index { |range| range.begin > ip }
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?(ip)
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(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :bypassed_ips, :received_any_stats, :blocking_mode, :blocked_user_agent_regexp, :monitored_user_agent_regexp, :user_agent_details, :blocked_ip_lists, :allowed_ip_lists, :monitored_ip_lists, :block_new_outbound, :domains, :excluded_user_ids_from_rate_limiting) do
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)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "1.6.0.beta.2"
5
+ VERSION = "1.7.0"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
8
  LIBZEN_VERSION = "0.1.61"
@@ -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 do
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("updated_settings", KEEPALIVE_INTERVAL)
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(settings)
80
- return unless settings
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
- @worker.every(@polling_interval, run_now: false) do
95
- update_settings(updated_settings)
96
-
97
- @config.logger.debug("Forked worker process #{Process.pid}: updated runtime settings from parent")
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
- "config" => Aikido::Zen.api_cache.runtime_config,
70
- "firewall_lists" => Aikido::Zen.api_cache.runtime_firewall_lists
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.6.0.beta.2
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-07-16 00:00:00.000000000 Z
11
+ date: 2026-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby