Class: Aikido::Zen::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/aikido/zen/agent.rb

Overview

Handles the background processes that communicate with the Aikido servers, including managing the runtime settings that keep the app protected.

Defined Under Namespace

Classes: HeartbeatsManager

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config: Aikido::Zen.config, collector: Aikido::Zen.collector, detached_agent: Aikido::Zen.detached_agent, worker: Aikido::Zen::Worker.new(config: config), api_client: Aikido::Zen::APIClient.new(config: config), api_stream: Aikido::Zen::APIStream.new(config: config)) ⇒ Agent

Returns a new instance of Agent.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/aikido/zen/agent.rb', line 19

def initialize(
  config: Aikido::Zen.config,
  collector: Aikido::Zen.collector,
  detached_agent: Aikido::Zen.detached_agent,
  worker: Aikido::Zen::Worker.new(config: config),
  api_client: Aikido::Zen::APIClient.new(config: config),
  api_stream: Aikido::Zen::APIStream.new(config: config)
)
  @config = config
  @collector = collector
  @detached_agent = detached_agent
  @worker = worker
  @api_client = api_client
  @api_stream = api_stream

  @started_at = nil

  @runtime_config_update_mutex = Mutex.new
  @runtime_firewall_lists_update_mutex = Mutex.new
end

Class Method Details

.start(**opts) ⇒ Aikido::Zen::Agent

Initialize and start an agent instance.

Returns:



15
16
17
# File 'lib/aikido/zen/agent.rb', line 15

def self.start(**opts)
  new(**opts).tap(&:start!)
end

Instance Method Details

#handle_attack(attack) ⇒ void

This method returns an undefined value.

Given an Attack, report it to the Aikido server, and/or block the request depending on configuration.

Parameters:

  • attack (Attack)

    a detected attack.

Raises:



129
130
131
132
133
134
135
136
137
138
139
# File 'lib/aikido/zen/agent.rb', line 129

def handle_attack(attack)
  attack.will_be_blocked! if Aikido::Zen.blocking_mode?

  @config.logger.error(
    format("Zen has %s a %s: %s", attack.blocked? ? "blocked" : "detected", attack.humanized_name, attack.as_json.to_json)
  )
  report(Events::Attack.new(attack: attack)) if @api_client.can_make_requests?

  @collector.track_attack(attack)
  raise attack if attack.blocked?
end

#poll_for_setting_updatesvoid

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Sets up the timer task that polls the Aikido Runtime API for updates to the runtime settings every minute.

See Also:



189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/aikido/zen/agent.rb', line 189

def poll_for_setting_updates
  @worker.every(@config.polling_interval) do
    if @api_client.should_fetch_settings?
      if update_settings_from_runtime_config!(@api_client.fetch_runtime_config)
        updated_settings!
        @config.logger.info("Updated runtime settings after polling")
      end

      update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
      @config.logger.info("Updated runtime firewall list after polling")
    end
  end
end

#report(event) {|response| ... } ⇒ void

This method returns an undefined value.

Asynchronously reports an Event of any kind to the Aikido dashboard. If given a block, the API response will be passed to the block for handling.

Parameters:

Yield Parameters:

  • response (Object)

    the response from the reporting API in case of a successful request.



149
150
151
152
153
154
155
156
# File 'lib/aikido/zen/agent.rb', line 149

def report(event)
  @worker.perform do
    response = @api_client.report(event)
    yield response if response && block_given?
  rescue Aikido::Zen::APIError, Aikido::Zen::NetworkError => err
    @config.logger.error(err.message)
  end
end

#send_heartbeat(at: Time.now.utc) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Atomically flushes all the stats stored by the agent, and sends a heartbeat event. Scheduled to run automatically on a recurring schedule when reporting is enabled.

Parameters:

  • at (Time) (defaults to: Time.now.utc)

    the event time. Defaults to now.

See Also:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/aikido/zen/agent.rb', line 167

def send_heartbeat(at: Time.now.utc)
  return unless @api_client.can_make_requests?

  heartbeat = @collector.flush
  report(heartbeat) do |response|
    if update_settings_from_runtime_config!(response)
      updated_settings!
      @config.logger.info("Updated runtime settings after heartbeat")

      update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
      @config.logger.info("Updated runtime firewall list after heartbeat")
    end
  end
end

#start!Object

Raises:

  • (Aikido::ZenError)


44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/aikido/zen/agent.rb', line 44

def start!
  @config.logger.info("Starting Aikido agent v#{Aikido::Zen::VERSION}")

  raise Aikido::ZenError, "Aikido Agent already started!" if started?
  @started_at = Time.now.utc
  @collector.start(at: @started_at)

  if Aikido::Zen.blocking_mode?
    @config.logger.info("Requests identified as attacks will be blocked")
  else
    @config.logger.warn("Non-blocking mode enabled! No requests will be blocked")
  end

  if @api_client.can_make_requests?
    @config.logger.info("API Token set! Reporting has been enabled")
  else
    @config.logger.warn("No API Token set! Reporting has been disabled")
    return
  end

  at_exit { stop! if started? }

  report(Events::Started.new(time: @started_at)) do |response|
    if update_settings_from_runtime_config!(response)
      updated_settings!
      @config.logger.info("Updated runtime settings")
    end
  rescue => err
    @config.logger.error(err.message)
  end

  begin
    update_settings_from_runtime_firewall_lists!(@api_client.fetch_runtime_firewall_lists)
    @config.logger.info("Updated runtime firewall list")
  rescue => err
    @config.logger.error(err.message)
  end

  if @config.realtime_settings_updates_enabled?
    @api_stream.handle("config-updated") { |event| settings_updated(event) }
    @api_stream.start!
  end

  poll_for_setting_updates

  @config.initial_heartbeat_delays.each do |heartbeat_delay|
    @worker.delay(heartbeat_delay) do
      send_heartbeat
      @config.logger.info("Executed initial heartbeat after #{heartbeat_delay} seconds")
    end
  end
end

#started?Boolean

Returns:

  • (Boolean)


40
41
42
# File 'lib/aikido/zen/agent.rb', line 40

def started?
  !!@started_at
end

#stop!void

This method returns an undefined value.

Clean up any ongoing threads, and reset the state. Called automatically when the process exits.



101
102
103
104
105
106
107
# File 'lib/aikido/zen/agent.rb', line 101

def stop!
  @config.logger.info("Stopping Aikido agent")
  @started_at = nil
  @worker.shutdown

  @api_stream.stop!
end

#updated_settings!void

This method returns an undefined value.

Respond to the runtime settings changing after being fetched from the Aikido servers.



113
114
115
116
117
118
119
# File 'lib/aikido/zen/agent.rb', line 113

def updated_settings!
  if !heartbeats.running?
    heartbeats.start { send_heartbeat }
  elsif heartbeats.stale_settings?
    heartbeats.restart { send_heartbeat }
  end
end