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, worker: Aikido::Zen::Worker.new(config: config), api_client: Aikido::Zen::APIClient.new(config: config)) ⇒ Agent

Returns a new instance of Agent.



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/aikido/zen/agent.rb', line 19

def initialize(
  config: Aikido::Zen.config,
  collector: Aikido::Zen.collector,
  worker: Aikido::Zen::Worker.new(config: config),
  api_client: Aikido::Zen::APIClient.new(config: config)
)
  @started_at = nil

  @config = config
  @worker = worker
  @api_client = api_client
  @collector = collector
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:



103
104
105
106
107
108
109
110
111
# File 'lib/aikido/zen/agent.rb', line 103

def handle_attack(attack)
  attack.will_be_blocked! if @config.blocking_mode?

  @config.logger.error("[ATTACK DETECTED] #{attack.log_message}")
  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:



157
158
159
160
161
162
163
164
# File 'lib/aikido/zen/agent.rb', line 157

def poll_for_setting_updates
  @worker.every(@config.polling_interval) do
    if @api_client.should_fetch_settings?
      Aikido::Zen.runtime_settings.update_from_json(@api_client.fetch_settings)
      @config.logger.info "Updated runtime settings 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.



121
122
123
124
125
126
127
128
# File 'lib/aikido/zen/agent.rb', line 121

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:



139
140
141
142
143
144
145
146
147
148
# File 'lib/aikido/zen/agent.rb', line 139

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

  event = @collector.flush(at: at)

  report(event) do |response|
    Aikido::Zen.runtime_settings.update_from_json(response)
    @config.logger.info "Updated runtime settings after heartbeat"
  end
end

#start!Object

Raises:

  • (Aikido::ZenError)


37
38
39
40
41
42
43
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
# File 'lib/aikido/zen/agent.rb', line 37

def start!
  @config.logger.info "Starting Aikido agent"

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

  if @config.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|
    Aikido::Zen.runtime_settings.update_from_json(response)
    @config.logger.info "Updated runtime settings."
  rescue => err
    @config.logger.error(err.message)
  end

  poll_for_setting_updates

  @worker.delay(@config.initial_heartbeat_delay) do
    send_heartbeat if @collector.stats.any?
  end
end

#started?Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/aikido/zen/agent.rb', line 33

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.



77
78
79
80
81
# File 'lib/aikido/zen/agent.rb', line 77

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

#updated_settings!void

This method returns an undefined value.

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



87
88
89
90
91
92
93
# File 'lib/aikido/zen/agent.rb', line 87

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