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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/.aikido +6 -0
  3. data/.simplecov +6 -0
  4. data/README.md +67 -83
  5. data/benchmarks/README.md +8 -12
  6. data/docs/rails.md +1 -1
  7. data/lib/aikido/zen/agent.rb +10 -8
  8. data/lib/aikido/zen/api_client.rb +14 -4
  9. data/lib/aikido/zen/background_worker.rb +52 -0
  10. data/lib/aikido/zen/collector.rb +12 -1
  11. data/lib/aikido/zen/config.rb +20 -0
  12. data/lib/aikido/zen/context.rb +4 -0
  13. data/lib/aikido/zen/detached_agent/agent.rb +78 -0
  14. data/lib/aikido/zen/detached_agent/front_object.rb +37 -0
  15. data/lib/aikido/zen/detached_agent/server.rb +41 -0
  16. data/lib/aikido/zen/detached_agent.rb +2 -0
  17. data/lib/aikido/zen/errors.rb +8 -0
  18. data/lib/aikido/zen/internals.rb +41 -7
  19. data/lib/aikido/zen/libzen-v0.1.39-x86_64-mingw-64.dll +0 -0
  20. data/lib/aikido/zen/middleware/rack_throttler.rb +9 -3
  21. data/lib/aikido/zen/middleware/request_tracker.rb +6 -4
  22. data/lib/aikido/zen/outbound_connection_monitor.rb +4 -0
  23. data/lib/aikido/zen/rails_engine.rb +8 -8
  24. data/lib/aikido/zen/rate_limiter/breaker.rb +3 -3
  25. data/lib/aikido/zen/rate_limiter.rb +6 -11
  26. data/lib/aikido/zen/request/heuristic_router.rb +6 -0
  27. data/lib/aikido/zen/request/rails_router.rb +6 -18
  28. data/lib/aikido/zen/request/schema/auth_schemas.rb +14 -0
  29. data/lib/aikido/zen/request/schema.rb +18 -0
  30. data/lib/aikido/zen/runtime_settings.rb +2 -2
  31. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +4 -2
  32. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +4 -2
  33. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +4 -2
  34. data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +33 -21
  35. data/lib/aikido/zen/scanners/ssrf_scanner.rb +6 -1
  36. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +6 -0
  37. data/lib/aikido/zen/sink.rb +11 -1
  38. data/lib/aikido/zen/sinks/action_controller.rb +9 -4
  39. data/lib/aikido/zen/sinks/async_http.rb +35 -16
  40. data/lib/aikido/zen/sinks/curb.rb +52 -26
  41. data/lib/aikido/zen/sinks/em_http.rb +39 -25
  42. data/lib/aikido/zen/sinks/excon.rb +63 -45
  43. data/lib/aikido/zen/sinks/file.rb +67 -71
  44. data/lib/aikido/zen/sinks/http.rb +38 -19
  45. data/lib/aikido/zen/sinks/httpclient.rb +51 -22
  46. data/lib/aikido/zen/sinks/httpx.rb +37 -18
  47. data/lib/aikido/zen/sinks/kernel.rb +18 -57
  48. data/lib/aikido/zen/sinks/mysql2.rb +19 -7
  49. data/lib/aikido/zen/sinks/net_http.rb +37 -19
  50. data/lib/aikido/zen/sinks/patron.rb +41 -24
  51. data/lib/aikido/zen/sinks/pg.rb +50 -27
  52. data/lib/aikido/zen/sinks/resolv.rb +37 -16
  53. data/lib/aikido/zen/sinks/socket.rb +46 -17
  54. data/lib/aikido/zen/sinks/sqlite3.rb +31 -12
  55. data/lib/aikido/zen/sinks/trilogy.rb +19 -7
  56. data/lib/aikido/zen/sinks.rb +29 -20
  57. data/lib/aikido/zen/sinks_dsl.rb +226 -0
  58. data/lib/aikido/zen/version.rb +2 -2
  59. data/lib/aikido/zen/worker.rb +5 -0
  60. data/lib/aikido/zen.rb +59 -9
  61. data/placeholder/.gitignore +4 -0
  62. data/placeholder/README.md +11 -0
  63. data/placeholder/Rakefile +75 -0
  64. data/placeholder/lib/placeholder.rb.template +3 -0
  65. data/placeholder/placeholder.gemspec.template +20 -0
  66. data/tasklib/bench.rake +29 -6
  67. data/tasklib/libzen.rake +70 -66
  68. data/tasklib/wrk.rb +88 -0
  69. metadata +23 -13
  70. data/CHANGELOG.md +0 -25
  71. data/lib/aikido/zen/libzen-v0.1.37.x86_64.dll +0 -0
  72. data/lib/aikido.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ce4b289f036d48b9824da37911bbdf60f09db7ecfe000ae64aef3e4a36f8fd1
4
- data.tar.gz: c05604178d2b213e54efac4b5902fb15081c6955e159e21424450cd8d14ec965
3
+ metadata.gz: 6870d1d0a0a5f98ddaa519ea4d4629d5f188651767d14e7d73e7f8f5f768dce5
4
+ data.tar.gz: 8fe0c47688f64f8a735b192159339ff19499f4da99451045ad0e3fad95bccdde
5
5
  SHA512:
6
- metadata.gz: a4cf09ab6f3f577f9c2acd8e34a7b0da87d4aaa96427c87d4ae86b6697370c665e2fdd49e69829dc48795ec895f1d13643a6875491e277caa3b44c6b88230eb5
7
- data.tar.gz: b753b8b30ab3aa9c8c617f70728b79f70958ed16dee616a3db7f66627474bfa617c2d2acc05958b6c00bb90415fe412c80a96be9641fba2122ff7cfa5bf41450
6
+ metadata.gz: 516d0a01b52bc249bb5ed730322442c6885e76beb561b49e2dfac7dae399952ba55d7a9485c2f14ff17cdeea2e71ffdd4df94d25d106039aa4114f4327287d45
7
+ data.tar.gz: 3eb82a1a977b37c4cfba905a22ab071fd1f3ca68529c282b7ddb70659f6b52acefeba80d25d95d72db48bff6ce0dc43dc45e03cb04d691ec09781d4c237da64b
data/.aikido ADDED
@@ -0,0 +1,6 @@
1
+ exclude:
2
+ paths:
3
+ - benchmarks/
4
+ - docs/
5
+ - sample_apps/
6
+ - tasklib/wrk.rb
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
  ![Zen by Aikido for Ruby](./docs/banner.svg)
2
2
 
3
+ # Zen, in-app firewall for Ruby | by Aikido
4
+
3
5
  [![Gem Version](https://badge.fury.io/rb/aikido-zen.svg?icon=si%3Arubygems&style=flat)](https://badge.fury.io/rb/aikido-zen)
4
6
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
5
7
  [![Unit tests](https://github.com/AikidoSec/firewall-ruby/actions/workflows/main.yml/badge.svg)](https://github.com/AikidoSec/firewall-ruby/actions/workflows/main.yml)
6
8
  [![Release](https://github.com/AikidoSec/firewall-ruby/actions/workflows/release.yml/badge.svg)](https://github.com/AikidoSec/firewall-ruby/actions/workflows/release.yml)
7
9
 
8
- # Zen, in-app firewall for Ruby | by Aikido
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
- Zen, your in-app firewall for peace of mind—at runtime.
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
- Zen by Aikido is an embedded Web Application Firewall that autonomously protects
13
- Ruby on Rails apps against common and critical attacks.
16
+ ## Features
14
17
 
15
- It protects your Rails apps by preventing user input containing dangerous
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) (coming soon)
22
- * 🛡️ [Path traversal attacks](https://www.aikido.dev/blog/path-traversal-in-2024-the-year-unpacked) (coming soon)
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 Rails app to:
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
- * ✅ [trilogy](https://github.com/trilogy-libraries/trilogy) 2.x
40
- * ✅ [mysql2](https://github.com/brianmario/mysql2) 0.x
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 Query Builders
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 Clients
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
- * ✅ [HttpClient](https://github.com/nahi/httpclient) 2.x, 3.x
55
- * ✅ [excon](https://github.com/excon/excon) 0.x (0.50.0+), 1.x
56
- * ✅ [patron](https://github.com/toland/patron) 0.x (0.6.4+)
57
- * ✅ [typhoeus](https://github.com/typhoeus/typhoeus) 0.x (0.5.0+), 1.x
58
- * ✅ [curb](https://github.com/taf2/curb) 0.x (0.2.3+), 1.x
59
- * ✅ [em-http-request](https://github.com/igrigorik/em-http-request) 1.x
60
- * ✅ [async-http](https://github.com/igrigorik/em-http-request) 0.x (0.70.0+)
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 apps](docs/rails.md)
83
+ * [Ruby on Rails](docs/rails.md)
79
84
 
80
- ## Running in production (blocking) mode
85
+ ## Reporting to your Aikido Security dashboard
81
86
 
82
- By default, Zen will only detect and report attacks to Aikido.
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
- To block requests, set the `AIKIDO_BLOCK` environment variable to `true`.
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
- See [Reporting to Aikido](#reporting-to-your-aikido-security-dashboard) to learn
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
- ## Additional configuration
93
+ But you will get the most value by reporting your data to Aikido.
90
94
 
91
- [Configure Zen using environment variables for authentication, mode settings, debugging, and more.](https://help.aikido.dev/doc/configuration-via-env-vars/docrSItUkeR9)
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
- ## Reporting to your Aikido Security dashboard
97
+ Here's how:
94
98
 
95
- > Aikido is your no nonsense application security platform. One central system
96
- > that scans your source code & cloud, shows you what vulnerabilities matter,
97
- > and how to fix them - fast. So you can get back to building.
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
- Zen is a new product by Aikido. Built for developers to level up their security.
100
- While Aikido scans, get Zen for always-on protection.
108
+ ## Running in production (blocking) mode
101
109
 
102
- You can use some of Zen’s features without Aikido, of course. Peace of mind is
103
- just a few lines of code away.
110
+ By default, Zen will only detect and report attacks to Aikido.
104
111
 
105
- But you will get the most value by reporting your data to Aikido.
112
+ To block requests, set the `AIKIDO_BLOCK` environment variable to `true`.
106
113
 
107
- You will need an Aikido account and a token to report events to Aikido. If you
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
- Here's how:
116
+ ## Additional configuration
111
117
 
112
- * Log in to your Aikido account.
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
- ## Performance
120
+ ## License
124
121
 
125
- We run a benchmark on every commit to ensure Zen has a minimal impact on your
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 example, here's a benchmark that runs a single GET request to a Rails
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
- | Without Zen | With Zen | Difference |
132
- |------------------|---------------|---------------|
133
- | 3.527ms | 3.583ms | +0.056ms |
126
+ ## Benchmarks
134
127
 
135
- Using Ruby 3.3, Rails 7.1, SQLite 1.7, running on a MacBook Pro M1 Pro. Results
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) for more information.
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
- ## License
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
- For more information, please contact Aikido Security at this address:
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. For each sample application we
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
- To run all the benchmarks, run the following from the root of the project:
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
- $ bundle exec rake bench
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}:run
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.1_sql_injection` application:
19
+ For example, for the WRK of `rails7.1_benchmark` application:
24
20
 
25
21
  ```
26
- $ bundle exec rake bench:rails7.1_sql_injection:run
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.api_token = Rails.application.credentials.zen.token
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
@@ -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
- event = @collector.flush(at: at)
145
-
146
- report(event) do |response|
147
- Aikido::Zen.runtime_settings.update_from_json(response)
148
- @config.logger.info "Updated runtime settings after heartbeat"
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 @rate_limiter.throttle?(event)
76
- @config.logger.error("Not reporting #{event.type.upcase} event due to rate limiting")
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 #{event.type.upcase} event")
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 = @config.json_encoder.call(event.as_json)
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
@@ -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(request)
60
+ def track_request(*)
50
61
  synchronize(@stats) { |stats| stats.add_request }
51
62
  end
52
63
 
@@ -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
@@ -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