aikido-zen 0.1.0.alpha4-arm64-linux → 0.1.0-arm64-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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +136 -23
  3. data/Rakefile +4 -0
  4. data/benchmarks/README.md +27 -0
  5. data/benchmarks/rails7.1_sql_injection.js +74 -0
  6. data/docs/banner.svg +203 -0
  7. data/docs/config.md +123 -0
  8. data/docs/rails.md +70 -0
  9. data/lib/aikido/zen/actor.rb +1 -1
  10. data/lib/aikido/zen/agent/heartbeats_manager.rb +66 -0
  11. data/lib/aikido/zen/agent.rb +98 -112
  12. data/lib/aikido/zen/collector/hosts.rb +15 -0
  13. data/lib/aikido/zen/collector/routes.rb +64 -0
  14. data/lib/aikido/zen/{stats → collector}/sink_stats.rb +1 -1
  15. data/lib/aikido/zen/collector/stats.rb +111 -0
  16. data/lib/aikido/zen/{stats → collector}/users.rb +6 -2
  17. data/lib/aikido/zen/collector.rb +117 -0
  18. data/lib/aikido/zen/config.rb +17 -11
  19. data/lib/aikido/zen/context.rb +8 -1
  20. data/lib/aikido/zen/errors.rb +3 -1
  21. data/lib/aikido/zen/event.rb +7 -4
  22. data/lib/aikido/zen/libzen-v0.1.30.aarch64.so +0 -0
  23. data/lib/aikido/zen/middleware/set_context.rb +4 -1
  24. data/lib/aikido/zen/rails_engine.rb +27 -18
  25. data/lib/aikido/zen/request/schema/builder.rb +0 -2
  26. data/lib/aikido/zen/request.rb +6 -0
  27. data/lib/aikido/zen/runtime_settings.rb +6 -11
  28. data/lib/aikido/zen/sinks/action_controller.rb +64 -0
  29. data/lib/aikido/zen/sinks.rb +1 -0
  30. data/lib/aikido/zen/version.rb +2 -2
  31. data/lib/aikido/zen/worker.rb +82 -0
  32. data/lib/aikido/zen.rb +55 -50
  33. data/tasklib/bench.rake +70 -0
  34. metadata +19 -9
  35. data/CODE_OF_CONDUCT.md +0 -132
  36. data/lib/aikido/zen/libzen-v0.1.26.aarch64.so +0 -0
  37. data/lib/aikido/zen/stats/routes.rb +0 -53
  38. data/lib/aikido/zen/stats.rb +0 -171
@@ -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
- # @returns [Logger]
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 [Boolean] whether Zen should infer the schema from request bodies
87
- # sent to the app. Defaults to +false+ or the value of the environment
88
- # variable AIKIDO_FEATURE_COLLECT_API_SCHEMA.
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.blocking_mode = !!ENV.fetch("AIKIDO_BLOCKING", false)
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) { request.ip }
231
+ DEFAULT_RATE_LIMITING_DISCRIMINATOR = ->(request) {
232
+ request.actor ? "actor:#{request.actor.id}" : request.ip
233
+ }
228
234
  end
229
235
  end
@@ -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
  #
@@ -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
@@ -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
- routes: @stats.routes.as_json,
60
- hostnames: @stats.outbound_connections.as_json,
61
- users: @stats.users.as_json
62
+ users: @users.as_json,
63
+ routes: @routes.as_json,
64
+ hostnames: @hosts.as_json
62
65
  )
63
66
  end
64
67
  end
@@ -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.aikido_zen`.
9
- config.aikido_zen = Aikido::Zen.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
- app.middleware.use Aikido::Zen::Middleware::Throttler
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
- app.config.aikido_zen.logger = ::Rails.logger.tagged("aikido")
31
- app.config.aikido_zen.request_builder = Aikido::Zen::Context::RAILS_REQUEST_BUILDER
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.aikido_zen.json_encoder == Aikido::Zen::Config::DEFAULT_JSON_ENCODER
36
- app.config.aikido_zen.json_encoder = ActiveSupport::JSON.method(:encode)
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.aikido_zen.json_decoder == Aikido::Zen::Config::DEFAULT_JSON_DECODER
40
- app.config.aikido_zen.json_decoder = ActiveSupport::JSON.method(:decode)
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
- Aikido::Zen.initialize!
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
@@ -16,8 +16,6 @@ module Aikido::Zen
16
16
  end
17
17
 
18
18
  def schema
19
- return unless @config.api_schema_collection_enabled?
20
-
21
19
  Request::Schema.new(
22
20
  content_type: body_data_type,
23
21
  body_schema: body_schema,
@@ -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
- class RuntimeSettings < Concurrent::MutableStruct.new(
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
- observers.notify_observers(self) if updated_at != last_updated_at
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)
@@ -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)
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Aikido
4
4
  module Zen
5
- VERSION = "0.1.0.alpha4"
5
+ VERSION = "0.1.0"
6
6
 
7
7
  # The version of libzen_internals that we build against.
8
- LIBZEN_VERSION = "0.1.26"
8
+ LIBZEN_VERSION = "0.1.30"
9
9
  end
10
10
  end
@@ -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 context [Aikido::Zen::Request]
66
+ # @param request [Aikido::Zen::Request]
65
67
  # @return [void]
66
68
  def self.track_request(request)
67
- agent.stats.add_request(request)
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
- agent.stats.add_outbound(connection)
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
- actor = Aikido::Zen::Actor(user)
100
+ return if config.disabled?
84
101
 
85
- if actor
86
- agent.stats.add_user(actor)
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
- id_attr, name_attr = config.user_attribute_mappings.values_at(:id, :name)
89
- config.logger.warn(format(<<~LOG, obj: user, id: id_attr, name: name_attr))
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
- * Implement #to_aikido_actor
95
- * Implement #to_model and have %<id>p and %<name>p attributes
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
- private_class_method def self.agent
132
- # We shouldn't start collecting data before we even initialize the agent,
133
- # but might as well make sure we have a @agent going to report to.
134
- @agent or initialize!
135
- @agent
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