aikido-zen 1.0.1.beta.4-arm64-darwin → 1.0.2-arm64-darwin
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 +2 -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/config.md +9 -1
- data/docs/proxy.md +10 -0
- data/docs/rails.md +55 -13
- data/docs/troubleshooting.md +62 -0
- data/lib/aikido/zen/actor.rb +34 -4
- data/lib/aikido/zen/agent/heartbeats_manager.rb +5 -5
- data/lib/aikido/zen/agent.rb +19 -17
- data/lib/aikido/zen/attack.rb +19 -9
- data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
- data/lib/aikido/zen/attack_wave.rb +88 -0
- data/lib/aikido/zen/cache.rb +91 -0
- data/lib/aikido/zen/capped_collections.rb +22 -4
- data/lib/aikido/zen/collector/event.rb +238 -0
- data/lib/aikido/zen/collector/hosts.rb +16 -1
- data/lib/aikido/zen/collector/routes.rb +13 -8
- data/lib/aikido/zen/collector/stats.rb +33 -22
- data/lib/aikido/zen/collector/users.rb +5 -3
- data/lib/aikido/zen/collector.rb +107 -28
- data/lib/aikido/zen/config.rb +54 -21
- data/lib/aikido/zen/context/rack_request.rb +3 -0
- data/lib/aikido/zen/context/rails_request.rb +3 -0
- data/lib/aikido/zen/context.rb +42 -9
- 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/detached_agent/server.rb +63 -26
- data/lib/aikido/zen/event.rb +47 -2
- data/lib/aikido/zen/helpers.rb +24 -0
- data/lib/aikido/zen/internals.rb +23 -3
- data/lib/aikido/zen/libzen-v0.1.48-arm64-darwin.dylib +0 -0
- data/lib/aikido/zen/middleware/{check_allowed_addresses.rb → allowed_address_checker.rb} +1 -1
- data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
- data/lib/aikido/zen/middleware/{set_context.rb → context_setter.rb} +1 -1
- data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
- data/lib/aikido/zen/middleware/rack_throttler.rb +3 -1
- data/lib/aikido/zen/middleware/request_tracker.rb +9 -4
- data/lib/aikido/zen/outbound_connection.rb +18 -1
- data/lib/aikido/zen/payload.rb +1 -1
- data/lib/aikido/zen/rails_engine.rb +5 -8
- data/lib/aikido/zen/request/rails_router.rb +17 -2
- data/lib/aikido/zen/request.rb +21 -36
- data/lib/aikido/zen/route.rb +57 -0
- data/lib/aikido/zen/runtime_settings/endpoints.rb +37 -8
- data/lib/aikido/zen/runtime_settings.rb +6 -5
- data/lib/aikido/zen/scanners/path_traversal/helpers.rb +10 -7
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +5 -4
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +3 -2
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +3 -2
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +2 -1
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +8 -2
- data/lib/aikido/zen/sink.rb +1 -1
- data/lib/aikido/zen/sinks/action_controller.rb +3 -1
- data/lib/aikido/zen/sinks/async_http.rb +40 -42
- data/lib/aikido/zen/sinks/curb.rb +56 -58
- data/lib/aikido/zen/sinks/em_http.rb +27 -29
- data/lib/aikido/zen/sinks/excon.rb +62 -65
- data/lib/aikido/zen/sinks/file.rb +108 -71
- data/lib/aikido/zen/sinks/http.rb +26 -28
- data/lib/aikido/zen/sinks/httpclient.rb +27 -29
- data/lib/aikido/zen/sinks/httpx.rb +27 -29
- data/lib/aikido/zen/sinks/kernel.rb +11 -12
- data/lib/aikido/zen/sinks/mysql2.rb +10 -12
- data/lib/aikido/zen/sinks/net_http.rb +25 -27
- data/lib/aikido/zen/sinks/patron.rb +56 -58
- data/lib/aikido/zen/sinks/pg.rb +23 -25
- data/lib/aikido/zen/sinks/resolv.rb +21 -21
- data/lib/aikido/zen/sinks/socket.rb +17 -12
- data/lib/aikido/zen/sinks/sqlite3.rb +18 -21
- data/lib/aikido/zen/sinks/trilogy.rb +10 -12
- data/lib/aikido/zen/sinks.rb +1 -4
- data/lib/aikido/zen/sinks_dsl.rb +39 -15
- data/lib/aikido/zen/system_info.rb +1 -5
- data/lib/aikido/zen/version.rb +2 -2
- data/lib/aikido/zen.rb +78 -16
- data/tasklib/bench.rake +1 -1
- data/tasklib/libzen.rake +1 -0
- metadata +15 -5
- data/lib/aikido/zen/libzen-v0.1.39-arm64-darwin.dylib +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2927d99621ac205f92581d394233fa16eaf35370e982c2ec2b989e9ba5fd6412
|
|
4
|
+
data.tar.gz: b1fb31ad7000c89ef61c4ac4b17d2b6b8a2c5e274c7526151b628afdf2d40a3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd3eef94aa681bd522ebce53d10b10f16c7f0b001dc92271d43afd2e01a8280f71c48dbe01508d9aa2d5e7e54b6849403f41de1fdc780535a7be27db3027fb12
|
|
7
|
+
data.tar.gz: 66423f1af39166e55a8607dd27a5575de13c8caf89906e0bcb5372e9e4e448e3f7de34dc72ae4a7be1b69ad923562c6ccd4fb071fa1574a2ecf5ef42eca338f5
|
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
|
|
|
@@ -22,6 +23,7 @@ Zen will autonomously protect your Ruby applications against:
|
|
|
22
23
|
* 🛡️ [Command injection attacks](https://www.aikido.dev/blog/command-injection-in-2024-unpacked)
|
|
23
24
|
* 🛡️ [Path traversal attacks](https://www.aikido.dev/blog/path-traversal-in-2024-the-year-unpacked)
|
|
24
25
|
* 🛡️ [NoSQL injection attacks](https://www.aikido.dev/blog/web-application-security-vulnerabilities) (coming soon)
|
|
26
|
+
* 🛡️ [Attack waves](https://help.aikido.dev/zen-firewall/zen-features/attack-wave-protection)
|
|
25
27
|
|
|
26
28
|
Zen operates autonomously on the same server as your Ruby app to:
|
|
27
29
|
|
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/config.md
CHANGED
|
@@ -8,7 +8,7 @@ other Rack-based apps).
|
|
|
8
8
|
## Disable Zen
|
|
9
9
|
|
|
10
10
|
In order to fully turn off Zen and prevent it from intercepting any requests or
|
|
11
|
-
reporting back to the Aikido servers, set `
|
|
11
|
+
reporting back to the Aikido servers, set `AIKIDO_DISABLE=true` in your
|
|
12
12
|
environment, or set `Aikido::Zen.config.disabled = true`.
|
|
13
13
|
|
|
14
14
|
(We recommend the ENV variable as you can normally change this easily without
|
|
@@ -34,6 +34,14 @@ set it via `Aikido::Zen.config.token = <token>`.
|
|
|
34
34
|
|
|
35
35
|
**NOTE**: Never commit your token to the source code repository in plain text.
|
|
36
36
|
|
|
37
|
+
## Hardened mode
|
|
38
|
+
|
|
39
|
+
Zen hardens methods, restricting dangerous undocumented behavior to improve
|
|
40
|
+
security and performance.
|
|
41
|
+
|
|
42
|
+
To disable method hardening, set `AIKIDO_HARDEN=false` in your environment,
|
|
43
|
+
or set `Aikido::Zen.config.harden = false`.
|
|
44
|
+
|
|
37
45
|
## Logger
|
|
38
46
|
|
|
39
47
|
Zen logs to standard output by default. You can change this by changing the
|
data/docs/proxy.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Proxy settings
|
|
2
|
+
|
|
3
|
+
We'll automatically use the `HTTP_X_FORWARDED_FOR` header to determine the client's IP address when behind a trusted proxy.
|
|
4
|
+
|
|
5
|
+
If you need to use a different header to determine the client's IP address, you can set the `AIKIDO_CLIENT_IP_HEADER` environment variable to the name of that header. This will override the default `HTTP_X_FORWARDED_FOR` header.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# For Fly.io Platform
|
|
9
|
+
AIKIDO_CLIENT_IP_HEADER=HTTP_FLY_CLIENT_IP bin/rails server
|
|
10
|
+
```
|
data/docs/rails.md
CHANGED
|
@@ -2,27 +2,71 @@
|
|
|
2
2
|
|
|
3
3
|
To install Zen, add the gem:
|
|
4
4
|
|
|
5
|
-
```
|
|
5
|
+
```sh
|
|
6
6
|
bundle add aikido-zen
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
And require it before `Bundler.require` in `config/application.rb`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# config/application.rb
|
|
13
|
+
require_relative "boot"
|
|
14
|
+
|
|
15
|
+
require "rails/all"
|
|
16
|
+
|
|
17
|
+
require "aikido-zen"
|
|
18
|
+
Aikido::Zen.protect!
|
|
19
|
+
|
|
20
|
+
# Require the gems listed in Gemfile, including any gems
|
|
21
|
+
# you've limited to :test, :development, or :production.
|
|
22
|
+
Bundler.require(*Rails.groups)
|
|
23
|
+
|
|
24
|
+
...
|
|
25
|
+
```
|
|
26
|
+
|
|
9
27
|
That's it! Zen will start to run inside your app when it starts getting
|
|
10
28
|
requests.
|
|
11
29
|
|
|
30
|
+
## Rate limiting and user blocking
|
|
31
|
+
|
|
32
|
+
If you want to add the rate limiting feature to your app, modify your code like this:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# app/controllers/application_controller.rb
|
|
36
|
+
class ApplicationController < ActionController::Base
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def current_user
|
|
40
|
+
return unless session[:user_id]
|
|
41
|
+
User.find(session[:user_id])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def authenticate_user!
|
|
45
|
+
# Your authentication logic here
|
|
46
|
+
# ...
|
|
47
|
+
# Optional, if you want to use user based rate limiting or block specific users
|
|
48
|
+
Aikido::Zen.set_user(
|
|
49
|
+
id: current_user.id,
|
|
50
|
+
name: current_user.name
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
12
56
|
## Configuration
|
|
13
57
|
|
|
14
58
|
Zen exposes its configuration object to the Rails configuration, which you can
|
|
15
59
|
modify in an initializer if desired:
|
|
16
60
|
|
|
17
|
-
```
|
|
61
|
+
```ruby
|
|
18
62
|
# config/initializers/zen.rb
|
|
19
|
-
Rails.application.config.zen.
|
|
63
|
+
Rails.application.config.zen.option = value
|
|
20
64
|
```
|
|
21
65
|
|
|
22
66
|
You can access the configuration object both as `Aikido::Zen.config` or
|
|
23
67
|
`Rails.configuration.zen`.
|
|
24
68
|
|
|
25
|
-
See our [configuration guide](
|
|
69
|
+
See our [configuration guide](./config.md) for more details.
|
|
26
70
|
|
|
27
71
|
## Using Rails encrypted credentials
|
|
28
72
|
|
|
@@ -30,15 +74,15 @@ If you're using Rails' [encrypted credentials][creds], and prefer not storing
|
|
|
30
74
|
sensitive values in your env vars, you can easily configure Zen for it. For
|
|
31
75
|
example, assuming the following credentials structure:
|
|
32
76
|
|
|
33
|
-
```
|
|
77
|
+
```yaml
|
|
34
78
|
# config/credentials.yml.enc
|
|
35
79
|
zen:
|
|
36
80
|
token: "AIKIDO_RUNTIME_..."
|
|
37
81
|
```
|
|
38
82
|
|
|
39
|
-
You can
|
|
83
|
+
You can tell Zen to use it like so:
|
|
40
84
|
|
|
41
|
-
```
|
|
85
|
+
```ruby
|
|
42
86
|
# config/initializers/zen.rb
|
|
43
87
|
Rails.application.config.zen.token = Rails.application.credentials.zen.token
|
|
44
88
|
```
|
|
@@ -58,13 +102,11 @@ way.
|
|
|
58
102
|
|
|
59
103
|
## Logging
|
|
60
104
|
|
|
61
|
-
|
|
62
|
-
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:
|
|
63
106
|
|
|
64
|
-
```
|
|
107
|
+
```ruby
|
|
65
108
|
# config/initializers/zen.rb
|
|
66
|
-
Rails.application.config.zen.logger =
|
|
109
|
+
Rails.application.config.zen.logger = ::Rails.logger
|
|
67
110
|
```
|
|
68
111
|
|
|
69
|
-
|
|
70
|
-
class.
|
|
112
|
+
Zen expects an instance of Ruby's [Logger](https://github.com/ruby/logger) class.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
If the Zen Firewall isn't working as expected, follow these steps in order to diagnose common issues.
|
|
4
|
+
|
|
5
|
+
## Review installation steps
|
|
6
|
+
|
|
7
|
+
Double-check your setup against the [installation guide](../README.md#installation).
|
|
8
|
+
|
|
9
|
+
Make sure:
|
|
10
|
+
- Your runtime and framework are supported (see [Supported libraries and frameworks](../README.md#supported-libraries-and-frameworks)).
|
|
11
|
+
- The package installed successfully.
|
|
12
|
+
- Your framework-specific integration matches the example in the docs (for Rails, see the example in [docs/rails.md](../docs/rails.md)).
|
|
13
|
+
|
|
14
|
+
## Check connection to Aikido
|
|
15
|
+
|
|
16
|
+
The firewall must be able to reach Aikido's API endpoints.
|
|
17
|
+
|
|
18
|
+
Test connectivity from the same environment where your app runs, following the instructions on this page: https://help.aikido.dev/zen-firewall/miscellaneous/outbound-network-connections-for-zen
|
|
19
|
+
|
|
20
|
+
## Check logs for errors
|
|
21
|
+
|
|
22
|
+
Common places:
|
|
23
|
+
- Local dev: `cat log/development.log` or `tail -f log/development.log`
|
|
24
|
+
- Docker: `docker logs <your-app-container>`
|
|
25
|
+
- systemd: `journalctl -u <your-app-service> --since "1 hour ago"`
|
|
26
|
+
|
|
27
|
+
Tip: search logs for lines containing `Aikido` or `Zen`.
|
|
28
|
+
|
|
29
|
+
For example:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
grep -Ei 'aikido|zen' log/development.log
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Enable debug output temporarily
|
|
36
|
+
|
|
37
|
+
If you use Rails, set the log level to `info` or `debug` while you investigate.
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# config/environments/development.rb or production.rb
|
|
41
|
+
config.log_level = :info # use :debug if needed
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If you have your own logger, make sure Zen logs go to stdout.
|
|
45
|
+
|
|
46
|
+
You can enable Zen debugging mode as follows.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# config/initializers/zen.rb
|
|
50
|
+
Rails.application.config.zen.debugging = true
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or set `AIKIDO_DEBUG=true` in your environment.
|
|
54
|
+
|
|
55
|
+
## Contact support
|
|
56
|
+
|
|
57
|
+
If you still can't resolve the problem:
|
|
58
|
+
|
|
59
|
+
- Use the in-app chat to reach our support team directly.
|
|
60
|
+
- Or create an issue on [GitHub](https://github.com/AikidoSec/firewall-ruby/issues) with details about your setup, framework, and logs.
|
|
61
|
+
|
|
62
|
+
Include as much context as possible; this helps us respond faster.
|
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
|
-
if
|
|
47
|
-
@config.logger.info
|
|
46
|
+
if Aikido::Zen.blocking_mode?
|
|
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
|
|
@@ -103,7 +106,7 @@ module Aikido::Zen
|
|
|
103
106
|
# @raise [Aikido::Zen::UnderAttackError] if the firewall is configured
|
|
104
107
|
# to block requests.
|
|
105
108
|
def handle_attack(attack)
|
|
106
|
-
attack.will_be_blocked! if
|
|
109
|
+
attack.will_be_blocked! if Aikido::Zen.blocking_mode?
|
|
107
110
|
|
|
108
111
|
@config.logger.error(
|
|
109
112
|
format("Zen has %s a %s: %s", attack.blocked? ? "blocked" : "detected", attack.humanized_name, attack.as_json.to_json)
|
|
@@ -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
|
data/lib/aikido/zen/attack.rb
CHANGED
|
@@ -10,10 +10,11 @@ module Aikido::Zen
|
|
|
10
10
|
attr_reader :operation
|
|
11
11
|
attr_accessor :sink
|
|
12
12
|
|
|
13
|
-
def initialize(context:, sink:, operation:)
|
|
13
|
+
def initialize(context:, sink:, operation:, stack: nil)
|
|
14
14
|
@context = context
|
|
15
15
|
@operation = operation
|
|
16
16
|
@sink = sink
|
|
17
|
+
@stack = stack
|
|
17
18
|
@blocked = false
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -46,8 +47,9 @@ module Aikido::Zen
|
|
|
46
47
|
kind: kind,
|
|
47
48
|
blocked: blocked?,
|
|
48
49
|
metadata: metadata,
|
|
49
|
-
operation: @operation
|
|
50
|
-
|
|
50
|
+
operation: @operation,
|
|
51
|
+
stack: @stack
|
|
52
|
+
}.compact.merge(input.as_json)
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
def exception(*)
|
|
@@ -67,7 +69,9 @@ module Aikido::Zen
|
|
|
67
69
|
end
|
|
68
70
|
|
|
69
71
|
def metadata
|
|
70
|
-
{
|
|
72
|
+
{
|
|
73
|
+
filename: filepath
|
|
74
|
+
}
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
def humanized_name
|
|
@@ -133,7 +137,10 @@ module Aikido::Zen
|
|
|
133
137
|
end
|
|
134
138
|
|
|
135
139
|
def metadata
|
|
136
|
-
{
|
|
140
|
+
{
|
|
141
|
+
sql: @query,
|
|
142
|
+
dialect: @dialect.name
|
|
143
|
+
}
|
|
137
144
|
end
|
|
138
145
|
|
|
139
146
|
def exception(*)
|
|
@@ -165,8 +172,8 @@ module Aikido::Zen
|
|
|
165
172
|
|
|
166
173
|
def metadata
|
|
167
174
|
{
|
|
168
|
-
|
|
169
|
-
port: @request.uri.port
|
|
175
|
+
hostname: @request.uri.hostname,
|
|
176
|
+
port: @request.uri.port.to_s
|
|
170
177
|
}
|
|
171
178
|
end
|
|
172
179
|
end
|
|
@@ -192,7 +199,7 @@ module Aikido::Zen
|
|
|
192
199
|
end
|
|
193
200
|
|
|
194
201
|
def kind
|
|
195
|
-
"
|
|
202
|
+
"stored_ssrf"
|
|
196
203
|
end
|
|
197
204
|
|
|
198
205
|
def input
|
|
@@ -200,7 +207,10 @@ module Aikido::Zen
|
|
|
200
207
|
end
|
|
201
208
|
|
|
202
209
|
def metadata
|
|
203
|
-
{
|
|
210
|
+
{
|
|
211
|
+
hostname: @hostname,
|
|
212
|
+
privateIP: @address
|
|
213
|
+
}
|
|
204
214
|
end
|
|
205
215
|
end
|
|
206
216
|
end
|