class Sqreen::Frameworks::GenericFramework

This is the base class for framework specific code

Constants

LOCALHOST
PREFERRED_IP_HEADERS
P_FORM
P_GRAPE
P_QUERY
P_RACK
P_RACK_ROUTING
TRUSTED_PROXIES

Sourced from rack:Request#trusted_proxy?

Attributes

req_end_cb[W]
req_start_cb[W]
sqreen_configuration[RW]

Public Class Methods

cookies_params(request) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 371
def self.cookies_params(request)
  return nil unless request
  begin
    request.cookies
  rescue => e
    Sqreen.log.debug("cookies are invalid #{e.inspect}")
    nil
  end
end
form_params(request) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 351
def self.form_params(request)
  return nil unless request
  begin
    request.POST
  rescue => e
    Sqreen.log.debug("POST Parameters are invalid #{e.inspect}")
    nil
  end
end
new() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 27
def initialize
  clean_request_record

  # for notifying the ecosystem of request boundaries
  # XXX: this should be refactored. It shouldn't be
  # the framework doing these notifications to the ecosystem
  # Probably the rule callback should do it itself
  @req_start_cb = Proc.new {}
  @req_end_cb = Proc.new {}
end
parameters_from_request(request) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 395
def self.parameters_from_request(request)
  return {} unless request

  r = {
    P_FORM   => form_params(request),
    P_QUERY  => query_params(request),
    P_COOKIE => cookies_params(request),
  }
  if (p = rack_params(request))
    r[P_RACK] = p
  end
  p = request.env['sqreen.request.graphql_args']
  r['graphql'] = p if p
  # Add grape parameters if seen
  p = request.env['grape.request.params']
  r[P_GRAPE] = p if p
  p = request.env['rack.routing_args']
  if p
    r[P_RACK_ROUTING] = p.dup
    r[P_RACK_ROUTING].delete :route_info
    r[P_RACK_ROUTING].delete :version
  end
  r
end
query_params(request) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 381
def self.query_params(request)
  return nil unless request
  begin
    request.GET
  rescue => e
    Sqreen.log.debug("GET Parameters are invalid #{e.inspect}")
    nil
  end
end
rack_params(request) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 361
def self.rack_params(request)
  return nil unless request
  begin
    request.params
  rescue => e
    Sqreen.log.debug("Rack Parameters are invalid #{e.inspect}")
    nil
  end
end

Public Instance Methods

application_name() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 226
def application_name
  nil
end
body() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 420
def body
  return nil unless request.respond_to?(:body)
  return nil unless request.body.respond_to?(:read)
  return nil unless request.body.respond_to?(:rewind)

  body_io = request.body
  body = body_io.read(4096)
  body_io.rewind

  body
end
clean_request() click to toggle source

Cleanup request context

# File lib/sqreen/frameworks/generic.rb, line 308
def clean_request
  payload_creator = Sqreen::PayloadCreator.new(self)
  close_request_record(Sqreen.queue, Sqreen.observations_queue, payload_creator)
  self.remaining_perf_budget = nil
  SharedStorage.set(:request, nil)
  SharedStorage.set(:response, nil)
  SharedStorage.set(:xss_params, nil)
  SharedStorage.set(:whitelisted, nil)
  SharedStorage.set(:request_overtime, nil)
  @req_end_cb.call
end
client_ip() click to toggle source

What is the current client IP

# File lib/sqreen/frameworks/generic.rb, line 106
def client_ip
  req = request
  return nil unless req
  # Look for an external address being forwarded
  split_ips = []
  preferred_ip_headers.each do |header_name|
    forwarded = req.env[header_name]
    ips = split_ip_addresses(forwarded)
    lip = ips.find { |ip| (ip !~ TRUSTED_PROXIES) && valid_ip?(ip) }
    split_ips << ips unless ips.empty?
    return lip unless lip.nil?
  end
  # Else fall back to declared remote addr
  r = req.env['REMOTE_ADDR']
  # If this is localhost get the last hop before
  if r.nil? || r =~ LOCALHOST
    split_ips.each do |ips|
      lip = ips.find { |ip| (ip !~ LOCALHOST) && valid_ip?(ip) }
      return lip unless lip.nil?
    end
  end
  r
end
client_user_agent() click to toggle source

request user agent

# File lib/sqreen/frameworks/generic.rb, line 215
def client_user_agent
  req = request
  return nil unless req
  req.env['HTTP_USER_AGENT']
end
cwd() click to toggle source

Expose current working directory

# File lib/sqreen/frameworks/generic.rb, line 433
def cwd
  Dir.getwd
end
datadog_span() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 184
def datadog_span
  return unless defined?(Datadog) && (tracer = Datadog.tracer)

  tracer.active_span
end
db_settings(_options = {}) click to toggle source

What kind of database is this

# File lib/sqreen/frameworks/generic.rb, line 39
def db_settings(_options = {})
  raise Sqreen::NotImplementedYet
end
development?() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 53
def development?
  ENV['RACK_ENV'] == 'development'
end
filtered_request_params() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 332
def filtered_request_params
  params = request_params
  params.delete('cookies')
  params
end
framework_infos() click to toggle source

More information about the current framework

# File lib/sqreen/frameworks/generic.rb, line 44
def framework_infos
  raise Sqreen::NotImplementedYet unless ensure_rack_loaded
  {
    :framework_type => 'Rack',
    :framework_version => Rack.version,
    :environment => ENV['RACK_ENV'],
  }
end
full_params_include?(value, params = nil) click to toggle source

Does the parameters key/value include this value

# File lib/sqreen/frameworks/generic.rb, line 263
def full_params_include?(value, params = nil)
  params = request_params if params.nil?
  return false if params.nil?
  each_key_value_for_hash(params) do |param|
    return true if param == value
  end
  false
end
graphql_args=(args) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 391
def graphql_args=(args)
  request.env['sqreen.request.graphql_args'] = args if request
end
header(name) click to toggle source

Get a header by name

# File lib/sqreen/frameworks/generic.rb, line 131
def header(name)
  req = request
  return nil unless req
  req.env[name]
end
hostname() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 145
def hostname
  req = request
  return nil unless req
  http_host = req.env['HTTP_HOST']
  return http_host if http_host && !http_host.empty?
  req.env['SERVER_NAME']
end
http_headers() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 137
def http_headers
  req = request
  return nil unless req
  # dup to avoid potential error because of change (in another thread)
  # during iteration
  req.env.dup.select { |k, _| k.to_s.start_with?('HTTP_') }
end
instrument_when_ready!(instrumentor, rules) click to toggle source

Instrument with our rules when the framework as finished loading

# File lib/sqreen/frameworks/generic.rb, line 248
def instrument_when_ready!(instrumentor, rules)
  instrumentor.instrument!(rules, self)
end
ip_headers() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 81
def ip_headers
  req = request
  return [] unless req
  ips = []
  (preferred_ip_headers + ['REMOTE_ADDR']).each do |header|
    v = req.env[header]
    ips << [header, v] unless v.nil?
  end
  ips << ['rack.ip', req.ip] if req.respond_to?(:ip)
  ips
end
mark_request_overtime!() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 272
def mark_request_overtime!
  over = SharedStorage.get(:request_overtime)
  return false if over
  SharedStorage.set(:request_overtime, true)
  true
end
params_include?(value, params = nil) click to toggle source

Does the parameters value include this value

# File lib/sqreen/frameworks/generic.rb, line 253
def params_include?(value, params = nil)
  params = request_params if params.nil?
  return false if params.nil?
  each_value_for_hash(params) do |param|
    return true if param == value
  end
  false
end
prevent_startup() click to toggle source

Should the agent not be starting up?

# File lib/sqreen/frameworks/generic.rb, line 231
def prevent_startup
  # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
  return :sidekiq_cli if defined?(Sidekiq::CLI)
  return :delayed_job if defined?(Delayed::Command)

  # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
  run_in_test = sqreen_configuration.get(:run_in_test)
  return :rake if !run_in_test && $0.end_with?('rake')

  return :irb if $0 == 'irb'

  return if sqreen_configuration.nil?
  disable = sqreen_configuration.get(:disable)
  return :config_disable if disable == true || disable.to_s.to_i == 1
end
rack_client_ip() click to toggle source

What is the current client IP as seen by rack

# File lib/sqreen/frameworks/generic.rb, line 94
def rack_client_ip
  req = request
  return nil unless req
  return req.ip if req.respond_to?(:ip)
  req.env['REMOTE_ADDR']
end
remaining_perf_budget() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 320
def remaining_perf_budget
  SharedStorage.get(:performance_budget)
end
remaining_perf_budget=(value) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 324
def remaining_perf_budget=(value)
  SharedStorage.set(:performance_budget, value)
end
remote_addr() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 462
def remote_addr
  return nil unless request
  request.env['REMOTE_ADDR']
end
request() click to toggle source

Get the currently stored request

# File lib/sqreen/frameworks/generic.rb, line 299
def request
  SharedStorage.get(:request)
end
request_id() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 153
def request_id
  req = request
  return nil unless req
  req.env['HTTP_X_REQUEST_ID']
end
request_infos() click to toggle source

Summary of known request infos

# File lib/sqreen/frameworks/generic.rb, line 160
def request_infos
  req = request
  return {} unless req
  # FIXME: Use frozen string keys
  {
    :rid => request_id,
    :user_agent => client_user_agent,
    :scheme => req.scheme,
    :verb => req.env['REQUEST_METHOD'],
    :host => hostname,
    :port => req.env['SERVER_PORT'],
    :referer => req.env['HTTP_REFERER'],
    :path => request_path,
    :remote_port => req.env['REMOTE_PORT'],
    :remote_ip => remote_addr,
    :client_ip => client_ip,
  }.tap do |h|
    h.merge!(
      :datadog_trace_id => datadog_span.trace_id,
      :datadog_span_id => datadog_span.span_id,
    ) if datadog_span
  end
end
request_params() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 328
def request_params
  self.class.parameters_from_request(request)
end
request_path() click to toggle source

Request URL path

# File lib/sqreen/frameworks/generic.rb, line 208
def request_path
  req = request
  return nil unless req
  req.script_name + req.path_info
end
response() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 303
def response
  SharedStorage.get(:response)
end
response_infos() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 190
def response_infos
  return {} if response.nil?

  content_type = response.header['Content-Type']
  content_length = begin
                     Integer(response.header['Content-Length'])
                   rescue ArgumentError, TypeError
                     nil
                   end

  {
    :status => response.status,
    :content_type => content_type,
    :content_length => content_length,
  }
end
root() click to toggle source

Application root

# File lib/sqreen/frameworks/generic.rb, line 222
def root
  nil
end
store_request(object) click to toggle source

Fetch and store the current request object Nota: cleanup should be performed at end of request (see clean_request)

# File lib/sqreen/frameworks/generic.rb, line 281
def store_request(object)
  return unless ensure_rack_loaded

  rack_req = Rack::Request.new(object)
  @req_start_cb.call(rack_req)

  self.remaining_perf_budget = Sqreen.performance_budget
  SharedStorage.set(:request, rack_req)
  SharedStorage.set(:xss_params, nil)
  SharedStorage.set(:whitelisted, nil)
  SharedStorage.set(:request_overtime, nil)
end
store_response(rv, _env) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 294
def store_response(rv, _env)
  SharedStorage.set(:response, Rack::Response.new([], rv[0], rv[1]))
end
test?() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 57
def test?
  ENV['RACK_ENV'] == 'test'
end
whitelisted_ip() click to toggle source

Returns the current path that whitelist the request

# File lib/sqreen/frameworks/generic.rb, line 454
def whitelisted_ip
  ip = client_ip
  return nil unless ip
  find_whitelisted_ip(ip)
rescue
  nil
end
whitelisted_match() click to toggle source

Return the current item that whitelist this request returns nil if request is not whitelisted

# File lib/sqreen/frameworks/generic.rb, line 439
def whitelisted_match
  return nil unless request
  whitelisted = whitelisted_ip || whitelisted_path
  SharedStorage.set(:whitelisted, !whitelisted.nil?)
  whitelisted
end
whitelisted_path() click to toggle source

Returns the current path that whitelist the request

# File lib/sqreen/frameworks/generic.rb, line 447
def whitelisted_path
  path = request_path
  return nil unless path
  find_whitelisted_path(path)
end
xss_params(regexp = nil) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 468
def xss_params(regexp = nil)
  p = SharedStorage.get(:xss_params)
  return p unless p.nil?
  p = request_params
  parm = Set.new
  each_key_value_for_hash(p) do |value|
    next unless value.is_a?(String)
    next if value.size < 5
    value = value.dup.force_encoding(Encoding::ISO_8859_1).encode(Encoding::UTF_8) unless value.valid_encoding?
    next if regexp && !regexp.match?(value)
    parm << value
  end
  p = parm.to_a
  Sqreen.log.debug { "Filtered XSS params: #{p.inspect}" }
  SharedStorage.set(:xss_params, p)
  p
end

Protected Instance Methods

each_key_value_for_hash(params) { |k| ... } click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 547
def each_key_value_for_hash(params, &block)
  case params
  when Hash then params.each do |k, v|
    yield k
    each_key_value_for_hash(v, &block)
  end
  when Array then params.each { |v| each_key_value_for_hash(v, &block) }
  else
    yield params
  end
end
each_value_for_hash(params) { |params| ... } click to toggle source

FIXME: Extract to another object (utils?) FIXME: protect against cycles ?

# File lib/sqreen/frameworks/generic.rb, line 538
def each_value_for_hash(params, &block)
  case params
  when Hash  then params.each { |_k, v| each_value_for_hash(v, &block) }
  when Array then params.each { |v| each_value_for_hash(v, &block) }
  else
    yield params
  end
end
ensure_rack_loaded() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 559
def ensure_rack_loaded
  @cannot_load_rack ||= false
  return false if @cannot_load_rack
  require 'rack' unless defined?(Rack)
  true
rescue LoadError => e
  # FIXME: find a nice way to test this branch
  Sqreen::RemoteException.record(e)
  @cannot_load_rack = true
  false
end
find_whitelisted_ip(rip) click to toggle source

Is this a whitelisted ip? return the ip witelisted range that match ip

# File lib/sqreen/frameworks/generic.rb, line 517
def find_whitelisted_ip(rip)
  ret = (Sqreen.whitelisted_ips || {}).find do |_, ip|
    ip.include?(rip)
  end
  return nil unless ret
  ret.first
end
find_whitelisted_path(rpath) click to toggle source

Is this a whitelisted path? return the path witelisted prefix that match path

# File lib/sqreen/frameworks/generic.rb, line 509
def find_whitelisted_path(rpath)
  (Sqreen.whitelisted_paths || []).find do |path|
    rpath.start_with?(path)
  end
end
hook_rack_builder() click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 525
def hook_rack_builder
  Rack::Builder.class_eval do
    define_method(:to_app_with_sqreen) do |*args, &block|
      Sqreen.framework.to_app_done(Process.pid)
      to_app_without_sqreen(*args, &block)
    end
    alias_method :to_app_without_sqreen, :to_app
    alias_method :to_app, :to_app_with_sqreen
  end
end

Private Instance Methods

preferred_ip_headers() click to toggle source

TODO: remove global config_get

# File lib/sqreen/frameworks/generic.rb, line 67
def preferred_ip_headers
  @preferred_ip_headers ||=
    begin
      header_name = Sqreen.config_get(:ip_header)
      if header_name
        env_var = 'HTTP_' + header_name.tr('-', '_').upcase
        [env_var] + (PREFERRED_IP_HEADERS - [env_var])
      else
        PREFERRED_IP_HEADERS
      end
    end
end
split_ip_addresses(ip_addresses) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 573
def split_ip_addresses(ip_addresses)
  return [] unless ip_addresses

  r = ip_addresses.strip.split(/[,\s]+/)
  r.map! do |ip|
    m = /^(?:(?<ip>.*\..*)\:|\[(?<ip>.*)\]\:)/.match(ip)
    m ? m[:ip] : ip
  end
end
valid_ip?(ip) click to toggle source
# File lib/sqreen/frameworks/generic.rb, line 583
def valid_ip?(ip)
  IPAddr.new(ip)
  true
rescue
  false
end