sqreen 1.19.4 → 1.20.4.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -1
  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/agent_message.rb +20 -0
  7. data/lib/sqreen/aggregated_metric.rb +25 -0
  8. data/lib/sqreen/attack_detected.html +1 -2
  9. data/lib/sqreen/ca.crt +24 -0
  10. data/lib/sqreen/configuration.rb +11 -5
  11. data/lib/sqreen/deferred_logger.rb +50 -14
  12. data/lib/sqreen/deliveries/batch.rb +4 -1
  13. data/lib/sqreen/deliveries/simple.rb +4 -0
  14. data/lib/sqreen/deprecation.rb +38 -0
  15. data/lib/sqreen/endpoint_testing.rb +184 -0
  16. data/lib/sqreen/event.rb +7 -5
  17. data/lib/sqreen/events/attack.rb +23 -18
  18. data/lib/sqreen/events/remote_exception.rb +0 -22
  19. data/lib/sqreen/events/request_record.rb +15 -70
  20. data/lib/sqreen/frameworks/generic.rb +9 -0
  21. data/lib/sqreen/frameworks/rails.rb +0 -7
  22. data/lib/sqreen/frameworks/request_recorder.rb +13 -2
  23. data/lib/sqreen/graft/call.rb +76 -18
  24. data/lib/sqreen/graft/callback.rb +1 -1
  25. data/lib/sqreen/graft/hook.rb +187 -85
  26. data/lib/sqreen/graft/hook_point.rb +1 -1
  27. data/lib/sqreen/kit/signals/specialized/aggregated_metric.rb +72 -0
  28. data/lib/sqreen/kit/signals/specialized/attack.rb +57 -0
  29. data/lib/sqreen/kit/signals/specialized/binning_metric.rb +76 -0
  30. data/lib/sqreen/kit/signals/specialized/http_trace.rb +26 -0
  31. data/lib/sqreen/kit/signals/specialized/sdk_track_call.rb +50 -0
  32. data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +57 -0
  33. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  34. data/lib/sqreen/legacy/old_event_submission_strategy.rb +221 -0
  35. data/lib/sqreen/legacy/waf_redactions.rb +49 -0
  36. data/lib/sqreen/log.rb +3 -2
  37. data/lib/sqreen/log/loggable.rb +2 -1
  38. data/lib/sqreen/logger.rb +24 -0
  39. data/lib/sqreen/metrics/base.rb +3 -0
  40. data/lib/sqreen/metrics_store.rb +33 -12
  41. data/lib/sqreen/null_logger.rb +22 -0
  42. data/lib/sqreen/performance_notifications/binned_metrics.rb +8 -2
  43. data/lib/sqreen/remote_command.rb +1 -0
  44. data/lib/sqreen/rules.rb +5 -3
  45. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  46. data/lib/sqreen/rules/rule_cb.rb +4 -0
  47. data/lib/sqreen/rules/waf_cb.rb +11 -8
  48. data/lib/sqreen/runner.rb +103 -10
  49. data/lib/sqreen/sensitive_data_redactor.rb +19 -31
  50. data/lib/sqreen/session.rb +51 -43
  51. data/lib/sqreen/signals/conversions.rb +283 -0
  52. data/lib/sqreen/signals/http_trace_redaction.rb +111 -0
  53. data/lib/sqreen/signals/signals_submission_strategy.rb +78 -0
  54. data/lib/sqreen/version.rb +1 -1
  55. data/lib/sqreen/weave/budget.rb +46 -0
  56. data/lib/sqreen/weave/legacy/instrumentation.rb +156 -94
  57. data/lib/sqreen/worker.rb +6 -2
  58. metadata +51 -8
  59. data/lib/sqreen/backport.rb +0 -9
  60. data/lib/sqreen/backport/clock_gettime.rb +0 -74
  61. data/lib/sqreen/backport/original_name.rb +0 -88
@@ -8,17 +8,19 @@
8
8
  module Sqreen
9
9
  # Master interface for point in time events (e.g. Attack, RemoteException)
10
10
  class Event
11
+ # @return [Hash]
11
12
  attr_reader :payload
13
+
14
+ # @return [Time]
15
+ attr_accessor :time # writer used only in tests
16
+
12
17
  def initialize(payload)
13
18
  @payload = payload
14
- end
15
-
16
- def to_hash
17
- payload.to_hash
19
+ @time = Time.now.utc
18
20
  end
19
21
 
20
22
  def to_s
21
- "<#{self.class.name}: #{to_hash}>"
23
+ "<#{self.class.name}: #{payload.to_hash}>"
22
24
  end
23
25
  end
24
26
  end
@@ -11,6 +11,8 @@ module Sqreen
11
11
  # Attack
12
12
  # When creating a new attack, it gets automatically pushed to the event's
13
13
  # queue.
14
+ # XXX: TURNS OUT THIS CLASS IS ACTUALLY NOT USED ANYMORE
15
+ # Framework.observe is used instead with unstructured attack details
14
16
  class Attack < Event
15
17
  def self.record(payload)
16
18
  attack = Attack.new(payload)
@@ -26,11 +28,31 @@ module Sqreen
26
28
  payload['rule']['rulespack_id']
27
29
  end
28
30
 
29
- def type
31
+ def rule_name
30
32
  return nil unless payload['rule']
31
33
  payload['rule']['name']
32
34
  end
33
35
 
36
+ def test?
37
+ return nil unless payload['rule']
38
+ payload['rule']['test'] ? true : false
39
+ end
40
+
41
+ def beta?
42
+ return nil unless payload['rule']
43
+ payload['rule']['beta'] ? true : false
44
+ end
45
+
46
+ def block?
47
+ return nil unless payload['rule']
48
+ payload['rule']['block'] ? true : false
49
+ end
50
+
51
+ def attack_type
52
+ return nil unless payload['rule']
53
+ payload['rule']['attack_type']
54
+ end
55
+
34
56
  def time
35
57
  return nil unless payload['local']
36
58
  payload['local']['time']
@@ -44,22 +66,5 @@ module Sqreen
44
66
  def enqueue
45
67
  Sqreen.queue.push(self)
46
68
  end
47
-
48
- def to_hash
49
- res = {}
50
- rule_p = payload['rule']
51
- request_p = payload['request']
52
- res[:rule_name] = rule_p['name'] if rule_p && rule_p['name']
53
- res[:rulespack_id] = rule_p['rulespack_id'] if rule_p && rule_p['rulespack_id']
54
- res[:test] = rule_p['test'] if rule_p && rule_p['test']
55
- res[:infos] = payload['infos'] if payload['infos']
56
- res[:time] = time if time
57
- res[:client_ip] = request_p[:addr] if request_p && request_p[:addr]
58
- res[:request] = request_p if request_p
59
- res[:params] = payload['params'] if payload['params']
60
- res[:context] = payload['context'] if payload['context']
61
- res[:headers] = payload['headers'] if payload['headers']
62
- res
63
- end
64
69
  end
65
70
  end
@@ -30,27 +30,5 @@ module Sqreen
30
30
  def klass
31
31
  payload['exception'].class.name
32
32
  end
33
-
34
- def to_hash
35
- exception = payload['exception']
36
- ev = {
37
- :klass => exception.class.name,
38
- :message => exception.message,
39
- :params => payload['request_params'],
40
- :time => payload['time'],
41
- :infos => {
42
- :client_ip => payload['client_ip'],
43
- },
44
- :request => payload['request_infos'],
45
- :headers => payload['headers'],
46
- :rule_name => payload['rule_name'],
47
- :rulespack_id => payload['rulespack_id'],
48
- }
49
-
50
- ev[:infos].merge!(payload['infos']) if payload['infos']
51
- return ev unless exception.backtrace
52
- ev[:context] = { :backtrace => exception.backtrace.map(&:to_s) }
53
- ev
54
- end
55
33
  end
56
34
  end
@@ -14,6 +14,10 @@ require 'sqreen/sensitive_data_redactor'
14
14
  module Sqreen
15
15
  # When a request is deeemed worthy of being sent to the backend
16
16
  class RequestRecord < Sqreen::Event
17
+ attr_reader :redactor
18
+
19
+ # @param [Hash] payload
20
+ # @param [Sqreen::SensitiveDataRedactor] redactor
17
21
  def initialize(payload, redactor = nil)
18
22
  @redactor = redactor
19
23
  super(payload)
@@ -23,74 +27,18 @@ module Sqreen
23
27
  (payload && payload[:observed]) || {}
24
28
  end
25
29
 
26
- def to_hash
27
- res = { :version => '20171208' }
28
- if payload[:observed]
29
- res[:observed] = payload[:observed].dup
30
- rulespack = nil
31
- if observed[:attacks]
32
- res[:observed][:attacks] = observed[:attacks].map do |att|
33
- natt = att.dup
34
- rulespack = natt.delete(:rulespack_id) || rulespack
35
- natt
36
- end
37
- end
38
- if observed[:sqreen_exceptions]
39
- res[:observed][:sqreen_exceptions] = observed[:sqreen_exceptions].map do |exc|
40
- nex = exc.dup
41
- excp = nex.delete(:exception)
42
- if excp
43
- nex[:message] = excp.message
44
- nex[:klass] = excp.class.name
45
- end
46
- rulespack = nex.delete(:rulespack_id) || rulespack
47
- nex
48
- end
49
- end
50
- res[:rulespack_id] = rulespack unless rulespack.nil?
51
- if observed[:observations]
52
- res[:observed][:observations] = observed[:observations].map do |cat, key, value, time|
53
- { :category => cat, :key => key, :value => value, :time => time }
54
- end
55
- end
56
- if observed[:sdk]
57
- res[:observed][:sdk] = processed_sdk_calls
58
- end
59
- end
60
- res[:local] = payload['local'] if payload['local']
61
- if payload['request']
62
- res[:request] = payload['request'].dup
63
- res[:client_ip] = res[:request].delete(:client_ip) if res[:request][:client_ip]
64
- else
65
- res[:request] = {}
66
- end
67
- if payload['response']
68
- res[:response] = payload['response'].dup
69
- else
70
- res[:response] = {}
71
- end
72
-
73
- res[:request][:parameters] = payload['params'] if payload['params']
74
- res[:request][:headers] = payload['headers'] if payload['headers']
75
-
76
- res = Sqreen::EncodingSanitizer.sanitize(res)
30
+ def last_identify_args
31
+ return nil unless observed[:sdk]
77
32
 
78
- if @redactor
79
- res[:request], redacted = @redactor.redact(res[:request])
80
- if redacted.any? && res[:observed] && res[:observed][:attacks]
81
- res[:observed][:attacks] = @redactor.redact_attacks!(res[:observed][:attacks], redacted)
82
- end
83
- if redacted.any? && res[:observed] && res[:observed][:sqreen_exceptions]
84
- res[:observed][:sqreen_exceptions] = @redactor.redact_exceptions!(res[:observed][:sqreen_exceptions], redacted)
85
- end
33
+ observed[:sdk].reverse_each do |meth, _time, *args|
34
+ next unless meth == :identify
35
+ return args
86
36
  end
87
-
88
- res
37
+ nil
89
38
  end
90
39
 
91
- private
92
-
93
40
  def processed_sdk_calls
41
+ return [] unless observed[:sdk]
94
42
  auth_keys = last_identify_id
95
43
 
96
44
  observed[:sdk].map do |meth, time, *args|
@@ -102,6 +50,8 @@ module Sqreen
102
50
  end
103
51
  end
104
52
 
53
+ private
54
+
105
55
  def inject_identifiers(args, meth, auth_keys)
106
56
  return args unless meth == :track && auth_keys
107
57
 
@@ -118,13 +68,8 @@ module Sqreen
118
68
  end
119
69
 
120
70
  def last_identify_id
121
- return nil unless observed[:sdk]
122
-
123
- observed[:sdk].reverse_each do |meth, _time, *args|
124
- next unless meth == :identify
125
- return args.first if args.respond_to? :first
126
- end
127
- nil
71
+ args = last_identify_args
72
+ args.first if args.respond_to? :first
128
73
  end
129
74
  end
130
75
  end
@@ -209,7 +209,16 @@ module Sqreen
209
209
 
210
210
  # Should the agent not be starting up?
211
211
  def prevent_startup
212
+ # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
213
+ return :sidekiq_cli if defined?(Sidekiq::CLI)
214
+ return :delayed_job if defined?(Delayed::Command)
215
+
216
+ # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
217
+ run_in_test = sqreen_configuration.get(:run_in_test)
218
+ return :rake if !run_in_test && $0.end_with?('rake')
219
+
212
220
  return :irb if $0 == 'irb'
221
+
213
222
  return if sqreen_configuration.nil?
214
223
  disable = sqreen_configuration.get(:disable)
215
224
  return :config_disable if disable == true || disable.to_s.to_i == 1
@@ -103,13 +103,6 @@ module Sqreen
103
103
  run_in_test = sqreen_configuration.get(:run_in_test)
104
104
  return :rails_test if !run_in_test && (Rails.env.test? || Rails.env.cucumber?)
105
105
 
106
- # SQREEN-880 - prevent Sqreen startup on Sidekiq workers
107
- return :sidekiq_cli if defined?(Sidekiq::CLI)
108
- return :delayed_job if defined?(Delayed::Command)
109
-
110
- # Prevent Sqreen startup on rake tasks - unless this is a Sqreen test
111
- return :rake if !run_in_test && $0.end_with?('rake')
112
-
113
106
  return nil unless defined?(Rails::CommandsTasks)
114
107
  return nil if defined?(Rails::Server)
115
108
  return :rails_console if defined?(Rails::Console)
@@ -58,12 +58,20 @@ module Sqreen
58
58
  Sqreen.log.debug { "close_request_record called. observed_items: #{observed_items}" }
59
59
 
60
60
  clean_request_record if observed_items.nil?
61
- if only_metric_observation
61
+ if Sqreen.features['use_signals'] || only_metric_observation
62
62
  push_metrics(observations_queue, queue)
63
- return clean_request_record
64
63
  end
64
+
65
+ if only_metric_observation
66
+ clean_request_record
67
+ return
68
+ end
69
+
70
+ # signals require request section to be present
71
+ payload_requests << 'request'
65
72
  payload = payload_creator.payload(payload_requests)
66
73
  payload[:observed] = observed_items
74
+
67
75
  queue.push create_request_record(payload)
68
76
  clean_request_record
69
77
  end
@@ -79,10 +87,13 @@ module Sqreen
79
87
  @redactor ||= SensitiveDataRedactor.from_config
80
88
  end
81
89
 
90
+ # pushes metric observations to the observations queue
91
+ # and clears the list for the request record
82
92
  def push_metrics(observations_queue, event_queue)
83
93
  observed_items[:observations].each do |obs|
84
94
  observations_queue.push obs
85
95
  end
96
+ observed_items[:observations] = []
86
97
  return unless observations_queue.size > MAX_OBS_QUEUE_LENGTH / 2
87
98
  event_queue.push Sqreen::METRICS_EVENT
88
99
  end
@@ -93,58 +93,116 @@ module Sqreen
93
93
  end
94
94
  end
95
95
 
96
+ class TimerError < StandardError; end
97
+
96
98
  class Timer
97
99
  def self.read
98
100
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
101
  end
100
102
 
101
- attr_reader :tag
103
+ attr_reader :tag, :size
102
104
 
103
105
  def initialize(tag, &block)
104
106
  @tag = tag
105
- @blips = []
106
107
  @block = block
108
+ @tally = 0
109
+ @size = 0
107
110
  end
108
111
 
109
- def duration
110
- @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e }
112
+ def elapsed
113
+ raise(TimerError, 'Timer#elapsed when paused') if @size.even?
114
+
115
+ @tally + Timer.read
111
116
  end
112
117
 
113
- def elapsed
114
- @blips.each_with_index.reduce(0) { |a, (e, i)| i.even? ? a - e : a + e } + Timer.read
118
+ def duration
119
+ raise(TimerError, 'Timer#duration when running') if @size.odd?
120
+
121
+ @tally
115
122
  end
116
123
 
117
124
  def ignore
118
- @blips << Timer.read
125
+ raise(TimerError, 'Timer#ignore when paused') if @size.even?
126
+
127
+ @size += 1
128
+ @tally += Timer.read
119
129
  yield(self)
120
130
  ensure
121
- @blips << Timer.read
131
+ @size += 1
132
+ @tally -= Timer.read
122
133
  end
123
134
 
124
- def measure
125
- @blips << Timer.read
135
+ def measure(opts = nil)
136
+ raise(TimerError, 'Timer#measure when running') if @size.odd?
137
+
138
+ now = Timer.read
139
+
140
+ ignore = opts[:ignore] if opts
141
+ if ignore
142
+ ignore.size += 1
143
+ ignore.tally += now
144
+ end
145
+
146
+ @size += 1
147
+ @tally -= now
148
+
126
149
  yield(self)
127
150
  ensure
128
- @blips << Timer.read
151
+ now = Timer.read
152
+
153
+ if ignore
154
+ ignore.size += 1
155
+ ignore.tally -= now
156
+ end
157
+
158
+ @size += 1
159
+ @tally += now
160
+
129
161
  @block.call(self) if @block
130
- Sqreen::Graft.logger.debug { "#{@tag}: time=%.03fus" % (duration * 1_000_000) }
131
162
  end
132
163
 
133
- def start
134
- @blips << Timer.read
164
+ def start(at = Timer.read)
165
+ raise(TimerError, 'Timer#start when started') unless @size.even?
166
+
167
+ @size += 1
168
+ @tally -= at
169
+
170
+ at
171
+ end
172
+
173
+ def stop(at = Timer.read)
174
+ raise(TimerError, 'Timer#stop when unstarted') unless @size.odd?
175
+
176
+ @size += 1
177
+ @tally += at
178
+
179
+ at
180
+ end
181
+
182
+ def started?
183
+ @size != 0 && @size.odd?
135
184
  end
136
185
 
137
- def stop
138
- @blips << Timer.read
186
+ def stopped?
187
+ @size != 0 && @size.even?
139
188
  end
140
189
 
141
- def size
142
- @blips.size
190
+ def running?
191
+ @size.odd?
192
+ end
193
+
194
+ def paused?
195
+ @size.even?
143
196
  end
144
197
 
145
198
  def to_s
146
199
  "#{@tag}: time=%.03fus" % (duration * 1_000_000)
147
200
  end
201
+
202
+ protected
203
+
204
+ attr_reader :tally
205
+ attr_writer :size, :tally
148
206
  end
149
207
  end
150
208
  end
@@ -21,7 +21,7 @@ module Sqreen
21
21
  end
22
22
 
23
23
  def call(*args, &block)
24
- Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" }
24
+ # Sqreen::Graft.logger.debug { "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}" } if Sqreen::Graft.logger.debug?
25
25
  return if @disabled
26
26
  @block.call(*args, &block)
27
27
  end