aikido-zen 1.0.2-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 +7 -0
- data/.aikido +6 -0
- data/.ruby-version +1 -0
- data/.simplecov +32 -0
- data/.standard.yml +3 -0
- data/LICENSE +674 -0
- data/README.md +148 -0
- data/Rakefile +67 -0
- data/benchmarks/README.md +22 -0
- data/benchmarks/rails7.1_benchmark.js +1 -0
- data/benchmarks/rails7.1_sql_injection.js +102 -0
- data/docs/banner.svg +202 -0
- data/docs/config.md +133 -0
- data/docs/proxy.md +10 -0
- data/docs/rails.md +112 -0
- data/docs/troubleshooting.md +62 -0
- data/lib/aikido/zen/actor.rb +146 -0
- data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
- data/lib/aikido/zen/agent.rb +181 -0
- data/lib/aikido/zen/api_client.rb +145 -0
- data/lib/aikido/zen/attack.rb +217 -0
- data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
- data/lib/aikido/zen/attack_wave.rb +88 -0
- data/lib/aikido/zen/background_worker.rb +52 -0
- data/lib/aikido/zen/cache.rb +91 -0
- data/lib/aikido/zen/capped_collections.rb +86 -0
- data/lib/aikido/zen/collector/event.rb +238 -0
- data/lib/aikido/zen/collector/hosts.rb +30 -0
- data/lib/aikido/zen/collector/routes.rb +71 -0
- data/lib/aikido/zen/collector/sink_stats.rb +95 -0
- data/lib/aikido/zen/collector/stats.rb +122 -0
- data/lib/aikido/zen/collector/users.rb +32 -0
- data/lib/aikido/zen/collector.rb +223 -0
- data/lib/aikido/zen/config.rb +312 -0
- data/lib/aikido/zen/context/rack_request.rb +27 -0
- data/lib/aikido/zen/context/rails_request.rb +47 -0
- data/lib/aikido/zen/context.rb +145 -0
- data/lib/aikido/zen/detached_agent/agent.rb +79 -0
- data/lib/aikido/zen/detached_agent/front_object.rb +41 -0
- data/lib/aikido/zen/detached_agent/server.rb +78 -0
- data/lib/aikido/zen/detached_agent.rb +2 -0
- data/lib/aikido/zen/errors.rb +107 -0
- data/lib/aikido/zen/event.rb +116 -0
- data/lib/aikido/zen/helpers.rb +24 -0
- data/lib/aikido/zen/internals.rb +123 -0
- data/lib/aikido/zen/libzen-v0.1.48-aarch64-linux.so +0 -0
- data/lib/aikido/zen/middleware/allowed_address_checker.rb +26 -0
- data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
- data/lib/aikido/zen/middleware/context_setter.rb +26 -0
- data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
- data/lib/aikido/zen/middleware/middleware.rb +11 -0
- data/lib/aikido/zen/middleware/rack_throttler.rb +50 -0
- data/lib/aikido/zen/middleware/request_tracker.rb +197 -0
- data/lib/aikido/zen/outbound_connection.rb +62 -0
- data/lib/aikido/zen/outbound_connection_monitor.rb +23 -0
- data/lib/aikido/zen/package.rb +22 -0
- data/lib/aikido/zen/payload.rb +50 -0
- data/lib/aikido/zen/rails_engine.rb +53 -0
- data/lib/aikido/zen/rate_limiter/breaker.rb +61 -0
- data/lib/aikido/zen/rate_limiter/bucket.rb +76 -0
- data/lib/aikido/zen/rate_limiter/result.rb +31 -0
- data/lib/aikido/zen/rate_limiter.rb +50 -0
- data/lib/aikido/zen/request/heuristic_router.rb +115 -0
- data/lib/aikido/zen/request/rails_router.rb +92 -0
- data/lib/aikido/zen/request/schema/auth_discovery.rb +86 -0
- data/lib/aikido/zen/request/schema/auth_schemas.rb +54 -0
- data/lib/aikido/zen/request/schema/builder.rb +121 -0
- data/lib/aikido/zen/request/schema/definition.rb +107 -0
- data/lib/aikido/zen/request/schema/empty_schema.rb +28 -0
- data/lib/aikido/zen/request/schema.rb +87 -0
- data/lib/aikido/zen/request.rb +88 -0
- data/lib/aikido/zen/route.rb +96 -0
- data/lib/aikido/zen/runtime_settings/endpoints.rb +78 -0
- data/lib/aikido/zen/runtime_settings/ip_set.rb +36 -0
- data/lib/aikido/zen/runtime_settings/protection_settings.rb +62 -0
- data/lib/aikido/zen/runtime_settings/rate_limit_settings.rb +47 -0
- data/lib/aikido/zen/runtime_settings.rb +66 -0
- data/lib/aikido/zen/scan.rb +75 -0
- data/lib/aikido/zen/scanners/path_traversal/helpers.rb +68 -0
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +64 -0
- data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +65 -0
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +94 -0
- data/lib/aikido/zen/scanners/ssrf/dns_lookups.rb +27 -0
- data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +97 -0
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +266 -0
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +55 -0
- data/lib/aikido/zen/scanners.rb +7 -0
- data/lib/aikido/zen/sink.rb +118 -0
- data/lib/aikido/zen/sinks/action_controller.rb +85 -0
- data/lib/aikido/zen/sinks/async_http.rb +80 -0
- data/lib/aikido/zen/sinks/curb.rb +113 -0
- data/lib/aikido/zen/sinks/em_http.rb +83 -0
- data/lib/aikido/zen/sinks/excon.rb +118 -0
- data/lib/aikido/zen/sinks/file.rb +153 -0
- data/lib/aikido/zen/sinks/http.rb +93 -0
- data/lib/aikido/zen/sinks/httpclient.rb +95 -0
- data/lib/aikido/zen/sinks/httpx.rb +78 -0
- data/lib/aikido/zen/sinks/kernel.rb +33 -0
- data/lib/aikido/zen/sinks/mysql2.rb +31 -0
- data/lib/aikido/zen/sinks/net_http.rb +101 -0
- data/lib/aikido/zen/sinks/patron.rb +103 -0
- data/lib/aikido/zen/sinks/pg.rb +72 -0
- data/lib/aikido/zen/sinks/resolv.rb +62 -0
- data/lib/aikido/zen/sinks/socket.rb +85 -0
- data/lib/aikido/zen/sinks/sqlite3.rb +46 -0
- data/lib/aikido/zen/sinks/trilogy.rb +31 -0
- data/lib/aikido/zen/sinks/typhoeus.rb +78 -0
- data/lib/aikido/zen/sinks.rb +36 -0
- data/lib/aikido/zen/sinks_dsl.rb +250 -0
- data/lib/aikido/zen/synchronizable.rb +24 -0
- data/lib/aikido/zen/system_info.rb +80 -0
- data/lib/aikido/zen/version.rb +10 -0
- data/lib/aikido/zen/worker.rb +87 -0
- data/lib/aikido/zen.rb +303 -0
- data/lib/aikido-zen.rb +3 -0
- data/placeholder/.gitignore +4 -0
- data/placeholder/README.md +11 -0
- data/placeholder/Rakefile +75 -0
- data/placeholder/lib/placeholder.rb.template +3 -0
- data/placeholder/placeholder.gemspec.template +20 -0
- data/tasklib/bench.rake +94 -0
- data/tasklib/libzen.rake +133 -0
- data/tasklib/wrk.rb +88 -0
- metadata +214 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../capped_collections"
|
|
4
|
+
|
|
5
|
+
module Aikido::Zen
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# Tracks data specific to a single Sink.
|
|
9
|
+
class Collector::SinkStats
|
|
10
|
+
# @return [Integer] number of total calls to Sink#scan.
|
|
11
|
+
attr_accessor :scans
|
|
12
|
+
|
|
13
|
+
# @return [Integer] number of scans where our scanners raised an
|
|
14
|
+
# error that was handled.
|
|
15
|
+
attr_accessor :errors
|
|
16
|
+
|
|
17
|
+
# @return [Integer] number of scans where an attack was detected.
|
|
18
|
+
attr_accessor :attacks
|
|
19
|
+
|
|
20
|
+
# @return [Integer] number of scans where an attack was detected
|
|
21
|
+
# _and_ blocked by the Zen.
|
|
22
|
+
attr_accessor :blocked_attacks
|
|
23
|
+
|
|
24
|
+
# @return [Set<Float>] keeps the duration of individual scans. If
|
|
25
|
+
# this grows to match Config#max_performance_samples, the set is
|
|
26
|
+
# cleared and the data is aggregated into #compressed_timings.
|
|
27
|
+
attr_accessor :timings
|
|
28
|
+
|
|
29
|
+
# @return [Array<CompressedTiming>] list of aggregated stats.
|
|
30
|
+
attr_accessor :compressed_timings
|
|
31
|
+
|
|
32
|
+
def initialize(name, config)
|
|
33
|
+
@name = name
|
|
34
|
+
@config = config
|
|
35
|
+
|
|
36
|
+
@scans = 0
|
|
37
|
+
@errors = 0
|
|
38
|
+
|
|
39
|
+
@attacks = 0
|
|
40
|
+
@blocked_attacks = 0
|
|
41
|
+
|
|
42
|
+
@timings = Set.new
|
|
43
|
+
@compressed_timings = CappedSet.new(@config.max_compressed_stats)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def add_timing(duration)
|
|
47
|
+
compress_timings if @timings.size >= @config.max_performance_samples
|
|
48
|
+
@timings << duration
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def compress_timings(at: Time.now.utc)
|
|
52
|
+
return if @timings.empty?
|
|
53
|
+
|
|
54
|
+
list = @timings.sort
|
|
55
|
+
@timings.clear
|
|
56
|
+
|
|
57
|
+
mean = list.sum / list.size
|
|
58
|
+
percentiles = percentiles(list, 50, 75, 90, 95, 99)
|
|
59
|
+
|
|
60
|
+
@compressed_timings << CompressedTiming.new(mean, percentiles, at)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def as_json
|
|
64
|
+
{
|
|
65
|
+
total: @scans,
|
|
66
|
+
interceptorThrewError: @errors,
|
|
67
|
+
withoutContext: 0,
|
|
68
|
+
attacksDetected: {
|
|
69
|
+
total: @attacks,
|
|
70
|
+
blocked: @blocked_attacks
|
|
71
|
+
},
|
|
72
|
+
compressedTimings: @compressed_timings.as_json
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private def percentiles(sorted, *scores)
|
|
77
|
+
return {} if sorted.empty? || scores.empty?
|
|
78
|
+
|
|
79
|
+
scores.map { |p|
|
|
80
|
+
index = (sorted.size * (p / 100.0)).floor
|
|
81
|
+
[p, sorted.at(index)]
|
|
82
|
+
}.to_h
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
CompressedTiming = Struct.new(:mean, :percentiles, :compressed_at) do
|
|
86
|
+
def as_json
|
|
87
|
+
{
|
|
88
|
+
averageInMs: mean * 1000,
|
|
89
|
+
percentiles: percentiles.transform_values { |t| t * 1000 },
|
|
90
|
+
compressedAt: compressed_at.to_i * 1000
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../capped_collections"
|
|
4
|
+
|
|
5
|
+
module Aikido::Zen
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# Tracks information about how the Aikido Agent is used in the app.
|
|
9
|
+
class Collector::Stats
|
|
10
|
+
# @!visibility private
|
|
11
|
+
attr_reader :started_at, :ended_at, :requests, :aborted_requests, :attack_waves, :blocked_attack_waves, :sinks
|
|
12
|
+
|
|
13
|
+
# @!visibility private
|
|
14
|
+
attr_writer :ended_at
|
|
15
|
+
|
|
16
|
+
def initialize(config = Aikido::Zen.config)
|
|
17
|
+
super()
|
|
18
|
+
@config = config
|
|
19
|
+
|
|
20
|
+
@started_at = @ended_at = nil
|
|
21
|
+
@requests = 0
|
|
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) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def empty?
|
|
30
|
+
@requests.zero? && @sinks.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def any?
|
|
35
|
+
!empty?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Track the timestamp we start tracking this series of stats.
|
|
39
|
+
#
|
|
40
|
+
# @param at [Time]
|
|
41
|
+
# @return [void]
|
|
42
|
+
def start(at = Time.now.utc)
|
|
43
|
+
@started_at = at
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Sets the end time for these stats block, freezes it to avoid any more
|
|
47
|
+
# writing to them, and compresses the timing stats in anticipation of
|
|
48
|
+
# sending these to the Aikido servers.
|
|
49
|
+
#
|
|
50
|
+
# @param at [Time] the time at which we're resetting, which is set as the
|
|
51
|
+
# ending time for the returned copy.
|
|
52
|
+
# @return [void]
|
|
53
|
+
def flush(at: Time.now.utc)
|
|
54
|
+
# Make sure the timing stats are compressed before copying, since we
|
|
55
|
+
# need these compressed when we serialize this for the API.
|
|
56
|
+
@sinks.each_value { |sink| sink.compress_timings(at: at) }
|
|
57
|
+
@ended_at = at
|
|
58
|
+
freeze
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @return [void]
|
|
62
|
+
def add_request
|
|
63
|
+
@requests += 1
|
|
64
|
+
end
|
|
65
|
+
|
|
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]
|
|
79
|
+
stats.scans += 1
|
|
80
|
+
stats.errors += 1 if has_errors
|
|
81
|
+
stats.add_timing(duration)
|
|
82
|
+
end
|
|
83
|
+
|
|
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]
|
|
89
|
+
stats.attacks += 1
|
|
90
|
+
stats.blocked_attacks += 1 if being_blocked
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def as_json
|
|
94
|
+
total_attacks, total_blocked = aggregate_attacks_from_sinks
|
|
95
|
+
{
|
|
96
|
+
startedAt: @started_at.to_i * 1000,
|
|
97
|
+
endedAt: (@ended_at.to_i * 1000 if @ended_at),
|
|
98
|
+
operations: @sinks.transform_values(&:as_json),
|
|
99
|
+
requests: {
|
|
100
|
+
total: @requests,
|
|
101
|
+
aborted: @aborted_requests,
|
|
102
|
+
attacksDetected: {
|
|
103
|
+
total: total_attacks,
|
|
104
|
+
blocked: total_blocked
|
|
105
|
+
},
|
|
106
|
+
attackWaves: {
|
|
107
|
+
total: @attack_waves,
|
|
108
|
+
blocked: @blocked_attack_waves
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private def aggregate_attacks_from_sinks
|
|
115
|
+
@sinks.each_value.reduce([0, 0]) { |(attacks, blocked), stats|
|
|
116
|
+
[attacks + stats.attacks, blocked + stats.blocked_attacks]
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
require_relative "sink_stats"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../capped_collections"
|
|
4
|
+
|
|
5
|
+
module Aikido::Zen
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# Keeps track of the users that were seen by the app.
|
|
9
|
+
class Collector::Users < Aikido::Zen::CappedMap
|
|
10
|
+
def initialize(config = Aikido::Zen.config)
|
|
11
|
+
super(config.max_users_tracked)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @param actor [Aikido::Zen::Actor]
|
|
15
|
+
# @return [void]
|
|
16
|
+
def add(actor)
|
|
17
|
+
if key?(actor.id)
|
|
18
|
+
self[actor.id] |= actor
|
|
19
|
+
else
|
|
20
|
+
self[actor.id] = actor
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def each(&blk)
|
|
25
|
+
each_value(&blk)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def as_json
|
|
29
|
+
map(&:as_json)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "collector/event"
|
|
4
|
+
|
|
5
|
+
module Aikido::Zen
|
|
6
|
+
# Handles collecting all the runtime statistics to report back to the Aikido
|
|
7
|
+
# servers.
|
|
8
|
+
class Collector
|
|
9
|
+
def initialize(config: Aikido::Zen.config)
|
|
10
|
+
@config = config
|
|
11
|
+
|
|
12
|
+
@events = Queue.new
|
|
13
|
+
|
|
14
|
+
@stats = Concurrent::AtomicReference.new(Stats.new(@config))
|
|
15
|
+
@users = Concurrent::AtomicReference.new(Users.new(@config))
|
|
16
|
+
@hosts = Concurrent::AtomicReference.new(Hosts.new(@config))
|
|
17
|
+
@routes = Concurrent::AtomicReference.new(Routes.new(@config))
|
|
18
|
+
|
|
19
|
+
@middleware_installed = Concurrent::AtomicBoolean.new
|
|
20
|
+
end
|
|
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
|
+
|
|
50
|
+
# Flush all the stats into a Heartbeat event that can be reported back to
|
|
51
|
+
# the Aikido servers.
|
|
52
|
+
#
|
|
53
|
+
# @param at [Time] the time at which stats collection stopped and the start
|
|
54
|
+
# of the new stats collection period. Defaults to now.
|
|
55
|
+
# @return [Aikido::Zen::Events::Heartbeat]
|
|
56
|
+
def flush(at: Time.now.utc)
|
|
57
|
+
handle
|
|
58
|
+
|
|
59
|
+
stats = @stats.get_and_set(Stats.new(@config))
|
|
60
|
+
users = @users.get_and_set(Users.new(@config))
|
|
61
|
+
hosts = @hosts.get_and_set(Hosts.new(@config))
|
|
62
|
+
routes = @routes.get_and_set(Routes.new(@config))
|
|
63
|
+
|
|
64
|
+
start(at: at)
|
|
65
|
+
stats = stats.flush(at: at)
|
|
66
|
+
|
|
67
|
+
Aikido::Zen::Events::Heartbeat.new(
|
|
68
|
+
stats: stats, users: users, hosts: hosts, routes: routes, middleware_installed: middleware_installed?
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Sets the start time for this collection period.
|
|
73
|
+
#
|
|
74
|
+
# @param at [Time] defaults to now.
|
|
75
|
+
# @return [void]
|
|
76
|
+
def start(at: Time.now.utc)
|
|
77
|
+
synchronize(@stats) { |stats| stats.start(at) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Track stats about the requests
|
|
81
|
+
#
|
|
82
|
+
# @return [void]
|
|
83
|
+
def track_request
|
|
84
|
+
add_event(Events::TrackRequest.new)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def handle_track_request
|
|
88
|
+
synchronize(@stats) { |stats| stats.add_request }
|
|
89
|
+
end
|
|
90
|
+
|
|
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
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Track stats about a scan performed by one of our sinks.
|
|
106
|
+
#
|
|
107
|
+
# @param scan [Aikido::Zen::Scan]
|
|
108
|
+
# @return [void]
|
|
109
|
+
def track_scan(scan)
|
|
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
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Track stats about an attack detected by our scanners.
|
|
120
|
+
#
|
|
121
|
+
# @param attack [Aikido::Zen::Attack]
|
|
122
|
+
# @return [void]
|
|
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:)
|
|
128
|
+
synchronize(@stats) do |stats|
|
|
129
|
+
stats.add_attack(sink_name, being_blocked: being_blocked)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
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
|
+
|
|
145
|
+
# Track an HTTP connections to an external host.
|
|
146
|
+
#
|
|
147
|
+
# @param connection [Aikido::Zen::OutboundConnection]
|
|
148
|
+
# @return [void]
|
|
149
|
+
def track_outbound(connection)
|
|
150
|
+
add_event(Events::TrackOutbound.new(connection))
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def handle_track_outbound(connection)
|
|
154
|
+
synchronize(@hosts) { |hosts| hosts.add(connection) }
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Record the visited endpoint, and if enabled, the API schema for this endpoint.
|
|
158
|
+
#
|
|
159
|
+
# @param request [Aikido::Zen::Request]
|
|
160
|
+
# @return [void]
|
|
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) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def middleware_installed!
|
|
170
|
+
@middleware_installed.make_true
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @api private
|
|
174
|
+
#
|
|
175
|
+
# @note Visible for testing.
|
|
176
|
+
def stats
|
|
177
|
+
handle
|
|
178
|
+
@stats.get
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# @api private
|
|
182
|
+
#
|
|
183
|
+
# @note Visible for testing.
|
|
184
|
+
def users
|
|
185
|
+
handle
|
|
186
|
+
@users.get
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @api private
|
|
190
|
+
#
|
|
191
|
+
# @note Visible for testing.
|
|
192
|
+
def hosts
|
|
193
|
+
handle
|
|
194
|
+
@hosts.get
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# @api private
|
|
198
|
+
#
|
|
199
|
+
# @note Visible for testing.
|
|
200
|
+
def routes
|
|
201
|
+
handle
|
|
202
|
+
@routes.get
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# @api private
|
|
206
|
+
#
|
|
207
|
+
# @note Visible for testing.
|
|
208
|
+
def middleware_installed?
|
|
209
|
+
@middleware_installed.true?
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Atomically modify an object's state within a block, ensuring it's safe
|
|
213
|
+
# from other threads.
|
|
214
|
+
private def synchronize(object)
|
|
215
|
+
object.update { |obj| obj.tap { yield obj } }
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
require_relative "collector/stats"
|
|
221
|
+
require_relative "collector/users"
|
|
222
|
+
require_relative "collector/hosts"
|
|
223
|
+
require_relative "collector/routes"
|