class Sqreen::Frameworks::GenericFramework
This is the base class for framework specific code
Constants
- LOCALHOST
- PREFERRED_IP_HEADERS
- P_COOKIE
- P_FORM
- P_GRAPE
- P_QUERY
- P_RACK
- P_RACK_ROUTING
- TRUSTED_PROXIES
Sourced from rack:Request#trusted_proxy?
Attributes
Public Class Methods
# 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
# 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
# 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
# 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
# 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
# File lib/sqreen/frameworks/generic.rb, line 226 def application_name nil end
# 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
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
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
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
Expose current working directory
# File lib/sqreen/frameworks/generic.rb, line 433 def cwd Dir.getwd end
# File lib/sqreen/frameworks/generic.rb, line 184 def datadog_span return unless defined?(Datadog) && (tracer = Datadog.tracer) tracer.active_span end
What kind of database is this
# File lib/sqreen/frameworks/generic.rb, line 39 def db_settings(_options = {}) raise Sqreen::NotImplementedYet end
# File lib/sqreen/frameworks/generic.rb, line 53 def development? ENV['RACK_ENV'] == 'development' end
# File lib/sqreen/frameworks/generic.rb, line 332 def filtered_request_params params = request_params params.delete('cookies') params end
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
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
# File lib/sqreen/frameworks/generic.rb, line 391 def graphql_args=(args) request.env['sqreen.request.graphql_args'] = args if request end
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
# 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
# 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 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
# 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
# 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
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
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
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
# File lib/sqreen/frameworks/generic.rb, line 320 def remaining_perf_budget SharedStorage.get(:performance_budget) end
# File lib/sqreen/frameworks/generic.rb, line 324 def remaining_perf_budget=(value) SharedStorage.set(:performance_budget, value) end
# File lib/sqreen/frameworks/generic.rb, line 462 def remote_addr return nil unless request request.env['REMOTE_ADDR'] end
Get the currently stored request
# File lib/sqreen/frameworks/generic.rb, line 299 def request SharedStorage.get(:request) end
# File lib/sqreen/frameworks/generic.rb, line 153 def request_id req = request return nil unless req req.env['HTTP_X_REQUEST_ID'] end
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
# File lib/sqreen/frameworks/generic.rb, line 328 def request_params self.class.parameters_from_request(request) end
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
# File lib/sqreen/frameworks/generic.rb, line 303 def response SharedStorage.get(:response) end
# 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
Application root
# File lib/sqreen/frameworks/generic.rb, line 222 def root nil end
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
# File lib/sqreen/frameworks/generic.rb, line 294 def store_response(rv, _env) SharedStorage.set(:response, Rack::Response.new([], rv[0], rv[1])) end
# File lib/sqreen/frameworks/generic.rb, line 57 def test? ENV['RACK_ENV'] == 'test' end
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
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
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
# 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
# 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
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
# 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
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
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
# 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
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
# 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
# File lib/sqreen/frameworks/generic.rb, line 583 def valid_ip?(ip) IPAddr.new(ip) true rescue false end