sqreen 1.12.0 → 1.13.0
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.
- checksums.yaml +4 -4
- data/lib/sqreen/actions.rb +68 -10
- data/lib/sqreen/configuration.rb +2 -0
- data/lib/sqreen/events/request_record.rb +40 -3
- data/lib/sqreen/frameworks/generic.rb +18 -4
- data/lib/sqreen/instrumentation.rb +22 -2
- data/lib/sqreen/metrics.rb +1 -0
- data/lib/sqreen/metrics/binning.rb +74 -0
- data/lib/sqreen/metrics_store.rb +6 -2
- data/lib/sqreen/performance_notifications/binned_metrics.rb +119 -0
- data/lib/sqreen/rules_callbacks/run_block_user_actions.rb +34 -0
- data/lib/sqreen/runner.rb +23 -0
- data/lib/sqreen/sdk.rb +2 -2
- data/lib/sqreen/shared_storage.rb +5 -0
- data/lib/sqreen/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fb4ef79ad2d573f02250c303800c9d4c8b64a3f1e0318fbf2f7e198508826b7b
|
4
|
+
data.tar.gz: 991f85b77cb27f9387fb027e74538aded1a7f0e4dd0c1a78a8a89041154d790a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a575ae38cbccc9381e8dea3fedc7c7ac24ce0a7174b0ba799f264cfbd8bf0a346c4c60fc6518172f6bff09d692193d469cb31fe4513781f67468d52c7db14419
|
7
|
+
data.tar.gz: 8dffc28661b0e8fd608cae0917326519abf7c6d8f4b36d96ffead33e6df3059f0de05187a03d425d21068e36a04f8b6f0b42c9375bce9a9a7515c1fe6f847ff4
|
data/lib/sqreen/actions.rb
CHANGED
@@ -6,6 +6,7 @@ require 'sqreen/log'
|
|
6
6
|
require 'sqreen/exception'
|
7
7
|
require 'sqreen/sdk'
|
8
8
|
require 'sqreen/frameworks'
|
9
|
+
require 'singleton'
|
9
10
|
|
10
11
|
module Sqreen
|
11
12
|
# Implements actions (behavior taken in response to agent signals)
|
@@ -58,25 +59,39 @@ module Sqreen
|
|
58
59
|
return nil
|
59
60
|
end
|
60
61
|
|
61
|
-
|
62
|
+
opts = {
|
63
|
+
:duration => duration,
|
64
|
+
:send_response => hash['send_response'],
|
65
|
+
}
|
66
|
+
|
67
|
+
subclass.new(id, opts, hash['parameters'] || {})
|
62
68
|
end
|
63
69
|
|
70
|
+
# Base class for actions
|
64
71
|
class Base
|
65
|
-
attr_reader :id, :expiry
|
72
|
+
attr_reader :id, :expiry, :send_response
|
66
73
|
|
67
|
-
def initialize(id,
|
74
|
+
def initialize(id, opts)
|
68
75
|
@id = id
|
76
|
+
duration = opts[:duration]
|
69
77
|
@expiry = Time.new + duration unless duration.nil?
|
78
|
+
@send_response = if opts[:send_response].nil?
|
79
|
+
true
|
80
|
+
else
|
81
|
+
!!opts[:send_response]
|
82
|
+
end
|
70
83
|
end
|
71
84
|
|
72
85
|
# See Sqreen::CB for return values
|
73
86
|
def run(*args)
|
74
87
|
return if expiry && Time.new > expiry
|
75
88
|
ret = do_run *args
|
76
|
-
unless ret.nil?
|
89
|
+
unless ret.nil? || !@send_response
|
77
90
|
Sqreen.internal_track(event_name,
|
78
|
-
'properties' =>
|
79
|
-
|
91
|
+
'properties' => {
|
92
|
+
'output' => event_properties(*args),
|
93
|
+
'action_id' => id,
|
94
|
+
})
|
80
95
|
end
|
81
96
|
ret
|
82
97
|
end
|
@@ -155,8 +170,8 @@ module Sqreen
|
|
155
170
|
include IpRanges
|
156
171
|
self.type_name = 'block_ip'
|
157
172
|
|
158
|
-
def initialize(id,
|
159
|
-
super(id,
|
173
|
+
def initialize(id, opts, params = {})
|
174
|
+
super(id, opts)
|
160
175
|
parse_ip_ranges params
|
161
176
|
end
|
162
177
|
|
@@ -179,8 +194,8 @@ module Sqreen
|
|
179
194
|
|
180
195
|
attr_reader :redirect_url
|
181
196
|
|
182
|
-
def initialize(id,
|
183
|
-
super(id,
|
197
|
+
def initialize(id, opts, params = {})
|
198
|
+
super(id, opts)
|
184
199
|
@redirect_url = params['url']
|
185
200
|
raise "no url provided for action #{id}" unless @redirect_url
|
186
201
|
parse_ip_ranges params
|
@@ -199,5 +214,48 @@ module Sqreen
|
|
199
214
|
{ 'ip_address' => client_ip, 'url' => @redirect_url }
|
200
215
|
end
|
201
216
|
end
|
217
|
+
|
218
|
+
# Blocks a user at the point Sqreen::identify()
|
219
|
+
# or Sqreen::auth_track() are called
|
220
|
+
class BlockUser < Base
|
221
|
+
self.type_name = 'block_user'
|
222
|
+
|
223
|
+
def initialize(id, opts, params = {})
|
224
|
+
super(id, opts)
|
225
|
+
@users = params['users']
|
226
|
+
raise ::Sqreen::Exception, 'nil "users" param for block_user action' if @users.nil?
|
227
|
+
raise ::Sqreen::Exception, '"users" param must be an array' unless @users.is_a? Array
|
228
|
+
end
|
229
|
+
|
230
|
+
def do_run(identity_params)
|
231
|
+
return unless @users.include? stringify_keys(identity_params)
|
232
|
+
Sqreen.log.info(
|
233
|
+
"Will raise due to user being blocked by action #{id}. " \
|
234
|
+
"Blocked user identity: #{identity_params}"
|
235
|
+
)
|
236
|
+
|
237
|
+
e = Sqreen::AttackBlocked.new(
|
238
|
+
"Blocked user with identity #{identity_params} " \
|
239
|
+
'due to automatic security response. No action is required'
|
240
|
+
)
|
241
|
+
|
242
|
+
{
|
243
|
+
:status => :raise,
|
244
|
+
:exception => e,
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
def event_properties(identity_params)
|
249
|
+
{ 'user' => identity_params }
|
250
|
+
end
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
def stringify_keys(hash)
|
255
|
+
Hash[
|
256
|
+
hash.map { |k, v| [k.to_s, v] }
|
257
|
+
]
|
258
|
+
end
|
259
|
+
end
|
202
260
|
end
|
203
261
|
end
|
data/lib/sqreen/configuration.rb
CHANGED
@@ -42,9 +42,7 @@ module Sqreen
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
if observed[:sdk]
|
45
|
-
res[:observed][:sdk] =
|
46
|
-
{ :name => meth, :time => time, :args => args }
|
47
|
-
end
|
45
|
+
res[:observed][:sdk] = processed_sdk_calls
|
48
46
|
end
|
49
47
|
end
|
50
48
|
res[:local] = payload['local'] if payload['local']
|
@@ -58,5 +56,44 @@ module Sqreen
|
|
58
56
|
res[:request][:headers] = payload['headers'] if payload['headers']
|
59
57
|
res
|
60
58
|
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def processed_sdk_calls
|
63
|
+
auth_keys = last_identify_id
|
64
|
+
|
65
|
+
observed[:sdk].map do |meth, time, *args|
|
66
|
+
{
|
67
|
+
:name => meth,
|
68
|
+
:time => time,
|
69
|
+
:args => inject_identifiers(args, meth, auth_keys),
|
70
|
+
}
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def inject_identifiers(args, meth, auth_keys)
|
75
|
+
return args unless meth == :track && auth_keys
|
76
|
+
|
77
|
+
track_opts = args[1] || {}
|
78
|
+
if track_opts[:user_identifiers].nil?
|
79
|
+
args[1] = track_opts.dup
|
80
|
+
args[1][:user_identifiers] = auth_keys
|
81
|
+
elsif track_opts[:user_identifiers] != auth_keys
|
82
|
+
Sqreen.log.warn 'Sqreen.identify and Sqreen.track have been called ' \
|
83
|
+
'with different user_identifiers values'
|
84
|
+
end
|
85
|
+
|
86
|
+
args
|
87
|
+
end
|
88
|
+
|
89
|
+
def last_identify_id
|
90
|
+
return nil unless observed[:sdk]
|
91
|
+
|
92
|
+
observed[:sdk].reverse_each do |meth, _time, *args|
|
93
|
+
next unless meth == :identify
|
94
|
+
return args.first if args.respond_to? :first
|
95
|
+
end
|
96
|
+
nil
|
97
|
+
end
|
61
98
|
end
|
62
99
|
end
|
@@ -44,16 +44,30 @@ module Sqreen
|
|
44
44
|
ENV['RACK_ENV'] == 'development'
|
45
45
|
end
|
46
46
|
|
47
|
-
|
47
|
+
PREFERRED_IP_HEADERS = %w[HTTP_X_FORWARDED_FOR HTTP_X_REAL_IP
|
48
48
|
HTTP_CLIENT_IP HTTP_X_FORWARDED
|
49
49
|
HTTP_X_CLUSTER_CLIENT_IP HTTP_FORWARDED_FOR
|
50
|
-
HTTP_FORWARDED HTTP_VIA
|
50
|
+
HTTP_FORWARDED HTTP_VIA].freeze
|
51
|
+
|
52
|
+
def preferred_ip_headers
|
53
|
+
@preferred_ip_headers ||=
|
54
|
+
begin
|
55
|
+
header_name = Sqreen.config_get(:ip_header)
|
56
|
+
if header_name
|
57
|
+
env_var = 'HTTP_' + header_name.tr('-', '_').upcase
|
58
|
+
[env_var] + (PREFERRED_IP_HEADERS - [env_var])
|
59
|
+
else
|
60
|
+
PREFERRED_IP_HEADERS
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
private :preferred_ip_headers
|
51
65
|
|
52
66
|
def ip_headers
|
53
67
|
req = request
|
54
68
|
return [] unless req
|
55
69
|
ips = []
|
56
|
-
(
|
70
|
+
(preferred_ip_headers + ['REMOTE_ADDR']).each do |header|
|
57
71
|
v = req.env[header]
|
58
72
|
ips << [header, v] unless v.nil?
|
59
73
|
end
|
@@ -79,7 +93,7 @@ module Sqreen
|
|
79
93
|
return nil unless req
|
80
94
|
# Look for an external address being forwarded
|
81
95
|
split_ips = []
|
82
|
-
|
96
|
+
preferred_ip_headers.each do |header_name|
|
83
97
|
forwarded = req.env[header_name]
|
84
98
|
ips = split_ip_addresses(forwarded)
|
85
99
|
lip = ips.find { |ip| (ip !~ TRUSTED_PROXIES) && valid_ip?(ip) }
|
@@ -11,6 +11,7 @@ require 'sqreen/rules_signature'
|
|
11
11
|
require 'sqreen/shared_storage'
|
12
12
|
require 'sqreen/rules_callbacks/record_request_context'
|
13
13
|
require 'sqreen/rules_callbacks/run_req_start_actions'
|
14
|
+
require 'sqreen/rules_callbacks/run_block_user_actions'
|
14
15
|
require 'set'
|
15
16
|
|
16
17
|
# How to override a class method:
|
@@ -245,6 +246,10 @@ module Sqreen
|
|
245
246
|
def self.define_callback_method(meth, original_meth, klass_name)
|
246
247
|
@sqreen_multi_instr ||= nil
|
247
248
|
proc do |*args, &block|
|
249
|
+
record_req_hp = @@record_request_hookpoints.include?([klass_name, meth]) &&
|
250
|
+
Sqreen::PerformanceNotifications.listen_for?
|
251
|
+
Sqreen::PerformanceNotifications::BinnedMetrics.start_request if record_req_hp
|
252
|
+
|
248
253
|
budget = nil
|
249
254
|
skip_call = Thread.current[:sqreen_in_use]
|
250
255
|
begin
|
@@ -370,6 +375,7 @@ module Sqreen
|
|
370
375
|
next unless ret.is_a? Hash
|
371
376
|
case ret[:status]
|
372
377
|
when :raise, 'raise'
|
378
|
+
raise ret[:exception] if ret.key?(:exception)
|
373
379
|
raise Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
|
374
380
|
when :override, 'override'
|
375
381
|
result = ret[:new_return_value]
|
@@ -379,10 +385,11 @@ module Sqreen
|
|
379
385
|
end
|
380
386
|
result
|
381
387
|
ensure
|
382
|
-
if
|
388
|
+
if record_req_hp
|
383
389
|
Sqreen::PerformanceNotifications.instrument('Callbacks/hooks_reporting/pre') do
|
384
390
|
Sqreen::PerformanceNotifications::LogPerformance.next_request
|
385
391
|
Sqreen::PerformanceNotifications::NewRelic.next_request
|
392
|
+
Sqreen::PerformanceNotifications::BinnedMetrics.finish_request
|
386
393
|
end
|
387
394
|
end
|
388
395
|
Thread.current[:sqreen_in_use] = false
|
@@ -439,6 +446,8 @@ module Sqreen
|
|
439
446
|
end
|
440
447
|
alias_method meth, saved_meth_name
|
441
448
|
send(method_kind, meth)
|
449
|
+
|
450
|
+
remove_method saved_meth_name
|
442
451
|
end
|
443
452
|
end
|
444
453
|
|
@@ -450,6 +459,15 @@ module Sqreen
|
|
450
459
|
saved_meth_name = get_saved_method_name(meth)
|
451
460
|
new_method = "#{meth}_modified".to_sym
|
452
461
|
|
462
|
+
# do not include ancestors o/wise we might get a saved method from a
|
463
|
+
# superclass which is never called because the original name
|
464
|
+
# (instrumented) method of the superclass has been overridden
|
465
|
+
private_meths = klass_name.private_instance_methods(false)
|
466
|
+
if private_meths.include?(saved_meth_name)
|
467
|
+
Sqreen.log.debug { "#{saved_meth_name} found #{klass_name}##{meth} already instrumented" }
|
468
|
+
return saved_meth_name
|
469
|
+
end
|
470
|
+
|
453
471
|
p = Instrumentation.define_callback_method(meth, saved_meth_name,
|
454
472
|
klass_name)
|
455
473
|
method_kind = nil
|
@@ -664,7 +682,9 @@ module Sqreen
|
|
664
682
|
# @return [Array<Sqreen::CB>]
|
665
683
|
def hardcoded_callbacks(framework)
|
666
684
|
[
|
667
|
-
Sqreen::Rules::RunReqStartActions.new(framework)
|
685
|
+
Sqreen::Rules::RunReqStartActions.new(framework),
|
686
|
+
Sqreen::Rules::RunBlockUserActions.new(Sqreen, :identify, 0),
|
687
|
+
Sqreen::Rules::RunBlockUserActions.new(Sqreen, :auth_track, 1),
|
668
688
|
]
|
669
689
|
end
|
670
690
|
|
data/lib/sqreen/metrics.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Copyright (c) 2018 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/metrics/base'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Metric
|
8
|
+
# Takes numbers as samples and bins them (effectively, rounds them).
|
9
|
+
class Binning < Base
|
10
|
+
# upper bound of i-th bin is factor * base^(i-1)
|
11
|
+
def initialize(base, factor)
|
12
|
+
super()
|
13
|
+
@base = base
|
14
|
+
@factor = factor
|
15
|
+
log_base = Math.log(base)
|
16
|
+
log_factor = Math.log(factor)
|
17
|
+
@inv_log_base = 1 / log_base
|
18
|
+
@add_parcel = - log_factor / log_base
|
19
|
+
new_sample(Time.now.utc)
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(_at, _key, x)
|
23
|
+
h = @sample[OBSERVATION_KEY]
|
24
|
+
bin = bin_no(x)
|
25
|
+
h[bin] += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_sample(time)
|
29
|
+
return nil if @sample[OBSERVATION_KEY].empty?
|
30
|
+
super(time)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
# these two are called by next_sample
|
36
|
+
|
37
|
+
def finalize_sample(time)
|
38
|
+
@sample[FINISH_KEY] = time
|
39
|
+
h = @sample[OBSERVATION_KEY]
|
40
|
+
@sample[OBSERVATION_KEY] = { 'u' => @factor, 'b' => @base, 'v' => h }
|
41
|
+
end
|
42
|
+
|
43
|
+
def new_sample(time)
|
44
|
+
@sample = {
|
45
|
+
OBSERVATION_KEY => Hash.new { |hash, key| hash[key] = 0 },
|
46
|
+
START_KEY => time,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def bin_no(x)
|
53
|
+
res = 2 + (@inv_log_base * Math.log(x) + @add_parcel).floor
|
54
|
+
res < 1 ? 1 : res
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# overrides the constructor
|
59
|
+
class ParameterizedBinning < Binning
|
60
|
+
def initialize
|
61
|
+
raise 'Not configured' unless self.class.base && self.class.factor
|
62
|
+
super(self.class.base, self.class.factor)
|
63
|
+
end
|
64
|
+
|
65
|
+
class << self
|
66
|
+
attr_reader :base, :factor
|
67
|
+
def parameters(opts)
|
68
|
+
@base = opts[:base]
|
69
|
+
@factor = opts[:factor]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/sqreen/metrics_store.rb
CHANGED
@@ -33,7 +33,7 @@ module Sqreen
|
|
33
33
|
|
34
34
|
# Definition contains a name,period and aggregate at least
|
35
35
|
# @param definition [Hash] a metric definition
|
36
|
-
# @param
|
36
|
+
# @param mklass [Object] Override metric object (used in testing)
|
37
37
|
def create_metric(definition, mklass = nil)
|
38
38
|
name = definition[NAME_KEY]
|
39
39
|
kind = definition[KIND_KEY]
|
@@ -47,6 +47,10 @@ module Sqreen
|
|
47
47
|
metric
|
48
48
|
end
|
49
49
|
|
50
|
+
def metric?(name)
|
51
|
+
@metrics.key?(name)
|
52
|
+
end
|
53
|
+
|
50
54
|
def update(name, at, key, value)
|
51
55
|
at = at.utc
|
52
56
|
metric, period, start = @metrics[name]
|
@@ -71,7 +75,7 @@ module Sqreen
|
|
71
75
|
def next_sample(name, at)
|
72
76
|
metric = @metrics[name][0]
|
73
77
|
r = metric.next_sample(at)
|
74
|
-
@metrics[name][2] = at
|
78
|
+
@metrics[name][2] = at # start
|
75
79
|
if r
|
76
80
|
r[NAME_KEY] = name
|
77
81
|
obs = r[Metric::OBSERVATION_KEY]
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Copyright (c) 2018 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/performance_notifications'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
#
|
8
|
+
module PerformanceNotifications
|
9
|
+
# Logs callback performance
|
10
|
+
class BinnedMetrics
|
11
|
+
EVENT_REQ = 'req'.freeze # request total time
|
12
|
+
EVENT_TOTAL_TIME = 'sq'.freeze # sqreen total overhead callback time
|
13
|
+
|
14
|
+
EVT_NAME_REGEXP = %r{\ACallbacks/([^/]+)/([^/]+)\z}
|
15
|
+
|
16
|
+
# @param metrics_store [Sqreen::MetricsStore]
|
17
|
+
def initialize(metrics_store, period)
|
18
|
+
@metrics_store = metrics_store
|
19
|
+
@period = period
|
20
|
+
@subid = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def enable
|
24
|
+
return unless @subid.nil?
|
25
|
+
|
26
|
+
metrics_store.create_metric(
|
27
|
+
'name' => EVENT_REQ, 'period' => period, 'kind' => 'ParameterizedBinning'
|
28
|
+
)
|
29
|
+
metrics_store.create_metric(
|
30
|
+
'name' => EVENT_TOTAL_TIME, 'period' => period, 'kind' => 'ParameterizedBinning'
|
31
|
+
)
|
32
|
+
|
33
|
+
@subid = Sqreen::PerformanceNotifications.subscribe(&method(:log))
|
34
|
+
end
|
35
|
+
|
36
|
+
def disable
|
37
|
+
return if @subid.nil?
|
38
|
+
Sqreen::PerformanceNotifications.unsubscribe(@subid)
|
39
|
+
@subid = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def log(event, start, finish, _meta)
|
43
|
+
return unless event =~ EVT_NAME_REGEXP
|
44
|
+
rule, cb = Regexp.last_match.captures
|
45
|
+
|
46
|
+
metric_name = "sq.#{rule}.#{cb}"
|
47
|
+
ensure_metric(metric_name)
|
48
|
+
|
49
|
+
finish_time = SQREEN_MONO_TIME ? Time.now.utc : finish
|
50
|
+
time_millis = (finish - start) * 1000
|
51
|
+
SharedStorage[:sqreen_request_time] += time_millis
|
52
|
+
metrics_store.update(metric_name, finish_time, nil, time_millis)
|
53
|
+
end
|
54
|
+
|
55
|
+
def start_request
|
56
|
+
SharedStorage[:request_start_time] = PerformanceNotifications.time
|
57
|
+
SharedStorage[:sqreen_request_time] = 0.0
|
58
|
+
end
|
59
|
+
|
60
|
+
def finish_request
|
61
|
+
start_time = SharedStorage[:request_start_time]
|
62
|
+
finish_time = PerformanceNotifications.time
|
63
|
+
duration_millis = (finish_time - start_time) * 1000
|
64
|
+
|
65
|
+
finish_time_obj = SQREEN_MONO_TIME ? Time.now.utc : finish_time
|
66
|
+
# format of evt is [cat, key, value, timestamp]
|
67
|
+
Sqreen.observations_queue.push(
|
68
|
+
[EVENT_REQ, nil, duration_millis, finish_time_obj]
|
69
|
+
)
|
70
|
+
Sqreen.observations_queue.push(
|
71
|
+
[EVENT_TOTAL_TIME, nil,
|
72
|
+
SharedStorage[:sqreen_request_time], finish_time_obj]
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# @return [Sqreen::MetricsStore]
|
79
|
+
attr_reader :metrics_store
|
80
|
+
attr_reader :period
|
81
|
+
|
82
|
+
def ensure_metric(metric_name)
|
83
|
+
return if metrics_store.metric?(metric_name)
|
84
|
+
metrics_store.create_metric(
|
85
|
+
'name' => metric_name, 'period' => period, 'kind' => 'ParameterizedBinning'
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
@instance = nil
|
90
|
+
class << self
|
91
|
+
# @return [Sqreen::PerformanceNotifications::BinnedMetrics]
|
92
|
+
attr_reader :instance
|
93
|
+
def enable(metrics_store, period = 60, base = 2.0, factor = 0.1)
|
94
|
+
disable
|
95
|
+
Sqreen::Metric::ParameterizedBinning.parameters(
|
96
|
+
:base => base, :factor => factor
|
97
|
+
)
|
98
|
+
@instance = new(metrics_store, period).tap(&:enable)
|
99
|
+
end
|
100
|
+
|
101
|
+
def disable
|
102
|
+
return unless instance
|
103
|
+
instance.disable
|
104
|
+
@instance = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def start_request
|
108
|
+
return unless instance
|
109
|
+
instance.start_request
|
110
|
+
end
|
111
|
+
|
112
|
+
def finish_request
|
113
|
+
return unless instance
|
114
|
+
instance.finish_request
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Copyright (c) 2018 Sqreen. All Rights Reserved.
|
2
|
+
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
|
+
|
4
|
+
require 'sqreen/rule_callback'
|
5
|
+
require 'sqreen/actions'
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Rules
|
9
|
+
# Runs the block_user actions (for hooking Sqreen.{identify,auth_user})
|
10
|
+
class RunBlockUserActions < CB
|
11
|
+
def initialize(klass, method, auth_keys_idx)
|
12
|
+
super(klass, method)
|
13
|
+
@auth_keys_idx = auth_keys_idx
|
14
|
+
end
|
15
|
+
|
16
|
+
def post(_retval, _inst, args, _budget = nil)
|
17
|
+
actions = actions_repo[Sqreen::Actions::BlockUser]
|
18
|
+
|
19
|
+
actions.each do |action|
|
20
|
+
res = action.run args[@auth_keys_idx]
|
21
|
+
return res unless res.nil?
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# @return [Sqreen::Actions::Repository]
|
29
|
+
def actions_repo
|
30
|
+
Sqreen::Actions::Repository.instance
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/sqreen/runner.rb
CHANGED
@@ -17,6 +17,7 @@ require 'sqreen/metrics_store'
|
|
17
17
|
require 'sqreen/deliveries/simple'
|
18
18
|
require 'sqreen/deliveries/batch'
|
19
19
|
require 'sqreen/performance_notifications/metrics'
|
20
|
+
require 'sqreen/performance_notifications/binned_metrics'
|
20
21
|
require 'sqreen/instrumentation'
|
21
22
|
require 'sqreen/call_countable'
|
22
23
|
|
@@ -30,6 +31,11 @@ module Sqreen
|
|
30
31
|
|
31
32
|
METRICS_EVENT = 'metrics'.freeze
|
32
33
|
|
34
|
+
PERF_METRICS_PERIOD = 60 # 1 min
|
35
|
+
DEFAULT_PERF_LEVEL = 0 # disabled
|
36
|
+
DEFAULT_PERF_UNIT = 0.1 # ms
|
37
|
+
DEFAULT_PERF_BASE = 2.0
|
38
|
+
|
33
39
|
class << self
|
34
40
|
attr_reader :features
|
35
41
|
def update_features(features)
|
@@ -204,6 +210,20 @@ module Sqreen
|
|
204
210
|
end
|
205
211
|
end
|
206
212
|
|
213
|
+
def config_binned_metrics(level, base, factor)
|
214
|
+
level = level.to_i
|
215
|
+
if level <= 0
|
216
|
+
Sqreen.log.debug('Disabling binned metrics')
|
217
|
+
PerformanceNotifications::BinnedMetrics.disable
|
218
|
+
else
|
219
|
+
Sqreen.log.warn("Unknown value for perf_level: #{level}. Treating as 1") unless level == 1
|
220
|
+
PerformanceNotifications::BinnedMetrics.enable(
|
221
|
+
metrics_engine, PERF_METRICS_PERIOD, base.to_f, factor.to_f
|
222
|
+
)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
|
207
227
|
def setup_instrumentation(context_infos = {})
|
208
228
|
Sqreen.log.info 'setup instrumentation'
|
209
229
|
rulespack_id, rules = load_rules(context_infos)
|
@@ -282,6 +302,9 @@ module Sqreen
|
|
282
302
|
Sqreen.update_features(features)
|
283
303
|
session.request_compression = features['request_compression'] if session
|
284
304
|
self.performance_metrics_period = features['performance_metrics_period']
|
305
|
+
config_binned_metrics(features['perf_level'] || DEFAULT_PERF_LEVEL,
|
306
|
+
features['perf_base'] || DEFAULT_PERF_BASE,
|
307
|
+
features['perf_unit'] || DEFAULT_PERF_UNIT)
|
285
308
|
self.call_counts_metrics_period = features['call_counts_metrics_period']
|
286
309
|
hd = features['heartbeat_delay'].to_i
|
287
310
|
self.heartbeat_delay = hd if hd > 0
|
data/lib/sqreen/sdk.rb
CHANGED
@@ -36,13 +36,13 @@ module Sqreen
|
|
36
36
|
# For internal usage. Users are to call track() instead.
|
37
37
|
def internal_track(event_name, options = {})
|
38
38
|
properties = options[:properties]
|
39
|
-
|
39
|
+
user_identifiers = options[:user_identifiers]
|
40
40
|
timestamp = options[:timestamp] || Time.now.utc
|
41
41
|
# Not in SDK v0
|
42
42
|
# request = options[:request]
|
43
43
|
|
44
44
|
args = {}
|
45
|
-
args[:
|
45
|
+
args[:user_identifiers] = user_identifiers if user_identifiers
|
46
46
|
args[:properties] = properties if properties
|
47
47
|
|
48
48
|
Sqreen.framework.observe(
|
data/lib/sqreen/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqreen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sqreen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-07-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: execjs
|
@@ -81,12 +81,14 @@ files:
|
|
81
81
|
- lib/sqreen/metrics.rb
|
82
82
|
- lib/sqreen/metrics/average.rb
|
83
83
|
- lib/sqreen/metrics/base.rb
|
84
|
+
- lib/sqreen/metrics/binning.rb
|
84
85
|
- lib/sqreen/metrics/collect.rb
|
85
86
|
- lib/sqreen/metrics/sum.rb
|
86
87
|
- lib/sqreen/metrics_store.rb
|
87
88
|
- lib/sqreen/middleware.rb
|
88
89
|
- lib/sqreen/payload_creator.rb
|
89
90
|
- lib/sqreen/performance_notifications.rb
|
91
|
+
- lib/sqreen/performance_notifications/binned_metrics.rb
|
90
92
|
- lib/sqreen/performance_notifications/log.rb
|
91
93
|
- lib/sqreen/performance_notifications/log_performance.rb
|
92
94
|
- lib/sqreen/performance_notifications/metrics.rb
|
@@ -111,6 +113,7 @@ files:
|
|
111
113
|
- lib/sqreen/rules_callbacks/record_request_context.rb
|
112
114
|
- lib/sqreen/rules_callbacks/reflected_xss.rb
|
113
115
|
- lib/sqreen/rules_callbacks/regexp_rule.rb
|
116
|
+
- lib/sqreen/rules_callbacks/run_block_user_actions.rb
|
114
117
|
- lib/sqreen/rules_callbacks/run_req_start_actions.rb
|
115
118
|
- lib/sqreen/rules_callbacks/shell_env.rb
|
116
119
|
- lib/sqreen/rules_callbacks/url_matches.rb
|