aikido-zen 0.1.0.alpha4-x86_64-mingw-64 → 0.1.0-x86_64-mingw-64

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +136 -23
  3. data/Rakefile +4 -0
  4. data/benchmarks/README.md +27 -0
  5. data/benchmarks/rails7.1_sql_injection.js +74 -0
  6. data/docs/banner.svg +203 -0
  7. data/docs/config.md +123 -0
  8. data/docs/rails.md +70 -0
  9. data/lib/aikido/zen/actor.rb +1 -1
  10. data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
  11. data/lib/aikido/zen/agent.rb +98 -112
  12. data/lib/aikido/zen/collector/hosts.rb +15 -0
  13. data/lib/aikido/zen/collector/routes.rb +64 -0
  14. data/lib/aikido/zen/{stats → collector}/sink_stats.rb +1 -1
  15. data/lib/aikido/zen/collector/stats.rb +111 -0
  16. data/lib/aikido/zen/{stats → collector}/users.rb +6 -2
  17. data/lib/aikido/zen/collector.rb +117 -0
  18. data/lib/aikido/zen/config.rb +17 -11
  19. data/lib/aikido/zen/context.rb +8 -1
  20. data/lib/aikido/zen/errors.rb +3 -1
  21. data/lib/aikido/zen/event.rb +7 -4
  22. data/lib/aikido/zen/{libzen-v0.1.26.x86_64.dll → libzen-v0.1.30.x86_64.dll} +0 -0
  23. data/lib/aikido/zen/middleware/set_context.rb +4 -1
  24. data/lib/aikido/zen/rails_engine.rb +27 -18
  25. data/lib/aikido/zen/request/schema/builder.rb +0 -2
  26. data/lib/aikido/zen/request.rb +6 -0
  27. data/lib/aikido/zen/runtime_settings.rb +6 -11
  28. data/lib/aikido/zen/sinks/action_controller.rb +64 -0
  29. data/lib/aikido/zen/sinks.rb +1 -0
  30. data/lib/aikido/zen/version.rb +2 -2
  31. data/lib/aikido/zen/worker.rb +82 -0
  32. data/lib/aikido/zen.rb +55 -50
  33. data/tasklib/bench.rake +70 -0
  34. metadata +19 -9
  35. data/CODE_OF_CONDUCT.md +0 -132
  36. data/lib/aikido/zen/stats/routes.rb +0 -53
  37. data/lib/aikido/zen/stats.rb +0 -171
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "timeout"
5
+
6
+ SERVER_PIDS = {}
7
+
8
+ def stop_servers
9
+ SERVER_PIDS.each { |_, pid| Process.kill("TERM", pid) }
10
+ SERVER_PIDS.clear
11
+ end
12
+
13
+ def boot_server(dir, port:, env: {})
14
+ env["PORT"] = port.to_s
15
+
16
+ Dir.chdir(dir) do
17
+ SERVER_PIDS[port] = Process.spawn(
18
+ env,
19
+ "rails", "server", "--pid", "#{Dir.pwd}/tmp/pids/server.#{port}.pid",
20
+ out: "/dev/null"
21
+ )
22
+ rescue
23
+ SERVER_PIDS.delete(port)
24
+ end
25
+ end
26
+
27
+ def port_open?(port, timeout: 1)
28
+ Timeout.timeout(timeout) do
29
+ TCPSocket.new("127.0.0.1", port).close
30
+ true
31
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
32
+ false
33
+ end
34
+ rescue Timeout::Error
35
+ false
36
+ end
37
+
38
+ def wait_for_servers
39
+ ports = SERVER_PIDS.keys
40
+
41
+ Timeout.timeout(10) do
42
+ ports.reject! { |port| port_open?(port) } while ports.any?
43
+ end
44
+ rescue Timeout::Error
45
+ raise "Could not reach ports: #{ports.join(", ")}"
46
+ end
47
+
48
+ Pathname.glob("sample_apps/*").select(&:directory?).each do |dir|
49
+ namespace :bench do
50
+ namespace dir.basename.to_s do
51
+ desc "Run benchmarks for the #{dir.basename} sample app"
52
+ task run: [:boot_protected_app, :boot_unprotected_app] do
53
+ wait_for_servers
54
+ Dir.chdir("benchmarks") { sh "k6 run #{dir.basename}.js" }
55
+ ensure
56
+ stop_servers
57
+ end
58
+
59
+ task :boot_protected_app do
60
+ boot_server(dir, port: 3001)
61
+ end
62
+
63
+ task :boot_unprotected_app do
64
+ boot_server(dir, port: 3002, env: {"AIKIDO_DISABLE" => "true"})
65
+ end
66
+ end
67
+
68
+ task default: "#{dir.basename}:run"
69
+ end
70
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aikido-zen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha4
4
+ version: 0.1.0
5
5
  platform: x86_64-mingw-64
6
6
  authors:
7
7
  - Nicolas Sanguinetti
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-24 00:00:00.000000000 Z
11
+ date: 2024-11-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -65,18 +65,29 @@ files:
65
65
  - ".ruby-version"
66
66
  - ".standard.yml"
67
67
  - CHANGELOG.md
68
- - CODE_OF_CONDUCT.md
69
68
  - LICENSE
70
69
  - README.md
71
70
  - Rakefile
71
+ - benchmarks/README.md
72
+ - benchmarks/rails7.1_sql_injection.js
73
+ - docs/banner.svg
74
+ - docs/config.md
75
+ - docs/rails.md
72
76
  - lib/aikido-zen.rb
73
77
  - lib/aikido.rb
74
78
  - lib/aikido/zen.rb
75
79
  - lib/aikido/zen/actor.rb
76
80
  - lib/aikido/zen/agent.rb
81
+ - lib/aikido/zen/agent/heartbeats_manager.rb
77
82
  - lib/aikido/zen/api_client.rb
78
83
  - lib/aikido/zen/attack.rb
79
84
  - lib/aikido/zen/capped_collections.rb
85
+ - lib/aikido/zen/collector.rb
86
+ - lib/aikido/zen/collector/hosts.rb
87
+ - lib/aikido/zen/collector/routes.rb
88
+ - lib/aikido/zen/collector/sink_stats.rb
89
+ - lib/aikido/zen/collector/stats.rb
90
+ - lib/aikido/zen/collector/users.rb
80
91
  - lib/aikido/zen/config.rb
81
92
  - lib/aikido/zen/context.rb
82
93
  - lib/aikido/zen/context/rack_request.rb
@@ -84,7 +95,7 @@ files:
84
95
  - lib/aikido/zen/errors.rb
85
96
  - lib/aikido/zen/event.rb
86
97
  - lib/aikido/zen/internals.rb
87
- - lib/aikido/zen/libzen-v0.1.26.x86_64.dll
98
+ - lib/aikido/zen/libzen-v0.1.30.x86_64.dll
88
99
  - lib/aikido/zen/middleware/check_allowed_addresses.rb
89
100
  - lib/aikido/zen/middleware/set_context.rb
90
101
  - lib/aikido/zen/middleware/throttler.rb
@@ -121,6 +132,7 @@ files:
121
132
  - lib/aikido/zen/scanners/stored_ssrf_scanner.rb
122
133
  - lib/aikido/zen/sink.rb
123
134
  - lib/aikido/zen/sinks.rb
135
+ - lib/aikido/zen/sinks/action_controller.rb
124
136
  - lib/aikido/zen/sinks/async_http.rb
125
137
  - lib/aikido/zen/sinks/curb.rb
126
138
  - lib/aikido/zen/sinks/em_http.rb
@@ -137,13 +149,11 @@ files:
137
149
  - lib/aikido/zen/sinks/sqlite3.rb
138
150
  - lib/aikido/zen/sinks/trilogy.rb
139
151
  - lib/aikido/zen/sinks/typhoeus.rb
140
- - lib/aikido/zen/stats.rb
141
- - lib/aikido/zen/stats/routes.rb
142
- - lib/aikido/zen/stats/sink_stats.rb
143
- - lib/aikido/zen/stats/users.rb
144
152
  - lib/aikido/zen/synchronizable.rb
145
153
  - lib/aikido/zen/system_info.rb
146
154
  - lib/aikido/zen/version.rb
155
+ - lib/aikido/zen/worker.rb
156
+ - tasklib/bench.rake
147
157
  - tasklib/libzen.rake
148
158
  homepage: https://aikido.dev
149
159
  licenses:
@@ -167,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
177
  - !ruby/object:Gem::Version
168
178
  version: '0'
169
179
  requirements: []
170
- rubygems_version: 3.5.16
180
+ rubygems_version: 3.5.22
171
181
  signing_key:
172
182
  specification_version: 4
173
183
  summary: Embedded Web Application Firewall that autonomously protects Ruby apps against
data/CODE_OF_CONDUCT.md DELETED
@@ -1,132 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- We as members, contributors, and leaders pledge to make participation in our
6
- community a harassment-free experience for everyone, regardless of age, body
7
- size, visible or invisible disability, ethnicity, sex characteristics, gender
8
- identity and expression, level of experience, education, socio-economic status,
9
- nationality, personal appearance, race, caste, color, religion, or sexual
10
- identity and orientation.
11
-
12
- We pledge to act and interact in ways that contribute to an open, welcoming,
13
- diverse, inclusive, and healthy community.
14
-
15
- ## Our Standards
16
-
17
- Examples of behavior that contributes to a positive environment for our
18
- community include:
19
-
20
- * Demonstrating empathy and kindness toward other people
21
- * Being respectful of differing opinions, viewpoints, and experiences
22
- * Giving and gracefully accepting constructive feedback
23
- * Accepting responsibility and apologizing to those affected by our mistakes,
24
- and learning from the experience
25
- * Focusing on what is best not just for us as individuals, but for the overall
26
- community
27
-
28
- Examples of unacceptable behavior include:
29
-
30
- * The use of sexualized language or imagery, and sexual attention or advances of
31
- any kind
32
- * Trolling, insulting or derogatory comments, and personal or political attacks
33
- * Public or private harassment
34
- * Publishing others' private information, such as a physical or email address,
35
- without their explicit permission
36
- * Other conduct which could reasonably be considered inappropriate in a
37
- professional setting
38
-
39
- ## Enforcement Responsibilities
40
-
41
- Community leaders are responsible for clarifying and enforcing our standards of
42
- acceptable behavior and will take appropriate and fair corrective action in
43
- response to any behavior that they deem inappropriate, threatening, offensive,
44
- or harmful.
45
-
46
- Community leaders have the right and responsibility to remove, edit, or reject
47
- comments, commits, code, wiki edits, issues, and other contributions that are
48
- not aligned to this Code of Conduct, and will communicate reasons for moderation
49
- decisions when appropriate.
50
-
51
- ## Scope
52
-
53
- This Code of Conduct applies within all community spaces, and also applies when
54
- an individual is officially representing the community in public spaces.
55
- Examples of representing our community include using an official email address,
56
- posting via an official social media account, or acting as an appointed
57
- representative at an online or offline event.
58
-
59
- ## Enforcement
60
-
61
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
- reported to the community leaders responsible for enforcement at
63
- [hello@aikido.dev](mailto:hello@aikido.dev).
64
- All complaints will be reviewed and investigated promptly and fairly.
65
-
66
- All community leaders are obligated to respect the privacy and security of the
67
- reporter of any incident.
68
-
69
- ## Enforcement Guidelines
70
-
71
- Community leaders will follow these Community Impact Guidelines in determining
72
- the consequences for any action they deem in violation of this Code of Conduct:
73
-
74
- ### 1. Correction
75
-
76
- **Community Impact**: Use of inappropriate language or other behavior deemed
77
- unprofessional or unwelcome in the community.
78
-
79
- **Consequence**: A private, written warning from community leaders, providing
80
- clarity around the nature of the violation and an explanation of why the
81
- behavior was inappropriate. A public apology may be requested.
82
-
83
- ### 2. Warning
84
-
85
- **Community Impact**: A violation through a single incident or series of
86
- actions.
87
-
88
- **Consequence**: A warning with consequences for continued behavior. No
89
- interaction with the people involved, including unsolicited interaction with
90
- those enforcing the Code of Conduct, for a specified period of time. This
91
- includes avoiding interactions in community spaces as well as external channels
92
- like social media. Violating these terms may lead to a temporary or permanent
93
- ban.
94
-
95
- ### 3. Temporary Ban
96
-
97
- **Community Impact**: A serious violation of community standards, including
98
- sustained inappropriate behavior.
99
-
100
- **Consequence**: A temporary ban from any sort of interaction or public
101
- communication with the community for a specified period of time. No public or
102
- private interaction with the people involved, including unsolicited interaction
103
- with those enforcing the Code of Conduct, is allowed during this period.
104
- Violating these terms may lead to a permanent ban.
105
-
106
- ### 4. Permanent Ban
107
-
108
- **Community Impact**: Demonstrating a pattern of violation of community
109
- standards, including sustained inappropriate behavior, harassment of an
110
- individual, or aggression toward or disparagement of classes of individuals.
111
-
112
- **Consequence**: A permanent ban from any sort of public interaction within the
113
- community.
114
-
115
- ## Attribution
116
-
117
- This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
- version 2.1, available at
119
- [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
-
121
- Community Impact Guidelines were inspired by
122
- [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
-
124
- For answers to common questions about this code of conduct, see the FAQ at
125
- [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
- [https://www.contributor-covenant.org/translations][translations].
127
-
128
- [homepage]: https://www.contributor-covenant.org
129
- [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
- [Mozilla CoC]: https://github.com/mozilla/diversity
131
- [FAQ]: https://www.contributor-covenant.org/faq
132
- [translations]: https://www.contributor-covenant.org/translations
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../request/schema/empty_schema"
4
-
5
- class Aikido::Zen::Stats
6
- # @api private
7
- #
8
- # Keeps track of the visited routes.
9
- class Routes
10
- def initialize
11
- @routes = Hash.new do |h, k|
12
- h[k] = Record.new(0, Aikido::Zen::Request::Schema::EMPTY_SCHEMA)
13
- end
14
- end
15
-
16
- # @param route [Aikido::Zen::Route, nil] tracks the visit, if given.
17
- # @param schema [Aikido::Zen::Request::Schema, nil] the schema of the
18
- # request, if the feature is enabled.
19
- # @return [void]
20
- def add(route, schema = nil)
21
- @routes[route].add(schema) if route
22
- end
23
-
24
- # @!visibility private
25
- def [](route)
26
- @routes[route]
27
- end
28
-
29
- # @!visibility private
30
- def empty?
31
- @routes.empty?
32
- end
33
-
34
- def as_json
35
- @routes.map do |route, record|
36
- {
37
- method: route.verb,
38
- path: route.path,
39
- hits: record.hits,
40
- apispec: record.schema&.as_json
41
- }.compact
42
- end
43
- end
44
-
45
- # @!visibility private
46
- Record = Struct.new(:hits, :schema) do
47
- def add(new_schema = nil)
48
- self.hits += 1
49
- self.schema |= new_schema
50
- end
51
- end
52
- end
53
- end
@@ -1,171 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "concurrent"
4
-
5
- require_relative "capped_collections"
6
-
7
- module Aikido::Zen
8
- # Tracks information about how the Aikido Agent is used in the app.
9
- class Stats < Concurrent::Synchronization::LockableObject
10
- # @!visibility private
11
- attr_reader :started_at, :ended_at, :requests, :aborted_requests, :sinks
12
-
13
- # @!visibility private
14
- attr_writer :ended_at
15
-
16
- def initialize(config = Aikido::Zen.config)
17
- super()
18
- @config = config
19
- reset_state
20
- end
21
-
22
- # @return [Boolean]
23
- def empty?
24
- synchronize { @requests.zero? && @sinks.empty? }
25
- end
26
-
27
- # @return [Boolean]
28
- def any?
29
- !empty?
30
- end
31
-
32
- # Track the timestamp we start tracking this series of stats.
33
- #
34
- # @return [void]
35
- def start(at = Time.now.utc)
36
- synchronize { @started_at = at }
37
- end
38
-
39
- # Atomically copies the stats and resets them to their initial values, so
40
- # you can start gathering a new set while being able to read the old ones
41
- # without fear that a thread might modify them.
42
- #
43
- # @param at [Time] the time at which we're resetting, which is set as the
44
- # ending time for the returned copy.
45
- #
46
- # @return [Aikido::Zen::Stats] a frozen copy of the object before
47
- # resetting, so you can access the data there, with its +ended_at+
48
- # set to the given timestamp.
49
- def reset(at: Time.now.utc)
50
- synchronize {
51
- # Make sure the timing stats are compressed before copying, since we
52
- # need these compressed when we serialize this for the API.
53
- @sinks.each_value(&:compress_timings)
54
-
55
- copy = clone
56
- copy.ended_at = at
57
-
58
- reset_state
59
- start(at)
60
-
61
- copy
62
- }
63
- end
64
-
65
- # @param request [Aikido::Zen::Request]
66
- # @return [void]
67
- def add_request(request)
68
- synchronize {
69
- @requests += 1
70
- @routes.add(request.route, request.schema)
71
- }
72
- self
73
- end
74
-
75
- # @param connection [Aikido::Zen::OutboundConnection]
76
- # @return [void]
77
- def add_outbound(connection)
78
- synchronize { @outbound_connections << connection }
79
- self
80
- end
81
-
82
- # @param [Aikido::Zen::Scan]
83
- # @return [void]
84
- def add_scan(scan)
85
- synchronize {
86
- stats = @sinks[scan.sink.name]
87
- stats.scans += 1
88
- stats.errors += 1 if scan.errors?
89
- stats.add_timing(scan.duration)
90
- }
91
- self
92
- end
93
-
94
- # @param attack [Aikido::Zen::Attack]
95
- # @param being_blocked [Boolean] whether the Agent blocked the
96
- # request where this Attack happened or not.
97
- #
98
- # @return [void]
99
- def add_attack(attack, being_blocked:)
100
- synchronize {
101
- stats = @sinks[attack.sink.name]
102
- stats.attacks += 1
103
- stats.blocked_attacks += 1 if being_blocked
104
- }
105
- self
106
- end
107
-
108
- # @param actor [Aikido::Zen::Actor]
109
- # @return [void]
110
- def add_user(actor)
111
- synchronize { @users.add(actor) }
112
- end
113
-
114
- # @return [#as_json] the set of routes visited during this stats-gathering
115
- # period.
116
- def routes
117
- synchronize { @routes }
118
- end
119
-
120
- # @return [Enumerable<#as_json>] the set of users that had an active session
121
- # during this stats-gathering period.
122
- def users
123
- synchronize { @users }
124
- end
125
-
126
- # @return [#as_json] the set of connections to outbound hosts that were
127
- # established during this stats-gathering period.
128
- def outbound_connections
129
- synchronize { @outbound_connections }
130
- end
131
-
132
- def as_json
133
- synchronize {
134
- total_attacks, total_blocked = aggregate_attacks_from_sinks
135
- {
136
- startedAt: @started_at.to_i * 1000,
137
- endedAt: (@ended_at.to_i * 1000 if @ended_at),
138
- sinks: @sinks.transform_values(&:as_json),
139
- requests: {
140
- total: @requests,
141
- aborted: @aborted_requests,
142
- attacksDetected: {
143
- total: total_attacks,
144
- blocked: total_blocked
145
- }
146
- }
147
- }
148
- }
149
- end
150
-
151
- private def reset_state
152
- @sinks = Hash.new { |h, k| h[k] = SinkStats.new(k, @config) }
153
- @started_at = @ended_at = nil
154
- @requests = 0
155
- @routes = Routes.new
156
- @users = Users.new(@config.max_users_tracked)
157
- @outbound_connections = CappedSet.new(@config.max_outbound_connections)
158
- @aborted_requests = 0
159
- end
160
-
161
- private def aggregate_attacks_from_sinks
162
- @sinks.each_value.reduce([0, 0]) { |(attacks, blocked), stats|
163
- [attacks + stats.attacks, blocked + stats.blocked_attacks]
164
- }
165
- end
166
- end
167
- end
168
-
169
- require_relative "stats/users"
170
- require_relative "stats/routes"
171
- require_relative "stats/sink_stats"