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), 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
# 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),
  api_stream: Aikido::Zen::APIStream.new(config: config)
)
  @config = config
  @collector = collector
  @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:



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

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:



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

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.



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

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:



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

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)


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
86
87
88
89
90
91
92
93
# File 'lib/aikido/zen/agent.rb', line 42

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)


38
39
40
# File 'lib/aikido/zen/agent.rb', line 38

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.



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

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.



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

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