class Sqreen::Rules::WAFCB

Constants

ERROR_CODES
INFINITE_BUDGET_US

will be converted to a long, so better not to overflow

MAX_FIXNUM

2^30 -1 or 2^62 -1

Attributes

binding_accessors[R]
max_run_budget_us[R]
waf_rule_name[R]

Public Class Methods

finalizer(rule_name) click to toggle source
# File lib/sqreen/rules/waf_cb.rb, line 125
def self.finalizer(rule_name)
  lambda do |object_id|
    return unless WAFCB.libsqreen?

    ::LibSqreen::WAF.delete(waf_rule_name)
    Sqreen.log.debug("WAF rule #{rule_name} deleted, from #<#{name}:0x#{object_id.to_s(16).rjust(16, '0')}>")
  end
end
libsqreen?() click to toggle source
# File lib/sqreen/rules/waf_cb.rb, line 24
def self.libsqreen?
  Sqreen::Dependency::LibSqreen.required?
end
new(*args) click to toggle source
Calls superclass method Sqreen::Rules::RuleCB::new
# File lib/sqreen/rules/waf_cb.rb, line 34
def initialize(*args)
  super(*args)
  @overtimeable = false

  unless WAFCB.libsqreen? && WAFCB.waf?
    Sqreen.log.warn('libsqreen gem with waf not found')
    return
  end

  unless @data['values']
    Sqreen.log.warn('no values in data')
    return
  end

  ::LibSqreen::WAF.logger = Sqreen.log

  name = format("%s_%s", SecureRandom.uuid, rule_name)
  unless @data['values']['waf_rules'] && (::LibSqreen::WAF[name] = @data['values']['waf_rules'])
    Sqreen.log.error("WAF rule #{name} failed to be set, from #<#{self.class.name}:0x#{object_id.to_s(16).rjust(16, '0')}>")
    return
  end
  @waf_rule_name = name
  Sqreen.log.debug("WAF rule #{name} set, from #<#{self.class.name}:0x#{object_id.to_s(16).rjust(16, '0')}>")

  @binding_accessors = @data['values'].fetch('binding_accessors', []).each_with_object({}) do |e, h|
    h[e] = BindingAccessor.new(e)
  end

  # 0 for using defaults (PW_RUN_TIMEOUT)
  @max_run_budget_us = (@data['values'].fetch('max_budget_ms', 0) * 1000).to_i
  @max_run_budget_us = INFINITE_BUDGET_US if @max_run_budget_us >= INFINITE_BUDGET_US

  Sqreen.log.debug { "Max WAF run budget for #{@waf_rule_name} set to #{@max_run_budget_us} us" }

  ObjectSpace.define_finalizer(self, WAFCB.finalizer(@waf_rule_name.dup))
end
waf?() click to toggle source
# File lib/sqreen/rules/waf_cb.rb, line 28
def self.waf?
  Sqreen::Dependency.const_exist?('LibSqreen::WAF')
end

Public Instance Methods

pre(instance, args, budget) click to toggle source
# File lib/sqreen/rules/waf_cb.rb, line 71
def pre(instance, args, budget)
  return unless WAFCB.libsqreen? && WAFCB.waf?

  request = framework.request
  return if !waf_rule_name || !request

  env = [binding, framework, instance, args]

  start = Sqreen.time if budget

  capper = Sqreen::Util::Capper.new(string_size_cap: 4096, size_cap: 150, depth_cap: 10)
  waf_args = binding_accessors.each_with_object({}) do |(e, b), h|
    h[e] = capper.call(b.resolve(*env))
  end
  waf_args = Sqreen::Kit::StringSanitizer.sanitize(waf_args)

  if budget
    rem_budget_s = budget - (Sqreen.time - start)
    return advise_action(nil) if rem_budget_s <= 0.0

    waf_gen_budget_us = [(rem_budget_s * 1_000_000).to_i, MAX_FIXNUM].min
  else # no budget
    waf_gen_budget_us = INFINITE_BUDGET_US
  end

  action, data = ::LibSqreen::WAF.run(waf_rule_name, waf_args,
                                      waf_gen_budget_us, @max_run_budget_us)

  case action
  when :monitor
    record_event({ waf_data: data })
    advise_action(nil)
  when :block
    record_event({ waf_data: data })
    advise_action(:raise)
  when :good
    advise_action(nil)
  when :timeout
    Sqreen.log.debug("WAF over time budget: #{action}")
    advise_action(nil)
  when :invalid_call
    Sqreen.log.debug("Error from waf: #{action}")
    advise_action(nil)
    raise Sqreen::WAFError.new(waf_rule_name, action, data, waf_args)
  when :invalid_rule, :invalid_flow, :no_rule
    Sqreen.log.debug("error from waf: #{action}")
    advise_action(nil)
    raise Sqreen::WAFError.new(waf_rule_name, action, data)
  else
    Sqreen.log.warn("unexpected action returned from waf")
    advise_action(nil)
  end
end
record_exception(exception, infos = {}, at = Time.now.utc) click to toggle source
Calls superclass method Sqreen::Rules::RuleCB#record_exception
# File lib/sqreen/rules/waf_cb.rb, line 134
def record_exception(exception, infos = {}, at = Time.now.utc)
  infos.merge!(waf_infos(exception)) if exception.is_a?(Sqreen::WAFError)
  super(exception, infos, at)
end

Private Instance Methods

waf_infos(e) click to toggle source

see github.com/sqreen/TechDoc/blob/master/content/specs/spec000016-waf-integration.md#error-management

# File lib/sqreen/rules/waf_cb.rb, line 142
def waf_infos(e)
  {
    waf:  {
      waf_rule: e.rule_name,
      error_code: ERROR_CODES[e.error],
    }.tap do |r|
      r[:error_data] = e.data if e.data
      r[:args] = e.args if e.arg
    end,
  }
end