aikido-zen 1.5.1-aarch64-linux → 1.6.0-aarch64-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/benchmarks/ipc/benchmark.rb +149 -0
- data/benchmarks/rpc/benchmark.rb +57 -0
- data/docs/rails.md +22 -0
- data/lib/aikido/zen/actor.rb +10 -10
- data/lib/aikido/zen/agent.rb +61 -25
- data/lib/aikido/zen/api_cache.rb +61 -0
- data/lib/aikido/zen/api_stream.rb +6 -0
- data/lib/aikido/zen/attack.rb +14 -14
- data/lib/aikido/zen/attack_wave.rb +8 -8
- data/lib/aikido/zen/collector/event.rb +26 -26
- data/lib/aikido/zen/collector/routes.rb +4 -4
- data/lib/aikido/zen/collector/sink_stats.rb +10 -10
- data/lib/aikido/zen/collector/stats.rb +17 -17
- data/lib/aikido/zen/config.rb +50 -25
- data/lib/aikido/zen/errors.rb +0 -8
- data/lib/aikido/zen/event.rb +12 -12
- data/lib/aikido/zen/ipc/ipc.rb +506 -0
- data/lib/aikido/zen/ipc/rpc.rb +349 -0
- data/lib/aikido/zen/ipc.rb +4 -0
- data/lib/aikido/zen/middleware/fork_detector.rb +8 -2
- data/lib/aikido/zen/middleware/rack_throttler.rb +2 -4
- data/lib/aikido/zen/outbound_connection.rb +3 -3
- data/lib/aikido/zen/payload.rb +3 -3
- data/lib/aikido/zen/rate_limiter/result.rb +20 -0
- data/lib/aikido/zen/request.rb +14 -9
- data/lib/aikido/zen/route.rb +3 -3
- data/lib/aikido/zen/sinks/action_controller.rb +2 -4
- data/lib/aikido/zen/system_info.rb +13 -13
- data/lib/aikido/zen/version.rb +1 -1
- data/lib/aikido/zen/worker_process/agent/client.rb +141 -0
- data/lib/aikido/zen/worker_process/agent/server.rb +97 -0
- data/lib/aikido/zen/worker_process/agent.rb +4 -0
- data/lib/aikido/zen/worker_process.rb +3 -0
- data/lib/aikido/zen.rb +55 -29
- metadata +12 -6
- data/lib/aikido/zen/detached_agent/agent.rb +0 -79
- data/lib/aikido/zen/detached_agent/front_object.rb +0 -41
- data/lib/aikido/zen/detached_agent/server.rb +0 -78
- data/lib/aikido/zen/detached_agent.rb +0 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eba16aa05cb3f3506e487a3dfd7c03a1d4bbc186a397053aaf43562fad1ed4f4
|
|
4
|
+
data.tar.gz: b9815eab35c5f245eba4587c64bf4bfda422c1bcfea1d1d21eee747057723e41
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c99f6276ce2d33f42d3b9ec7f5430bb647cd72cb146e583066d97d918cc0e59b21d82999dada088197627be1226579cbea675f7750c609346a2d7487f39b3e5
|
|
7
|
+
data.tar.gz: ad2e3595f4fee8181ba2083f05a2a15181cf62db472f47c112a3480dd6575846cf50d7e9c3ac420a5f3ddbd48bda9b98cff815de50a12a35e427ebbce7e68427
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require "aikido-zen"
|
|
6
|
+
|
|
7
|
+
class BasicIO
|
|
8
|
+
def initialize(data, timeout: 5)
|
|
9
|
+
@data = data
|
|
10
|
+
@size = data.bytesize
|
|
11
|
+
@timeout = timeout
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def read(socket)
|
|
15
|
+
buffer = String.new(encoding: Encoding::BINARY)
|
|
16
|
+
|
|
17
|
+
while buffer.bytesize < @size
|
|
18
|
+
case chunk = socket.read_nonblock(@size - buffer.bytesize, exception: false)
|
|
19
|
+
when :wait_readable
|
|
20
|
+
raise Errno::ETIMEDOUT, "read timed out" unless IO.select([socket], nil, nil, @timeout)
|
|
21
|
+
when nil
|
|
22
|
+
raise EOFError
|
|
23
|
+
else
|
|
24
|
+
buffer << chunk
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
buffer
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def write(socket)
|
|
32
|
+
written = 0
|
|
33
|
+
|
|
34
|
+
while written < @data.bytesize
|
|
35
|
+
case n = socket.write_nonblock(@data.byteslice(written..), exception: false)
|
|
36
|
+
when :wait_writable
|
|
37
|
+
raise Errno::ETIMEDOUT, "write timed out" unless IO.select(nil, [socket], nil, @timeout)
|
|
38
|
+
else
|
|
39
|
+
written += n
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class TimedIO
|
|
46
|
+
include Aikido::Zen::IPC::TimedIO
|
|
47
|
+
|
|
48
|
+
def initialize(data, timeout: 5)
|
|
49
|
+
@data = data
|
|
50
|
+
@size = data.bytesize
|
|
51
|
+
@timeout = timeout
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def read(socket)
|
|
55
|
+
read_with_deadline(socket, @size, Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def write(socket)
|
|
59
|
+
write_with_deadline(socket, @data, Process.clock_gettime(Process::CLOCK_MONOTONIC) + @timeout)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class FramedIO
|
|
64
|
+
include Aikido::Zen::IPC::FramedIO
|
|
65
|
+
|
|
66
|
+
def initialize(data, timeout: 5)
|
|
67
|
+
@data = data
|
|
68
|
+
@timeout = timeout
|
|
69
|
+
@buffer = String.new(encoding: Encoding::BINARY)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def read(socket)
|
|
73
|
+
read_coalesced_frame_with_timeout(socket, @buffer, nil, @timeout)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def write(socket)
|
|
77
|
+
write_coalesced_frame_with_timeout(socket, @data, nil, @timeout)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def run(duration, io)
|
|
82
|
+
secret = SecureRandom.bytes(32)
|
|
83
|
+
|
|
84
|
+
server = Aikido::Zen::IPC::Server.start(secret) do |socket|
|
|
85
|
+
loop do
|
|
86
|
+
io.read(socket)
|
|
87
|
+
end
|
|
88
|
+
rescue EOFError, Errno::ECONNRESET, Errno::EPIPE
|
|
89
|
+
# disconnected
|
|
90
|
+
rescue IOError
|
|
91
|
+
# client stopped
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
client = Aikido::Zen::IPC::Client.new(secret, server.host, server.port)
|
|
95
|
+
|
|
96
|
+
sent = 0
|
|
97
|
+
|
|
98
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
|
|
99
|
+
|
|
100
|
+
result = Benchmark.measure do
|
|
101
|
+
while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
|
|
102
|
+
io.write(client.socket)
|
|
103
|
+
|
|
104
|
+
sent += 1
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
[sent, result.real]
|
|
109
|
+
ensure
|
|
110
|
+
client.close
|
|
111
|
+
server.stop
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def report(label, sent, real, data_size, baseline: nil)
|
|
115
|
+
rate = sent / real
|
|
116
|
+
mib_per_sec = rate * data_size / (1024 * 1024)
|
|
117
|
+
|
|
118
|
+
puts label
|
|
119
|
+
puts "#{rate.round} messages/sec, #{mib_per_sec.round(2)} MiB/sec"
|
|
120
|
+
|
|
121
|
+
if baseline
|
|
122
|
+
overhead = 100 - (100.0 * rate / baseline)
|
|
123
|
+
puts "#{overhead.round(1)}% fewer messages/sec than baseline"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
if __FILE__ == $0
|
|
128
|
+
duration = (ENV["DURATION"] || 5).to_i
|
|
129
|
+
data_size = (ENV["DATA_SIZE"] || 1024).to_i
|
|
130
|
+
|
|
131
|
+
data = SecureRandom.bytes(data_size)
|
|
132
|
+
|
|
133
|
+
basic_io = BasicIO.new(data)
|
|
134
|
+
basic_io_sent, basic_io_real = run(duration, basic_io)
|
|
135
|
+
report("baseline", basic_io_sent, basic_io_real, data_size)
|
|
136
|
+
basic_io_rate = basic_io_sent / basic_io_real
|
|
137
|
+
|
|
138
|
+
puts
|
|
139
|
+
|
|
140
|
+
timed_io = TimedIO.new(data)
|
|
141
|
+
timed_io_sent, timed_io_real = run(duration, timed_io)
|
|
142
|
+
report("TimedIO", timed_io_sent, timed_io_real, data_size, baseline: basic_io_rate)
|
|
143
|
+
|
|
144
|
+
puts
|
|
145
|
+
|
|
146
|
+
framed_io = FramedIO.new(data)
|
|
147
|
+
framed_io_sent, framed_io_real = run(duration, framed_io)
|
|
148
|
+
report("FramedIO (coalesced)", framed_io_sent, framed_io_real, data_size, baseline: basic_io_rate)
|
|
149
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "benchmark"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require "logger"
|
|
6
|
+
require "aikido-zen"
|
|
7
|
+
|
|
8
|
+
def run(duration, concurrency)
|
|
9
|
+
secret = SecureRandom.bytes(32)
|
|
10
|
+
logger = Logger.new(File::NULL)
|
|
11
|
+
|
|
12
|
+
server = Aikido::Zen::RPC::Server.start(secret, logger: logger) do |server|
|
|
13
|
+
server.handle("echo") { |respond, value| respond.call(value) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
client = Aikido::Zen::RPC::Client.start(secret, server.host, server.port, logger: logger)
|
|
17
|
+
|
|
18
|
+
calls = Concurrent::AtomicFixnum.new(0)
|
|
19
|
+
|
|
20
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + duration
|
|
21
|
+
|
|
22
|
+
result = Benchmark.measure do
|
|
23
|
+
threads = concurrency.times.map do
|
|
24
|
+
Thread.new do
|
|
25
|
+
while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
|
|
26
|
+
client.invoke("echo", 5.0, "hello")
|
|
27
|
+
|
|
28
|
+
calls.increment
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
threads.each(&:join)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
[calls.value, result.real]
|
|
37
|
+
ensure
|
|
38
|
+
client.stop
|
|
39
|
+
server.stop
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def report(label, total_calls, real)
|
|
43
|
+
rate = total_calls / real
|
|
44
|
+
latency_ms = real / total_calls * 1000
|
|
45
|
+
|
|
46
|
+
puts label
|
|
47
|
+
puts "#{rate.round} calls/sec, #{latency_ms.round(3)} ms/call"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if __FILE__ == $0
|
|
51
|
+
duration = (ENV["DURATION"] || 5).to_i
|
|
52
|
+
concurrency = (ENV["CONCURRENCY"] || 1).to_i
|
|
53
|
+
|
|
54
|
+
calls, real = run(duration, concurrency)
|
|
55
|
+
|
|
56
|
+
report("RPC echo (concurrency: #{concurrency})", calls, real)
|
|
57
|
+
end
|
data/docs/rails.md
CHANGED
|
@@ -27,6 +27,28 @@ Bundler.require(*Rails.groups)
|
|
|
27
27
|
That's it! Zen will start to run inside your app when it starts getting
|
|
28
28
|
requests.
|
|
29
29
|
|
|
30
|
+
## Running with multiple worker processes (Puma)
|
|
31
|
+
|
|
32
|
+
Zen can share rate limiting state, runtime settings, and runtime stats
|
|
33
|
+
across all of an app's worker processes. This only works if Rails is booted
|
|
34
|
+
once and then forked into workers, which on Puma requires
|
|
35
|
+
[`preload_app!`][puma-preload]:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
# config/puma.rb
|
|
39
|
+
preload_app!
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Without `preload_app!`, each worker boots Rails independently, so Zen
|
|
43
|
+
tracks rate limits, settings, and stats separately per worker instead
|
|
44
|
+
of sharing them.
|
|
45
|
+
|
|
46
|
+
> [!NOTE]
|
|
47
|
+
> This only matters for multi-process setups; it doesn't apply to
|
|
48
|
+
> single-process or threaded-only deployments.
|
|
49
|
+
|
|
50
|
+
[puma-preload]: https://github.com/puma/puma#cluster-mode
|
|
51
|
+
|
|
30
52
|
## Rate limiting and user blocking
|
|
31
53
|
|
|
32
54
|
If you want to add the rate limiting feature to your app, modify your code like this:
|
data/lib/aikido/zen/actor.rb
CHANGED
|
@@ -40,11 +40,11 @@ module Aikido::Zen
|
|
|
40
40
|
class Actor
|
|
41
41
|
def self.from_json(data)
|
|
42
42
|
new(
|
|
43
|
-
id: data[
|
|
44
|
-
name: data[
|
|
45
|
-
ip: data[
|
|
46
|
-
first_seen_at: Time.at(data[
|
|
47
|
-
last_seen_at: Time.at(data[
|
|
43
|
+
id: data["id"],
|
|
44
|
+
name: data["name"],
|
|
45
|
+
ip: data["lastIpAddress"],
|
|
46
|
+
first_seen_at: Time.at(data["firstSeenAt"] / 1000),
|
|
47
|
+
last_seen_at: Time.at(data["lastSeenAt"] / 1000)
|
|
48
48
|
)
|
|
49
49
|
end
|
|
50
50
|
|
|
@@ -135,11 +135,11 @@ module Aikido::Zen
|
|
|
135
135
|
|
|
136
136
|
def as_json
|
|
137
137
|
{
|
|
138
|
-
id
|
|
139
|
-
name
|
|
140
|
-
lastIpAddress
|
|
141
|
-
firstSeenAt
|
|
142
|
-
lastSeenAt
|
|
138
|
+
"id" => id,
|
|
139
|
+
"name" => name,
|
|
140
|
+
"lastIpAddress" => ip,
|
|
141
|
+
"firstSeenAt" => first_seen_at.to_i * 1000,
|
|
142
|
+
"lastSeenAt" => last_seen_at.to_i * 1000
|
|
143
143
|
}.compact
|
|
144
144
|
end
|
|
145
145
|
end
|
data/lib/aikido/zen/agent.rb
CHANGED
|
@@ -19,14 +19,12 @@ module Aikido::Zen
|
|
|
19
19
|
def initialize(
|
|
20
20
|
config: Aikido::Zen.config,
|
|
21
21
|
collector: Aikido::Zen.collector,
|
|
22
|
-
detached_agent: Aikido::Zen.detached_agent,
|
|
23
22
|
worker: Aikido::Zen::Worker.new(config: config),
|
|
24
23
|
api_client: Aikido::Zen::APIClient.new(config: config),
|
|
25
24
|
api_stream: Aikido::Zen::APIStream.new(config: config)
|
|
26
25
|
)
|
|
27
26
|
@config = config
|
|
28
27
|
@collector = collector
|
|
29
|
-
@detached_agent = detached_agent
|
|
30
28
|
@worker = worker
|
|
31
29
|
@api_client = api_client
|
|
32
30
|
@api_stream = api_stream
|
|
@@ -61,26 +59,36 @@ module Aikido::Zen
|
|
|
61
59
|
return
|
|
62
60
|
end
|
|
63
61
|
|
|
62
|
+
# Code coverage is disabled here because the block is only called when
|
|
63
|
+
# the process exits.
|
|
64
|
+
# :nocov:
|
|
64
65
|
at_exit { stop! if started? }
|
|
66
|
+
# :nocov:
|
|
65
67
|
|
|
66
68
|
report(Events::Started.new(time: @started_at)) do |response|
|
|
67
|
-
if update_settings_from_runtime_config!(response)
|
|
69
|
+
if update_settings_from_runtime_config!(response, reason: "after start")
|
|
68
70
|
updated_settings!
|
|
69
|
-
|
|
71
|
+
# :nocov:
|
|
72
|
+
else
|
|
73
|
+
# empty
|
|
70
74
|
end
|
|
75
|
+
# :nocov:
|
|
71
76
|
rescue => err
|
|
72
77
|
@config.logger.error(err.message)
|
|
73
78
|
end
|
|
74
79
|
|
|
75
80
|
begin
|
|
76
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
77
|
-
@config.logger.info("Updated runtime firewall list")
|
|
81
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after start")
|
|
78
82
|
rescue => err
|
|
79
83
|
@config.logger.error(err.message)
|
|
80
84
|
end
|
|
81
85
|
|
|
82
86
|
if @config.realtime_settings_updates_enabled?
|
|
83
|
-
@api_stream.handle("config-updated")
|
|
87
|
+
@api_stream.handle("config-updated") do |event|
|
|
88
|
+
@config.logger.debug("Received server-sent event: config-updated")
|
|
89
|
+
settings_updated(event)
|
|
90
|
+
end
|
|
91
|
+
|
|
84
92
|
@api_stream.start!
|
|
85
93
|
end
|
|
86
94
|
|
|
@@ -169,13 +177,15 @@ module Aikido::Zen
|
|
|
169
177
|
|
|
170
178
|
heartbeat = @collector.flush
|
|
171
179
|
report(heartbeat) do |response|
|
|
172
|
-
if update_settings_from_runtime_config!(response)
|
|
180
|
+
if update_settings_from_runtime_config!(response, reason: "after heartbeat")
|
|
173
181
|
updated_settings!
|
|
174
|
-
@config.logger.info("Updated runtime settings after heartbeat")
|
|
175
182
|
|
|
176
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
177
|
-
|
|
183
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after heartbeat")
|
|
184
|
+
# :nocov:
|
|
185
|
+
else
|
|
186
|
+
# empty
|
|
178
187
|
end
|
|
188
|
+
# :nocov:
|
|
179
189
|
end
|
|
180
190
|
end
|
|
181
191
|
|
|
@@ -189,13 +199,15 @@ module Aikido::Zen
|
|
|
189
199
|
def poll_for_setting_updates
|
|
190
200
|
@worker.every(@config.polling_interval) do
|
|
191
201
|
if @api_client.should_fetch_settings?
|
|
192
|
-
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
|
|
202
|
+
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after polling")
|
|
193
203
|
updated_settings!
|
|
194
|
-
|
|
204
|
+
# :nocov:
|
|
205
|
+
else
|
|
206
|
+
# empty
|
|
195
207
|
end
|
|
208
|
+
# :nocov:
|
|
196
209
|
|
|
197
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
198
|
-
@config.logger.info("Updated runtime firewall list after polling")
|
|
210
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after polling")
|
|
199
211
|
end
|
|
200
212
|
end
|
|
201
213
|
end
|
|
@@ -206,13 +218,15 @@ module Aikido::Zen
|
|
|
206
218
|
updated_at = Time.at(event[:data]["configUpdatedAt"].to_i)
|
|
207
219
|
|
|
208
220
|
if should_fetch_settings?(updated_at)
|
|
209
|
-
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
|
|
221
|
+
if update_settings_from_runtime_config!(@api_client.fetch_runtime_config, reason: "after server-sent event")
|
|
210
222
|
updated_settings!
|
|
211
|
-
@config.logger.info("Updated runtime settings after server-side event")
|
|
212
223
|
|
|
213
|
-
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
|
|
214
|
-
|
|
224
|
+
update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists, reason: "after server-sent event")
|
|
225
|
+
# :nocov:
|
|
226
|
+
else
|
|
227
|
+
# empty
|
|
215
228
|
end
|
|
229
|
+
# :nocov:
|
|
216
230
|
end
|
|
217
231
|
end
|
|
218
232
|
|
|
@@ -231,22 +245,44 @@ module Aikido::Zen
|
|
|
231
245
|
end
|
|
232
246
|
|
|
233
247
|
# @param data [Hash]
|
|
234
|
-
# @
|
|
235
|
-
|
|
248
|
+
# @param reason [String] context appended to the log message when logged
|
|
249
|
+
# @return [Boolean] whether the settings were updated; or nil if another
|
|
250
|
+
# thread is already applying an update.
|
|
251
|
+
def update_settings_from_runtime_config!(data, reason:)
|
|
252
|
+
# :nocov:
|
|
236
253
|
return unless @runtime_config_update_mutex.try_lock
|
|
254
|
+
# :nocov:
|
|
237
255
|
begin
|
|
238
|
-
Aikido::Zen.
|
|
256
|
+
return false unless Aikido::Zen.api_cache.update_runtime_config(data)
|
|
257
|
+
|
|
258
|
+
if Aikido::Zen.runtime_settings.update_from_runtime_config_json(data)
|
|
259
|
+
@config.logger.info("Updated runtime settings #{reason}")
|
|
260
|
+
true
|
|
261
|
+
else
|
|
262
|
+
false
|
|
263
|
+
end
|
|
239
264
|
ensure
|
|
240
265
|
@runtime_config_update_mutex.unlock
|
|
241
266
|
end
|
|
242
267
|
end
|
|
243
268
|
|
|
244
269
|
# @param data [Hash]
|
|
245
|
-
# @
|
|
246
|
-
|
|
270
|
+
# @param reason [String] context appended to the log message when logged
|
|
271
|
+
# @return [Boolean] whether the settings were updated; or nil if another
|
|
272
|
+
# thread is already applying an update.
|
|
273
|
+
def update_settings_from_runtime_firewall_lists!(data, reason:)
|
|
274
|
+
# :nocov:
|
|
247
275
|
return unless @runtime_firewall_lists_update_mutex.try_lock
|
|
276
|
+
# :nocov:
|
|
248
277
|
begin
|
|
249
|
-
Aikido::Zen.
|
|
278
|
+
return false unless Aikido::Zen.api_cache.update_runtime_firewall_lists(data)
|
|
279
|
+
|
|
280
|
+
if Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(data)
|
|
281
|
+
@config.logger.info("Updated runtime firewall list #{reason}")
|
|
282
|
+
true
|
|
283
|
+
else
|
|
284
|
+
false
|
|
285
|
+
end
|
|
250
286
|
ensure
|
|
251
287
|
@runtime_firewall_lists_update_mutex.unlock
|
|
252
288
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aikido::Zen
|
|
4
|
+
class APICache
|
|
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
|
|
60
|
+
end
|
|
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/attack.rb
CHANGED
|
@@ -44,11 +44,11 @@ module Aikido::Zen
|
|
|
44
44
|
|
|
45
45
|
def as_json
|
|
46
46
|
{
|
|
47
|
-
kind
|
|
48
|
-
blocked
|
|
49
|
-
metadata
|
|
50
|
-
operation
|
|
51
|
-
stack
|
|
47
|
+
"kind" => kind,
|
|
48
|
+
"blocked" => blocked?,
|
|
49
|
+
"metadata" => metadata,
|
|
50
|
+
"operation" => @operation,
|
|
51
|
+
"stack" => @stack
|
|
52
52
|
}.compact.merge(input.as_json)
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -70,7 +70,7 @@ module Aikido::Zen
|
|
|
70
70
|
|
|
71
71
|
def metadata
|
|
72
72
|
{
|
|
73
|
-
filename
|
|
73
|
+
"filename" => filepath
|
|
74
74
|
}
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -107,7 +107,7 @@ module Aikido::Zen
|
|
|
107
107
|
|
|
108
108
|
def metadata
|
|
109
109
|
{
|
|
110
|
-
command
|
|
110
|
+
"command" => @command
|
|
111
111
|
}
|
|
112
112
|
end
|
|
113
113
|
|
|
@@ -139,9 +139,9 @@ module Aikido::Zen
|
|
|
139
139
|
|
|
140
140
|
def metadata
|
|
141
141
|
{
|
|
142
|
-
sql
|
|
143
|
-
dialect
|
|
144
|
-
failedToTokenize
|
|
142
|
+
"sql" => @query,
|
|
143
|
+
"dialect" => @dialect.name,
|
|
144
|
+
"failedToTokenize" => @failed_to_tokenize || nil
|
|
145
145
|
}.compact
|
|
146
146
|
end
|
|
147
147
|
|
|
@@ -174,8 +174,8 @@ module Aikido::Zen
|
|
|
174
174
|
|
|
175
175
|
def metadata
|
|
176
176
|
{
|
|
177
|
-
hostname
|
|
178
|
-
port
|
|
177
|
+
"hostname" => @request.uri.hostname,
|
|
178
|
+
"port" => @request.uri.port.to_s
|
|
179
179
|
}
|
|
180
180
|
end
|
|
181
181
|
end
|
|
@@ -212,8 +212,8 @@ module Aikido::Zen
|
|
|
212
212
|
|
|
213
213
|
def metadata
|
|
214
214
|
{
|
|
215
|
-
hostname
|
|
216
|
-
privateIP
|
|
215
|
+
"hostname" => @hostname,
|
|
216
|
+
"privateIP" => @address
|
|
217
217
|
}
|
|
218
218
|
end
|
|
219
219
|
end
|
|
@@ -69,9 +69,9 @@ module Aikido::Zen
|
|
|
69
69
|
|
|
70
70
|
def as_json
|
|
71
71
|
{
|
|
72
|
-
ipAddress
|
|
73
|
-
userAgent
|
|
74
|
-
source
|
|
72
|
+
"ipAddress" => @ip_address,
|
|
73
|
+
"userAgent" => @user_agent,
|
|
74
|
+
"source" => @source
|
|
75
75
|
}.compact
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -101,10 +101,10 @@ module Aikido::Zen
|
|
|
101
101
|
|
|
102
102
|
def as_json
|
|
103
103
|
{
|
|
104
|
-
metadata
|
|
105
|
-
samples
|
|
104
|
+
"metadata" => {
|
|
105
|
+
"samples" => @samples.as_json.to_json # The API only accepts string values in metadata
|
|
106
106
|
},
|
|
107
|
-
user
|
|
107
|
+
"user" => @user.as_json
|
|
108
108
|
}.compact
|
|
109
109
|
end
|
|
110
110
|
|
|
@@ -130,8 +130,8 @@ module Aikido::Zen
|
|
|
130
130
|
|
|
131
131
|
def as_json
|
|
132
132
|
{
|
|
133
|
-
method
|
|
134
|
-
url
|
|
133
|
+
"method" => @verb.as_json,
|
|
134
|
+
"url" => @path.as_json
|
|
135
135
|
}.compact
|
|
136
136
|
end
|
|
137
137
|
|