sqreen 1.19.0-java → 1.20.1-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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/lib/sqreen/agent_message.rb +20 -0
  4. data/lib/sqreen/aggregated_metric.rb +25 -0
  5. data/lib/sqreen/ca.crt +24 -0
  6. data/lib/sqreen/configuration.rb +10 -4
  7. data/lib/sqreen/deliveries/batch.rb +4 -1
  8. data/lib/sqreen/deliveries/simple.rb +4 -0
  9. data/lib/sqreen/endpoint_testing.rb +184 -0
  10. data/lib/sqreen/event.rb +7 -5
  11. data/lib/sqreen/events/attack.rb +23 -18
  12. data/lib/sqreen/events/remote_exception.rb +0 -22
  13. data/lib/sqreen/events/request_record.rb +15 -70
  14. data/lib/sqreen/frameworks/request_recorder.rb +13 -2
  15. data/lib/sqreen/kit/signals/specialized/aggregated_metric.rb +72 -0
  16. data/lib/sqreen/kit/signals/specialized/attack.rb +57 -0
  17. data/lib/sqreen/kit/signals/specialized/binning_metric.rb +76 -0
  18. data/lib/sqreen/kit/signals/specialized/http_trace.rb +26 -0
  19. data/lib/sqreen/kit/signals/specialized/sdk_track_call.rb +50 -0
  20. data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +57 -0
  21. data/lib/sqreen/legacy/old_event_submission_strategy.rb +221 -0
  22. data/lib/sqreen/legacy/waf_redactions.rb +49 -0
  23. data/lib/sqreen/log/loggable.rb +1 -1
  24. data/lib/sqreen/metrics/base.rb +3 -0
  25. data/lib/sqreen/metrics_store.rb +22 -12
  26. data/lib/sqreen/performance_notifications/binned_metrics.rb +8 -2
  27. data/lib/sqreen/rules.rb +4 -2
  28. data/lib/sqreen/rules/not_found_cb.rb +2 -0
  29. data/lib/sqreen/rules/rule_cb.rb +2 -0
  30. data/lib/sqreen/rules/waf_cb.rb +13 -10
  31. data/lib/sqreen/runner.rb +75 -8
  32. data/lib/sqreen/sensitive_data_redactor.rb +19 -31
  33. data/lib/sqreen/session.rb +51 -43
  34. data/lib/sqreen/signals/conversions.rb +283 -0
  35. data/lib/sqreen/signals/http_trace_redaction.rb +111 -0
  36. data/lib/sqreen/signals/signals_submission_strategy.rb +78 -0
  37. data/lib/sqreen/version.rb +1 -1
  38. data/lib/sqreen/weave/legacy/instrumentation.rb +7 -7
  39. metadata +50 -6
  40. data/lib/sqreen/backport.rb +0 -9
  41. data/lib/sqreen/backport/clock_gettime.rb +0 -74
  42. data/lib/sqreen/backport/original_name.rb +0 -88
@@ -0,0 +1,57 @@
1
+ # typed: ignore
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ require 'sqreen/kit/signals/point'
7
+ require 'sqreen/kit/signals/dto_helper'
8
+
9
+ # reference: https://github.com/sqreen/SignalsSchemas/blob/master/schemas/payload/sqreen_exception/2020-01-01T00_00_00_000Z/schema.cue
10
+
11
+ module Sqreen
12
+ module Kit
13
+ module Signals
14
+ module Specialized
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ class Sqreen::Kit::Signals::Specialized::SqreenException < Sqreen::Kit::Signals::Point
21
+ PAYLOAD_SCHEMA_VERSION = 'sqreen_exception/2020-01-01T00:00:00.000Z'.freeze
22
+
23
+ # @return [Hash]
24
+ attr_accessor :infos
25
+
26
+ # @return [Exception]
27
+ attr_accessor :ruby_exception
28
+
29
+ add_mandatory_attrs :source, :time, :ruby_exception
30
+
31
+ validate_str_attr :source, /\A(?:sqreen:rule:[a-f0-9]{40}:.+)|(?:sqreen:agent:.+)\z/
32
+
33
+ def self.attributes_for_to_h_self
34
+ [] # don't include ruby_exception in list of attributes for to_h
35
+ end
36
+
37
+ def initialize(values = {})
38
+ self.payload_schema = PAYLOAD_SCHEMA_VERSION
39
+ self.signal_name = 'sq.agent.exception'
40
+ self.time = values[:time] || Time.now
41
+ super
42
+ end
43
+
44
+ def payload
45
+ return nil unless @ruby_exception
46
+ compact_hash({
47
+ klass: @ruby_exception.class.to_s,
48
+ message: @ruby_exception.message,
49
+ infos: @infos,
50
+ })
51
+ end
52
+
53
+ def location
54
+ return nil unless @ruby_exception
55
+ Sqreen::Kit::Signals::Location.new(exception: @ruby_exception)
56
+ end
57
+ end
@@ -0,0 +1,221 @@
1
+ # typed: ignore
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ require 'sqreen/aggregated_metric'
7
+ require 'sqreen/log/loggable'
8
+ require 'sqreen/legacy/waf_redactions'
9
+
10
+ module Sqreen
11
+ module Legacy
12
+ # see also Sqreen::Signals::SignalsSubmissionStrategy
13
+ # usage in Sqreen:Session
14
+ class OldEventSubmissionStrategy
15
+ include Sqreen::Log::Loggable
16
+
17
+ RETRY_MANY = 301
18
+
19
+ def initialize(post_proc)
20
+ @post_proc = post_proc
21
+ end
22
+
23
+ def post_metrics(metrics)
24
+ return if metrics.nil? || metrics.empty?
25
+ payload = { metrics: metrics.map { |m| EventToHash.convert_agg_metric(m) } }
26
+ post('metrics', payload, {}, RETRY_MANY)
27
+ end
28
+
29
+ # @param attack [Sqreen::Attack]
30
+ def post_attack(attack)
31
+ post('attack', EventToHash.convert_attack(attack), {}, RETRY_MANY)
32
+ end
33
+
34
+ # @param [Sqreen::RequestRecord] request_record
35
+ def post_request_record(request_record)
36
+ rr_hash = EventToHash.convert_request_record(request_record)
37
+ post('request_record', rr_hash, {}, RETRY_MANY)
38
+ end
39
+
40
+ # Post an exception to Sqreen for analysis
41
+ # @param exception [RemoteException] Exception and context to be sent over
42
+ def post_sqreen_exception(exception)
43
+ data = EventToHash.convert_exception(exception)
44
+ post('sqreen_exception', data, {}, 5)
45
+ rescue StandardError => e
46
+ logger.warn(format('Could not post exception (network down? %s) %s',
47
+ e.inspect,
48
+ exception.inspect))
49
+ nil
50
+ end
51
+
52
+ def post_batch(events)
53
+ batch = events.map do |event|
54
+ h = case event
55
+ when AggregatedMetric
56
+ logger.warn "Aggregated metric event in non-signal mode. Signals disabled at runtime?"
57
+ next
58
+ when Attack # in practice only found inside req rec
59
+ EventToHash.convert_attack event
60
+ when RemoteException
61
+ EventToHash.convert_exception event
62
+ when RequestRecord
63
+ EventToHash.convert_request_record event
64
+ else
65
+ logger.warn "Unexpected event type: #{event}"
66
+ next
67
+ end
68
+ h['event_type'] = event_kind(event)
69
+ h
70
+ end
71
+ Sqreen.log.debug do
72
+ tally = Hash[events.group_by(&:class).map { |k, v| [k, v.count] }]
73
+ "Doing batch with the following tally of event types: #{tally}"
74
+ end
75
+ post('batch', { batch: batch }, {}, RETRY_MANY)
76
+ end
77
+
78
+ private
79
+
80
+ # see +Sqreen::Session.post+
81
+ def post(*args)
82
+ @post_proc[*args]
83
+ end
84
+
85
+ def event_kind(event)
86
+ case event
87
+ when Sqreen::RemoteException then 'sqreen_exception'
88
+ when Sqreen::Attack then 'attack'
89
+ when Sqreen::RequestRecord then 'request_record'
90
+ end
91
+ end
92
+ end
93
+
94
+ module EventToHash
95
+ class << self
96
+ # @param attack [Sqreen::Attack]
97
+ def convert_attack(attack)
98
+ payload = attack.payload
99
+ res = {}
100
+ rule_p = payload['rule']
101
+ request_p = payload['request']
102
+ res[:rule_name] = rule_p['name'] if rule_p && rule_p['name']
103
+ res[:rulespack_id] = rule_p['rulespack_id'] if rule_p && rule_p['rulespack_id']
104
+ res[:test] = rule_p['test'] if rule_p && rule_p['test']
105
+ res[:infos] = payload['infos'] if payload['infos']
106
+ res[:time] = attack.time
107
+ res[:client_ip] = request_p[:addr] if request_p && request_p[:addr]
108
+ res[:request] = request_p if request_p
109
+ res[:params] = payload['params'] if payload['params']
110
+ res[:context] = payload['context'] if payload['context']
111
+ res[:headers] = payload['headers'] if payload['headers']
112
+ res
113
+ end
114
+
115
+ # @param [Sqreen::RequestRecord] rr
116
+ def convert_request_record(rr)
117
+ res = { :version => '20171208' }
118
+ payload = rr.payload
119
+
120
+ if payload[:observed]
121
+ res[:observed] = payload[:observed].dup
122
+ rulespack = nil
123
+ if rr.observed[:attacks]
124
+ res[:observed][:attacks] = rr.observed[:attacks].map do |att|
125
+ natt = att.dup
126
+ [:attack_type, :block].each { |k| natt.delete(k) } # signals stuff
127
+ rulespack = natt.delete(:rulespack_id) || rulespack
128
+ natt
129
+ end
130
+ end
131
+ if rr.observed[:sqreen_exceptions]
132
+ res[:observed][:sqreen_exceptions] = rr.observed[:sqreen_exceptions].map do |exc|
133
+ nex = exc.dup
134
+ excp = nex.delete(:exception)
135
+ if excp
136
+ nex[:message] = excp.message
137
+ nex[:klass] = excp.class.name
138
+ end
139
+ rulespack = nex.delete(:rulespack_id) || rulespack
140
+ nex
141
+ end
142
+ end
143
+ res[:rulespack_id] = rulespack unless rulespack.nil?
144
+ if rr.observed[:observations]
145
+ res[:observed][:observations] = rr.observed[:observations].map do |cat, key, value, time|
146
+ { :category => cat, :key => key, :value => value, :time => time }
147
+ end
148
+ end
149
+ if rr.observed[:sdk] # rubocop:disable Style/IfUnlessModifier
150
+ res[:observed][:sdk] = rr.processed_sdk_calls
151
+ end
152
+ end
153
+ res[:local] = payload['local'] if payload['local']
154
+ if payload['request']
155
+ res[:request] = payload['request'].dup
156
+ res[:client_ip] = res[:request].delete(:client_ip) if res[:request][:client_ip]
157
+ else
158
+ res[:request] = {}
159
+ end
160
+ if payload['response']
161
+ res[:response] = payload['response'].dup
162
+ else
163
+ res[:response] = {}
164
+ end
165
+
166
+ res[:request][:parameters] = payload['params'] if payload['params']
167
+ res[:request][:headers] = payload['headers'] if payload['headers']
168
+
169
+ res = Sqreen::EncodingSanitizer.sanitize(res)
170
+
171
+ if rr.redactor
172
+ res[:request], redacted = rr.redactor.redact(res[:request])
173
+ redacted = redacted.uniq
174
+ if redacted.any? && res[:observed] && res[:observed][:attacks]
175
+ res[:observed][:attacks] = WafRedactions.redact_attacks!(res[:observed][:attacks], redacted)
176
+ end
177
+ if redacted.any? && res[:observed] && res[:observed][:sqreen_exceptions]
178
+ res[:observed][:sqreen_exceptions] = WafRedactions.redact_exceptions!(res[:observed][:sqreen_exceptions], redacted)
179
+ end
180
+ end
181
+
182
+ res
183
+ end
184
+
185
+ # @param exception_evt [Sqreen::RemoteException]
186
+ def convert_exception(exception_evt)
187
+ payload = exception_evt.payload
188
+ exception = payload['exception']
189
+ ev = {
190
+ :klass => exception.class.name,
191
+ :message => exception.message,
192
+ :params => payload['request_params'],
193
+ :time => payload['time'],
194
+ :infos => {
195
+ :client_ip => payload['client_ip'],
196
+ },
197
+ :request => payload['request_infos'],
198
+ :headers => payload['headers'],
199
+ :rule_name => payload['rule_name'],
200
+ :rulespack_id => payload['rulespack_id'],
201
+ }
202
+
203
+ ev[:infos].merge!(payload['infos']) if payload['infos']
204
+ return ev unless exception.backtrace
205
+ ev[:context] = { :backtrace => exception.backtrace.map(&:to_s) }
206
+ ev
207
+ end
208
+
209
+ # @param [Sqreen::AggregatedMetric] agg_metric
210
+ def convert_agg_metric(agg_metric)
211
+ {
212
+ name: agg_metric.name,
213
+ observation: agg_metric.data,
214
+ start: agg_metric.start,
215
+ finish: agg_metric.finish,
216
+ }
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,49 @@
1
+ # typed: ignore
2
+
3
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
+ # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
+
6
+ module Sqreen
7
+ module Legacy
8
+ module WafRedactions
9
+ class << self
10
+ def redact_attacks!(attacks, values)
11
+ return attacks if values.empty?
12
+
13
+ values = values.map { |v| v.downcase if v.is_a?(String) }
14
+
15
+ attacks.each do |e|
16
+ next(e) unless e[:infos]
17
+ next(e) unless e[:infos][:waf_data]
18
+
19
+ parsed = JSON.parse(e[:infos][:waf_data])
20
+ redacted = parsed.each do |w|
21
+ next unless (filters = w['filter'])
22
+
23
+ filters.each do |f|
24
+ next unless (v = f['resolved_value'])
25
+ next unless values.include?(v.downcase)
26
+
27
+ f['match_status'] = SensitiveDataRedactor::MASK
28
+ f['resolved_value'] = SensitiveDataRedactor::MASK
29
+ end
30
+ end
31
+ e[:infos][:waf_data] = JSON.dump(redacted)
32
+ end
33
+ end
34
+
35
+ # see https://github.com/sqreen/TechDoc/blob/master/content/specs/spec000022-waf-data-sanitization.md#changes-to-the-agents
36
+ def redact_exceptions!(exceptions, values)
37
+ return exceptions if values.empty?
38
+
39
+ exceptions.each do |e|
40
+ next(e) unless e[:infos]
41
+ next(e) unless e[:infos][:waf]
42
+
43
+ e[:infos][:waf].delete(:args)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -23,6 +23,6 @@ module Sqreen::Log::Loggable
23
23
  end
24
24
 
25
25
  def logger
26
- @logger || self.class.logger
26
+ @logger || singleton_class.logger
27
27
  end
28
28
  end
@@ -12,6 +12,9 @@ module Sqreen
12
12
  FINISH_KEY = 'finish'.freeze
13
13
  # Base interface for a metric
14
14
  class Base
15
+ attr_accessor :name, :period # for signals serialization
16
+ attr_accessor :rule # optional
17
+
15
18
  def initialize(_opts={})
16
19
  @sample = nil
17
20
  end
@@ -3,6 +3,7 @@
3
3
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
+ require 'sqreen/aggregated_metric'
6
7
  require 'sqreen/metrics'
7
8
  require 'sqreen/mono_time'
8
9
  require 'sqreen/metrics_store/unknown_metric'
@@ -30,8 +31,9 @@ module Sqreen
30
31
 
31
32
  # Definition contains a name,period and aggregate at least
32
33
  # @param definition [Hash] a metric definition
34
+ # @param rule [RuleCB] the rule associated with this metric, if any
33
35
  # @param mklass [Object] Override metric object (used in testing)
34
- def create_metric(definition, mklass = nil)
36
+ def create_metric(definition, rule = nil, mklass = nil)
35
37
  name = definition[NAME_KEY]
36
38
  kind = definition[KIND_KEY]
37
39
  klass = valid_metric(kind, name)
@@ -43,6 +45,9 @@ module Sqreen
43
45
  definition[PERIOD_KEY],
44
46
  nil # Start
45
47
  ]
48
+ metric.name = name
49
+ metric.rule = rule
50
+ metric.period = definition[PERIOD_KEY]
46
51
  metric
47
52
  end
48
53
 
@@ -50,7 +55,7 @@ module Sqreen
50
55
  @metrics.key?(name)
51
56
  end
52
57
 
53
- # @params at [Time] when is the store emptied
58
+ # @param at [Time] when is the store emptied
54
59
  def update(name, at, key, value)
55
60
  metric, period, start = @metrics[name]
56
61
  raise UnregisteredMetric, "Unknown metric #{name}" unless metric
@@ -59,7 +64,7 @@ module Sqreen
59
64
  end
60
65
 
61
66
  # Drains every metrics and returns the store content
62
- # @params at [Time] when is the store emptied
67
+ # @param at [Time] when is the store emptied
63
68
  def publish(flush = true, at = Sqreen.time)
64
69
  @metrics.each do |name, (_, period, start)|
65
70
  next_sample(name, at) if flush || !start.nil? && (start + period) < at
@@ -75,15 +80,20 @@ module Sqreen
75
80
  metric = @metrics[name][0]
76
81
  r = metric.next_sample(at)
77
82
  @metrics[name][2] = at # new start
78
- if r
79
- r[NAME_KEY] = name
80
- obs = r[Metric::OBSERVATION_KEY]
81
- start_of_mono = Time.now.utc - Sqreen.time
82
- r[Metric::START_KEY] = start_of_mono + r[Metric::START_KEY]
83
- r[Metric::FINISH_KEY] = start_of_mono + r[Metric::FINISH_KEY]
84
- @store << r if obs && (!obs.respond_to?(:empty?) || !obs.empty?)
85
- end
86
- r
83
+ return unless r
84
+
85
+ r[NAME_KEY] = name
86
+ obs = r[Metric::OBSERVATION_KEY]
87
+ return unless obs && (!obs.respond_to?(:empty?) || !obs.empty?)
88
+ start_of_mono = Time.now.utc - Sqreen.time
89
+
90
+ agg = AggregatedMetric.new
91
+ agg.metric = metric
92
+ agg.rule = agg.metric.rule
93
+ agg.start = start_of_mono + r[Metric::START_KEY]
94
+ agg.finish = start_of_mono + r[Metric::FINISH_KEY]
95
+ agg.data = obs
96
+ @store << agg
87
97
  end
88
98
 
89
99
  def valid_metric(kind, name)
@@ -122,10 +122,16 @@ module Sqreen
122
122
  attr_reader :metrics_store
123
123
  attr_reader :period
124
124
 
125
- def ensure_metric(metric_name)
125
+ def ensure_metric(metric_name, rule = nil)
126
126
  return if metrics_store.metric?(metric_name)
127
127
  metrics_store.create_metric(
128
- 'name' => metric_name, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_opts
128
+ {
129
+ 'name' => metric_name,
130
+ 'period' => period,
131
+ 'kind' => 'Binning',
132
+ 'options' => @perf_metric_opts,
133
+ },
134
+ rule
129
135
  )
130
136
  end
131
137
 
@@ -135,13 +135,15 @@ module Sqreen
135
135
  return nil
136
136
  end
137
137
 
138
+ rule_cb = cb_class.new(instr_class, instr_method, hash_rule)
139
+
138
140
  if metrics_store
139
141
  (hash_rule[Attrs::METRICS] || []).each do |metric|
140
- metrics_store.create_metric(metric)
142
+ metrics_store.create_metric(metric, rule_cb)
141
143
  end
142
144
  end
143
145
 
144
- cb_class.new(instr_class, instr_method, hash_rule)
146
+ rule_cb
145
147
  rescue => e
146
148
  rule_name = nil
147
149
  rulespack_id = nil