sqreen 1.23.0 → 1.24.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6b62b53864420b27411824bf5d51c80949a7e98357edd128e64ee0e77039476
4
- data.tar.gz: 85a9d762af8063c9e42978100907561397bd00e8278978b9a67f01e5fe52670f
3
+ metadata.gz: dea2ca186e8470cf1cec16ca8ab33156c1002d28004c3a68c2296e2ec5d9b7ed
4
+ data.tar.gz: 2722948150517f7f7c29c73fa9f49acac18266e2cc286e70907b8ff91ba535d1
5
5
  SHA512:
6
- metadata.gz: ca4c03f84749101fe1b2235ead061cf597bfb0a4d08d95d9cd3b24cdb9ca77eea3928fa00ce4db22219acea02c93539251f384002ccb3e4fffd1c484d54ad938
7
- data.tar.gz: 02e372ee65122d783685df0756bf069a30225348239703465a7a20f751865bc5ab79ba9c0c99dafc1551d1812532669089681dc71b8375cb4791df749f9d3977
6
+ metadata.gz: 1ecf19e0200c0c1d9012f8140996e5c1918be31a9dcca081f302cdf05e818fe5137199489d30486eca18b814e6d98bfeda5efb1ee8103efe08d158fca5f12717
7
+ data.tar.gz: 6f01d3300a326c1504917cfe2e1a25f46020f936a5400d6b347a9a1d1001f616912fb58bffe466539ecb47ef259e233a9cc5652f6da2a1739e5c4cdd5f01949b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ## 1.24.2
2
+
3
+ * Fix kwargs for rule callbacks on Ruby 3+
4
+ * Fix properties propagation for custom events
5
+ * Fix Devise key type mismatch for signup
6
+
7
+ ## 1.24.1
8
+
9
+ * Add Datadog trace keeping through sampling
10
+ * Improve Datadog correlation compatibility with Sinatra
11
+ * Improve attack event correlation with Datadog spans
12
+
13
+ ## 1.24.0
14
+
15
+ * Add Sqreen event correlation with Datadog traces
16
+
17
+ ## 1.23.2
18
+
19
+ * Fix compatibility with NewRelic for attack events
20
+ * Fix incorrect rule rejection despite all signature checks individually passing
21
+
22
+ ## 1.23.1
23
+
24
+ * Improve compatibility with gems such as puma and graphql on Ruby 3.0
25
+
1
26
  ## 1.23.0
2
27
 
3
28
  * Implement GraphQL support
data/bin/sqreen ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ def logger
4
+ @logger ||= Logger.new(STDOUT, level: :debug)
5
+ end
6
+
7
+ def verify(rules)
8
+ verifier = Sqreen::SqreenSignedVerifier.new
9
+
10
+ invalid_rules = rules.reject do |rule|
11
+ valid = verifier.verify(rule)
12
+
13
+ if valid
14
+ logger.debug { "rule: #{rule['name']} signed: true result: ok" }
15
+ else
16
+ logger.error { "rule: #{rule['name']} singed: true result: fail" }
17
+ end
18
+
19
+ valid
20
+ end
21
+
22
+ if invalid_rules.any?
23
+ logger.error { "weave: instrument status: abort reason: signature result: fail" }
24
+ raise Sqreen::Exception, "Signature error: rules: #{invalid_rules.map { |r| r['name'] }.inspect}"
25
+ else
26
+ logger.info { "weave: instrument rules: signed result: ok" }
27
+ end
28
+ end
29
+
30
+ def check_signature(file)
31
+ require 'json'
32
+ require 'logger'
33
+ require 'sqreen/sqreen_signed_verifier'
34
+
35
+ content = File.open(file, 'rb', &:read)
36
+ json = JSON.parse(content)
37
+
38
+ p verify(json)
39
+ end
40
+
41
+ case ARGV[0]
42
+ when 'check-signature' then check_signature(ARGV[1]) || exit(1)
43
+ end
@@ -56,6 +56,8 @@ module Sqreen
56
56
  :default => nil },
57
57
  { :env => :SQREEN_RULES_SIGNATURE, :name => :rules_verify_signature,
58
58
  :default => true },
59
+ { :env => :SQREEN_RULES_DUMP, :name => :rules_dump,
60
+ :default => false },
59
61
  { :env => :SQREEN_LOG_LEVEL, :name => :log_level,
60
62
  :default => 'INFO', :choice => %w[UNKNOWN FATAL ERROR WARN INFO DEBUG] },
61
63
  { :env => :SQREEN_LOG_LOCATION, :name => :log_location,
@@ -19,7 +19,7 @@ module Sqreen
19
19
  def ignore_sqreen_exceptions
20
20
  return unless required?
21
21
 
22
- NewRelic::Agent::Agent.instance.error_collector.ignore(['Sqreen::AttackBlocked'])
22
+ ::NewRelic::Agent::Agent.instance.error_collector.ignore(['Sqreen::AttackBlocked'])
23
23
  rescue ::Exception => e # rubocop:disable Lint/RescueException
24
24
  Sqreen.log.warn "Failed ignoring AttackBlocked on NewRelic: #{e.inspect}"
25
25
  end
@@ -61,6 +61,26 @@ module Sqreen
61
61
  u.append(p)
62
62
  end
63
63
  end
64
+
65
+ insert_datadog_middleware(builder, *args, &block)
66
+ end
67
+
68
+ def insert_datadog_middleware(builder, *args, &block)
69
+ return unless defined?(Datadog) && Datadog.respond_to?(:configuration) && Datadog.configuration.instrumented_integrations.key?(:sinatra)
70
+
71
+ Datadog.configure do |c|
72
+ sinatra_config = Datadog.configuration[:sinatra]
73
+
74
+ c.use(
75
+ :rack,
76
+ service_name: sinatra_config[:service_name],
77
+ distributed_tracing: sinatra_config[:distributed_tracing],
78
+ ) unless Datadog.configuration.instrumented_integrations.key?(:rack)
79
+ end
80
+
81
+ insert_middleware(builder, Datadog::Contrib::Rack::TraceMiddleware, args, block) do |p, u|
82
+ u.insert(0, p)
83
+ end
64
84
  end
65
85
 
66
86
  def wrap_middleware(middleware, *args, &block)
@@ -63,6 +63,14 @@ module Sqreen
63
63
  payload['context']['backtrace']
64
64
  end
65
65
 
66
+ def datadog_trace_id
67
+ payload['context']['datadog_trace_id']
68
+ end
69
+
70
+ def datadog_span_id
71
+ payload['context']['datadog_span_id']
72
+ end
73
+
66
74
  def enqueue
67
75
  Sqreen.queue.push(self)
68
76
  end
@@ -173,7 +173,18 @@ module Sqreen
173
173
  :remote_port => req.env['REMOTE_PORT'],
174
174
  :remote_ip => remote_addr,
175
175
  :client_ip => client_ip,
176
- }
176
+ }.tap do |h|
177
+ h.merge!(
178
+ :datadog_trace_id => datadog_span.trace_id,
179
+ :datadog_span_id => datadog_span.span_id,
180
+ ) if datadog_span
181
+ end
182
+ end
183
+
184
+ def datadog_span
185
+ return unless defined?(Datadog) && (tracer = Datadog.tracer)
186
+
187
+ tracer.active_span
177
188
  end
178
189
 
179
190
  def response_infos
@@ -4,9 +4,8 @@
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
6
  require 'sqreen/graft'
7
- require 'sqreen/graft/call'
8
- require 'sqreen/graft/callback'
9
7
  require 'sqreen/graft/hook_point'
8
+ require 'sqreen/graft/callback'
10
9
 
11
10
  module Sqreen
12
11
  module Graft
@@ -124,299 +123,12 @@ module Sqreen
124
123
  @raised = []
125
124
  @ensured = []
126
125
  end
127
-
128
- def self.wrapper(hook)
129
- timed_hooks_proc = proc do |t|
130
- if (request = Thread.current[:sqreen_http_request])
131
- request[:timed_hooks] << t if request[:timed_level] >= 1
132
- end
133
- end
134
- timed_callbacks_proc = proc do |t|
135
- if (request = Thread.current[:sqreen_http_request])
136
- request[:timed_callbacks] << t if request[:timed_level] >= 1
137
- end
138
- end
139
-
140
- Proc.new do |*args, &block|
141
- request = Thread.current[:sqreen_http_request]
142
-
143
- if Thread.current[:sqreen_hook_entered]
144
- if hook.point.super?
145
- return super(*args, &block)
146
- else
147
- return hook.point.apply(self, 'sqreen_hook', *args, &block)
148
- end
149
- end
150
-
151
- if request && request[:time_budget_expended] && (hook.before + hook.after + hook.raised + hook.ensured).none?(&:mandatory)
152
- if request[:timed_level] >= 2
153
- begin
154
- request[:skipped_callbacks].concat(hook.before)
155
-
156
- if hook.point.super?
157
- return super(*args, &block)
158
- else
159
- return hook.point.apply(self, 'sqreen_hook', *args, &block)
160
- end
161
- rescue ::Exception # rubocop:disable Lint/RescueException
162
- request[:skipped_callbacks].concat(hook.raised)
163
- raise
164
- else
165
- request[:skipped_callbacks].concat(hook.after)
166
- ensure
167
- request[:skipped_callbacks].concat(hook.ensured)
168
- end
169
- else
170
- if hook.point.super? # rubocop:disable Style/IfInsideElse
171
- return super(*args, &block)
172
- else
173
- return hook.point.apply(self, 'sqreen_hook', *args, &block)
174
- end
175
- end
176
- end
177
-
178
- hook_point_super = hook.point.super?
179
- logger = Sqreen::Graft.logger
180
- logger_debug = Sqreen::Graft.logger.debug?
181
-
182
- Timer.new(hook.point, &timed_hooks_proc).measure do |chrono|
183
- # budget implies request
184
- # TODO: make budget depend on a generic context (currently "request")
185
- budget = request[:time_budget] if request
186
- if budget
187
- budget_threshold = request[:time_budget_threshold]
188
- budget_ratio = request[:time_budget_ratio]
189
- sqreen_timer = request[:sqreen_timer]
190
- request_timer = request[:request_timer]
191
- end
192
-
193
- hooked_call = HookedCall.new(self, args)
194
-
195
- begin
196
- begin
197
- sqreen_timer.start if budget
198
- Thread.current[:sqreen_hook_entered] = true
199
-
200
- # TODO: make Call have #ball to throw by cb
201
- # TODO: can Call be the ball? r = catch(Call.new, &c)
202
- # TODO: is catch return value a Call? a #dispatch?
203
- # TODO: make before/after/raised return a CallbackCollection << Array (or extend with module)
204
- # TODO: add CallbackCollection#each_with_call(instance, args) { |call| ... } ?
205
- # TODO: HookCall x CallbackCollection#each_with_call x Flow
206
- # TODO: TimedHookCall TimedCallbackCall
207
- # TODO: TimeBoundHookCall TimeBoundCallbackCall TimeBoundFlow?
208
-
209
- request_elapsed = request_timer.elapsed if budget
210
-
211
- hook.before.each do |c|
212
- next if c.ignore && c.ignore.call
213
-
214
- if budget && !c.mandatory
215
- sqreen_elapsed = sqreen_timer.elapsed
216
- if budget_ratio && !request[:time_budget_expended]
217
- fixed_budget = budget_threshold
218
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
219
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
220
- else
221
- remaining = budget_threshold - sqreen_elapsed
222
- end
223
- unless remaining > 0
224
- request[:skipped_callbacks] << c
225
- request[:time_budget_expended] = true
226
- next
227
- end
228
- end
229
-
230
- flow = catch(Ball.new) do |ball|
231
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
232
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passed), ball)
233
- end
234
- end
235
-
236
- next unless c.flow && flow.is_a?(Flow)
237
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
238
- hooked_call.args_pass = flow.args and hooked_call.args_passing = true if flow.args?
239
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
240
- break if flow.break?
241
- end unless hook.disabled?
242
- rescue StandardError => e
243
- Sqreen::Weave.logger.debug { "exception:#{e.class} when:before message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
244
- Sqreen::RemoteException.record(e) if Sqreen.queue
245
- end
246
-
247
- raise hooked_call.raise if hooked_call.raising
248
- return hooked_call.return if hooked_call.returning
249
- ensure
250
- Thread.current[:sqreen_hook_entered] = false
251
- sqreen_timer.stop if budget
252
- end unless hook.before.empty?
253
-
254
- begin
255
- chrono.ignore do
256
- if hook_point_super
257
- hooked_call.returned = super(*(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
258
- else
259
- hooked_call.returned = hook.point.apply(hooked_call.instance, 'sqreen_hook', *(hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed), &block)
260
- end
261
- end
262
- rescue ::Exception => e # rubocop:disable Lint/RescueException
263
- begin
264
- sqreen_timer.start if budget
265
- Thread.current[:sqreen_hook_entered] = true
266
- hooked_call.raised = e
267
-
268
- logger.debug { "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} exception:#{e}" } if logger_debug
269
-
270
- # TODO: early escape causes raise too early then caught by ensure which mistakenly reports legit errors to sqreen
271
- # raise if hook.raised.empty?
272
-
273
- request_elapsed = request_timer.elapsed if budget
274
-
275
- hook.raised.each do |c|
276
- next if c.ignore && c.ignore.call
277
-
278
- if budget && !c.mandatory
279
- sqreen_elapsed = sqreen_timer.elapsed
280
- if budget_ratio && !request[:time_budget_expended]
281
- fixed_budget = budget_threshold
282
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
283
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
284
- else
285
- remaining = budget_threshold - sqreen_elapsed
286
- end
287
- unless remaining > 0
288
- request[:skipped_callbacks] << c
289
- request[:time_budget_expended] = true
290
- next
291
- end
292
- end
293
-
294
- flow = catch(Ball.new) do |ball|
295
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
296
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, hooked_call.raised), ball)
297
- end
298
- end
299
-
300
- next unless c.flow && flow.is_a?(Flow)
301
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
302
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
303
- hooked_call.retrying = true if flow.retry?
304
- break if flow.break?
305
- end unless hook.disabled?
306
- rescue StandardError => e
307
- Sqreen::Weave.logger.debug { "exception:#{e.class} when:raised message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
308
- Sqreen::RemoteException.record(e) if Sqreen.queue
309
- end
310
-
311
- retry if hooked_call.retrying
312
- raise hooked_call.raise if hooked_call.raising
313
- return hooked_call.return if hooked_call.returning
314
- raise
315
- else
316
- begin
317
- sqreen_timer.start if budget
318
- Thread.current[:sqreen_hook_entered] = true
319
-
320
- # TODO: hooked_call.returning should be always false here?
321
- return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.after.empty?
322
-
323
- request_elapsed = request_timer.elapsed if budget
324
-
325
- hook.after.each do |c|
326
- next if c.ignore && c.ignore.call
327
-
328
- if budget && !c.mandatory
329
- sqreen_elapsed = sqreen_timer.elapsed
330
- if budget_ratio && !request[:time_budget_expended]
331
- fixed_budget = budget_threshold
332
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
333
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
334
- else
335
- remaining = budget_threshold - sqreen_elapsed
336
- end
337
- unless remaining > 0
338
- request[:skipped_callbacks] << c
339
- request[:time_budget_expended] = true
340
- next
341
- end
342
- end
343
-
344
- flow = catch(Ball.new) do |ball|
345
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
346
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
347
- end
348
- end
349
-
350
- next unless c.flow && flow.is_a?(Flow)
351
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
352
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
353
- break if flow.break?
354
- end unless hook.disabled?
355
- rescue StandardError => e
356
- Sqreen::Weave.logger.debug { "exception:#{e.class} when:after message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
357
- Sqreen::RemoteException.record(e) if Sqreen.queue
358
- end
359
-
360
- raise hooked_call.raise if hooked_call.raising
361
- return hooked_call.returning ? hooked_call.return : hooked_call.returned
362
- ensure
363
- begin
364
- # TODO: sqreen_timer.start if someone has thrown?
365
- # TODO: sqreen_timer.stop at end of rescue+else?
366
- # TODO: Thread.current[:sqreen_hook_entered] = true if neither rescue nor else ie thrown?
367
- # TODO: Thread.current[:sqreen_hook_entered] = false at end of rescue+else? (risky?)
368
-
369
- # TODO: uniform early bail out? (but don't forget sqreen_hook_entered and sqreen_timer)
370
- # return hooked_call.returning ? hooked_call.return : hooked_call.returned if hook.ensured.empty?
371
-
372
- # done at either rescue or else
373
- # request_elapsed = request_timer.elapsed if budget # && !hook.ensured.empty?
374
-
375
- hook.ensured.each do |c|
376
- next if c.ignore && c.ignore.call
377
-
378
- if budget && !c.mandatory
379
- sqreen_elapsed = sqreen_timer.elapsed
380
- if budget_ratio && !request[:time_budget_expended]
381
- fixed_budget = budget_threshold
382
- proportional_budget = (request_elapsed - sqreen_elapsed) * budget_ratio
383
- remaining = (fixed_budget > proportional_budget ? fixed_budget : proportional_budget) - sqreen_elapsed
384
- else
385
- remaining = budget_threshold - sqreen_elapsed
386
- end
387
- unless remaining > 0
388
- request[:skipped_callbacks] << c
389
- request[:time_budget_expended] = true
390
- next
391
- end
392
- end
393
-
394
- flow = catch(Ball.new) do |ball|
395
- Timer.new(c.name, &timed_callbacks_proc).measure(ignore: chrono) do
396
- c.call(CallbackCall.new(c, remaining, hooked_call.instance, hooked_call.args_passing ? hooked_call.args_pass : hooked_call.args_passed, nil, hooked_call.returned), ball)
397
- end
398
- end
399
-
400
- next unless c.flow && flow.is_a?(Flow)
401
- hooked_call.raise = flow.raise and hooked_call.raising = true if flow.raise?
402
- hooked_call.return = flow.return and hooked_call.returning = true if flow.return?
403
- break if flow.break?
404
- end unless hook.ensured.empty? || hook.disabled?
405
-
406
- Thread.current[:sqreen_hook_entered] = false
407
- sqreen_timer.stop if budget
408
- rescue StandardError => e
409
- Sqreen::Weave.logger.debug { "exception:#{e.class} when:ensured message:'#{e.message}' location:\"#{e.backtrace[0]}\"" }
410
- Sqreen::RemoteException.record(e) if Sqreen.queue
411
- end
412
-
413
- # TODO: should we run the following?
414
- # raise hooked_call.raise if hooked_call.raising
415
- # return hooked_call.returning ? hooked_call.return : hooked_call.returned
416
- end
417
- end # chrono
418
- end
419
- end
420
126
  end
421
127
  end
422
128
  end
129
+
130
+ if RUBY_VERSION =~ /^2\./
131
+ load File.join(__dir__, 'hook.ruby_2.rb')
132
+ else
133
+ load File.join(__dir__, 'hook.ruby_3.rb')
134
+ end