aikido-zen 1.0.2.beta.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.
- checksums.yaml +7 -0
- data/.aikido +6 -0
- data/.ruby-version +1 -0
- data/.simplecov +26 -0
- data/.standard.yml +3 -0
- data/LICENSE +674 -0
- data/README.md +146 -0
- data/Rakefile +67 -0
- data/benchmarks/README.md +23 -0
- data/benchmarks/rails7.1_sql_injection.js +70 -0
- data/docs/banner.svg +202 -0
- data/docs/config.md +125 -0
- data/docs/proxy.md +10 -0
- data/docs/rails.md +114 -0
- data/lib/aikido/zen/actor.rb +116 -0
- data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
- data/lib/aikido/zen/agent.rb +179 -0
- data/lib/aikido/zen/api_client.rb +145 -0
- data/lib/aikido/zen/attack.rb +207 -0
- data/lib/aikido/zen/background_worker.rb +52 -0
- data/lib/aikido/zen/capped_collections.rb +68 -0
- data/lib/aikido/zen/collector/hosts.rb +15 -0
- data/lib/aikido/zen/collector/routes.rb +66 -0
- data/lib/aikido/zen/collector/sink_stats.rb +95 -0
- data/lib/aikido/zen/collector/stats.rb +111 -0
- data/lib/aikido/zen/collector/users.rb +30 -0
- data/lib/aikido/zen/collector.rb +144 -0
- data/lib/aikido/zen/config.rb +282 -0
- data/lib/aikido/zen/context/rack_request.rb +24 -0
- data/lib/aikido/zen/context/rails_request.rb +44 -0
- data/lib/aikido/zen/context.rb +112 -0
- data/lib/aikido/zen/detached_agent/agent.rb +78 -0
- data/lib/aikido/zen/detached_agent/front_object.rb +37 -0
- data/lib/aikido/zen/detached_agent/server.rb +78 -0
- data/lib/aikido/zen/detached_agent.rb +2 -0
- data/lib/aikido/zen/errors.rb +107 -0
- data/lib/aikido/zen/event.rb +71 -0
- data/lib/aikido/zen/internals.rb +103 -0
- data/lib/aikido/zen/libzen-v0.1.39-aarch64-linux.so +0 -0
- data/lib/aikido/zen/middleware/check_allowed_addresses.rb +26 -0
- data/lib/aikido/zen/middleware/middleware.rb +11 -0
- data/lib/aikido/zen/middleware/rack_throttler.rb +48 -0
- data/lib/aikido/zen/middleware/request_tracker.rb +192 -0
- data/lib/aikido/zen/middleware/set_context.rb +26 -0
- data/lib/aikido/zen/outbound_connection.rb +45 -0
- data/lib/aikido/zen/outbound_connection_monitor.rb +23 -0
- data/lib/aikido/zen/package.rb +22 -0
- data/lib/aikido/zen/payload.rb +50 -0
- data/lib/aikido/zen/rails_engine.rb +56 -0
- data/lib/aikido/zen/rate_limiter/breaker.rb +61 -0
- data/lib/aikido/zen/rate_limiter/bucket.rb +76 -0
- data/lib/aikido/zen/rate_limiter/result.rb +31 -0
- data/lib/aikido/zen/rate_limiter.rb +50 -0
- data/lib/aikido/zen/request/heuristic_router.rb +115 -0
- data/lib/aikido/zen/request/rails_router.rb +77 -0
- data/lib/aikido/zen/request/schema/auth_discovery.rb +86 -0
- data/lib/aikido/zen/request/schema/auth_schemas.rb +54 -0
- data/lib/aikido/zen/request/schema/builder.rb +121 -0
- data/lib/aikido/zen/request/schema/definition.rb +107 -0
- data/lib/aikido/zen/request/schema/empty_schema.rb +28 -0
- data/lib/aikido/zen/request/schema.rb +87 -0
- data/lib/aikido/zen/request.rb +122 -0
- data/lib/aikido/zen/route.rb +39 -0
- data/lib/aikido/zen/runtime_settings/endpoints.rb +49 -0
- data/lib/aikido/zen/runtime_settings/ip_set.rb +36 -0
- data/lib/aikido/zen/runtime_settings/protection_settings.rb +62 -0
- data/lib/aikido/zen/runtime_settings/rate_limit_settings.rb +47 -0
- data/lib/aikido/zen/runtime_settings.rb +65 -0
- data/lib/aikido/zen/scan.rb +75 -0
- data/lib/aikido/zen/scanners/path_traversal/helpers.rb +65 -0
- data/lib/aikido/zen/scanners/path_traversal_scanner.rb +63 -0
- data/lib/aikido/zen/scanners/shell_injection/helpers.rb +159 -0
- data/lib/aikido/zen/scanners/shell_injection_scanner.rb +64 -0
- data/lib/aikido/zen/scanners/sql_injection_scanner.rb +93 -0
- data/lib/aikido/zen/scanners/ssrf/dns_lookups.rb +27 -0
- data/lib/aikido/zen/scanners/ssrf/private_ip_checker.rb +97 -0
- data/lib/aikido/zen/scanners/ssrf_scanner.rb +265 -0
- data/lib/aikido/zen/scanners/stored_ssrf_scanner.rb +49 -0
- data/lib/aikido/zen/scanners.rb +7 -0
- data/lib/aikido/zen/sink.rb +118 -0
- data/lib/aikido/zen/sinks/action_controller.rb +83 -0
- data/lib/aikido/zen/sinks/async_http.rb +80 -0
- data/lib/aikido/zen/sinks/curb.rb +113 -0
- data/lib/aikido/zen/sinks/em_http.rb +83 -0
- data/lib/aikido/zen/sinks/excon.rb +118 -0
- data/lib/aikido/zen/sinks/file.rb +112 -0
- data/lib/aikido/zen/sinks/http.rb +93 -0
- data/lib/aikido/zen/sinks/httpclient.rb +95 -0
- data/lib/aikido/zen/sinks/httpx.rb +78 -0
- data/lib/aikido/zen/sinks/kernel.rb +33 -0
- data/lib/aikido/zen/sinks/mysql2.rb +31 -0
- data/lib/aikido/zen/sinks/net_http.rb +101 -0
- data/lib/aikido/zen/sinks/patron.rb +103 -0
- data/lib/aikido/zen/sinks/pg.rb +72 -0
- data/lib/aikido/zen/sinks/resolv.rb +62 -0
- data/lib/aikido/zen/sinks/socket.rb +78 -0
- data/lib/aikido/zen/sinks/sqlite3.rb +46 -0
- data/lib/aikido/zen/sinks/trilogy.rb +31 -0
- data/lib/aikido/zen/sinks/typhoeus.rb +78 -0
- data/lib/aikido/zen/sinks.rb +36 -0
- data/lib/aikido/zen/sinks_dsl.rb +250 -0
- data/lib/aikido/zen/synchronizable.rb +24 -0
- data/lib/aikido/zen/system_info.rb +84 -0
- data/lib/aikido/zen/version.rb +10 -0
- data/lib/aikido/zen/worker.rb +87 -0
- data/lib/aikido/zen.rb +246 -0
- data/lib/aikido-zen.rb +3 -0
- data/placeholder/.gitignore +4 -0
- data/placeholder/README.md +11 -0
- data/placeholder/Rakefile +75 -0
- data/placeholder/lib/placeholder.rb.template +3 -0
- data/placeholder/placeholder.gemspec.template +20 -0
- data/tasklib/bench.rake +94 -0
- data/tasklib/libzen.rake +133 -0
- data/tasklib/wrk.rb +88 -0
- metadata +205 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aikido::Zen
|
4
|
+
module Sinks
|
5
|
+
module Trilogy
|
6
|
+
SINK = Sinks.add("trilogy", scanners: [Scanners::SQLInjectionScanner])
|
7
|
+
|
8
|
+
module Helpers
|
9
|
+
def self.scan(query, operation)
|
10
|
+
SINK.scan(query: query, dialect: :mysql, operation: operation)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.load_sinks!
|
15
|
+
if Aikido::Zen.satisfy "trilogy", ">= 2.0"
|
16
|
+
require "trilogy"
|
17
|
+
|
18
|
+
::Trilogy.class_eval do
|
19
|
+
extend Sinks::DSL
|
20
|
+
|
21
|
+
sink_before :query do |query|
|
22
|
+
Helpers.scan(query, "query")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Aikido::Zen::Sinks::Trilogy.load_sinks!
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../sink"
|
4
|
+
require_relative "../outbound_connection_monitor"
|
5
|
+
|
6
|
+
module Aikido::Zen
|
7
|
+
module Sinks
|
8
|
+
module Typhoeus
|
9
|
+
SINK = Sinks.add("typhoeus", scanners: [
|
10
|
+
Aikido::Zen::Scanners::SSRFScanner,
|
11
|
+
Aikido::Zen::OutboundConnectionMonitor
|
12
|
+
])
|
13
|
+
|
14
|
+
before_callback = ->(request) {
|
15
|
+
wrapped_request = Aikido::Zen::Scanners::SSRFScanner::Request.new(
|
16
|
+
verb: request.options[:method],
|
17
|
+
uri: URI(request.url),
|
18
|
+
headers: request.options[:headers]
|
19
|
+
)
|
20
|
+
|
21
|
+
# Store the request information so the DNS sinks can pick it up.
|
22
|
+
if (context = Aikido::Zen.current_context)
|
23
|
+
prev_request = context["ssrf.request"]
|
24
|
+
context["ssrf.request"] = wrapped_request
|
25
|
+
end
|
26
|
+
|
27
|
+
SINK.scan(
|
28
|
+
connection: Aikido::Zen::OutboundConnection.from_uri(URI(request.base_url)),
|
29
|
+
request: wrapped_request,
|
30
|
+
operation: "request"
|
31
|
+
)
|
32
|
+
|
33
|
+
request.on_headers do |response|
|
34
|
+
context["ssrf.request"] = prev_request if context
|
35
|
+
|
36
|
+
Aikido::Zen::Scanners::SSRFScanner.track_redirects(
|
37
|
+
request: wrapped_request,
|
38
|
+
response: Aikido::Zen::Scanners::SSRFScanner::Response.new(
|
39
|
+
status: response.code,
|
40
|
+
headers: response.headers.to_h
|
41
|
+
)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# When Typhoeus is configured with followlocation: true, the redirect
|
46
|
+
# following happens between the on_headers and the on_complete callback,
|
47
|
+
# so we need this one to detect if the request resulted in an automatic
|
48
|
+
# redirect that was followed.
|
49
|
+
request.on_complete do |response|
|
50
|
+
break if response.effective_url == request.url
|
51
|
+
|
52
|
+
last_effective_request = Aikido::Zen::Scanners::SSRFScanner::Request.new(
|
53
|
+
verb: request.options[:method],
|
54
|
+
uri: URI(response.effective_url),
|
55
|
+
headers: request.options[:headers]
|
56
|
+
)
|
57
|
+
context["ssrf.request"] = last_effective_request if context
|
58
|
+
|
59
|
+
# In this case, we can't actually stop the request from happening, but
|
60
|
+
# we can scan again (now that we know another request happened), to
|
61
|
+
# stop the response from being exposed to the user. This downgrades
|
62
|
+
# the SSRF into a blind SSRF, which is better than doing nothing.
|
63
|
+
SINK.scan(
|
64
|
+
connection: Aikido::Zen::OutboundConnection.from_uri(URI(response.effective_url)),
|
65
|
+
request: last_effective_request,
|
66
|
+
operation: "request"
|
67
|
+
)
|
68
|
+
ensure
|
69
|
+
context["ssrf.request"] = nil if context
|
70
|
+
end
|
71
|
+
|
72
|
+
true
|
73
|
+
}
|
74
|
+
|
75
|
+
::Typhoeus.before.prepend(before_callback)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Code coverage is disabled in this file because it is environment-specific and
|
4
|
+
# not intended to be tested directly.
|
5
|
+
# :nocov:
|
6
|
+
|
7
|
+
require_relative "sink"
|
8
|
+
require_relative "sinks_dsl"
|
9
|
+
|
10
|
+
require_relative "sinks/action_controller" if defined?(::ActionController)
|
11
|
+
|
12
|
+
require_relative "sinks/kernel"
|
13
|
+
|
14
|
+
require_relative "sinks/file"
|
15
|
+
require_relative "sinks/socket"
|
16
|
+
require_relative "sinks/resolv"
|
17
|
+
require_relative "sinks/net_http"
|
18
|
+
|
19
|
+
# http.rb aims to support and is tested against Ruby 3.0+:
|
20
|
+
# https://github.com/httprb/http?tab=readme-ov-file#supported-ruby-versions
|
21
|
+
require_relative "sinks/http" if RUBY_VERSION >= "3.0"
|
22
|
+
|
23
|
+
require_relative "sinks/httpx"
|
24
|
+
require_relative "sinks/httpclient"
|
25
|
+
require_relative "sinks/excon"
|
26
|
+
require_relative "sinks/curb"
|
27
|
+
require_relative "sinks/patron"
|
28
|
+
require_relative "sinks/typhoeus" if defined?(::Typhoeus)
|
29
|
+
require_relative "sinks/async_http"
|
30
|
+
require_relative "sinks/em_http"
|
31
|
+
require_relative "sinks/mysql2"
|
32
|
+
require_relative "sinks/pg"
|
33
|
+
require_relative "sinks/sqlite3"
|
34
|
+
require_relative "sinks/trilogy"
|
35
|
+
|
36
|
+
# :nocov:
|
@@ -0,0 +1,250 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aikido::Zen
|
4
|
+
module Sinks
|
5
|
+
module DSL
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# In the context of `Aikido::Zen::Sinks::DSL`, the terms safe and presafe
|
9
|
+
# are defined as follows:
|
10
|
+
#
|
11
|
+
# safe: the desired state for a sink, particularly with respect to rescue.
|
12
|
+
#
|
13
|
+
# A sink is considered safe when unintended errors in the sink are handled,
|
14
|
+
# and-so are prevented from disrupting the operation of the original method
|
15
|
+
# (by raising unintended errors).
|
16
|
+
#
|
17
|
+
# presafe: the default state of a sink, particularly with respect to rescue.
|
18
|
+
#
|
19
|
+
# A sink is in the presafe state before and while unintended errors in the
|
20
|
+
# sink are not handled.
|
21
|
+
#
|
22
|
+
# Sink methods (like all methods) are in the presafe state when defined and
|
23
|
+
# become safe when unexpected errors cannot cause harm. The `safe` method
|
24
|
+
# is used to establish a safe state for the duration of the block executed.
|
25
|
+
# It is sometimes useful to be able to reestablish the presafe safe while
|
26
|
+
# inside a `safe` block; the `presafe` method allows this.
|
27
|
+
#
|
28
|
+
# Methods that contain the term presafe in their name should be used with
|
29
|
+
# appropriate care and understanding.
|
30
|
+
#
|
31
|
+
# IMPORTANT: All sinks should be safe!
|
32
|
+
#
|
33
|
+
# While this DSL proves useful for defining safe sink methods that follow
|
34
|
+
# common patterns, there are exceptions. It is always possible to define
|
35
|
+
# sink methods without using this DSL, but this should only be done when
|
36
|
+
# absolutely necessary. The sink methods defined using this DSL are safe,
|
37
|
+
# unless explicitly declared presafe.
|
38
|
+
#
|
39
|
+
# IMPORTANT: No sinks should be presafe in production!
|
40
|
+
#
|
41
|
+
# We are all responsible for ensuring that the sinks we implement are safe
|
42
|
+
# for production use. This DSL is only here to assist, by taking care of
|
43
|
+
# delicate edge cases and reducing the space for errors.
|
44
|
+
#
|
45
|
+
# When writing sink methods manually, some principles should be considered,
|
46
|
+
# to ensure safety for production:
|
47
|
+
#
|
48
|
+
# 1. Sink methods should ensure that the original method is always called,
|
49
|
+
# passing all parameters (positional, keyword, and block) exactly as they
|
50
|
+
# were passed to the original method, and return the result returned by
|
51
|
+
# the original method exactly as it was returned by the original method.
|
52
|
+
# (Unless intervention is required.)
|
53
|
+
#
|
54
|
+
# 2. Sink methods should not predict the signature of the original method,
|
55
|
+
# and-so restrict it from varying. The original method implementation is
|
56
|
+
# the sole and ultimate reference for its own behavior. We are observers
|
57
|
+
# (unless intervention is required).
|
58
|
+
#
|
59
|
+
# 3. Unexpected errors that are encountered in sink methods should not be
|
60
|
+
# capable of interfering with or preventing the normal operation of the
|
61
|
+
# original method. This includes but is not limited to exceptions that
|
62
|
+
# that may be raised when the sink method is called. Safe sink methods
|
63
|
+
# should return control to their caller (unless intervention is required).
|
64
|
+
#
|
65
|
+
# These are the guidelines adhered to by the `Aikido::Zen::Sinks::DSL`.
|
66
|
+
|
67
|
+
# The error with an original error as its cause to re-raise in `safe`.
|
68
|
+
class PresafeError < StandardError
|
69
|
+
end
|
70
|
+
|
71
|
+
# Safely execute the given block
|
72
|
+
#
|
73
|
+
# All standard errors are suppressed except `Aikido::Zen::UnderAttackError`s.
|
74
|
+
# This ensures that unexpected errors do not interrupt the execution of the
|
75
|
+
# original method, while all detected attacks are raised.
|
76
|
+
#
|
77
|
+
# When an error is wrapped in `PresafeError` the original error is reraised.
|
78
|
+
#
|
79
|
+
# @yield the block to execute
|
80
|
+
def safe
|
81
|
+
yield
|
82
|
+
rescue Aikido::Zen::UnderAttackError
|
83
|
+
raise
|
84
|
+
rescue PresafeError => err
|
85
|
+
raise err.cause
|
86
|
+
rescue => err
|
87
|
+
Aikido::Zen.config.logger.debug("[safe] #{err.class}: #{err.message}")
|
88
|
+
end
|
89
|
+
|
90
|
+
# Presafely execute the given block
|
91
|
+
#
|
92
|
+
# Safely wrap standard errors in `PresafeError` so that the original error is
|
93
|
+
# reraised when rescued in `safe`.
|
94
|
+
#
|
95
|
+
# @yield the block to execute
|
96
|
+
def presafe
|
97
|
+
yield
|
98
|
+
rescue => err
|
99
|
+
raise PresafeError, cause: err
|
100
|
+
end
|
101
|
+
|
102
|
+
# Define a method `method_name` that presafely executes the given block before
|
103
|
+
# the original method.
|
104
|
+
#
|
105
|
+
# @param method_name [Symbol, String] the name of the method to define
|
106
|
+
# @yield the block to execute before the original method
|
107
|
+
# @yieldparam args [Array] the positional arguments passed to the original method
|
108
|
+
# @yieldparam kwargs [Hash] the keyword arguments passed to the original method
|
109
|
+
#
|
110
|
+
# @return [void]
|
111
|
+
def presafe_sink_before(method_name, &block)
|
112
|
+
raise ArgumentError, "block required" unless block
|
113
|
+
|
114
|
+
original = instance_method(method_name)
|
115
|
+
|
116
|
+
define_method(method_name) do |*args, **kwargs, &blk|
|
117
|
+
instance_exec(*args, **kwargs, &block)
|
118
|
+
original.bind_call(self, *args, **kwargs, &blk)
|
119
|
+
end
|
120
|
+
rescue NameError
|
121
|
+
Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
|
122
|
+
end
|
123
|
+
|
124
|
+
# Define a method `method_name` that safely executes the given block before
|
125
|
+
# the original method.
|
126
|
+
#
|
127
|
+
# @param method_name [Symbol, String] the name of the method to define
|
128
|
+
# @yield the block to execute before the original method
|
129
|
+
# @yieldparam args [Array] the positional arguments passed to the original method
|
130
|
+
# @yieldparam kwargs [Hash] the keyword arguments passed to the original method
|
131
|
+
#
|
132
|
+
# @return [void]
|
133
|
+
#
|
134
|
+
# @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
|
135
|
+
def sink_before(method_name, &block)
|
136
|
+
raise ArgumentError, "block required" unless block
|
137
|
+
|
138
|
+
presafe_sink_before(method_name) do |*args, **kwargs|
|
139
|
+
DSL.safe do
|
140
|
+
instance_exec(*args, **kwargs, &block)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Define a method `method_name` that presafely executes the given block after
|
146
|
+
# the original method.
|
147
|
+
#
|
148
|
+
# @param method_name [Symbol, String] the name of the method to define
|
149
|
+
# @yield the block to execute after the original method
|
150
|
+
# @yieldparam result [Object] the result returned by the original method
|
151
|
+
# @yieldparam args [Array] the positional arguments passed to the original method
|
152
|
+
# @yieldparam kwargs [Hash] the keyword arguments passed to the original method
|
153
|
+
#
|
154
|
+
# @return [void]
|
155
|
+
def presafe_sink_after(method_name, &block)
|
156
|
+
raise ArgumentError, "block required" unless block
|
157
|
+
|
158
|
+
original = instance_method(method_name)
|
159
|
+
|
160
|
+
define_method(method_name) do |*args, **kwargs, &blk|
|
161
|
+
result = original.bind_call(self, *args, **kwargs, &blk)
|
162
|
+
instance_exec(result, *args, **kwargs, &block)
|
163
|
+
result
|
164
|
+
end
|
165
|
+
rescue NameError
|
166
|
+
Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
|
167
|
+
end
|
168
|
+
|
169
|
+
# Define a method `method_name` that safely executes the given block after
|
170
|
+
# the original method.
|
171
|
+
#
|
172
|
+
# @param method_name [Symbol, String] the name of the method to define
|
173
|
+
# @yield the block to execute after the original method
|
174
|
+
# @yieldparam result [Object] the result returned by the original method
|
175
|
+
# @yieldparam args [Array] the positional arguments passed to the original method
|
176
|
+
# @yieldparam kwargs [Hash] the keyword arguments passed to the original method
|
177
|
+
#
|
178
|
+
# @return [void]
|
179
|
+
#
|
180
|
+
# @note the block is executed within `safe` to handle errors safely; the original method is executed outside of `safe` to preserve the original behavior
|
181
|
+
def sink_after(method_name, &block)
|
182
|
+
raise ArgumentError, "block required" unless block
|
183
|
+
|
184
|
+
presafe_sink_after(method_name) do |result, *args, **kwargs|
|
185
|
+
DSL.safe do
|
186
|
+
instance_exec(result, *args, **kwargs, &block)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Define a method `method_name` that presafely executes the given block around
|
192
|
+
# the original method.
|
193
|
+
#
|
194
|
+
# @param method_name [Symbol, String] the name of the method to define
|
195
|
+
# @yield the block to execute around the original method
|
196
|
+
# @yieldparam original_call [Proc] the proc that calls the original method
|
197
|
+
# @yieldparam args [Array] the positional arguments passed to the original method
|
198
|
+
# @yieldparam kwargs [Hash] the keyword arguments passed to the original method
|
199
|
+
#
|
200
|
+
# @return [void]
|
201
|
+
def presafe_sink_around(method_name, &block)
|
202
|
+
raise ArgumentError, "block required" unless block
|
203
|
+
|
204
|
+
original = instance_method(method_name)
|
205
|
+
|
206
|
+
define_method(method_name) do |*args, **kwargs, &blk|
|
207
|
+
result = nil
|
208
|
+
original_call = proc do
|
209
|
+
result = original.bind_call(self, *args, **kwargs, &blk)
|
210
|
+
end
|
211
|
+
instance_exec(original_call, *args, **kwargs, &block)
|
212
|
+
result
|
213
|
+
end
|
214
|
+
rescue NameError
|
215
|
+
Aikido::Zen.config.logger.warn("cannot wrap method `#{method_name}' for class `#{self}'")
|
216
|
+
end
|
217
|
+
|
218
|
+
# Define a method `method_name` that safely executes the given block around
|
219
|
+
# the original method.
|
220
|
+
#
|
221
|
+
# @param method_name [Symbol, String] the name of the method to define
|
222
|
+
# @yield the block to execute around the original method
|
223
|
+
# @yieldparam original_call [Proc] the proc that calls the original method
|
224
|
+
# @yieldparam args [Array] the positional arguments passed to the original method
|
225
|
+
# @yieldparam kwargs [Hash] the keyword arguments passed to the original method
|
226
|
+
#
|
227
|
+
# @return [void]
|
228
|
+
#
|
229
|
+
# @note the block is executed within `safe` to handle errors safely; the original method is executed within `presafe` to preserve the original behavior
|
230
|
+
# @note if the block does not call `original_call`, the original method is called automatically after the block is executed
|
231
|
+
def sink_around(method_name, &block)
|
232
|
+
raise ArgumentError, "block required" unless block
|
233
|
+
|
234
|
+
presafe_sink_around(method_name) do |presafe_original_call, *args, **kwargs|
|
235
|
+
original_called = false
|
236
|
+
original_call = proc do
|
237
|
+
original_called = true
|
238
|
+
DSL.presafe do
|
239
|
+
presafe_original_call.call
|
240
|
+
end
|
241
|
+
end
|
242
|
+
DSL.safe do
|
243
|
+
instance_exec(original_call, *args, **kwargs, &block)
|
244
|
+
end
|
245
|
+
presafe_original_call.call unless original_called
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aikido::Zen
|
4
|
+
# @!visibility private
|
5
|
+
#
|
6
|
+
# Provides the synchronization part of Concurrent's LockableObject, but allows
|
7
|
+
# objects to take keyword arguments as well.
|
8
|
+
#
|
9
|
+
# NOTE: This is meant to be prepennded.
|
10
|
+
module Synchronizable
|
11
|
+
def initialize(*, **)
|
12
|
+
@__lock__ = ::Mutex.new
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def synchronize
|
17
|
+
if @__lock__.owned?
|
18
|
+
yield
|
19
|
+
else
|
20
|
+
@__lock__.synchronize { yield }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "rubygems"
|
5
|
+
|
6
|
+
require_relative "package"
|
7
|
+
|
8
|
+
module Aikido::Zen
|
9
|
+
# Provides information about the currently running Agent.
|
10
|
+
class SystemInfo
|
11
|
+
def initialize(config = Aikido::Zen.config)
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
def attacks_block_requests?
|
16
|
+
!!@config.blocking_mode
|
17
|
+
end
|
18
|
+
|
19
|
+
def attacks_are_only_reported?
|
20
|
+
!attacks_block_requests?
|
21
|
+
end
|
22
|
+
|
23
|
+
def library_name
|
24
|
+
"firewall-ruby"
|
25
|
+
end
|
26
|
+
|
27
|
+
def library_version
|
28
|
+
VERSION
|
29
|
+
end
|
30
|
+
|
31
|
+
def platform_version
|
32
|
+
RUBY_VERSION
|
33
|
+
end
|
34
|
+
|
35
|
+
def hostname
|
36
|
+
@hostname ||= Socket.gethostname
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Array<Aikido::Zen::Package>] a list of loaded rubygems that are
|
40
|
+
# supported by Aikido (i.e. we have a Sink that scans the package for
|
41
|
+
# vulnerabilities and protects you).
|
42
|
+
def packages
|
43
|
+
@packages ||= Gem.loaded_specs
|
44
|
+
.map { |_, spec| Package.new(spec.name, spec.version) }
|
45
|
+
.select(&:supported?)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] the first non-loopback IPv4 address that we can use
|
49
|
+
# to identify this host. If the machine is solely identified by IPv6
|
50
|
+
# addresses, then this will instead return an IPv6 address.
|
51
|
+
def ip_address
|
52
|
+
@ip_address ||= Socket.ip_address_list
|
53
|
+
.reject { |ip| ip.ipv4_loopback? || ip.ipv6_loopback? || ip.unix? }
|
54
|
+
.min_by { |ip| ip.ipv4? ? 0 : 1 }
|
55
|
+
.ip_address
|
56
|
+
end
|
57
|
+
|
58
|
+
def os_name
|
59
|
+
Gem::Platform.local.os
|
60
|
+
end
|
61
|
+
|
62
|
+
def os_version
|
63
|
+
Gem::Platform.local.version || "unknown"
|
64
|
+
end
|
65
|
+
|
66
|
+
def as_json
|
67
|
+
{
|
68
|
+
dryMode: attacks_are_only_reported?,
|
69
|
+
library: library_name,
|
70
|
+
version: library_version,
|
71
|
+
hostname: hostname,
|
72
|
+
ipAddress: ip_address,
|
73
|
+
platform: {version: platform_version},
|
74
|
+
os: {name: os_name, version: os_version},
|
75
|
+
packages: packages.reduce({}) { |all, package| all.update(package.as_json) },
|
76
|
+
incompatiblePackages: {},
|
77
|
+
stack: [],
|
78
|
+
serverless: false,
|
79
|
+
nodeEnv: "",
|
80
|
+
preventedPrototypePollution: false
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "concurrent"
|
4
|
+
|
5
|
+
module Aikido::Zen
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# The worker manages the background thread in which Zen communicates with the
|
9
|
+
# Aikido server.
|
10
|
+
class Worker
|
11
|
+
# @return [Concurrent::ExecutorService]
|
12
|
+
attr_reader :executor
|
13
|
+
|
14
|
+
# @!visibility private
|
15
|
+
attr_reader :timers, :deferrals
|
16
|
+
|
17
|
+
def initialize(config: Aikido::Zen.config)
|
18
|
+
@config = config
|
19
|
+
@timers = []
|
20
|
+
@deferrals = []
|
21
|
+
@executor = Concurrent::SingleThreadExecutor.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Queue a block to be run asynchronously in the background thread.
|
25
|
+
#
|
26
|
+
# @return [void]
|
27
|
+
def perform(&block)
|
28
|
+
executor.post do
|
29
|
+
yield
|
30
|
+
rescue Exception => err # rubocop:disable Lint/RescueException
|
31
|
+
@config.logger.error "Error in background worker: #{err.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Queue a block to be run asynchronously after a delay.
|
36
|
+
#
|
37
|
+
# @param interval [Integer] amount of seconds to wait.
|
38
|
+
# @return [void]
|
39
|
+
def delay(interval, &task)
|
40
|
+
Concurrent::ScheduledTask
|
41
|
+
.execute(interval, executor: executor) { perform(&task) }
|
42
|
+
.tap { |deferral| @deferrals << deferral }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Queue a block to run repeatedly on a timer on the background thread. The
|
46
|
+
# timer will consider how long the block takes to run to schedule the next
|
47
|
+
# run. For example, if you schedule a block to run every 10 seconds, and the
|
48
|
+
# block itself takes 2 seconds, the second iteration will be run 8 seconds
|
49
|
+
# after the first one.
|
50
|
+
#
|
51
|
+
# If the block takes longer than the given interval, the second iteration
|
52
|
+
# will be run immediately.
|
53
|
+
#
|
54
|
+
# @param interval [Integer] amount of seconds to wait between runs.
|
55
|
+
# @param run_now [Boolean] whether to run the block immediately, or wait for
|
56
|
+
# +interval+ seconds before the first run. Defaults to +true+.
|
57
|
+
# @return [void]
|
58
|
+
def every(interval, run_now: true, &task)
|
59
|
+
Concurrent::TimerTask
|
60
|
+
.execute(
|
61
|
+
run_now: run_now,
|
62
|
+
executor: executor,
|
63
|
+
interval_type: :fixed_rate,
|
64
|
+
execution_interval: interval
|
65
|
+
) {
|
66
|
+
perform(&task)
|
67
|
+
}
|
68
|
+
.tap { |timer| @timers << timer }
|
69
|
+
end
|
70
|
+
|
71
|
+
# Safely clean up and kill the thread, giving time to kill any ongoing tasks
|
72
|
+
# on the queue.
|
73
|
+
#
|
74
|
+
# @return [void]
|
75
|
+
def shutdown
|
76
|
+
@deferrals.each { |task| task.cancel if task.pending? }
|
77
|
+
@timers.each { |task| task.shutdown }
|
78
|
+
@executor.shutdown
|
79
|
+
@executor.wait_for_termination(30)
|
80
|
+
end
|
81
|
+
|
82
|
+
def restart
|
83
|
+
shutdown
|
84
|
+
@executor = Concurrent::SingleThreadExecutor.new
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|