sqreen 1.15.0-java → 1.15.5-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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