Class: Otto::Security::Configurator

Inherits:
Object
  • Object
show all
Defined in:
lib/otto/security/configurator.rb

Overview

Consolidates all security configuration methods into a single configurator class. This provides a unified interface for configuring CSRF protection, input validation, rate limiting, trusted proxies, and authentication strategies.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(security_config, middleware_stack, auth_config = nil) ⇒ Configurator

Returns a new instance of Configurator.



19
20
21
22
23
24
# File 'lib/otto/security/configurator.rb', line 19

def initialize(security_config, middleware_stack, auth_config = nil)
  @security_config = security_config
  @middleware_stack = middleware_stack
  # Use provided auth_config or initialize a new one
  @auth_config = auth_config || { auth_strategies: {}, default_auth_strategy: 'noauth' }
end

Instance Attribute Details

#auth_configObject

Returns the value of attribute auth_config.



17
18
19
# File 'lib/otto/security/configurator.rb', line 17

def auth_config
  @auth_config
end

#middleware_stackObject (readonly)

Returns the value of attribute middleware_stack.



16
17
18
# File 'lib/otto/security/configurator.rb', line 16

def middleware_stack
  @middleware_stack
end

#security_configObject (readonly)

Returns the value of attribute security_config.



16
17
18
# File 'lib/otto/security/configurator.rb', line 16

def security_config
  @security_config
end

Instance Method Details

#add_auth_strategy(name, strategy) ⇒ Object

Add a single authentication strategy

Part of the Security::Configurator facade for consolidated configuration. This delegates to the same storage as Otto#add_auth_strategy, allowing authentication to be configured alongside other security features.

Prefer using Otto#add_auth_strategy directly for simpler cases, or use this when configuring multiple security features together via the security facade.

Examples:

otto.security.add_auth_strategy('session', SessionStrategy.new)

Parameters:

Raises:

  • (ArgumentError)

    if strategy name already registered



299
300
301
302
303
304
305
306
# File 'lib/otto/security/configurator.rb', line 299

def add_auth_strategy(name, strategy)
  # Strict mode: Detect strategy name collisions
  if @auth_config[:auth_strategies].key?(name)
    raise ArgumentError, "Authentication strategy '#{name}' is already registered"
  end

  @auth_config[:auth_strategies][name] = strategy
end

#add_rate_limit_rule(name, options) ⇒ Object

Add a custom rate limiting rule.

Parameters:

  • name (String, Symbol)

    Rule name

  • options (Hash)

    Rule configuration

Options Hash (options):

  • :limit (Integer)

    Maximum requests

  • :period (Integer)

    Time period in seconds (default: 60)

  • :condition (Proc)

    Optional condition proc that receives request



143
144
145
# File 'lib/otto/security/configurator.rb', line 143

def add_rate_limit_rule(name, options)
  @security_config.rate_limiting_config[:custom_rules][name.to_s] = options
end

#add_trusted_proxy(proxy) ⇒ Object

Add a trusted proxy server for accurate client IP detection. Only requests from trusted proxies will have their forwarded headers honored.

Parameters:

  • proxy (String, Regexp)

    IP address, CIDR range, or regex pattern



151
152
153
# File 'lib/otto/security/configurator.rb', line 151

def add_trusted_proxy(proxy)
  @security_config.add_trusted_proxy(proxy)
end

#configure(csrf_protection: false, request_validation: false, rate_limiting: false, trusted_proxies: [], trusted_proxy_depth: nil, trusted_proxy_header: nil, security_headers: {}, hsts: false, csp: false, frame_protection: false, authentication: false) ⇒ Object

Unified security configuration method with sensible defaults

Provides a comprehensive, one-stop configuration method for Otto's security features. This method allows configuring multiple security aspects in a single call, with flexible options.

Examples:

Configure multiple security features in one call

otto.security.configure(
  csrf_protection: true,
  request_validation: true,
  rate_limiting: { requests_per_minute: 100 },
  trusted_proxies: ['10.0.0.0/8'],
  security_headers: { 'x-custom-header' => 'value' },
  hsts: true,
  csp: "default-src 'self'",
  frame_protection: 'SAMEORIGIN'
)

Parameters:

  • csrf_protection (Boolean, Hash) (defaults to: false)

    Enable CSRF protection

    • true: Enable with default settings
    • Hash: Provide custom CSRF configuration
  • request_validation (Boolean) (defaults to: false)

    Enable input validation and sanitization

  • rate_limiting (Boolean, Hash) (defaults to: false)

    Enable rate limiting

    • true: Enable with default settings
    • Hash: Provide custom rate limiting rules
  • trusted_proxies (String, Array<String>, Symbol) (defaults to: [])

    IP addresses or CIDR ranges to trust, or :none to assert that no proxy is trusted

  • trusted_proxy_depth (Integer, nil) (defaults to: nil)

    Count-based proxy depth ("trust the last N hops") for non-enumerable proxy tiers; mutually exclusive with trusted_proxies (validated at configuration freeze)

  • trusted_proxy_header (String, nil) (defaults to: nil)

    Forwarded header depth mode counts hops from: 'X-Forwarded-For' (default), 'Forwarded' (RFC 7239), or 'Both'. Otto reads it only in depth mode, but setting it always pins Rack::Request.forwarded_priority (process-global) to that family and claims the family for this process; see Otto::Security::Config.apply_rack_forwarding_family!.

  • security_headers (Hash) (defaults to: {})

    Custom security headers to merge with defaults

  • hsts (Boolean) (defaults to: false)

    Enable HTTP Strict Transport Security

  • csp (Boolean, String) (defaults to: false)

    Enable Content Security Policy

  • frame_protection (Boolean, String) (defaults to: false)

    Enable frame protection

  • authentication (Boolean) (defaults to: false)

    Enable authentication



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
96
97
98
99
100
101
# File 'lib/otto/security/configurator.rb', line 66

def configure(
  csrf_protection: false,
  request_validation: false,
  rate_limiting: false,
  trusted_proxies: [],
  trusted_proxy_depth: nil,
  trusted_proxy_header: nil,
  security_headers: {},
  hsts: false,
  csp: false,
  frame_protection: false,
  authentication: false
)
  enable_csrf_protection! if csrf_protection
  enable_request_validation! if request_validation
  enable_rate_limiting!(rate_limiting.is_a?(Hash) ? rate_limiting : {}) if rate_limiting

  if Otto::Security::Config.trust_no_proxies_option?(trusted_proxies)
    trust_no_proxies!
  else
    # Pass the list whole so add_trusted_proxy validates every entry
    # before registering any; a mixed list like ['10.0.0.0/8', 'none']
    # must not leave the first half installed.
    add_trusted_proxy(Array(trusted_proxies)) unless Array(trusted_proxies).empty?
  end
  self.trusted_proxy_depth = trusted_proxy_depth unless trusted_proxy_depth.nil?
  self.trusted_proxy_header = trusted_proxy_header unless trusted_proxy_header.nil?
  # Proxy trust configured here (after Otto.new) commits the app to its
  # forwarding family now rather than at freeze.
  @security_config.commit_rack_forwarding_family!
  self.security_headers = security_headers unless security_headers.empty?

  enable_hsts! if hsts
  enable_csp! if csp
  enable_frame_protection! if frame_protection
end

#configure_auth_strategies(strategies, default_strategy: 'noauth') ⇒ Object

Configure authentication strategies for route-level access control.

Parameters:

  • strategies (Hash)

    Hash mapping strategy names to strategy instances

  • default_strategy (String) (defaults to: 'noauth')

    Default strategy to use when none specified



312
313
314
315
316
# File 'lib/otto/security/configurator.rb', line 312

def configure_auth_strategies(strategies, default_strategy: 'noauth')
  # Merge new strategies with existing ones, preserving shared state
  @auth_config[:auth_strategies].merge!(strategies)
  @auth_config[:default_auth_strategy] = default_strategy
end

#configure_rate_limiting(config) ⇒ Object

Configure rate limiting settings.

Parameters:

  • config (Hash)

    Rate limiting configuration

Options Hash (config):

  • :requests_per_minute (Integer)

    Maximum requests per minute per IP

  • :custom_rules (Hash)

    Hash of custom rate limiting rules

  • :cache_store (Object)

    Custom cache store for rate limiting



324
325
326
# File 'lib/otto/security/configurator.rb', line 324

def configure_rate_limiting(config)
  @security_config.rate_limiting_config.merge!(config)
end

#csp_report_to_url=(url) ⇒ Object

Configure the absolute URL for the modern Reporting API endpoint (report-to directive + Reporting-Endpoints header) without injecting middleware. Prefer #enable_csp_reporting! with endpoint_url: for the full turnkey setup.

Parameters:

  • url (String, nil)

    absolute endpoint URL, or nil to disable modern reporting



281
282
283
# File 'lib/otto/security/configurator.rb', line 281

def csp_report_to_url=(url)
  @security_config.csp_report_to_url = url
end

#csp_report_uri=(uri) ⇒ Object

Configure the CSP violation report path without injecting middleware. Prefer #enable_csp_reporting! for the full turnkey setup.

Parameters:

  • uri (String, nil)

    report path (matched against PATH_INFO), or nil to disable



271
272
273
# File 'lib/otto/security/configurator.rb', line 271

def csp_report_uri=(uri)
  @security_config.csp_report_uri = uri
end

#enable_csp!(policy = "default-src 'self'") ⇒ Object

Enable Content Security Policy (CSP) header to prevent XSS attacks. The default policy only allows resources from the same origin.

Parameters:

  • policy (String) (defaults to: "default-src 'self'")

    CSP policy string (default: "default-src 'self'")



212
213
214
# File 'lib/otto/security/configurator.rb', line 212

def enable_csp!(policy = "default-src 'self'")
  @security_config.enable_csp!(policy)
end

#enable_csp_emission!(eager: false, development_mode: nil) ⇒ Object

Mount Otto::Security::CSP::EmitMiddleware (passive backstop that emits a nonce CSP for responses lacking one, never clobbering). Enable nonce-CSP (#enable_csp_with_nonce!) for it to emit anything; until then it is INERT (a transparent pass-through), not an error, and the two may be enabled in either order. Emit-if-consumed by default — see Otto::Security::Core#enable_csp_emission!.

Parameters:

  • eager (Boolean) (defaults to: false)

    mint-and-emit for every eligible HTML response

  • development_mode (Boolean, #call, nil) (defaults to: nil)

    development-directive toggle; a callable is evaluated per request with the env



241
242
243
244
245
# File 'lib/otto/security/configurator.rb', line 241

def enable_csp_emission!(eager: false, development_mode: nil)
  return if middleware_enabled?(Otto::Security::CSP::EmitMiddleware)

  @middleware_stack.add(Otto::Security::CSP::EmitMiddleware, eager: eager, development_mode: development_mode)
end

#enable_csp_reporting!(report_uri, endpoint_url: nil) {|report| ... } ⇒ Object

Enable turnkey CSP violation reporting: set the report URI (appends a report-uri directive to emitted policies), register the callback, and inject Otto::Security::CSP::ReportMiddleware pinned OUTERMOST so it intercepts report POSTs ahead of CSRF regardless of enable order.

Parameters:

  • report_uri (String)

    path browsers POST reports to (matched against PATH_INFO)

  • endpoint_url (String, nil) (defaults to: nil)

    absolute URL for the modern Reporting API endpoint (emits report-to + Reporting-Endpoints); nil emits only the legacy report-uri

Yield Parameters:



257
258
259
260
261
262
263
264
265
# File 'lib/otto/security/configurator.rb', line 257

def enable_csp_reporting!(report_uri, endpoint_url: nil, &block)
  @security_config.csp_report_uri = report_uri
  @security_config.csp_report_to_url = endpoint_url unless endpoint_url.nil?
  @security_config.on_csp_violation(&block) if block

  return if middleware_enabled?(Otto::Security::CSP::ReportMiddleware)

  @middleware_stack.add_with_position(Otto::Security::CSP::ReportMiddleware, position: :outermost)
end

#enable_csp_with_nonce!(debug: false) ⇒ Object

Enable Content Security Policy (CSP) with nonce support for dynamic header generation. This enables the res.send_csp_headers response helper method.

Parameters:

  • debug (Boolean) (defaults to: false)

    Enable debug logging for CSP headers (default: false)



227
228
229
# File 'lib/otto/security/configurator.rb', line 227

def enable_csp_with_nonce!(debug: false)
  @security_config.enable_csp_with_nonce!(debug: debug)
end

#enable_csrf_protection!Object

Enable CSRF protection for POST, PUT, DELETE, and PATCH requests. This will automatically add CSRF tokens to HTML forms and validate them on unsafe HTTP methods.



106
107
108
109
110
111
# File 'lib/otto/security/configurator.rb', line 106

def enable_csrf_protection!
  return if middleware_enabled?(Otto::Security::Middleware::CSRFMiddleware)

  @security_config.enable_csrf_protection!
  @middleware_stack.add(Otto::Security::Middleware::CSRFMiddleware)
end

#enable_frame_protection!(option = 'SAMEORIGIN') ⇒ Object

Enable X-Frame-Options header to prevent clickjacking attacks.

Parameters:

  • option (String) (defaults to: 'SAMEORIGIN')

    Frame options: 'DENY', 'SAMEORIGIN', or 'ALLOW-FROM uri'



219
220
221
# File 'lib/otto/security/configurator.rb', line 219

def enable_frame_protection!(option = 'SAMEORIGIN')
  @security_config.enable_frame_protection!(option)
end

#enable_hsts!(max_age: 31_536_000, include_subdomains: true) ⇒ Object

Enable HTTP Strict Transport Security (HSTS) header. WARNING: This can make your domain inaccessible if HTTPS is not properly configured. Only enable this when you're certain HTTPS is working correctly.

Parameters:

  • max_age (Integer) (defaults to: 31_536_000)

    Maximum age in seconds (default: 1 year)

  • include_subdomains (Boolean) (defaults to: true)

    Apply to all subdomains (default: true)



204
205
206
# File 'lib/otto/security/configurator.rb', line 204

def enable_hsts!(max_age: 31_536_000, include_subdomains: true)
  @security_config.enable_hsts!(max_age: max_age, include_subdomains: include_subdomains)
end

#enable_rate_limiting!(options = {}) ⇒ Object

Enable rate limiting to protect against abuse and DDoS attacks. This will automatically add rate limiting rules based on client IP.

Parameters:

  • options (Hash) (defaults to: {})

    Rate limiting configuration options

Options Hash (options):

  • :requests_per_minute (Integer)

    Maximum requests per minute per IP (default: 100)

  • :custom_rules (Hash)

    Custom rate limiting rules



128
129
130
131
132
133
134
# File 'lib/otto/security/configurator.rb', line 128

def enable_rate_limiting!(options = {})
  return if middleware_enabled?(Otto::Security::Middleware::RateLimitMiddleware)

  Otto::Security::RateLimiting.ensure_available!
  configure_rate_limiting(options)
  @middleware_stack.add(Otto::Security::Middleware::RateLimitMiddleware)
end

#enable_request_validation!Object

Enable request validation including input sanitization, size limits, and protection against XSS and SQL injection attacks.



115
116
117
118
119
120
# File 'lib/otto/security/configurator.rb', line 115

def enable_request_validation!
  return if middleware_enabled?(Otto::Security::Middleware::ValidationMiddleware)

  @security_config.input_validation = true
  @middleware_stack.add(Otto::Security::Middleware::ValidationMiddleware)
end

#security_headers=(headers) ⇒ Object

Set custom security headers that will be added to all responses. These merge with the default security headers.

Parameters:

  • headers (Hash)

    Hash of header name => value pairs



194
195
196
# File 'lib/otto/security/configurator.rb', line 194

def security_headers=(headers)
  @security_config.security_headers.merge!(headers)
end

#trust_no_proxies!void

This method returns an undefined value.

Assert that NO proxy is trusted (equivalent to trusted_proxies: :none). Makes env false for every peer, so forwarded client-IP and host/scheme/port carriers are ignored and stripped. See Otto::Security::Config#trust_no_proxies!.

Raises:

  • (ArgumentError)

    if trusted proxies or a depth >= 1 are configured



162
163
164
# File 'lib/otto/security/configurator.rb', line 162

def trust_no_proxies!
  @security_config.trust_no_proxies!
end

#trusted_proxy_depth=(depth) ⇒ Object

Set count-based trusted-proxy depth ("trust the last N hops") for non-enumerable proxy tiers (Fly, cloud load balancers, dynamic reverse proxies). Mutually exclusive with trusted_proxies; the conflict is validated when the configuration is frozen.

Parameters:

  • depth (Integer, nil)

    number of trusted hops (nil/0 disables depth mode)



172
173
174
# File 'lib/otto/security/configurator.rb', line 172

def trusted_proxy_depth=(depth)
  @security_config.trusted_proxy_depth = depth
end

#trusted_proxy_header=(header) ⇒ Object

Select which forwarded header depth mode counts hops from: 'X-Forwarded-For' (default), 'Forwarded' (RFC 7239), or 'Both'. Otto reads the value only when depth mode is active, but setting it always pins Rack::Request.forwarded_priority (process-global) to that family and claims the family for this process, even under trusted_proxies: :none; see Otto::Security::Config.apply_rack_forwarding_family!. Mirrors OneTimeSecret's site.network.trusted_proxy.header.

Parameters:

  • header (String)

    one of Otto::Security::Config::TRUSTED_PROXY_HEADERS



186
187
188
# File 'lib/otto/security/configurator.rb', line 186

def trusted_proxy_header=(header)
  @security_config.trusted_proxy_header = header
end