sqreen 1.15.0-java → 1.15.5-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sqreen/actions.rb +114 -43
  3. data/lib/sqreen/callback_tree.rb +16 -3
  4. data/lib/sqreen/callbacks.rb +7 -34
  5. data/lib/sqreen/capped_queue.rb +5 -1
  6. data/lib/sqreen/configuration.rb +4 -0
  7. data/lib/sqreen/deliveries/batch.rb +7 -4
  8. data/lib/sqreen/event.rb +4 -0
  9. data/lib/sqreen/events/request_record.rb +40 -7
  10. data/lib/sqreen/frameworks/generic.rb +0 -2
  11. data/lib/sqreen/frameworks/request_recorder.rb +14 -1
  12. data/lib/sqreen/instrumentation.rb +57 -33
  13. data/lib/sqreen/js/mini_racer_adapter.rb +46 -8
  14. data/lib/sqreen/metrics/average.rb +1 -1
  15. data/lib/sqreen/metrics/base.rb +4 -2
  16. data/lib/sqreen/metrics/binning.rb +3 -2
  17. data/lib/sqreen/metrics/collect.rb +1 -1
  18. data/lib/sqreen/metrics/sum.rb +1 -1
  19. data/lib/sqreen/metrics_store.rb +10 -5
  20. data/lib/sqreen/mono_time.rb +18 -0
  21. data/lib/sqreen/performance_notifications.rb +13 -38
  22. data/lib/sqreen/performance_notifications/binned_metrics.rb +12 -14
  23. data/lib/sqreen/performance_notifications/log.rb +6 -1
  24. data/lib/sqreen/performance_notifications/log_performance.rb +3 -1
  25. data/lib/sqreen/performance_notifications/metrics.rb +6 -3
  26. data/lib/sqreen/performance_notifications/newrelic.rb +6 -2
  27. data/lib/sqreen/remote_command.rb +26 -0
  28. data/lib/sqreen/rule_attributes.rb +1 -0
  29. data/lib/sqreen/rule_callback.rb +38 -0
  30. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +3 -2
  31. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +1 -1
  32. data/lib/sqreen/rules_callbacks/run_block_user_actions.rb +1 -1
  33. data/lib/sqreen/rules_callbacks/run_req_start_actions.rb +8 -2
  34. data/lib/sqreen/runner.rb +11 -8
  35. data/lib/sqreen/sdk.rb +7 -1
  36. data/lib/sqreen/session.rb +4 -0
  37. data/lib/sqreen/trie.rb +274 -0
  38. data/lib/sqreen/version.rb +1 -1
  39. metadata +4 -2
@@ -246,7 +246,6 @@ module Sqreen
246
246
  return unless ensure_rack_loaded
247
247
  self.remaining_perf_budget = Sqreen.performance_budget
248
248
  SharedStorage.set(:request, Rack::Request.new(object))
249
- SharedStorage.inc(:stored_requests)
250
249
  SharedStorage.set(:xss_params, nil)
251
250
  SharedStorage.set(:whitelisted, nil)
252
251
  SharedStorage.set(:request_overtime, nil)
@@ -259,7 +258,6 @@ module Sqreen
259
258
 
260
259
  # Cleanup request context
261
260
  def clean_request
262
- return unless SharedStorage.dec(:stored_requests) <= 0
263
261
  payload_creator = Sqreen::PayloadCreator.new(self)
264
262
  close_request_record(Sqreen.queue, Sqreen.observations_queue, payload_creator)
265
263
  self.remaining_perf_budget = nil
@@ -5,6 +5,8 @@ require 'sqreen/shared_storage'
5
5
  require 'sqreen/events/request_record'
6
6
  require 'sqreen/performance_notifications/log_performance'
7
7
  require 'sqreen/performance_notifications/newrelic'
8
+ require 'sqreen/log'
9
+ require 'sqreen/runner'
8
10
 
9
11
  module Sqreen
10
12
  # Store event/observations that happened in this request
@@ -47,6 +49,8 @@ module Sqreen
47
49
  end
48
50
 
49
51
  def close_request_record(queue, observations_queue, payload_creator)
52
+ Sqreen.log.debug { "close_request_record called. observed_items: #{observed_items}" }
53
+
50
54
  clean_request_record if observed_items.nil?
51
55
  if only_metric_observation
52
56
  push_metrics(observations_queue, queue)
@@ -54,12 +58,21 @@ module Sqreen
54
58
  end
55
59
  payload = payload_creator.payload(payload_requests)
56
60
  payload[:observed] = observed_items
57
- queue.push RequestRecord.new(payload)
61
+ queue.push create_request_record(payload)
58
62
  clean_request_record
59
63
  end
60
64
 
61
65
  protected
62
66
 
67
+ def create_request_record(payload)
68
+ RequestRecord.new(payload, redactor)
69
+ end
70
+
71
+ def redactor
72
+ return nil unless Sqreen.config_get(:strip_sensitive_data)
73
+ @redactor ||= SensitiveDataRedactor.from_config
74
+ end
75
+
63
76
  def push_metrics(observations_queue, event_queue)
64
77
  observed_items[:observations].each do |obs|
65
78
  observations_queue.push obs
@@ -12,6 +12,7 @@ require 'sqreen/shared_storage'
12
12
  require 'sqreen/rules_callbacks/record_request_context'
13
13
  require 'sqreen/rules_callbacks/run_req_start_actions'
14
14
  require 'sqreen/rules_callbacks/run_block_user_actions'
15
+ require 'sqreen/mono_time'
15
16
  require 'set'
16
17
 
17
18
  # How to override a class method:
@@ -37,6 +38,11 @@ require 'set'
37
38
  module Sqreen
38
39
  class Instrumentation
39
40
  OVERTIME_METRIC = 'request_overtime'.freeze
41
+
42
+ PRE_CB = 'pre'.freeze
43
+ POST_CB = 'post'.freeze
44
+ FAILING_CB = 'failing'.freeze
45
+
40
46
  MGMT_COST = 0.000025
41
47
  @@override_semaphore = Mutex.new
42
48
  @@overriden_singleton_methods = false
@@ -64,7 +70,7 @@ module Sqreen
64
70
  @@overriden_methods
65
71
  end
66
72
  def self.callback_wrapper_pre(callbacks, framework, budget, _klass, method, instance, args, &block)
67
- all_start = Sqreen::PerformanceNotifications.time
73
+ all_start = Sqreen.time
68
74
  #Instrumentation.guard_call(method, []) do
69
75
  returns = []
70
76
  callbacks.each do |cb|
@@ -76,9 +82,9 @@ module Sqreen
76
82
  end
77
83
  Sqreen.log.debug { "running pre cb #{cb}" }
78
84
  begin
79
- start = Sqreen::PerformanceNotifications.time
85
+ start = Sqreen.time
80
86
  res = cb.pre(instance, args, budget, &block)
81
- stop = Sqreen::PerformanceNotifications.time
87
+ stop = Sqreen.time
82
88
  # The first few pre callbacks could not have a request & hence a budget just yet so we try harder to find it
83
89
  budget = framework.remaining_perf_budget if framework && !budget && Sqreen.performance_budget
84
90
  if budget
@@ -95,6 +101,7 @@ module Sqreen
95
101
  res[:rule_name] = rule
96
102
  end
97
103
  returns << res
104
+ break if res.is_a?(Hash) && res[:skip_rem_cbs]
98
105
  rescue StandardError => e
99
106
  Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
100
107
  Sqreen.log.debug e.backtrace
@@ -105,21 +112,21 @@ module Sqreen
105
112
  end
106
113
  next
107
114
  end
108
- Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/pre", start, stop)
115
+ Sqreen::PerformanceNotifications.notify(rule || cb.class.name, PRE_CB, start, stop)
109
116
  end
110
- all_stop = Sqreen::PerformanceNotifications.time
117
+ all_stop = Sqreen.time
111
118
  framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size if framework && budget
112
- Sqreen::PerformanceNotifications.notify('Callbacks/hooks_pre/pre', all_start, all_stop)
119
+ Sqreen::PerformanceNotifications.notify('hooks_pre', PRE_CB, all_start, all_stop)
113
120
  returns
114
121
  #end
115
122
  rescue StandardError => e
116
- Sqreen.log.warn { "we catched an exception between cbs: #{e.inspect}" }
123
+ Sqreen.log.warn { "we caught an exception between cbs: #{e.inspect}" }
117
124
  Sqreen::RemoteException.record(e)
118
125
  []
119
126
  end
120
127
 
121
128
  def self.callback_wrapper_post(callbacks, framework, budget, _klass, method, return_val, instance, args, &block)
122
- all_start = Sqreen::PerformanceNotifications.time
129
+ all_start = Sqreen.time
123
130
  #Instrumentation.guard_call(method, []) do
124
131
  returns = []
125
132
  callbacks.reverse_each do |cb|
@@ -130,9 +137,9 @@ module Sqreen
130
137
  end
131
138
  Sqreen.log.debug { "running post cb #{cb}" }
132
139
  begin
133
- start = Sqreen::PerformanceNotifications.time
140
+ start = Sqreen.time
134
141
  res = cb.post(return_val, instance, args, budget, &block)
135
- stop = Sqreen::PerformanceNotifications.time
142
+ stop = Sqreen.time
136
143
  if budget
137
144
  budget -= (stop - start)
138
145
  cb.overtime! if budget <= 0.0
@@ -157,23 +164,23 @@ module Sqreen
157
164
  end
158
165
  next
159
166
  end
160
- Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/post", start, stop)
167
+ Sqreen::PerformanceNotifications.notify(rule || cb.class.name, POST_CB, start, stop)
161
168
  end
162
- all_stop = Sqreen::PerformanceNotifications.time
169
+ all_stop = Sqreen.time
163
170
  if framework && budget && framework.remaining_perf_budget
164
171
  framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size
165
172
  end
166
- Sqreen::PerformanceNotifications.notify('Callbacks/hooks_post/post', all_start, all_stop)
173
+ Sqreen::PerformanceNotifications.notify('hooks_post', POST_CB, all_start, all_stop)
167
174
  returns
168
175
  #end
169
176
  rescue StandardError => e
170
- Sqreen.log.warn { "we catched an exception between cbs: #{e.inspect}" }
177
+ Sqreen.log.warn { "we caught an exception between cbs: #{e.inspect}" }
171
178
  Sqreen::RemoteException.record(e)
172
179
  []
173
180
  end
174
181
 
175
182
  def self.callback_wrapper_failing(callbacks, framework, budget, exception, _klass, method, instance, args, &block)
176
- all_start = Sqreen::PerformanceNotifications.time
183
+ all_start = Sqreen.time
177
184
  #Instrumentation.guard_call(method, []) do
178
185
  returns = []
179
186
  callbacks.each do |cb|
@@ -184,9 +191,9 @@ module Sqreen
184
191
  end
185
192
  Sqreen.log.debug { "running failing cb #{cb}" }
186
193
  begin
187
- start = Sqreen::PerformanceNotifications.time
194
+ start = Sqreen.time
188
195
  res = cb.failing(exception, instance, args, budget, &block)
189
- stop = Sqreen::PerformanceNotifications.time
196
+ stop = Sqreen.time
190
197
  if budget
191
198
  budget -= (stop - start)
192
199
  cb.overtime! if budget <= 0.0
@@ -211,17 +218,17 @@ module Sqreen
211
218
  end
212
219
  next
213
220
  end
214
- Sqreen::PerformanceNotifications.notify("Callbacks/#{rule || cb.class.name}/failing", start, stop)
221
+ Sqreen::PerformanceNotifications.notify(rule || cb.class.name, FAILING_CB, start, stop)
215
222
  end
216
- all_stop = Sqreen::PerformanceNotifications.time
223
+ all_stop = Sqreen.time
217
224
  if framework && budget && framework.remaining_perf_budget
218
225
  framework.remaining_perf_budget = budget - (all_stop - all_start) - MGMT_COST * callbacks.size
219
226
  end
220
- Sqreen::PerformanceNotifications.notify('Callbacks/hooks_failing/failing', all_start, all_stop)
227
+ Sqreen::PerformanceNotifications.notify('hooks_failing', FAILING_CB, all_start, all_stop)
221
228
  returns
222
229
  # end
223
230
  rescue StandardError => e
224
- Sqreen.log.warn "we catched an exception between cbs: #{e.inspect}"
231
+ Sqreen.log.warn "we caught an exception between cbs: #{e.inspect}"
225
232
  Sqreen::RemoteException.record(e)
226
233
  []
227
234
  end
@@ -245,11 +252,8 @@ module Sqreen
245
252
 
246
253
  def self.define_callback_method(meth, original_meth, klass_name)
247
254
  @sqreen_multi_instr ||= nil
248
- proc do |*args, &block|
249
- record_req_hp = @@record_request_hookpoints.include?([klass_name, meth]) &&
250
- Sqreen::PerformanceNotifications.listen_for?
251
- Sqreen::PerformanceNotifications::BinnedMetrics.start_request if record_req_hp
252
255
 
256
+ proc do |*args, &block|
253
257
  budget = nil
254
258
  skip_call = Thread.current[:sqreen_in_use]
255
259
  begin
@@ -385,16 +389,28 @@ module Sqreen
385
389
  end
386
390
  result
387
391
  ensure
388
- if record_req_hp
389
- Sqreen::PerformanceNotifications.instrument('Callbacks/hooks_reporting/pre') do
390
- Sqreen::PerformanceNotifications::LogPerformance.next_request
391
- Sqreen::PerformanceNotifications::NewRelic.next_request
392
- Sqreen::PerformanceNotifications::BinnedMetrics.finish_request
393
- end
394
- end
395
392
  Thread.current[:sqreen_in_use] = false
396
393
  end
397
394
  end
395
+ end # end of proc
396
+ end
397
+
398
+ def self.request_hookpoint_method(original_meth)
399
+ proc do |*args, &block|
400
+ has_notifications = Sqreen::PerformanceNotifications.listen_for?
401
+ Sqreen::PerformanceNotifications::BinnedMetrics.start_request if has_notifications
402
+
403
+ begin
404
+ send(original_meth, *args, &block)
405
+ ensure
406
+ if has_notifications
407
+ Sqreen::PerformanceNotifications.instrument('next_req_notifs', PRE_CB) do
408
+ Sqreen::PerformanceNotifications::LogPerformance.next_request
409
+ Sqreen::PerformanceNotifications::NewRelic.next_request
410
+ Sqreen::PerformanceNotifications::BinnedMetrics.finish_request
411
+ end
412
+ end
413
+ end
398
414
  end
399
415
  end
400
416
 
@@ -476,6 +492,12 @@ module Sqreen
476
492
 
477
493
  define_method(new_method, p)
478
494
 
495
+ if @@record_request_hookpoints.include?([klass_name, meth])
496
+ p = Instrumentation.request_hookpoint_method(new_method)
497
+ new_method = "#{new_method}_req_hp_wrapper"
498
+ define_method(new_method, p)
499
+ end
500
+
479
501
  if public_method_defined?(meth)
480
502
  method_kind = :public
481
503
  elsif protected_method_defined?(meth)
@@ -568,6 +590,8 @@ module Sqreen
568
590
  method = cb.method
569
591
  key = [klass, method]
570
592
 
593
+ @@record_request_hookpoints << key if cb.is_a?(Sqreen::Rules::RecordRequestContext)
594
+
571
595
  already_overriden = @@overriden_methods.include? key
572
596
 
573
597
  if !already_overriden
@@ -609,7 +633,6 @@ module Sqreen
609
633
 
610
634
  @@registered_callbacks.add(cb)
611
635
  @@unovertimable_hookpoints << key unless cb.overtimeable
612
- @@record_request_hookpoints << key if cb.is_a?(Sqreen::Rules::RecordRequestContext)
613
636
  @@instrumented_pid = Process.pid
614
637
  end
615
638
  end
@@ -710,6 +733,7 @@ module Sqreen
710
733
  add_callback(rcb)
711
734
  end
712
735
 
736
+ # add hardcoded callbacks, observing priority
713
737
  hardcoded_callbacks(framework).each { |cb| add_callback(cb) }
714
738
 
715
739
  Sqreen.instrumentation_ready = true
@@ -82,13 +82,22 @@ module Sqreen
82
82
 
83
83
  def run_js_cb(cb_name, budget, arguments)
84
84
  @pool.with_context do |ctx|
85
+ if ctx.code_failed?(@code_id)
86
+ Sqreen.log.debug do
87
+ "Skipping execution of callback #{cb_name} (code md5 #{@code_id})" \
88
+ " due to prev failure of definition evaluation"
89
+ end
90
+ return nil
91
+ end
92
+
85
93
  ctx.add_code(@code_id, @code) unless ctx.has_code?(@code_id)
86
94
 
87
95
  begin
88
- json_args = "[#{arguments.map(&method(:fixup_bad_encoding)).map(&:to_json).join(',')}]"
96
+ json_args = convert_arguments(arguments)
89
97
  ctx.eval_unsafe(
90
- "sqreen_data['#{@code_id}']['#{cb_name}'].apply(this, #{json_args})", nil, budget)
98
+ "sqreen_data['#{@code_id}']['#{cb_name}'].apply(this, #{json_args})", nil, budget)
91
99
  rescue @module::ScriptTerminatedError
100
+ Sqreen.log.debug "ScriptTerminatedError/#{cb_name}"
92
101
  nil
93
102
  end
94
103
  end
@@ -100,9 +109,31 @@ module Sqreen
100
109
 
101
110
  private
102
111
 
103
- def fixup_bad_encoding(arg)
104
- # NOTE: we don't fix encoding problems in deeper structures
112
+ def convert_arguments(args)
113
+ JSON.generate(args)
114
+ rescue JSON::GeneratorError, Encoding::UndefinedConversionError
115
+ fixed_args = fixup_bad_encoding(args)
116
+ JSON.generate(fixed_args)
117
+ end
118
+
119
+ def fixup_bad_encoding(arg, max_depth = 100)
120
+ return nil if max_depth <= 0
121
+
122
+ if arg.is_a?(Array)
123
+ return arg.map { |it| fixup_bad_encoding(it, max_depth - 1) }
124
+ end
125
+
126
+ if arg.is_a?(Hash)
127
+ return Hash[
128
+ arg.map do |k, v|
129
+ [fixup_bad_encoding(k, max_depth - 1),
130
+ fixup_bad_encoding(v, max_depth - 1)]
131
+ end
132
+ ]
133
+ end
134
+
105
135
  return arg unless arg.is_a?(String)
136
+
106
137
  unless arg.valid_encoding?
107
138
  return arg.dup.force_encoding(Encoding::ISO_8859_1)
108
139
  end
@@ -127,13 +158,20 @@ module Sqreen
127
158
  @code_ids.include?(code_id)
128
159
  end
129
160
 
161
+ def code_failed?(code_id)
162
+ return false unless @failed_code_ids
163
+ @failed_code_ids.include?(code_id)
164
+ end
165
+
130
166
  def add_code(code_id, code)
167
+ eval_unsafe "(function() { #{code} })()"
168
+ transf_global_funcs code_id
131
169
  @code_ids ||= Set.new
132
- # if it fails, we don't try again
133
170
  @code_ids << code_id
134
-
135
- eval_unsafe code
136
- transf_global_funcs code_id
171
+ rescue
172
+ @failed_code_ids ||= Set.new
173
+ @failed_code_ids << code_id
174
+ raise
137
175
  end
138
176
 
139
177
  def eval_unsafe(str, filename = nil, timeoutv = nil)
@@ -9,7 +9,7 @@ module Sqreen
9
9
  class Average < Base
10
10
  # from class attr_accessor :aggregate
11
11
 
12
- def update(_at, key, value)
12
+ def update(key, value)
13
13
  super
14
14
  @sums[key] ||= 0
15
15
  @sums[key] += value
@@ -18,12 +18,12 @@ module Sqreen
18
18
  # @param _at [Time] when was the observation made
19
19
  # @param _key [String] which aggregation key was it made for
20
20
  # @param _value [Object] The observation
21
- def update(_at, _key, _value)
21
+ def update(_key, _value)
22
22
  raise Sqreen::Exception, 'No current sample' unless @sample
23
23
  end
24
24
 
25
25
  # create a new empty sample and publish the last one
26
- # @param time [Time] Time of start/finish
26
+ # @param time [Float] Time of start of new sample/end of the last one
27
27
  def next_sample(time)
28
28
  finalize_sample(time) unless @sample.nil?
29
29
  current_sample = @sample
@@ -33,10 +33,12 @@ module Sqreen
33
33
 
34
34
  protected
35
35
 
36
+ # @param time [Float]
36
37
  def new_sample(time)
37
38
  @sample = { OBSERVATION_KEY => {}, START_KEY => time }
38
39
  end
39
40
 
41
+ # @param time [Float]
40
42
  def finalize_sample(time)
41
43
  @sample[FINISH_KEY] = time
42
44
  end
@@ -1,6 +1,7 @@
1
1
  # Copyright (c) 2018 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
+ require 'sqreen/mono_time'
4
5
  require 'sqreen/metrics/base'
5
6
 
6
7
  module Sqreen
@@ -20,10 +21,10 @@ module Sqreen
20
21
  log_factor = Math.log(@factor)
21
22
  @inv_log_base = 1 / log_base
22
23
  @add_parcel = - log_factor / log_base
23
- new_sample(Time.now.utc)
24
+ new_sample(Sqreen.time)
24
25
  end
25
26
 
26
- def update(_at, _key, x)
27
+ def update(_key, x)
27
28
  h = @sample[OBSERVATION_KEY]
28
29
  bin = bin_no(x)
29
30
  h[bin] += 1
@@ -11,7 +11,7 @@ module Sqreen
11
11
  class Collect < Base
12
12
  # from class attr_accessor :aggregate
13
13
 
14
- def update(_at, key, value)
14
+ def update(key, value)
15
15
  super
16
16
  s = @sample[OBSERVATION_KEY]
17
17
  s[key] ||= []
@@ -9,7 +9,7 @@ module Sqreen
9
9
  class Sum < Base
10
10
  # from class attr_accessor :aggregate
11
11
 
12
- def update(_at, key, value)
12
+ def update(key, value)
13
13
  super
14
14
  s = @sample[OBSERVATION_KEY]
15
15
  s[key] ||= 0