sqreen 1.20.1-java → 1.21.0.beta3-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 (77) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +16 -0
  3. data/lib/sqreen/actions/block_user.rb +1 -1
  4. data/lib/sqreen/actions/redirect_ip.rb +1 -1
  5. data/lib/sqreen/actions/redirect_user.rb +1 -1
  6. data/lib/sqreen/attack_detected.html +1 -2
  7. data/lib/sqreen/condition_evaluator.rb +9 -2
  8. data/lib/sqreen/conditionable.rb +24 -6
  9. data/lib/sqreen/configuration.rb +1 -1
  10. data/lib/sqreen/deferred_logger.rb +50 -14
  11. data/lib/sqreen/deliveries/batch.rb +8 -1
  12. data/lib/sqreen/deprecation.rb +38 -0
  13. data/lib/sqreen/ecosystem.rb +96 -0
  14. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  15. data/lib/sqreen/ecosystem/exception_reporting.rb +26 -0
  16. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  17. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  18. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  19. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  20. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  21. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  22. data/lib/sqreen/ecosystem/module_api/message_producer.rb +51 -0
  23. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  24. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  25. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  26. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  27. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  28. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  29. data/lib/sqreen/ecosystem/module_registry.rb +44 -0
  30. data/lib/sqreen/ecosystem/redis/redis_connection.rb +43 -0
  31. data/lib/sqreen/ecosystem/tracing/modules/client.rb +31 -0
  32. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  33. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  34. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  35. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  36. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  37. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  38. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  39. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  40. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  41. data/lib/sqreen/ecosystem_integration.rb +87 -0
  42. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +99 -0
  43. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +42 -0
  44. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  45. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  46. data/lib/sqreen/events/request_record.rb +0 -1
  47. data/lib/sqreen/frameworks/generic.rb +24 -1
  48. data/lib/sqreen/frameworks/rails.rb +0 -7
  49. data/lib/sqreen/frameworks/request_recorder.rb +2 -0
  50. data/lib/sqreen/graft/call.rb +106 -19
  51. data/lib/sqreen/graft/callback.rb +1 -1
  52. data/lib/sqreen/graft/hook.rb +212 -100
  53. data/lib/sqreen/graft/hook_point.rb +18 -11
  54. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  55. data/lib/sqreen/legacy/old_event_submission_strategy.rb +9 -2
  56. data/lib/sqreen/log.rb +3 -2
  57. data/lib/sqreen/log/loggable.rb +1 -0
  58. data/lib/sqreen/logger.rb +24 -0
  59. data/lib/sqreen/metrics.rb +1 -0
  60. data/lib/sqreen/metrics/req_detailed.rb +41 -0
  61. data/lib/sqreen/metrics_store.rb +11 -0
  62. data/lib/sqreen/null_logger.rb +22 -0
  63. data/lib/sqreen/remote_command.rb +4 -0
  64. data/lib/sqreen/rules.rb +8 -4
  65. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  66. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  67. data/lib/sqreen/rules/rule_cb.rb +4 -2
  68. data/lib/sqreen/rules/waf_cb.rb +3 -3
  69. data/lib/sqreen/runner.rb +63 -8
  70. data/lib/sqreen/session.rb +2 -0
  71. data/lib/sqreen/signals/conversions.rb +6 -1
  72. data/lib/sqreen/version.rb +1 -1
  73. data/lib/sqreen/weave/budget.rb +35 -0
  74. data/lib/sqreen/weave/legacy/instrumentation.rb +274 -132
  75. data/lib/sqreen/worker.rb +6 -2
  76. metadata +46 -9
  77. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -114,15 +114,19 @@ module Sqreen
114
114
  Sqreen.log.warn('No JavaScript engine is available. ' \
115
115
  'JavaScript callbacks will be ignored')
116
116
  end
117
- Sqreen.log.info("Ignoring JS callback #{rule_name}")
117
+ Sqreen.log.debug("Ignoring JS callback #{rule_name}")
118
118
  return nil
119
119
  end
120
120
 
121
121
  cb_class = ExecJSCB if js
122
122
 
123
- if cbname && Rules.const_defined?(cbname)
124
- # Only load callbacks from sqreen
125
- cb_class = Rules.const_get(cbname)
123
+ if cbname
124
+ cb_class = if cbname.include?('::')
125
+ # Only load callbacks from sqreen
126
+ Rules.walk_const_get(cbname) if cbname.start_with?('::Sqreen::', 'Sqreen::')
127
+ else
128
+ Rules.const_get(cbname) if Rules.const_defined?(cbname) # rubocop:disable Style/IfInsideElse
129
+ end
126
130
  end
127
131
 
128
132
  if cb_class.nil?
@@ -33,7 +33,7 @@ module Sqreen
33
33
  private
34
34
 
35
35
  def insert_values(ranges)
36
- Sqreen.log.info 'no ips given for IP blacklisting' if ranges.empty?
36
+ Sqreen.log.debug 'no ips given for IP blacklisting' if ranges.empty?
37
37
 
38
38
  ranges.map { |r| Prefix.from_str(r, r) }.each do |prefix|
39
39
  trie_for(prefix).insert prefix
@@ -50,7 +50,7 @@ module Sqreen
50
50
  begin
51
51
  ipa = IPAddr.new(rip)
52
52
  rescue StandardError
53
- Sqreen.log.info "invalid IP address given by framework: #{rip}"
53
+ Sqreen.log.debug "invalid IP address given by framework: #{rip}"
54
54
  return nil
55
55
  end
56
56
 
@@ -55,12 +55,12 @@ module Sqreen
55
55
  end
56
56
 
57
57
  def respond_page
58
- page = open(File.join(File.dirname(__FILE__), '../attack_detected.html'))
58
+ @page ||= File.read(File.join(File.dirname(__FILE__), '../attack_detected.html'))
59
59
  headers = {
60
60
  'Content-Type' => 'text/html',
61
- 'Content-Length' => page.size.to_s,
61
+ 'Content-Length' => @page.size.to_s,
62
62
  }
63
- [@status_code, headers, page]
63
+ [@status_code, headers, [@page]]
64
64
  end
65
65
  end
66
66
  end
@@ -3,6 +3,7 @@
3
3
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
+ require 'sqreen/deprecation'
6
7
  require 'sqreen/framework_cb'
7
8
  require 'sqreen/context'
8
9
  require 'sqreen/conditionable'
@@ -89,9 +90,9 @@ module Sqreen
89
90
  framework.observe(:sqreen_exceptions, payload)
90
91
  end
91
92
 
92
- # Recommend taking an action (optionnally adding more data/context)
93
+ # Recommend taking an action (optionally adding more data/context)
93
94
  #
94
- # This will format the requested action and optionnally
95
+ # This will format the requested action and optionally
95
96
  # override it if it should not be taken (should not block for example)
96
97
  def advise_action(action, additional_data = {})
97
98
  return if action.nil? && additional_data.empty?
@@ -109,6 +110,7 @@ module Sqreen
109
110
  )
110
111
  true
111
112
  end
113
+ Sqreen::Deprecation.deprecate(instance_method(:overtime!))
112
114
  end
113
115
  end
114
116
  end
@@ -11,7 +11,7 @@ require 'sqreen/safe_json'
11
11
  require 'sqreen/exception'
12
12
  require 'sqreen/util/capper'
13
13
  require 'sqreen/dependency/libsqreen'
14
- require 'sqreen/encoding_sanitizer'
14
+ require 'sqreen/kit/string_sanitizer'
15
15
 
16
16
  module Sqreen
17
17
  module Rules
@@ -60,7 +60,7 @@ module Sqreen
60
60
  end
61
61
 
62
62
  # 0 for using defaults (PW_RUN_TIMEOUT)
63
- @max_run_budget_us = (@data['values'].fetch('budget_in_ms', 0) * 1000).to_i
63
+ @max_run_budget_us = (@data['values'].fetch('max_budget_ms', 0) * 1000).to_i
64
64
  @max_run_budget_us = INFINITE_BUDGET_US if @max_run_budget_us >= INFINITE_BUDGET_US
65
65
 
66
66
  Sqreen.log.debug { "Max WAF run budget for #{@waf_rule_name} set to #{@max_run_budget_us} us" }
@@ -82,7 +82,7 @@ module Sqreen
82
82
  waf_args = binding_accessors.each_with_object({}) do |(e, b), h|
83
83
  h[e] = capper.call(b.resolve(*env))
84
84
  end
85
- waf_args = Sqreen::EncodingSanitizer.sanitize(waf_args)
85
+ waf_args = Sqreen::Kit::StringSanitizer.sanitize(waf_args)
86
86
 
87
87
  if budget
88
88
  rem_budget_s = budget - (Sqreen.time - start)
@@ -14,6 +14,7 @@ require 'sqreen/log'
14
14
  require 'sqreen/agent_message'
15
15
  require 'sqreen/rules'
16
16
  require 'sqreen/session'
17
+ require 'sqreen/version'
17
18
  require 'sqreen/remote_command'
18
19
  require 'sqreen/capped_queue'
19
20
  require 'sqreen/metrics_store'
@@ -26,6 +27,7 @@ require 'sqreen/legacy/instrumentation'
26
27
  require 'sqreen/call_countable'
27
28
  require 'sqreen/weave/legacy/instrumentation'
28
29
  require 'sqreen/kit/configuration'
30
+ require 'sqreen/ecosystem_integration'
29
31
 
30
32
  module Sqreen
31
33
  @features = {}
@@ -52,10 +54,6 @@ module Sqreen
52
54
  @queue ||= CappedQueue.new(MAX_QUEUE_LENGTH)
53
55
  end
54
56
 
55
- def update_queue(queue)
56
- @queue = queue
57
- end
58
-
59
57
  def observations_queue
60
58
  @observations_queue ||= CappedQueue.new(MAX_OBS_QUEUE_LENGTH)
61
59
  end
@@ -104,8 +102,8 @@ module Sqreen
104
102
  # we may want to do that in a thread in order to prevent delaying app
105
103
  # startup
106
104
  # set_at_exit do not place a global at_exit (used for testing)
105
+ # @param [Sqreen::Frameworks::GenericFramework] framework
107
106
  def initialize(configuration, framework, set_at_exit = true, session_class = Sqreen::Session)
108
- Sqreen.update_queue(CappedQueue.new(MAX_QUEUE_LENGTH))
109
107
  @logged_out_tried = false
110
108
  @configuration = configuration
111
109
  @framework = framework
@@ -132,6 +130,7 @@ module Sqreen
132
130
  Sqreen::Kit::Configuration.ingestion_url = chosen_endpoints.ingestion.url
133
131
  Sqreen::Kit::Configuration.certificate_store = chosen_endpoints.ingestion.ca_store
134
132
  Sqreen::Kit::Configuration.proxy_url = @proxy_url
133
+ Sqreen::Kit::Configuration.default_source = "sqreen:agent:ruby:#{Sqreen::VERSION}"
135
134
 
136
135
  register_exit_cb if set_at_exit
137
136
 
@@ -142,7 +141,12 @@ module Sqreen
142
141
  end
143
142
 
144
143
  if @configuration.get(:weave) || needs_weave.call
145
- @instrumenter = Sqreen::Weave::Legacy::Instrumentation.new(metrics_engine)
144
+ # XXX: don't get updated
145
+ opts = {
146
+ perf_req_metrics_max_reqs: Sqreen.features['perf_req_metrics_max_reqs'],
147
+ perf_req_metrics_period: Sqreen.features['perf_req_metrics_period'],
148
+ }
149
+ @instrumenter = Sqreen::Weave::Legacy::Instrumentation.new(metrics_engine, opts)
146
150
  else
147
151
  @instrumenter = Sqreen::Legacy::Instrumentation.new(metrics_engine)
148
152
  end
@@ -168,6 +172,12 @@ module Sqreen
168
172
  end
169
173
  self.features = wanted_features
170
174
 
175
+ @ecosystem_integration = EcosystemIntegration.new(framework,
176
+ Sqreen.queue,
177
+ create_binning_metric_proc)
178
+ framework.req_start_cb = @ecosystem_integration.method(:request_start)
179
+ framework.req_end_cb = @ecosystem_integration.method(:request_end)
180
+
171
181
  # Ensure a deliverer is there unless features have set it first
172
182
  self.deliverer ||= Deliveries::Simple.new(session)
173
183
  context_infos = {}
@@ -268,6 +278,10 @@ module Sqreen
268
278
  rulespack_id, rules = load_rules(context_infos)
269
279
  @framework.instrument_when_ready!(instrumenter, rules)
270
280
  Sqreen.log.info 'Instrumentation set up'
281
+
282
+ # XXX: ecosystem instrumentation should likely be deferred
283
+ # the same way the rest might be
284
+ @ecosystem_integration.init unless Sqreen.features['disable_ecosystem']
271
285
  rulespack_id.to_s
272
286
  end
273
287
 
@@ -387,11 +401,28 @@ module Sqreen
387
401
 
388
402
  def change_performance_budget(budget, _context_infos = {})
389
403
  return false unless budget.nil? || budget.to_f > 0
390
- prev = Sqreen.performance_budget
391
- Sqreen.update_performance_budget(budget)
404
+
405
+ if @configuration.get(:weave)
406
+ prev = Sqreen::Weave::Budget.current
407
+ prev = prev.to_h if prev
408
+
409
+ budget_s = budget.to_f / 1000.0 if budget
410
+ Sqreen::Weave::Budget.update(threshold: budget_s)
411
+ else
412
+ prev = Sqreen.performance_budget
413
+ Sqreen.update_performance_budget(budget)
414
+ end
415
+
392
416
  { :was => prev }
393
417
  end
394
418
 
419
+ # @param [String] tracing_id_prefix
420
+ # @param [Array<Hash{String=>Object}>] sampling_config
421
+ def tracing_enable(tracing_id_prefix, sampling_config, _context_infos = {})
422
+ @ecosystem_integration.handle_tracing_command(tracing_id_prefix, sampling_config)
423
+ { status: true }
424
+ end
425
+
395
426
  def upload_bundle(_context_infos = {})
396
427
  t = Time.now
397
428
  session.post_bundle(RuntimeInfos.dependencies_signature, RuntimeInfos.dependencies)
@@ -478,6 +509,15 @@ module Sqreen
478
509
  logout
479
510
  end
480
511
 
512
+ def restart(_context_infos = {})
513
+ shutdown
514
+ heartbeat_delay = @heartbeat_delay
515
+ Thread.new do
516
+ sleep(2 * heartbeat_delay)
517
+ Sqreen::Worker.start(Sqreen.framework)
518
+ end
519
+ end
520
+
481
521
  def logout(retrying = true)
482
522
  return unless session
483
523
  Sqreen.log.debug("Logging out")
@@ -515,6 +555,21 @@ module Sqreen
515
555
 
516
556
  private
517
557
 
558
+ def create_binning_metric_proc
559
+ lambda do |metric_name|
560
+ return if @metrics_engine.metric?(metric_name)
561
+ metrics_engine.create_metric(
562
+ 'name' => metric_name,
563
+ 'kind' => 'Binning',
564
+ 'period' => Sqreen.features['performance_metrics_period'] || 60,
565
+ 'options' => {
566
+ 'base' => Sqreen.features['perf_base'] || PerformanceNotifications::BinnedMetrics::DEFAULT_PERF_BASE,
567
+ 'factor' => Sqreen.features['perf_unit'] || PerformanceNotifications::BinnedMetrics::DEFAULT_PERF_UNIT,
568
+ },
569
+ )
570
+ end
571
+ end
572
+
518
573
  def post_endpoint_testing_msgs(chosen_endpoints)
519
574
  chosen_endpoints.messages.each do |msg|
520
575
  session.post_agent_message(@framework, msg)
@@ -249,8 +249,10 @@ module Sqreen
249
249
  end
250
250
  Sqreen.log.info 'Login success.'
251
251
  @session_id = res['session_id']
252
+
252
253
  Kit::Configuration.session_key = @session_id
253
254
  Kit.reset
255
+
254
256
  Sqreen.log.debug { "received session_id #{@session_id}" }
255
257
  Sqreen.logged_in = true
256
258
  res
@@ -118,6 +118,7 @@ module Sqreen
118
118
  signals += req_rec.processed_sdk_calls
119
119
  .select { |h| h[:name] == :track }
120
120
  .map { |h| convert_track(h) }
121
+ signals += (observed[:signals] || [])
121
122
 
122
123
  trace = Kit::Signals::Specialized::HttpTrace.new(
123
124
  actor: Kit::Signals::Actor.new(
@@ -137,7 +138,7 @@ module Sqreen
137
138
  trace
138
139
  end
139
140
 
140
- # @param [Array<Sqreen::Kit::Signals::Signal|Sqreen::Kit::Signals::Trace>] batch
141
+ # @return [Array<Sqreen::Kit::Signals::Signal|Sqreen::Kit::Signals::Trace>]
141
142
  def convert_batch(batch)
142
143
  batch.map do |evt|
143
144
  case evt
@@ -147,6 +148,10 @@ module Sqreen
147
148
  convert_metric_sample(evt)
148
149
  when RequestRecord
149
150
  convert_req_record(evt)
151
+ when Sqreen::Kit::Signals::Signal
152
+ evt
153
+ when Sqreen::Kit::Signals::Trace
154
+ evt
150
155
  else
151
156
  raise NotImplementedError, "Unknown type of event in batch: #{evt}"
152
157
  end
@@ -4,5 +4,5 @@
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
6
  module Sqreen
7
- VERSION = '1.20.1'.freeze
7
+ VERSION = '1.21.0.beta3'.freeze
8
8
  end
@@ -0,0 +1,35 @@
1
+ # typed: false
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ require 'sqreen/log/loggable'
7
+ require 'sqreen/weave'
8
+
9
+ class Sqreen::Weave::Budget
10
+ include Sqreen::Log::Loggable
11
+
12
+ def initialize(threshold)
13
+ @threshold = threshold
14
+ end
15
+
16
+ attr_reader :threshold
17
+
18
+ def to_h
19
+ { threshold: threshold }
20
+ end
21
+
22
+ class << self
23
+ attr_reader :current
24
+
25
+ def update(opts = nil)
26
+ Sqreen::Weave.logger.info("budget update:#{opts.inspect}")
27
+
28
+ return @current = nil if opts.nil? || opts.empty?
29
+
30
+ threshold = opts[:threshold]
31
+
32
+ @current = threshold
33
+ end
34
+ end
35
+ end
@@ -4,23 +4,41 @@
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
6
  require 'sqreen/weave/legacy'
7
+ require 'sqreen/weave/budget'
8
+ require 'sqreen/graft/hook'
7
9
  require 'sqreen/graft/hook_point'
8
10
  require 'sqreen/call_countable'
9
11
  require 'sqreen/rules'
10
12
  require 'sqreen/rules/record_request_context'
13
+ require 'sqreen/sqreen_signed_verifier'
14
+ require 'rack/request'
15
+ begin
16
+ require 'sq_detailed_metrics'
17
+ rescue LoadError => _e # rubocop:disable Lint/HandleExceptions
18
+ end
11
19
 
12
20
  class Sqreen::Weave::Legacy::Instrumentation
13
21
  attr_accessor :metrics_engine
14
22
 
23
+ HAS_SQ_DETAILED_METRICS = defined?(::SqDetailedMetrics)
24
+ REQ_LVL_2_METRIC = 'request_level_perf'.freeze
25
+
15
26
  def initialize(metrics_engine, opts = {})
16
27
  Sqreen::Weave.logger.debug { "#{self.class.name}#initialize #{metrics_engine}" }
17
28
  @hooks = []
18
29
 
30
+ unless HAS_SQ_DETAILED_METRICS
31
+ Sqreen::Weave.logger.warn { "Detailed metrics are unavailable" }
32
+ end
33
+
19
34
  self.metrics_engine = metrics_engine
20
35
 
21
36
  ### bail out if no metric engine
22
37
  return if metrics_engine.nil?
23
38
 
39
+ # XXX: these metric definitions do not support change of opts
40
+ # due to features updates!
41
+
24
42
  ### init metric to count calls to sqreen
25
43
  metrics_engine.create_metric(
26
44
  'name' => 'sqreen_call_counts',
@@ -60,12 +78,42 @@ class Sqreen::Weave::Legacy::Instrumentation
60
78
  'options' => opts[:perf_metric_percent] || { 'base' => 1.3, 'factor' => 1.0 },
61
79
  )
62
80
 
81
+ metrics_engine.create_metric(
82
+ 'name' => 'req.sq.hook.overhead',
83
+ 'period' => 60,
84
+ 'kind' => 'Binning',
85
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
86
+ )
87
+
88
+ metrics_engine.create_metric(
89
+ 'name' => 'sq.hook.overhead',
90
+ 'period' => 60,
91
+ 'kind' => 'Binning',
92
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
93
+ )
94
+
95
+ metrics_engine.create_metric(
96
+ 'name' => 'sq.shrinkwrap',
97
+ 'period' => 60,
98
+ 'kind' => 'Binning',
99
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
100
+ )
101
+
63
102
  Sqreen.thread_cpu_time? && metrics_engine.create_metric(
64
103
  'name' => 'sq_thread_cpu_pct',
65
104
  'period' => opts[:period] || 60,
66
105
  'kind' => 'Binning',
67
106
  'options' => opts[:perf_metric_percent] || { 'base' => 1.3, 'factor' => 1.0 },
68
107
  )
108
+
109
+ if HAS_SQ_DETAILED_METRICS # rubocop:disable Style/GuardClause
110
+ @lvl_2_metric = metrics_engine.create_metric(
111
+ 'name' => REQ_LVL_2_METRIC,
112
+ 'period' => opts[:perf_req_metrics_period] || 60,
113
+ 'kind' => 'ReqDetailed',
114
+ )
115
+ @lvl_2_max_reqs = opts[:perf_req_metrics_max_reqs] || 100
116
+ end
69
117
  end
70
118
 
71
119
  # needed by Sqreen::Runner#initialize
@@ -84,6 +132,15 @@ class Sqreen::Weave::Legacy::Instrumentation
84
132
 
85
133
  ### set up rule signature verifier
86
134
  verifier = nil
135
+ if Sqreen.features['rules_signature'] &&
136
+ Sqreen.config_get(:rules_verify_signature) == true &&
137
+ !defined?(::JRUBY_VERSION)
138
+ verifier = Sqreen::SqreenSignedVerifier.new
139
+ Sqreen::Weave.logger.debug('Rules signature enabled')
140
+ else
141
+ Sqreen::Weave.logger.debug('Rules signature disabled')
142
+ end
143
+
87
144
  ### force clean instrumentation callback list
88
145
  @hooks = []
89
146
  ### for each rule description
@@ -94,6 +151,25 @@ class Sqreen::Weave::Legacy::Instrumentation
94
151
  next unless rule_callback
95
152
  ### attach framework to callback
96
153
  rule_callback.framework = framework
154
+ ## create metric
155
+ Sqreen::Weave.logger.debug { "Adding rule metric: #{rule_callback}" }
156
+ [:pre, :post, :failing].each do |whence|
157
+ next unless rule_callback.send(:"#{whence}?")
158
+ metric_name = "sq.#{rule['name']}.#{whence}"
159
+ metrics_engine.create_metric(
160
+ 'name' => metric_name,
161
+ 'period' => 60,
162
+ 'kind' => 'Binning',
163
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
164
+ )
165
+ metric_name = "req.sq.#{rule['name']}.#{whence}"
166
+ metrics_engine.create_metric(
167
+ 'name' => metric_name,
168
+ 'period' => 60,
169
+ 'kind' => 'Binning',
170
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
171
+ )
172
+ end
97
173
  ### install callback, observing priority
98
174
  Sqreen::Weave.logger.debug { "Adding rule callback: #{rule_callback}" }
99
175
  @hooks << add_callback("weave,rule=#{rule['name']}", rule_callback, strategy)
@@ -107,30 +183,62 @@ class Sqreen::Weave::Legacy::Instrumentation
107
183
  end
108
184
 
109
185
  metrics_engine = self.metrics_engine
186
+ lvl_2_metric = @lvl_2_metric
187
+ lvl_2_max_reqs = @lvl_2_max_reqs
188
+
110
189
  request_hook = Sqreen::Graft::Hook['Sqreen::ShrinkWrap#call', strategy]
111
190
  @hooks << request_hook
112
191
  request_hook.add do
113
- before('wave,meta,request', rank: -100000, mandatory: true) do |_call|
192
+ before('wave,meta,request', rank: -100000, mandatory: true) do |call|
114
193
  next unless Sqreen.instrumentation_ready
115
194
 
116
- uuid = SecureRandom.uuid
117
- now = Sqreen::Graft::Timer.read
195
+ # shrinkwrap_timer = Sqreen::Graft::Timer.new('weave,shrinkwrap')
196
+ # shrinkwrap_timer.start
197
+
198
+ request_timer = Sqreen::Graft::Timer.new("request")
199
+ request_timer.start
200
+ sqreen_timer = Sqreen::Graft::Timer.new("sqreen")
201
+ budget = Sqreen::Weave::Budget.current
202
+
203
+ timed_level = (Sqreen.features['perf_level'] || 1).to_i
204
+ timed_level = 1 if !HAS_SQ_DETAILED_METRICS && timed_level == 2
205
+ if timed_level == 2 && lvl_2_metric.num_requests >= lvl_2_max_reqs
206
+ timed_level = 1
207
+ Sqreen::Weave.logger.debug { "Reducing timed level to 1 (#{lvl_2_metric.num_requests} reqs accumulated)" }
208
+ end
209
+
210
+ Sqreen::Weave.logger.debug { "request budget: #{budget} timed.level: #{timed_level}" } if Sqreen::Weave.logger.debug?
211
+
212
+ route_found = nil
213
+ if timed_level >= 2
214
+ rack_env, = call.args
215
+ rack_request = Rack::Request.new(rack_env) if rack_env
216
+
217
+ # TODO: Rails engines
218
+ # TODO: Struct
219
+ # TODO: Sinatra
220
+ # TODO: Rack?
221
+ Rails.application.routes.router.recognize(rack_request) do |route, params|
222
+ route = ActionDispatch::Routing::RouteWrapper.new(route)
223
+ route_found = { name: route.name, verb: route.verb, path: route.path, reqs: route.reqs, params: params }
224
+ end if defined?(Rails) && Rails.application && defined?(ActionDispatch::Routing::RouteWrapper)
225
+ end
226
+
227
+ # TODO: Struct
118
228
  Thread.current[:sqreen_http_request] = {
119
- uuid: uuid,
120
- start_time: now,
121
- time_budget: Sqreen.performance_budget,
229
+ request_timer: request_timer,
230
+ sqreen_timer: sqreen_timer,
122
231
  time_budget_expended: false,
123
- timer: Sqreen::Graft::Timer.new("request_#{uuid}"),
232
+ time_budget: budget,
124
233
  timed_callbacks: [],
125
234
  timed_hooks: [],
126
- timed_hooks_before: [],
127
- timed_hooks_after: [],
128
- timed_hooks_raised: [],
129
- timed_hooks_ensured: [],
235
+ timed_level: timed_level,
130
236
  skipped_callbacks: [],
237
+ route: ("#{route_found[:verb]} #{route_found[:path]}" if route_found),
238
+ # timed_shrinkwrap: shrinkwrap_timer,
131
239
  }
132
240
 
133
- Sqreen::Weave.logger.debug { "request.uuid: #{uuid}" }
241
+ # shrinkwrap_timer.stop
134
242
  end
135
243
 
136
244
  ensured('weave,meta,request', rank: 100000, mandatory: true) do |_call|
@@ -138,105 +246,89 @@ class Sqreen::Weave::Legacy::Instrumentation
138
246
 
139
247
  next if request.nil?
140
248
 
249
+ timed_level = request[:timed_level]
250
+ req_detailed = SqDetailedMetrics::Request.new if timed_level >= 2
251
+
252
+ # shrinkwrap_timer = request[:timed_shrinkwrap]
253
+ # shrinkwrap_timer.start
254
+
141
255
  Thread.current[:sqreen_http_request] = nil
142
- now = Sqreen::Graft::Timer.read
143
- utc_now = Time.now.utc
144
-
145
- request[:timed_callbacks].each do |timer|
146
- duration = timer.duration
147
- # stop = now
148
- # start = now - duration
149
- timer.tag =~ /weave,rule=(.*)$/ && rule = $1
150
- timer.tag =~ /@before/ && whence = 'pre'
151
- timer.tag =~ /@after/ && whence = 'post'
152
- timer.tag =~ /@raised/ && whence = 'failing'
153
-
154
- next unless rule && whence
155
-
156
- # Sqreen::PerformanceNotifications.notify(rule, whence, start, stop)
157
- # => BinnedMetrics
158
- metric_name = "sq.#{rule}.#{whence}"
159
- unless metrics_engine.metric?(metric_name)
160
- metrics_engine.create_metric(
161
- 'name' => metric_name,
162
- 'period' => 60,
163
- 'kind' => 'Binning',
164
- 'options' => { 'base' => 2.0, 'factor' => 0.1 },
165
- )
256
+ request_timer = request[:request_timer]
257
+ now = request_timer.stop
258
+
259
+ if timed_level >= 1
260
+ request[:timed_callbacks].each do |timer|
261
+ duration_ms = timer.duration * 1000.0
262
+ # XXX: the timer tag should have this structured data;
263
+ # it would be better than recomputing this for every measurement
264
+ metric_name = ::Sqreen::Weave::Legacy::Instrumentation.tag_to_metric_name(timer.tag)
265
+
266
+ next unless metric_name
267
+
268
+ metrics_engine.update(metric_name, now, nil, duration_ms)
269
+ duration_ms *= -1.0 if timer.conditions_passed
270
+ req_detailed.add_measurement metric_name, duration_ms if req_detailed
166
271
  end
167
- metrics_engine.update(metric_name, now, nil, duration * 1000)
168
272
  end
169
273
 
170
- metric_name = 'sq.hooks_pre.pre'
171
- duration = request[:timed_hooks_before].sum(&:duration)
172
- unless metrics_engine.metric?(metric_name)
173
- metrics_engine.create_metric(
174
- 'name' => metric_name,
175
- 'period' => 60,
176
- 'kind' => 'Binning',
177
- 'options' => { 'base' => 2.0, 'factor' => 0.1 },
178
- )
179
- end
180
- metrics_engine.update(metric_name, now, nil, duration * 1000)
181
-
182
- metric_name = 'sq.hooks_post.post'
183
- duration = request[:timed_hooks_after].sum(&:duration)
184
- unless metrics_engine.metric?(metric_name)
185
- metrics_engine.create_metric(
186
- 'name' => metric_name,
187
- 'period' => 60,
188
- 'kind' => 'Binning',
189
- 'options' => { 'base' => 2.0, 'factor' => 0.1 },
190
- )
191
- end
192
- metrics_engine.update(metric_name, now, nil, duration * 1000)
193
-
194
- metric_name = 'sq.hooks_failing.failing'
195
- duration = request[:timed_hooks_raised].sum(&:duration)
196
- unless metrics_engine.metric?(metric_name)
197
- metrics_engine.create_metric(
198
- 'name' => metric_name,
199
- 'period' => 60,
200
- 'kind' => 'Binning',
201
- 'options' => { 'base' => 2.0, 'factor' => 0.1 },
202
- )
274
+ sqreen_timer = request[:sqreen_timer]
275
+ Sqreen::Weave.logger.debug do
276
+ "request sqreen_timer.total: #{'%.03fus' % (sqreen_timer.duration * 1_000_000)}"
277
+ end if Sqreen::Weave.logger.debug?
278
+ Sqreen::Weave.logger.debug do
279
+ "request request_timer.total: #{'%.03fus' % (request_timer.duration * 1_000_000)}"
280
+ end if Sqreen::Weave.logger.debug?
281
+
282
+ if timed_level >= 1 && Sqreen::Weave.logger.debug?
283
+ skipped = request[:skipped_callbacks].map(&:name)
284
+ Sqreen::Weave.logger.debug { "request callback.skipped.count: #{skipped.count}" } if Sqreen::Weave.logger.debug?
285
+ timings = request[:timed_callbacks].map(&:to_s)
286
+ total = request[:timed_callbacks].sum(&:duration)
287
+ Sqreen::Weave.logger.debug { "request callback.total: #{'%.03fus' % (total * 1_000_000)} callback.count: #{timings.count}" } if Sqreen::Weave.logger.debug?
288
+ timings = request[:timed_hooks].map(&:to_s)
289
+ total = request[:timed_hooks].sum(&:duration)
290
+ Sqreen::Weave.logger.debug { "request hook.total: #{'%.03fus' % (total * 1_000_000)} hook.count: #{timings.count}" } if Sqreen::Weave.logger.debug?
203
291
  end
204
- metrics_engine.update(metric_name, now, nil, duration * 1000)
205
-
206
- skipped = request[:skipped_callbacks].map(&:name)
207
- Sqreen::Weave.logger.debug { "request:#{request[:uuid]} callback.skipped.size: #{skipped.count} callback.skipped: [#{skipped.join(', ')}]" }
208
- timer = request[:timer]
209
- total = timer.duration
210
- Sqreen::Weave.logger.debug { "request:#{request[:uuid]} timer.total: #{'%.03fus' % (total * 1_000_000)} timer.size: #{timer.size}" }
211
- timings = request[:timed_callbacks].map(&:to_s)
212
- total = request[:timed_callbacks].sum(&:duration)
213
- Sqreen::Weave.logger.debug { "request:#{request[:uuid]} callback.total: #{'%.03fus' % (total * 1_000_000)} callback.timings: [#{timings.join(', ')}]" }
214
- timings = request[:timed_hooks].map(&:to_s)
215
- total = request[:timed_hooks].sum(&:duration)
216
- Sqreen::Weave.logger.debug { "request:#{request[:uuid]} hook.total: #{'%.03fus' % (total * 1_000_000)} hook.timings: [#{timings.join(', ')}]" }
217
-
218
- skipped = request[:skipped_callbacks].map(&:name)
219
- skipped_rule_name = skipped.first && skipped.first =~ /weave,rule=(.*)$/ && $1
220
- Sqreen.observations_queue.push(['request_overtime', skipped_rule_name, 1, utc_now]) if skipped_rule_name
221
-
222
- sqreen_request_duration = total
223
- Sqreen.observations_queue.push(['sq', nil, sqreen_request_duration * 1000, utc_now])
224
-
225
- request_duration = now - request[:start_time]
226
- Sqreen.observations_queue.push(['req', nil, request_duration * 1000, utc_now])
292
+
293
+ overtime_cb = ::Sqreen::Weave::Legacy::Instrumentation.tag_to_metric_name(request[:overtime_cb]) \
294
+ if request[:overtime_cb]
295
+ metrics_engine.update('request_overtime', now, overtime_cb, 1) if overtime_cb
296
+
297
+ sqreen_request_duration = sqreen_timer.duration * 1000.0
298
+ metrics_engine.update('sq', now, nil, sqreen_request_duration)
299
+
300
+ request_duration = request_timer.duration * 1000.0
301
+ metrics_engine.update('req', now, nil, request_duration)
227
302
 
228
303
  sqreen_request_ratio = (sqreen_request_duration * 100.0) / (request_duration - sqreen_request_duration)
229
- Sqreen.observations_queue.push(['pct', nil, sqreen_request_ratio, utc_now])
304
+ metrics_engine.update('pct', now, nil, sqreen_request_ratio)
305
+ Sqreen::Weave.logger.debug { "request sqreen_timer.ratio: #{'%.03f' % (sqreen_request_ratio / 100.0)}" } if Sqreen::Weave.logger.debug?
306
+
307
+ if req_detailed
308
+ req_detailed.route = request[:route]
309
+ req_detailed.overtime_cb = overtime_cb if overtime_cb
310
+ req_detailed.add_measurement 'sq', sqreen_request_duration
311
+ req_detailed.add_measurement 'req', request_duration
312
+
313
+ metrics_engine.update(REQ_LVL_2_METRIC, now, nil, req_detailed)
314
+ end
315
+
316
+ # shrinkwrap_timer.stop
317
+
318
+ # duration = shrinkwrap_timer.duration
319
+ # metrics_engine.update('sq.shrinkwrap', now, nil, duration * 1000)
230
320
  end
231
321
  end.install
232
322
 
233
323
  ### globally declare instrumentation ready
234
324
  Sqreen.instrumentation_ready = true
325
+ Sqreen::Weave.logger.info { "Instrumentation activated" }
235
326
  end
236
327
 
237
328
  # needed by Sqreen::Runner
238
329
  def remove_all_callbacks
239
330
  Sqreen.instrumentation_ready = false
331
+ Sqreen::Weave.logger.info { "Instrumentation deactivated" }
240
332
 
241
333
  loop do
242
334
  hook = @hooks.pop
@@ -253,6 +345,15 @@ class Sqreen::Weave::Legacy::Instrumentation
253
345
  klass = callback.klass
254
346
  method = callback.method
255
347
 
348
+ if (call_count = ENV['SQREEN_DEBUG_CALL_COUNT'])
349
+ call_count = JSON.parse(call_count)
350
+ if callback.respond_to?(:rule_name) && call_count.key?(callback.rule_name)
351
+ count = call_count[callback.rule_name]
352
+ Sqreen::Weave.logger.debug { "override rule: #{callback.rule_name} call_count: #{count.inspect}" }
353
+ callback.instance_eval { @call_count_interval = call_count[callback.rule_name] }
354
+ end
355
+ end
356
+
256
357
  if Sqreen::Graft::HookPoint.new("#{klass}.#{method}").exist?
257
358
  hook_point = "#{klass}.#{method}"
258
359
  elsif Sqreen::Graft::HookPoint.new("#{klass}##{method}").exist?
@@ -268,14 +369,14 @@ class Sqreen::Weave::Legacy::Instrumentation
268
369
  hook = Sqreen::Graft::Hook[hook_point, strategy]
269
370
  hook.add do
270
371
  if callback.pre?
271
- before(rule, rank: priority, mandatory: !callback.overtimeable, flow: block, ignore: ignore) do |call, b|
372
+ use_flow = block || callback.is_a?(::Sqreen::Conditionable)
373
+ before(rule, rank: priority, mandatory: !callback.overtimeable, flow: use_flow, ignore: ignore) do |call, b|
272
374
  next unless Thread.current[:sqreen_http_request]
273
375
 
274
376
  i = call.instance
275
377
  a = call.args
276
378
  r = call.remaining
277
379
 
278
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#pre instance=#{i}" }
279
380
  begin
280
381
  ret = callback.pre(i, a, r)
281
382
  rescue StandardError => e
@@ -286,17 +387,30 @@ class Sqreen::Weave::Legacy::Instrumentation
286
387
  Sqreen::RemoteException.record(e)
287
388
  end
288
389
  end
289
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#pre instance=#{i} => return=#{ret.inspect}" }
290
-
291
- case ret[:status]
292
- when :skip, 'skip'
293
- throw(b, b.return(ret[:new_return_value]).break!) if ret.key?(:new_return_value)
294
- when :modify_args, 'modify_args'
295
- throw(b, b.args(ret[:args]))
296
- when :raise, 'raise'
297
- throw(b, b.raise(ret[:exception])) if ret.key?(:exception)
298
- throw(b, b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required.")))
299
- end unless ret.nil? || !ret.is_a?(Hash)
390
+
391
+ next if ret.nil? || !ret.is_a?(Hash)
392
+
393
+ throw_val =
394
+ case ret[:status]
395
+ when :skip, 'skip'
396
+ b.return(ret[:new_return_value]).break! if ret.key?(:new_return_value)
397
+ when :modify_args, 'modify_args'
398
+ b.args(ret[:args])
399
+ when :raise, 'raise'
400
+ if ret.key?(:exception)
401
+ b.raise(ret[:exception])
402
+ else
403
+ b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required."))
404
+ end
405
+ end if block
406
+
407
+ if ret && ret[:passed_conditions]
408
+ throw_val ||= b.noop
409
+ throw_val.passed_conditions!
410
+ end
411
+ next unless throw_val
412
+ throw_val.break! if ret[:skip_rem_cbs]
413
+ throw(b, throw_val)
300
414
  end
301
415
  end
302
416
 
@@ -309,7 +423,6 @@ class Sqreen::Weave::Legacy::Instrumentation
309
423
  a = call.args
310
424
  r = call.remaining
311
425
 
312
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#post instance=#{i}" }
313
426
  begin
314
427
  ret = callback.post(v, i, a, r)
315
428
  rescue StandardError => e
@@ -320,15 +433,22 @@ class Sqreen::Weave::Legacy::Instrumentation
320
433
  Sqreen::RemoteException.record(e)
321
434
  end
322
435
  end
323
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#post instance=#{i} => return=#{ret.inspect}" }
324
-
325
- case ret[:status]
326
- when :override, 'override'
327
- throw(b, b.return(ret[:new_return_value])) if ret.key?(:new_return_value)
328
- when :raise, 'raise'
329
- throw(b, b.raise(ret[:exception])) if ret.key?(:exception)
330
- throw(b, b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required.")))
331
- end unless ret.nil? || !ret.is_a?(Hash)
436
+
437
+ throw_val =
438
+ case ret[:status]
439
+ when :override, 'override'
440
+ b.return(ret[:new_return_value]) if ret.key?(:new_return_value)
441
+ when :raise, 'raise'
442
+ b.raise(ret[:exception]) if ret.key?(:exception)
443
+ b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required."))
444
+ end unless ret.nil? || !ret.is_a?(Hash) || !block
445
+
446
+ if ret && ret[:passed_conditions]
447
+ throw_val ||= b.noop
448
+ throw_val.passed_conditions!
449
+ end
450
+ next unless throw_val
451
+ throw(b, throw_val)
332
452
  end
333
453
  end
334
454
 
@@ -341,7 +461,6 @@ class Sqreen::Weave::Legacy::Instrumentation
341
461
  a = call.args
342
462
  r = call.remaining
343
463
 
344
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#failing instance=#{i}" }
345
464
  begin
346
465
  ret = callback.failing(e, i, a, r)
347
466
  rescue StandardError => e
@@ -352,23 +471,30 @@ class Sqreen::Weave::Legacy::Instrumentation
352
471
  Sqreen::RemoteException.record(e)
353
472
  end
354
473
  end
355
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#failing instance=#{i} => return=#{ret.inspect}" }
356
474
 
357
475
  throw(b, b.raise(e)) if ret.nil? || !ret.is_a?(Hash)
358
476
 
359
- case ret[:status]
360
- when :override, 'override'
361
- throw(b, b.return(ret[:new_return_value])) if ret.key?(:new_return_value)
362
- when :retry, 'retry'
363
- throw(b, b.retry)
364
- when :raise, 'raise'
365
- throw(b, b.raise(ret[:exception])) if ret.key?(:exception)
366
- throw(b, b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required.")))
367
- when :reraise, 'reraise'
368
- throw(b, b.raise(e))
369
- else
370
- throw(b, b.raise(e))
371
- end unless ret.nil? || !ret.is_a?(Hash)
477
+ throw_val =
478
+ case ret[:status]
479
+ when :override, 'override'
480
+ b.return(ret[:new_return_value]) if ret.key?(:new_return_value)
481
+ when :retry, 'retry'
482
+ b.retry
483
+ when :raise, 'raise'
484
+ b.raise(ret[:exception]) if ret.key?(:exception)
485
+ b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required."))
486
+ when :reraise, 'reraise'
487
+ b.raise(e)
488
+ else
489
+ b.raise(e)
490
+ end unless ret.nil? || !ret.is_a?(Hash) || !block
491
+
492
+ if ret && ret[:passed_conditions]
493
+ throw_val ||= b.noop
494
+ throw_val.passed_conditions!
495
+ end
496
+ next unless throw_val
497
+ throw(b, throw_val)
372
498
  end
373
499
  end
374
500
  end.install
@@ -403,4 +529,20 @@ class Sqreen::Weave::Legacy::Instrumentation
403
529
  Sqreen::Rules::RunUserActions.new(Sqreen, :auth_track, 1),
404
530
  ]
405
531
  end
532
+
533
+ def self.tag_to_metric_name(tag)
534
+ cached = @cache_tag_to_metric[tag]
535
+ return cached unless cached.nil?
536
+
537
+ tag =~ /weave,rule=(.*)$/ && rule = $1 and # rubocop:disable Style/AndOr
538
+ (tag =~ /@before/ && whence = 'pre' or # rubocop:disable Style/AndOr
539
+ tag =~ /@after/ && whence = 'post' or # rubocop:disable Style/AndOr
540
+ tag =~ /@raised/ && whence = 'failing' or # rubocop:disable Style/AndOr
541
+ tag =~ /@ensured/ && whence = 'finally')
542
+
543
+ @cache_tag_to_metric[tag] =
544
+ rule && whence ? "sq.#{rule}.#{whence}" : false
545
+ end
546
+
547
+ @cache_tag_to_metric = {}
406
548
  end