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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/sqreen/agent_message.rb +20 -0
- data/lib/sqreen/aggregated_metric.rb +25 -0
- data/lib/sqreen/ca.crt +24 -0
- data/lib/sqreen/configuration.rb +10 -4
- data/lib/sqreen/deliveries/batch.rb +4 -1
- data/lib/sqreen/deliveries/simple.rb +4 -0
- data/lib/sqreen/endpoint_testing.rb +184 -0
- data/lib/sqreen/event.rb +7 -5
- data/lib/sqreen/events/attack.rb +23 -18
- data/lib/sqreen/events/remote_exception.rb +0 -22
- data/lib/sqreen/events/request_record.rb +15 -70
- data/lib/sqreen/frameworks/request_recorder.rb +13 -2
- data/lib/sqreen/kit/signals/specialized/aggregated_metric.rb +72 -0
- data/lib/sqreen/kit/signals/specialized/attack.rb +57 -0
- data/lib/sqreen/kit/signals/specialized/binning_metric.rb +76 -0
- data/lib/sqreen/kit/signals/specialized/http_trace.rb +26 -0
- data/lib/sqreen/kit/signals/specialized/sdk_track_call.rb +50 -0
- data/lib/sqreen/kit/signals/specialized/sqreen_exception.rb +57 -0
- data/lib/sqreen/legacy/old_event_submission_strategy.rb +221 -0
- data/lib/sqreen/legacy/waf_redactions.rb +49 -0
- data/lib/sqreen/log/loggable.rb +1 -1
- data/lib/sqreen/metrics/base.rb +3 -0
- data/lib/sqreen/metrics_store.rb +22 -12
- data/lib/sqreen/performance_notifications/binned_metrics.rb +8 -2
- data/lib/sqreen/rules.rb +4 -2
- data/lib/sqreen/rules/not_found_cb.rb +2 -0
- data/lib/sqreen/rules/rule_cb.rb +2 -0
- data/lib/sqreen/rules/waf_cb.rb +13 -10
- data/lib/sqreen/runner.rb +75 -8
- data/lib/sqreen/sensitive_data_redactor.rb +19 -31
- data/lib/sqreen/session.rb +51 -43
- data/lib/sqreen/signals/conversions.rb +283 -0
- data/lib/sqreen/signals/http_trace_redaction.rb +111 -0
- data/lib/sqreen/signals/signals_submission_strategy.rb +78 -0
- data/lib/sqreen/version.rb +1 -1
- data/lib/sqreen/weave/legacy/instrumentation.rb +7 -7
- metadata +50 -6
- data/lib/sqreen/backport.rb +0 -9
- data/lib/sqreen/backport/clock_gettime.rb +0 -74
- 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
|
data/lib/sqreen/log/loggable.rb
CHANGED
data/lib/sqreen/metrics/base.rb
CHANGED
data/lib/sqreen/metrics_store.rb
CHANGED
@@ -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
|
-
# @
|
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
|
-
# @
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
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
|
|
data/lib/sqreen/rules.rb
CHANGED
@@ -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
|
-
|
146
|
+
rule_cb
|
145
147
|
rescue => e
|
146
148
|
rule_name = nil
|
147
149
|
rulespack_id = nil
|