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,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cache"
|
|
4
|
+
require_relative "attack_wave/helpers"
|
|
5
|
+
|
|
6
|
+
module Aikido::Zen
|
|
7
|
+
module AttackWave
|
|
8
|
+
class Detector
|
|
9
|
+
def initialize(config: Aikido::Zen.config, clock: nil)
|
|
10
|
+
@config = config
|
|
11
|
+
|
|
12
|
+
@event_times = Cache.new(@config.attack_wave_max_cache_entries, ttl: @config.attack_wave_min_time_between_events, clock: clock)
|
|
13
|
+
|
|
14
|
+
@request_counts = Cache.new(@config.attack_wave_max_cache_entries, 0, ttl: @config.attack_wave_min_time_between_requests, clock: clock)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def attack_wave?(context)
|
|
18
|
+
client_ip = context.request.client_ip
|
|
19
|
+
|
|
20
|
+
return false unless client_ip
|
|
21
|
+
|
|
22
|
+
return false if @event_times[client_ip]
|
|
23
|
+
|
|
24
|
+
return false unless AttackWave::Helpers.web_scanner?(context)
|
|
25
|
+
|
|
26
|
+
request_count = @request_counts[client_ip] += 1
|
|
27
|
+
|
|
28
|
+
return false if request_count < @config.attack_wave_threshold
|
|
29
|
+
|
|
30
|
+
@event_times[client_ip] = Time.now.utc
|
|
31
|
+
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Request
|
|
37
|
+
# @return [String]
|
|
38
|
+
attr_reader :ip_address
|
|
39
|
+
|
|
40
|
+
# @return [String]
|
|
41
|
+
attr_reader :user_agent
|
|
42
|
+
|
|
43
|
+
# @return [String]
|
|
44
|
+
attr_reader :source
|
|
45
|
+
|
|
46
|
+
# @param ip_address [String]
|
|
47
|
+
# @param user_agent [String]
|
|
48
|
+
# @param source [String]
|
|
49
|
+
# @return [Aikido::Zen::AttackWave::Request]
|
|
50
|
+
def initialize(ip_address:, user_agent:, source:)
|
|
51
|
+
@ip_address = ip_address
|
|
52
|
+
@user_agent = user_agent
|
|
53
|
+
@source = source
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def as_json
|
|
57
|
+
{
|
|
58
|
+
ipAddress: @ip_address,
|
|
59
|
+
userAgent: @user_agent,
|
|
60
|
+
source: @source
|
|
61
|
+
}.compact
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class Attack
|
|
66
|
+
# @return [Hash<String, String>]
|
|
67
|
+
attr_reader :metadata
|
|
68
|
+
|
|
69
|
+
# @return [Aikido::Zen::Actor]
|
|
70
|
+
attr_reader :user
|
|
71
|
+
|
|
72
|
+
# @param metadata [Hash<String, String>]
|
|
73
|
+
# @param metadata [Aikido::Zen::Actor]
|
|
74
|
+
# @return [Aikido::Zen::AttackWave::Attack]
|
|
75
|
+
def initialize(metadata:, user:)
|
|
76
|
+
@metadata = metadata
|
|
77
|
+
@user = user
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def as_json
|
|
81
|
+
{
|
|
82
|
+
metadata: @metadata.as_json,
|
|
83
|
+
user: @user.as_json
|
|
84
|
+
}.compact
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Aikido::Zen
|
|
2
|
+
# Generic background worker class backed by queue. Meant to be used by any
|
|
3
|
+
# background process that needs to do heavy tasks.
|
|
4
|
+
class BackgroundWorker
|
|
5
|
+
# @param block [block] A block that receives 1 message directly from the queue
|
|
6
|
+
def initialize(&block)
|
|
7
|
+
@queue = Queue.new
|
|
8
|
+
@block = block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# starts the background thread, blocking the thread until a new messages arrives
|
|
12
|
+
# or the queue is stopped.
|
|
13
|
+
def start
|
|
14
|
+
@thread = Thread.new do
|
|
15
|
+
while running? || actions?
|
|
16
|
+
action = wait_for_action
|
|
17
|
+
@block.call(action) unless action.nil?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def restart
|
|
23
|
+
stop
|
|
24
|
+
@queue = Queue.new # re-open the queue
|
|
25
|
+
start
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Drain the queue to do not lose any messages
|
|
29
|
+
def stop
|
|
30
|
+
@queue.close # stop accepting messages
|
|
31
|
+
@thread.join # wait for the queue to be drained
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def enqueue(scan)
|
|
35
|
+
@queue.push(scan)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def actions?
|
|
41
|
+
!@queue.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def running?
|
|
45
|
+
!@queue.closed?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def wait_for_action
|
|
49
|
+
@queue.pop(false)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aikido::Zen
|
|
4
|
+
class Cache
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
# @api private
|
|
8
|
+
# Visible for testing.
|
|
9
|
+
def_delegators :@data,
|
|
10
|
+
:size, :empty?
|
|
11
|
+
|
|
12
|
+
def initialize(capacity, default_value = nil, ttl:, clock: nil)
|
|
13
|
+
@default_value = default_value
|
|
14
|
+
@ttl = ttl
|
|
15
|
+
@clock = clock
|
|
16
|
+
|
|
17
|
+
@data = CappedMap.new(capacity, mode: :lru)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def key?(key)
|
|
21
|
+
@data.key?(key) && !@data[key].expired?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @param key [Object] the key
|
|
25
|
+
# @param value [Object] the value
|
|
26
|
+
# @return [Object] the value that the key was set to
|
|
27
|
+
def []=(key, value)
|
|
28
|
+
if key?(key)
|
|
29
|
+
entry = @data[key]
|
|
30
|
+
entry.refresh
|
|
31
|
+
entry.value = value
|
|
32
|
+
else
|
|
33
|
+
@data[key] = CacheEntry.new(value, ttl: @ttl, clock: @clock)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def [](key)
|
|
38
|
+
if key?(key)
|
|
39
|
+
@data[key].value
|
|
40
|
+
else
|
|
41
|
+
@default_value
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def delete(key)
|
|
46
|
+
if key?(key)
|
|
47
|
+
@data.delete(key).value
|
|
48
|
+
else
|
|
49
|
+
@data.delete(key)
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @api private
|
|
55
|
+
# Visible for testing.
|
|
56
|
+
def to_a
|
|
57
|
+
@data.map { |key, entry| [key, entry.value] }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @api private
|
|
61
|
+
# Visible for testing.
|
|
62
|
+
def to_h
|
|
63
|
+
to_a.to_h
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class CacheEntry
|
|
68
|
+
attr_accessor :value
|
|
69
|
+
|
|
70
|
+
DEFAULT_CLOCK = -> { Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) }
|
|
71
|
+
|
|
72
|
+
# @param value [Object] the value
|
|
73
|
+
# @param ttl [Integer] the time-to-live in milliseconds
|
|
74
|
+
# @return [Aikido::Zen::CacheEntry]
|
|
75
|
+
def initialize(value, ttl:, clock: nil)
|
|
76
|
+
@value = value
|
|
77
|
+
@ttl = ttl
|
|
78
|
+
@clock = clock || DEFAULT_CLOCK
|
|
79
|
+
|
|
80
|
+
refresh
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def refresh
|
|
84
|
+
@expires = @clock.call + @ttl
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def expired?
|
|
88
|
+
@clock.call >= @expires
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module Aikido::Zen
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# Provides a FIFO set with a maximum size. Adding an element after the
|
|
9
|
+
# capacity has been reached kicks the oldest element in the set out,
|
|
10
|
+
# while maintaining the uniqueness property of a set (relying on #eql?
|
|
11
|
+
# and #hash).
|
|
12
|
+
class CappedSet
|
|
13
|
+
include Enumerable
|
|
14
|
+
extend Forwardable
|
|
15
|
+
|
|
16
|
+
def_delegators :@data, :size, :empty?
|
|
17
|
+
|
|
18
|
+
# @return [Integer]
|
|
19
|
+
attr_reader :capacity
|
|
20
|
+
|
|
21
|
+
def initialize(capacity, mode: :fifo)
|
|
22
|
+
@data = CappedMap.new(capacity, mode: mode)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def <<(element)
|
|
26
|
+
@data[element] = nil
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
alias_method :add, :<<
|
|
30
|
+
alias_method :push, :<<
|
|
31
|
+
|
|
32
|
+
def each(&b)
|
|
33
|
+
@data.each_key(&b)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def as_json
|
|
37
|
+
map(&:as_json)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @api private
|
|
42
|
+
#
|
|
43
|
+
# Provides a FIFO hash-like structure with a maximum size. Adding a new key
|
|
44
|
+
# after the capacity has been reached kicks the first element pair added out.
|
|
45
|
+
class CappedMap
|
|
46
|
+
include Enumerable
|
|
47
|
+
extend Forwardable
|
|
48
|
+
|
|
49
|
+
def_delegators :@data,
|
|
50
|
+
:delete, :key?,
|
|
51
|
+
:each, :each_key, :each_value,
|
|
52
|
+
:size, :empty?, :to_hash
|
|
53
|
+
|
|
54
|
+
# @return [Integer]
|
|
55
|
+
attr_reader :capacity
|
|
56
|
+
|
|
57
|
+
def initialize(capacity, mode: :fifo)
|
|
58
|
+
raise ArgumentError, "cannot set capacity lower than 1: #{capacity}" if capacity < 1
|
|
59
|
+
|
|
60
|
+
unless [:fifo, :lru].include?(mode)
|
|
61
|
+
raise ArgumentError, "unsupported mode: #{mode}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@capacity = capacity
|
|
65
|
+
@mode = mode
|
|
66
|
+
|
|
67
|
+
@data = {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def []=(key, value)
|
|
71
|
+
@data[key] = value
|
|
72
|
+
@data.delete(@data.each_key.first) if @data.size > @capacity
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def [](key)
|
|
76
|
+
@data[key] = @data.delete(key) if @mode == :lru && key?(key)
|
|
77
|
+
@data[key]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def fetch(key, ...)
|
|
81
|
+
return self[key] if key?(key)
|
|
82
|
+
|
|
83
|
+
@data.fetch(key, ...)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,30 @@
|
|
|
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 hostnames to which the app has made outbound HTTP
|
|
9
|
+
# requests.
|
|
10
|
+
class Collector::Hosts < Aikido::Zen::CappedMap
|
|
11
|
+
def initialize(config = Aikido::Zen.config)
|
|
12
|
+
super(config.max_outbound_connections)
|
|
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
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../request/schema/empty_schema"
|
|
4
|
+
|
|
5
|
+
module Aikido::Zen
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# Keeps track of the visited routes.
|
|
9
|
+
class Collector::Routes
|
|
10
|
+
# @api private
|
|
11
|
+
# Visible for testing.
|
|
12
|
+
attr_reader :visits
|
|
13
|
+
|
|
14
|
+
def initialize(config = Aikido::Zen.config)
|
|
15
|
+
@config = config
|
|
16
|
+
@visits = Hash.new { |h, k| h[k] = Record.new }
|
|
17
|
+
end
|
|
18
|
+
|
|
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?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def as_json
|
|
27
|
+
@visits.map do |route, record|
|
|
28
|
+
{
|
|
29
|
+
method: route.verb,
|
|
30
|
+
path: route.path,
|
|
31
|
+
hits: record.hits,
|
|
32
|
+
apispec: record.schema.as_json
|
|
33
|
+
}.compact
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @api private
|
|
38
|
+
# Visible for testing.
|
|
39
|
+
def [](route)
|
|
40
|
+
@visits[route]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @api private
|
|
44
|
+
def empty?
|
|
45
|
+
@visits.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @api private
|
|
49
|
+
Record = Struct.new(:hits, :schema, :samples) do
|
|
50
|
+
def initialize(config = Aikido::Zen.config)
|
|
51
|
+
super(0, Aikido::Zen::Request::Schema::EMPTY_SCHEMA, 0)
|
|
52
|
+
@config = config
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def increment(schema)
|
|
56
|
+
self.hits += 1
|
|
57
|
+
|
|
58
|
+
if sample_schema?
|
|
59
|
+
self.samples += 1
|
|
60
|
+
self.schema |= schema
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def sample_schema?
|
|
67
|
+
samples < @config.api_schema_max_samples
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|