Module: Aikido::Zen

Defined in:
lib/aikido/zen.rb,
lib/aikido/zen/sql.rb,
lib/aikido/zen/scan.rb,
lib/aikido/zen/sink.rb,
lib/aikido/zen/actor.rb,
lib/aikido/zen/agent.rb,
lib/aikido/zen/cache.rb,
lib/aikido/zen/event.rb,
lib/aikido/zen/route.rb,
lib/aikido/zen/attack.rb,
lib/aikido/zen/config.rb,
lib/aikido/zen/errors.rb,
lib/aikido/zen/worker.rb,
lib/aikido/zen/context.rb,
lib/aikido/zen/helpers.rb,
lib/aikido/zen/ipc/rpc.rb,
lib/aikido/zen/package.rb,
lib/aikido/zen/payload.rb,
lib/aikido/zen/request.rb,
lib/aikido/zen/version.rb,
lib/aikido/zen/sinks/pg.rb,
lib/aikido/zen/api_cache.rb,
lib/aikido/zen/collector.rb,
lib/aikido/zen/internals.rb,
lib/aikido/zen/sinks_dsl.rb,
lib/aikido/zen/api_client.rb,
lib/aikido/zen/api_stream.rb,
lib/aikido/zen/sinks/curb.rb,
lib/aikido/zen/sinks/file.rb,
lib/aikido/zen/sinks/http.rb,
lib/aikido/zen/attack_wave.rb,
lib/aikido/zen/sinks/excon.rb,
lib/aikido/zen/sinks/httpx.rb,
lib/aikido/zen/system_info.rb,
lib/aikido/zen/rails_engine.rb,
lib/aikido/zen/rate_limiter.rb,
lib/aikido/zen/sinks/kernel.rb,
lib/aikido/zen/sinks/mysql2.rb,
lib/aikido/zen/sinks/patron.rb,
lib/aikido/zen/sinks/resolv.rb,
lib/aikido/zen/sinks/socket.rb,
lib/aikido/zen/sinks/em_http.rb,
lib/aikido/zen/sinks/sqlite3.rb,
lib/aikido/zen/sinks/trilogy.rb,
lib/aikido/zen/idor/protector.rb,
lib/aikido/zen/request/schema.rb,
lib/aikido/zen/sinks/net_http.rb,
lib/aikido/zen/sinks/typhoeus.rb,
lib/aikido/zen/synchronizable.rb,
lib/aikido/zen/collector/event.rb,
lib/aikido/zen/collector/hosts.rb,
lib/aikido/zen/collector/stats.rb,
lib/aikido/zen/collector/users.rb,
lib/aikido/zen/collector/routes.rb,
lib/aikido/zen/runtime_settings.rb,
lib/aikido/zen/sinks/async_http.rb,
lib/aikido/zen/sinks/httpclient.rb,
lib/aikido/zen/background_worker.rb,
lib/aikido/zen/capped_collections.rb,
lib/aikido/zen/attack_wave/helpers.rb,
lib/aikido/zen/outbound_connection.rb,
lib/aikido/zen/rate_limiter/bucket.rb,
lib/aikido/zen/rate_limiter/result.rb,
lib/aikido/zen/collector/sink_stats.rb,
lib/aikido/zen/context/rack_request.rb,
lib/aikido/zen/idor/analysis_result.rb,
lib/aikido/zen/rate_limiter/breaker.rb,
lib/aikido/zen/request/rails_router.rb,
lib/aikido/zen/context/rails_request.rb,
lib/aikido/zen/scanners/ssrf_scanner.rb,
lib/aikido/zen/request/schema/builder.rb,
lib/aikido/zen/runtime_settings/ip_set.rb,
lib/aikido/zen/sinks/action_controller.rb,
lib/aikido/zen/agent/heartbeats_manager.rb,
lib/aikido/zen/middleware/fork_detector.rb,
lib/aikido/zen/request/heuristic_router.rb,
lib/aikido/zen/runtime_settings/domains.rb,
lib/aikido/zen/runtime_settings/ip_list.rb,
lib/aikido/zen/middleware/context_setter.rb,
lib/aikido/zen/middleware/rack_throttler.rb,
lib/aikido/zen/scanners/ssrf/dns_lookups.rb,
lib/aikido/zen/middleware/ip_list_checker.rb,
lib/aikido/zen/middleware/request_tracker.rb,
lib/aikido/zen/runtime_settings/endpoints.rb,
lib/aikido/zen/middleware/attack_protector.rb,
lib/aikido/zen/request/schema/auth_schemas.rb,
lib/aikido/zen/request/schema/empty_schema.rb,
lib/aikido/zen/scanners/stored_ssrf_scanner.rb,
lib/aikido/zen/middleware/user_agent_checker.rb,
lib/aikido/zen/request/schema/auth_discovery.rb,
lib/aikido/zen/scanners/sql_injection_scanner.rb,
lib/aikido/zen/scanners/path_traversal/helpers.rb,
lib/aikido/zen/scanners/path_traversal_scanner.rb,
lib/aikido/zen/middleware/attack_wave_protector.rb,
lib/aikido/zen/runtime_settings/domain_settings.rb,
lib/aikido/zen/scanners/shell_injection_scanner.rb,
lib/aikido/zen/scanners/ssrf/private_ip_checker.rb,
lib/aikido/zen/middleware/allowed_address_checker.rb,
lib/aikido/zen/runtime_settings/protection_settings.rb,
lib/aikido/zen/runtime_settings/rate_limit_settings.rb,
lib/aikido/zen/ipc/ipc.rb

Defined Under Namespace

Modules: AttackWave, Attacks, Events, Helpers, IDOR, IPC, Internals, Middleware, RPC, Rails, SQL, Scanners, Sinks, WorkerProcess Classes: APICache, APIClient, APIError, APIStream, Actor, Agent, Attack, BackgroundWorker, Cache, CacheEntry, CappedMap, CappedSet, Collector, Config, Context, DecodeError, Event, InternalsError, NetworkError, OutboundConnection, OutboundConnectionBlockedError, Package, PathTraversalError, Payload, RailsEngine, RateLimitedError, RateLimiter, Request, Route, RuntimeSettings, SQLInjectionError, SSRFDetectedError, Scan, ShellInjectionError, Sink, SystemInfo, UnderAttackError, Worker

Constant Summary collapse

VERSION =
"1.8.0"
LIBZEN_VERSION =

The version of libzen_internals that we build against.

"0.1.61"

Class Method Summary collapse

Class Method Details

.Actor(actor) ⇒ Object .Actor(data) ⇒ Object

Converts an object into an Actor for reporting back to the Aikido Dashboard.

Overloads:

  • .Actor(actor) ⇒ Object

    Returns Aikido::Zen::Actor.

    Parameters:

    • anything that implements #to_aikido_actor will have that method called and its value returned.

    Returns:

    • Aikido::Zen::Actor

  • .Actor(data) ⇒ Object

    Returns Aikido::Zen::Actor.

    Parameters:

    Options Hash (data):

    • :id (String)

      a unique identifier for this user.

    • :name (String, nil)

      an optional name to display in the UI.

    Returns:

    • Aikido::Zen::Actor



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

def self.Actor(data)
  return if data.nil?
  return data.to_aikido_actor if data.respond_to?(:to_aikido_actor)

  attrs = {}
  if data.respond_to?(:to_hash)
    attrs = data.to_hash
      .slice("id", "name", :id, :name)
      .compact
      .transform_keys(&:to_sym)
      .transform_values(&:to_s)
  else
    return nil
  end

  return nil if attrs[:id].nil? || attrs[:id].to_s.strip.empty?

  Actor.new(**attrs)
end

.agentObject



420
421
422
# File 'lib/aikido/zen.rb', line 420

def self.agent
  @agent
end

.api_cacheObject



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

def self.api_cache
  @api_cache ||= APICache.new
end

.attack_wave_detectorAikido::Zen::AttackWave::Detector

Returns the attack wave detector.

Returns:

  • the attack wave detector.



273
274
275
# File 'lib/aikido/zen.rb', line 273

def self.attack_wave_detector
  @attack_wave_detector ||= AttackWave::Detector.new
end

.block_outbound?(connection) ⇒ Boolean

Returns whether this outbound connection should be blocked.

Parameters:

Returns:

  • whether this outbound connection should be blocked.



229
230
231
232
233
# File 'lib/aikido/zen.rb', line 229

def self.block_outbound?(connection)
  return false if current_context&.protection_disabled?

  runtime_settings.block_outbound?(connection)
end

.blocking_mode?Boolean

Returns whether the Aikido agent is currently blocking requests. Blocking mode is configured at startup and can be controlled through the Aikido dashboard at runtime.

Returns:

  • whether the Aikido agent is currently blocking requests. Blocking mode is configured at startup and can be controlled through the Aikido dashboard at runtime.



120
121
122
123
124
125
# File 'lib/aikido/zen.rb', line 120

def self.blocking_mode?
  blocking_mode = runtime_settings.blocking_mode
  return blocking_mode unless blocking_mode.nil?

  config.blocking_mode
end

.calculate_rate_limits(request) ⇒ Object



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

def self.calculate_rate_limits(request)
  worker_process_client = @worker_process_client

  if worker_process_client
    begin
      worker_process_client.calculate_rate_limits(request)
    rescue
      rate_limiter.calculate_rate_limits(request)
    end
  else
    rate_limiter.calculate_rate_limits(request)
  end
end

.collectorObject

Manages runtime metrics extracted from your app, which are uploaded to the Aikido servers if configured to do so.



135
136
137
# File 'lib/aikido/zen.rb', line 135

def self.collector
  @collector ||= Collector.new
end

.configAikido::Zen::Config

Returns the agent configuration.

Returns:

  • the agent configuration.



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

def self.config
  @config ||= Config.new
end

.current_contextAikido::Zen::Context?

Gets the current context object that holds all information about the current request.

Returns:



143
144
145
# File 'lib/aikido/zen.rb', line 143

def self.current_context
  Fiber.current.aikido_current_context
end

.current_context=(context) ⇒ Aikido::Zen::Context?

Sets the current context object that holds all information about the current request, or nil to clear the current context.

Parameters:

Returns:



152
153
154
# File 'lib/aikido/zen.rb', line 152

def self.current_context=(context)
  Fiber.current.aikido_current_context = context
end

.detect_attack_wave(context, status_code = nil) ⇒ Array<Aikido::Zen::AttackWave::Sample>?

Classifies the current request and, if it is classified as suspicious, records a sample against the attack wave detector.

Parameters:

  • (defaults to: nil)

Returns:



283
284
285
286
287
288
289
290
# File 'lib/aikido/zen.rb', line 283

def self.detect_attack_wave(context, status_code = nil)
  client_ip = context.request.client_ip
  return nil unless client_ip

  return nil unless AttackWave::Helpers.web_scanner?(context, status_code)

  record_attack_wave(client_ip, AttackWave::Helpers.sample_for(context))
end

.enable_idor_protectionvoid

This method returns an undefined value.

Enable IDOR protection for the current context.



340
341
342
343
344
345
# File 'lib/aikido/zen.rb', line 340

def self.enable_idor_protection
  context = current_context
  return unless context

  context.idor_protection_enabled = true
end

.fork!Object



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/aikido/zen.rb', line 448

def fork!
  server = @worker_process_server
  return unless server

  @worker_process_server = nil
  server.close

  client = @worker_process_client
  @worker_process_client = nil
  client&.close

  client = WorkerProcess::Agent::Client.new(server.host, server.port)
  client.start
  @worker_process_client = client
rescue => err
  config.logger.error("Forked worker process #{Process.pid}: failed to start worker process client: #{err.message}")
end

.idor_protect(sql, dialect_name, params = nil) ⇒ void

This method returns an undefined value.

Parameters:

  • (defaults to: nil)

Raises:



330
331
332
333
334
335
# File 'lib/aikido/zen.rb', line 330

def self.idor_protect(sql, dialect_name, params = nil)
  context = current_context
  return unless context

  idor_protector.protect(sql, dialect_name, params, context)
end

.idor_protectorAikido::Zen::IDOR::Protector

Returns:



321
322
323
# File 'lib/aikido/zen.rb', line 321

def self.idor_protector
  @idor_protector ||= IDOR::Protector.new
end

.middleware_installed!Object

Marks that the Zen middleware was installed properly

Returns:

  • void



383
384
385
# File 'lib/aikido/zen.rb', line 383

def self.middleware_installed!
  collector.middleware_installed!
end

.protect!void

This method returns an undefined value.

Enable protection. Until this method is called no sinks are loaded and the Aikido Agent does not start.

This method should be called only once, in the application after the initialization process is complete.



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/aikido/zen.rb', line 45

def self.protect!
  if config.disabled?
    config.logger.warn("Zen has been disabled and will not run")
    return
  end

  unless load_sources! && load_sinks!
    config.logger.warn("Zen could not find any supported libraries or frameworks. Visit https://github.com/AikidoSec/firewall-ruby for more information.")
    return
  end

  middleware_installed!
end

.rate_limiterObject



95
96
97
# File 'lib/aikido/zen.rb', line 95

def self.rate_limiter
  @rate_limiter ||= RateLimiter.new
end

.record_attack_wave(client_ip, sample) ⇒ Array<Aikido::Zen::AttackWave::Sample>?

Records a suspicious sample and, if the threshold for triggering an attack wave has been crossed, flags the client IP as having just triggered an attack wave.

In multiprocess deployments, on RPC failure, the worker process records against its local detector.

If RPC failures are intermittent, it is possible that an attack wave may be missed or duplicated, because state is split between the global and local detectors.

Parameters:

Returns:



306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/aikido/zen.rb', line 306

def self.record_attack_wave(client_ip, sample)
  worker_process_client = @worker_process_client

  if worker_process_client
    begin
      worker_process_client.record_attack_wave(client_ip, sample)
    rescue
      attack_wave_detector.record(client_ip, sample)
    end
  else
    attack_wave_detector.record(client_ip, sample)
  end
end

.request_bypassed!void

This method returns an undefined value.

Marks the current request as bypassed.



169
170
171
# File 'lib/aikido/zen.rb', line 169

def self.request_bypassed!
  current_context&.request_bypassed = true
end

.request_bypassed?Boolean

Returns whether the current request is bypassed.

Returns:

  • whether the current request is bypassed.



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

def self.request_bypassed?
  context = current_context
  return unless context

  return context.request_bypassed unless context.request_bypassed.nil?

  context.request_bypassed = runtime_settings.bypassed_ip?(context.request.client_ip)
end

.runtime_settingsAikido::Zen::RuntimeSettings

Returns the firewall configuration sourced from your Aikido dashboard. This is periodically polled for updates.

Returns:

  • the firewall configuration sourced from your Aikido dashboard. This is periodically polled for updates.



83
84
85
# File 'lib/aikido/zen.rb', line 83

def self.runtime_settings
  @runtime_settings ||= RuntimeSettings.new
end

.runtime_settings=(settings) ⇒ Object



87
88
89
# File 'lib/aikido/zen.rb', line 87

def self.runtime_settings=(settings)
  @runtime_settings = settings
end

.secretObject



113
114
115
# File 'lib/aikido/zen.rb', line 113

def self.secret
  @secret ||= SecureRandom.bytes(32)
end

.set_tenant_id(tenant_id) ⇒ void

This method returns an undefined value.

Set the tenant ID for the current request.

Parameters:



351
352
353
354
355
356
# File 'lib/aikido/zen.rb', line 351

def self.set_tenant_id(tenant_id)
  context = current_context
  return unless context

  context.request.tenant_id = tenant_id
end

.start!Object



431
432
433
434
435
436
437
438
439
440
# File 'lib/aikido/zen.rb', line 431

def start!
  return unless start?

  return unless @has_started.make_true

  @worker_process_server = WorkerProcess::Agent::Server.new
  @worker_process_server.start

  @agent = Agent.start
end

.start?Boolean

Returns:



442
443
444
445
446
# File 'lib/aikido/zen.rb', line 442

def start?
  !config.api_token.nil? ||
    config.blocking_mode? ||
    config.debugging?
end

.system_infoObject

Gets information about the current system configuration, which is sent to the server along with any events.



129
130
131
# File 'lib/aikido/zen.rb', line 129

def self.system_info
  @system_info ||= SystemInfo.new
end

.track_attack_wave(_attack_wave) ⇒ void

This method returns an undefined value.

Track statistics about an attack wave the app is handling.

Parameters:



207
208
209
# File 'lib/aikido/zen.rb', line 207

def self.track_attack_wave(_attack_wave)
  collector.track_attack_wave(being_blocked: false)
end

.track_discovered_route(request) ⇒ void

This method returns an undefined value.

Track statistics about a route that the app has discovered.

Parameters:



215
216
217
# File 'lib/aikido/zen.rb', line 215

def self.track_discovered_route(request)
  collector.track_route(request)
end

.track_ip_list(ip_list_keys) ⇒ void

This method returns an undefined value.

Track blocked and monitored IP lists.

Parameters:

  • the IP list keys from matching runtime firewall list IP lists.



199
200
201
# File 'lib/aikido/zen.rb', line 199

def self.track_ip_list(ip_list_keys)
  collector.track_ip_list(ip_list_keys)
end

.track_outbound(connection) ⇒ void

This method returns an undefined value.

Tracks a network connection made to an external service.

Parameters:



223
224
225
# File 'lib/aikido/zen.rb', line 223

def self.track_outbound(connection)
  collector.track_outbound(connection)
end

.track_rate_limited_request(_request) ⇒ Object



181
182
183
# File 'lib/aikido/zen.rb', line 181

def self.track_rate_limited_request(_request)
  collector.track_rate_limited_request
end

.track_request(_request) ⇒ void

This method returns an undefined value.

Track statistics about an HTTP request the app is handling.

Parameters:



177
178
179
# File 'lib/aikido/zen.rb', line 177

def self.track_request(_request)
  collector.track_request
end

.track_scan(scan) ⇒ void

This method returns an undefined value.

Track statistics about the result of a Sink's scan, and report it as an Attack if one is detected.

Parameters:

Raises:

  • if the scan detected an Attack and blocking_mode is enabled.



242
243
244
245
# File 'lib/aikido/zen.rb', line 242

def self.track_scan(scan)
  collector.track_scan(scan)
  agent.handle_attack(scan.attack) if scan.attack?
end

.track_user(user) ⇒ void Also known as: set_user

This method returns an undefined value.

Track the user making the current request.



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/aikido/zen.rb', line 251

def self.track_user(user)
  return if config.disabled?

  if (actor = Aikido::Zen::Actor(user))
    collector.track_user(actor)
    current_context.request.actor = actor if current_context
  else
    config.logger.warn(format("      Incompatible object sent to track_user: %<obj>p\n\n      The object must either implement #to_aikido_actor, or be a Hash with\n      an :id (or \"id\") and, optionally, a :name (or \"name\") key.\n    LOG\n  end\nend\n", obj: user))

.track_user_agent(user_agent_keys) ⇒ void

This method returns an undefined value.

Track blocked and monitored user agents.

Parameters:

  • the user agent keys from matching runtime firewall list user agent details.



190
191
192
# File 'lib/aikido/zen.rb', line 190

def self.track_user_agent(user_agent_keys)
  collector.track_user_agent(user_agent_keys)
end

.without_idor_protection { ... } ⇒ Object

Execute a block with the IDOR protection disabled.

Yields:

  • the block to execute with the IDOR protection disabled.

Returns:

  • the result of the block

Raises:

  • if no block is given



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/aikido/zen.rb', line 363

def self.without_idor_protection
  raise ArgumentError, "block required" unless block_given?

  context = current_context

  if context
    begin
      original_idor_protection_enabled = context.idor_protection_enabled
      context.idor_protection_enabled = false
      yield
    ensure
      context.idor_protection_enabled = original_idor_protection_enabled
    end
  else
    yield
  end
end

.worker_process_serverObject



424
425
426
# File 'lib/aikido/zen.rb', line 424

def self.worker_process_server
  @worker_process_server
end