sqreen 1.20.1-java → 1.22.0-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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -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 +8 -2
  8. data/lib/sqreen/configuration.rb +1 -1
  9. data/lib/sqreen/deferred_logger.rb +50 -14
  10. data/lib/sqreen/deliveries/batch.rb +8 -1
  11. data/lib/sqreen/dependency/detector.rb +11 -3
  12. data/lib/sqreen/dependency/new_relic.rb +10 -1
  13. data/lib/sqreen/deprecation.rb +38 -0
  14. data/lib/sqreen/ecosystem.rb +123 -0
  15. data/lib/sqreen/ecosystem/databases/database_connection_data.rb +23 -0
  16. data/lib/sqreen/ecosystem/databases/mongo.rb +39 -0
  17. data/lib/sqreen/ecosystem/databases/mysql.rb +54 -0
  18. data/lib/sqreen/ecosystem/databases/postgres.rb +51 -0
  19. data/lib/sqreen/ecosystem/databases/redis.rb +36 -0
  20. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  21. data/lib/sqreen/ecosystem/exception_reporting.rb +28 -0
  22. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  23. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  24. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  25. data/lib/sqreen/ecosystem/messaging/bunny.rb +61 -0
  26. data/lib/sqreen/ecosystem/messaging/kafka.rb +70 -0
  27. data/lib/sqreen/ecosystem/messaging/kinesis.rb +66 -0
  28. data/lib/sqreen/ecosystem/messaging/sqs.rb +68 -0
  29. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  30. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  31. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  32. data/lib/sqreen/ecosystem/module_api/message_producer.rb +57 -0
  33. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  34. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  35. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  36. data/lib/sqreen/ecosystem/module_api/tracing/consumer_data.rb +13 -0
  37. data/lib/sqreen/ecosystem/module_api/tracing/messaging_data.rb +35 -0
  38. data/lib/sqreen/ecosystem/module_api/tracing/producer_data.rb +13 -0
  39. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  40. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  41. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  42. data/lib/sqreen/ecosystem/module_registry.rb +48 -0
  43. data/lib/sqreen/ecosystem/tracing/modules/client.rb +35 -0
  44. data/lib/sqreen/ecosystem/tracing/modules/consumer.rb +35 -0
  45. data/lib/sqreen/ecosystem/tracing/modules/determine_ip.rb +28 -0
  46. data/lib/sqreen/ecosystem/tracing/modules/producer.rb +35 -0
  47. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  48. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  49. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  50. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  51. data/lib/sqreen/ecosystem/tracing/signals/tracing_consumer.rb +56 -0
  52. data/lib/sqreen/ecosystem/tracing/signals/tracing_producer.rb +56 -0
  53. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  54. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  55. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  56. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  57. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  58. data/lib/sqreen/ecosystem_integration.rb +81 -0
  59. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +89 -0
  60. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +38 -0
  61. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  62. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  63. data/lib/sqreen/events/request_record.rb +0 -1
  64. data/lib/sqreen/frameworks/generic.rb +36 -1
  65. data/lib/sqreen/frameworks/rails.rb +0 -7
  66. data/lib/sqreen/frameworks/request_recorder.rb +2 -0
  67. data/lib/sqreen/graft/call.rb +85 -18
  68. data/lib/sqreen/graft/callback.rb +1 -1
  69. data/lib/sqreen/graft/hook.rb +192 -88
  70. data/lib/sqreen/graft/hook_point.rb +18 -11
  71. data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +2 -0
  72. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  73. data/lib/sqreen/legacy/old_event_submission_strategy.rb +9 -2
  74. data/lib/sqreen/log.rb +3 -2
  75. data/lib/sqreen/log/loggable.rb +1 -0
  76. data/lib/sqreen/logger.rb +24 -0
  77. data/lib/sqreen/metrics_store.rb +11 -0
  78. data/lib/sqreen/null_logger.rb +22 -0
  79. data/lib/sqreen/remote_command.rb +4 -0
  80. data/lib/sqreen/rules.rb +8 -4
  81. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  82. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  83. data/lib/sqreen/rules/rule_cb.rb +2 -0
  84. data/lib/sqreen/rules/waf_cb.rb +3 -3
  85. data/lib/sqreen/runner.rb +47 -7
  86. data/lib/sqreen/session.rb +2 -0
  87. data/lib/sqreen/signals/conversions.rb +6 -1
  88. data/lib/sqreen/version.rb +1 -1
  89. data/lib/sqreen/weave/budget.rb +46 -0
  90. data/lib/sqreen/weave/legacy/instrumentation.rb +252 -109
  91. data/lib/sqreen/worker.rb +6 -2
  92. metadata +60 -11
  93. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -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.open(File.join(File.dirname(__FILE__), '../attack_detected.html'), 'rb', &:read)
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'
@@ -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
 
@@ -168,6 +167,10 @@ module Sqreen
168
167
  end
169
168
  self.features = wanted_features
170
169
 
170
+ @ecosystem_integration = EcosystemIntegration.new(framework, Sqreen.queue)
171
+ framework.req_start_cb = @ecosystem_integration.method(:request_start)
172
+ framework.req_end_cb = @ecosystem_integration.method(:request_end)
173
+
171
174
  # Ensure a deliverer is there unless features have set it first
172
175
  self.deliverer ||= Deliveries::Simple.new(session)
173
176
  context_infos = {}
@@ -268,6 +271,10 @@ module Sqreen
268
271
  rulespack_id, rules = load_rules(context_infos)
269
272
  @framework.instrument_when_ready!(instrumenter, rules)
270
273
  Sqreen.log.info 'Instrumentation set up'
274
+
275
+ # XXX: ecosystem instrumentation should likely be deferred
276
+ # the same way the rest might be
277
+ @ecosystem_integration.init
271
278
  rulespack_id.to_s
272
279
  end
273
280
 
@@ -387,11 +394,35 @@ module Sqreen
387
394
 
388
395
  def change_performance_budget(budget, _context_infos = {})
389
396
  return false unless budget.nil? || budget.to_f > 0
390
- prev = Sqreen.performance_budget
391
- Sqreen.update_performance_budget(budget)
397
+
398
+ if @configuration.get(:weave)
399
+ prev = Sqreen::Weave::Budget.current
400
+ prev = prev.to_h if prev
401
+
402
+ budget_s = budget.to_f / 1000 if budget
403
+
404
+ feature = features['performance_budget']
405
+ if feature
406
+ budget_s = feature['threshold'] if feature.key?('threshold')
407
+ ratio = feature['ratio'] if feature.key?('ratio')
408
+ end
409
+
410
+ Sqreen::Weave::Budget.update(threshold: budget_s, ratio: ratio)
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")
@@ -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.22.0'.freeze
8
8
  end
@@ -0,0 +1,46 @@
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, ratio = nil)
13
+ @threshold = threshold
14
+ @ratio = ratio
15
+ end
16
+
17
+ def static?
18
+ threshold && !ratio
19
+ end
20
+
21
+ def dynamic?
22
+ threshold && ratio
23
+ end
24
+
25
+ attr_reader :threshold
26
+ attr_reader :ratio
27
+
28
+ def to_h
29
+ { threshold: threshold, ratio: ratio }
30
+ end
31
+
32
+ class << self
33
+ attr_reader :current
34
+
35
+ def update(opts = nil)
36
+ Sqreen::Weave.logger.info("budget update:#{opts.inspect}")
37
+
38
+ return @current = nil if opts.nil? || opts.empty?
39
+
40
+ threshold = opts[:threshold]
41
+ ratio = opts[:ratio]
42
+
43
+ @current = new(threshold, ratio)
44
+ end
45
+ end
46
+ end
@@ -4,10 +4,13 @@
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'
11
14
 
12
15
  class Sqreen::Weave::Legacy::Instrumentation
13
16
  attr_accessor :metrics_engine
@@ -60,6 +63,27 @@ class Sqreen::Weave::Legacy::Instrumentation
60
63
  'options' => opts[:perf_metric_percent] || { 'base' => 1.3, 'factor' => 1.0 },
61
64
  )
62
65
 
66
+ metrics_engine.create_metric(
67
+ 'name' => 'req.sq.hook.overhead',
68
+ 'period' => 60,
69
+ 'kind' => 'Binning',
70
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
71
+ )
72
+
73
+ metrics_engine.create_metric(
74
+ 'name' => 'sq.hook.overhead',
75
+ 'period' => 60,
76
+ 'kind' => 'Binning',
77
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
78
+ )
79
+
80
+ metrics_engine.create_metric(
81
+ 'name' => 'sq.shrinkwrap',
82
+ 'period' => 60,
83
+ 'kind' => 'Binning',
84
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
85
+ )
86
+
63
87
  Sqreen.thread_cpu_time? && metrics_engine.create_metric(
64
88
  'name' => 'sq_thread_cpu_pct',
65
89
  'period' => opts[:period] || 60,
@@ -72,18 +96,79 @@ class Sqreen::Weave::Legacy::Instrumentation
72
96
  def instrument!(rules, framework)
73
97
  Sqreen::Weave.logger.debug { "#{rules.count} rules, #{framework}" }
74
98
 
99
+ # TODO: make config able to see if value was user-set or default
75
100
  strategy = Sqreen.config_get(:weave_strategy)
101
+ # TODO: factor generic hint system out
102
+ # TODO: factor those hint definitions to dependency
103
+ strategy_hints = []
76
104
  if strategy == :prepend && !Module.respond_to?(:prepend)
77
- Sqreen::Weave.logger.warn { "strategy: #{strategy.inspect} unavailable, falling back to :chain" }
78
- strategy = :chain
79
- elsif strategy == :chain && Gem::Specification.select { |s| s.name == 'scout_apm' && Gem::Requirement.new('>= 2.5.2').satisfied_by?(Gem::Version.new(s.version)) }.any?
80
- Sqreen::Weave.logger.warn { "strategy: #{strategy.inspect} unavailable with scout_apm >= 2.5.2, switching to :prepend" }
81
- strategy = :prepend
105
+ Sqreen::Weave.logger.debug { "strategy: #{strategy.inspect} unavailable, falling back to :chain" }
106
+ strategy_hints << [:chain, 'Module.respond_to?(:prepend)', 'false']
107
+ end
108
+ if Gem::Specification.select { |s| s.name == 'scout_apm' && Gem::Requirement.new('< 2.5.2').satisfied_by?(Gem::Version.new(s.version)) }.any?
109
+ Sqreen::Weave.logger.debug { "strategy: :prepend unavailable with scout_apm < 2.5.2, switching to :chain" }
110
+ strategy_hints << [:chain, 'scout_apm', '< 2.5.2']
111
+ end
112
+ if Gem::Specification.select { |s| s.name == 'scout_apm' && Gem::Requirement.new('>= 2.5.2').satisfied_by?(Gem::Version.new(s.version)) }.any?
113
+ Sqreen::Weave.logger.debug { "strategy: :chain unavailable with scout_apm >= 2.5.2, switching to :prepend" }
114
+ strategy_hints << [:prepend, 'scout_apm', '>= 2.5.2']
115
+ end
116
+ if Gem::Specification.select { |s| s.name == 'ddtrace' && Gem::Requirement.new('< 0.27').satisfied_by?(Gem::Version.new(s.version)) }.any?
117
+ Sqreen::Weave.logger.debug { "strategy: :prepend unavailable with ddtrace < 0.27, switching to :chain" }
118
+ strategy_hints << [:chain, 'ddtrace', '< 0.27']
119
+ end
120
+ if Gem::Specification.select { |s| s.name == 'ddtrace' && Gem::Requirement.new('>= 0.27').satisfied_by?(Gem::Version.new(s.version)) }.any?
121
+ Sqreen::Weave.logger.debug { "strategy: :chain unavailable with ddtrace >= 0.27, switching to :prepend" }
122
+ strategy_hints << [:prepend, 'ddtrace', '>= 0.27']
123
+ end
124
+ if Gem::Specification.select { |s| s.name == 'skylight' && Gem::Requirement.new('< 5.0.0.beta').satisfied_by?(Gem::Version.new(s.version)) }.any?
125
+ Sqreen::Weave.logger.debug { "strategy: :prepend unavailable with skylight < 5.0.0.beta, switching to :chain" }
126
+ strategy_hints << [:chain, 'skylight', '< 5.0.0.beta']
127
+ end
128
+ if Gem::Specification.select { |s| s.name == 'skylight' && Gem::Requirement.new('>= 5.0.0.beta').satisfied_by?(Gem::Version.new(s.version)) }.any?
129
+ Sqreen::Weave.logger.debug { "strategy: :chain unavailable with skylight >= 5.0.0.beta, switching to :prepend" }
130
+ strategy_hints << [:prepend, 'skylight', '>= 5.0.0.beta']
131
+ end
132
+ if strategy_hints.map(&:first).uniq.count > 1
133
+ raise Sqreen::Exception, "conflicting instrumentation strategies: #{strategy_hints.inspect}"
134
+ end
135
+ if strategy_hints.map(&:first).uniq.count == 1 && strategy != strategy_hints.first.first
136
+ was = strategy
137
+ strategy = strategy_hints.first.first
138
+ Sqreen::Weave.logger.warn { "strategy: #{strategy.inspect} was: #{was.inspect} hints: #{strategy_hints.inspect}" }
139
+ else
140
+ Sqreen::Weave.logger.info { "strategy: #{strategy.inspect}" }
82
141
  end
83
- Sqreen::Weave.logger.debug { "strategy: #{strategy.inspect}" }
84
142
 
85
143
  ### set up rule signature verifier
86
144
  verifier = nil
145
+ # TODO: check for JRuby via dependency
146
+ # TODO: reinstate signatures for JRuby
147
+ if Sqreen.config_get(:rules_verify_signature) == true && !defined?(::JRUBY_VERSION)
148
+ verifier = Sqreen::SqreenSignedVerifier.new
149
+ Sqreen::Weave.logger.debug('rules: signature status: enabled')
150
+ else
151
+ Sqreen::Weave.logger.debug('rules: signature status: disabled')
152
+ end
153
+
154
+ if verifier
155
+ invalid_rules = rules.reject do |rule|
156
+ valid = verifier.verify(rule)
157
+
158
+ if valid
159
+ Sqreen::Weave.logger.debug { "rule: #{rule['name']} signed: true result: ok" }
160
+ else
161
+ Sqreen::Weave.logger.error { "rule: #{rule['name']} singed: true result: fail" }
162
+ end
163
+ end
164
+ if invalid_rules.any?
165
+ Sqreen::Weave.logger.error { "weave: instrument status: abort reason: signature result: fail" }
166
+ raise Sqreen::Exception, "Signature error: rules: #{invalid_rules.map { |r| r['name'] }.inspect}"
167
+ else
168
+ Sqreen::Weave.logger.info { "weave: instrument rules: signed result: ok" }
169
+ end
170
+ end
171
+
87
172
  ### force clean instrumentation callback list
88
173
  @hooks = []
89
174
  ### for each rule description
@@ -94,6 +179,25 @@ class Sqreen::Weave::Legacy::Instrumentation
94
179
  next unless rule_callback
95
180
  ### attach framework to callback
96
181
  rule_callback.framework = framework
182
+ ## create metric
183
+ Sqreen::Weave.logger.debug { "Adding rule metric: #{rule_callback}" }
184
+ [:pre, :post, :failing].each do |whence|
185
+ next unless rule_callback.send(:"#{whence}?")
186
+ metric_name = "sq.#{rule['name']}.#{whence}"
187
+ metrics_engine.create_metric(
188
+ 'name' => metric_name,
189
+ 'period' => 60,
190
+ 'kind' => 'Binning',
191
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
192
+ )
193
+ metric_name = "req.sq.#{rule['name']}.#{whence}"
194
+ metrics_engine.create_metric(
195
+ 'name' => metric_name,
196
+ 'period' => 60,
197
+ 'kind' => 'Binning',
198
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
199
+ )
200
+ end
97
201
  ### install callback, observing priority
98
202
  Sqreen::Weave.logger.debug { "Adding rule callback: #{rule_callback}" }
99
203
  @hooks << add_callback("weave,rule=#{rule['name']}", rule_callback, strategy)
@@ -107,30 +211,43 @@ class Sqreen::Weave::Legacy::Instrumentation
107
211
  end
108
212
 
109
213
  metrics_engine = self.metrics_engine
214
+
110
215
  request_hook = Sqreen::Graft::Hook['Sqreen::ShrinkWrap#call', strategy]
111
216
  @hooks << request_hook
112
217
  request_hook.add do
113
218
  before('wave,meta,request', rank: -100000, mandatory: true) do |_call|
114
219
  next unless Sqreen.instrumentation_ready
115
220
 
116
- uuid = SecureRandom.uuid
117
- now = Sqreen::Graft::Timer.read
221
+ # shrinkwrap_timer = Sqreen::Graft::Timer.new('weave,shrinkwrap')
222
+ # shrinkwrap_timer.start
223
+
224
+ request_timer = Sqreen::Graft::Timer.new("request")
225
+ request_timer.start
226
+ sqreen_timer = Sqreen::Graft::Timer.new("sqreen")
227
+ budget = Sqreen::Weave::Budget.current
228
+ request_budget_threshold = budget.threshold if budget
229
+ request_budget_ratio = budget.ratio if budget
230
+ request_budget_is_dynamic = !request_budget_ratio.nil?
231
+ request_budget = !request_budget_threshold.nil?
232
+ timed_level = (Sqreen.features['perf_level'] || 1).to_i
233
+ Sqreen::Weave.logger.debug { "request budget: #{budget.to_h} timed.level: #{timed_level}" } if Sqreen::Weave.logger.debug?
234
+
118
235
  Thread.current[:sqreen_http_request] = {
119
- uuid: uuid,
120
- start_time: now,
121
- time_budget: Sqreen.performance_budget,
236
+ request_timer: request_timer,
237
+ sqreen_timer: sqreen_timer,
122
238
  time_budget_expended: false,
123
- timer: Sqreen::Graft::Timer.new("request_#{uuid}"),
239
+ time_budget_threshold: request_budget_threshold,
240
+ time_budget_dynamic: request_budget_is_dynamic,
241
+ time_budget_ratio: request_budget_ratio,
242
+ time_budget: request_budget,
124
243
  timed_callbacks: [],
125
244
  timed_hooks: [],
126
- timed_hooks_before: [],
127
- timed_hooks_after: [],
128
- timed_hooks_raised: [],
129
- timed_hooks_ensured: [],
245
+ timed_level: timed_level,
130
246
  skipped_callbacks: [],
247
+ # timed_shrinkwrap: shrinkwrap_timer,
131
248
  }
132
249
 
133
- Sqreen::Weave.logger.debug { "request.uuid: #{uuid}" }
250
+ # shrinkwrap_timer.stop
134
251
  end
135
252
 
136
253
  ensured('weave,meta,request', rank: 100000, mandatory: true) do |_call|
@@ -138,105 +255,118 @@ class Sqreen::Weave::Legacy::Instrumentation
138
255
 
139
256
  next if request.nil?
140
257
 
258
+ # shrinkwrap_timer = request[:timed_shrinkwrap]
259
+ # shrinkwrap_timer.start
260
+
141
261
  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
- )
262
+ request_timer = request[:request_timer]
263
+ now = request_timer.stop
264
+
265
+ if request[:timed_level] >= 1
266
+ request[:timed_callbacks].each do |timer|
267
+ duration = timer.duration
268
+
269
+ timer.tag =~ /weave,rule=(.*)$/ && rule = $1
270
+ next unless rule
271
+
272
+ whence = case timer.tag
273
+ when /@before/ then 'pre'
274
+ when /@after/ then 'post'
275
+ when /@raised/ then 'failing'
276
+ end
277
+ next unless whence
278
+
279
+ metric_name = "sq.#{rule}.#{whence}"
280
+ metrics_engine.update(metric_name, now, nil, duration * 1000)
281
+ # Sqreen.observations_queue.push([metric_name, nil, duration * 1000, utc_now])
166
282
  end
167
- metrics_engine.update(metric_name, now, nil, duration * 1000)
168
- end
169
283
 
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
- )
284
+ request[:timed_hooks].each do |timer|
285
+ duration = timer.duration
286
+ metrics_engine.update('sq.hook.overhead', now, nil, duration * 1000)
287
+ # Sqreen.observations_queue.push(['sq.hook.overhead', nil, duration * 1000, utc_now])
288
+ end
203
289
  end
204
- metrics_engine.update(metric_name, now, nil, duration * 1000)
205
290
 
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(', ')}]" }
291
+ sqreen_timer = request[:sqreen_timer]
292
+ total = sqreen_timer.duration
293
+ Sqreen::Weave.logger.debug { "request sqreen_timer.total: #{'%.03fus' % (total * 1_000_000)}" } if Sqreen::Weave.logger.debug?
294
+ total = request_timer.duration
295
+ Sqreen::Weave.logger.debug { "request request_timer.total: #{'%.03fus' % (total * 1_000_000)}" } if Sqreen::Weave.logger.debug?
296
+
297
+ if request[:timed_level] >= 2
298
+ skipped = request[:skipped_callbacks].map(&:name)
299
+ Sqreen::Weave.logger.debug { "request callback.skipped.count: #{skipped.count}" } if Sqreen::Weave.logger.debug?
300
+ timings = request[:timed_callbacks].map(&:to_s)
301
+ total = request[:timed_callbacks].sum(&:duration)
302
+ Sqreen::Weave.logger.debug { "request callback.total: #{'%.03fus' % (total * 1_000_000)} callback.count: #{timings.count}" } if Sqreen::Weave.logger.debug?
303
+ timings = request[:timed_hooks].map(&:to_s)
304
+ total = request[:timed_hooks].sum(&:duration)
305
+ Sqreen::Weave.logger.debug { "request hook.total: #{'%.03fus' % (total * 1_000_000)} hook.count: #{timings.count}" } if Sqreen::Weave.logger.debug?
306
+ end
217
307
 
218
308
  skipped = request[:skipped_callbacks].map(&:name)
219
309
  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
310
+ metrics_engine.update('request_overtime', now, skipped_rule_name, 1) if skipped_rule_name
311
+ # Sqreen.observations_queue.push(['request_overtime', skipped_rule_name, 1, utc_now]) if skipped_rule_name
221
312
 
222
- sqreen_request_duration = total
223
- Sqreen.observations_queue.push(['sq', nil, sqreen_request_duration * 1000, utc_now])
313
+ sqreen_request_duration = sqreen_timer.duration
314
+ metrics_engine.update('sq', now, nil, sqreen_request_duration * 1000)
315
+ # Sqreen.observations_queue.push(['sq', nil, sqreen_request_duration * 1000, utc_now])
224
316
 
225
- request_duration = now - request[:start_time]
226
- Sqreen.observations_queue.push(['req', nil, request_duration * 1000, utc_now])
317
+ request_duration = request_timer.duration
318
+ metrics_engine.update('req', now, nil, request_duration * 1000)
319
+ # Sqreen.observations_queue.push(['req', nil, request_duration * 1000, utc_now])
227
320
 
228
321
  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])
322
+ metrics_engine.update('pct', now, nil, sqreen_request_ratio)
323
+ # Sqreen.observations_queue.push(['pct', nil, sqreen_request_ratio, utc_now])
324
+ Sqreen::Weave.logger.debug { "request sqreen_timer.ratio: #{'%.03f' % (sqreen_request_ratio / 100.0)}" } if Sqreen::Weave.logger.debug?
325
+
326
+ if request[:timed_level] >= 2
327
+ tallies = Hash.new(0.0)
328
+ request[:timed_callbacks].each do |timer|
329
+ duration = timer.duration
330
+
331
+ timer.tag =~ /weave,rule=(.*)$/ && rule = $1
332
+ next unless rule
333
+
334
+ whence = case timer.tag
335
+ when /@before/ then 'pre'
336
+ when /@after/ then 'post'
337
+ when /@raised/ then 'failing'
338
+ end
339
+ next unless whence
340
+
341
+ metric_name = "req.sq.#{rule}.#{whence}"
342
+ tallies[metric_name] += duration
343
+ end
344
+ tallies.each do |metric_name, duration|
345
+ metrics_engine.update(metric_name, now, nil, duration * 1000)
346
+ # Sqreen.observations_queue.push([metric_name, nil, duration * 1000, utc_now])
347
+ end
348
+
349
+ duration = request[:timed_hooks].sum(&:duration)
350
+ metrics_engine.update('req.sq.hook.overhead', now, nil, duration * 1000)
351
+ # Sqreen.observations_queue.push(['req.sq.hook.overhead', nil, duration * 1000, utc_now])
352
+ end
353
+
354
+ # shrinkwrap_timer.stop
355
+
356
+ # duration = shrinkwrap_timer.duration
357
+ # metrics_engine.update('sq.shrinkwrap', now, nil, duration * 1000)
230
358
  end
231
359
  end.install
232
360
 
233
361
  ### globally declare instrumentation ready
234
362
  Sqreen.instrumentation_ready = true
363
+ Sqreen::Weave.logger.info { "Instrumentation activated" }
235
364
  end
236
365
 
237
366
  # needed by Sqreen::Runner
238
367
  def remove_all_callbacks
239
368
  Sqreen.instrumentation_ready = false
369
+ Sqreen::Weave.logger.info { "Instrumentation deactivated" }
240
370
 
241
371
  loop do
242
372
  hook = @hooks.pop
@@ -253,6 +383,15 @@ class Sqreen::Weave::Legacy::Instrumentation
253
383
  klass = callback.klass
254
384
  method = callback.method
255
385
 
386
+ if (call_count = ENV['SQREEN_DEBUG_CALL_COUNT'])
387
+ call_count = JSON.parse(call_count)
388
+ if callback.respond_to?(:rule_name) && call_count.key?(callback.rule_name)
389
+ count = call_count[callback.rule_name]
390
+ Sqreen::Weave.logger.debug { "override rule: #{callback.rule_name} call_count: #{count.inspect}" }
391
+ callback.instance_eval { @call_count_interval = call_count[callback.rule_name] }
392
+ end
393
+ end
394
+
256
395
  if Sqreen::Graft::HookPoint.new("#{klass}.#{method}").exist?
257
396
  hook_point = "#{klass}.#{method}"
258
397
  elsif Sqreen::Graft::HookPoint.new("#{klass}##{method}").exist?
@@ -275,7 +414,6 @@ class Sqreen::Weave::Legacy::Instrumentation
275
414
  a = call.args
276
415
  r = call.remaining
277
416
 
278
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#pre instance=#{i}" }
279
417
  begin
280
418
  ret = callback.pre(i, a, r)
281
419
  rescue StandardError => e
@@ -286,17 +424,26 @@ class Sqreen::Weave::Legacy::Instrumentation
286
424
  Sqreen::RemoteException.record(e)
287
425
  end
288
426
  end
289
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#pre instance=#{i} => return=#{ret.inspect}" }
290
427
 
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)
428
+ next if ret.nil? || !ret.is_a?(Hash)
429
+
430
+ throw_val =
431
+ case ret[:status]
432
+ when :skip, 'skip'
433
+ b.return(ret[:new_return_value]).break! if ret.key?(:new_return_value)
434
+ when :modify_args, 'modify_args'
435
+ b.args(ret[:args])
436
+ when :raise, 'raise'
437
+ if ret.key?(:exception)
438
+ b.raise(ret[:exception])
439
+ else
440
+ b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required."))
441
+ end
442
+ end
443
+
444
+ next unless throw_val
445
+ throw_val.break! if ret[:skip_rem_cbs]
446
+ throw(b, throw_val)
300
447
  end
301
448
  end
302
449
 
@@ -309,7 +456,6 @@ class Sqreen::Weave::Legacy::Instrumentation
309
456
  a = call.args
310
457
  r = call.remaining
311
458
 
312
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#post instance=#{i}" }
313
459
  begin
314
460
  ret = callback.post(v, i, a, r)
315
461
  rescue StandardError => e
@@ -320,7 +466,6 @@ class Sqreen::Weave::Legacy::Instrumentation
320
466
  Sqreen::RemoteException.record(e)
321
467
  end
322
468
  end
323
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#post instance=#{i} => return=#{ret.inspect}" }
324
469
 
325
470
  case ret[:status]
326
471
  when :override, 'override'
@@ -341,7 +486,6 @@ class Sqreen::Weave::Legacy::Instrumentation
341
486
  a = call.args
342
487
  r = call.remaining
343
488
 
344
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#failing instance=#{i}" }
345
489
  begin
346
490
  ret = callback.failing(e, i, a, r)
347
491
  rescue StandardError => e
@@ -352,7 +496,6 @@ class Sqreen::Weave::Legacy::Instrumentation
352
496
  Sqreen::RemoteException.record(e)
353
497
  end
354
498
  end
355
- Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#failing instance=#{i} => return=#{ret.inspect}" }
356
499
 
357
500
  throw(b, b.raise(e)) if ret.nil? || !ret.is_a?(Hash)
358
501