sqreen 1.23.0 → 1.24.2

Sign up to get free protection for your applications and to get access to all the features.
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