sqreen 1.15.0-java → 1.15.5-java

Sign up to get free protection for your applications and to get access to all the features.
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