sqreen 1.15.0-java → 1.15.5-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sqreen/actions.rb +114 -43
  3. data/lib/sqreen/callback_tree.rb +16 -3
  4. data/lib/sqreen/callbacks.rb +7 -34
  5. data/lib/sqreen/capped_queue.rb +5 -1
  6. data/lib/sqreen/configuration.rb +4 -0
  7. data/lib/sqreen/deliveries/batch.rb +7 -4
  8. data/lib/sqreen/event.rb +4 -0
  9. data/lib/sqreen/events/request_record.rb +40 -7
  10. data/lib/sqreen/frameworks/generic.rb +0 -2
  11. data/lib/sqreen/frameworks/request_recorder.rb +14 -1
  12. data/lib/sqreen/instrumentation.rb +57 -33
  13. data/lib/sqreen/js/mini_racer_adapter.rb +46 -8
  14. data/lib/sqreen/metrics/average.rb +1 -1
  15. data/lib/sqreen/metrics/base.rb +4 -2
  16. data/lib/sqreen/metrics/binning.rb +3 -2
  17. data/lib/sqreen/metrics/collect.rb +1 -1
  18. data/lib/sqreen/metrics/sum.rb +1 -1
  19. data/lib/sqreen/metrics_store.rb +10 -5
  20. data/lib/sqreen/mono_time.rb +18 -0
  21. data/lib/sqreen/performance_notifications.rb +13 -38
  22. data/lib/sqreen/performance_notifications/binned_metrics.rb +12 -14
  23. data/lib/sqreen/performance_notifications/log.rb +6 -1
  24. data/lib/sqreen/performance_notifications/log_performance.rb +3 -1
  25. data/lib/sqreen/performance_notifications/metrics.rb +6 -3
  26. data/lib/sqreen/performance_notifications/newrelic.rb +6 -2
  27. data/lib/sqreen/remote_command.rb +26 -0
  28. data/lib/sqreen/rule_attributes.rb +1 -0
  29. data/lib/sqreen/rule_callback.rb +38 -0
  30. data/lib/sqreen/rules_callbacks/binding_accessor_matcher.rb +3 -2
  31. data/lib/sqreen/rules_callbacks/blacklist_ips.rb +1 -1
  32. data/lib/sqreen/rules_callbacks/run_block_user_actions.rb +1 -1
  33. data/lib/sqreen/rules_callbacks/run_req_start_actions.rb +8 -2
  34. data/lib/sqreen/runner.rb +11 -8
  35. data/lib/sqreen/sdk.rb +7 -1
  36. data/lib/sqreen/session.rb +4 -0
  37. data/lib/sqreen/trie.rb +274 -0
  38. data/lib/sqreen/version.rb +1 -1
  39. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae1a29b376a222af3ccfc344c0c1010e5043f5f249113e917038af356d8ac24e
4
- data.tar.gz: 3c5bad999caff0579ee484efe534e7b2b2b36fa0f68fac9f8900422c816b4ed3
3
+ metadata.gz: bb1cc783655676697b23d94b34643b1990d7ef8011d4304d1f8602ee39830e0e
4
+ data.tar.gz: d569d191c90467462c4b9431750e31df1eac0f2a7fd5f8b9f85707937b460da6
5
5
  SHA512:
6
- metadata.gz: 571a374c56db67eaed6775693c261467299a41e0f183e1d1847281d9998ded1611ca4cd6805372bab3bdfa42e1b0fb98d00dcc4433ee2aea89284a3d3cde9779
7
- data.tar.gz: dabdb93c32dced05d2d7b56e89fa6e829b0e83cf4e07b98c3b12c15ba82ace39e64210c08e16a4f7f059c2d6cdeea42a54f4f29a0646db36cea098d611d661e6
6
+ metadata.gz: 0e415a37c0cccbb9776f1c46d39e7d6a2da002950cf1a97d680c777c636ebfd225aeaebc552d50b1bf92e934946ceaf5a04214efa74ccb2b51e9d4f8787851be
7
+ data.tar.gz: 624b68dbc3150ec7f9637c9926c2ee9f02134310ee69327d161ea45d1959846b97c0458fdadf69842e6042a2e59dbbbc6e27733e5e12eb85d8bca6982bc44e2f
@@ -2,6 +2,7 @@
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
 
4
4
  require 'ipaddr'
5
+ require 'sqreen/trie'
5
6
  require 'sqreen/log'
6
7
  require 'sqreen/exception'
7
8
  require 'sqreen/sdk'
@@ -24,21 +25,17 @@ module Sqreen
24
25
  class Repository
25
26
  include Singleton
26
27
 
27
- def initialize
28
- @actions = {} # indexed by subclass
29
- @actions.default_proc = proc { |h, k| h[k] = [] }
28
+ def add(params, action)
29
+ action.class.index(params || {}, action)
30
30
  end
31
31
 
32
- def <<(action)
33
- @actions[action.class] << action
34
- end
35
-
36
- def [](action_class)
37
- @actions[action_class]
32
+ def get(action_class, key)
33
+ action_class = Base.get_type_class(action_class) unless action_class.class == Class
34
+ action_class.actions_matching key
38
35
  end
39
36
 
40
37
  def clear
41
- @actions.clear
38
+ Base.known_subclasses.each(&:clear)
42
39
  end
43
40
  end
44
41
 
@@ -68,6 +65,8 @@ module Sqreen
68
65
  end
69
66
 
70
67
  # Base class for actions
68
+ # subclasses must also implement some methods in their singleton classes
69
+ # (actions_matching, index and clear)
71
70
  class Base
72
71
  attr_reader :id, :expiry, :send_response
73
72
 
@@ -124,10 +123,27 @@ module Sqreen
124
123
  @@subclasses[name]
125
124
  end
126
125
 
126
+ def known_subclasses
127
+ @@subclasses.values
128
+ end
129
+
127
130
  def known_types
128
131
  @@subclasses.keys
129
132
  end
130
133
 
134
+ # all actions matching, possibly already expired
135
+ def actions_matching(_key)
136
+ raise 'implement in singletons of subclasses'
137
+ end
138
+
139
+ def index(_params, _action)
140
+ raise 'implement in singletons of subclasses'
141
+ end
142
+
143
+ def clear
144
+ raise 'implement in singletons of subclasses'
145
+ end
146
+
131
147
  def inherited(subclass)
132
148
  class << subclass
133
149
  public :new
@@ -143,43 +159,75 @@ module Sqreen
143
159
  end
144
160
  end
145
161
 
146
- module IpRanges
147
- attr_reader :ranges
162
+ module IpRangesIndex
163
+ def add_prefix(prefix_str, data)
164
+ @trie_v4 ||= Sqreen::Trie.new
165
+ @trie_v6 ||= Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
166
+ prefix = Sqreen::Prefix.from_str(prefix_str, data)
167
+ trie = prefix.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
168
+ trie.insert prefix
169
+ end
170
+
171
+ def matching_actions(client_ip)
172
+ parsed_ip = IPAddr.new(client_ip)
173
+ trie = parsed_ip.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
174
+ return [] unless trie
175
+ found = trie.search_matching(parsed_ip.to_i, parsed_ip.family)
176
+ return [] unless found.size > 0
177
+
178
+ Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
179
+ found.map(&:data)
180
+ end
181
+
182
+ def clear
183
+ @trie_v4 = Sqreen::Trie.new
184
+ @trie_v6 = Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
185
+ end
186
+ end
187
+
188
+ module IpRangeIndexedActionClass
189
+ include IpRangesIndex
190
+
191
+ def actions_matching(client_ip)
192
+ matching_actions client_ip
193
+ end
194
+
195
+ def index(params, action)
196
+ ranges = parse_ip_ranges params
197
+
198
+ ranges.each do |r|
199
+ add_prefix r, action
200
+ end
201
+ end
202
+
203
+ private
148
204
 
205
+ # returns array of prefixes in string form
149
206
  def parse_ip_ranges(params)
150
207
  ranges = params['ip_cidr']
151
208
  unless ranges && ranges.is_a?(Array) && !ranges.empty?
152
209
  raise 'no non-empty ip_cidr array present'
153
210
  end
154
211
 
155
- @ranges = ranges.map &IPAddr.method(:new)
156
- end
157
-
158
- def matches_ip?(client_ip)
159
- parsed_ip = IPAddr.new client_ip
160
- found = ranges.find { |r| r.include? parsed_ip }
161
- return false unless found
162
-
163
- Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
164
- true
212
+ ranges
165
213
  end
166
214
  end
167
215
 
168
216
  # Block a list of IP address ranges. Standard "raise" behavior.
169
217
  class BlockIp < Base
170
- include IpRanges
218
+ extend IpRangeIndexedActionClass
219
+
171
220
  self.type_name = 'block_ip'
172
221
 
173
222
  def initialize(id, opts, params = {})
223
+ # no need to store the ranges for this action, the index filter the class
174
224
  super(id, opts)
175
- parse_ip_ranges params
176
225
  end
177
226
 
178
227
  def do_run(client_ip)
179
- return nil unless matches_ip? client_ip
180
228
  e = Sqreen::AttackBlocked.new("Blocked client's IP #{client_ip} " \
181
- "(action: #{id} covering range(s) #{ranges}). No action is required")
182
- { :status => :raise, :exception => e }
229
+ "(action: #{id}). No action is required")
230
+ { :status => :raise, :exception => e, :skip_rem_cbs => true }
183
231
  end
184
232
 
185
233
  def event_properties(client_ip)
@@ -190,7 +238,8 @@ module Sqreen
190
238
  # Block a list of IP address ranges by forcefully redirecting the user
191
239
  # to a specific URL.
192
240
  class RedirectIp < Base
193
- include IpRanges
241
+ extend IpRangeIndexedActionClass
242
+
194
243
  self.type_name = 'redirect_ip'
195
244
 
196
245
  attr_reader :redirect_url
@@ -199,16 +248,15 @@ module Sqreen
199
248
  super(id, opts)
200
249
  @redirect_url = params['url']
201
250
  raise "no url provided for action #{id}" unless @redirect_url
202
- parse_ip_ranges params
203
251
  end
204
252
 
205
253
  def do_run(client_ip)
206
- return nil unless matches_ip? client_ip
207
254
  Sqreen.log.info "Will request redirect for client with IP #{client_ip} " \
208
- "(action: #{id} covering range(s) #{ranges})."
255
+ "(action: #{id})."
209
256
  {
210
257
  :status => :skip,
211
258
  :new_return_value => [303, { 'Location' => @redirect_url }, ['']],
259
+ :skip_rem_cbs => true,
212
260
  }
213
261
  end
214
262
 
@@ -222,15 +270,46 @@ module Sqreen
222
270
  class BlockUser < Base
223
271
  self.type_name = 'block_user'
224
272
 
273
+ class << self
274
+ def actions_matching(identity_params)
275
+ return [] unless @idx
276
+ key = stringify_keys(identity_params)
277
+ actions = @idx[key]
278
+ actions || []
279
+ end
280
+
281
+ def index(params, action)
282
+ @idx ||= {}
283
+ users = params['users']
284
+ raise ::Sqreen::Exception, 'nil "users" param for block_user action' if users.nil?
285
+ raise ::Sqreen::Exception, '"users" param must be an array' unless users.is_a? Array
286
+
287
+ users.each do |u|
288
+ @idx[u] ||= []
289
+ @idx[u] << action
290
+ end
291
+ end
292
+
293
+ def clear
294
+ @idx = {}
295
+ end
296
+
297
+ private
298
+
299
+ def stringify_keys(hash)
300
+ Hash[
301
+ hash.map { |k, v| [k.to_s, v] }
302
+ ]
303
+ end
304
+ end
305
+
306
+ # BlockUser proper definition continues
307
+
225
308
  def initialize(id, opts, params = {})
226
309
  super(id, opts)
227
- @users = params['users']
228
- raise ::Sqreen::Exception, 'nil "users" param for block_user action' if @users.nil?
229
- raise ::Sqreen::Exception, '"users" param must be an array' unless @users.is_a? Array
230
310
  end
231
311
 
232
312
  def do_run(identity_params)
233
- return unless @users.include? stringify_keys(identity_params)
234
313
  Sqreen.log.info(
235
314
  "Will raise due to user being blocked by action #{id}. " \
236
315
  "Blocked user identity: #{identity_params}"
@@ -250,14 +329,6 @@ module Sqreen
250
329
  def event_properties(identity_params)
251
330
  { 'user' => identity_params }
252
331
  end
253
-
254
- private
255
-
256
- def stringify_keys(hash)
257
- Hash[
258
- hash.map { |k, v| [k.to_s, v] }
259
- ]
260
- end
261
332
  end
262
333
  end
263
334
  end
@@ -25,15 +25,15 @@ module Sqreen
25
25
 
26
26
  if cb.pre?
27
27
  methods[0] ||= []
28
- methods[0] << cb
28
+ add_to_array(methods[0], cb)
29
29
  end
30
30
  if cb.post?
31
31
  methods[1] ||= []
32
- methods[1] << cb
32
+ add_to_array(methods[1], cb)
33
33
  end
34
34
  if cb.failing?
35
35
  methods[2] ||= []
36
- methods[2] << cb
36
+ add_to_array(methods[2], cb)
37
37
  end
38
38
  end
39
39
 
@@ -88,5 +88,18 @@ module Sqreen
88
88
  end
89
89
  end
90
90
  end
91
+
92
+ private
93
+
94
+ def add_to_array(arr, cb)
95
+ # find last element with prio equal or smaller; insert after
96
+ pos = arr.size
97
+ arr.reverse_each do |arr_cb|
98
+ break if arr_cb.priority <= cb.priority
99
+ pos -= 1
100
+ end
101
+
102
+ arr.insert(pos, cb)
103
+ end
91
104
  end
92
105
  end
@@ -36,6 +36,8 @@ module Sqreen
36
36
  #
37
37
  # CB can also declare that they are whitelisted and should not be run at the moment.
38
38
 
39
+ DEFAULT_PRIORITY = 100
40
+
39
41
  attr_reader :klass, :method
40
42
  attr_reader :overtimeable
41
43
 
@@ -55,6 +57,11 @@ module Sqreen
55
57
  false
56
58
  end
57
59
 
60
+ # the lower, the closer to the beginning of the list
61
+ def priority
62
+ DEFAULT_PRIORITY
63
+ end
64
+
58
65
  def pre?
59
66
  @has_pre
60
67
  end
@@ -120,23 +127,6 @@ module Sqreen
120
127
  framework && !framework.whitelisted_match.nil?
121
128
  end
122
129
 
123
- # Record an attack event into Sqreen system
124
- # @param infos [Hash] Additional information about request
125
- def record_event(infos, at = Time.now.utc)
126
- return unless framework
127
- payload = {
128
- :infos => infos,
129
- :rulespack_id => rulespack_id,
130
- :rule_name => rule_name,
131
- :test => test,
132
- :time => at,
133
- }
134
- if payload_tpl.include?('context')
135
- payload[:backtrace] = Sqreen::Context.new.bt
136
- end
137
- framework.observe(:attacks, payload, payload_tpl)
138
- end
139
-
140
130
  # Record a metric observation
141
131
  # @param category [String] Name of the metric observed
142
132
  # @param key [String] aggregation key
@@ -146,22 +136,5 @@ module Sqreen
146
136
  return unless framework
147
137
  framework.observe(:observations, [category, key, observation, at], [], false)
148
138
  end
149
-
150
- # Record an exception that just occurred
151
- # @param exception [Exception] Exception to send over
152
- # @param infos [Hash] Additional contextual information
153
- def record_exception(exception, infos = {}, at = Time.now.utc)
154
- return unless framework
155
- payload = {
156
- :exception => exception,
157
- :infos => infos,
158
- :rulespack_id => rulespack_id,
159
- :rule_name => rule_name,
160
- :test => test,
161
- :time => at,
162
- :backtrace => exception.backtrace || Sqreen::Context.bt,
163
- }
164
- framework.observe(:sqreen_exceptions, payload)
165
- end
166
139
  end
167
140
  end
@@ -15,7 +15,11 @@ module Sqreen
15
15
  alias original_push push
16
16
 
17
17
  def push(value)
18
- pop until size < @capacity
18
+ until size < @capacity
19
+ discarded = pop
20
+ Sqreen.log.debug { "Discarded from queue: #{discarded}" }
21
+ end
22
+ Sqreen.log.debug { "Pushed to the queue: #{value}" }
19
23
  original_push(value)
20
24
  end
21
25
  end
@@ -53,6 +53,10 @@ module Sqreen
53
53
  :default => nil },
54
54
  { :env => :SQREEN_STRIP_SENSITIVE_DATA, :name => :strip_sensitive_data,
55
55
  :default => true, :convert => :to_bool },
56
+ { :env => :SQREEN_STRIP_SENSITIVE_KEYS, :name => :strip_sensitive_keys,
57
+ :default => nil },
58
+ { :env => :SQREEN_STRIP_SENSITIVE_REGEX, :name => :strip_sensitive_regex,
59
+ :default => nil },
56
60
 
57
61
  ].freeze
58
62
 
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'sqreen/deliveries/simple'
5
5
  require 'sqreen/events/remote_exception'
6
+ require 'sqreen/mono_time'
7
+
6
8
  module Sqreen
7
9
  module Deliveries
8
10
  # Simple delivery method that batch event already seen in a batch
@@ -41,22 +43,23 @@ module Sqreen
41
43
  def stale?
42
44
  min = @first_seen.values.min
43
45
  return false if min.nil?
44
- (min + max_staleness) < Time.now
46
+ (min + max_staleness) < Sqreen.time
45
47
  end
46
48
 
47
49
  def post_batch_needed?(event)
48
- now = Time.now
50
+ now = Sqreen.time
51
+ # do not use any? {} due to side effects inside block
49
52
  event_keys(event).map do |key|
50
53
  was = @first_seen[key]
51
54
  @first_seen[key] ||= now
52
- was.nil? || current_batch.size > max_batch || (was + max_staleness) < now
55
+ was.nil? || current_batch.size > max_batch || now > (was + max_staleness)
53
56
  end.any?
54
57
  end
55
58
 
56
59
  def post_batch
57
60
  session.post_batch(current_batch)
58
61
  current_batch.clear
59
- now = Time.now
62
+ now = Sqreen.time
60
63
  @first_seen.each_key do |key|
61
64
  @first_seen[key] = now
62
65
  end
@@ -12,5 +12,9 @@ module Sqreen
12
12
  def to_hash
13
13
  payload.to_hash
14
14
  end
15
+
16
+ def to_s
17
+ "<#{self.class.name}: #{to_hash}>"
18
+ end
15
19
  end
16
20
  end
@@ -7,6 +7,11 @@ require 'sqreen/event'
7
7
  module Sqreen
8
8
  # When a request is deeemed worthy of being sent to the backend
9
9
  class RequestRecord < Sqreen::Event
10
+ def initialize(payload, redactor = nil)
11
+ @redactor = redactor
12
+ super(payload)
13
+ end
14
+
10
15
  def observed
11
16
  (payload && payload[:observed]) || {}
12
17
  end
@@ -56,8 +61,8 @@ module Sqreen
56
61
  res[:request][:parameters] = payload['params'] if payload['params']
57
62
  res[:request][:headers] = payload['headers'] if payload['headers']
58
63
 
59
- if Sqreen.config_get(:strip_sensitive_data)
60
- res[:request] = SensitiveDataRedactor.redact(res[:request])
64
+ if @redactor
65
+ res[:request] = @redactor.redact(res[:request])
61
66
  end
62
67
 
63
68
  res
@@ -105,14 +110,42 @@ module Sqreen
105
110
 
106
111
  # For redacting sensitive data and avoid having it sent to our servers
107
112
  class SensitiveDataRedactor
108
- SENSITIVE_KEYS = Set.new(%w[password secret passwd authorization api_key apikey access_token]).freeze
113
+ DEFAULT_SENSITIVE_KEYS = Set.new(%w[password secret passwd authorization api_key apikey access_token]).freeze
114
+ DEFAULT_REGEX = /\A(?:\d[ -]*?){13,16}\z/
109
115
  MASK = '<Redacted by Sqreen>'.freeze
110
- REGEX = /\A(?:\d[ -]*?){13,16}\z/
111
116
 
112
- def self.redact(obj)
117
+ def self.from_config
118
+ keys = Sqreen.config_get(:strip_sensitive_keys)
119
+ if keys && keys.is_a?(String)
120
+ keys = keys.split(',')
121
+ else
122
+ keys = nil
123
+ end
124
+
125
+ regex = Sqreen.config_get(:strip_sensitive_regex)
126
+ if regex && regex.is_a?(String)
127
+ begin
128
+ regex = Regexp.compile(regex)
129
+ rescue RegexpError
130
+ Sqreen.log.warn("Invalid regular expression given in strip_sensitive_keys: #{regex}")
131
+ regex = nil
132
+ end
133
+ else
134
+ regex = nil
135
+ end
136
+
137
+ new(keys: keys, regex: regex)
138
+ end
139
+
140
+ def initialize(params = {})
141
+ @regex = params[:regex] || DEFAULT_REGEX
142
+ @keys = params[:keys] || DEFAULT_SENSITIVE_KEYS
143
+ end
144
+
145
+ def redact(obj)
113
146
  case obj
114
147
  when String
115
- return MASK if obj =~ REGEX
148
+ return MASK if obj =~ @regex
116
149
 
117
150
  when Array
118
151
  return obj.map(&method(:redact))
@@ -121,7 +154,7 @@ module Sqreen
121
154
  return Hash[
122
155
  obj.map do |k, v|
123
156
  ck = k.is_a?(String) ? k.downcase : k
124
- [k, SENSITIVE_KEYS.include?(ck) ? MASK : redact(v)]
157
+ [k, @keys.include?(ck) ? MASK : redact(v)]
125
158
  end
126
159
  ]
127
160
  end