sqreen 1.20.1-java → 1.21.0.beta3-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 (77) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +16 -0
  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/attack_detected.html +1 -2
  7. data/lib/sqreen/condition_evaluator.rb +9 -2
  8. data/lib/sqreen/conditionable.rb +24 -6
  9. data/lib/sqreen/configuration.rb +1 -1
  10. data/lib/sqreen/deferred_logger.rb +50 -14
  11. data/lib/sqreen/deliveries/batch.rb +8 -1
  12. data/lib/sqreen/deprecation.rb +38 -0
  13. data/lib/sqreen/ecosystem.rb +96 -0
  14. data/lib/sqreen/ecosystem/dispatch_table.rb +43 -0
  15. data/lib/sqreen/ecosystem/exception_reporting.rb +26 -0
  16. data/lib/sqreen/ecosystem/http/net_http.rb +50 -0
  17. data/lib/sqreen/ecosystem/http/rack_request.rb +39 -0
  18. data/lib/sqreen/ecosystem/loggable.rb +13 -0
  19. data/lib/sqreen/ecosystem/module_api.rb +30 -0
  20. data/lib/sqreen/ecosystem/module_api/event_listener.rb +18 -0
  21. data/lib/sqreen/ecosystem/module_api/instrumentation.rb +23 -0
  22. data/lib/sqreen/ecosystem/module_api/message_producer.rb +51 -0
  23. data/lib/sqreen/ecosystem/module_api/signal_producer.rb +24 -0
  24. data/lib/sqreen/ecosystem/module_api/tracing.rb +45 -0
  25. data/lib/sqreen/ecosystem/module_api/tracing/client_data.rb +31 -0
  26. data/lib/sqreen/ecosystem/module_api/tracing/server_data.rb +27 -0
  27. data/lib/sqreen/ecosystem/module_api/tracing_id_generation.rb +16 -0
  28. data/lib/sqreen/ecosystem/module_api/transaction_storage.rb +71 -0
  29. data/lib/sqreen/ecosystem/module_registry.rb +44 -0
  30. data/lib/sqreen/ecosystem/redis/redis_connection.rb +43 -0
  31. data/lib/sqreen/ecosystem/tracing/modules/client.rb +31 -0
  32. data/lib/sqreen/ecosystem/tracing/modules/server.rb +30 -0
  33. data/lib/sqreen/ecosystem/tracing/sampler.rb +160 -0
  34. data/lib/sqreen/ecosystem/tracing/sampling_configuration.rb +150 -0
  35. data/lib/sqreen/ecosystem/tracing/signals/tracing_client.rb +53 -0
  36. data/lib/sqreen/ecosystem/tracing/signals/tracing_server.rb +53 -0
  37. data/lib/sqreen/ecosystem/tracing_broker.rb +101 -0
  38. data/lib/sqreen/ecosystem/tracing_id_setup.rb +34 -0
  39. data/lib/sqreen/ecosystem/transaction_storage.rb +64 -0
  40. data/lib/sqreen/ecosystem/util/call_writers_from_init.rb +13 -0
  41. data/lib/sqreen/ecosystem_integration.rb +87 -0
  42. data/lib/sqreen/ecosystem_integration/around_callbacks.rb +99 -0
  43. data/lib/sqreen/ecosystem_integration/instrumentation_service.rb +42 -0
  44. data/lib/sqreen/ecosystem_integration/request_lifecycle_tracking.rb +58 -0
  45. data/lib/sqreen/ecosystem_integration/signal_consumption.rb +35 -0
  46. data/lib/sqreen/events/request_record.rb +0 -1
  47. data/lib/sqreen/frameworks/generic.rb +24 -1
  48. data/lib/sqreen/frameworks/rails.rb +0 -7
  49. data/lib/sqreen/frameworks/request_recorder.rb +2 -0
  50. data/lib/sqreen/graft/call.rb +106 -19
  51. data/lib/sqreen/graft/callback.rb +1 -1
  52. data/lib/sqreen/graft/hook.rb +212 -100
  53. data/lib/sqreen/graft/hook_point.rb +18 -11
  54. data/lib/sqreen/legacy/instrumentation.rb +22 -10
  55. data/lib/sqreen/legacy/old_event_submission_strategy.rb +9 -2
  56. data/lib/sqreen/log.rb +3 -2
  57. data/lib/sqreen/log/loggable.rb +1 -0
  58. data/lib/sqreen/logger.rb +24 -0
  59. data/lib/sqreen/metrics.rb +1 -0
  60. data/lib/sqreen/metrics/req_detailed.rb +41 -0
  61. data/lib/sqreen/metrics_store.rb +11 -0
  62. data/lib/sqreen/null_logger.rb +22 -0
  63. data/lib/sqreen/remote_command.rb +4 -0
  64. data/lib/sqreen/rules.rb +8 -4
  65. data/lib/sqreen/rules/blacklist_ips_cb.rb +2 -2
  66. data/lib/sqreen/rules/custom_error_cb.rb +3 -3
  67. data/lib/sqreen/rules/rule_cb.rb +4 -2
  68. data/lib/sqreen/rules/waf_cb.rb +3 -3
  69. data/lib/sqreen/runner.rb +63 -8
  70. data/lib/sqreen/session.rb +2 -0
  71. data/lib/sqreen/signals/conversions.rb +6 -1
  72. data/lib/sqreen/version.rb +1 -1
  73. data/lib/sqreen/weave/budget.rb +35 -0
  74. data/lib/sqreen/weave/legacy/instrumentation.rb +274 -132
  75. data/lib/sqreen/worker.rb +6 -2
  76. metadata +46 -9
  77. data/lib/sqreen/encoding_sanitizer.rb +0 -27
@@ -0,0 +1,44 @@
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
+ next unless mod.respond_to? :setup
20
+ logger.debug { "Initializing module with type #{mod.class}" }
21
+ mod.setup
22
+ end
23
+ end
24
+
25
+ def destroy_all
26
+ # not implemented
27
+ end
28
+
29
+ # @param [Class] type
30
+ def each_module(type = nil, &block)
31
+ selected_mods = type ? (@mods.select { |mod| mod.is_a?(type) }) : @mods
32
+ if block_given?
33
+ selected_mods.each(&block)
34
+ else
35
+ selected_mods.each
36
+ end
37
+ end
38
+
39
+ def module_subset(type)
40
+ each_module(type).to_a
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ require 'sqreen/ecosystem/module_api'
2
+ require 'sqreen/ecosystem/module_api/instrumentation'
3
+ require 'sqreen/ecosystem/module_api/message_producer'
4
+ require 'sqreen/ecosystem/module_api/tracing_id_generation'
5
+ require 'sqreen/ecosystem/module_api/tracing/client_data'
6
+
7
+ module Sqreen
8
+ module Ecosystem
9
+ module Redis
10
+ class RedisConnection
11
+ class RedisConnectionData
12
+ include ModuleApi::Tracing::ClientData
13
+
14
+ attr_accessor :port
15
+ end
16
+
17
+ include ModuleApi::Instrumentation
18
+ include ModuleApi::MessageProducer
19
+ include ModuleApi::TracingIdGeneration
20
+
21
+ def setup
22
+ advice = wrap_for_interest(ModuleApi::Tracing::ClientData, &method(:before_advice))
23
+ instrument 'Redis::Connection::TCPSocket#connect',
24
+ before: advice
25
+ end
26
+
27
+ private
28
+
29
+ def before_advice
30
+ host = call.args[0]
31
+ port = call.args[1]
32
+
33
+ RedisConnectionData.new(
34
+ transport: 'redis',
35
+ host: host,
36
+ port: port,
37
+ tracing_identifier: create_tracing_id
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,31 @@
1
+ require 'sqreen/ecosystem/tracing/signals/tracing_client'
2
+ require 'sqreen/ecosystem/module_api/tracing'
3
+ require 'sqreen/ecosystem/module_api/tracing/client_data'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ module Tracing
8
+ module Modules
9
+ class Client
10
+ include ModuleApi::Tracing
11
+
12
+ consumes ModuleApi::Tracing::ClientData
13
+ fixed_scope 'client'
14
+
15
+ # @param [Sqreen::Ecosystem::ModuleApi::Tracing::ClientData] data
16
+ def receive(data)
17
+ signal = Tracing::Signals::TracingClient.new
18
+ signal.payload = Tracing::Signals::TracingClient::Payload.new(
19
+ transport: data.transport,
20
+ host: data.host,
21
+ ip: data.ip,
22
+ tracing_identifier: data.tracing_identifier
23
+ )
24
+
25
+ submit_signal signal
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ require 'sqreen/ecosystem/tracing/signals/tracing_server'
2
+ require 'sqreen/ecosystem/module_api/tracing'
3
+ require 'sqreen/ecosystem/module_api/tracing/server_data'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ module Tracing
8
+ module Modules
9
+ class Server
10
+ include ModuleApi::Tracing
11
+
12
+ consumes ModuleApi::Tracing::ServerData
13
+ fixed_scope 'server'
14
+
15
+ # @param [Sqreen::Ecosystem::ModuleApi::Tracing::ServerData] data
16
+ def receive(data)
17
+ signal = Tracing::Signals::TracingServer.new
18
+ signal.payload = Tracing::Signals::TracingServer::Payload.new(
19
+ transport: data.transport,
20
+ client_ip: data.client_ip,
21
+ tracing_identifier: data.tracing_identifier
22
+ )
23
+
24
+ submit_signal signal
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ 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
@@ -0,0 +1,150 @@
1
+ require 'thread'
2
+ require 'sqreen/ecosystem/loggable'
3
+ require 'sqreen/ecosystem/tracing/sampler'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ module Tracing
8
+ # tracing sampling configuration, as specified by the 2nd argument of the
9
+ # tracing_enable command.
10
+ # See https://github.com/sqreen/TechDoc/blob/master/content/specs/spec000025-enabling-tracing.md
11
+ class SamplingConfiguration
12
+ include Loggable
13
+
14
+ DEFAULT_SCOPE = '*'.freeze
15
+
16
+ def initialize(sampling_config)
17
+ @sampling_config = sampling_config
18
+ @samplers = {}
19
+ @samplers_virtual = build_virtual_holders(sampling_config)
20
+ end
21
+
22
+ # @param [String] scope either the scope or the part behind @ in a virtual scope
23
+ # @param [String] qualifier the part after @ in a virtual scope or nil
24
+ def should_sample?(scope, qualifier = nil)
25
+ if qualifier
26
+ fetch_sampler_virtual(scope, qualifier).should_sample?
27
+ else
28
+ fetch_sampler(scope).should_sample?
29
+ end
30
+ end
31
+
32
+ def forget_virtual_scope(scope, qualifier)
33
+ holder = @samplers_virtual[scope]
34
+ return unless holder.delete!(qualifier)
35
+ end
36
+
37
+ private
38
+
39
+ def fetch_sampler_virtual(scope, qualifier)
40
+ holder = @samplers_virtual[scope]
41
+
42
+ # no virtual scope configured, fallback to plain scope
43
+ return fetch_sampler(scope) unless holder
44
+
45
+ holder[qualifier]
46
+ end
47
+
48
+ def fetch_sampler(scope)
49
+ sampler = @samplers[scope]
50
+ return sampler if sampler
51
+
52
+ cfg = @sampling_config[scope] || @sampling_config[DEFAULT_SCOPE]
53
+ if cfg
54
+ @samplers[scope] = SamplingConfiguration.build_sampler(scope, cfg)
55
+ else
56
+ logger.info { "Disabling scope #{scope} due to its not being configured" }
57
+ @samplers[scope] = DisabledScopeSampler
58
+ end
59
+ end
60
+
61
+ def build_virtual_holders(sampling_config)
62
+ Hash[
63
+ sampling_config
64
+ .select { |scope, _cfg| scope.end_with?('@*') }
65
+ .map do |scope, cfg|
66
+ parent = scope[0...-2] # remove trailing @*
67
+ [parent, VirtualScopesHolder.new(parent, cfg)]
68
+ end
69
+ ]
70
+ end
71
+
72
+ class << self
73
+ include Loggable
74
+
75
+ def build_sampler(scope, cfg)
76
+ do_build_sampler(cfg['enabled'], cfg['sampling'] || [{}])
77
+ rescue StandardError => e
78
+ logger.warn "Invalid sampling configuration for #{scope}: #{e.inspect}"
79
+ logger.debug { e.backtrace }
80
+ DisabledScopeSampler
81
+ end
82
+
83
+ private
84
+
85
+ def do_build_sampler(enabled, sampling)
86
+ if enabled
87
+ Sampler.new(sampling)
88
+ else
89
+ logger.debug { "Disabling scope #{scope}" }
90
+ DisabledScopeSampler
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ class VirtualScopesHolder
97
+ include Loggable
98
+
99
+ MAX_VIRTUAL_SCOPES = 120
100
+ DISCARD_SIZE = 20
101
+
102
+ def initialize(parent, cfg)
103
+ @parent = parent
104
+ @cfg = cfg
105
+ @virtual_scopes = {}
106
+ @mutex = Mutex.new
107
+ end
108
+
109
+ def [](qualifier)
110
+ return false unless @cfg['enabled']
111
+
112
+ @mutex.synchronize do
113
+ sampler = @virtual_scopes[qualifier]
114
+ return sampler if sampler
115
+
116
+ @virtual_scopes[qualifier] = create_virtual(qualifier)
117
+ end
118
+ end
119
+
120
+ def delete!(qualifier)
121
+ @virtual_scopes.delete(qualifier)
122
+ end
123
+
124
+ private
125
+
126
+ def create_virtual(qualifier)
127
+ discard if @virtual_scopes.size >= MAX_VIRTUAL_SCOPES
128
+
129
+ @virtual_scopes[qualifier] =
130
+ SamplingConfiguration.build_sampler("#{@parent}@#{qualifier}", @cfg)
131
+ end
132
+
133
+ def discard
134
+ logger.info { "Discarding excess virtual scopes for scope '#{@parent}'" }
135
+ discard_keys = @virtual_scopes.keys.sample(DISCARD_SIZE)
136
+ @virtual_scopes.delete_if { |k, _v| discard_keys.include? k }
137
+ end
138
+ end
139
+
140
+ # fake sampler that always returns false
141
+ class DisabledScopeSampler
142
+ class << self
143
+ def should_sample?
144
+ false
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end