aikido-zen 0.1.1-x86_64-linux → 0.2.0-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 +1 -0
- data/CHANGELOG.md +4 -0
- data/README.md +11 -2
- 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 +2 -2
- data/lib/aikido/zen/agent.rb +3 -1
- data/lib/aikido/zen/api_client.rb +3 -3
- data/lib/aikido/zen/attack.rb +105 -36
- data/lib/aikido/zen/collector/routes.rb +2 -0
- data/lib/aikido/zen/collector.rb +19 -3
- data/lib/aikido/zen/config.rb +44 -20
- data/lib/aikido/zen/errors.rb +10 -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} +3 -11
- data/lib/aikido/zen/middleware/request_tracker.rb +190 -0
- data/lib/aikido/zen/middleware/set_context.rb +1 -4
- data/lib/aikido/zen/payload.rb +2 -0
- data/lib/aikido/zen/rails_engine.rb +8 -0
- data/lib/aikido/zen/rate_limiter.rb +1 -1
- 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 +0 -3
- data/lib/aikido/zen/scanners/path_traversal/helpers.rb +65 -0
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +61 -0
- data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +62 -0
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +0 -4
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +9 -6
- data/lib/aikido/zen/scanners.rb +2 -0
- data/lib/aikido/zen/sinks/action_controller.rb +26 -12
- data/lib/aikido/zen/sinks/file.rb +120 -0
- data/lib/aikido/zen/sinks/kernel.rb +73 -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.rb +14 -1
- data/tasklib/bench.rake +3 -2
- metadata +16 -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
@@ -40,7 +40,7 @@ You can just tell Zen to use it like so:
|
|
40
40
|
|
41
41
|
``` ruby
|
42
42
|
# config/initializers/zen.rb
|
43
|
-
Rails.application.config.zen.
|
43
|
+
Rails.application.config.zen.api_token = Rails.application.credentials.zen.token
|
44
44
|
```
|
45
45
|
|
46
46
|
[creds]: https://guides.rubyonrails.org/security.html#environmental-security
|
@@ -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
@@ -103,7 +103,9 @@ module Aikido::Zen
|
|
103
103
|
def handle_attack(attack)
|
104
104
|
attack.will_be_blocked! if @config.blocking_mode?
|
105
105
|
|
106
|
-
@config.logger.error(
|
106
|
+
@config.logger.error(
|
107
|
+
format("Zen has %s a %s: %s", attack.blocked? ? "blocked" : "detected", attack.humanized_name, attack.as_json.to_json)
|
108
|
+
)
|
107
109
|
report(Events::Attack.new(attack: attack)) if @api_client.can_make_requests?
|
108
110
|
|
109
111
|
@collector.track_attack(attack)
|
@@ -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)
|
@@ -93,14 +93,14 @@ module Aikido::Zen
|
|
93
93
|
# response.
|
94
94
|
#
|
95
95
|
# @param request [Net::HTTPRequest]
|
96
|
-
# @param base_url [URI] which API to use. Defaults to +Config#
|
96
|
+
# @param base_url [URI] which API to use. Defaults to +Config#api_endpoint+.
|
97
97
|
#
|
98
98
|
# @return [Object] the result of decoding the JSON response from the server.
|
99
99
|
#
|
100
100
|
# @raise [Aikido::Zen::APIError] in case of a 4XX or 5XX response.
|
101
101
|
# @raise [Aikido::Zen::NetworkError] if an error occurs trying to make the
|
102
102
|
# request.
|
103
|
-
private def request(request, base_url: @config.
|
103
|
+
private def request(request, base_url: @config.api_endpoint)
|
104
104
|
Net::HTTP.start(base_url.host, base_url.port, http_settings) do |http|
|
105
105
|
response = http.request(request)
|
106
106
|
|
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
|
data/lib/aikido/zen/collector.rb
CHANGED
@@ -11,6 +11,7 @@ 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
|
+
@middleware_installed = Concurrent::AtomicBoolean.new
|
14
15
|
end
|
15
16
|
|
16
17
|
# Flush all the stats into a Heartbeat event that can be reported back to
|
@@ -28,7 +29,9 @@ module Aikido::Zen
|
|
28
29
|
start(at: at)
|
29
30
|
stats = stats.flush(at: at)
|
30
31
|
|
31
|
-
Events::Heartbeat.new(
|
32
|
+
Events::Heartbeat.new(
|
33
|
+
stats: stats, users: users, hosts: hosts, routes: routes, middleware_installed: middleware_installed?
|
34
|
+
)
|
32
35
|
end
|
33
36
|
|
34
37
|
# Sets the start time for this collection period.
|
@@ -39,13 +42,17 @@ module Aikido::Zen
|
|
39
42
|
synchronize(@stats) { |stats| stats.start(at) }
|
40
43
|
end
|
41
44
|
|
42
|
-
# Track stats about the
|
43
|
-
# enabled, the API schema for this endpoint.
|
45
|
+
# Track stats about the requests
|
44
46
|
#
|
45
47
|
# @param request [Aikido::Zen::Request]
|
46
48
|
# @return [void]
|
47
49
|
def track_request(request)
|
48
50
|
synchronize(@stats) { |stats| stats.add_request }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Record the visited endpoint, and if enabled, the API schema for this endpoint.
|
54
|
+
# @param request [Aikido::Zen::Request]
|
55
|
+
def track_route(request)
|
49
56
|
synchronize(@routes) { |routes| routes.add(request) if request.route }
|
50
57
|
end
|
51
58
|
|
@@ -83,6 +90,10 @@ module Aikido::Zen
|
|
83
90
|
synchronize(@users) { |users| users.add(actor) }
|
84
91
|
end
|
85
92
|
|
93
|
+
def middleware_installed!
|
94
|
+
@middleware_installed.make_true
|
95
|
+
end
|
96
|
+
|
86
97
|
# @api private
|
87
98
|
def routes
|
88
99
|
@routes.get
|
@@ -103,6 +114,11 @@ module Aikido::Zen
|
|
103
114
|
@stats.get
|
104
115
|
end
|
105
116
|
|
117
|
+
# @api private
|
118
|
+
def middleware_installed?
|
119
|
+
@middleware_installed.true?
|
120
|
+
end
|
121
|
+
|
106
122
|
# Atomically modify an object's state within a block, ensuring it's safe
|
107
123
|
# from other threads.
|
108
124
|
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,11 @@ module Aikido::Zen
|
|
53
53
|
attr_accessor :json_decoder
|
54
54
|
|
55
55
|
# @return [Logger]
|
56
|
-
|
56
|
+
attr_reader :logger
|
57
|
+
|
58
|
+
# @return [Boolean] is the agent in debugging mode?
|
59
|
+
attr_accessor :debugging
|
60
|
+
alias_method :debugging?, :debugging
|
57
61
|
|
58
62
|
# @return [Integer] maximum number of timing measurements to keep in memory
|
59
63
|
# before compressing them.
|
@@ -76,10 +80,10 @@ module Aikido::Zen
|
|
76
80
|
# the oldest seen users.
|
77
81
|
attr_accessor :max_users_tracked
|
78
82
|
|
79
|
-
# @return [Proc{Aikido::Zen::Request => Array(Integer, Hash, #each)}]
|
80
|
-
# Rack handler used to respond to requests from IPs blocked in the Aikido
|
83
|
+
# @return [Proc{(Aikido::Zen::Request, Symbol) => Array(Integer, Hash, #each)}]
|
84
|
+
# Rack handler used to respond to requests from IPs, users or others blocked in the Aikido
|
81
85
|
# dashboard.
|
82
|
-
attr_accessor :
|
86
|
+
attr_accessor :blocked_responder
|
83
87
|
|
84
88
|
# @return [Proc{Aikido::Zen::Request => Array(Integer, Hash, #each)}]
|
85
89
|
# Rack handler used to respond to requests that have been rate limited.
|
@@ -90,6 +94,12 @@ module Aikido::Zen
|
|
90
94
|
# differentiate different clients. By default this uses the request IP.
|
91
95
|
attr_accessor :rate_limiting_discriminator
|
92
96
|
|
97
|
+
# @return [Boolean] whether Aikido Zen should collect api schemas.
|
98
|
+
# Defaults to true. Can be set through AIKIDO_FEATURE_COLLECT_API_SCHEMA
|
99
|
+
# environment variable.
|
100
|
+
attr_accessor :collect_api_schema
|
101
|
+
alias_method :collect_api_schema?, :collect_api_schema
|
102
|
+
|
93
103
|
# @return [Integer] max number of requests we sample per endpoint when
|
94
104
|
# computing the schema.
|
95
105
|
attr_accessor :api_schema_max_samples
|
@@ -131,27 +141,29 @@ module Aikido::Zen
|
|
131
141
|
|
132
142
|
def initialize
|
133
143
|
self.disabled = read_boolean_from_env(ENV.fetch("AIKIDO_DISABLED", false))
|
134
|
-
self.blocking_mode = read_boolean_from_env(ENV.fetch("
|
144
|
+
self.blocking_mode = read_boolean_from_env(ENV.fetch("AIKIDO_BLOCK", false))
|
135
145
|
self.api_timeouts = 10
|
136
|
-
self.
|
137
|
-
self.
|
146
|
+
self.api_endpoint = ENV.fetch("AIKIDO_ENDPOINT", DEFAULT_AIKIDO_ENDPOINT)
|
147
|
+
self.realtime_endpoint = ENV.fetch("AIKIDO_REALTIME_ENDPOINT", DEFAULT_RUNTIME_BASE_URL)
|
138
148
|
self.api_token = ENV.fetch("AIKIDO_TOKEN", nil)
|
139
149
|
self.polling_interval = 60
|
140
150
|
self.initial_heartbeat_delay = 60
|
141
151
|
self.json_encoder = DEFAULT_JSON_ENCODER
|
142
152
|
self.json_decoder = DEFAULT_JSON_DECODER
|
143
|
-
self.
|
153
|
+
self.debugging = read_boolean_from_env(ENV.fetch("AIKIDO_DEBUG", false))
|
154
|
+
self.logger = Logger.new($stdout, progname: "aikido", level: debugging ? Logger::DEBUG : Logger::INFO)
|
144
155
|
self.max_performance_samples = 5000
|
145
156
|
self.max_compressed_stats = 100
|
146
157
|
self.max_outbound_connections = 200
|
147
158
|
self.max_users_tracked = 1000
|
148
159
|
self.request_builder = Aikido::Zen::Context::RACK_REQUEST_BUILDER
|
149
|
-
self.
|
160
|
+
self.blocked_responder = DEFAULT_BLOCKED_RESPONDER
|
150
161
|
self.rate_limited_responder = DEFAULT_RATE_LIMITED_RESPONDER
|
151
162
|
self.rate_limiting_discriminator = DEFAULT_RATE_LIMITING_DISCRIMINATOR
|
152
163
|
self.server_rate_limit_deadline = 1800 # 30 min
|
153
164
|
self.client_rate_limit_period = 3600 # 1 hour
|
154
165
|
self.client_rate_limit_max_events = 100
|
166
|
+
self.collect_api_schema = read_boolean_from_env(ENV.fetch("AIKIDO_FEATURE_COLLECT_API_SCHEMA", true))
|
155
167
|
self.api_schema_max_samples = Integer(ENV.fetch("AIKIDO_MAX_API_DISCOVERY_SAMPLES", 10))
|
156
168
|
self.api_schema_collection_max_depth = 20
|
157
169
|
self.api_schema_collection_max_properties = 20
|
@@ -161,15 +173,22 @@ module Aikido::Zen
|
|
161
173
|
# Set the base URL for API requests.
|
162
174
|
#
|
163
175
|
# @param url [String, URI]
|
164
|
-
def
|
165
|
-
@
|
176
|
+
def api_endpoint=(url)
|
177
|
+
@api_endpoint = URI(url)
|
166
178
|
end
|
167
179
|
|
168
180
|
# Set the base URL for runtime API requests.
|
169
181
|
#
|
170
182
|
# @param url [String, URI]
|
171
|
-
def
|
172
|
-
@
|
183
|
+
def realtime_endpoint=(url)
|
184
|
+
@realtime_endpoint = URI(url)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Set the logger and configure its severity level according to agent's debug mode
|
188
|
+
# @param logger [::Logger]
|
189
|
+
def logger=(logger)
|
190
|
+
@logger = logger
|
191
|
+
@logger.level = Logger::DEBUG if debugging
|
173
192
|
end
|
174
193
|
|
175
194
|
# @overload def api_timeouts=(timeouts)
|
@@ -205,7 +224,7 @@ module Aikido::Zen
|
|
205
224
|
end
|
206
225
|
|
207
226
|
# @!visibility private
|
208
|
-
|
227
|
+
DEFAULT_AIKIDO_ENDPOINT = "https://guard.aikido.dev"
|
209
228
|
|
210
229
|
# @!visibility private
|
211
230
|
DEFAULT_RUNTIME_BASE_URL = "https://runtime.aikido.dev"
|
@@ -217,9 +236,14 @@ module Aikido::Zen
|
|
217
236
|
DEFAULT_JSON_DECODER = JSON.method(:parse)
|
218
237
|
|
219
238
|
# @!visibility private
|
220
|
-
|
221
|
-
message =
|
222
|
-
|
239
|
+
DEFAULT_BLOCKED_RESPONDER = ->(request, blocking_type) do
|
240
|
+
message = case blocking_type
|
241
|
+
when :ip
|
242
|
+
format("Your IP address is not allowed to access this resource. (Your IP: %s)", request.ip)
|
243
|
+
else
|
244
|
+
"You are blocked by Zen."
|
245
|
+
end
|
246
|
+
[403, {"Content-Type" => "text/plain"}, [message]]
|
223
247
|
end
|
224
248
|
|
225
249
|
# @!visibility private
|
data/lib/aikido/zen/errors.rb
CHANGED
@@ -60,7 +60,6 @@ module Aikido
|
|
60
60
|
attr_reader :attack
|
61
61
|
|
62
62
|
def initialize(attack)
|
63
|
-
super(attack.log_message)
|
64
63
|
@attack = attack
|
65
64
|
end
|
66
65
|
end
|
@@ -75,6 +74,16 @@ module Aikido
|
|
75
74
|
def_delegators :@attack, :request, :input
|
76
75
|
end
|
77
76
|
|
77
|
+
class PathTraversalError < UnderAttackError
|
78
|
+
extend Forwardable
|
79
|
+
def_delegators :@attack, :input
|
80
|
+
end
|
81
|
+
|
82
|
+
class ShellInjectionError < UnderAttackError
|
83
|
+
extend Forwardable
|
84
|
+
def_delegators :@attack, :input
|
85
|
+
end
|
86
|
+
|
78
87
|
# Raised when there's any problem communicating (or loading) libzen.
|
79
88
|
class InternalsError < ZenError
|
80
89
|
# @param attempt [String] description of what we were trying to do.
|
data/lib/aikido/zen/event.rb
CHANGED
@@ -48,12 +48,13 @@ module Aikido::Zen
|
|
48
48
|
end
|
49
49
|
|
50
50
|
class Heartbeat < Event
|
51
|
-
def initialize(stats:, users:, hosts:, routes:, **opts)
|
51
|
+
def initialize(stats:, users:, hosts:, routes:, middleware_installed:, **opts)
|
52
52
|
super(type: "heartbeat", **opts)
|
53
53
|
@stats = stats
|
54
54
|
@users = users
|
55
55
|
@hosts = hosts
|
56
56
|
@routes = routes
|
57
|
+
@middleware_installed = middleware_installed
|
57
58
|
end
|
58
59
|
|
59
60
|
def as_json
|
@@ -61,7 +62,8 @@ module Aikido::Zen
|
|
61
62
|
stats: @stats.as_json,
|
62
63
|
users: @users.as_json,
|
63
64
|
routes: @routes.as_json,
|
64
|
-
hostnames: @hosts.as_json
|
65
|
+
hostnames: @hosts.as_json,
|
66
|
+
middlewareInstalled: @middleware_installed
|
65
67
|
)
|
66
68
|
end
|
67
69
|
end
|
Binary file
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../context"
|
4
|
-
|
5
3
|
module Aikido::Zen
|
6
4
|
module Middleware
|
7
5
|
# Middleware that rejects requests from IPs blocked in the Aikido dashboard.
|
@@ -13,24 +11,14 @@ module Aikido::Zen
|
|
13
11
|
end
|
14
12
|
|
15
13
|
def call(env)
|
16
|
-
request = request_from(env)
|
14
|
+
request = Aikido::Zen::Middleware.request_from(env)
|
17
15
|
|
18
16
|
allowed_ips = @settings.endpoints[request.route].allowed_ips
|
19
17
|
|
20
18
|
if allowed_ips.empty? || allowed_ips.include?(request.ip)
|
21
19
|
@app.call(env)
|
22
20
|
else
|
23
|
-
@config.
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def request_from(env)
|
30
|
-
if (current_context = Aikido::Zen.current_context)
|
31
|
-
current_context.request
|
32
|
-
else
|
33
|
-
Context.from_rack_env(env).request
|
21
|
+
@config.blocked_responder.call(request, :ip)
|
34
22
|
end
|
35
23
|
end
|
36
24
|
end
|
@@ -4,10 +4,10 @@ require_relative "../context"
|
|
4
4
|
|
5
5
|
module Aikido::Zen
|
6
6
|
module Middleware
|
7
|
-
#
|
7
|
+
# Rack middleware that rejects requests from clients that are making too many
|
8
8
|
# requests to a given endpoint, based in the runtime configuration in the
|
9
9
|
# Aikido dashboard.
|
10
|
-
class
|
10
|
+
class RackThrottler
|
11
11
|
def initialize(
|
12
12
|
app,
|
13
13
|
config: Aikido::Zen.config,
|
@@ -21,7 +21,7 @@ module Aikido::Zen
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def call(env)
|
24
|
-
request = request_from(env)
|
24
|
+
request = Aikido::Zen::Middleware.request_from(env)
|
25
25
|
|
26
26
|
if should_throttle?(request)
|
27
27
|
@config.rate_limited_responder.call(request)
|
@@ -37,14 +37,6 @@ module Aikido::Zen
|
|
37
37
|
|
38
38
|
@rate_limiter.throttle?(request)
|
39
39
|
end
|
40
|
-
|
41
|
-
def request_from(env)
|
42
|
-
if (current_context = Aikido::Zen.current_context)
|
43
|
-
current_context.request
|
44
|
-
else
|
45
|
-
Context.from_rack_env(env).request
|
46
|
-
end
|
47
|
-
end
|
48
40
|
end
|
49
41
|
end
|
50
42
|
end
|