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)) ⇒ Agent

Returns a new instance of Agent.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 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)
)
  @started_at = nil

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



117
118
119
120
121
122
123
124
125
126
127
# File 'lib/aikido/zen/agent.rb', line 117

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:



174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/aikido/zen/agent.rb', line 174

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

      Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(@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.



137
138
139
140
141
142
143
144
# File 'lib/aikido/zen/agent.rb', line 137

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:



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

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

  heartbeat = @collector.flush
  report(heartbeat) do |response|
    if Aikido::Zen.runtime_settings.update_from_runtime_config_json(response)
      updated_settings!
      @config.logger.info("Updated runtime settings after heartbeat")
    end
  end
end

#start!Object

Raises:

  • (Aikido::ZenError)


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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/aikido/zen/agent.rb', line 39

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

  begin
    Aikido::Zen.runtime_settings.update_from_runtime_firewall_lists_json(@api_client.fetch_runtime_firewall_lists)
    @config.logger.info("Updated runtime firewall list")
  rescue => err
    @config.logger.error(err.message)
  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)


35
36
37
# File 'lib/aikido/zen/agent.rb', line 35

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.



91
92
93
94
95
# File 'lib/aikido/zen/agent.rb', line 91

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.



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

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