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,53 @@
1
+ require 'sqreen/kit/configuration'
2
+ require 'sqreen/kit/signals/point'
3
+ require 'sqreen/kit/signals/dto_helper'
4
+
5
+ # reference: https://github.com/sqreen/SignalsSchemas/blob/master/schemas/payload/tracing/client-2020-04-21/schema.cue
6
+
7
+ module Sqreen
8
+ module Ecosystem
9
+ module Tracing
10
+ module Signals
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ class Sqreen::Ecosystem::Tracing::Signals::TracingClient < Sqreen::Kit::Signals::Point
17
+ readonly_attrs :payload_schema, :source, :signal_name
18
+
19
+ def initialize(values = {})
20
+ self.payload_schema = Payload::SCHEMA_VERSION
21
+ self.source = Sqreen::Kit::Configuration.default_source
22
+ self.signal_name = 'tracing.client'
23
+ self.time = values[:time] || Time.now
24
+ super
25
+ end
26
+
27
+ def payload=(payload)
28
+ unless payload.is_a?(Payload)
29
+ raise ArgumentError, "Payload should be a #{Payload}"
30
+ end
31
+ super
32
+ end
33
+
34
+ class Payload
35
+ include Sqreen::Kit::Signals::DtoHelper
36
+
37
+ add_mandatory_attrs :transport, :host
38
+
39
+ SCHEMA_VERSION = 'tracing/client-2020-04-21'.freeze
40
+
41
+ # @return [Symbol]
42
+ attr_accessor :transport
43
+
44
+ # @return [String]
45
+ attr_accessor :host
46
+
47
+ # @return [String]
48
+ attr_accessor :ip
49
+
50
+ # @return [String]
51
+ attr_accessor :tracing_identifier
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ require 'sqreen/kit/configuration'
2
+ require 'sqreen/kit/signals/point'
3
+ require 'sqreen/kit/signals/dto_helper'
4
+
5
+ # reference: https://github.com/sqreen/SignalsSchemas/blob/master/schemas/payload/tracing/server-2020-04-21/schema.cue
6
+
7
+ module Sqreen
8
+ module Ecosystem
9
+ module Tracing
10
+ module Signals
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ class Sqreen::Ecosystem::Tracing::Signals::TracingServer < Sqreen::Kit::Signals::Point
17
+ readonly_attrs :payload_schema, :source, :signal_name
18
+
19
+ def initialize(values = {})
20
+ self.payload_schema = Payload::SCHEMA_VERSION
21
+ self.source = Sqreen::Kit::Configuration.default_source
22
+ self.signal_name = 'tracing.server'
23
+ self.time = values[:time] || Time.now
24
+ super
25
+ end
26
+
27
+ def payload=(payload)
28
+ unless payload.is_a?(Payload)
29
+ raise ArgumentError, "Payload should be a #{Payload}"
30
+ end
31
+ super
32
+ end
33
+
34
+ class Payload
35
+ include Sqreen::Kit::Signals::DtoHelper
36
+
37
+ add_mandatory_attrs :transport, :client_ip
38
+
39
+ SCHEMA_VERSION = 'tracing/server-2020-04-21'.freeze
40
+
41
+ # @return [Symbol]
42
+ attr_accessor :transport
43
+
44
+ # @return [String]
45
+ attr_accessor :client_ip
46
+
47
+ # @return [Array<String>]
48
+ attr_accessor :previous_hops
49
+
50
+ # @return [String]
51
+ attr_accessor :tracing_identifier
52
+ end
53
+ end
@@ -0,0 +1,101 @@
1
+ require 'sqreen/ecosystem/loggable'
2
+ require 'sqreen/ecosystem/exception_reporting'
3
+
4
+ module Sqreen
5
+ module Ecosystem
6
+ class TracingBroker
7
+ include Loggable
8
+ include ExceptionReporting
9
+
10
+ # Stores a lookup resolution so that lookup (incl. sampling) is done
11
+ # only once, not in the beginning of the producer code (when it asks
12
+ # whether it should proceed) and again once it delivers the data
13
+ ObserverLookup = Struct.new(:modules)
14
+
15
+ # @return [Sqreen::Ecosystem::Tracing::SamplingConfiguration]
16
+ attr_writer :sampling_configuration
17
+
18
+ # @param [Array<Sqreen::Ecosystem::ModuleApi::Tracing>] tracing_modules
19
+ def initialize(tracing_modules)
20
+ @sampling_configuration = nil
21
+ @type_to_subscribers = {}
22
+ tracing_modules.each do |mod|
23
+ consumed_type = mod.consumed_type
24
+ @type_to_subscribers[consumed_type] ||= []
25
+ @type_to_subscribers[consumed_type] << mod
26
+ end
27
+ end
28
+
29
+ # @param [Object] data
30
+ # @param [Sqreen::Ecosystem::TracingBroker::ObserverLookup] prev_lookup
31
+ def publish(data, prev_lookup)
32
+ prev_lookup.modules.each do |mod|
33
+ mod_process_data mod, data
34
+ end
35
+ end
36
+
37
+ # @param [Module] data_type
38
+ # @param [Hash] _hints reserved for future use, e.g. virtual scopes
39
+ # @return [Sqreen::Ecosystem::TracingBroker::ObserverLookup]
40
+ def interested_consumers(data_type, _hints = {})
41
+ unless @sampling_configuration
42
+ logger.debug do
43
+ "Declaring no one is interested in #{data_type} " \
44
+ "because tracing hasn't been enabled yet"
45
+ end
46
+ return false
47
+ end
48
+
49
+ # if we have several modules with the same scope, we
50
+ # should ask whether we should sample only once
51
+ scope_to_should_sample = Hash.new do |hash, scope|
52
+ result = @sampling_configuration.should_sample?(scope)
53
+ if result
54
+ logger.debug { "Will sample scope #{scope}. Sampling line: #{result}" }
55
+ else
56
+ logger.debug { "Will NOT sample scope #{scope}" }
57
+ end
58
+
59
+ hash[scope] = result
60
+ end
61
+
62
+ res = subscribers(data_type).select do |mod|
63
+ scope_to_should_sample[mod.scope]
64
+ end
65
+
66
+ res.empty? ? false : ObserverLookup.new(res)
67
+ end
68
+
69
+ private
70
+
71
+ # @param [Sqreen::Ecosystem::ModuleApi::Tracing] mod
72
+ def mod_process_data(mod, data)
73
+ mod.receive(data)
74
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
75
+ report_exception("Error invoking tracing module #{mod}", e)
76
+ end
77
+
78
+ # @param [Module] data_type
79
+ # @return Array<Sqreen::Ecosystem::ModuleApi::Tracing>
80
+ def subscribers(data_type)
81
+ subscribers = @type_to_subscribers[data_type]
82
+
83
+ # None of the modules subscribes to data_type directly,
84
+ # but maybe they subscribe to one of the ancestors
85
+ # Cache this lookup
86
+ unless subscribers
87
+ subscribers = parents(data_type).inject([]) do |accum, type|
88
+ accum + (@type_to_subscribers[type] || [])
89
+ end
90
+ @type_to_subscribers[data_type] = subscribers
91
+ end
92
+
93
+ subscribers
94
+ end
95
+
96
+ def parents(type)
97
+ type.ancestors - Object.ancestors - [type]
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,34 @@
1
+ require 'sqreen/ecosystem/module_registry'
2
+ require 'sqreen/ecosystem/transaction_storage'
3
+ require 'sqreen/ecosystem/module_api/signal_producer'
4
+
5
+ module Sqreen
6
+ module Ecosystem
7
+ class TracingIdSetup
8
+ # @param [Array<Sqreen::Ecosystem::ModuleApi::SignalProducer>] signal_producer_modules
9
+ def initialize(signal_producer_modules)
10
+ @modules = signal_producer_modules
11
+ @tracing_id_prefix = nil
12
+ end
13
+
14
+ def setup_modules
15
+ inject_out_of_tx_tracing_id_gen
16
+ end
17
+
18
+ attr_writer :tracing_id_prefix
19
+
20
+ private
21
+
22
+ def inject_out_of_tx_tracing_id_gen
23
+ @modules.each do |mod|
24
+ mod.tracing_id_producer = method(:generate_tracing_id)
25
+ end
26
+ end
27
+
28
+ def generate_tracing_id
29
+ return nil unless @tracing_id_prefix
30
+ "#{@tracing_id_prefix}.#{SecureRandom.uuid}"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,64 @@
1
+ require 'sqreen/ecosystem/loggable'
2
+
3
+ module Sqreen
4
+ module Ecosystem
5
+ # The transaction storage is a mechanism for the modules to share
6
+ # request-scoped data with each other or to keep request-scoped objects
7
+ # themselves, although, for this last use case to be effective,
8
+ # propagation of request start/end events to the modules will likely be
9
+ # needed. A more generic notification of data availability to modules
10
+ # also may be needed in the future.
11
+ #
12
+ # This is not be used to share data with the Ecosystem client
13
+ #
14
+ # This class is not thread safe because it can call the lazy getter
15
+ # twice if [] is called concurrently.
16
+ class TransactionStorage
17
+ include Loggable
18
+
19
+ class << self
20
+ # @return [Sqreen::Ecosystem::TransactionStorage]
21
+ def create_thread_local
22
+ Thread.current[:tx_storage] = new
23
+ end
24
+
25
+ # @return [Sqreen::Ecosystem::TransactionStorage]
26
+ def fetch_thread_local
27
+ Thread.current[:tx_storage]
28
+ end
29
+
30
+ def destroy_thread_local
31
+ Thread.current[:tx_storage] = nil
32
+ end
33
+ end
34
+
35
+ def initialize
36
+ @values = {}
37
+ @values_lazy = {}
38
+ end
39
+
40
+ def []=(key, value)
41
+ @values[key] = value
42
+ end
43
+
44
+ def set_lazy(key, &block)
45
+ @values_lazy[key] = block
46
+ end
47
+
48
+ def [](key)
49
+ v = @values[key]
50
+ return v unless v.nil?
51
+
52
+ v = @values_lazy[key]
53
+ return nil if v.nil?
54
+
55
+ begin
56
+ @values[key] = v.call
57
+ rescue StandardError => e
58
+ logger.warn { "Error resolving key #{e} with lazy value: #{e.message}" }
59
+ raise
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,13 @@
1
+ module Sqreen
2
+ module Ecosystem
3
+ module Util
4
+ module CallWritersFromInit
5
+ def initialize(values = {})
6
+ values.each do |attr, val|
7
+ public_send("#{attr}=", val)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ require 'sqreen/log/loggable'
2
+ require 'sqreen/metrics_store'
3
+ require 'sqreen/ecosystem'
4
+ require 'sqreen/ecosystem/dispatch_table'
5
+ require 'sqreen/ecosystem_integration/around_callbacks'
6
+ require 'sqreen/ecosystem_integration/instrumentation_service'
7
+ require 'sqreen/ecosystem_integration/request_lifecycle_tracking'
8
+ require 'sqreen/ecosystem_integration/signal_consumption'
9
+
10
+ module Sqreen
11
+ # This class is the interface through which the agent interacts
12
+ # with the ecosystem.
13
+ #
14
+ # Other classes in the EcosystemIntegration module implement the
15
+ # functionality that the ecosystem requires in order to deliver
16
+ # data to the agent and to be informed by the agent of certain
17
+ # key events (see Sqreen::Ecosystem::DispatchTable).
18
+ class EcosystemIntegration
19
+ include Sqreen::Log::Loggable
20
+
21
+ # @param [Sqreen::Framework] framework
22
+ # @param [Proc] create_binning_metric
23
+ def initialize(framework, queue, create_binning_metric)
24
+ @framework = framework
25
+ @queue = queue
26
+ # XXX: created metrics are insulated from feature upgrades
27
+ @create_binning_metric = create_binning_metric
28
+ @request_lifecycle = RequestLifecycleTracking.new
29
+ @online = false
30
+ end
31
+
32
+ def init
33
+ raise 'already initialized' if @online
34
+
35
+ setup_dispatch_table
36
+ AroundCallbacks.create_metric = @create_binning_metric
37
+ Ecosystem.init
38
+ logger.info 'Ecosystem successfully initialized'
39
+ @online = true
40
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
41
+ logger.warn { "Error initializing Ecosystem: #{e.message}" }
42
+ logger.debug { e.backtrace.map { |x| " #{x}" }.join("\n") }
43
+ Sqreen::RemoteException.record(e)
44
+ end
45
+
46
+ def disable
47
+ raise NotImplementedYet
48
+ end
49
+
50
+ def request_start(rack_request)
51
+ return unless @online
52
+
53
+ Ecosystem.start_transaction
54
+ @request_lifecycle.notify_request_start(rack_request)
55
+ end
56
+
57
+ def request_end
58
+ return unless @online
59
+
60
+ Ecosystem.end_transaction
61
+ end
62
+
63
+ def handle_tracing_command(trace_id_prefix, scopes_config)
64
+ return unless @online
65
+
66
+ Ecosystem.configure_sampling(trace_id_prefix, scopes_config)
67
+ end
68
+
69
+ private
70
+
71
+ def setup_dispatch_table
72
+ Ecosystem::DispatchTable.consume_signal =
73
+ create_signal_consumption.method(:consume_signal)
74
+
75
+ Ecosystem::DispatchTable.add_request_start_listener =
76
+ @request_lifecycle.method(:add_start_observer)
77
+
78
+ Ecosystem::DispatchTable.fetch_logger = lambda { logger }
79
+
80
+ Ecosystem::DispatchTable.instrument = InstrumentationService.method(:instrument)
81
+ end
82
+
83
+ def create_signal_consumption
84
+ SignalConsumption.new(@framework, @request_lifecycle, @queue)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,99 @@
1
+ require 'sqreen/metrics_store'
2
+ require 'sqreen/log/loggable'
3
+ require 'sqreen/events/remote_exception'
4
+ require 'sqreen/mono_time'
5
+ require 'sqreen/graft/call'
6
+
7
+ module Sqreen
8
+ class EcosystemIntegration
9
+ module AroundCallbacks
10
+ class << self
11
+ include Log::Loggable::ClassMethods
12
+
13
+ # @return [Proc]
14
+ attr_accessor :create_metric
15
+
16
+ # for instrumentation hooks
17
+ # instrumentation hooks already handle budgets/metrics
18
+ def wrap_instrumentation_hook(module_name, action, callable)
19
+ # make tag similar to that of rules
20
+ # TODO: move to structured tags to avoid silly string manips
21
+ action_for_metric = if %w[pre post failing finally].include?(action)
22
+ action
23
+ else
24
+ 'pre'
25
+ end
26
+ metric_name = "sq.ecosystem_#{module_name}.#{action_for_metric}"
27
+ create_metric.call(metric_name)
28
+
29
+ Proc.new do |*args|
30
+ begin
31
+ callable.call(*args)
32
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
33
+ # 2) rescue exceptions
34
+ logger.warn { "Error in #{module_name}:#{action}: #{e.message}" }
35
+ logger.debug { e.backtrace.map { |x| " #{x}" }.join("\n") }
36
+ Sqreen::RemoteException.record(e)
37
+ end # end begin
38
+ end # end proc
39
+ end
40
+
41
+ # XXX: not used yet
42
+ def wrap_generic_callback(module_name, action, callable)
43
+ timer_name = "ecosystem:#{module_name}@#{action}"
44
+ perf_notif_name = "ecosystem_#{module_name}"
45
+
46
+ # XXX: register metric
47
+
48
+ Proc.new do |*args|
49
+ begin
50
+ req_storage = Thread.current[:sqreen_http_request]
51
+
52
+ timer = Graft::Timer.new(timer_name) do |t|
53
+ # this is an epilogue to measure()
54
+ req_storage && req_storage[:timed_hooks] << t
55
+ end
56
+
57
+ req_timer = nil
58
+ timer.measure do
59
+ # not in a request, no budget; call cb
60
+ next callable.call(*args) unless req_storage
61
+
62
+ # 1) budget enforcement
63
+ # skip callback if budget already expended
64
+ next if req_storage[:time_budget_expended]
65
+
66
+ budget = req_storage[:time_budget]
67
+ if budget
68
+ req_timer = req_storage[:timer]
69
+ remaining = budget - req_timer.elapsed
70
+ unless remaining > 0
71
+ req_storage[:time_budget_expended] = true
72
+ next # skip callback
73
+ end
74
+ end
75
+
76
+ callable.call(*args)
77
+ end
78
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
79
+ # 2) rescue exceptions
80
+ logger.warn { "Error in #{module_name}:#{action}: #{e.message}" }
81
+ logger.debug { e.backtrace.map { |x| " #{x}" }.join("\n") }
82
+ Sqreen::RemoteException.record(e)
83
+ ensure
84
+ # 3) contribute to performance metrics
85
+ if timer
86
+ req_timer.include_measurements(timer) if req_timer
87
+
88
+ # XXX: PerformanceNotifications is used no more
89
+ Sqreen::PerformanceNotifications.notify(
90
+ perf_notif_name, action, *timer.start_and_end
91
+ )
92
+ end
93
+ end # end begin
94
+ end # end proc
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end