aikido-zen 0.1.0.alpha4-arm64-darwin → 0.1.0-arm64-darwin
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 +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
|