aikido-zen 1.0.2-aarch64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. checksums.yaml +7 -0
  2. data/.aikido +6 -0
  3. data/.ruby-version +1 -0
  4. data/.simplecov +32 -0
  5. data/.standard.yml +3 -0
  6. data/LICENSE +674 -0
  7. data/README.md +148 -0
  8. data/Rakefile +67 -0
  9. data/benchmarks/README.md +22 -0
  10. data/benchmarks/rails7.1_benchmark.js +1 -0
  11. data/benchmarks/rails7.1_sql_injection.js +102 -0
  12. data/docs/banner.svg +202 -0
  13. data/docs/config.md +133 -0
  14. data/docs/proxy.md +10 -0
  15. data/docs/rails.md +112 -0
  16. data/docs/troubleshooting.md +62 -0
  17. data/lib/aikido/zen/actor.rb +146 -0
  18. data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
  19. data/lib/aikido/zen/agent.rb +181 -0
  20. data/lib/aikido/zen/api_client.rb +145 -0
  21. data/lib/aikido/zen/attack.rb +217 -0
  22. data/lib/aikido/zen/attack_wave/helpers.rb +457 -0
  23. data/lib/aikido/zen/attack_wave.rb +88 -0
  24. data/lib/aikido/zen/background_worker.rb +52 -0
  25. data/lib/aikido/zen/cache.rb +91 -0
  26. data/lib/aikido/zen/capped_collections.rb +86 -0
  27. data/lib/aikido/zen/collector/event.rb +238 -0
  28. data/lib/aikido/zen/collector/hosts.rb +30 -0
  29. data/lib/aikido/zen/collector/routes.rb +71 -0
  30. data/lib/aikido/zen/collector/sink_stats.rb +95 -0
  31. data/lib/aikido/zen/collector/stats.rb +122 -0
  32. data/lib/aikido/zen/collector/users.rb +32 -0
  33. data/lib/aikido/zen/collector.rb +223 -0
  34. data/lib/aikido/zen/config.rb +312 -0
  35. data/lib/aikido/zen/context/rack_request.rb +27 -0
  36. data/lib/aikido/zen/context/rails_request.rb +47 -0
  37. data/lib/aikido/zen/context.rb +145 -0
  38. data/lib/aikido/zen/detached_agent/agent.rb +79 -0
  39. data/lib/aikido/zen/detached_agent/front_object.rb +41 -0
  40. data/lib/aikido/zen/detached_agent/server.rb +78 -0
  41. data/lib/aikido/zen/detached_agent.rb +2 -0
  42. data/lib/aikido/zen/errors.rb +107 -0
  43. data/lib/aikido/zen/event.rb +116 -0
  44. data/lib/aikido/zen/helpers.rb +24 -0
  45. data/lib/aikido/zen/internals.rb +123 -0
  46. data/lib/aikido/zen/libzen-v0.1.48-aarch64-linux.so +0 -0
  47. data/lib/aikido/zen/middleware/allowed_address_checker.rb +26 -0
  48. data/lib/aikido/zen/middleware/attack_wave_protector.rb +46 -0
  49. data/lib/aikido/zen/middleware/context_setter.rb +26 -0
  50. data/lib/aikido/zen/middleware/fork_detector.rb +23 -0
  51. data/lib/aikido/zen/middleware/middleware.rb +11 -0
  52. data/lib/aikido/zen/middleware/rack_throttler.rb +50 -0
  53. data/lib/aikido/zen/middleware/request_tracker.rb +197 -0
  54. data/lib/aikido/zen/outbound_connection.rb +62 -0
  55. data/lib/aikido/zen/outbound_connection_monitor.rb +23 -0
  56. data/lib/aikido/zen/package.rb +22 -0
  57. data/lib/aikido/zen/payload.rb +50 -0
  58. data/lib/aikido/zen/rails_engine.rb +53 -0
  59. data/lib/aikido/zen/rate_limiter/breaker.rb +61 -0
  60. data/lib/aikido/zen/rate_limiter/bucket.rb +76 -0
  61. data/lib/aikido/zen/rate_limiter/result.rb +31 -0
  62. data/lib/aikido/zen/rate_limiter.rb +50 -0
  63. data/lib/aikido/zen/request/heuristic_router.rb +115 -0
  64. data/lib/aikido/zen/request/rails_router.rb +92 -0
  65. data/lib/aikido/zen/request/schema/auth_discovery.rb +86 -0
  66. data/lib/aikido/zen/request/schema/auth_schemas.rb +54 -0
  67. data/lib/aikido/zen/request/schema/builder.rb +121 -0
  68. data/lib/aikido/zen/request/schema/definition.rb +107 -0
  69. data/lib/aikido/zen/request/schema/empty_schema.rb +28 -0
  70. data/lib/aikido/zen/request/schema.rb +87 -0
  71. data/lib/aikido/zen/request.rb +88 -0
  72. data/lib/aikido/zen/route.rb +96 -0
  73. data/lib/aikido/zen/runtime_settings/endpoints.rb +78 -0
  74. data/lib/aikido/zen/runtime_settings/ip_set.rb +36 -0
  75. data/lib/aikido/zen/runtime_settings/protection_settings.rb +62 -0
  76. data/lib/aikido/zen/runtime_settings/rate_limit_settings.rb +47 -0
  77. data/lib/aikido/zen/runtime_settings.rb +66 -0
  78. data/lib/aikido/zen/scan.rb +75 -0
  79. data/lib/aikido/zen/scanners/path_traversal/helpers.rb +68 -0
  80. data/lib/aikido/zen/scanners/path_traversal_scanner.rb +64 -0
  81. data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
  82. data/lib/aikido/zen/scanners/shell_injection_scanner.rb +65 -0
  83. data/lib/aikido/zen/scanners/sql_injection_scanner.rb +94 -0
  84. data/lib/aikido/zen/scanners/ssrf/dns_lookups.rb +27 -0
  85. data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +97 -0
  86. data/lib/aikido/zen/scanners/ssrf_scanner.rb +266 -0
  87. data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +55 -0
  88. data/lib/aikido/zen/scanners.rb +7 -0
  89. data/lib/aikido/zen/sink.rb +118 -0
  90. data/lib/aikido/zen/sinks/action_controller.rb +85 -0
  91. data/lib/aikido/zen/sinks/async_http.rb +80 -0
  92. data/lib/aikido/zen/sinks/curb.rb +113 -0
  93. data/lib/aikido/zen/sinks/em_http.rb +83 -0
  94. data/lib/aikido/zen/sinks/excon.rb +118 -0
  95. data/lib/aikido/zen/sinks/file.rb +153 -0
  96. data/lib/aikido/zen/sinks/http.rb +93 -0
  97. data/lib/aikido/zen/sinks/httpclient.rb +95 -0
  98. data/lib/aikido/zen/sinks/httpx.rb +78 -0
  99. data/lib/aikido/zen/sinks/kernel.rb +33 -0
  100. data/lib/aikido/zen/sinks/mysql2.rb +31 -0
  101. data/lib/aikido/zen/sinks/net_http.rb +101 -0
  102. data/lib/aikido/zen/sinks/patron.rb +103 -0
  103. data/lib/aikido/zen/sinks/pg.rb +72 -0
  104. data/lib/aikido/zen/sinks/resolv.rb +62 -0
  105. data/lib/aikido/zen/sinks/socket.rb +85 -0
  106. data/lib/aikido/zen/sinks/sqlite3.rb +46 -0
  107. data/lib/aikido/zen/sinks/trilogy.rb +31 -0
  108. data/lib/aikido/zen/sinks/typhoeus.rb +78 -0
  109. data/lib/aikido/zen/sinks.rb +36 -0
  110. data/lib/aikido/zen/sinks_dsl.rb +250 -0
  111. data/lib/aikido/zen/synchronizable.rb +24 -0
  112. data/lib/aikido/zen/system_info.rb +80 -0
  113. data/lib/aikido/zen/version.rb +10 -0
  114. data/lib/aikido/zen/worker.rb +87 -0
  115. data/lib/aikido/zen.rb +303 -0
  116. data/lib/aikido-zen.rb +3 -0
  117. data/placeholder/.gitignore +4 -0
  118. data/placeholder/README.md +11 -0
  119. data/placeholder/Rakefile +75 -0
  120. data/placeholder/lib/placeholder.rb.template +3 -0
  121. data/placeholder/placeholder.gemspec.template +20 -0
  122. data/tasklib/bench.rake +94 -0
  123. data/tasklib/libzen.rake +133 -0
  124. data/tasklib/wrk.rb +88 -0
  125. metadata +214 -0
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../capped_collections"
4
+
5
+ module Aikido::Zen
6
+ # @api private
7
+ #
8
+ # Tracks data specific to a single Sink.
9
+ class Collector::SinkStats
10
+ # @return [Integer] number of total calls to Sink#scan.
11
+ attr_accessor :scans
12
+
13
+ # @return [Integer] number of scans where our scanners raised an
14
+ # error that was handled.
15
+ attr_accessor :errors
16
+
17
+ # @return [Integer] number of scans where an attack was detected.
18
+ attr_accessor :attacks
19
+
20
+ # @return [Integer] number of scans where an attack was detected
21
+ # _and_ blocked by the Zen.
22
+ attr_accessor :blocked_attacks
23
+
24
+ # @return [Set<Float>] keeps the duration of individual scans. If
25
+ # this grows to match Config#max_performance_samples, the set is
26
+ # cleared and the data is aggregated into #compressed_timings.
27
+ attr_accessor :timings
28
+
29
+ # @return [Array<CompressedTiming>] list of aggregated stats.
30
+ attr_accessor :compressed_timings
31
+
32
+ def initialize(name, config)
33
+ @name = name
34
+ @config = config
35
+
36
+ @scans = 0
37
+ @errors = 0
38
+
39
+ @attacks = 0
40
+ @blocked_attacks = 0
41
+
42
+ @timings = Set.new
43
+ @compressed_timings = CappedSet.new(@config.max_compressed_stats)
44
+ end
45
+
46
+ def add_timing(duration)
47
+ compress_timings if @timings.size >= @config.max_performance_samples
48
+ @timings << duration
49
+ end
50
+
51
+ def compress_timings(at: Time.now.utc)
52
+ return if @timings.empty?
53
+
54
+ list = @timings.sort
55
+ @timings.clear
56
+
57
+ mean = list.sum / list.size
58
+ percentiles = percentiles(list, 50, 75, 90, 95, 99)
59
+
60
+ @compressed_timings << CompressedTiming.new(mean, percentiles, at)
61
+ end
62
+
63
+ def as_json
64
+ {
65
+ total: @scans,
66
+ interceptorThrewError: @errors,
67
+ withoutContext: 0,
68
+ attacksDetected: {
69
+ total: @attacks,
70
+ blocked: @blocked_attacks
71
+ },
72
+ compressedTimings: @compressed_timings.as_json
73
+ }
74
+ end
75
+
76
+ private def percentiles(sorted, *scores)
77
+ return {} if sorted.empty? || scores.empty?
78
+
79
+ scores.map { |p|
80
+ index = (sorted.size * (p / 100.0)).floor
81
+ [p, sorted.at(index)]
82
+ }.to_h
83
+ end
84
+
85
+ CompressedTiming = Struct.new(:mean, :percentiles, :compressed_at) do
86
+ def as_json
87
+ {
88
+ averageInMs: mean * 1000,
89
+ percentiles: percentiles.transform_values { |t| t * 1000 },
90
+ compressedAt: compressed_at.to_i * 1000
91
+ }
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../capped_collections"
4
+
5
+ module Aikido::Zen
6
+ # @api private
7
+ #
8
+ # Tracks information about how the Aikido Agent is used in the app.
9
+ class Collector::Stats
10
+ # @!visibility private
11
+ attr_reader :started_at, :ended_at, :requests, :aborted_requests, :attack_waves, :blocked_attack_waves, :sinks
12
+
13
+ # @!visibility private
14
+ attr_writer :ended_at
15
+
16
+ def initialize(config = Aikido::Zen.config)
17
+ super()
18
+ @config = config
19
+
20
+ @started_at = @ended_at = nil
21
+ @requests = 0
22
+ @aborted_requests = 0
23
+ @attack_waves = 0
24
+ @blocked_attack_waves = 0
25
+ @sinks = Hash.new { |h, k| h[k] = Collector::SinkStats.new(k, @config) }
26
+ end
27
+
28
+ # @return [Boolean]
29
+ def empty?
30
+ @requests.zero? && @sinks.empty?
31
+ end
32
+
33
+ # @return [Boolean]
34
+ def any?
35
+ !empty?
36
+ end
37
+
38
+ # Track the timestamp we start tracking this series of stats.
39
+ #
40
+ # @param at [Time]
41
+ # @return [void]
42
+ def start(at = Time.now.utc)
43
+ @started_at = at
44
+ end
45
+
46
+ # Sets the end time for these stats block, freezes it to avoid any more
47
+ # writing to them, and compresses the timing stats in anticipation of
48
+ # sending these to the Aikido servers.
49
+ #
50
+ # @param at [Time] the time at which we're resetting, which is set as the
51
+ # ending time for the returned copy.
52
+ # @return [void]
53
+ def flush(at: Time.now.utc)
54
+ # Make sure the timing stats are compressed before copying, since we
55
+ # need these compressed when we serialize this for the API.
56
+ @sinks.each_value { |sink| sink.compress_timings(at: at) }
57
+ @ended_at = at
58
+ freeze
59
+ end
60
+
61
+ # @return [void]
62
+ def add_request
63
+ @requests += 1
64
+ end
65
+
66
+ # @param being_blocked [Boolean] whether the Agent blocked the request
67
+ # @return [void]
68
+ def add_attack_wave(being_blocked:)
69
+ @attack_waves += 1
70
+ @blocked_attack_waves += 1 if being_blocked
71
+ end
72
+
73
+ # @param sink_name [String] the name of the sink
74
+ # @param duration [Float] the length the scan in seconds
75
+ # @param has_errors [Boolean] whether errors occurred during the scan
76
+ # @return [void]
77
+ def add_scan(sink_name, duration, has_errors:)
78
+ stats = @sinks[sink_name]
79
+ stats.scans += 1
80
+ stats.errors += 1 if has_errors
81
+ stats.add_timing(duration)
82
+ end
83
+
84
+ # @param sink_name [String] the name of the sink
85
+ # @param being_blocked [Boolean] whether the Agent blocked the request
86
+ # @return [void]
87
+ def add_attack(sink_name, being_blocked:)
88
+ stats = @sinks[sink_name]
89
+ stats.attacks += 1
90
+ stats.blocked_attacks += 1 if being_blocked
91
+ end
92
+
93
+ def as_json
94
+ total_attacks, total_blocked = aggregate_attacks_from_sinks
95
+ {
96
+ startedAt: @started_at.to_i * 1000,
97
+ endedAt: (@ended_at.to_i * 1000 if @ended_at),
98
+ operations: @sinks.transform_values(&:as_json),
99
+ requests: {
100
+ total: @requests,
101
+ aborted: @aborted_requests,
102
+ attacksDetected: {
103
+ total: total_attacks,
104
+ blocked: total_blocked
105
+ },
106
+ attackWaves: {
107
+ total: @attack_waves,
108
+ blocked: @blocked_attack_waves
109
+ }
110
+ }
111
+ }
112
+ end
113
+
114
+ private def aggregate_attacks_from_sinks
115
+ @sinks.each_value.reduce([0, 0]) { |(attacks, blocked), stats|
116
+ [attacks + stats.attacks, blocked + stats.blocked_attacks]
117
+ }
118
+ end
119
+ end
120
+ end
121
+
122
+ require_relative "sink_stats"
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../capped_collections"
4
+
5
+ module Aikido::Zen
6
+ # @api private
7
+ #
8
+ # Keeps track of the users that were seen by the app.
9
+ class Collector::Users < Aikido::Zen::CappedMap
10
+ def initialize(config = Aikido::Zen.config)
11
+ super(config.max_users_tracked)
12
+ end
13
+
14
+ # @param actor [Aikido::Zen::Actor]
15
+ # @return [void]
16
+ def add(actor)
17
+ if key?(actor.id)
18
+ self[actor.id] |= actor
19
+ else
20
+ self[actor.id] = actor
21
+ end
22
+ end
23
+
24
+ def each(&blk)
25
+ each_value(&blk)
26
+ end
27
+
28
+ def as_json
29
+ map(&:as_json)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "collector/event"
4
+
5
+ module Aikido::Zen
6
+ # Handles collecting all the runtime statistics to report back to the Aikido
7
+ # servers.
8
+ class Collector
9
+ def initialize(config: Aikido::Zen.config)
10
+ @config = config
11
+
12
+ @events = Queue.new
13
+
14
+ @stats = Concurrent::AtomicReference.new(Stats.new(@config))
15
+ @users = Concurrent::AtomicReference.new(Users.new(@config))
16
+ @hosts = Concurrent::AtomicReference.new(Hosts.new(@config))
17
+ @routes = Concurrent::AtomicReference.new(Routes.new(@config))
18
+
19
+ @middleware_installed = Concurrent::AtomicBoolean.new
20
+ end
21
+
22
+ # Add an event, to be handled in the collector in current process or sent
23
+ # to a collector in another process.
24
+ #
25
+ # @param event [Aikido::Zen::Collector::Event] the event to add
26
+ # @return [void]
27
+ def add_event(event)
28
+ @events << event
29
+ end
30
+
31
+ # @return [Boolean] whether the collector has any events
32
+ def has_events?
33
+ !@events.empty?
34
+ end
35
+
36
+ # Flush all events.
37
+ #
38
+ # @return [Array<Aikido::Zen::Collector::Event>]
39
+ def flush_events
40
+ Array.new(@events.size) { @events.pop }
41
+ end
42
+
43
+ # Handle the events in the queue.
44
+ #
45
+ # @return [void]
46
+ def handle
47
+ flush_events.each { |event| event.handle(self) }
48
+ end
49
+
50
+ # Flush all the stats into a Heartbeat event that can be reported back to
51
+ # the Aikido servers.
52
+ #
53
+ # @param at [Time] the time at which stats collection stopped and the start
54
+ # of the new stats collection period. Defaults to now.
55
+ # @return [Aikido::Zen::Events::Heartbeat]
56
+ def flush(at: Time.now.utc)
57
+ handle
58
+
59
+ stats = @stats.get_and_set(Stats.new(@config))
60
+ users = @users.get_and_set(Users.new(@config))
61
+ hosts = @hosts.get_and_set(Hosts.new(@config))
62
+ routes = @routes.get_and_set(Routes.new(@config))
63
+
64
+ start(at: at)
65
+ stats = stats.flush(at: at)
66
+
67
+ Aikido::Zen::Events::Heartbeat.new(
68
+ stats: stats, users: users, hosts: hosts, routes: routes, middleware_installed: middleware_installed?
69
+ )
70
+ end
71
+
72
+ # Sets the start time for this collection period.
73
+ #
74
+ # @param at [Time] defaults to now.
75
+ # @return [void]
76
+ def start(at: Time.now.utc)
77
+ synchronize(@stats) { |stats| stats.start(at) }
78
+ end
79
+
80
+ # Track stats about the requests
81
+ #
82
+ # @return [void]
83
+ def track_request
84
+ add_event(Events::TrackRequest.new)
85
+ end
86
+
87
+ def handle_track_request
88
+ synchronize(@stats) { |stats| stats.add_request }
89
+ end
90
+
91
+ # Track stats about an attack detected by our scanners.
92
+ #
93
+ # @param attack [Aikido::Zen::Events::AttackWave]
94
+ # @return [void]
95
+ def track_attack_wave(being_blocked:)
96
+ add_event(Events::TrackAttackWave.new(being_blocked: being_blocked))
97
+ end
98
+
99
+ def handle_track_attack_wave(being_blocked:)
100
+ synchronize(@stats) do |stats|
101
+ stats.add_attack_wave(being_blocked: being_blocked)
102
+ end
103
+ end
104
+
105
+ # Track stats about a scan performed by one of our sinks.
106
+ #
107
+ # @param scan [Aikido::Zen::Scan]
108
+ # @return [void]
109
+ def track_scan(scan)
110
+ add_event(Events::TrackScan.new(scan.sink.name, scan.duration, has_errors: scan.errors?))
111
+ end
112
+
113
+ def handle_track_scan(sink_name, duration, has_errors:)
114
+ synchronize(@stats) do |stats|
115
+ stats.add_scan(sink_name, duration, has_errors: has_errors)
116
+ end
117
+ end
118
+
119
+ # Track stats about an attack detected by our scanners.
120
+ #
121
+ # @param attack [Aikido::Zen::Attack]
122
+ # @return [void]
123
+ def track_attack(attack)
124
+ add_event(Events::TrackAttack.new(attack.sink.name, being_blocked: attack.blocked?))
125
+ end
126
+
127
+ def handle_track_attack(sink_name, being_blocked:)
128
+ synchronize(@stats) do |stats|
129
+ stats.add_attack(sink_name, being_blocked: being_blocked)
130
+ end
131
+ end
132
+
133
+ # Track the user reported by the developer to be behind this request.
134
+ #
135
+ # @param actor [Aikido::Zen::Actor]
136
+ # @return [void]
137
+ def track_user(actor)
138
+ add_event(Events::TrackUser.new(actor))
139
+ end
140
+
141
+ def handle_track_user(actor)
142
+ synchronize(@users) { |users| users.add(actor) }
143
+ end
144
+
145
+ # Track an HTTP connections to an external host.
146
+ #
147
+ # @param connection [Aikido::Zen::OutboundConnection]
148
+ # @return [void]
149
+ def track_outbound(connection)
150
+ add_event(Events::TrackOutbound.new(connection))
151
+ end
152
+
153
+ def handle_track_outbound(connection)
154
+ synchronize(@hosts) { |hosts| hosts.add(connection) }
155
+ end
156
+
157
+ # Record the visited endpoint, and if enabled, the API schema for this endpoint.
158
+ #
159
+ # @param request [Aikido::Zen::Request]
160
+ # @return [void]
161
+ def track_route(request)
162
+ add_event(Events::TrackRoute.new(request.route, request.schema))
163
+ end
164
+
165
+ def handle_track_route(route, schema)
166
+ synchronize(@routes) { |routes| routes.add(route, schema) }
167
+ end
168
+
169
+ def middleware_installed!
170
+ @middleware_installed.make_true
171
+ end
172
+
173
+ # @api private
174
+ #
175
+ # @note Visible for testing.
176
+ def stats
177
+ handle
178
+ @stats.get
179
+ end
180
+
181
+ # @api private
182
+ #
183
+ # @note Visible for testing.
184
+ def users
185
+ handle
186
+ @users.get
187
+ end
188
+
189
+ # @api private
190
+ #
191
+ # @note Visible for testing.
192
+ def hosts
193
+ handle
194
+ @hosts.get
195
+ end
196
+
197
+ # @api private
198
+ #
199
+ # @note Visible for testing.
200
+ def routes
201
+ handle
202
+ @routes.get
203
+ end
204
+
205
+ # @api private
206
+ #
207
+ # @note Visible for testing.
208
+ def middleware_installed?
209
+ @middleware_installed.true?
210
+ end
211
+
212
+ # Atomically modify an object's state within a block, ensuring it's safe
213
+ # from other threads.
214
+ private def synchronize(object)
215
+ object.update { |obj| obj.tap { yield obj } }
216
+ end
217
+ end
218
+ end
219
+
220
+ require_relative "collector/stats"
221
+ require_relative "collector/users"
222
+ require_relative "collector/hosts"
223
+ require_relative "collector/routes"