aikido-zen 0.1.0.alpha4-x86_64-mingw-64 → 0.1.0-x86_64-mingw-64

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) 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.26.x86_64.dll → libzen-v0.1.30.x86_64.dll} +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/stats/routes.rb +0 -53
  37. 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