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
data/lib/aikido/zen.rb ADDED
@@ -0,0 +1,303 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "zen/helpers"
4
+ require_relative "zen/version"
5
+ require_relative "zen/errors"
6
+ require_relative "zen/actor"
7
+ require_relative "zen/config"
8
+ require_relative "zen/collector"
9
+ require_relative "zen/system_info"
10
+ require_relative "zen/worker"
11
+ require_relative "zen/agent"
12
+ require_relative "zen/api_client"
13
+ require_relative "zen/context"
14
+ require_relative "zen/detached_agent"
15
+ require_relative "zen/middleware/middleware"
16
+ require_relative "zen/middleware/fork_detector"
17
+ require_relative "zen/middleware/context_setter"
18
+ require_relative "zen/middleware/allowed_address_checker"
19
+ require_relative "zen/middleware/attack_wave_protector"
20
+ require_relative "zen/middleware/request_tracker"
21
+ require_relative "zen/outbound_connection"
22
+ require_relative "zen/outbound_connection_monitor"
23
+ require_relative "zen/runtime_settings"
24
+ require_relative "zen/rate_limiter"
25
+ require_relative "zen/attack_wave"
26
+ require_relative "zen/scanners"
27
+
28
+ module Aikido
29
+ module Zen
30
+ # Enable protection. Until this method is called no sinks are loaded
31
+ # and the Aikido Agent does not start.
32
+ #
33
+ # This method should be called only once, in the application after the
34
+ # initialization process is complete.
35
+ #
36
+ # @return [void]
37
+ def self.protect!
38
+ if config.disabled?
39
+ config.logger.warn("Zen has been disabled and will not run.")
40
+ return
41
+ end
42
+
43
+ unless load_sources! && load_sinks!
44
+ config.logger.warn("Zen could not find any supported libraries or frameworks. Visit https://github.com/AikidoSec/firewall-ruby for more information.")
45
+ return
46
+ end
47
+
48
+ middleware_installed!
49
+ end
50
+
51
+ # @!visibility private
52
+ # Returns whether the loaded gem specification satisfies the listed requirements.
53
+ #
54
+ # Returns false if the gem specification is not loaded.
55
+ #
56
+ # @param name [String] the gem name
57
+ # @param requirements [Array<String>] a variable number of gem requirement strings
58
+ #
59
+ # @return [Boolean] true if the gem specification is loaded and all gem requirements are satisfied
60
+ def self.satisfy(name, *requirements)
61
+ spec = Gem.loaded_specs[name]
62
+
63
+ return false if spec.nil?
64
+
65
+ Gem::Requirement.new(*requirements).satisfied_by?(spec.version)
66
+ end
67
+
68
+ # @return [Aikido::Zen::Config] the agent configuration.
69
+ def self.config
70
+ @config ||= Config.new
71
+ end
72
+
73
+ # @return [Aikido::Zen::RuntimeSettings] the firewall configuration sourced
74
+ # from your Aikido dashboard. This is periodically polled for updates.
75
+ def self.runtime_settings
76
+ @runtime_settings ||= RuntimeSettings.new
77
+ end
78
+
79
+ def self.runtime_settings=(settings)
80
+ @runtime_settings = settings
81
+ end
82
+
83
+ # @return [Boolean] whether the Aikido agent is currently blocking requests.
84
+ # Blocking mode is configured at startup and can be controlled through the
85
+ # Aikido dashboard at runtime.
86
+ def self.blocking_mode?
87
+ blocking_mode = runtime_settings.blocking_mode
88
+ return blocking_mode unless blocking_mode.nil?
89
+
90
+ config.blocking_mode
91
+ end
92
+
93
+ # Gets information about the current system configuration, which is sent to
94
+ # the server along with any events.
95
+ def self.system_info
96
+ @system_info ||= SystemInfo.new
97
+ end
98
+
99
+ # Manages runtime metrics extracted from your app, which are uploaded to the
100
+ # Aikido servers if configured to do so.
101
+ def self.collector
102
+ @collector ||= Collector.new
103
+ end
104
+
105
+ # Gets the current context object that holds all information about the
106
+ # current request.
107
+ #
108
+ # @return [Aikido::Zen::Context, nil]
109
+ def self.current_context
110
+ Thread.current[:_aikido_current_context_]
111
+ end
112
+
113
+ # Sets the current context object that holds all information about the
114
+ # current request, or +nil+ to clear the current context.
115
+ #
116
+ # @param context [Aikido::Zen::Context, nil]
117
+ # @return [Aikido::Zen::Context, nil]
118
+ def self.current_context=(context)
119
+ Thread.current[:_aikido_current_context_] = context
120
+ end
121
+
122
+ # Track statistics about an HTTP request the app is handling.
123
+ #
124
+ # @param request [Aikido::Zen::Request]
125
+ # @return [void]
126
+ def self.track_request(request)
127
+ collector.track_request
128
+ end
129
+
130
+ # Track statistics about an attack wave the app is handling.
131
+ #
132
+ # @param attack_wave [Aikido::Zen::Events::AttackWave]
133
+ # @return [void]
134
+ def self.track_attack_wave(attack_wave)
135
+ collector.track_attack_wave(being_blocked: false)
136
+ end
137
+
138
+ # Track statistics about a route that the app has discovered.
139
+ #
140
+ # @param request [Aikido::Zen::Request]
141
+ # @return [void]
142
+ def self.track_discovered_route(request)
143
+ collector.track_route(request)
144
+ end
145
+
146
+ # Tracks a network connection made to an external service.
147
+ #
148
+ # @param connection [Aikido::Zen::OutboundConnection]
149
+ # @return [void]
150
+ def self.track_outbound(connection)
151
+ collector.track_outbound(connection)
152
+ end
153
+
154
+ # Track statistics about the result of a Sink's scan, and report it as
155
+ # an Attack if one is detected.
156
+ #
157
+ # @param scan [Aikido::Zen::Scan]
158
+ # @return [void]
159
+ # @raise [Aikido::Zen::UnderAttackError] if the scan detected an Attack
160
+ # and blocking_mode is enabled.
161
+ def self.track_scan(scan)
162
+ collector.track_scan(scan)
163
+ agent.handle_attack(scan.attack) if scan.attack?
164
+ end
165
+
166
+ # Track the user making the current request.
167
+ #
168
+ # @param (see Aikido::Zen.Actor)
169
+ # @return [void]
170
+ def self.track_user(user)
171
+ return if config.disabled?
172
+
173
+ if (actor = Aikido::Zen::Actor(user))
174
+ collector.track_user(actor)
175
+ current_context.request.actor = actor if current_context
176
+ else
177
+ config.logger.warn(format(<<~LOG, obj: user))
178
+ Incompatible object sent to track_user: %<obj>p
179
+
180
+ The object must either implement #to_aikido_actor, or be a Hash with
181
+ an :id (or "id") and, optionally, a :name (or "name") key.
182
+ LOG
183
+ end
184
+ end
185
+
186
+ # Align with other Zen implementations, while keeping internal consistency.
187
+ class << self
188
+ alias_method :set_user, :track_user
189
+ end
190
+
191
+ # Marks that the Zen middleware was installed properly
192
+ # @return void
193
+ def self.middleware_installed!
194
+ collector.middleware_installed!
195
+ end
196
+
197
+ # @return [Aikido::Zen::AttackWave::Detector] the attack wave detector.
198
+ def self.attack_wave_detector
199
+ @attack_wave_detector ||= AttackWave::Detector.new
200
+ end
201
+
202
+ # @!visibility private
203
+ # Load all sources.
204
+ #
205
+ # @return [Boolean] true if any sources were loaded
206
+ def self.load_sources!
207
+ if Aikido::Zen.satisfy("rails", ">= 7.0")
208
+ require_relative "zen/rails_engine"
209
+
210
+ return true
211
+ end
212
+
213
+ false
214
+ end
215
+
216
+ # @!visibility private
217
+ # Load all sinks.
218
+ #
219
+ # @return [Boolean] true if any sinks were loaded
220
+ def self.load_sinks!
221
+ require_relative "zen/sinks"
222
+
223
+ !Aikido::Zen::Sinks.registry.empty?
224
+ end
225
+
226
+ # @!visibility private
227
+ # Stop any background threads.
228
+ def self.stop!
229
+ @agent&.stop!
230
+ @detached_agent_server&.stop!
231
+ end
232
+
233
+ # @!visibility private
234
+ # Starts the background agent if it has not been started yet.
235
+ def self.agent
236
+ @agent ||= Agent.start
237
+ end
238
+
239
+ def self.detached_agent
240
+ @detached_agent ||= DetachedAgent::Agent.new
241
+ end
242
+
243
+ def self.detached_agent_server
244
+ @detached_agent_server ||= DetachedAgent::Server.start
245
+ end
246
+
247
+ class << self
248
+ # `agent` and `detached_agent` are started on the first method call.
249
+ # A mutex controls thread execution to prevent multiple attempts.
250
+ LOCK = Mutex.new
251
+
252
+ def start!
253
+ return unless start?
254
+
255
+ @pid = Process.pid
256
+
257
+ LOCK.synchronize do
258
+ agent
259
+ detached_agent_server
260
+ end
261
+ end
262
+
263
+ def start?
264
+ !config.api_token.nil? ||
265
+ config.blocking_mode? ||
266
+ config.debugging?
267
+ end
268
+
269
+ def check_and_handle_fork
270
+ handle_fork if forked?
271
+ end
272
+
273
+ def forked?
274
+ pid_changed = Process.pid != @pid
275
+ @pid = Process.pid
276
+ pid_changed
277
+ end
278
+
279
+ def handle_fork
280
+ @detached_agent&.handle_fork
281
+ end
282
+ end
283
+
284
+ # @!visibility private
285
+ # Returns the stack trace trimmed to where execution last entered Zen.
286
+ #
287
+ # @return [String]
288
+ def self.clean_stack_trace
289
+ stack_trace = caller_locations
290
+
291
+ spec = Gem.loaded_specs["aikido-zen"]
292
+
293
+ # Only trim stack frames from .../lib/aikido/zen/ in the aikido-zen gem,
294
+ # so calls in sample apps are preserved.
295
+ lib_path_start = File.expand_path(File.join(spec.full_gem_path, "lib", "aikido", "zen")) + File::SEPARATOR
296
+
297
+ index = stack_trace.index { |frame| !File.expand_path(frame.path).start_with?(lib_path_start) }
298
+ stack_trace = stack_trace[index..] if index
299
+
300
+ stack_trace.map(&:to_s).join("\n")
301
+ end
302
+ end
303
+ end
data/lib/aikido-zen.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "aikido/zen"
@@ -0,0 +1,4 @@
1
+ LICENSE
2
+ *.gemspec
3
+ /lib/*.rb
4
+ *.gem
@@ -0,0 +1,11 @@
1
+ # Security Placeholder
2
+
3
+ This gem has been published by [Aikido Security](https://aikido.dev) to help prevent supply chain attacks and protect the integrity of the Ruby ecosystem.
4
+
5
+ It is **not intended for direct use** and contains no functional code.
6
+
7
+ If you are looking for the actual library, please use [`aikido-zen`](https://rubygems.org/gems/aikido-zen).
8
+
9
+ ---
10
+
11
+ This package exists solely for security purposes. For more information, visit [aikido.dev](https://aikido.dev).
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake"
4
+ require "rake/clean"
5
+ require "rubygems/package"
6
+ require "fileutils"
7
+
8
+ GEM_NAMES = %w[aikido]
9
+
10
+ # Clean up created files
11
+ CLEAN.include("LICENSE")
12
+ CLEAN.include(*GEM_NAMES.map { |name| "#{name}.gemspec" })
13
+ CLEAN.include(*GEM_NAMES.map { |name| "lib/#{name}.rb" })
14
+ CLOBBER.include(*GEM_NAMES.map { |name| "#{name}-*.gem" })
15
+
16
+ namespace :build do
17
+ GEM_NAMES.each do |gem_name|
18
+ file "LICENSE" => ["../LICENSE"] do
19
+ FileUtils.cp("../LICENSE", "LICENSE")
20
+ puts "Copied LICENSE"
21
+ end
22
+
23
+ entry_point_path = "lib/#{gem_name}.rb"
24
+
25
+ # Generate the entry point file from template if needed
26
+ file entry_point_path => ["lib/placeholder.rb.template"] do
27
+ template = File.read("lib/placeholder.rb.template")
28
+ content = template.gsub("@GEM_NAME", gem_name)
29
+ File.write(entry_point_path, content)
30
+ puts "Generated #{entry_point_path}"
31
+ end
32
+
33
+ gemspec_path = "#{gem_name}.gemspec"
34
+
35
+ # Generate gemspec file from template if needed
36
+ file gemspec_path => ["placeholder.gemspec.template"] do
37
+ template = File.read("placeholder.gemspec.template")
38
+ content = template.gsub("@GEM_NAME", gem_name)
39
+ File.write(gemspec_path, content)
40
+ puts "Generated #{gemspec_path}"
41
+ end
42
+
43
+ desc "Build the #{gem_name} gem"
44
+ task gem_name => [entry_point_path, gemspec_path, "LICENSE"] do
45
+ gemspec = Gem::Specification.load(gemspec_path)
46
+ raise "Failed to load gemspec: #{gemspec_path}" unless gemspec
47
+
48
+ gem_path = Gem::Package.build(gemspec)
49
+ puts "Built #{gem_path}"
50
+ end
51
+ end
52
+
53
+ desc "Build all gems"
54
+ task all: GEM_NAMES.map { |gem_name| "build:#{gem_name}" }
55
+ end
56
+
57
+ namespace :release do
58
+ GEM_NAMES.each do |gem_name|
59
+ gemspec_path = "#{gem_name}.gemspec"
60
+
61
+ desc "Build and publish the #{gem_name} to RubyGems"
62
+ task gem_name => ["build:#{gem_name}"] do
63
+ gemspec = Gem::Specification.load(gemspec_path)
64
+ raise "Failed to load gemspec: #{gemspec_path}" unless gemspec
65
+
66
+ gem_path = "#{gemspec.name}-#{gemspec.version}.gem"
67
+
68
+ puts "Publishing #{gem_path} to RubyGem..."
69
+ sh "gem push #{gem_path}"
70
+ end
71
+ end
72
+
73
+ desc "Build and publish all gems to RubyGems"
74
+ task all: GEM_NAMES.map { |gem_name| "release:#{gem_name}" }
75
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ raise LoadError, "This gem has been published by Aikido Security to help prevent supply chain attacks. It is not intended for direct use. Please use 'aikido-zen' instead."
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "@GEM_NAME"
5
+ spec.version = "0.0.2"
6
+ spec.authors = ["Aikido Security"]
7
+ spec.email = ["dev-admin@aikido.dev"]
8
+ spec.summary = "Security placeholder for 'aikido-zen'."
9
+ spec.description = "This gem has been published by Aikido Security to help prevent supply chain attacks. It is not intended for direct use. Please use 'aikido-zen' instead."
10
+ spec.homepage = "https://aikido.dev/zen"
11
+ spec.license = "AGPL-3.0-or-later"
12
+
13
+ spec.required_ruby_version = ">= 2.3"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.com/aikidosec/firewall-ruby"
17
+
18
+ spec.files = ["lib/@GEM_NAME.rb", "README.md", "LICENSE"]
19
+ spec.require_paths = ["lib"]
20
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "timeout"
5
+ require_relative "wrk"
6
+
7
+ SERVER_PIDS = {}
8
+ PORT_PROTECTED = 3001
9
+ PORT_UNPROTECTED = 3002
10
+
11
+ def stop_servers
12
+ SERVER_PIDS.each { |_, pid| Process.kill("TERM", pid) }
13
+ SERVER_PIDS.clear
14
+ end
15
+
16
+ def boot_server(dir, port:, env: {})
17
+ env["RAILS_MIN_THREADS"] = NUMBER_OF_THREADS
18
+ env["RAILS_MAX_THREADS"] = NUMBER_OF_THREADS
19
+ env["PORT"] = port.to_s
20
+ env["SECRET_KEY_BASE"] = rand(36**64).to_s(36)
21
+
22
+ Dir.chdir(dir) do
23
+ SERVER_PIDS[port] = Process.spawn(
24
+ env,
25
+ "rails", "server", "--pid", "#{Dir.pwd}/tmp/pids/server.#{port}.pid", "-e", "production",
26
+ out: "/dev/null"
27
+ )
28
+ rescue
29
+ SERVER_PIDS.delete(port)
30
+ end
31
+ end
32
+
33
+ def port_open?(port, timeout: 1)
34
+ Timeout.timeout(timeout) do
35
+ TCPSocket.new("127.0.0.1", port).close
36
+ true
37
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
38
+ false
39
+ end
40
+ rescue Timeout::Error
41
+ false
42
+ end
43
+
44
+ def wait_for_servers
45
+ ports = SERVER_PIDS.keys
46
+
47
+ Timeout.timeout(10) do
48
+ ports.reject! { |port| port_open?(port) } while ports.any?
49
+ end
50
+ rescue Timeout::Error
51
+ raise "Could not reach ports: #{ports.join(", ")}"
52
+ end
53
+
54
+ Pathname.glob("sample_apps/*").select(&:directory?).each do |dir|
55
+ namespace :bench do
56
+ namespace dir.basename.to_s do
57
+ desc "Run WRK benchmarks for the #{dir.basename} sample app"
58
+ task wrk_run: [:boot_protected_app, :boot_unprotected_app] do
59
+ throughput_decrease_limit_perc = 25
60
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.0.0") && Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.1.0")
61
+ # add higher limit for ruby 3.0
62
+ throughput_decrease_limit_perc = 35
63
+ end
64
+
65
+ wait_for_servers
66
+ run_benchmark(
67
+ route_zen: "http://localhost:#{PORT_PROTECTED}/benchmark", # Application with Zen
68
+ route_no_zen: "http://localhost:#{PORT_UNPROTECTED}/benchmark", # Application without Zen
69
+ description: "An empty route (1ms simulated delay)",
70
+ throughput_decrease_limit_perc: throughput_decrease_limit_perc,
71
+ latency_increase_limit_ms: 200
72
+ )
73
+ ensure
74
+ stop_servers
75
+ end
76
+
77
+ desc "Run K6 benchmarks for the #{dir.basename} sample app"
78
+ task k6_run: [:boot_protected_app, :boot_unprotected_app] do
79
+ wait_for_servers
80
+ Dir.chdir("benchmarks") { sh "k6 run #{dir.basename}.js" }
81
+ ensure
82
+ stop_servers
83
+ end
84
+
85
+ task :boot_protected_app do
86
+ boot_server(dir, port: PORT_PROTECTED)
87
+ end
88
+
89
+ task :boot_unprotected_app do
90
+ boot_server(dir, port: PORT_UNPROTECTED, env: {"AIKIDO_DISABLE" => "true"})
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ffi"
4
+ require "open-uri"
5
+ require "rubygems/package_task"
6
+
7
+ require_relative "../lib/aikido/zen/version"
8
+
9
+ class LibZen
10
+ attr_reader :platform, :suffix, :artifact
11
+
12
+ def initialize(platform_suffix, artifact = nil)
13
+ platform, suffix = platform_suffix.split(".", 2)
14
+ @platform = Gem::Platform.new(platform)
15
+ @suffix = suffix
16
+ @artifact = artifact
17
+ end
18
+
19
+ def version
20
+ "v#{Aikido::Zen::LIBZEN_VERSION}"
21
+ end
22
+
23
+ def path
24
+ "lib/aikido/zen/libzen-#{version}-#{platform}.#{suffix}"
25
+ end
26
+
27
+ def url
28
+ File.join("https://github.com/AikidoSec/zen-internals/releases/download", version, artifact)
29
+ end
30
+
31
+ def gemspec(source = Bundler.load_gemspec("aikido-zen.gemspec"))
32
+ return @spec if defined?(@spec)
33
+
34
+ @spec = source.dup
35
+ @spec.platform = platform
36
+ @spec.files << path
37
+ @spec
38
+ end
39
+
40
+ def gem_path
41
+ "pkg/#{gemspec.name}-#{gemspec.version}-#{gemspec.platform}.gem"
42
+ end
43
+
44
+ def resolvable?
45
+ downloadable? || File.exist?(path)
46
+ end
47
+
48
+ def downloadable?
49
+ !artifact.nil?
50
+ end
51
+
52
+ def download
53
+ puts "Downloading #{path}"
54
+ File.open(path, "wb") { |file| FileUtils.copy_stream(URI(url).open("rb"), file) }
55
+ end
56
+
57
+ def verify
58
+ expected = URI(url + ".sha256sum").read.split(/\s+/).first
59
+ actual = Digest::SHA256.file(path).to_s
60
+
61
+ if expected != actual
62
+ abort "Checksum verification failed for #{path}: expected #{expected}, but got #{actual}"
63
+ end
64
+ end
65
+
66
+ def namespace
67
+ platform.to_s
68
+ end
69
+
70
+ def pkg_dir
71
+ File.dirname(gem_path)
72
+ end
73
+ end
74
+
75
+ LIBZENS = [
76
+ LibZen.new("arm64-darwin.dylib", "libzen_internals_aarch64-apple-darwin.dylib"),
77
+ LibZen.new("arm64-linux.so", "libzen_internals_aarch64-unknown-linux-gnu.so"),
78
+ LibZen.new("arm64-linux-musl.so", "libzen_internals_aarch64-unknown-linux-musl.so"),
79
+ LibZen.new("aarch64-linux.so", "libzen_internals_aarch64-unknown-linux-gnu.so"),
80
+ LibZen.new("x86_64-darwin.dylib", "libzen_internals_x86_64-apple-darwin.dylib"),
81
+ LibZen.new("x86_64-linux.so", "libzen_internals_x86_64-unknown-linux-gnu.so"),
82
+ LibZen.new("x86_64-linux-musl.so", "libzen_internals_x86_64-unknown-linux-musl.so"),
83
+ LibZen.new("x86_64-mingw64.dll", "libzen_internals_x86_64-pc-windows-gnu.dll"),
84
+ # Not officially supported, but used during testing:
85
+ LibZen.new("x86_64-freebsd.so"),
86
+ LibZen.new("x86_64-solaris.so")
87
+ ].filter(&:resolvable?)
88
+
89
+ namespace :libzen do
90
+ LIBZENS.each do |lib|
91
+ desc "Download libzen for #{lib.platform} if necessary"
92
+ task(lib.namespace => lib.path)
93
+
94
+ if lib.downloadable?
95
+ file(lib.path) do
96
+ lib.download
97
+ lib.verify
98
+ end
99
+ CLEAN.include(lib.path)
100
+ end
101
+
102
+ directory lib.pkg_dir
103
+ CLOBBER.include(lib.pkg_dir)
104
+
105
+ file(lib.gem_path => [lib.path, lib.pkg_dir]) do
106
+ path = Gem::Package.build(lib.gemspec)
107
+ mv path, lib.pkg_dir
108
+ end
109
+ CLOBBER.include(lib.pkg_dir)
110
+
111
+ task "#{lib.namespace}:release" => [lib.gem_path, "release:guard_clean"] do
112
+ sh "gem", "push", lib.gem_path
113
+ end
114
+ end
115
+
116
+ desc "Build all the native gems"
117
+ task gems: LIBZENS.map(&:gem_path)
118
+
119
+ desc "Push all the native gems to RubyGems"
120
+ task release: LIBZENS.map { |lib| "#{lib.namespace}:release" }
121
+
122
+ desc "Download the libzen pre-built library for all platforms"
123
+ task "download:all" => LIBZENS.map(&:path)
124
+
125
+ desc "Downloads the libzen library for the current platform"
126
+ task "download:current" do
127
+ platform = Gem::Platform.local.dup
128
+ platform.version = nil unless Rake::Task.task_defined?("libzen:#{platform}")
129
+
130
+ # Invoke the most specific task
131
+ Rake::Task["libzen:#{platform}"].invoke
132
+ end
133
+ end