aikido-zen 0.1.1-x86_64-linux → 1.0.0.pre.beta.1-x86_64-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 +7 -0
- data/CHANGELOG.md +4 -0
- data/README.md +11 -2
- data/benchmarks/README.md +8 -12
- data/benchmarks/rails7.1_sql_injection.js +30 -34
- data/docs/banner.svg +128 -129
- data/docs/config.md +8 -6
- data/docs/rails.md +1 -1
- data/lib/aikido/zen/agent.rb +13 -9
- data/lib/aikido/zen/api_client.rb +17 -7
- data/lib/aikido/zen/attack.rb +105 -36
- data/lib/aikido/zen/background_worker.rb +52 -0
- data/lib/aikido/zen/collector/routes.rb +2 -0
- data/lib/aikido/zen/collector.rb +31 -4
- data/lib/aikido/zen/config.rb +55 -20
- data/lib/aikido/zen/detached_agent/agent.rb +78 -0
- data/lib/aikido/zen/detached_agent/front_object.rb +37 -0
- data/lib/aikido/zen/detached_agent/server.rb +41 -0
- data/lib/aikido/zen/detached_agent.rb +2 -0
- data/lib/aikido/zen/errors.rb +18 -1
- data/lib/aikido/zen/event.rb +4 -2
- data/lib/aikido/zen/libzen-v0.1.37.x86_64.so +0 -0
- data/lib/aikido/zen/middleware/check_allowed_addresses.rb +2 -14
- data/lib/aikido/zen/middleware/middleware.rb +11 -0
- data/lib/aikido/zen/middleware/{throttler.rb → rack_throttler.rb} +11 -13
- data/lib/aikido/zen/middleware/request_tracker.rb +190 -0
- data/lib/aikido/zen/middleware/set_context.rb +1 -4
- data/lib/aikido/zen/outbound_connection_monitor.rb +4 -0
- data/lib/aikido/zen/payload.rb +2 -0
- data/lib/aikido/zen/rails_engine.rb +12 -0
- data/lib/aikido/zen/rate_limiter/breaker.rb +3 -3
- data/lib/aikido/zen/rate_limiter.rb +7 -12
- data/lib/aikido/zen/request/rails_router.rb +6 -18
- data/lib/aikido/zen/request/schema/auth_schemas.rb +14 -0
- data/lib/aikido/zen/request/schema/builder.rb +0 -2
- data/lib/aikido/zen/request/schema/definition.rb +0 -5
- data/lib/aikido/zen/request/schema.rb +18 -3
- data/lib/aikido/zen/runtime_settings.rb +2 -2
- data/lib/aikido/zen/scanners/path_traversal/helpers.rb +65 -0
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +63 -0
- data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +64 -0
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +4 -6
- data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +33 -21
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +15 -7
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +6 -0
- data/lib/aikido/zen/scanners.rb +2 -0
- data/lib/aikido/zen/sink.rb +6 -1
- data/lib/aikido/zen/sinks/action_controller.rb +34 -15
- data/lib/aikido/zen/sinks/file.rb +120 -0
- data/lib/aikido/zen/sinks/kernel.rb +73 -0
- data/lib/aikido/zen/sinks/socket.rb +13 -0
- data/lib/aikido/zen/sinks.rb +8 -0
- data/lib/aikido/zen/system_info.rb +1 -1
- data/lib/aikido/zen/version.rb +2 -2
- data/lib/aikido/zen/worker.rb +5 -0
- data/lib/aikido/zen.rb +54 -8
- data/tasklib/bench.rake +31 -7
- data/tasklib/wrk.rb +88 -0
- metadata +22 -8
- data/lib/aikido/zen/libzen-v0.1.31.x86_64.so +0 -0
data/docs/config.md
CHANGED
@@ -17,7 +17,7 @@ requiring a full deploy.)
|
|
17
17
|
## Blocking mode
|
18
18
|
|
19
19
|
In order to have Aikido block requests that look like attacks, you can set
|
20
|
-
`
|
20
|
+
`AIKIDO_BLOCK=true` in your environment, or set
|
21
21
|
`Aikido::Zen.config.blocking_mode = true`.
|
22
22
|
|
23
23
|
(We recommend the ENV variable as you can normally change this easily without
|
@@ -79,21 +79,23 @@ Aikido::Zen.rate_limited_responder = ->(request) {
|
|
79
79
|
}
|
80
80
|
```
|
81
81
|
|
82
|
-
##
|
82
|
+
## Blocking responses
|
83
83
|
|
84
|
-
If you're using the IP blocking features of Zen, you can configure the response
|
84
|
+
If you're using the IP, users or bot blocking features of Zen, you can configure the response
|
85
85
|
we send users when their request is rejected with a Proc that returns a
|
86
86
|
Rack-compatible response tuple, like this:
|
87
87
|
|
88
88
|
``` ruby
|
89
|
-
Aikido::Zen.
|
89
|
+
Aikido::Zen.blocked_responder = ->(request, blocking_type) {
|
90
90
|
# Here, request is an instance of Aikido::Zen::Request, which follows the
|
91
91
|
# underlying Rack::Request (or ActionDispatch::Request in Rails) API.
|
92
|
-
|
92
|
+
# And blocking_type is [:ip, :user]
|
93
|
+
|
94
|
+
[403, {"Content-Type" => "application/json"}, ['{"error":"#{blocking_type.to_s}_blocked"}']]
|
93
95
|
}
|
94
96
|
```
|
95
97
|
|
96
|
-
By default, Zen emits a `text/plain` 403 response that tells the user
|
98
|
+
By default, Zen emits a `text/plain` 403 response that tells the user the request
|
97
99
|
is not allowed.
|
98
100
|
|
99
101
|
## API schema sampling
|
data/docs/rails.md
CHANGED
@@ -48,7 +48,7 @@ Rails.application.config.zen.token = Rails.application.credentials.zen.token
|
|
48
48
|
## Blocking mode
|
49
49
|
|
50
50
|
By default, Zen will only detect and log attacks, but will not block them. You
|
51
|
-
can enable blocking mode by setting the `
|
51
|
+
can enable blocking mode by setting the `AIKIDO_BLOCK` environment variable
|
52
52
|
to `true`.
|
53
53
|
|
54
54
|
When in blocking mode, Zen will raise an exception when it detects an attack.
|
data/lib/aikido/zen/agent.rb
CHANGED
@@ -19,6 +19,7 @@ module Aikido::Zen
|
|
19
19
|
def initialize(
|
20
20
|
config: Aikido::Zen.config,
|
21
21
|
collector: Aikido::Zen.collector,
|
22
|
+
detached_agent: Aikido::Zen.detached_agent,
|
22
23
|
worker: Aikido::Zen::Worker.new(config: config),
|
23
24
|
api_client: Aikido::Zen::APIClient.new(config: config)
|
24
25
|
)
|
@@ -28,6 +29,7 @@ module Aikido::Zen
|
|
28
29
|
@worker = worker
|
29
30
|
@api_client = api_client
|
30
31
|
@collector = collector
|
32
|
+
@detached_agent = detached_agent
|
31
33
|
end
|
32
34
|
|
33
35
|
def started?
|
@@ -35,7 +37,7 @@ module Aikido::Zen
|
|
35
37
|
end
|
36
38
|
|
37
39
|
def start!
|
38
|
-
@config.logger.info "Starting Aikido agent"
|
40
|
+
@config.logger.info "Starting Aikido agent v#{Aikido::Zen::VERSION}"
|
39
41
|
|
40
42
|
raise Aikido::ZenError, "Aikido Agent already started!" if started?
|
41
43
|
@started_at = Time.now.utc
|
@@ -57,7 +59,7 @@ module Aikido::Zen
|
|
57
59
|
at_exit { stop! if started? }
|
58
60
|
|
59
61
|
report(Events::Started.new(time: @started_at)) do |response|
|
60
|
-
Aikido::Zen.runtime_settings.update_from_json(response)
|
62
|
+
updated_settings! if Aikido::Zen.runtime_settings.update_from_json(response)
|
61
63
|
@config.logger.info "Updated runtime settings."
|
62
64
|
rescue => err
|
63
65
|
@config.logger.error(err.message)
|
@@ -103,7 +105,9 @@ module Aikido::Zen
|
|
103
105
|
def handle_attack(attack)
|
104
106
|
attack.will_be_blocked! if @config.blocking_mode?
|
105
107
|
|
106
|
-
@config.logger.error(
|
108
|
+
@config.logger.error(
|
109
|
+
format("Zen has %s a %s: %s", attack.blocked? ? "blocked" : "detected", attack.humanized_name, attack.as_json.to_json)
|
110
|
+
)
|
107
111
|
report(Events::Attack.new(attack: attack)) if @api_client.can_make_requests?
|
108
112
|
|
109
113
|
@collector.track_attack(attack)
|
@@ -139,11 +143,11 @@ module Aikido::Zen
|
|
139
143
|
def send_heartbeat(at: Time.now.utc)
|
140
144
|
return unless @api_client.can_make_requests?
|
141
145
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
146
|
+
@collector.flush_heartbeats.each do |heartbeat|
|
147
|
+
report(heartbeat) do |response|
|
148
|
+
updated_settings! if Aikido::Zen.runtime_settings.update_from_json(response)
|
149
|
+
@config.logger.info "Updated runtime settings after heartbeat"
|
150
|
+
end
|
147
151
|
end
|
148
152
|
end
|
149
153
|
|
@@ -157,7 +161,7 @@ module Aikido::Zen
|
|
157
161
|
def poll_for_setting_updates
|
158
162
|
@worker.every(@config.polling_interval) do
|
159
163
|
if @api_client.should_fetch_settings?
|
160
|
-
Aikido::Zen.runtime_settings.update_from_json(@api_client.fetch_settings)
|
164
|
+
updated_settings! if Aikido::Zen.runtime_settings.update_from_json(@api_client.fetch_settings)
|
161
165
|
@config.logger.info "Updated runtime settings after polling"
|
162
166
|
end
|
163
167
|
end
|
@@ -36,7 +36,7 @@ module Aikido::Zen
|
|
36
36
|
|
37
37
|
response = request(
|
38
38
|
Net::HTTP::Get.new("/config", default_headers),
|
39
|
-
base_url: @config.
|
39
|
+
base_url: @config.realtime_endpoint
|
40
40
|
)
|
41
41
|
|
42
42
|
new_updated_at = Time.at(response["configUpdatedAt"].to_i / 1000)
|
@@ -72,16 +72,26 @@ module Aikido::Zen
|
|
72
72
|
# @return (see #fetch_settings)
|
73
73
|
# @raise (see #request)
|
74
74
|
def report(event)
|
75
|
-
if
|
76
|
-
|
75
|
+
event_type = if event.respond_to?(:type)
|
76
|
+
event.type
|
77
|
+
else
|
78
|
+
event[:type]
|
79
|
+
end
|
80
|
+
|
81
|
+
if @rate_limiter.throttle?(event_type)
|
82
|
+
@config.logger.error("Not reporting #{event_type.upcase} event due to rate limiting")
|
77
83
|
return
|
78
84
|
end
|
79
85
|
|
80
|
-
@config.logger.debug("Reporting #{
|
86
|
+
@config.logger.debug("Reporting #{event_type.upcase} event")
|
81
87
|
|
82
88
|
req = Net::HTTP::Post.new("/api/runtime/events", default_headers)
|
83
89
|
req.content_type = "application/json"
|
84
|
-
req.body =
|
90
|
+
req.body = if event.respond_to?(:as_json)
|
91
|
+
@config.json_encoder.call(event.as_json)
|
92
|
+
else
|
93
|
+
@config.json_encoder.call(event)
|
94
|
+
end
|
85
95
|
|
86
96
|
request(req)
|
87
97
|
rescue Aikido::Zen::RateLimitedError
|
@@ -93,14 +103,14 @@ module Aikido::Zen
|
|
93
103
|
# response.
|
94
104
|
#
|
95
105
|
# @param request [Net::HTTPRequest]
|
96
|
-
# @param base_url [URI] which API to use. Defaults to +Config#
|
106
|
+
# @param base_url [URI] which API to use. Defaults to +Config#api_endpoint+.
|
97
107
|
#
|
98
108
|
# @return [Object] the result of decoding the JSON response from the server.
|
99
109
|
#
|
100
110
|
# @raise [Aikido::Zen::APIError] in case of a 4XX or 5XX response.
|
101
111
|
# @raise [Aikido::Zen::NetworkError] if an error occurs trying to make the
|
102
112
|
# request.
|
103
|
-
private def request(request, base_url: @config.
|
113
|
+
private def request(request, base_url: @config.api_endpoint)
|
104
114
|
Net::HTTP.start(base_url.host, base_url.port, http_settings) do |http|
|
105
115
|
response = http.request(request)
|
106
116
|
|
data/lib/aikido/zen/attack.rb
CHANGED
@@ -25,20 +25,93 @@ module Aikido::Zen
|
|
25
25
|
@blocked
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def humanized_name
|
29
29
|
raise NotImplementedError, "implement in subclasses"
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
32
|
+
def kind
|
33
|
+
raise NotImplementedError, "implement in subclasses"
|
34
|
+
end
|
35
|
+
|
36
|
+
def input
|
37
|
+
raise NotImplementedError, "implement in subclasses"
|
38
|
+
end
|
39
|
+
|
40
|
+
def metadata
|
33
41
|
raise NotImplementedError, "implement in subclasses"
|
34
42
|
end
|
35
43
|
|
44
|
+
def as_json
|
45
|
+
{
|
46
|
+
kind: kind,
|
47
|
+
blocked: blocked?,
|
48
|
+
metadata: metadata,
|
49
|
+
operation: @operation
|
50
|
+
}.merge(input.as_json)
|
51
|
+
end
|
52
|
+
|
36
53
|
def exception(*)
|
37
54
|
raise NotImplementedError, "implement in subclasses"
|
38
55
|
end
|
39
56
|
end
|
40
57
|
|
41
58
|
module Attacks
|
59
|
+
class PathTraversalAttack < Attack
|
60
|
+
attr_reader :input
|
61
|
+
attr_reader :filepath
|
62
|
+
|
63
|
+
def initialize(input:, filepath:, **opts)
|
64
|
+
super(**opts)
|
65
|
+
@input = input
|
66
|
+
@filepath = filepath
|
67
|
+
end
|
68
|
+
|
69
|
+
def metadata
|
70
|
+
{filename: filepath}
|
71
|
+
end
|
72
|
+
|
73
|
+
def humanized_name
|
74
|
+
"path traversal attack"
|
75
|
+
end
|
76
|
+
|
77
|
+
def kind
|
78
|
+
"path_traversal"
|
79
|
+
end
|
80
|
+
|
81
|
+
def exception(*)
|
82
|
+
PathTraversalError.new(self)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class ShellInjectionAttack < Attack
|
87
|
+
attr_reader :input
|
88
|
+
attr_reader :command
|
89
|
+
|
90
|
+
def initialize(input:, command:, **opts)
|
91
|
+
super(**opts)
|
92
|
+
@input = input
|
93
|
+
@command = command
|
94
|
+
end
|
95
|
+
|
96
|
+
def humanized_name
|
97
|
+
"shell injection"
|
98
|
+
end
|
99
|
+
|
100
|
+
def kind
|
101
|
+
"shell_injection"
|
102
|
+
end
|
103
|
+
|
104
|
+
def metadata
|
105
|
+
{
|
106
|
+
command: @command
|
107
|
+
}
|
108
|
+
end
|
109
|
+
|
110
|
+
def exception(*)
|
111
|
+
ShellInjectionError.new(self)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
42
115
|
class SQLInjectionAttack < Attack
|
43
116
|
attr_reader :query
|
44
117
|
attr_reader :input
|
@@ -51,20 +124,16 @@ module Aikido::Zen
|
|
51
124
|
@dialect = dialect
|
52
125
|
end
|
53
126
|
|
54
|
-
def
|
55
|
-
|
56
|
-
"SQL Injection: Malicious user input «%s» detected in %s query «%s»",
|
57
|
-
@input, @dialect, @query
|
58
|
-
)
|
127
|
+
def humanized_name
|
128
|
+
"SQL injection"
|
59
129
|
end
|
60
130
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
}.merge(@input.as_json)
|
131
|
+
def kind
|
132
|
+
"sql_injection"
|
133
|
+
end
|
134
|
+
|
135
|
+
def metadata
|
136
|
+
{sql: @query}
|
68
137
|
end
|
69
138
|
|
70
139
|
def exception(*)
|
@@ -82,24 +151,23 @@ module Aikido::Zen
|
|
82
151
|
@request = request
|
83
152
|
end
|
84
153
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
154
|
+
def humanized_name
|
155
|
+
"server-side request forgery"
|
156
|
+
end
|
157
|
+
|
158
|
+
def kind
|
159
|
+
"ssrf"
|
90
160
|
end
|
91
161
|
|
92
162
|
def exception(*)
|
93
163
|
SSRFDetectedError.new(self)
|
94
164
|
end
|
95
165
|
|
96
|
-
def
|
166
|
+
def metadata
|
97
167
|
{
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
operation: @operation
|
102
|
-
}.merge(@input.as_json)
|
168
|
+
host: @request.uri.hostname,
|
169
|
+
port: @request.uri.port
|
170
|
+
}
|
103
171
|
end
|
104
172
|
end
|
105
173
|
|
@@ -115,23 +183,24 @@ module Aikido::Zen
|
|
115
183
|
@address = address
|
116
184
|
end
|
117
185
|
|
118
|
-
def
|
119
|
-
|
120
|
-
"Stored SSRF: Request to sensitive host «%s» (%s) detected from unknown source in %s",
|
121
|
-
@hostname, @address, @operation
|
122
|
-
)
|
186
|
+
def humanized_name
|
187
|
+
"server-side request forgery"
|
123
188
|
end
|
124
189
|
|
125
190
|
def exception(*)
|
126
191
|
SSRFDetectedError.new(self)
|
127
192
|
end
|
128
193
|
|
129
|
-
def
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
194
|
+
def kind
|
195
|
+
"ssrf"
|
196
|
+
end
|
197
|
+
|
198
|
+
def input
|
199
|
+
Aikido::Zen::Payload::UNKNOWN_PAYLOAD
|
200
|
+
end
|
201
|
+
|
202
|
+
def metadata
|
203
|
+
{}
|
135
204
|
end
|
136
205
|
end
|
137
206
|
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
|
data/lib/aikido/zen/collector.rb
CHANGED
@@ -11,6 +11,8 @@ module Aikido::Zen
|
|
11
11
|
@users = Concurrent::AtomicReference.new(Users.new(@config))
|
12
12
|
@hosts = Concurrent::AtomicReference.new(Hosts.new(@config))
|
13
13
|
@routes = Concurrent::AtomicReference.new(Routes.new(@config))
|
14
|
+
@heartbeats = Queue.new
|
15
|
+
@middleware_installed = Concurrent::AtomicBoolean.new
|
14
16
|
end
|
15
17
|
|
16
18
|
# Flush all the stats into a Heartbeat event that can be reported back to
|
@@ -28,7 +30,19 @@ module Aikido::Zen
|
|
28
30
|
start(at: at)
|
29
31
|
stats = stats.flush(at: at)
|
30
32
|
|
31
|
-
Events::Heartbeat.new(
|
33
|
+
Events::Heartbeat.new(
|
34
|
+
stats: stats, users: users, hosts: hosts, routes: routes, middleware_installed: middleware_installed?
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
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 }
|
32
46
|
end
|
33
47
|
|
34
48
|
# Sets the start time for this collection period.
|
@@ -39,13 +53,17 @@ module Aikido::Zen
|
|
39
53
|
synchronize(@stats) { |stats| stats.start(at) }
|
40
54
|
end
|
41
55
|
|
42
|
-
# Track stats about the
|
43
|
-
# enabled, the API schema for this endpoint.
|
56
|
+
# Track stats about the requests
|
44
57
|
#
|
45
58
|
# @param request [Aikido::Zen::Request]
|
46
59
|
# @return [void]
|
47
|
-
def track_request(
|
60
|
+
def track_request(*)
|
48
61
|
synchronize(@stats) { |stats| stats.add_request }
|
62
|
+
end
|
63
|
+
|
64
|
+
# Record the visited endpoint, and if enabled, the API schema for this endpoint.
|
65
|
+
# @param request [Aikido::Zen::Request]
|
66
|
+
def track_route(request)
|
49
67
|
synchronize(@routes) { |routes| routes.add(request) if request.route }
|
50
68
|
end
|
51
69
|
|
@@ -83,6 +101,10 @@ module Aikido::Zen
|
|
83
101
|
synchronize(@users) { |users| users.add(actor) }
|
84
102
|
end
|
85
103
|
|
104
|
+
def middleware_installed!
|
105
|
+
@middleware_installed.make_true
|
106
|
+
end
|
107
|
+
|
86
108
|
# @api private
|
87
109
|
def routes
|
88
110
|
@routes.get
|
@@ -103,6 +125,11 @@ module Aikido::Zen
|
|
103
125
|
@stats.get
|
104
126
|
end
|
105
127
|
|
128
|
+
# @api private
|
129
|
+
def middleware_installed?
|
130
|
+
@middleware_installed.true?
|
131
|
+
end
|
132
|
+
|
106
133
|
# Atomically modify an object's state within a block, ensuring it's safe
|
107
134
|
# from other threads.
|
108
135
|
private def synchronize(object)
|
data/lib/aikido/zen/config.rb
CHANGED
@@ -16,18 +16,18 @@ module Aikido::Zen
|
|
16
16
|
alias_method :disabled?, :disabled
|
17
17
|
|
18
18
|
# @return [Boolean] whether Aikido should only report infractions or block
|
19
|
-
# the request by raising an Exception. Defaults to whether
|
19
|
+
# the request by raising an Exception. Defaults to whether AIKIDO_BLOCK
|
20
20
|
# is set to a non-empty value in your environment, or +false+ otherwise.
|
21
21
|
attr_accessor :blocking_mode
|
22
22
|
alias_method :blocking_mode?, :blocking_mode
|
23
23
|
|
24
24
|
# @return [URI] The HTTP host for the Aikido API. Defaults to
|
25
25
|
# +https://guard.aikido.dev+.
|
26
|
-
attr_reader :
|
26
|
+
attr_reader :api_endpoint
|
27
27
|
|
28
28
|
# @return [URI] The HTTP host for the Aikido Runtime API. Defaults to
|
29
29
|
# +https://runtime.aikido.dev+.
|
30
|
-
attr_reader :
|
30
|
+
attr_reader :realtime_endpoint
|
31
31
|
|
32
32
|
# @return [Hash] HTTP timeouts for communicating with the API.
|
33
33
|
attr_reader :api_timeouts
|
@@ -53,7 +53,16 @@ module Aikido::Zen
|
|
53
53
|
attr_accessor :json_decoder
|
54
54
|
|
55
55
|
# @return [Logger]
|
56
|
-
|
56
|
+
attr_reader :logger
|
57
|
+
|
58
|
+
# @return [string] Path of the socket where the detached agent will listen.
|
59
|
+
# By default, is stored under the root application path with file name
|
60
|
+
# `aikido-detached-agent.socket`
|
61
|
+
attr_reader :detached_agent_socket_path
|
62
|
+
|
63
|
+
# @return [Boolean] is the agent in debugging mode?
|
64
|
+
attr_accessor :debugging
|
65
|
+
alias_method :debugging?, :debugging
|
57
66
|
|
58
67
|
# @return [Integer] maximum number of timing measurements to keep in memory
|
59
68
|
# before compressing them.
|
@@ -76,10 +85,10 @@ module Aikido::Zen
|
|
76
85
|
# the oldest seen users.
|
77
86
|
attr_accessor :max_users_tracked
|
78
87
|
|
79
|
-
# @return [Proc{Aikido::Zen::Request => Array(Integer, Hash, #each)}]
|
80
|
-
# Rack handler used to respond to requests from IPs blocked in the Aikido
|
88
|
+
# @return [Proc{(Aikido::Zen::Request, Symbol) => Array(Integer, Hash, #each)}]
|
89
|
+
# Rack handler used to respond to requests from IPs, users or others blocked in the Aikido
|
81
90
|
# dashboard.
|
82
|
-
attr_accessor :
|
91
|
+
attr_accessor :blocked_responder
|
83
92
|
|
84
93
|
# @return [Proc{Aikido::Zen::Request => Array(Integer, Hash, #each)}]
|
85
94
|
# Rack handler used to respond to requests that have been rate limited.
|
@@ -90,6 +99,12 @@ module Aikido::Zen
|
|
90
99
|
# differentiate different clients. By default this uses the request IP.
|
91
100
|
attr_accessor :rate_limiting_discriminator
|
92
101
|
|
102
|
+
# @return [Boolean] whether Aikido Zen should collect api schemas.
|
103
|
+
# Defaults to true. Can be set through AIKIDO_FEATURE_COLLECT_API_SCHEMA
|
104
|
+
# environment variable.
|
105
|
+
attr_accessor :collect_api_schema
|
106
|
+
alias_method :collect_api_schema?, :collect_api_schema
|
107
|
+
|
93
108
|
# @return [Integer] max number of requests we sample per endpoint when
|
94
109
|
# computing the schema.
|
95
110
|
attr_accessor :api_schema_max_samples
|
@@ -131,27 +146,30 @@ module Aikido::Zen
|
|
131
146
|
|
132
147
|
def initialize
|
133
148
|
self.disabled = read_boolean_from_env(ENV.fetch("AIKIDO_DISABLED", false))
|
134
|
-
self.blocking_mode = read_boolean_from_env(ENV.fetch("
|
149
|
+
self.blocking_mode = read_boolean_from_env(ENV.fetch("AIKIDO_BLOCK", false))
|
135
150
|
self.api_timeouts = 10
|
136
|
-
self.
|
137
|
-
self.
|
151
|
+
self.api_endpoint = ENV.fetch("AIKIDO_ENDPOINT", DEFAULT_AIKIDO_ENDPOINT)
|
152
|
+
self.realtime_endpoint = ENV.fetch("AIKIDO_REALTIME_ENDPOINT", DEFAULT_RUNTIME_BASE_URL)
|
138
153
|
self.api_token = ENV.fetch("AIKIDO_TOKEN", nil)
|
139
154
|
self.polling_interval = 60
|
140
155
|
self.initial_heartbeat_delay = 60
|
141
156
|
self.json_encoder = DEFAULT_JSON_ENCODER
|
142
157
|
self.json_decoder = DEFAULT_JSON_DECODER
|
143
|
-
self.
|
158
|
+
self.debugging = read_boolean_from_env(ENV.fetch("AIKIDO_DEBUG", false))
|
159
|
+
self.logger = Logger.new($stdout, progname: "aikido", level: debugging ? Logger::DEBUG : Logger::INFO)
|
144
160
|
self.max_performance_samples = 5000
|
161
|
+
self.detached_agent_socket_path = "aikido-detached-agent.socket"
|
145
162
|
self.max_compressed_stats = 100
|
146
163
|
self.max_outbound_connections = 200
|
147
164
|
self.max_users_tracked = 1000
|
148
165
|
self.request_builder = Aikido::Zen::Context::RACK_REQUEST_BUILDER
|
149
|
-
self.
|
166
|
+
self.blocked_responder = DEFAULT_BLOCKED_RESPONDER
|
150
167
|
self.rate_limited_responder = DEFAULT_RATE_LIMITED_RESPONDER
|
151
168
|
self.rate_limiting_discriminator = DEFAULT_RATE_LIMITING_DISCRIMINATOR
|
152
169
|
self.server_rate_limit_deadline = 1800 # 30 min
|
153
170
|
self.client_rate_limit_period = 3600 # 1 hour
|
154
171
|
self.client_rate_limit_max_events = 100
|
172
|
+
self.collect_api_schema = read_boolean_from_env(ENV.fetch("AIKIDO_FEATURE_COLLECT_API_SCHEMA", true))
|
155
173
|
self.api_schema_max_samples = Integer(ENV.fetch("AIKIDO_MAX_API_DISCOVERY_SAMPLES", 10))
|
156
174
|
self.api_schema_collection_max_depth = 20
|
157
175
|
self.api_schema_collection_max_properties = 20
|
@@ -161,15 +179,22 @@ module Aikido::Zen
|
|
161
179
|
# Set the base URL for API requests.
|
162
180
|
#
|
163
181
|
# @param url [String, URI]
|
164
|
-
def
|
165
|
-
@
|
182
|
+
def api_endpoint=(url)
|
183
|
+
@api_endpoint = URI(url)
|
166
184
|
end
|
167
185
|
|
168
186
|
# Set the base URL for runtime API requests.
|
169
187
|
#
|
170
188
|
# @param url [String, URI]
|
171
|
-
def
|
172
|
-
@
|
189
|
+
def realtime_endpoint=(url)
|
190
|
+
@realtime_endpoint = URI(url)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Set the logger and configure its severity level according to agent's debug mode
|
194
|
+
# @param logger [::Logger]
|
195
|
+
def logger=(logger)
|
196
|
+
@logger = logger
|
197
|
+
@logger.level = Logger::DEBUG if debugging
|
173
198
|
end
|
174
199
|
|
175
200
|
# @overload def api_timeouts=(timeouts)
|
@@ -191,6 +216,11 @@ module Aikido::Zen
|
|
191
216
|
@api_timeouts.update(value)
|
192
217
|
end
|
193
218
|
|
219
|
+
def detached_agent_socket_path=(path)
|
220
|
+
@detached_agent_socket_path = path
|
221
|
+
@detached_agent_socket_path = "drbunix:" + @detached_agent_socket_path unless @detached_agent_socket_path.start_with?("drbunix:")
|
222
|
+
end
|
223
|
+
|
194
224
|
private
|
195
225
|
|
196
226
|
def read_boolean_from_env(value)
|
@@ -205,7 +235,7 @@ module Aikido::Zen
|
|
205
235
|
end
|
206
236
|
|
207
237
|
# @!visibility private
|
208
|
-
|
238
|
+
DEFAULT_AIKIDO_ENDPOINT = "https://guard.aikido.dev"
|
209
239
|
|
210
240
|
# @!visibility private
|
211
241
|
DEFAULT_RUNTIME_BASE_URL = "https://runtime.aikido.dev"
|
@@ -217,9 +247,14 @@ module Aikido::Zen
|
|
217
247
|
DEFAULT_JSON_DECODER = JSON.method(:parse)
|
218
248
|
|
219
249
|
# @!visibility private
|
220
|
-
|
221
|
-
message =
|
222
|
-
|
250
|
+
DEFAULT_BLOCKED_RESPONDER = ->(request, blocking_type) do
|
251
|
+
message = case blocking_type
|
252
|
+
when :ip
|
253
|
+
format("Your IP address is not allowed to access this resource. (Your IP: %s)", request.ip)
|
254
|
+
else
|
255
|
+
"You are blocked by Zen."
|
256
|
+
end
|
257
|
+
[403, {"Content-Type" => "text/plain"}, [message]]
|
223
258
|
end
|
224
259
|
|
225
260
|
# @!visibility private
|