aikido-zen 1.0.2.beta.5-x86_64-linux-musl → 1.0.2.beta.7-x86_64-linux-musl
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 +1 -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/rails.md +5 -7
- data/lib/aikido/zen/actor.rb +34 -4
- data/lib/aikido/zen/agent/heartbeats_manager.rb +5 -5
- data/lib/aikido/zen/agent.rb +17 -15
- data/lib/aikido/zen/collector/event.rb +209 -0
- data/lib/aikido/zen/collector/routes.rb +13 -8
- data/lib/aikido/zen/collector/stats.rb +16 -19
- data/lib/aikido/zen/collector/users.rb +3 -1
- data/lib/aikido/zen/collector.rb +94 -29
- data/lib/aikido/zen/config.rb +4 -10
- data/lib/aikido/zen/context.rb +8 -7
- 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/internals.rb +23 -3
- data/lib/aikido/zen/libzen-v0.1.48-x86_64-linux-musl.so +0 -0
- data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
- data/lib/aikido/zen/middleware/request_tracker.rb +1 -1
- data/lib/aikido/zen/outbound_connection.rb +7 -0
- data/lib/aikido/zen/rails_engine.rb +2 -6
- data/lib/aikido/zen/route.rb +7 -0
- data/lib/aikido/zen/runtime_settings.rb +1 -1
- data/lib/aikido/zen/scanners/path_traversal/helpers.rb +10 -7
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +2 -2
- data/lib/aikido/zen/sink.rb +1 -1
- data/lib/aikido/zen/sinks/file.rb +43 -4
- data/lib/aikido/zen/sinks/kernel.rb +1 -1
- data/lib/aikido/zen/version.rb +2 -2
- data/lib/aikido/zen.rb +8 -9
- metadata +6 -3
- data/lib/aikido/zen/libzen-v0.1.39-x86_64-linux-musl.so +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '08a5166218ef33bff1e3bd5d2210a7d0d486f313392d1f0cd1267d1e532d7e3b'
|
|
4
|
+
data.tar.gz: ecdd09fd9e950159f384fbdca19a4a195834a9c678cc8971b33d985cc6177d02
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1d56405c7e298ed9eb09792fd30dbd76713359ea58d8a1105f3685a08c7f512ac46dc1f20c6af4c0730e256c6528f53ef32f69bff2ec3b868c457e31a9e7eaf
|
|
7
|
+
data.tar.gz: 6fbd473f19fc40c13ce0bf12528256e20e80ed20828035bd4f18f6a0d7553ecd7459a23e8b50d6faf81e01698e11bb3b637a24279b81809fc7e1d569756aac4c
|
data/.simplecov
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
return if RUBY_VERSION < "3.0"
|
|
7
7
|
return if ENV["DISABLE_COVERAGE"] == "true"
|
|
8
8
|
|
|
9
|
+
# Output coverage as LCOV to support CodeCov
|
|
10
|
+
if ENV["COVERAGE_OUTPUT_LCOV"] == "true"
|
|
11
|
+
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
|
|
12
|
+
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
|
13
|
+
end
|
|
14
|
+
|
|
9
15
|
SimpleCov.start do
|
|
10
16
|
# Make sure SimpleCov waits until after the tests
|
|
11
17
|
# are finished to generate the coverage reports.
|
data/README.md
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
[](http://makeapullrequest.com)
|
|
7
7
|
[](https://github.com/AikidoSec/firewall-ruby/actions/workflows/main.yml)
|
|
8
8
|
[](https://github.com/AikidoSec/firewall-ruby/actions/workflows/release.yml)
|
|
9
|
+
[](https://codecov.io/gh/AikidoSec/firewall-ruby)
|
|
9
10
|
|
|
10
11
|
Zen, your in-app firewall for peace of mind - at runtime.
|
|
11
12
|
|
data/benchmarks/README.md
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rails7.1_sql_injection.js
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import http from
|
|
2
|
-
import {Trend} from
|
|
1
|
+
import http from "k6/http";
|
|
2
|
+
import {Trend} from "k6/metrics";
|
|
3
3
|
|
|
4
4
|
const HTTP = {
|
|
5
5
|
withZen: {
|
|
@@ -10,7 +10,7 @@ const HTTP = {
|
|
|
10
10
|
get: (path, ...args) => http.get("http://localhost:3002" + path, ...args),
|
|
11
11
|
post: (path, ...args) => http.post("http://localhost:3002" + path, ...args)
|
|
12
12
|
}
|
|
13
|
-
}
|
|
13
|
+
};
|
|
14
14
|
|
|
15
15
|
function test(name, fn) {
|
|
16
16
|
const withZen = fn(HTTP.withZen);
|
|
@@ -36,35 +36,67 @@ function buildTestTrends(prefix) {
|
|
|
36
36
|
|
|
37
37
|
const tests = {
|
|
38
38
|
test_post_page_with_json_body: buildTestTrends("test_post_page_with_json_body"),
|
|
39
|
-
test_get_page_without_attack: buildTestTrends("test_get_page_without_attack")
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
test_get_page_without_attack: buildTestTrends("test_get_page_without_attack")
|
|
40
|
+
};
|
|
41
|
+
|
|
42
42
|
export const options = {
|
|
43
43
|
vus: 1, // Number of virtual users
|
|
44
|
-
|
|
44
|
+
duration: "60s",
|
|
45
45
|
thresholds: {
|
|
46
|
-
http_req_failed: [
|
|
46
|
+
http_req_failed: ["rate==0"], // We are marking the attacks as expected, so we should have no errors.
|
|
47
47
|
test_post_page_with_json_body_delta: ["med<10"],
|
|
48
|
-
test_get_page_without_attack_delta: ["med<10"]
|
|
49
|
-
test_get_page_with_sql_injection_delta: ["med<10"],
|
|
48
|
+
test_get_page_without_attack_delta: ["med<10"]
|
|
50
49
|
}
|
|
51
50
|
};
|
|
52
51
|
|
|
52
|
+
const headers = {
|
|
53
|
+
Authorization:
|
|
54
|
+
"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.Bw8sSk3kdnT9d803kqqE_LZJzY1PzMl5cbmuanQKxrI",
|
|
55
|
+
Accept:
|
|
56
|
+
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
|
57
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
58
|
+
Dnt: "1",
|
|
59
|
+
Priority: "u=0, i",
|
|
60
|
+
"Sec-Ch-Ua":
|
|
61
|
+
'"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',
|
|
62
|
+
"Sec-Ch-Ua-Arch": '"arm"',
|
|
63
|
+
"Sec-Ch-Ua-Bitness": '"64"',
|
|
64
|
+
"Sec-Ch-Ua-Full-Version-List":
|
|
65
|
+
'"Not)A;Brand";v="99.0.0.0", "Google Chrome";v="127.0.6533.72", "Chromium";v="127.0.6533.72"',
|
|
66
|
+
"Sec-Ch-Ua-Mobile": "?0",
|
|
67
|
+
"Sec-Ch-Ua-Model": '""',
|
|
68
|
+
"Sec-Ch-Ua-Platform": '"macOS"',
|
|
69
|
+
"Sec-Ch-Ua-Platform-Version": '"14.5.0"',
|
|
70
|
+
"Sec-Ch-Ua-Wow64": "?0",
|
|
71
|
+
"Sec-Fetch-Dest": "document",
|
|
72
|
+
"Sec-Fetch-Mode": "navigate",
|
|
73
|
+
"Sec-Fetch-Site": "cross-site",
|
|
74
|
+
"Sec-Fetch-User": "?1",
|
|
75
|
+
"Sec-Gpc": "1",
|
|
76
|
+
"Upgrade-Insecure-Requests": "1",
|
|
77
|
+
"User-Agent":
|
|
78
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
|
|
79
|
+
};
|
|
80
|
+
|
|
53
81
|
const expectAttack = http.expectedStatuses(200, 500);
|
|
54
82
|
|
|
55
83
|
export default function () {
|
|
56
84
|
test("test_post_page_with_json_body",
|
|
57
|
-
(http) => http.post("/cats",
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
85
|
+
(http) => http.post("/cats",
|
|
86
|
+
JSON.stringify({cat: {name: "Féline Dion"}}),
|
|
87
|
+
{
|
|
88
|
+
headers: {
|
|
89
|
+
...headers,
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
"Accept": "application/json"
|
|
92
|
+
}
|
|
93
|
+
})
|
|
63
94
|
)
|
|
64
95
|
|
|
65
|
-
test("test_get_page_without_attack",
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
test("test_get_page_without_attack",
|
|
97
|
+
(http) => http.get("/cats/count",
|
|
98
|
+
{
|
|
99
|
+
headers: headers
|
|
100
|
+
})
|
|
69
101
|
)
|
|
70
102
|
}
|
data/docs/rails.md
CHANGED
|
@@ -60,7 +60,7 @@ modify in an initializer if desired:
|
|
|
60
60
|
|
|
61
61
|
```ruby
|
|
62
62
|
# config/initializers/zen.rb
|
|
63
|
-
Rails.application.config.zen.
|
|
63
|
+
Rails.application.config.zen.option = value
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
You can access the configuration object both as `Aikido::Zen.config` or
|
|
@@ -80,7 +80,7 @@ zen:
|
|
|
80
80
|
token: "AIKIDO_RUNTIME_..."
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
You can
|
|
83
|
+
You can tell Zen to use it like so:
|
|
84
84
|
|
|
85
85
|
```ruby
|
|
86
86
|
# config/initializers/zen.rb
|
|
@@ -102,13 +102,11 @@ way.
|
|
|
102
102
|
|
|
103
103
|
## Logging
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
You can redirect the log to a separate stream by overriding the logger:
|
|
105
|
+
You can override the logger to integrate with your application logging strategy:
|
|
107
106
|
|
|
108
107
|
```ruby
|
|
109
108
|
# config/initializers/zen.rb
|
|
110
|
-
Rails.application.config.zen.logger =
|
|
109
|
+
Rails.application.config.zen.logger = ::Rails.logger
|
|
111
110
|
```
|
|
112
111
|
|
|
113
|
-
|
|
114
|
-
class.
|
|
112
|
+
Zen expects an instance of Ruby's [Logger](https://github.com/ruby/logger) class.
|
data/lib/aikido/zen/actor.rb
CHANGED
|
@@ -38,6 +38,16 @@ module Aikido::Zen
|
|
|
38
38
|
|
|
39
39
|
# Represents someone connecting to the application and making requests.
|
|
40
40
|
class Actor
|
|
41
|
+
def self.from_json(data)
|
|
42
|
+
new(
|
|
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
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
41
51
|
# @return [String] a unique identifier for this user.
|
|
42
52
|
attr_reader :id
|
|
43
53
|
|
|
@@ -50,18 +60,20 @@ module Aikido::Zen
|
|
|
50
60
|
# @param id [String]
|
|
51
61
|
# @param name [String, nil]
|
|
52
62
|
# @param ip [String, nil]
|
|
53
|
-
# @param
|
|
63
|
+
# @param first_seen_at [Time]
|
|
64
|
+
# @param last_seen_at [Time]
|
|
54
65
|
def initialize(
|
|
55
66
|
id:,
|
|
56
67
|
name: nil,
|
|
57
68
|
ip: Aikido::Zen.current_context&.request&.ip,
|
|
58
|
-
|
|
69
|
+
first_seen_at: Time.now.utc,
|
|
70
|
+
last_seen_at: first_seen_at
|
|
59
71
|
)
|
|
60
72
|
@id = id
|
|
61
73
|
@name = name
|
|
62
|
-
@first_seen_at = seen_at
|
|
63
|
-
@last_seen_at = Concurrent::AtomicReference.new(seen_at)
|
|
64
74
|
@ip = Concurrent::AtomicReference.new(ip)
|
|
75
|
+
@first_seen_at = first_seen_at
|
|
76
|
+
@last_seen_at = Concurrent::AtomicReference.new(last_seen_at)
|
|
65
77
|
end
|
|
66
78
|
|
|
67
79
|
# @return [Time]
|
|
@@ -89,6 +101,24 @@ module Aikido::Zen
|
|
|
89
101
|
@ip.try_update { |last_ip| [ip, last_ip].compact.first }
|
|
90
102
|
end
|
|
91
103
|
|
|
104
|
+
# Merges the actor with another actor.
|
|
105
|
+
#
|
|
106
|
+
# @param other [Aikido::Zen::Actor]
|
|
107
|
+
# @return [Aikido::Zen::Actor]
|
|
108
|
+
def merge(other)
|
|
109
|
+
older = (first_seen_at < other.first_seen_at) ? self : other
|
|
110
|
+
newer = (last_seen_at > other.last_seen_at) ? self : other
|
|
111
|
+
|
|
112
|
+
self.class.new(
|
|
113
|
+
id: @id,
|
|
114
|
+
name: newer.name,
|
|
115
|
+
ip: newer.ip,
|
|
116
|
+
first_seen_at: older.first_seen_at,
|
|
117
|
+
last_seen_at: newer.last_seen_at
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
alias_method :|, :merge
|
|
121
|
+
|
|
92
122
|
# @return [self]
|
|
93
123
|
def to_aikido_actor
|
|
94
124
|
self
|
|
@@ -20,7 +20,7 @@ module Aikido::Zen
|
|
|
20
20
|
# @return [Boolean] whether the currently running heartbeat matches the
|
|
21
21
|
# expected interval in the runtime settings.
|
|
22
22
|
def stale_settings?
|
|
23
|
-
running? && @timer.execution_interval !=
|
|
23
|
+
running? && @timer.execution_interval != interval
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Sets up the the timer to run the given block at the appropriate interval.
|
|
@@ -30,11 +30,11 @@ module Aikido::Zen
|
|
|
30
30
|
def start(&task)
|
|
31
31
|
return if running?
|
|
32
32
|
|
|
33
|
-
if
|
|
34
|
-
@config.logger.debug "Scheduling heartbeats every #{
|
|
35
|
-
@timer = @worker.every(
|
|
33
|
+
if interval&.nonzero?
|
|
34
|
+
@config.logger.debug "Scheduling heartbeats every #{interval} seconds"
|
|
35
|
+
@timer = @worker.every(interval, run_now: false, &task)
|
|
36
36
|
else
|
|
37
|
-
@config.logger.warn(format("Heartbeat could not be set up (interval: %p)",
|
|
37
|
+
@config.logger.warn(format("Heartbeat could not be set up (interval: %p)", interval))
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
data/lib/aikido/zen/agent.rb
CHANGED
|
@@ -37,22 +37,22 @@ module Aikido::Zen
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def start!
|
|
40
|
-
@config.logger.info
|
|
40
|
+
@config.logger.info("Starting Aikido agent v#{Aikido::Zen::VERSION}")
|
|
41
41
|
|
|
42
42
|
raise Aikido::ZenError, "Aikido Agent already started!" if started?
|
|
43
43
|
@started_at = Time.now.utc
|
|
44
44
|
@collector.start(at: @started_at)
|
|
45
45
|
|
|
46
46
|
if @config.blocking_mode?
|
|
47
|
-
@config.logger.info
|
|
47
|
+
@config.logger.info("Requests identified as attacks will be blocked")
|
|
48
48
|
else
|
|
49
|
-
@config.logger.warn
|
|
49
|
+
@config.logger.warn("Non-blocking mode enabled! No requests will be blocked.")
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
if @api_client.can_make_requests?
|
|
53
|
-
@config.logger.info
|
|
53
|
+
@config.logger.info("API Token set! Reporting has been enabled.")
|
|
54
54
|
else
|
|
55
|
-
@config.logger.warn
|
|
55
|
+
@config.logger.warn("No API Token set! Reporting has been disabled.")
|
|
56
56
|
return
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -60,15 +60,18 @@ module Aikido::Zen
|
|
|
60
60
|
|
|
61
61
|
report(Events::Started.new(time: @started_at)) do |response|
|
|
62
62
|
updated_settings! if Aikido::Zen.runtime_settings.update_from_json(response)
|
|
63
|
-
@config.logger.info
|
|
63
|
+
@config.logger.info("Updated runtime settings.")
|
|
64
64
|
rescue => err
|
|
65
65
|
@config.logger.error(err.message)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
poll_for_setting_updates
|
|
69
69
|
|
|
70
|
-
@
|
|
71
|
-
|
|
70
|
+
@config.initial_heartbeat_delays.each do |heartbeat_delay|
|
|
71
|
+
@worker.delay(heartbeat_delay) do
|
|
72
|
+
send_heartbeat
|
|
73
|
+
@config.logger.info("Executed initial heartbeat after #{heartbeat_delay} seconds")
|
|
74
|
+
end
|
|
72
75
|
end
|
|
73
76
|
end
|
|
74
77
|
|
|
@@ -77,7 +80,7 @@ module Aikido::Zen
|
|
|
77
80
|
#
|
|
78
81
|
# @return [void]
|
|
79
82
|
def stop!
|
|
80
|
-
@config.logger.info
|
|
83
|
+
@config.logger.info("Stopping Aikido agent")
|
|
81
84
|
@started_at = nil
|
|
82
85
|
@worker.shutdown
|
|
83
86
|
end
|
|
@@ -143,11 +146,10 @@ module Aikido::Zen
|
|
|
143
146
|
def send_heartbeat(at: Time.now.utc)
|
|
144
147
|
return unless @api_client.can_make_requests?
|
|
145
148
|
|
|
146
|
-
@collector.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
end
|
|
149
|
+
heartbeat = @collector.flush
|
|
150
|
+
report(heartbeat) do |response|
|
|
151
|
+
updated_settings! if Aikido::Zen.runtime_settings.update_from_json(response)
|
|
152
|
+
@config.logger.info("Updated runtime settings after heartbeat")
|
|
151
153
|
end
|
|
152
154
|
end
|
|
153
155
|
|
|
@@ -162,7 +164,7 @@ module Aikido::Zen
|
|
|
162
164
|
@worker.every(@config.polling_interval) do
|
|
163
165
|
if @api_client.should_fetch_settings?
|
|
164
166
|
updated_settings! if Aikido::Zen.runtime_settings.update_from_json(@api_client.fetch_settings)
|
|
165
|
-
@config.logger.info
|
|
167
|
+
@config.logger.info("Updated runtime settings after polling")
|
|
166
168
|
end
|
|
167
169
|
end
|
|
168
170
|
end
|
|
@@ -0,0 +1,209 @@
|
|
|
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 TrackScan < Event
|
|
56
|
+
register "track_scan"
|
|
57
|
+
|
|
58
|
+
def self.from_json(data)
|
|
59
|
+
new(
|
|
60
|
+
data[:sink_name],
|
|
61
|
+
data[:duration],
|
|
62
|
+
has_errors: data[:has_errors]
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def initialize(sink_name, duration, has_errors:)
|
|
67
|
+
super()
|
|
68
|
+
@sink_name = sink_name
|
|
69
|
+
@duration = duration
|
|
70
|
+
@has_errors = has_errors
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def as_json
|
|
74
|
+
super.update({
|
|
75
|
+
sink_name: @sink_name,
|
|
76
|
+
duration: @duration,
|
|
77
|
+
has_errors: @has_errors
|
|
78
|
+
})
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def handle(collector)
|
|
82
|
+
collector.handle_track_scan(@sink_name, @duration, has_errors: @has_errors)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def inspect
|
|
86
|
+
"#<#{self.class.name} #{@sink_name} #{format "%0.6f", @duration} #{@has_errors}>"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
class TrackAttack < Event
|
|
91
|
+
register "track_attack"
|
|
92
|
+
|
|
93
|
+
def self.from_json(data)
|
|
94
|
+
new(
|
|
95
|
+
data[:sink_name],
|
|
96
|
+
being_blocked: data[:being_blocked]
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def initialize(sink_name, being_blocked:)
|
|
101
|
+
super()
|
|
102
|
+
@sink_name = sink_name
|
|
103
|
+
@being_blocked = being_blocked
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def as_json
|
|
107
|
+
super.update({
|
|
108
|
+
sink_name: @sink_name,
|
|
109
|
+
being_blocked: @being_blocked
|
|
110
|
+
})
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def handle(collector)
|
|
114
|
+
collector.handle_track_attack(@sink_name, being_blocked: @being_blocked)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def inspect
|
|
118
|
+
"#<#{self.class.name} #{@sink_name} #{@being_blocked}>"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class TrackUser < Event
|
|
123
|
+
register "track_user"
|
|
124
|
+
|
|
125
|
+
def self.from_json(data)
|
|
126
|
+
new(Aikido::Zen::Actor.from_json(data[:actor]))
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def initialize(actor)
|
|
130
|
+
super()
|
|
131
|
+
@actor = actor
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def as_json
|
|
135
|
+
super.update({
|
|
136
|
+
actor: @actor.as_json
|
|
137
|
+
})
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def handle(collector)
|
|
141
|
+
collector.handle_track_user(@actor)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def inspect
|
|
145
|
+
"#<#{self.class.name} #{@actor.id} #{@actor.name}>"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class TrackOutbound < Event
|
|
150
|
+
register "track_outbound"
|
|
151
|
+
|
|
152
|
+
def self.from_json(data)
|
|
153
|
+
new(OutboundConnection.from_json(data[:connection]))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def initialize(connection)
|
|
157
|
+
super()
|
|
158
|
+
@connection = connection
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def as_json
|
|
162
|
+
super.update({
|
|
163
|
+
connection: @connection.as_json
|
|
164
|
+
})
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def handle(collector)
|
|
168
|
+
collector.handle_track_outbound(@connection)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def inspect
|
|
172
|
+
"#<#{self.class.name} #{@connection.host}:#{@connection.port}>"
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
class TrackRoute < Event
|
|
177
|
+
register "track_route"
|
|
178
|
+
|
|
179
|
+
def self.from_json(data)
|
|
180
|
+
new(
|
|
181
|
+
Route.from_json(data[:route]),
|
|
182
|
+
Request::Schema.from_json(data[:schema])
|
|
183
|
+
)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def initialize(route, schema)
|
|
187
|
+
super()
|
|
188
|
+
@route = route
|
|
189
|
+
@schema = schema
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def as_json
|
|
193
|
+
super.update({
|
|
194
|
+
route: @route.as_json,
|
|
195
|
+
schema: @schema.as_json
|
|
196
|
+
})
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def handle(collector)
|
|
200
|
+
collector.handle_track_route(@route, @schema)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def inspect
|
|
204
|
+
"#<#{self.class.name} #{@route.verb} #{@route.path.inspect}>"
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
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
|