Class: Sqreen::Frameworks::GenericFramework

Inherits:
Object
  • Object
show all
Includes:
RequestRecorder
Defined in:
lib/sqreen/frameworks/generic.rb

Overview

This is the base class for framework specific code

Constant Summary collapse

PREFERRED_IP_HEADERS =
%w[HTTP_X_FORWARDED_FOR HTTP_X_REAL_IP
HTTP_CLIENT_IP HTTP_X_FORWARDED
HTTP_X_CLUSTER_CLIENT_IP HTTP_FORWARDED_FOR
HTTP_FORWARDED HTTP_VIA].freeze
TRUSTED_PROXIES =

Sourced from rack:Request#trusted_proxy?

/\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
LOCALHOST =
/\A127\.0\.0\.1\Z|\A::1\Z|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
P_FORM =
'form'.freeze
P_RACK =
'rack'.freeze
P_QUERY =
'query'.freeze
'cookies'.freeze
P_GRAPE =
'grape_params'.freeze
P_RACK_ROUTING =
'rack_routing'.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from RequestRecorder

#clean_request_record, #close_request_record, #observe, #observed_items, #observed_items=, #only_metric_observation, #only_metric_observation=, #payload_requests, #payload_requests=

Constructor Details

#initializeGenericFramework

Returns a new instance of GenericFramework.



27
28
29
30
31
32
33
34
35
36
# 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

Instance Attribute Details

#req_end_cb=(value) ⇒ Object (writeonly)

Sets the attribute req_end_cb

Parameters:

  • value

    the value to set the attribute req_end_cb to.



25
26
27
# File 'lib/sqreen/frameworks/generic.rb', line 25

def req_end_cb=(value)
  @req_end_cb = value
end

#req_start_cb=(value) ⇒ Object (writeonly)

Sets the attribute req_start_cb

Parameters:

  • value

    the value to set the attribute req_start_cb to.



25
26
27
# File 'lib/sqreen/frameworks/generic.rb', line 25

def req_start_cb=(value)
  @req_start_cb = value
end

#sqreen_configurationObject

Returns the value of attribute sqreen_configuration.



23
24
25
# File 'lib/sqreen/frameworks/generic.rb', line 23

def sqreen_configuration
  @sqreen_configuration
end

Class Method Details

.cookies_params(request) ⇒ Object



383
384
385
386
387
388
389
390
391
# File 'lib/sqreen/frameworks/generic.rb', line 383

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) ⇒ Object



363
364
365
366
367
368
369
370
371
# File 'lib/sqreen/frameworks/generic.rb', line 363

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

.parameters_from_request(request) ⇒ Object



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/sqreen/frameworks/generic.rb', line 407

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) ⇒ Object



393
394
395
396
397
398
399
400
401
# File 'lib/sqreen/frameworks/generic.rb', line 393

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) ⇒ Object



373
374
375
376
377
378
379
380
381
# File 'lib/sqreen/frameworks/generic.rb', line 373

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

Instance Method Details

#application_nameObject



238
239
240
# File 'lib/sqreen/frameworks/generic.rb', line 238

def application_name
  nil
end

#bodyObject



432
433
434
435
436
437
438
439
440
441
442
# File 'lib/sqreen/frameworks/generic.rb', line 432

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_requestObject

Cleanup request context



320
321
322
323
324
325
326
327
328
329
330
# File 'lib/sqreen/frameworks/generic.rb', line 320

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_ipObject

What is the current client IP



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# 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_agentObject

request user agent



227
228
229
230
231
# File 'lib/sqreen/frameworks/generic.rb', line 227

def client_user_agent
  req = request
  return nil unless req
  req.env['HTTP_USER_AGENT']
end

#cwdObject

Expose current working directory



445
446
447
# File 'lib/sqreen/frameworks/generic.rb', line 445

def cwd
  Dir.getwd
end

#datadog_spanObject



184
185
186
187
188
189
190
191
192
# File 'lib/sqreen/frameworks/generic.rb', line 184

def datadog_span
  return unless defined?(Datadog)

  if defined?(Datadog::Tracing) && Datadog::Tracing.respond_to?(:active_span)
    Datadog::Tracing.active_span
  elsif Datadog.respond_to?(:tracer) && Datadog.tracer
    Datadog.tracer.active_span
  end
end

#datadog_traceObject



194
195
196
197
198
199
200
# File 'lib/sqreen/frameworks/generic.rb', line 194

def datadog_trace
  return unless defined?(Datadog)

  if defined?(Datadog::Tracing) && Datadog::Tracing.respond_to?(:active_trace)
    Datadog::Tracing.active_trace
  end
end

#db_settings(_options = {}) ⇒ Object

What kind of database is this



39
40
41
# File 'lib/sqreen/frameworks/generic.rb', line 39

def db_settings(_options = {})
  raise Sqreen::NotImplementedYet
end

#development?Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/sqreen/frameworks/generic.rb', line 53

def development?
  ENV['RACK_ENV'] == 'development'
end

#filtered_request_paramsObject



344
345
346
347
348
# File 'lib/sqreen/frameworks/generic.rb', line 344

def filtered_request_params
  params = request_params
  params.delete('cookies')
  params
end

#framework_infosObject

More information about the current framework



44
45
46
47
48
49
50
51
# 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) ⇒ Boolean

Does the parameters key/value include this value

Returns:

  • (Boolean)


275
276
277
278
279
280
281
282
# File 'lib/sqreen/frameworks/generic.rb', line 275

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) ⇒ Object



403
404
405
# File 'lib/sqreen/frameworks/generic.rb', line 403

def graphql_args=(args)
  request.env['sqreen.request.graphql_args'] = args if request
end

#header(name) ⇒ Object

Get a header by name



131
132
133
134
135
# File 'lib/sqreen/frameworks/generic.rb', line 131

def header(name)
  req = request
  return nil unless req
  req.env[name]
end

#hostnameObject



145
146
147
148
149
150
151
# 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_headersObject



137
138
139
140
141
142
143
# 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) ⇒ Object

Instrument with our rules when the framework as finished loading



260
261
262
# File 'lib/sqreen/frameworks/generic.rb', line 260

def instrument_when_ready!(instrumentor, rules)
  instrumentor.instrument!(rules, self)
end

#ip_headersObject



81
82
83
84
85
86
87
88
89
90
91
# 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!Object



284
285
286
287
288
289
# File 'lib/sqreen/frameworks/generic.rb', line 284

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) ⇒ Boolean

Does the parameters value include this value

Returns:

  • (Boolean)


265
266
267
268
269
270
271
272
# File 'lib/sqreen/frameworks/generic.rb', line 265

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_startupObject

Should the agent not be starting up?



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/sqreen/frameworks/generic.rb', line 243

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_ipObject

What is the current client IP as seen by rack



94
95
96
97
98
99
# 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_budgetObject



332
333
334
# File 'lib/sqreen/frameworks/generic.rb', line 332

def remaining_perf_budget
  SharedStorage.get(:performance_budget)
end

#remaining_perf_budget=(value) ⇒ Object



336
337
338
# File 'lib/sqreen/frameworks/generic.rb', line 336

def remaining_perf_budget=(value)
  SharedStorage.set(:performance_budget, value)
end

#remote_addrObject



474
475
476
477
# File 'lib/sqreen/frameworks/generic.rb', line 474

def remote_addr
  return nil unless request
  request.env['REMOTE_ADDR']
end

#requestObject

Get the currently stored request



311
312
313
# File 'lib/sqreen/frameworks/generic.rb', line 311

def request
  SharedStorage.get(:request)
end

#request_idObject



153
154
155
156
157
# 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_infosObject

Summary of known request infos



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# 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_paramsObject



340
341
342
# File 'lib/sqreen/frameworks/generic.rb', line 340

def request_params
  self.class.parameters_from_request(request)
end

#request_pathObject

Request URL path



220
221
222
223
224
# File 'lib/sqreen/frameworks/generic.rb', line 220

def request_path
  req = request
  return nil unless req
  req.script_name + req.path_info
end

#responseObject



315
316
317
# File 'lib/sqreen/frameworks/generic.rb', line 315

def response
  SharedStorage.get(:response)
end

#response_infosObject



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/sqreen/frameworks/generic.rb', line 202

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

#rootObject

Application root



234
235
236
# File 'lib/sqreen/frameworks/generic.rb', line 234

def root
  nil
end

#store_request(object) ⇒ Object

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



293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/sqreen/frameworks/generic.rb', line 293

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) ⇒ Object



306
307
308
# File 'lib/sqreen/frameworks/generic.rb', line 306

def store_response(rv, _env)
  SharedStorage.set(:response, Rack::Response.new([], rv[0], rv[1]))
end

#test?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/sqreen/frameworks/generic.rb', line 57

def test?
  ENV['RACK_ENV'] == 'test'
end

#whitelisted_ipObject

Returns the current path that whitelist the request



466
467
468
469
470
471
472
# File 'lib/sqreen/frameworks/generic.rb', line 466

def whitelisted_ip
  ip = client_ip
  return nil unless ip
  find_whitelisted_ip(ip)
rescue
  nil
end

#whitelisted_matchObject

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



451
452
453
454
455
456
# File 'lib/sqreen/frameworks/generic.rb', line 451

def whitelisted_match
  return nil unless request
  whitelisted = whitelisted_ip || whitelisted_path
  SharedStorage.set(:whitelisted, !whitelisted.nil?)
  whitelisted
end

#whitelisted_pathObject

Returns the current path that whitelist the request



459
460
461
462
463
# File 'lib/sqreen/frameworks/generic.rb', line 459

def whitelisted_path
  path = request_path
  return nil unless path
  find_whitelisted_path(path)
end

#xss_params(regexp = nil) ⇒ Object



480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/sqreen/frameworks/generic.rb', line 480

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