sqreen 1.20.4 → 1.21.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +0 -25
- data/lib/sqreen/actions/block_user.rb +1 -1
- data/lib/sqreen/actions/redirect_ip.rb +1 -1
- data/lib/sqreen/actions/redirect_user.rb +1 -1
- data/lib/sqreen/condition_evaluator.rb +2 -8
- data/lib/sqreen/configuration.rb +1 -1
- data/lib/sqreen/deferred_logger.rb +14 -50
- data/lib/sqreen/deliveries/batch.rb +8 -1
- data/lib/sqreen/ecosystem.rb +80 -0
- data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
- data/lib/sqreen/ecosystem/http/net_http.rb +51 -0
- data/lib/sqreen/ecosystem/http/rack_request.rb +38 -0
- data/lib/sqreen/ecosystem/loggable.rb +13 -0
- data/lib/sqreen/ecosystem/module_api.rb +30 -0
- data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
- data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
- data/lib/sqreen/ecosystem/module_api/signal_producer.rb +26 -0
- data/lib/sqreen/ecosystem/module_api/tracing_push_down.rb +34 -0
- data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
- data/lib/sqreen/ecosystem/module_registry.rb +39 -0
- data/lib/sqreen/ecosystem/redis/redis_connection.rb +35 -0
- data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
- data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
- data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
- data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
- data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
- data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
- data/lib/sqreen/ecosystem_integration.rb +70 -0
- data/lib/sqreen/ecosystem_integration/around_callbacks.rb +89 -0
- data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +38 -0
- data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +56 -0
- data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
- data/lib/sqreen/encoding_sanitizer.rb +27 -0
- data/lib/sqreen/events/request_record.rb +1 -0
- data/lib/sqreen/frameworks/generic.rb +15 -10
- data/lib/sqreen/frameworks/rails.rb +7 -0
- data/lib/sqreen/frameworks/request_recorder.rb +0 -2
- data/lib/sqreen/graft/call.rb +23 -72
- data/lib/sqreen/graft/callback.rb +1 -1
- data/lib/sqreen/graft/hook.rb +85 -187
- data/lib/sqreen/graft/hook_point.rb +1 -1
- data/lib/sqreen/legacy/instrumentation.rb +10 -22
- data/lib/sqreen/legacy/old_event_submission_strategy.rb +8 -3
- data/lib/sqreen/log.rb +2 -3
- data/lib/sqreen/log/loggable.rb +0 -1
- data/lib/sqreen/logger.rb +0 -24
- data/lib/sqreen/metrics_store.rb +0 -11
- data/lib/sqreen/null_logger.rb +0 -22
- data/lib/sqreen/remote_command.rb +3 -1
- data/lib/sqreen/rules.rb +4 -8
- data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
- data/lib/sqreen/rules/custom_error_cb.rb +3 -3
- data/lib/sqreen/rules/rule_cb.rb +0 -2
- data/lib/sqreen/rules/waf_cb.rb +3 -3
- data/lib/sqreen/runner.rb +21 -33
- data/lib/sqreen/session.rb +2 -0
- data/lib/sqreen/signals/conversions.rb +6 -1
- data/lib/sqreen/version.rb +1 -1
- data/lib/sqreen/weave/legacy/instrumentation.rb +103 -194
- data/lib/sqreen/worker.rb +2 -6
- metadata +35 -10
- data/lib/sqreen/deprecation.rb +0 -38
- data/lib/sqreen/weave/budget.rb +0 -46
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'sqreen/ecosystem/loggable'
|
2
|
+
|
3
|
+
module Sqreen
|
4
|
+
module Ecosystem
|
5
|
+
# The API that the transport/tracing modules are written against
|
6
|
+
module ModuleApi
|
7
|
+
TRACE_ID_HEADER = 'X-Sqreen-Trace-Identifier'.freeze
|
8
|
+
TRACE_ID_ENV_KEY = 'HTTP_X_SQREEN_TRACE_IDENTIFIER'.freeze
|
9
|
+
|
10
|
+
Loggable = Sqreen::Ecosystem::Loggable
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
attr_writer :module_name
|
14
|
+
|
15
|
+
def module_name
|
16
|
+
if instance_variable_defined?(:@module_name)
|
17
|
+
@module_name
|
18
|
+
else
|
19
|
+
# to snake case
|
20
|
+
@module_name = to_s.sub(/.*::/, '').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.included(mod)
|
26
|
+
mod.extend(ClassMethods)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'sqreen/ecosystem/dispatch_table'
|
2
|
+
|
3
|
+
module Sqreen
|
4
|
+
module Ecosystem
|
5
|
+
module ModuleApi
|
6
|
+
module EventListener
|
7
|
+
private
|
8
|
+
|
9
|
+
# XXX: callbacks need to be wrapped in order ot handle
|
10
|
+
# perfcap, exceptions, and maybe other concerns applying
|
11
|
+
# across the board
|
12
|
+
def on_request_start(&cb)
|
13
|
+
DispatchTable.add_request_start_listener.call(cb)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'sqreen/ecosystem/module_api'
|
2
|
+
|
3
|
+
module Sqreen
|
4
|
+
module Ecosystem
|
5
|
+
module ModuleApi
|
6
|
+
module Instrumentation
|
7
|
+
def self.included(mod)
|
8
|
+
mod.send :include, ModuleApi unless mod.ancestors.include?(ModuleApi)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# Just forwards the call to the instrumentation service
|
14
|
+
# @param [String] method
|
15
|
+
# @param [Hash{Symbol=>Proc}] advice keys are one of: :before, :after,
|
16
|
+
# :raised,
|
17
|
+
def instrument(method, advice)
|
18
|
+
DispatchTable.instrument.call(self.class.module_name, method, advice)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'sqreen/ecosystem/dispatch_table'
|
2
|
+
require 'sqreen/ecosystem/module_api/transaction_storage'
|
3
|
+
|
4
|
+
module Sqreen
|
5
|
+
module Ecosystem
|
6
|
+
module ModuleApi
|
7
|
+
module SignalProducer
|
8
|
+
include TransactionStorage
|
9
|
+
|
10
|
+
# for injection
|
11
|
+
attr_writer :tracing_id_producer
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def create_tracing_id
|
16
|
+
@tracing_id_producer.call
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Sqreen::Kit::Signals::Signal] signal
|
20
|
+
def submit_signal(signal)
|
21
|
+
DispatchTable.consume_signal.call(signal)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'sqreen/ecosystem/loggable'
|
2
|
+
require 'sqreen/ecosystem/tracing/sampling_configuration'
|
3
|
+
|
4
|
+
module Sqreen
|
5
|
+
module Ecosystem
|
6
|
+
module ModuleApi
|
7
|
+
module TracingPushDown
|
8
|
+
include Loggable
|
9
|
+
|
10
|
+
# method for ecosystem to inject the config
|
11
|
+
# @param [Sqreen::Ecosystem::SamplingConfiguration]
|
12
|
+
attr_writer :sampling_config
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def should_sample?(scope)
|
17
|
+
unless @sampling_config
|
18
|
+
logger.debug { "Scope #{scope} is disabled because tracing hasn't been enabled yet" }
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
result = @sampling_config.should_sample?(scope)
|
23
|
+
if result
|
24
|
+
logger.debug { "Will sample scope #{scope}. Sampling line: #{result}" }
|
25
|
+
else
|
26
|
+
logger.debug { "Will NOT sample scope #{scope}" }
|
27
|
+
end
|
28
|
+
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'sqreen/ecosystem/transaction_storage'
|
2
|
+
require 'sqreen/ecosystem/loggable'
|
3
|
+
|
4
|
+
module Sqreen
|
5
|
+
module Ecosystem
|
6
|
+
module ModuleApi
|
7
|
+
module TransactionStorage
|
8
|
+
class TxLocalVariables
|
9
|
+
class << self
|
10
|
+
class << self
|
11
|
+
include Sqreen::Ecosystem::Loggable
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def attr_reader(attr, _opts = {})
|
16
|
+
define_method attr do
|
17
|
+
tx_storage = Ecosystem::TransactionStorage.fetch_thread_local
|
18
|
+
return unless tx_storage
|
19
|
+
tx_storage[attr]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def attr_accessor(attr, opts = {})
|
24
|
+
# reader
|
25
|
+
attr_reader attr, opts
|
26
|
+
|
27
|
+
# writer (2 variants)
|
28
|
+
do_assign = proc do |value|
|
29
|
+
tx_storage = Ecosystem::TransactionStorage.fetch_thread_local
|
30
|
+
unless tx_storage
|
31
|
+
logger.debug do
|
32
|
+
"Assignment of tx local attribute #{attr} to #{value} has no effect"
|
33
|
+
end
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
tx_storage[attr] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
if opts.fetch(:allow_overwrite, false)
|
41
|
+
define "#{attr}=", &do_assign
|
42
|
+
else
|
43
|
+
define_method "#{attr}=" do |value|
|
44
|
+
cur = public_send(attr)
|
45
|
+
unless cur.nil?
|
46
|
+
raise "Cannot override value of #{attr} from #{cur} with #{value}"
|
47
|
+
end
|
48
|
+
|
49
|
+
do_assign.call(value)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end # TxLocalVariables.singleton_class.singleton_class
|
54
|
+
|
55
|
+
# usage:
|
56
|
+
# attr_reader :xxx
|
57
|
+
|
58
|
+
# in the future, we'll possibly need to expose the full
|
59
|
+
# TransactionStorage to the modules, at least if we don't
|
60
|
+
# opt for a more structured fashion of data exchange between
|
61
|
+
# the modules.
|
62
|
+
end # TxLocalVariables.singleton_class
|
63
|
+
end # TxLocalVariables
|
64
|
+
|
65
|
+
def tx_local_vars
|
66
|
+
TxLocalVariables
|
67
|
+
end
|
68
|
+
end # TransactionStorage module
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'sqreen/ecosystem/loggable'
|
2
|
+
|
3
|
+
module Sqreen
|
4
|
+
module Ecosystem
|
5
|
+
class ModuleRegistry
|
6
|
+
include Sqreen::Ecosystem::Loggable
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@mods = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def register(mod)
|
13
|
+
@mods << mod
|
14
|
+
end
|
15
|
+
|
16
|
+
def init_all
|
17
|
+
logger.info { "Initializing #{@mods.size} ecosystem modules" }
|
18
|
+
each_module do |mod|
|
19
|
+
logger.debug { "Initializing module with type #{mod.class}" }
|
20
|
+
mod.setup
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy_all
|
25
|
+
# not implemented
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [Class] type
|
29
|
+
def each_module(type = nil, &block)
|
30
|
+
selected_mods = type ? (@mods.select { |mod| mod.is_a?(type) }) : @mods
|
31
|
+
if block_given?
|
32
|
+
selected_mods.each(&block)
|
33
|
+
else
|
34
|
+
selected_mods.each
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'sqreen/ecosystem/tracing/signals/tracing_client'
|
2
|
+
require 'sqreen/ecosystem/module_api/signal_producer'
|
3
|
+
|
4
|
+
module Sqreen
|
5
|
+
module Ecosystem
|
6
|
+
module Redis
|
7
|
+
class RedisConnection
|
8
|
+
include ModuleApi::SignalProducer
|
9
|
+
include ModuleApi::TracingPushDown
|
10
|
+
include ModuleApi::Instrumentation
|
11
|
+
|
12
|
+
def setup
|
13
|
+
instrument 'Redis::Connection::TCPSocket#connect',
|
14
|
+
before: method(:before_advice)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def before_advice
|
20
|
+
return unless should_sample?('client')
|
21
|
+
|
22
|
+
host = call.args[0]
|
23
|
+
port = call.args[1]
|
24
|
+
|
25
|
+
signal = Sqreen::Kit::Signals::Specialized::TracingClient.new
|
26
|
+
signal.payload = Sqreen::Kit::Signals::Specialized::TracingClient::Payload.new(
|
27
|
+
transport: 'redis',
|
28
|
+
host: host, port: port
|
29
|
+
)
|
30
|
+
submit_signal signal
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'thread' # for Mutex
|
2
|
+
require 'singleton'
|
3
|
+
require 'sqreen/ecosystem/loggable'
|
4
|
+
|
5
|
+
# see https://github.com/sqreen/TechDoc/blob/master/content/specs/spec000024-sampling.md
|
6
|
+
|
7
|
+
module Sqreen
|
8
|
+
module Ecosystem
|
9
|
+
module Tracing
|
10
|
+
class Sampler
|
11
|
+
# @param [Array<Hash{String=>Object}>] definition
|
12
|
+
def initialize(definition)
|
13
|
+
@lines = definition.map { |h| Line.new(h) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def should_sample?
|
17
|
+
line = @lines.find(&:triggers?)
|
18
|
+
line ? line.saved_definition : false
|
19
|
+
end
|
20
|
+
|
21
|
+
class Line
|
22
|
+
include Loggable
|
23
|
+
|
24
|
+
attr_reader :saved_definition
|
25
|
+
|
26
|
+
# @param [Hash{String=>Object}] definition
|
27
|
+
def initialize(definition)
|
28
|
+
@saved_definition = definition
|
29
|
+
@primitives = []
|
30
|
+
|
31
|
+
unknown = definition.keys - PRIMITIVES_MAP.keys
|
32
|
+
unless unknown.empty?
|
33
|
+
logger.warn "Unknown primitives: #{unknown}"
|
34
|
+
@primitives << AlwaysFalsePrimitive.instance
|
35
|
+
return
|
36
|
+
end
|
37
|
+
|
38
|
+
PRIMITIVES_MAP.each do |key, prim_class|
|
39
|
+
next unless definition[key]
|
40
|
+
@primitives << prim_class.new(definition[key])
|
41
|
+
end
|
42
|
+
# if @primitives is empty the line will always
|
43
|
+
# return true: [].all?(&:triggers?) is true
|
44
|
+
end
|
45
|
+
|
46
|
+
def triggers?
|
47
|
+
@primitives.all?(&:triggers?)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class AlwaysFalsePrimitive
|
52
|
+
include Singleton
|
53
|
+
|
54
|
+
def triggers?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class CallsPrimitive
|
60
|
+
def initialize(calls_period)
|
61
|
+
@calls_period = calls_period
|
62
|
+
@count = 0
|
63
|
+
@mutex = Mutex.new
|
64
|
+
end
|
65
|
+
|
66
|
+
def triggers?
|
67
|
+
prev_count = nil
|
68
|
+
@mutex.synchronize do
|
69
|
+
prev_count = @count
|
70
|
+
@count += 1
|
71
|
+
end
|
72
|
+
|
73
|
+
(prev_count % @calls_period).zero?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class RandomPrimitive
|
78
|
+
def initialize(probability)
|
79
|
+
@probability = probability
|
80
|
+
end
|
81
|
+
|
82
|
+
def triggers?
|
83
|
+
@probability >= rand
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class MaxDurationMinutesPrimitive
|
88
|
+
def initialize(time_in_minutes)
|
89
|
+
@deadline = Sqreen.time + time_in_minutes * 60
|
90
|
+
@passed = false # no locking needed
|
91
|
+
end
|
92
|
+
|
93
|
+
def triggers?
|
94
|
+
return false if @passed
|
95
|
+
if Sqreen.time > @deadline
|
96
|
+
@passed = true
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class TargetPerMinutePrimitive
|
104
|
+
def initialize(max_calls)
|
105
|
+
@max_calls = max_calls
|
106
|
+
@minute_last_call = cur_minute
|
107
|
+
@calls_accumulated = 0
|
108
|
+
@mutex = Mutex.new
|
109
|
+
end
|
110
|
+
|
111
|
+
def triggers?
|
112
|
+
this_minute = cur_minute
|
113
|
+
calls_cur_minute = @mutex.synchronize do
|
114
|
+
if @minute_last_call == this_minute
|
115
|
+
@calls_accumulated += 1
|
116
|
+
else
|
117
|
+
@minute_last_call = this_minute
|
118
|
+
@calls_accumulated = 1
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
calls_cur_minute <= @max_calls
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def cur_minute
|
128
|
+
(Sqreen.time / 60).floor
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class MaxCallsPrimitive
|
133
|
+
def initialize(max_calls)
|
134
|
+
@max_calls = max_calls
|
135
|
+
@disabled = false # to avoid lock
|
136
|
+
@mutex = Mutex.new
|
137
|
+
@num_calls = 0
|
138
|
+
end
|
139
|
+
|
140
|
+
def triggers?
|
141
|
+
return false if @disabled
|
142
|
+
num_calls = @mutex.synchronize do
|
143
|
+
@num_calls += 1
|
144
|
+
end
|
145
|
+
|
146
|
+
num_calls <= @max_calls
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
PRIMITIVES_MAP = {
|
151
|
+
"calls" => CallsPrimitive,
|
152
|
+
"random" => RandomPrimitive,
|
153
|
+
"max_duration_minutes" => MaxDurationMinutesPrimitive,
|
154
|
+
"target_per_minute" => TargetPerMinutePrimitive,
|
155
|
+
"max_calls" => MaxCallsPrimitive,
|
156
|
+
}.freeze
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|