aikido-zen 0.1.0.alpha4-arm64-darwin → 0.1.0-arm64-darwin
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +136 -23
- data/Rakefile +4 -0
- data/benchmarks/README.md +27 -0
- data/benchmarks/rails7.1_sql_injection.js +74 -0
- data/docs/banner.svg +203 -0
- data/docs/config.md +123 -0
- data/docs/rails.md +70 -0
- data/lib/aikido/zen/actor.rb +1 -1
- data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
- data/lib/aikido/zen/agent.rb +98 -112
- data/lib/aikido/zen/collector/hosts.rb +15 -0
- data/lib/aikido/zen/collector/routes.rb +64 -0
- data/lib/aikido/zen/{stats → collector}/sink_stats.rb +1 -1
- data/lib/aikido/zen/collector/stats.rb +111 -0
- data/lib/aikido/zen/{stats → collector}/users.rb +6 -2
- data/lib/aikido/zen/collector.rb +117 -0
- data/lib/aikido/zen/config.rb +17 -11
- data/lib/aikido/zen/context.rb +8 -1
- data/lib/aikido/zen/errors.rb +3 -1
- data/lib/aikido/zen/event.rb +7 -4
- data/lib/aikido/zen/{libzen-v0.1.26.aarch64.dylib → libzen-v0.1.30.aarch64.dylib} +0 -0
- data/lib/aikido/zen/middleware/set_context.rb +4 -1
- data/lib/aikido/zen/rails_engine.rb +27 -18
- data/lib/aikido/zen/request/schema/builder.rb +0 -2
- data/lib/aikido/zen/request.rb +6 -0
- data/lib/aikido/zen/runtime_settings.rb +6 -11
- data/lib/aikido/zen/sinks/action_controller.rb +64 -0
- data/lib/aikido/zen/sinks.rb +1 -0
- data/lib/aikido/zen/version.rb +2 -2
- data/lib/aikido/zen/worker.rb +82 -0
- data/lib/aikido/zen.rb +55 -50
- data/tasklib/bench.rake +70 -0
- metadata +19 -9
- data/CODE_OF_CONDUCT.md +0 -132
- data/lib/aikido/zen/stats/routes.rb +0 -53
- data/lib/aikido/zen/stats.rb +0 -171
data/lib/aikido/zen/config.rb
CHANGED
@@ -8,6 +8,13 @@ require_relative "context"
|
|
8
8
|
|
9
9
|
module Aikido::Zen
|
10
10
|
class Config
|
11
|
+
# @return [Boolean] whether Aikido should be turned completely off (no
|
12
|
+
# intercepting calls to protect the app, no agent process running, no
|
13
|
+
# middleware installed). Defaults to false (so, enabled). Can be set
|
14
|
+
# via the AIKIDO_DISABLED environment variable.
|
15
|
+
attr_accessor :disabled
|
16
|
+
alias_method :disabled?, :disabled
|
17
|
+
|
11
18
|
# @return [Boolean] whether Aikido should only report infractions or block
|
12
19
|
# the request by raising an Exception. Defaults to whether AIKIDO_BLOCKING
|
13
20
|
# is set to a non-empty value in your environment, or +false+ otherwise.
|
@@ -45,7 +52,7 @@ module Aikido::Zen
|
|
45
52
|
# into an Object. Defaults to the standard library's JSON.parse method.
|
46
53
|
attr_accessor :json_decoder
|
47
54
|
|
48
|
-
# @
|
55
|
+
# @return [Logger]
|
49
56
|
attr_accessor :logger
|
50
57
|
|
51
58
|
# @return [Integer] maximum number of timing measurements to keep in memory
|
@@ -83,11 +90,9 @@ module Aikido::Zen
|
|
83
90
|
# differentiate different clients. By default this uses the request IP.
|
84
91
|
attr_accessor :rate_limiting_discriminator
|
85
92
|
|
86
|
-
# @return [
|
87
|
-
#
|
88
|
-
|
89
|
-
attr_accessor :api_schema_collection_enabled
|
90
|
-
alias_method :api_schema_collection_enabled?, :api_schema_collection_enabled
|
93
|
+
# @return [Integer] max number of requests we sample per endpoint when
|
94
|
+
# computing the schema.
|
95
|
+
attr_accessor :api_schema_max_samples
|
91
96
|
|
92
97
|
# @api private
|
93
98
|
# @return [Integer] max number of levels deep we want to read a nested
|
@@ -125,7 +130,8 @@ module Aikido::Zen
|
|
125
130
|
attr_accessor :imds_allowed_hosts
|
126
131
|
|
127
132
|
def initialize
|
128
|
-
self.
|
133
|
+
self.disabled = read_boolean_from_env(ENV.fetch("AIKIDO_DISABLED", false))
|
134
|
+
self.blocking_mode = read_boolean_from_env(ENV.fetch("AIKIDO_BLOCKING", false))
|
129
135
|
self.api_timeouts = 10
|
130
136
|
self.api_base_url = ENV.fetch("AIKIDO_BASE_URL", DEFAULT_API_BASE_URL)
|
131
137
|
self.runtime_api_base_url = ENV.fetch("AIKIDO_RUNTIME_URL", DEFAULT_RUNTIME_BASE_URL)
|
@@ -146,11 +152,9 @@ module Aikido::Zen
|
|
146
152
|
self.server_rate_limit_deadline = 1800 # 30 min
|
147
153
|
self.client_rate_limit_period = 3600 # 1 hour
|
148
154
|
self.client_rate_limit_max_events = 100
|
149
|
-
|
150
|
-
self.api_schema_collection_enabled = read_boolean_from_env(ENV.fetch("AIKIDO_FEATURE_COLLECT_API_SCHEMA", false))
|
155
|
+
self.api_schema_max_samples = Integer(ENV.fetch("AIKIDO_MAX_API_DISCOVERY_SAMPLES", 10))
|
151
156
|
self.api_schema_collection_max_depth = 20
|
152
157
|
self.api_schema_collection_max_properties = 20
|
153
|
-
|
154
158
|
self.imds_allowed_hosts = ["metadata.google.internal", "metadata.goog"]
|
155
159
|
end
|
156
160
|
|
@@ -224,6 +228,8 @@ module Aikido::Zen
|
|
224
228
|
end
|
225
229
|
|
226
230
|
# @!visibility private
|
227
|
-
DEFAULT_RATE_LIMITING_DISCRIMINATOR = ->(request) {
|
231
|
+
DEFAULT_RATE_LIMITING_DISCRIMINATOR = ->(request) {
|
232
|
+
request.actor ? "actor:#{request.actor.id}" : request.ip
|
233
|
+
}
|
228
234
|
end
|
229
235
|
end
|
data/lib/aikido/zen/context.rb
CHANGED
@@ -7,13 +7,20 @@ require_relative "payload"
|
|
7
7
|
|
8
8
|
module Aikido::Zen
|
9
9
|
class Context
|
10
|
+
# Build a Context object for the current HTTP request based on the currently
|
11
|
+
# configured request builder.
|
12
|
+
#
|
13
|
+
# @param env [Hash] the Rack env hash.
|
14
|
+
# @param config [Aikido::Zen::Config]
|
15
|
+
# @return [Aikido::Zen::Context]
|
10
16
|
def self.from_rack_env(env, config = Aikido::Zen.config)
|
11
17
|
config.request_builder.call(env)
|
12
18
|
end
|
13
19
|
|
20
|
+
# @return [Aikido::Zen::Request]
|
14
21
|
attr_reader :request
|
15
22
|
|
16
|
-
# @param [Rack::Request] a Request object that implements the
|
23
|
+
# @param request [Rack::Request] a Request object that implements the
|
17
24
|
# Rack::Request API, to which we will delegate behavior.
|
18
25
|
# @param settings [Aikido::Zen::RuntimeSettings]
|
19
26
|
#
|
data/lib/aikido/zen/errors.rb
CHANGED
@@ -3,11 +3,13 @@
|
|
3
3
|
require "forwardable"
|
4
4
|
|
5
5
|
module Aikido
|
6
|
+
# @!visibility private
|
6
7
|
# Support rescuing Aikido::Error without forcing a single base class to all
|
7
8
|
# errors (so things that should be e.g. a TypeError, can have the correct
|
8
9
|
# superclass).
|
9
|
-
module Error; end
|
10
|
+
module Error; end # :nodoc:
|
10
11
|
|
12
|
+
# @!visibility private
|
11
13
|
# Generic error for problems with the Agent.
|
12
14
|
class ZenError < RuntimeError
|
13
15
|
include Error
|
data/lib/aikido/zen/event.rb
CHANGED
@@ -48,17 +48,20 @@ module Aikido::Zen
|
|
48
48
|
end
|
49
49
|
|
50
50
|
class Heartbeat < Event
|
51
|
-
def initialize(stats:, **opts)
|
51
|
+
def initialize(stats:, users:, hosts:, routes:, **opts)
|
52
52
|
super(type: "heartbeat", **opts)
|
53
53
|
@stats = stats
|
54
|
+
@users = users
|
55
|
+
@hosts = hosts
|
56
|
+
@routes = routes
|
54
57
|
end
|
55
58
|
|
56
59
|
def as_json
|
57
60
|
super.update(
|
58
61
|
stats: @stats.as_json,
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
users: @users.as_json,
|
63
|
+
routes: @routes.as_json,
|
64
|
+
hostnames: @hosts.as_json
|
62
65
|
)
|
63
66
|
end
|
64
67
|
end
|
Binary file
|
@@ -3,6 +3,9 @@
|
|
3
3
|
require_relative "../context"
|
4
4
|
|
5
5
|
module Aikido::Zen
|
6
|
+
# @!visibility private
|
7
|
+
ENV_KEY = "aikido.context"
|
8
|
+
|
6
9
|
module Middleware
|
7
10
|
# Rack middleware that keeps the current context in a Thread/Fiber-local
|
8
11
|
# variable so that other parts of the agent/firewall can access it.
|
@@ -14,7 +17,7 @@ module Aikido::Zen
|
|
14
17
|
def call(env)
|
15
18
|
context = Context.from_rack_env(env)
|
16
19
|
|
17
|
-
Aikido::Zen.current_context = context
|
20
|
+
Aikido::Zen.current_context = env[ENV_KEY] = context
|
18
21
|
Aikido::Zen.track_request(context.request)
|
19
22
|
|
20
23
|
@app.call(env)
|
@@ -5,44 +5,53 @@ require "action_dispatch"
|
|
5
5
|
module Aikido::Zen
|
6
6
|
class RailsEngine < ::Rails::Engine
|
7
7
|
config.before_configuration do
|
8
|
-
# Access library configuration at `Rails.application.config.
|
9
|
-
config.
|
8
|
+
# Access library configuration at `Rails.application.config.zen`.
|
9
|
+
config.zen = Aikido::Zen.config
|
10
10
|
end
|
11
11
|
|
12
12
|
initializer "aikido.add_middleware" do |app|
|
13
|
+
next if config.zen.disabled?
|
14
|
+
|
13
15
|
app.middleware.use Aikido::Zen::Middleware::SetContext
|
14
16
|
app.middleware.use Aikido::Zen::Middleware::CheckAllowedAddresses
|
15
|
-
|
16
|
-
|
17
|
-
# Due to how Rails sets up its middleware chain, the routing is evaluated
|
18
|
-
# (and the Request object constructed) in the app that terminates the
|
19
|
-
# chain, so no amount of middleware will be able to access it.
|
20
|
-
#
|
21
|
-
# This way, we overwrite the Request object as early as we can in the
|
22
|
-
# request handling, so that by the time we start evaluating inputs, we
|
23
|
-
# have assigned the request correctly.
|
17
|
+
|
24
18
|
ActiveSupport.on_load(:action_controller) do
|
19
|
+
# Due to how Rails sets up its middleware chain, the routing is evaluated
|
20
|
+
# (and the Request object constructed) in the app that terminates the
|
21
|
+
# chain, so no amount of middleware will be able to access it.
|
22
|
+
#
|
23
|
+
# This way, we overwrite the Request object as early as we can in the
|
24
|
+
# request handling, so that by the time we start evaluating inputs, we
|
25
|
+
# have assigned the request correctly.
|
25
26
|
before_action { Aikido::Zen.current_context.update_request(request) }
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
29
30
|
initializer "aikido.configuration" do |app|
|
30
|
-
|
31
|
-
|
31
|
+
# Allow the logger to be configured before checking if disabled? so we can
|
32
|
+
# let the user know that the agent is disabled.
|
33
|
+
app.config.zen.logger = ::Rails.logger.tagged("aikido")
|
34
|
+
|
35
|
+
next if config.zen.disabled?
|
36
|
+
|
37
|
+
app.config.zen.request_builder = Aikido::Zen::Context::RAILS_REQUEST_BUILDER
|
32
38
|
|
33
39
|
# Plug Rails' JSON encoder/decoder, but only if the user hasn't changed
|
34
40
|
# them for something else.
|
35
|
-
if app.config.
|
36
|
-
app.config.
|
41
|
+
if app.config.zen.json_encoder == Aikido::Zen::Config::DEFAULT_JSON_ENCODER
|
42
|
+
app.config.zen.json_encoder = ActiveSupport::JSON.method(:encode)
|
37
43
|
end
|
38
44
|
|
39
|
-
if app.config.
|
40
|
-
app.config.
|
45
|
+
if app.config.zen.json_decoder == Aikido::Zen::Config::DEFAULT_JSON_DECODER
|
46
|
+
app.config.zen.json_decoder = ActiveSupport::JSON.method(:decode)
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
44
50
|
config.after_initialize do
|
45
|
-
|
51
|
+
if config.zen.disabled?
|
52
|
+
config.zen.logger.warn("Zen has been disabled and will not run.")
|
53
|
+
next
|
54
|
+
end
|
46
55
|
|
47
56
|
# Make sure this is run at the end of the initialization process, so
|
48
57
|
# that any gems required after aikido-zen are detected and patched
|
data/lib/aikido/zen/request.rb
CHANGED
@@ -11,6 +11,12 @@ module Aikido::Zen
|
|
11
11
|
# @return [Aikido::Zen::Router]
|
12
12
|
attr_reader :router
|
13
13
|
|
14
|
+
# The current user, if set by the host app.
|
15
|
+
#
|
16
|
+
# @return [Aikido::Zen::Actor, nil]
|
17
|
+
# @see Aikido::Zen.track_user
|
18
|
+
attr_accessor :actor
|
19
|
+
|
14
20
|
def initialize(delegate, framework:, router:)
|
15
21
|
super(delegate)
|
16
22
|
@framework = framework
|
@@ -11,16 +11,11 @@ module Aikido::Zen
|
|
11
11
|
#
|
12
12
|
# You can subscribe to changes with +#add_observer(object, func_name)+, which
|
13
13
|
# will call the function passing the settings as an argument.
|
14
|
-
|
15
|
-
:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :skip_protection_for_ips, :received_any_stats
|
16
|
-
)
|
17
|
-
include Concurrent::Concern::Observable
|
18
|
-
|
14
|
+
RuntimeSettings = Struct.new(:updated_at, :heartbeat_interval, :endpoints, :blocked_user_ids, :skip_protection_for_ips, :received_any_stats) do
|
19
15
|
def initialize(*)
|
20
|
-
self.observers = Concurrent::Collection::CopyOnWriteObserverSet.new
|
21
16
|
super
|
22
|
-
self.endpoints ||= Endpoints.new
|
23
|
-
self.skip_protection_for_ips ||= IPSet.new
|
17
|
+
self.endpoints ||= RuntimeSettings::Endpoints.new
|
18
|
+
self.skip_protection_for_ips ||= RuntimeSettings::IPSet.new
|
24
19
|
end
|
25
20
|
|
26
21
|
# @!attribute [rw] updated_at
|
@@ -56,12 +51,12 @@ module Aikido::Zen
|
|
56
51
|
|
57
52
|
self.updated_at = Time.at(data["configUpdatedAt"].to_i / 1000)
|
58
53
|
self.heartbeat_interval = (data["heartbeatIntervalInMS"].to_i / 1000)
|
59
|
-
self.endpoints = Endpoints.from_json(data["endpoints"])
|
54
|
+
self.endpoints = RuntimeSettings::Endpoints.from_json(data["endpoints"])
|
60
55
|
self.blocked_user_ids = data["blockedUserIds"]
|
61
|
-
self.skip_protection_for_ips = IPSet.from_json(data["allowedIPAddresses"])
|
56
|
+
self.skip_protection_for_ips = RuntimeSettings::IPSet.from_json(data["allowedIPAddresses"])
|
62
57
|
self.received_any_stats = data["receivedAnyStats"]
|
63
58
|
|
64
|
-
|
59
|
+
Aikido::Zen.agent.updated_settings! if updated_at != last_updated_at
|
65
60
|
end
|
66
61
|
end
|
67
62
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aikido::Zen
|
4
|
+
module Sinks
|
5
|
+
module ActionController
|
6
|
+
# Implements the "middleware" for rate limiting in Rails apps, where we
|
7
|
+
# need to check at the end of the `before_action` chain, rather than in
|
8
|
+
# an actual Rack middleware, to allow for calls to Zen.track_user being
|
9
|
+
# made from before_actions in the host app, thus allowing rate-limiting
|
10
|
+
# by user ID rather than solely by IP.
|
11
|
+
class Throttler
|
12
|
+
def initialize(
|
13
|
+
config: Aikido::Zen.config,
|
14
|
+
settings: Aikido::Zen.runtime_settings,
|
15
|
+
rate_limiter: Aikido::Zen::RateLimiter.new
|
16
|
+
)
|
17
|
+
@config = config
|
18
|
+
@settings = settings
|
19
|
+
@rate_limiter = rate_limiter
|
20
|
+
end
|
21
|
+
|
22
|
+
def throttle(controller)
|
23
|
+
context = controller.request.env[Aikido::Zen::ENV_KEY]
|
24
|
+
request = context.request
|
25
|
+
|
26
|
+
if should_throttle?(request)
|
27
|
+
status, headers, body = @config.rate_limited_responder.call(request)
|
28
|
+
controller.headers.update(headers)
|
29
|
+
controller.render plain: Array(body).join, status: status
|
30
|
+
|
31
|
+
return true
|
32
|
+
end
|
33
|
+
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
private def should_throttle?(request)
|
38
|
+
return false if @settings.skip_protection_for_ips.include?(request.ip)
|
39
|
+
|
40
|
+
@rate_limiter.throttle?(request)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.throttler
|
45
|
+
@throttler ||= Aikido::Zen::Sinks::ActionController::Throttler.new
|
46
|
+
end
|
47
|
+
|
48
|
+
module Extensions
|
49
|
+
def run_callbacks(kind, *)
|
50
|
+
return super unless kind == :process_action
|
51
|
+
|
52
|
+
super do
|
53
|
+
rate_limiter = Aikido::Zen::Sinks::ActionController.throttler
|
54
|
+
throttled = rate_limiter.throttle(self)
|
55
|
+
|
56
|
+
yield if block_given? && !throttled
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
::AbstractController::Callbacks.prepend(Aikido::Zen::Sinks::ActionController::Extensions)
|
data/lib/aikido/zen/sinks.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative "sink"
|
|
4
4
|
|
5
5
|
require_relative "sinks/socket"
|
6
6
|
|
7
|
+
require_relative "sinks/action_controller" if defined?(::ActionController)
|
7
8
|
require_relative "sinks/resolv" if defined?(::Resolv)
|
8
9
|
require_relative "sinks/net_http" if defined?(::Net::HTTP)
|
9
10
|
require_relative "sinks/http" if defined?(::HTTP)
|
data/lib/aikido/zen/version.rb
CHANGED
@@ -0,0 +1,82 @@
|
|
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
|
+
end
|
82
|
+
end
|
data/lib/aikido/zen.rb
CHANGED
@@ -4,7 +4,9 @@ require_relative "zen/version"
|
|
4
4
|
require_relative "zen/errors"
|
5
5
|
require_relative "zen/actor"
|
6
6
|
require_relative "zen/config"
|
7
|
+
require_relative "zen/collector"
|
7
8
|
require_relative "zen/system_info"
|
9
|
+
require_relative "zen/worker"
|
8
10
|
require_relative "zen/agent"
|
9
11
|
require_relative "zen/api_client"
|
10
12
|
require_relative "zen/context"
|
@@ -24,12 +26,24 @@ module Aikido
|
|
24
26
|
@config ||= Config.new
|
25
27
|
end
|
26
28
|
|
29
|
+
# @return [Aikido::Zen::RuntimeSettings] the firewall configuration sourced
|
30
|
+
# from your Aikido dashboard. This is periodically polled for updates.
|
31
|
+
def self.runtime_settings
|
32
|
+
@runtime_settings ||= RuntimeSettings.new
|
33
|
+
end
|
34
|
+
|
27
35
|
# Gets information about the current system configuration, which is sent to
|
28
36
|
# the server along with any events.
|
29
37
|
def self.system_info
|
30
38
|
@system_info ||= SystemInfo.new
|
31
39
|
end
|
32
40
|
|
41
|
+
# Manages runtime metrics extracted from your app, which are uploaded to the
|
42
|
+
# Aikido servers if configured to do so.
|
43
|
+
def self.collector
|
44
|
+
@collector ||= Collector.new
|
45
|
+
end
|
46
|
+
|
33
47
|
# Gets the current context object that holds all information about the
|
34
48
|
# current request.
|
35
49
|
#
|
@@ -47,24 +61,13 @@ module Aikido
|
|
47
61
|
Thread.current[:_aikido_current_context_] = context
|
48
62
|
end
|
49
63
|
|
50
|
-
# Track statistics about the result of a Sink's scan, and report it as an
|
51
|
-
# Attack if one is detected.
|
52
|
-
#
|
53
|
-
# @param scan [Aikido::Zen::Scan]
|
54
|
-
# @return [void]
|
55
|
-
# @raise [Aikido::Zen::UnderAttackError] if the scan detected an Attack
|
56
|
-
# and blocking_mode is enabled.
|
57
|
-
def self.track_scan(scan)
|
58
|
-
agent.stats.add_scan(scan)
|
59
|
-
agent.handle_attack(scan.attack) if scan.attack?
|
60
|
-
end
|
61
|
-
|
62
64
|
# Track statistics about an HTTP request the app is handling.
|
63
65
|
#
|
64
|
-
# @param
|
66
|
+
# @param request [Aikido::Zen::Request]
|
65
67
|
# @return [void]
|
66
68
|
def self.track_request(request)
|
67
|
-
|
69
|
+
autostart
|
70
|
+
collector.track_request(request)
|
68
71
|
end
|
69
72
|
|
70
73
|
# Tracks a network connection made to an external service.
|
@@ -72,7 +75,21 @@ module Aikido
|
|
72
75
|
# @param connection [Aikido::Zen::OutboundConnection]
|
73
76
|
# @return [void]
|
74
77
|
def self.track_outbound(connection)
|
75
|
-
|
78
|
+
autostart
|
79
|
+
collector.track_outbound(connection)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Track statistics about the result of a Sink's scan, and report it as
|
83
|
+
# an Attack if one is detected.
|
84
|
+
#
|
85
|
+
# @param scan [Aikido::Zen::Scan]
|
86
|
+
# @return [void]
|
87
|
+
# @raise [Aikido::Zen::UnderAttackError] if the scan detected an Attack
|
88
|
+
# and blocking_mode is enabled.
|
89
|
+
def self.track_scan(scan)
|
90
|
+
autostart
|
91
|
+
collector.track_scan(scan)
|
92
|
+
agent.handle_attack(scan.attack) if scan.attack?
|
76
93
|
end
|
77
94
|
|
78
95
|
# Track the user making the current request.
|
@@ -80,43 +97,22 @@ module Aikido
|
|
80
97
|
# @param (see Aikido::Zen.Actor)
|
81
98
|
# @return [void]
|
82
99
|
def self.track_user(user)
|
83
|
-
|
100
|
+
return if config.disabled?
|
84
101
|
|
85
|
-
if actor
|
86
|
-
|
102
|
+
if (actor = Aikido::Zen::Actor(user))
|
103
|
+
autostart
|
104
|
+
collector.track_user(actor)
|
105
|
+
current_context.request.actor = actor if current_context
|
87
106
|
else
|
88
|
-
|
89
|
-
|
90
|
-
Incompatible object sent to Aikido::Zen.track_user: %<obj>p
|
91
|
-
|
92
|
-
The object must satisfy one of the following:
|
107
|
+
config.logger.warn(format(<<~LOG, obj: user))
|
108
|
+
Incompatible object sent to track_user: %<obj>p
|
93
109
|
|
94
|
-
|
95
|
-
|
96
|
-
* Be a Hash with :id (or "id") and, optionally, :name (or "name") keys
|
110
|
+
The object must either implement #to_aikido_actor, or be a Hash with
|
111
|
+
an :id (or "id") and, optionally, a :name (or "name") key.
|
97
112
|
LOG
|
98
113
|
end
|
99
114
|
end
|
100
115
|
|
101
|
-
# Starts the background threads that keep the agent running.
|
102
|
-
#
|
103
|
-
# @return [void]
|
104
|
-
def self.initialize!
|
105
|
-
@agent ||= Agent.new
|
106
|
-
@agent.start!
|
107
|
-
end
|
108
|
-
|
109
|
-
# Stop any background threads.
|
110
|
-
def self.stop!
|
111
|
-
@agent&.stop!
|
112
|
-
end
|
113
|
-
|
114
|
-
# @return [Aikido::Zen::RuntimeSettings] the firewall configuration sourced
|
115
|
-
# from your Aikido dashboard. This is periodically polled for updates.
|
116
|
-
def self.runtime_settings
|
117
|
-
@runtime_settings ||= RuntimeSettings.new
|
118
|
-
end
|
119
|
-
|
120
116
|
# Load all sinks matching libraries loaded into memory. This method should
|
121
117
|
# be called after all other dependencies have been loaded into memory (i.e.
|
122
118
|
# at the end of the initialization process).
|
@@ -128,11 +124,20 @@ module Aikido
|
|
128
124
|
require_relative "zen/sinks"
|
129
125
|
end
|
130
126
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
127
|
+
# @!visibility private
|
128
|
+
# Stop any background threads.
|
129
|
+
def self.stop!
|
130
|
+
agent&.stop!
|
131
|
+
end
|
132
|
+
|
133
|
+
# @!visibility private
|
134
|
+
# Starts the background agent if it has not been started yet.
|
135
|
+
def self.agent
|
136
|
+
@agent ||= Agent.start
|
137
|
+
end
|
138
|
+
|
139
|
+
class << self
|
140
|
+
alias_method :autostart, :agent
|
136
141
|
end
|
137
142
|
end
|
138
143
|
end
|