aikido-zen 1.0.1.beta.5-arm64-linux → 1.0.2-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/.simplecov +6 -0
- data/README.md +2 -0
- data/benchmarks/README.md +0 -1
- data/benchmarks/rails7.1_benchmark.js +1 -0
- data/benchmarks/rails7.1_sql_injection.js +52 -20
- data/docs/config.md +9 -1
- data/docs/proxy.md +10 -0
- data/docs/rails.md +55 -13
- data/docs/troubleshooting.md +62 -0
- data/lib/aikido/zen/actor.rb +34 -4
- data/lib/aikido/zen/agent/heartbeats_manager.rb +5 -5
- data/lib/aikido/zen/agent.rb +19 -17
- data/lib/aikido/zen/attack.rb +19 -9
- data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
- data/lib/aikido/zen/attack_wave.rb +88 -0
- data/lib/aikido/zen/cache.rb +91 -0
- data/lib/aikido/zen/capped_collections.rb +22 -4
- data/lib/aikido/zen/collector/event.rb +238 -0
- data/lib/aikido/zen/collector/hosts.rb +16 -1
- data/lib/aikido/zen/collector/routes.rb +13 -8
- data/lib/aikido/zen/collector/stats.rb +33 -22
- data/lib/aikido/zen/collector/users.rb +5 -3
- data/lib/aikido/zen/collector.rb +107 -28
- data/lib/aikido/zen/config.rb +54 -21
- data/lib/aikido/zen/context/rack_request.rb +3 -0
- data/lib/aikido/zen/context/rails_request.rb +3 -0
- data/lib/aikido/zen/context.rb +42 -9
- data/lib/aikido/zen/detached_agent/agent.rb +28 -27
- data/lib/aikido/zen/detached_agent/front_object.rb +10 -6
- data/lib/aikido/zen/detached_agent/server.rb +63 -26
- data/lib/aikido/zen/event.rb +47 -2
- data/lib/aikido/zen/helpers.rb +24 -0
- data/lib/aikido/zen/internals.rb +23 -3
- data/lib/aikido/zen/libzen-v0.1.48-arm64-linux.so +0 -0
- data/lib/aikido/zen/middleware/{check_allowed_addresses.rb → allowed_address_checker.rb} +1 -1
- data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
- data/lib/aikido/zen/middleware/{set_context.rb → context_setter.rb} +1 -1
- data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
- data/lib/aikido/zen/middleware/rack_throttler.rb +3 -1
- data/lib/aikido/zen/middleware/request_tracker.rb +9 -4
- data/lib/aikido/zen/outbound_connection.rb +18 -1
- data/lib/aikido/zen/payload.rb +1 -1
- data/lib/aikido/zen/rails_engine.rb +5 -8
- data/lib/aikido/zen/request/rails_router.rb +17 -2
- data/lib/aikido/zen/request.rb +21 -36
- data/lib/aikido/zen/route.rb +57 -0
- data/lib/aikido/zen/runtime_settings/endpoints.rb +37 -8
- data/lib/aikido/zen/runtime_settings.rb +6 -5
- data/lib/aikido/zen/scanners/path_traversal/helpers.rb +10 -7
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +5 -4
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +3 -2
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +3 -2
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +2 -1
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +8 -2
- data/lib/aikido/zen/sink.rb +1 -1
- data/lib/aikido/zen/sinks/action_controller.rb +3 -1
- data/lib/aikido/zen/sinks/file.rb +45 -4
- data/lib/aikido/zen/sinks/kernel.rb +1 -1
- data/lib/aikido/zen/sinks/socket.rb +7 -0
- data/lib/aikido/zen/sinks_dsl.rb +12 -0
- data/lib/aikido/zen/system_info.rb +1 -5
- data/lib/aikido/zen/version.rb +2 -2
- data/lib/aikido/zen.rb +77 -15
- data/tasklib/bench.rake +1 -1
- data/tasklib/libzen.rake +1 -0
- metadata +15 -5
- data/lib/aikido/zen/libzen-v0.1.39-arm64-linux.so +0 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aikido::Zen
|
|
4
|
+
class Collector
|
|
5
|
+
class Event
|
|
6
|
+
@@registry = {}
|
|
7
|
+
|
|
8
|
+
# @api protected
|
|
9
|
+
def self.register(type)
|
|
10
|
+
const_set(:TYPE, type)
|
|
11
|
+
@@registry[type] = self
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.from_json(data)
|
|
15
|
+
type = data[:type]
|
|
16
|
+
subclass = @@registry[type]
|
|
17
|
+
subclass.from_json(data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :type
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
@type = self.class::TYPE
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def as_json
|
|
27
|
+
{
|
|
28
|
+
type: @type
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def handle(collector)
|
|
33
|
+
raise NotImplementedError, "implement in subclasses"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @api private
|
|
38
|
+
module Events
|
|
39
|
+
class TrackRequest < Event
|
|
40
|
+
register "track_request"
|
|
41
|
+
|
|
42
|
+
def self.from_json(data)
|
|
43
|
+
new
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def handle(collector)
|
|
47
|
+
collector.handle_track_request
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def inspect
|
|
51
|
+
"#<#{self.class.name}>"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class TrackAttackWave < Event
|
|
56
|
+
register "track_attack_wave"
|
|
57
|
+
|
|
58
|
+
def self.from_json(data)
|
|
59
|
+
new(
|
|
60
|
+
being_blocked: data[:being_blocked]
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def initialize(being_blocked:)
|
|
65
|
+
super()
|
|
66
|
+
@being_blocked = being_blocked
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def as_json
|
|
70
|
+
super.update({
|
|
71
|
+
being_blocked: @being_blocked
|
|
72
|
+
})
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def handle(collector)
|
|
76
|
+
collector.handle_track_attack_wave(being_blocked: @being_blocked)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def inspect
|
|
80
|
+
"#<#{self.class.name} #{@being_blocked}>"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class TrackScan < Event
|
|
85
|
+
register "track_scan"
|
|
86
|
+
|
|
87
|
+
def self.from_json(data)
|
|
88
|
+
new(
|
|
89
|
+
data[:sink_name],
|
|
90
|
+
data[:duration],
|
|
91
|
+
has_errors: data[:has_errors]
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def initialize(sink_name, duration, has_errors:)
|
|
96
|
+
super()
|
|
97
|
+
@sink_name = sink_name
|
|
98
|
+
@duration = duration
|
|
99
|
+
@has_errors = has_errors
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def as_json
|
|
103
|
+
super.update({
|
|
104
|
+
sink_name: @sink_name,
|
|
105
|
+
duration: @duration,
|
|
106
|
+
has_errors: @has_errors
|
|
107
|
+
})
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def handle(collector)
|
|
111
|
+
collector.handle_track_scan(@sink_name, @duration, has_errors: @has_errors)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def inspect
|
|
115
|
+
"#<#{self.class.name} #{@sink_name} #{format "%0.6f", @duration} #{@has_errors}>"
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class TrackAttack < Event
|
|
120
|
+
register "track_attack"
|
|
121
|
+
|
|
122
|
+
def self.from_json(data)
|
|
123
|
+
new(
|
|
124
|
+
data[:sink_name],
|
|
125
|
+
being_blocked: data[:being_blocked]
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def initialize(sink_name, being_blocked:)
|
|
130
|
+
super()
|
|
131
|
+
@sink_name = sink_name
|
|
132
|
+
@being_blocked = being_blocked
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def as_json
|
|
136
|
+
super.update({
|
|
137
|
+
sink_name: @sink_name,
|
|
138
|
+
being_blocked: @being_blocked
|
|
139
|
+
})
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def handle(collector)
|
|
143
|
+
collector.handle_track_attack(@sink_name, being_blocked: @being_blocked)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def inspect
|
|
147
|
+
"#<#{self.class.name} #{@sink_name} #{@being_blocked}>"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class TrackUser < Event
|
|
152
|
+
register "track_user"
|
|
153
|
+
|
|
154
|
+
def self.from_json(data)
|
|
155
|
+
new(Aikido::Zen::Actor.from_json(data[:actor]))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def initialize(actor)
|
|
159
|
+
super()
|
|
160
|
+
@actor = actor
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def as_json
|
|
164
|
+
super.update({
|
|
165
|
+
actor: @actor.as_json
|
|
166
|
+
})
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def handle(collector)
|
|
170
|
+
collector.handle_track_user(@actor)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def inspect
|
|
174
|
+
"#<#{self.class.name} #{@actor.id} #{@actor.name}>"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class TrackOutbound < Event
|
|
179
|
+
register "track_outbound"
|
|
180
|
+
|
|
181
|
+
def self.from_json(data)
|
|
182
|
+
new(OutboundConnection.from_json(data[:connection]))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def initialize(connection)
|
|
186
|
+
super()
|
|
187
|
+
@connection = connection
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def as_json
|
|
191
|
+
super.update({
|
|
192
|
+
connection: @connection.as_json
|
|
193
|
+
})
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def handle(collector)
|
|
197
|
+
collector.handle_track_outbound(@connection)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def inspect
|
|
201
|
+
"#<#{self.class.name} #{@connection.host}:#{@connection.port}>"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
class TrackRoute < Event
|
|
206
|
+
register "track_route"
|
|
207
|
+
|
|
208
|
+
def self.from_json(data)
|
|
209
|
+
new(
|
|
210
|
+
Route.from_json(data[:route]),
|
|
211
|
+
Request::Schema.from_json(data[:schema])
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def initialize(route, schema)
|
|
216
|
+
super()
|
|
217
|
+
@route = route
|
|
218
|
+
@schema = schema
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def as_json
|
|
222
|
+
super.update({
|
|
223
|
+
route: @route.as_json,
|
|
224
|
+
schema: @schema.as_json
|
|
225
|
+
})
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def handle(collector)
|
|
229
|
+
collector.handle_track_route(@route, @schema)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def inspect
|
|
233
|
+
"#<#{self.class.name} #{@route.verb} #{@route.path.inspect}>"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
@@ -7,9 +7,24 @@ module Aikido::Zen
|
|
|
7
7
|
#
|
|
8
8
|
# Keeps track of the hostnames to which the app has made outbound HTTP
|
|
9
9
|
# requests.
|
|
10
|
-
class Collector::Hosts < Aikido::Zen::
|
|
10
|
+
class Collector::Hosts < Aikido::Zen::CappedMap
|
|
11
11
|
def initialize(config = Aikido::Zen.config)
|
|
12
12
|
super(config.max_outbound_connections)
|
|
13
13
|
end
|
|
14
|
+
|
|
15
|
+
# @param host [Aikido::Zen::OutboundConnection]
|
|
16
|
+
# @return [void]
|
|
17
|
+
def add(host)
|
|
18
|
+
self[host] ||= host
|
|
19
|
+
self[host].hit
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def each(&blk)
|
|
23
|
+
each_value(&blk)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def as_json
|
|
27
|
+
map(&:as_json)
|
|
28
|
+
end
|
|
14
29
|
end
|
|
15
30
|
end
|
|
@@ -7,6 +7,8 @@ module Aikido::Zen
|
|
|
7
7
|
#
|
|
8
8
|
# Keeps track of the visited routes.
|
|
9
9
|
class Collector::Routes
|
|
10
|
+
# @api private
|
|
11
|
+
# Visible for testing.
|
|
10
12
|
attr_reader :visits
|
|
11
13
|
|
|
12
14
|
def initialize(config = Aikido::Zen.config)
|
|
@@ -14,11 +16,11 @@ module Aikido::Zen
|
|
|
14
16
|
@visits = Hash.new { |h, k| h[k] = Record.new }
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
# @param
|
|
18
|
-
# @
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
# @param route [Aikido::Zen::Route] the route of the request
|
|
20
|
+
# @param schema [Aikido::Zen::Request::Schema] the schema for the request
|
|
21
|
+
# @return [void]
|
|
22
|
+
def add(route, schema)
|
|
23
|
+
@visits[route].increment(schema) unless route.nil?
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def as_json
|
|
@@ -33,6 +35,7 @@ module Aikido::Zen
|
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
# @api private
|
|
38
|
+
# Visible for testing.
|
|
36
39
|
def [](route)
|
|
37
40
|
@visits[route]
|
|
38
41
|
end
|
|
@@ -49,16 +52,18 @@ module Aikido::Zen
|
|
|
49
52
|
@config = config
|
|
50
53
|
end
|
|
51
54
|
|
|
52
|
-
def increment(
|
|
55
|
+
def increment(schema)
|
|
53
56
|
self.hits += 1
|
|
54
57
|
|
|
55
58
|
if sample_schema?
|
|
56
59
|
self.samples += 1
|
|
57
|
-
self.schema |=
|
|
60
|
+
self.schema |= schema
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
63
|
|
|
61
|
-
private
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def sample_schema?
|
|
62
67
|
samples < @config.api_schema_max_samples
|
|
63
68
|
end
|
|
64
69
|
end
|
|
@@ -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, :sinks
|
|
11
|
+
attr_reader :started_at, :ended_at, :requests, :aborted_requests, :attack_waves, :blocked_attack_waves, :sinks
|
|
12
12
|
|
|
13
13
|
# @!visibility private
|
|
14
14
|
attr_writer :ended_at
|
|
@@ -16,10 +16,13 @@ module Aikido::Zen
|
|
|
16
16
|
def initialize(config = Aikido::Zen.config)
|
|
17
17
|
super()
|
|
18
18
|
@config = config
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
@started_at = @ended_at = nil
|
|
21
21
|
@requests = 0
|
|
22
22
|
@aborted_requests = 0
|
|
23
|
+
@attack_waves = 0
|
|
24
|
+
@blocked_attack_waves = 0
|
|
25
|
+
@sinks = Hash.new { |h, k| h[k] = Collector::SinkStats.new(k, @config) }
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# @return [Boolean]
|
|
@@ -35,10 +38,9 @@ module Aikido::Zen
|
|
|
35
38
|
# Track the timestamp we start tracking this series of stats.
|
|
36
39
|
#
|
|
37
40
|
# @param at [Time]
|
|
38
|
-
# @return [
|
|
41
|
+
# @return [void]
|
|
39
42
|
def start(at = Time.now.utc)
|
|
40
43
|
@started_at = at
|
|
41
|
-
self
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
# Sets the end time for these stats block, freezes it to avoid any more
|
|
@@ -47,7 +49,7 @@ module Aikido::Zen
|
|
|
47
49
|
#
|
|
48
50
|
# @param at [Time] the time at which we're resetting, which is set as the
|
|
49
51
|
# ending time for the returned copy.
|
|
50
|
-
# @return [
|
|
52
|
+
# @return [void]
|
|
51
53
|
def flush(at: Time.now.utc)
|
|
52
54
|
# Make sure the timing stats are compressed before copying, since we
|
|
53
55
|
# need these compressed when we serialize this for the API.
|
|
@@ -56,31 +58,36 @@ module Aikido::Zen
|
|
|
56
58
|
freeze
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
# @return [
|
|
61
|
+
# @return [void]
|
|
60
62
|
def add_request
|
|
61
63
|
@requests += 1
|
|
62
|
-
self
|
|
63
64
|
end
|
|
64
65
|
|
|
65
|
-
# @param
|
|
66
|
-
# @return [
|
|
67
|
-
def
|
|
68
|
-
|
|
66
|
+
# @param being_blocked [Boolean] whether the Agent blocked the request
|
|
67
|
+
# @return [void]
|
|
68
|
+
def add_attack_wave(being_blocked:)
|
|
69
|
+
@attack_waves += 1
|
|
70
|
+
@blocked_attack_waves += 1 if being_blocked
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @param sink_name [String] the name of the sink
|
|
74
|
+
# @param duration [Float] the length the scan in seconds
|
|
75
|
+
# @param has_errors [Boolean] whether errors occurred during the scan
|
|
76
|
+
# @return [void]
|
|
77
|
+
def add_scan(sink_name, duration, has_errors:)
|
|
78
|
+
stats = @sinks[sink_name]
|
|
69
79
|
stats.scans += 1
|
|
70
|
-
stats.errors += 1 if
|
|
71
|
-
stats.add_timing(
|
|
72
|
-
self
|
|
80
|
+
stats.errors += 1 if has_errors
|
|
81
|
+
stats.add_timing(duration)
|
|
73
82
|
end
|
|
74
83
|
|
|
75
|
-
# @param
|
|
76
|
-
# @param being_blocked [Boolean] whether the Agent blocked the
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
stats = @sinks[attack.sink.name]
|
|
84
|
+
# @param sink_name [String] the name of the sink
|
|
85
|
+
# @param being_blocked [Boolean] whether the Agent blocked the request
|
|
86
|
+
# @return [void]
|
|
87
|
+
def add_attack(sink_name, being_blocked:)
|
|
88
|
+
stats = @sinks[sink_name]
|
|
81
89
|
stats.attacks += 1
|
|
82
90
|
stats.blocked_attacks += 1 if being_blocked
|
|
83
|
-
self
|
|
84
91
|
end
|
|
85
92
|
|
|
86
93
|
def as_json
|
|
@@ -88,13 +95,17 @@ module Aikido::Zen
|
|
|
88
95
|
{
|
|
89
96
|
startedAt: @started_at.to_i * 1000,
|
|
90
97
|
endedAt: (@ended_at.to_i * 1000 if @ended_at),
|
|
91
|
-
|
|
98
|
+
operations: @sinks.transform_values(&:as_json),
|
|
92
99
|
requests: {
|
|
93
100
|
total: @requests,
|
|
94
101
|
aborted: @aborted_requests,
|
|
95
102
|
attacksDetected: {
|
|
96
103
|
total: total_attacks,
|
|
97
104
|
blocked: total_blocked
|
|
105
|
+
},
|
|
106
|
+
attackWaves: {
|
|
107
|
+
total: @attack_waves,
|
|
108
|
+
blocked: @blocked_attack_waves
|
|
98
109
|
}
|
|
99
110
|
}
|
|
100
111
|
}
|
|
@@ -11,16 +11,18 @@ module Aikido::Zen
|
|
|
11
11
|
super(config.max_users_tracked)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
# @param actor [Aikido::Zen::Actor]
|
|
15
|
+
# @return [void]
|
|
14
16
|
def add(actor)
|
|
15
17
|
if key?(actor.id)
|
|
16
|
-
self[actor.id]
|
|
18
|
+
self[actor.id] |= actor
|
|
17
19
|
else
|
|
18
20
|
self[actor.id] = actor
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
def each(&
|
|
23
|
-
each_value(&
|
|
24
|
+
def each(&blk)
|
|
25
|
+
each_value(&blk)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
def as_json
|
data/lib/aikido/zen/collector.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "collector/event"
|
|
4
|
+
|
|
3
5
|
module Aikido::Zen
|
|
4
6
|
# Handles collecting all the runtime statistics to report back to the Aikido
|
|
5
7
|
# servers.
|
|
@@ -7,14 +9,44 @@ module Aikido::Zen
|
|
|
7
9
|
def initialize(config: Aikido::Zen.config)
|
|
8
10
|
@config = config
|
|
9
11
|
|
|
12
|
+
@events = Queue.new
|
|
13
|
+
|
|
10
14
|
@stats = Concurrent::AtomicReference.new(Stats.new(@config))
|
|
11
15
|
@users = Concurrent::AtomicReference.new(Users.new(@config))
|
|
12
16
|
@hosts = Concurrent::AtomicReference.new(Hosts.new(@config))
|
|
13
17
|
@routes = Concurrent::AtomicReference.new(Routes.new(@config))
|
|
14
|
-
|
|
18
|
+
|
|
15
19
|
@middleware_installed = Concurrent::AtomicBoolean.new
|
|
16
20
|
end
|
|
17
21
|
|
|
22
|
+
# Add an event, to be handled in the collector in current process or sent
|
|
23
|
+
# to a collector in another process.
|
|
24
|
+
#
|
|
25
|
+
# @param event [Aikido::Zen::Collector::Event] the event to add
|
|
26
|
+
# @return [void]
|
|
27
|
+
def add_event(event)
|
|
28
|
+
@events << event
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Boolean] whether the collector has any events
|
|
32
|
+
def has_events?
|
|
33
|
+
!@events.empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Flush all events.
|
|
37
|
+
#
|
|
38
|
+
# @return [Array<Aikido::Zen::Collector::Event>]
|
|
39
|
+
def flush_events
|
|
40
|
+
Array.new(@events.size) { @events.pop }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Handle the events in the queue.
|
|
44
|
+
#
|
|
45
|
+
# @return [void]
|
|
46
|
+
def handle
|
|
47
|
+
flush_events.each { |event| event.handle(self) }
|
|
48
|
+
end
|
|
49
|
+
|
|
18
50
|
# Flush all the stats into a Heartbeat event that can be reported back to
|
|
19
51
|
# the Aikido servers.
|
|
20
52
|
#
|
|
@@ -22,6 +54,8 @@ module Aikido::Zen
|
|
|
22
54
|
# of the new stats collection period. Defaults to now.
|
|
23
55
|
# @return [Aikido::Zen::Events::Heartbeat]
|
|
24
56
|
def flush(at: Time.now.utc)
|
|
57
|
+
handle
|
|
58
|
+
|
|
25
59
|
stats = @stats.get_and_set(Stats.new(@config))
|
|
26
60
|
users = @users.get_and_set(Users.new(@config))
|
|
27
61
|
hosts = @hosts.get_and_set(Hosts.new(@config))
|
|
@@ -30,21 +64,11 @@ module Aikido::Zen
|
|
|
30
64
|
start(at: at)
|
|
31
65
|
stats = stats.flush(at: at)
|
|
32
66
|
|
|
33
|
-
Events::Heartbeat.new(
|
|
67
|
+
Aikido::Zen::Events::Heartbeat.new(
|
|
34
68
|
stats: stats, users: users, hosts: hosts, routes: routes, middleware_installed: middleware_installed?
|
|
35
69
|
)
|
|
36
70
|
end
|
|
37
71
|
|
|
38
|
-
# Put heartbeats coming from child processes into the internal queue.
|
|
39
|
-
def push_heartbeat(heartbeat)
|
|
40
|
-
@heartbeats << heartbeat
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Drains into an array all the queued heartbeats
|
|
44
|
-
def flush_heartbeats
|
|
45
|
-
Array.new(@heartbeats.size) { @heartbeats.pop }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
72
|
# Sets the start time for this collection period.
|
|
49
73
|
#
|
|
50
74
|
# @param at [Time] defaults to now.
|
|
@@ -55,16 +79,27 @@ module Aikido::Zen
|
|
|
55
79
|
|
|
56
80
|
# Track stats about the requests
|
|
57
81
|
#
|
|
58
|
-
# @param request [Aikido::Zen::Request]
|
|
59
82
|
# @return [void]
|
|
60
|
-
def track_request
|
|
83
|
+
def track_request
|
|
84
|
+
add_event(Events::TrackRequest.new)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def handle_track_request
|
|
61
88
|
synchronize(@stats) { |stats| stats.add_request }
|
|
62
89
|
end
|
|
63
90
|
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
91
|
+
# Track stats about an attack detected by our scanners.
|
|
92
|
+
#
|
|
93
|
+
# @param attack [Aikido::Zen::Events::AttackWave]
|
|
94
|
+
# @return [void]
|
|
95
|
+
def track_attack_wave(being_blocked:)
|
|
96
|
+
add_event(Events::TrackAttackWave.new(being_blocked: being_blocked))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def handle_track_attack_wave(being_blocked:)
|
|
100
|
+
synchronize(@stats) do |stats|
|
|
101
|
+
stats.add_attack_wave(being_blocked: being_blocked)
|
|
102
|
+
end
|
|
68
103
|
end
|
|
69
104
|
|
|
70
105
|
# Track stats about a scan performed by one of our sinks.
|
|
@@ -72,7 +107,13 @@ module Aikido::Zen
|
|
|
72
107
|
# @param scan [Aikido::Zen::Scan]
|
|
73
108
|
# @return [void]
|
|
74
109
|
def track_scan(scan)
|
|
75
|
-
|
|
110
|
+
add_event(Events::TrackScan.new(scan.sink.name, scan.duration, has_errors: scan.errors?))
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def handle_track_scan(sink_name, duration, has_errors:)
|
|
114
|
+
synchronize(@stats) do |stats|
|
|
115
|
+
stats.add_scan(sink_name, duration, has_errors: has_errors)
|
|
116
|
+
end
|
|
76
117
|
end
|
|
77
118
|
|
|
78
119
|
# Track stats about an attack detected by our scanners.
|
|
@@ -80,25 +121,49 @@ module Aikido::Zen
|
|
|
80
121
|
# @param attack [Aikido::Zen::Attack]
|
|
81
122
|
# @return [void]
|
|
82
123
|
def track_attack(attack)
|
|
124
|
+
add_event(Events::TrackAttack.new(attack.sink.name, being_blocked: attack.blocked?))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def handle_track_attack(sink_name, being_blocked:)
|
|
83
128
|
synchronize(@stats) do |stats|
|
|
84
|
-
stats.add_attack(
|
|
129
|
+
stats.add_attack(sink_name, being_blocked: being_blocked)
|
|
85
130
|
end
|
|
86
131
|
end
|
|
87
132
|
|
|
133
|
+
# Track the user reported by the developer to be behind this request.
|
|
134
|
+
#
|
|
135
|
+
# @param actor [Aikido::Zen::Actor]
|
|
136
|
+
# @return [void]
|
|
137
|
+
def track_user(actor)
|
|
138
|
+
add_event(Events::TrackUser.new(actor))
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def handle_track_user(actor)
|
|
142
|
+
synchronize(@users) { |users| users.add(actor) }
|
|
143
|
+
end
|
|
144
|
+
|
|
88
145
|
# Track an HTTP connections to an external host.
|
|
89
146
|
#
|
|
90
147
|
# @param connection [Aikido::Zen::OutboundConnection]
|
|
91
148
|
# @return [void]
|
|
92
149
|
def track_outbound(connection)
|
|
150
|
+
add_event(Events::TrackOutbound.new(connection))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def handle_track_outbound(connection)
|
|
93
154
|
synchronize(@hosts) { |hosts| hosts.add(connection) }
|
|
94
155
|
end
|
|
95
156
|
|
|
96
|
-
#
|
|
157
|
+
# Record the visited endpoint, and if enabled, the API schema for this endpoint.
|
|
97
158
|
#
|
|
98
|
-
# @param
|
|
159
|
+
# @param request [Aikido::Zen::Request]
|
|
99
160
|
# @return [void]
|
|
100
|
-
def
|
|
101
|
-
|
|
161
|
+
def track_route(request)
|
|
162
|
+
add_event(Events::TrackRoute.new(request.route, request.schema))
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def handle_track_route(route, schema)
|
|
166
|
+
synchronize(@routes) { |routes| routes.add(route, schema) }
|
|
102
167
|
end
|
|
103
168
|
|
|
104
169
|
def middleware_installed!
|
|
@@ -106,26 +171,40 @@ module Aikido::Zen
|
|
|
106
171
|
end
|
|
107
172
|
|
|
108
173
|
# @api private
|
|
109
|
-
|
|
110
|
-
|
|
174
|
+
#
|
|
175
|
+
# @note Visible for testing.
|
|
176
|
+
def stats
|
|
177
|
+
handle
|
|
178
|
+
@stats.get
|
|
111
179
|
end
|
|
112
180
|
|
|
113
181
|
# @api private
|
|
182
|
+
#
|
|
183
|
+
# @note Visible for testing.
|
|
114
184
|
def users
|
|
185
|
+
handle
|
|
115
186
|
@users.get
|
|
116
187
|
end
|
|
117
188
|
|
|
118
189
|
# @api private
|
|
190
|
+
#
|
|
191
|
+
# @note Visible for testing.
|
|
119
192
|
def hosts
|
|
193
|
+
handle
|
|
120
194
|
@hosts.get
|
|
121
195
|
end
|
|
122
196
|
|
|
123
197
|
# @api private
|
|
124
|
-
|
|
125
|
-
|
|
198
|
+
#
|
|
199
|
+
# @note Visible for testing.
|
|
200
|
+
def routes
|
|
201
|
+
handle
|
|
202
|
+
@routes.get
|
|
126
203
|
end
|
|
127
204
|
|
|
128
205
|
# @api private
|
|
206
|
+
#
|
|
207
|
+
# @note Visible for testing.
|
|
129
208
|
def middleware_installed?
|
|
130
209
|
@middleware_installed.true?
|
|
131
210
|
end
|