aikido-zen 0.2.0-x86_64-mingw-64 → 1.0.1.beta.2-x86_64-mingw-64
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/.aikido +6 -0
- data/.simplecov +6 -0
- data/README.md +67 -83
- data/benchmarks/README.md +8 -12
- data/docs/rails.md +1 -1
- data/lib/aikido/zen/agent.rb +10 -8
- data/lib/aikido/zen/api_client.rb +14 -4
- data/lib/aikido/zen/background_worker.rb +52 -0
- data/lib/aikido/zen/collector.rb +12 -1
- data/lib/aikido/zen/config.rb +20 -0
- data/lib/aikido/zen/context.rb +4 -0
- 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 +8 -0
- data/lib/aikido/zen/internals.rb +41 -7
- data/lib/aikido/zen/libzen-v0.1.39-x86_64-mingw-64.dll +0 -0
- data/lib/aikido/zen/middleware/rack_throttler.rb +9 -3
- data/lib/aikido/zen/middleware/request_tracker.rb +6 -4
- data/lib/aikido/zen/outbound_connection_monitor.rb +4 -0
- data/lib/aikido/zen/rails_engine.rb +8 -8
- data/lib/aikido/zen/rate_limiter/breaker.rb +3 -3
- data/lib/aikido/zen/rate_limiter.rb +6 -11
- data/lib/aikido/zen/request/heuristic_router.rb +6 -0
- 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.rb +18 -0
- data/lib/aikido/zen/runtime_settings.rb +2 -2
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +4 -2
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +4 -2
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +4 -2
- data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +33 -21
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +6 -1
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +6 -0
- data/lib/aikido/zen/sink.rb +11 -1
- data/lib/aikido/zen/sinks/action_controller.rb +9 -4
- data/lib/aikido/zen/sinks/async_http.rb +35 -16
- data/lib/aikido/zen/sinks/curb.rb +52 -26
- data/lib/aikido/zen/sinks/em_http.rb +39 -25
- data/lib/aikido/zen/sinks/excon.rb +63 -45
- data/lib/aikido/zen/sinks/file.rb +67 -71
- data/lib/aikido/zen/sinks/http.rb +38 -19
- data/lib/aikido/zen/sinks/httpclient.rb +51 -22
- data/lib/aikido/zen/sinks/httpx.rb +37 -18
- data/lib/aikido/zen/sinks/kernel.rb +18 -57
- data/lib/aikido/zen/sinks/mysql2.rb +19 -7
- data/lib/aikido/zen/sinks/net_http.rb +37 -19
- data/lib/aikido/zen/sinks/patron.rb +41 -24
- data/lib/aikido/zen/sinks/pg.rb +50 -27
- data/lib/aikido/zen/sinks/resolv.rb +37 -16
- data/lib/aikido/zen/sinks/socket.rb +46 -17
- data/lib/aikido/zen/sinks/sqlite3.rb +31 -12
- data/lib/aikido/zen/sinks/trilogy.rb +19 -7
- data/lib/aikido/zen/sinks.rb +29 -20
- data/lib/aikido/zen/sinks_dsl.rb +226 -0
- data/lib/aikido/zen/version.rb +2 -2
- data/lib/aikido/zen/worker.rb +5 -0
- data/lib/aikido/zen.rb +59 -9
- data/placeholder/.gitignore +4 -0
- data/placeholder/README.md +11 -0
- data/placeholder/Rakefile +75 -0
- data/placeholder/lib/placeholder.rb.template +3 -0
- data/placeholder/placeholder.gemspec.template +20 -0
- data/tasklib/bench.rake +29 -6
- data/tasklib/libzen.rake +70 -66
- data/tasklib/wrk.rb +88 -0
- metadata +23 -13
- data/CHANGELOG.md +0 -25
- data/lib/aikido/zen/libzen-v0.1.37.x86_64.dll +0 -0
- data/lib/aikido.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6870d1d0a0a5f98ddaa519ea4d4629d5f188651767d14e7d73e7f8f5f768dce5
|
4
|
+
data.tar.gz: 8fe0c47688f64f8a735b192159339ff19499f4da99451045ad0e3fad95bccdde
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 516d0a01b52bc249bb5ed730322442c6885e76beb561b49e2dfac7dae399952ba55d7a9485c2f14ff17cdeea2e71ffdd4df94d25d106039aa4114f4327287d45
|
7
|
+
data.tar.gz: 3eb82a1a977b37c4cfba905a22ab071fd1f3ca68529c282b7ddb70659f6b52acefeba80d25d95d72db48bff6ce0dc43dc45e03cb04d691ec09781d4c237da64b
|
data/.aikido
ADDED
data/.simplecov
CHANGED
@@ -15,6 +15,12 @@ SimpleCov.start do
|
|
15
15
|
minimum_coverage line: 95, branch: 85
|
16
16
|
|
17
17
|
add_filter "/test/"
|
18
|
+
|
19
|
+
# WebMock excludes EM-HTTP-Request on Ruby 3.4:
|
20
|
+
# https://github.com/c960657/webmock/commit/34d16285dbcc574c90b273a89f16cb5fb9f4222a
|
21
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.4.0") && Gem.loaded_specs["em-http-request"].version <= Gem::Version.new("1.1.7")
|
22
|
+
add_filter "lib/aikido/zen/sinks/em_http.rb"
|
23
|
+
end
|
18
24
|
end
|
19
25
|
|
20
26
|
# vim: ft=ruby
|
data/README.md
CHANGED
@@ -1,146 +1,137 @@
|
|
1
1
|

|
2
2
|
|
3
|
+
# Zen, in-app firewall for Ruby | by Aikido
|
4
|
+
|
3
5
|
[](https://badge.fury.io/rb/aikido-zen)
|
4
6
|
[](http://makeapullrequest.com)
|
5
7
|
[](https://github.com/AikidoSec/firewall-ruby/actions/workflows/main.yml)
|
6
8
|
[](https://github.com/AikidoSec/firewall-ruby/actions/workflows/release.yml)
|
7
9
|
|
8
|
-
|
10
|
+
Zen, your in-app firewall for peace of mind - at runtime.
|
11
|
+
|
12
|
+
Zen by Aikido is an embedded Web Application Firewall that autonomously protects Ruby apps against common and critical attacks.
|
9
13
|
|
10
|
-
|
14
|
+
It protects your Ruby apps by preventing user input containing dangerous strings, which allow injection, pollution, and path traversal attacks. It runs on the same server as your Ruby app for simple [installation](#installation) and zero maintenance.
|
11
15
|
|
12
|
-
|
13
|
-
Ruby on Rails apps against common and critical attacks.
|
16
|
+
## Features
|
14
17
|
|
15
|
-
|
16
|
-
strings, preventing SQL injection and SSRF attacks. It runs embedded on your
|
17
|
-
Rails application, for simple installation and zero maintenance.
|
18
|
+
Zen will autonomously protect your Ruby applications against:
|
18
19
|
|
19
20
|
* 🛡️ [SQL injection attacks](https://www.aikido.dev/blog/the-state-of-sql-injections)
|
20
21
|
* 🛡️ [Server-side request forgery (SSRF)](https://github.com/AikidoSec/firewall-node/blob/main/docs/ssrf.md)
|
21
|
-
* 🛡️ [Command injection attacks](https://www.aikido.dev/blog/command-injection-in-2024-unpacked)
|
22
|
-
* 🛡️ [Path traversal attacks](https://www.aikido.dev/blog/path-traversal-in-2024-the-year-unpacked)
|
22
|
+
* 🛡️ [Command injection attacks](https://www.aikido.dev/blog/command-injection-in-2024-unpacked)
|
23
|
+
* 🛡️ [Path traversal attacks](https://www.aikido.dev/blog/path-traversal-in-2024-the-year-unpacked)
|
23
24
|
* 🛡️ [NoSQL injection attacks](https://www.aikido.dev/blog/web-application-security-vulnerabilities) (coming soon)
|
24
25
|
|
25
|
-
Zen operates autonomously on the same server as your
|
26
|
+
Zen operates autonomously on the same server as your Ruby app to:
|
26
27
|
|
27
|
-
* ✅ Secure your app like a classic web application firewall (WAF), but with none of the infrastructure or cost
|
28
|
-
* ✅ Rate limit specific API endpoints by IP or by user
|
29
|
-
* ✅ Allow you to block specific users manually
|
28
|
+
* ✅ Secure your app like a classic web application firewall (WAF), but with none of the infrastructure or cost
|
29
|
+
* ✅ Rate limit specific API endpoints by IP or by user
|
30
|
+
* ✅ Allow you to block specific users manually
|
30
31
|
|
31
32
|
## Supported libraries and frameworks
|
32
33
|
|
33
34
|
Zen for Ruby 2.7+ is compatible with:
|
34
35
|
|
36
|
+
### Web frameworks
|
37
|
+
|
38
|
+
* ✅ [Ruby on Rails](docs/rails.md) 7.x, 8.x
|
39
|
+
|
35
40
|
### Database drivers
|
36
41
|
|
37
|
-
* ✅ [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) 1.x, 2.x
|
38
|
-
* ✅ [pg](https://github.com/ged/ruby-pg) 1.x
|
39
|
-
* ✅ [
|
40
|
-
* ✅ [
|
42
|
+
* ✅ [`sqlite3`](https://github.com/sparklemotion/sqlite3-ruby) 1.x, 2.x
|
43
|
+
* ✅ [`pg`](https://github.com/ged/ruby-pg) 1.x
|
44
|
+
* ✅ [`mysql2`](https://github.com/brianmario/mysql2) 0.x
|
45
|
+
* ✅ [`trilogy`](https://github.com/trilogy-libraries/trilogy) 2.x
|
41
46
|
|
42
|
-
### ORMs and
|
47
|
+
### ORMs and query builders
|
43
48
|
|
44
49
|
See list above for supported database drivers.
|
45
50
|
|
46
51
|
* ✅ [ActiveRecord](https://github.com/rails/rails)
|
47
52
|
* ✅ [Sequel](https://github.com/jeremyevans/sequel)
|
48
53
|
|
49
|
-
### HTTP
|
54
|
+
### HTTP clients
|
50
55
|
|
51
|
-
* ✅ [net-http](https://github.com/ruby/net-http)
|
52
|
-
* ✅ [http.rb](https://github.com/httprb/http) 1.x, 2.x, 3.x, 4.x, 5.x
|
53
|
-
* ✅ [httpx](https://gitlab.com/os85/httpx) 1.x (1.1.3+)
|
54
|
-
* ✅ [
|
55
|
-
* ✅ [excon](https://github.com/excon/excon) 0.x (0.50.0+), 1.x
|
56
|
-
* ✅ [
|
57
|
-
* ✅ [
|
58
|
-
* ✅ [
|
59
|
-
* ✅ [
|
60
|
-
* ✅ [
|
56
|
+
* ✅ [`net-http`](https://github.com/ruby/net-http)
|
57
|
+
* ✅ [`http.rb`](https://github.com/httprb/http) 1.x, 2.x, 3.x, 4.x, 5.x
|
58
|
+
* ✅ [`httpx`](https://gitlab.com/os85/httpx) 1.x (1.1.3+)
|
59
|
+
* ✅ [`httpclient`](https://github.com/nahi/httpclient) 2.x, 3.x
|
60
|
+
* ✅ [`excon`](https://github.com/excon/excon) 0.x (0.50.0+), 1.x
|
61
|
+
* ✅ [`curb`](https://github.com/taf2/curb) 0.x (0.2.3+), 1.x
|
62
|
+
* ✅ [`patron`](https://github.com/toland/patron) 0.x (0.6.4+)
|
63
|
+
* ✅ [`typhoeus`](https://github.com/typhoeus/typhoeus) 0.x (0.5.0+), 1.x
|
64
|
+
* ✅ [`async-http`](https://github.com/igrigorik/em-http-request) 0.x (0.70.0+)
|
65
|
+
* ✅ [`em-http-request`](https://github.com/igrigorik/em-http-request) 1.x
|
61
66
|
|
62
67
|
## Installation
|
63
68
|
|
64
69
|
We recommend testing Zen locally or on staging before deploying to production.
|
65
70
|
|
66
|
-
```
|
71
|
+
```sh
|
67
72
|
bundle add aikido-zen
|
68
73
|
```
|
69
74
|
|
70
75
|
or, if not using bundler:
|
71
76
|
|
72
|
-
```
|
77
|
+
```sh
|
73
78
|
gem install aikido-zen
|
74
79
|
```
|
75
80
|
|
76
81
|
For framework specific instructions, check out our docs:
|
77
82
|
|
78
|
-
* [Ruby on Rails
|
83
|
+
* [Ruby on Rails](docs/rails.md)
|
79
84
|
|
80
|
-
##
|
85
|
+
## Reporting to your Aikido Security dashboard
|
81
86
|
|
82
|
-
|
87
|
+
> Aikido is your no nonsense application security platform. One central system that scans your source code & cloud, shows you what vulnerabilities matter, and how to fix them - fast. So you can get back to building.
|
83
88
|
|
84
|
-
|
89
|
+
Zen is a new product by Aikido. Built for developers to level up their security. While Aikido scans, get Zen for always-on protection.
|
85
90
|
|
86
|
-
|
87
|
-
how to send events to Aikido.
|
91
|
+
You can use some of Zen's features without Aikido, of course. Peace of mind is just a few lines of code away.
|
88
92
|
|
89
|
-
|
93
|
+
But you will get the most value by reporting your data to Aikido.
|
90
94
|
|
91
|
-
|
95
|
+
You will need an Aikido account and a token to report events to Aikido. If you don't have an account, you can [sign up for free](https://app.aikido.dev/login).
|
92
96
|
|
93
|
-
|
97
|
+
Here's how:
|
94
98
|
|
95
|
-
|
96
|
-
|
97
|
-
|
99
|
+
* [Log in to your Aikido account](https://app.aikido.dev/login).
|
100
|
+
* Go to [Zen](https://app.aikido.dev/runtime/services).
|
101
|
+
* Go to apps.
|
102
|
+
* Click on **Add app**.
|
103
|
+
* Choose a name for your app.
|
104
|
+
* Click **Generate token**.
|
105
|
+
* Copy the token.
|
106
|
+
* Set the token as an environment variable, `AIKIDO_TOKEN`, using [dotenv](https://github.com/bkeepers/dotenv) or another method of your choosing.
|
98
107
|
|
99
|
-
|
100
|
-
While Aikido scans, get Zen for always-on protection.
|
108
|
+
## Running in production (blocking) mode
|
101
109
|
|
102
|
-
|
103
|
-
just a few lines of code away.
|
110
|
+
By default, Zen will only detect and report attacks to Aikido.
|
104
111
|
|
105
|
-
|
112
|
+
To block requests, set the `AIKIDO_BLOCK` environment variable to `true`.
|
106
113
|
|
107
|
-
|
108
|
-
don't have an account, you can sign up for free.
|
114
|
+
See [Reporting to Aikido](#reporting-to-your-aikido-security-dashboard) to learn how to send events to Aikido.
|
109
115
|
|
110
|
-
|
116
|
+
## Additional configuration
|
111
117
|
|
112
|
-
|
113
|
-
* Go to "Zen" on the sidebar.
|
114
|
-
* Click on "Add App".
|
115
|
-
* Choose a name for your App.
|
116
|
-
* Click "Continue to Install"
|
117
|
-
* Click "Generate Token".
|
118
|
-
* Copy the token.
|
119
|
-
* Set the token as an environment variable, `AIKIDO_TOKEN`, using
|
120
|
-
[dotenv](https://github.com/bkeepers/dotenv) or another method
|
121
|
-
of your choosing.
|
118
|
+
[Configure Zen using environment variables for authentication, mode settings, debugging, and more.](https://help.aikido.dev/doc/configuration-via-env-vars/docrSItUkeR9)
|
122
119
|
|
123
|
-
##
|
120
|
+
## License
|
124
121
|
|
125
|
-
|
126
|
-
application's performance.
|
122
|
+
This program is offered under a commercial and under the AGPL license. You can be released from the requirements of the AGPL license by purchasing a commercial license. Buying such a license is mandatory as soon as you develop commercial activities involving the Zen software without disclosing the source code of your own applications.
|
127
123
|
|
128
|
-
For
|
129
|
-
endpoint that performs a single SQL SELECT query:
|
124
|
+
For more information, please contact Aikido Security at this address: support@aikido.dev or create an account at https://app.aikido.dev.
|
130
125
|
|
131
|
-
|
132
|
-
|------------------|---------------|---------------|
|
133
|
-
| 3.527ms | 3.583ms | +0.056ms |
|
126
|
+
## Benchmarks
|
134
127
|
|
135
|
-
|
136
|
-
will vary based on hardware.
|
128
|
+
We run a benchmark on every commit to ensure Zen has a minimal impact on your application's performance.
|
137
129
|
|
138
|
-
See [benchmarks](benchmarks)
|
130
|
+
See [benchmarks](benchmarks)
|
139
131
|
|
140
132
|
## Bug bounty program
|
141
133
|
|
142
|
-
Our bug bounty program is public and can be found by all registered Intigriti
|
143
|
-
users at: https://app.intigriti.com/researcher/programs/aikido/aikidoruntime
|
134
|
+
Our bug bounty program is public and can be found by all registered Intigriti users at: https://app.intigriti.com/researcher/programs/aikido/aikidozenbeta
|
144
135
|
|
145
136
|
## Contributing
|
146
137
|
|
@@ -150,13 +141,6 @@ See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more information.
|
|
150
141
|
|
151
142
|
See [CODE_OF_CONDUCT.md](.github/CODE_OF_CONDUCT.md) for more information.
|
152
143
|
|
153
|
-
##
|
154
|
-
|
155
|
-
This program is offered under a commercial and under the AGPL license. You can
|
156
|
-
be released from the requirements of the AGPL license by purchasing a commercial
|
157
|
-
license. Buying such a license is mandatory as soon as you develop commercial
|
158
|
-
activities involving the Zen software without disclosing the source code of your
|
159
|
-
own applications.
|
144
|
+
## Security
|
160
145
|
|
161
|
-
|
162
|
-
support@aikido.dev or create an account at https://app.aikido.dev.
|
146
|
+
See [SECURITY.md](.github/SECURITY.md) for more information.
|
data/benchmarks/README.md
CHANGED
@@ -1,27 +1,23 @@
|
|
1
1
|
# Benchmarking Zen for Ruby
|
2
2
|
|
3
|
-
This directory contains the benchmarking scripts that we use to ensure adding
|
4
|
-
Zen to your application does not impact performance significantly.
|
5
3
|
|
6
|
-
We use [Grafana K6](https://k6.io) for these.
|
7
|
-
include in this repo under [sample_apps](../sample_apps), you should find
|
8
|
-
a script here that runs certain benchmarks against that app.
|
4
|
+
We use [WRK](https://github.com/wg/wrk) & [Grafana K6](https://k6.io) for these.
|
9
5
|
|
10
|
-
|
6
|
+
WRK benchmarks are only requesting a URL (`/benchmark`). In case you want to add more
|
7
|
+
of those test, you have to code them in the file `tasklib/bench.rake`.
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
```
|
9
|
+
K6 tests are defined in `benchmarks` folder. They are a javascript file, with calls
|
10
|
+
to different endpoints.
|
15
11
|
|
16
12
|
In order to run a benchmarks against a single application, run the following
|
17
13
|
from the root of the project:
|
18
14
|
|
19
15
|
```
|
20
|
-
$ bundle exec rake bench:{app}:
|
16
|
+
$ BUNDLE_GEMFILE=./sample_apps/{app}/Gemfile bundle exec rake bench:{app}:(k6|wrk)_run
|
21
17
|
```
|
22
18
|
|
23
|
-
For example, for the `rails7.
|
19
|
+
For example, for the WRK of `rails7.1_benchmark` application:
|
24
20
|
|
25
21
|
```
|
26
|
-
$ bundle exec rake bench:rails7.
|
22
|
+
$ BUNDLE_GEMFILE=./sample_apps/rails7.1_benchmark/Gemfile bundle exec rake bench:rails7.1_benchmark:wrk_run
|
27
23
|
```
|
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.token = Rails.application.credentials.zen.token
|
44
44
|
```
|
45
45
|
|
46
46
|
[creds]: https://guides.rubyonrails.org/security.html#environmental-security
|
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)
|
@@ -141,11 +143,11 @@ module Aikido::Zen
|
|
141
143
|
def send_heartbeat(at: Time.now.utc)
|
142
144
|
return unless @api_client.can_make_requests?
|
143
145
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
149
151
|
end
|
150
152
|
end
|
151
153
|
|
@@ -159,7 +161,7 @@ module Aikido::Zen
|
|
159
161
|
def poll_for_setting_updates
|
160
162
|
@worker.every(@config.polling_interval) do
|
161
163
|
if @api_client.should_fetch_settings?
|
162
|
-
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)
|
163
165
|
@config.logger.info "Updated runtime settings after polling"
|
164
166
|
end
|
165
167
|
end
|
@@ -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
|
@@ -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,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
|
+
@heartbeats = Queue.new
|
14
15
|
@middleware_installed = Concurrent::AtomicBoolean.new
|
15
16
|
end
|
16
17
|
|
@@ -34,6 +35,16 @@ module Aikido::Zen
|
|
34
35
|
)
|
35
36
|
end
|
36
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 }
|
46
|
+
end
|
47
|
+
|
37
48
|
# Sets the start time for this collection period.
|
38
49
|
#
|
39
50
|
# @param at [Time] defaults to now.
|
@@ -46,7 +57,7 @@ module Aikido::Zen
|
|
46
57
|
#
|
47
58
|
# @param request [Aikido::Zen::Request]
|
48
59
|
# @return [void]
|
49
|
-
def track_request(
|
60
|
+
def track_request(*)
|
50
61
|
synchronize(@stats) { |stats| stats.add_request }
|
51
62
|
end
|
52
63
|
|
data/lib/aikido/zen/config.rb
CHANGED
@@ -8,6 +8,12 @@ require_relative "context"
|
|
8
8
|
|
9
9
|
module Aikido::Zen
|
10
10
|
class Config
|
11
|
+
# @api private
|
12
|
+
# @return [Boolean] whether Aikido should protect.
|
13
|
+
def protect?
|
14
|
+
!api_token.nil? || blocking_mode? || debugging?
|
15
|
+
end
|
16
|
+
|
11
17
|
# @return [Boolean] whether Aikido should be turned completely off (no
|
12
18
|
# intercepting calls to protect the app, no agent process running, no
|
13
19
|
# middleware installed). Defaults to false (so, enabled). Can be set
|
@@ -55,6 +61,11 @@ module Aikido::Zen
|
|
55
61
|
# @return [Logger]
|
56
62
|
attr_reader :logger
|
57
63
|
|
64
|
+
# @return [string] Path of the socket where the detached agent will listen.
|
65
|
+
# By default, is stored under the root application path with file name
|
66
|
+
# `aikido-detached-agent.sock`
|
67
|
+
attr_reader :detached_agent_socket_path
|
68
|
+
|
58
69
|
# @return [Boolean] is the agent in debugging mode?
|
59
70
|
attr_accessor :debugging
|
60
71
|
alias_method :debugging?, :debugging
|
@@ -153,6 +164,7 @@ module Aikido::Zen
|
|
153
164
|
self.debugging = read_boolean_from_env(ENV.fetch("AIKIDO_DEBUG", false))
|
154
165
|
self.logger = Logger.new($stdout, progname: "aikido", level: debugging ? Logger::DEBUG : Logger::INFO)
|
155
166
|
self.max_performance_samples = 5000
|
167
|
+
self.detached_agent_socket_path = ENV.fetch("AIKIDO_DETACHED_AGENT_SOCKET_PATH", DEFAULT_DETACHED_AGENT_SOCKET_PATH)
|
156
168
|
self.max_compressed_stats = 100
|
157
169
|
self.max_outbound_connections = 200
|
158
170
|
self.max_users_tracked = 1000
|
@@ -210,6 +222,11 @@ module Aikido::Zen
|
|
210
222
|
@api_timeouts.update(value)
|
211
223
|
end
|
212
224
|
|
225
|
+
def detached_agent_socket_path=(path)
|
226
|
+
@detached_agent_socket_path = path
|
227
|
+
@detached_agent_socket_path = "drbunix:" + @detached_agent_socket_path unless @detached_agent_socket_path.start_with?("drbunix:")
|
228
|
+
end
|
229
|
+
|
213
230
|
private
|
214
231
|
|
215
232
|
def read_boolean_from_env(value)
|
@@ -235,6 +252,9 @@ module Aikido::Zen
|
|
235
252
|
# @!visibility private
|
236
253
|
DEFAULT_JSON_DECODER = JSON.method(:parse)
|
237
254
|
|
255
|
+
# @!visibility private
|
256
|
+
DEFAULT_DETACHED_AGENT_SOCKET_PATH = "aikido-detached-agent.sock"
|
257
|
+
|
238
258
|
# @!visibility private
|
239
259
|
DEFAULT_BLOCKED_RESPONDER = ->(request, blocking_type) do
|
240
260
|
message = case blocking_type
|
data/lib/aikido/zen/context.rb
CHANGED
@@ -20,6 +20,9 @@ module Aikido::Zen
|
|
20
20
|
# @return [Aikido::Zen::Request]
|
21
21
|
attr_reader :request
|
22
22
|
|
23
|
+
# @return [Boolean]
|
24
|
+
attr_accessor :scanning
|
25
|
+
|
23
26
|
# @param request [Rack::Request] a Request object that implements the
|
24
27
|
# Rack::Request API, to which we will delegate behavior.
|
25
28
|
# @param settings [Aikido::Zen::RuntimeSettings]
|
@@ -32,6 +35,7 @@ module Aikido::Zen
|
|
32
35
|
@settings = settings
|
33
36
|
@payload_sources = sources
|
34
37
|
@metadata = {}
|
38
|
+
@scanning = false
|
35
39
|
end
|
36
40
|
|
37
41
|
# Fetch some metadata stored in the Context.
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "drb/drb"
|
4
|
+
require "drb/unix"
|
5
|
+
require_relative "front_object"
|
6
|
+
require_relative "../background_worker"
|
7
|
+
|
8
|
+
module Aikido::Zen::DetachedAgent
|
9
|
+
# Agent that runs in forked processes. It communicates with the parent process to dRB
|
10
|
+
# calls. It's in charge of schedule and send heartbeats to the *parent process*, to be
|
11
|
+
# later pushed.
|
12
|
+
#
|
13
|
+
# heartbeat & polling interval are configured to 10s , because they are connecting with
|
14
|
+
# parent process. We want to have the freshest data.
|
15
|
+
#
|
16
|
+
# It's possible to use `extend Forwardable` here for one-line forward calls to the
|
17
|
+
# @detached_agent_front object. Unfortunately, the methods to be called are
|
18
|
+
# created at runtime by `DRbObject`, which leads to an ugly warning about
|
19
|
+
# private methods after the delegator is bound.
|
20
|
+
class Agent
|
21
|
+
attr_reader :worker
|
22
|
+
|
23
|
+
def initialize(
|
24
|
+
heartbeat_interval: 10,
|
25
|
+
polling_interval: 10,
|
26
|
+
config: Aikido::Zen.config,
|
27
|
+
collector: Aikido::Zen.collector,
|
28
|
+
worker: Aikido::Zen::Worker.new(config: config)
|
29
|
+
)
|
30
|
+
@config = config
|
31
|
+
@heartbeat_interval = heartbeat_interval
|
32
|
+
@polling_interval = polling_interval
|
33
|
+
@worker = worker
|
34
|
+
@collector = collector
|
35
|
+
@detached_agent_front = DRbObject.new_with_uri(config.detached_agent_socket_path)
|
36
|
+
@has_forked = false
|
37
|
+
schedule_tasks
|
38
|
+
end
|
39
|
+
|
40
|
+
def send_heartbeat(at: Time.now.utc)
|
41
|
+
return unless @collector.stats.any?
|
42
|
+
|
43
|
+
heartbeat = @collector.flush(at: at)
|
44
|
+
@detached_agent_front.send_heartbeat_to_parent_process(heartbeat.as_json)
|
45
|
+
end
|
46
|
+
|
47
|
+
private def schedule_tasks
|
48
|
+
# For heartbeats is correct to send them from parent or child process. Otherwise, we'll lose
|
49
|
+
# stats made by the parent process.
|
50
|
+
@worker.every(@heartbeat_interval, run_now: false) { send_heartbeat }
|
51
|
+
|
52
|
+
# Runtime_settings fetch must happens only in the child processes, otherwise, due to
|
53
|
+
# we are updating the global runtime_settings, we could have an infinite recursion.
|
54
|
+
if @has_forked
|
55
|
+
@worker.every(@polling_interval) do
|
56
|
+
Aikido::Zen.runtime_settings = @detached_agent_front.updated_settings
|
57
|
+
@config.logger.debug "Updated runtime settings after polling from child process #{Process.pid}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def calculate_rate_limits(request)
|
63
|
+
@detached_agent_front.calculate_rate_limits(request.route, request.ip, request.actor.to_json)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Every time a fork occurs (a new child process is created), we need to start
|
67
|
+
# a DRb service in a background thread within the child process. This service
|
68
|
+
# will manage the connection and handle resource cleanup.
|
69
|
+
def handle_fork
|
70
|
+
@has_forked = true
|
71
|
+
DRb.start_service
|
72
|
+
# we need to ensure that there are not more jobs in the queue, but
|
73
|
+
# we reuse the same object
|
74
|
+
@worker.restart
|
75
|
+
schedule_tasks
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|