sqreen 1.18.2-java → 1.19.0-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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +35 -0
  3. data/LICENSE +3 -0
  4. data/lib/sqreen.rb +2 -0
  5. data/lib/sqreen/actions.rb +13 -337
  6. data/lib/sqreen/actions/actions_index.rb +16 -0
  7. data/lib/sqreen/actions/base.rb +104 -0
  8. data/lib/sqreen/actions/block_ip.rb +34 -0
  9. data/lib/sqreen/actions/block_user.rb +46 -0
  10. data/lib/sqreen/actions/ip_range_indexed_action_class.rb +16 -0
  11. data/lib/sqreen/actions/ip_ranges_index.rb +57 -0
  12. data/lib/sqreen/actions/redirect_ip.rb +42 -0
  13. data/lib/sqreen/actions/redirect_user.rb +47 -0
  14. data/lib/sqreen/actions/repository.rb +43 -0
  15. data/lib/sqreen/actions/unknown_action_type.rb +20 -0
  16. data/lib/sqreen/actions/user_action_class.rb +16 -0
  17. data/lib/sqreen/actions/users_index.rb +35 -0
  18. data/lib/sqreen/agent.rb +6 -2
  19. data/lib/sqreen/attack_blocked.rb +19 -0
  20. data/lib/sqreen/backport.rb +2 -0
  21. data/lib/sqreen/backport/clock_gettime.rb +74 -0
  22. data/lib/sqreen/backport/original_name.rb +2 -0
  23. data/lib/sqreen/binding_accessor.rb +11 -102
  24. data/lib/sqreen/binding_accessor/path_elem.rb +10 -0
  25. data/lib/sqreen/binding_accessor/transforms.rb +114 -0
  26. data/lib/sqreen/call_countable.rb +2 -0
  27. data/lib/sqreen/capped_queue.rb +4 -0
  28. data/lib/sqreen/{callbacks.rb → cb.rb} +3 -53
  29. data/lib/sqreen/{callback_tree.rb → cb_tree.rb} +4 -2
  30. data/lib/sqreen/condition_evaluator.rb +24 -5
  31. data/lib/sqreen/conditionable.rb +2 -0
  32. data/lib/sqreen/configuration.rb +19 -0
  33. data/lib/sqreen/context.rb +2 -0
  34. data/lib/sqreen/default_cb.rb +22 -0
  35. data/lib/sqreen/deferred_logger.rb +65 -0
  36. data/lib/sqreen/deliveries.rb +12 -0
  37. data/lib/sqreen/deliveries/batch.rb +9 -1
  38. data/lib/sqreen/deliveries/simple.rb +7 -0
  39. data/lib/sqreen/dependency.rb +3 -1
  40. data/lib/sqreen/dependency/detector.rb +22 -14
  41. data/lib/sqreen/dependency/libsqreen.rb +32 -0
  42. data/lib/sqreen/dependency/new_relic.rb +2 -0
  43. data/lib/sqreen/dependency/rack.rb +10 -5
  44. data/lib/sqreen/dependency/rails.rb +8 -0
  45. data/lib/sqreen/dependency/sentry.rb +2 -0
  46. data/lib/sqreen/dependency/sinatra.rb +58 -14
  47. data/lib/sqreen/encoding_sanitizer.rb +2 -0
  48. data/lib/sqreen/error_handling_middleware.rb +32 -0
  49. data/lib/sqreen/event.rb +4 -0
  50. data/lib/sqreen/events/attack.rb +4 -0
  51. data/lib/sqreen/events/remote_exception.rb +2 -0
  52. data/lib/sqreen/events/request_record.rb +13 -56
  53. data/lib/sqreen/exception.rb +11 -40
  54. data/lib/sqreen/formatter_with_tid.rb +47 -0
  55. data/lib/sqreen/framework_cb.rb +30 -0
  56. data/lib/sqreen/frameworks.rb +9 -0
  57. data/lib/sqreen/frameworks/generic.rb +22 -2
  58. data/lib/sqreen/frameworks/rails.rb +3 -0
  59. data/lib/sqreen/frameworks/rails3.rb +2 -0
  60. data/lib/sqreen/frameworks/request_recorder.rb +5 -0
  61. data/lib/sqreen/frameworks/sinatra.rb +4 -0
  62. data/lib/sqreen/frameworks/sqreen_test.rb +4 -0
  63. data/lib/sqreen/graft.rb +12 -0
  64. data/lib/sqreen/graft/call.rb +150 -0
  65. data/lib/sqreen/{dependency → graft}/callback.rb +12 -4
  66. data/lib/sqreen/graft/hook.rb +316 -0
  67. data/lib/sqreen/{dependency → graft}/hook_point.rb +152 -33
  68. data/lib/sqreen/graft/hook_point_error.rb +10 -0
  69. data/lib/sqreen/invalid_signature_exception.rb +10 -0
  70. data/lib/sqreen/js.rb +11 -0
  71. data/lib/sqreen/js/call_context.rb +12 -0
  72. data/lib/sqreen/js/context_pool.rb +62 -0
  73. data/lib/sqreen/js/exec_js_runnable.rb +22 -0
  74. data/lib/sqreen/js/execjs_adapter.rb +8 -47
  75. data/lib/sqreen/js/executable_js.rb +14 -0
  76. data/lib/sqreen/js/js_service.rb +4 -22
  77. data/lib/sqreen/js/js_service_adapter.rb +20 -0
  78. data/lib/sqreen/js/mini_racer_adapter.rb +8 -180
  79. data/lib/sqreen/js/mini_racer_executable_js.rb +144 -0
  80. data/lib/sqreen/js/thread_local_exec_js_runnable.rb +49 -0
  81. data/lib/{sqreen-alt.rb → sqreen/legacy.rb} +5 -1
  82. data/lib/sqreen/{instrumentation.rb → legacy/instrumentation.rb} +44 -15
  83. data/lib/sqreen/log.rb +10 -188
  84. data/lib/sqreen/log/loggable.rb +28 -0
  85. data/lib/sqreen/logger.rb +85 -0
  86. data/lib/sqreen/metrics.rb +2 -0
  87. data/lib/sqreen/metrics/average.rb +2 -0
  88. data/lib/sqreen/metrics/base.rb +2 -0
  89. data/lib/sqreen/metrics/binning.rb +2 -0
  90. data/lib/sqreen/metrics/collect.rb +2 -0
  91. data/lib/sqreen/metrics/sum.rb +2 -0
  92. data/lib/sqreen/metrics_store.rb +5 -11
  93. data/lib/sqreen/metrics_store/already_registered_metric.rb +13 -0
  94. data/lib/sqreen/metrics_store/unknown_metric.rb +13 -0
  95. data/lib/sqreen/metrics_store/unregistered_metric.rb +13 -0
  96. data/lib/sqreen/middleware.rb +2 -34
  97. data/lib/sqreen/mono_time.rb +4 -0
  98. data/lib/sqreen/node.rb +46 -0
  99. data/lib/sqreen/not_implemented_yet.rb +10 -0
  100. data/lib/sqreen/null_logger.rb +26 -0
  101. data/lib/sqreen/payload_creator.rb +4 -19
  102. data/lib/sqreen/payload_creator/header_section.rb +30 -0
  103. data/lib/sqreen/performance_notifications.rb +2 -0
  104. data/lib/sqreen/performance_notifications/binned_metrics.rb +2 -0
  105. data/lib/sqreen/performance_notifications/log.rb +2 -0
  106. data/lib/sqreen/performance_notifications/log_performance.rb +2 -0
  107. data/lib/sqreen/performance_notifications/metrics.rb +2 -0
  108. data/lib/sqreen/performance_notifications/newrelic.rb +2 -0
  109. data/lib/sqreen/prefix.rb +35 -0
  110. data/lib/sqreen/rails_middleware.rb +16 -0
  111. data/lib/sqreen/remote_command.rb +3 -8
  112. data/lib/sqreen/remote_command/failure_output.rb +16 -0
  113. data/lib/sqreen/rules.rb +34 -2
  114. data/lib/sqreen/{rule_attributes.rb → rules/attrs.rb} +2 -0
  115. data/lib/sqreen/{rules_callbacks/sdk_auth_track.rb → rules/auth_track_cb.rb} +4 -2
  116. data/lib/sqreen/{rules_callbacks/binding_accessor_matcher.rb → rules/binding_accessor_matcher_cb.rb} +6 -8
  117. data/lib/sqreen/{rules_callbacks → rules}/binding_accessor_metrics.rb +3 -1
  118. data/lib/sqreen/{rules_callbacks/blacklist_ips.rb → rules/blacklist_ips_cb.rb} +5 -2
  119. data/lib/sqreen/{rules_callbacks → rules}/count_http_codes.rb +4 -2
  120. data/lib/sqreen/{rules_callbacks/crawler_user_agent_matches.rb → rules/crawler_user_agent_matches_cb.rb} +3 -1
  121. data/lib/sqreen/{rules_callbacks/crawler_user_agent_matches_metrics.rb → rules/crawler_user_agent_matches_metrics_cb.rb} +3 -1
  122. data/lib/sqreen/{rules_callbacks/custom_error.rb → rules/custom_error_cb.rb} +3 -1
  123. data/lib/sqreen/{rules_callbacks/devise_auth_track.rb → rules/devise_auth_track_cb.rb} +4 -2
  124. data/lib/sqreen/{rules_callbacks/devise_signup_track.rb → rules/devise_signup_track_cb.rb} +4 -2
  125. data/lib/sqreen/{rules_callbacks/execjs.rb → rules/execjs_cb.rb} +51 -50
  126. data/lib/sqreen/{rules_callbacks/headers_insert.rb → rules/headers_insert_cb.rb} +8 -1
  127. data/lib/sqreen/{rules_callbacks → rules}/matcher_rule.rb +4 -2
  128. data/lib/sqreen/{rules_callbacks/not_found.rb → rules/not_found_cb.rb} +7 -2
  129. data/lib/sqreen/{rules_callbacks/rails_parameters.rb → rules/rails_parameters_cb.rb} +3 -1
  130. data/lib/sqreen/{rules_callbacks → rules}/record_request_context.rb +3 -1
  131. data/lib/sqreen/{rules_callbacks/regexp_rule.rb → rules/regexp_rule_cb.rb} +3 -1
  132. data/lib/sqreen/{rule_callback.rb → rules/rule_cb.rb} +4 -2
  133. data/lib/sqreen/{rules_callbacks → rules}/run_req_start_actions.rb +7 -3
  134. data/lib/sqreen/{rules_callbacks → rules}/run_user_actions.rb +4 -2
  135. data/lib/sqreen/{rules_callbacks/shell_env.rb → rules/shell_env_cb.rb} +3 -1
  136. data/lib/sqreen/{rules_callbacks/sdk_signup_track.rb → rules/signup_track_cb.rb} +4 -2
  137. data/lib/sqreen/rules/update_request_context.rb +22 -0
  138. data/lib/sqreen/{rules_callbacks/url_matches.rb → rules/url_matches_cb.rb} +3 -1
  139. data/lib/sqreen/{rules_callbacks/user_agent_matches.rb → rules/user_agent_matches_cb.rb} +3 -1
  140. data/lib/sqreen/{rules_callbacks/waf.rb → rules/waf_cb.rb} +41 -21
  141. data/lib/sqreen/{rules_callbacks/reflected_xss.rb → rules/xss_cb.rb} +12 -7
  142. data/lib/sqreen/run_when_called_cb.rb +23 -0
  143. data/lib/sqreen/runner.rb +25 -7
  144. data/lib/sqreen/runtime_infos.rb +4 -9
  145. data/lib/sqreen/safe_json.rb +2 -0
  146. data/lib/sqreen/sdk.rb +4 -0
  147. data/lib/sqreen/sensitive_data_redactor.rb +113 -0
  148. data/lib/sqreen/serializer.rb +2 -0
  149. data/lib/sqreen/session.rb +2 -0
  150. data/lib/sqreen/shared_storage.rb +2 -0
  151. data/lib/sqreen/shared_storage23.rb +2 -0
  152. data/lib/sqreen/shrink_wrap.rb +16 -0
  153. data/lib/sqreen/signature_verifier.rb +22 -0
  154. data/lib/sqreen/sinatra_middleware.rb +16 -0
  155. data/lib/sqreen/{rules_signature.rb → sqreen_signed_verifier.rb} +7 -17
  156. data/lib/sqreen/token_invalid_exception.rb +10 -0
  157. data/lib/sqreen/token_not_found_exception.rb +11 -0
  158. data/lib/sqreen/trie.rb +5 -64
  159. data/lib/sqreen/unauthorized.rb +10 -0
  160. data/lib/sqreen/util.rb +7 -0
  161. data/lib/sqreen/util/capped_array.rb +35 -0
  162. data/lib/sqreen/util/capped_hash.rb +41 -0
  163. data/lib/sqreen/util/capped_string.rb +26 -0
  164. data/lib/sqreen/util/capper.rb +67 -0
  165. data/lib/sqreen/version.rb +3 -1
  166. data/lib/sqreen/waf_error.rb +20 -0
  167. data/lib/sqreen/weave.rb +12 -0
  168. data/lib/sqreen/weave/hardcoded.rb +19 -0
  169. data/lib/sqreen/weave/instrumentor.rb +48 -0
  170. data/lib/sqreen/weave/legacy.rb +12 -0
  171. data/lib/sqreen/weave/legacy/instrumentation.rb +406 -0
  172. data/lib/sqreen/web_server.rb +2 -0
  173. data/lib/sqreen/web_server/generic.rb +2 -0
  174. data/lib/sqreen/web_server/passenger.rb +2 -0
  175. data/lib/sqreen/web_server/puma.rb +2 -0
  176. data/lib/sqreen/web_server/rainbows.rb +2 -0
  177. data/lib/sqreen/web_server/thin.rb +2 -0
  178. data/lib/sqreen/web_server/unicorn.rb +2 -0
  179. data/lib/sqreen/web_server/webrick.rb +2 -0
  180. data/lib/sqreen/worker.rb +2 -0
  181. metadata +105 -39
  182. data/lib/sqreen/dependency/hook.rb +0 -102
  183. data/lib/sqreen/rules_callbacks.rb +0 -35
  184. data/lib/sqreen/rules_callbacks/inspect_rule.rb +0 -25
@@ -0,0 +1,10 @@
1
+ # typed: strong
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/exception'
7
+
8
+ module Sqreen
9
+ class Unauthorized < Sqreen::Exception; end
10
+ end
@@ -0,0 +1,7 @@
1
+ # typed: strong
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; end
7
+ module Sqreen::Util; end
@@ -0,0 +1,35 @@
1
+ # typed: false
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/util'
7
+
8
+ class Sqreen::Util::CappedArray < Array
9
+ attr_reader :size_cap, :depth_cap
10
+
11
+ def initialize(*args, &block)
12
+ opts = args.last.is_a?(Hash) ? args.pop : {}
13
+ size_cap = opts[:size_cap] || 150
14
+ depth_cap = opts[:depth_cap] || 10
15
+ @size_cap = size_cap
16
+ @depth_cap = depth_cap
17
+
18
+ super(*args, &block)
19
+ end
20
+
21
+ def <<(value)
22
+ keep?(size, value) ? super : self
23
+ end
24
+ alias_method :append, :<<
25
+
26
+ def []=(index, value)
27
+ super if keep?(index, value)
28
+ end
29
+
30
+ private
31
+
32
+ def keep?(index, value)
33
+ index < size_cap && (depth_cap > 0 || !value.is_a?(Hash) && !value.is_a?(Array))
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ # typed: false
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/util'
7
+
8
+ class Sqreen::Util::CappedHash < Hash
9
+ attr_reader :size_cap, :depth_cap
10
+
11
+ def initialize(*args, &block)
12
+ opts = args.last.is_a?(Hash) ? args.pop : {}
13
+ size_cap = opts[:size_cap] || 150
14
+ depth_cap = opts[:depth_cap] || 10
15
+ @size_cap = size_cap
16
+ @depth_cap = depth_cap
17
+
18
+ super(*args, &block)
19
+ end
20
+
21
+ def []=(key, value)
22
+ super if key?(key) || keep?(value)
23
+ end
24
+ alias_method :store, :[]=
25
+
26
+ def merge!(h)
27
+ h.each { |k, v| self[k] = block_given? ? yield(k, self[k], v) : v }
28
+ end
29
+ alias_method :update, :merge!
30
+
31
+ def replace(h)
32
+ keep_if { false }
33
+ merge!(h)
34
+ end
35
+
36
+ private
37
+
38
+ def keep?(value)
39
+ size < size_cap && (depth_cap > 0 || !value.is_a?(Hash) && !value.is_a?(Array))
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ # typed: false
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/util'
7
+
8
+ class Sqreen::Util::CappedString < String
9
+ attr_reader :size_cap
10
+
11
+ def initialize(*args, &block)
12
+ opts = args.last.is_a?(Hash) ? args.pop : {}
13
+ size_cap = opts[:size_cap] || 4096
14
+ @size_cap = size_cap
15
+ super(*args, &block)
16
+ end
17
+
18
+ def <<(value)
19
+ return self unless size < size_cap
20
+
21
+ value = value[0, size_cap - size] if size + value.size > size_cap
22
+
23
+ super(value)
24
+ end
25
+ alias_method :concat, :<<
26
+ end
@@ -0,0 +1,67 @@
1
+ # typed: true
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/util'
7
+ require 'sqreen/util/capped_hash'
8
+ require 'sqreen/util/capped_string'
9
+ require 'sqreen/util/capped_array'
10
+
11
+ class Sqreen::Util::Capper
12
+ attr_reader :string_size_cap, :size_cap, :depth_cap
13
+
14
+ def initialize(opts = {})
15
+ string_size_cap = opts[:string_size_cap] || 4096
16
+ size_cap = opts[:size_cap] || 150
17
+ depth_cap = opts[:depth_cap] || 10
18
+ flat_size_cap = opts[:flat_size_cap] || 10000
19
+
20
+ @string_size_cap = string_size_cap
21
+ @size_cap = size_cap
22
+ @depth_cap = depth_cap
23
+ @flat_size_cap = flat_size_cap
24
+ end
25
+
26
+ def call(e)
27
+ r_call(e).first
28
+ end
29
+
30
+ private
31
+
32
+ def r_call(e, opts = {})
33
+ size = opts[:size] || @flat_size_cap
34
+ depth = opts[:depth] || @depth_cap
35
+
36
+ case e
37
+ when Hash
38
+ h = Sqreen::Util::CappedHash.new(size_cap: size_cap, depth_cap: depth)
39
+ e.each do |k, v|
40
+ break unless size > 0
41
+ k_capped, = r_call(k, size: size, depth: depth - 1)
42
+ v_capped, s = r_call(v, size: size, depth: depth - 1)
43
+ size -= s
44
+ h[k_capped] = v_capped
45
+ end
46
+ [h, h.size]
47
+ when Array
48
+ a = Sqreen::Util::CappedArray.new(size_cap: size_cap, depth_cap: depth)
49
+ e.each do |v|
50
+ break unless size > 0
51
+ c, s = r_call(v, size: size, depth: depth - 1)
52
+ a << c
53
+ size -= s
54
+ end
55
+ [a, a.size]
56
+ when String
57
+ return unless size > 0
58
+ size -= 1
59
+ s = Sqreen::Util::CappedString.new(size_cap: string_size_cap) << e
60
+ [s, 1]
61
+ else
62
+ return unless size > 0
63
+ size -= 1
64
+ [e, 1]
65
+ end
66
+ end
67
+ end
@@ -1,6 +1,8 @@
1
+ # typed: true
2
+
1
3
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
3
5
 
4
6
  module Sqreen
5
- VERSION = '1.18.2'.freeze
7
+ VERSION = '1.19.0'.freeze
6
8
  end
@@ -0,0 +1,20 @@
1
+ # typed: true
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/exception'
7
+
8
+ module Sqreen
9
+ class WAFError < Sqreen::Exception
10
+ attr_reader :rule_name, :error, :data, :args
11
+
12
+ def initialize(rule_name, error, data = nil, args = nil)
13
+ super(error.to_s)
14
+ @rule_name = rule_name
15
+ @error = error
16
+ @data = data
17
+ @args = args
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strong
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/log/loggable'
7
+
8
+ module Sqreen
9
+ module Weave
10
+ include Sqreen::Log::Loggable
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strong
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/weave'
7
+
8
+ class Sqreen::Weave::Hardcoded
9
+ # [
10
+ # ### callback for performing sec responses based on ip
11
+ # ### init redefined to implement smartass way to hook it upon the
12
+ # ### framework's middleware #call
13
+ # Sqreen::Rules::RunReqStartActions.new(framework),
14
+ # ### callback for performing sec responses based on user
15
+ # Sqreen::Rules::RunUserActions.new(Sqreen, :identify, 0),
16
+ # ### callback for performing sec responses based on user
17
+ # Sqreen::Rules::RunUserActions.new(Sqreen, :auth_track, 1),
18
+ # ]
19
+ end
@@ -0,0 +1,48 @@
1
+ # typed: true
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/weave'
7
+
8
+ # rule loader: decouple from runner
9
+ # remote rules from back
10
+ # local rules from local files
11
+ # => rule list (what is a rule?)
12
+ # => to callback (what is a callback?)
13
+ # => to instrumentation (== attach callbacks to their targets using graft)
14
+
15
+ # make shit like instrument framework independent (block passing?)
16
+ # => too much things assume only one framework
17
+ # possible to do run req actions without hardcoded cbs?
18
+ # (data comes from actions command, native cb merely binds to middleware)
19
+ # can cb be a form of abstraction?
20
+
21
+ # rule sig: decouple/split
22
+ # - data signer/checker
23
+ # apply this to rule data
24
+
25
+ # whitelist is mixed in
26
+
27
+ # metrics
28
+ # three dedicated metrics: abstract and isolate
29
+
30
+ class Sqreen::Weave::Instrumentor
31
+ def initialize(metrics_engine)
32
+ ### bail out if no metric engine
33
+ ### init metric to count calls to sqreen
34
+ ### init metric to count request whitelist matches (ip or path whitelist)
35
+ ### init metric to count over budget hits
36
+ end
37
+
38
+ def instrument!(rules, framework)
39
+ ### set up rule signature verifier
40
+ ### force clean instrumentation callback list
41
+ ### for each rule description, transform into format for adding callback
42
+ ### attach framework to callback
43
+ ### install callback, observing priority
44
+ ### for each hardcoded callback
45
+ ### install hardcoded callbacks, observing priority
46
+ ### globally declare instrumentation ready
47
+ end
48
+ end
@@ -0,0 +1,12 @@
1
+ # typed: strong
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/weave'
7
+
8
+ module Sqreen
9
+ module Weave
10
+ module Legacy; end
11
+ end
12
+ end
@@ -0,0 +1,406 @@
1
+ # typed: false
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/weave/legacy'
7
+ require 'sqreen/graft/hook_point'
8
+ require 'sqreen/call_countable'
9
+ require 'sqreen/rules'
10
+ require 'sqreen/rules/record_request_context'
11
+
12
+ class Sqreen::Weave::Legacy::Instrumentation
13
+ attr_accessor :metrics_engine
14
+
15
+ def initialize(metrics_engine, opts = {})
16
+ Sqreen::Weave.logger.debug { "#{self.class.name}#initialize #{metrics_engine}" }
17
+ @hooks = []
18
+
19
+ self.metrics_engine = metrics_engine
20
+
21
+ ### bail out if no metric engine
22
+ return if metrics_engine.nil?
23
+
24
+ ### init metric to count calls to sqreen
25
+ metrics_engine.create_metric(
26
+ 'name' => 'sqreen_call_counts',
27
+ 'period' => 60,
28
+ 'kind' => 'Sum',
29
+ )
30
+ ### init metric to count request whitelist matches (ip or path whitelist)
31
+ metrics_engine.create_metric(
32
+ 'name' => 'whitelisted',
33
+ 'period' => 60,
34
+ 'kind' => 'Sum',
35
+ )
36
+ ### init metric to count over budget hits
37
+ metrics_engine.create_metric(
38
+ 'name' => 'request_overtime',
39
+ 'period' => 60,
40
+ 'kind' => 'Sum',
41
+ )
42
+
43
+ # PerformanceNotifications::Binning
44
+ metrics_engine.create_metric(
45
+ 'name' => 'req',
46
+ 'period' => opts[:period] || 60,
47
+ 'kind' => 'Binning',
48
+ 'options' => opts[:perf_metric] || { 'base' => 2.0, 'factor' => 0.1 },
49
+ )
50
+ metrics_engine.create_metric(
51
+ 'name' => 'sq',
52
+ 'period' => opts[:period] || 60,
53
+ 'kind' => 'Binning',
54
+ 'options' => opts[:perf_metric] || { 'base' => 2.0, 'factor' => 0.1 },
55
+ )
56
+ metrics_engine.create_metric(
57
+ 'name' => 'pct',
58
+ 'period' => opts[:period] || 60,
59
+ 'kind' => 'Binning',
60
+ 'options' => opts[:perf_metric_percent] || { 'base' => 1.3, 'factor' => 1.0 },
61
+ )
62
+
63
+ Sqreen.thread_cpu_time? && metrics_engine.create_metric(
64
+ 'name' => 'sq_thread_cpu_pct',
65
+ 'period' => opts[:period] || 60,
66
+ 'kind' => 'Binning',
67
+ 'options' => opts[:perf_metric_percent] || { 'base' => 1.3, 'factor' => 1.0 },
68
+ )
69
+ end
70
+
71
+ # needed by Sqreen::Runner#initialize
72
+ def instrument!(rules, framework)
73
+ Sqreen::Weave.logger.debug { "#{rules.count} rules, #{framework}" }
74
+
75
+ strategy = Sqreen.config_get(:weave_strategy)
76
+ if strategy == :prepend && !Module.respond_to?(:prepend)
77
+ Sqreen::Weave.logger.warn { "strategy: #{strategy.inspect} unavailable, falling back to :chain" }
78
+ strategy = :chain
79
+ elsif strategy == :chain && Gem::Specification.select { |s| s.name == 'scout_apm' && Gem::Requirement.new('>= 2.5.2').satisfied_by?(Gem::Version.new(s.version)) }.any?
80
+ Sqreen::Weave.logger.warn { "strategy: #{strategy.inspect} unavailable with scout_apm >= 2.5.2, switching to :prepend" }
81
+ strategy = :prepend
82
+ end
83
+ Sqreen::Weave.logger.debug { "strategy: #{strategy.inspect}" }
84
+
85
+ ### set up rule signature verifier
86
+ verifier = nil
87
+ ### force clean instrumentation callback list
88
+ @hooks = []
89
+ ### for each rule description
90
+ rules.each do |rule|
91
+ Sqreen::Weave.logger.debug { "Processing rule: #{rule['name']}" }
92
+ ### transform into format for adding callback
93
+ rule_callback = Sqreen::Rules.cb_from_rule(rule, self, metrics_engine, verifier)
94
+ next unless rule_callback
95
+ ### attach framework to callback
96
+ rule_callback.framework = framework
97
+ ### install callback, observing priority
98
+ Sqreen::Weave.logger.debug { "Adding rule callback: #{rule_callback}" }
99
+ @hooks << add_callback("weave,rule=#{rule['name']}", rule_callback, strategy)
100
+ end
101
+
102
+ ### for each hardcoded callback
103
+ hardcoded_callbacks(framework).each do |hard_callback|
104
+ Sqreen::Weave.logger.debug { "Adding hardcoded callback: #{hard_callback}" }
105
+ ### install hardcoded callbacks, observing priority
106
+ @hooks << add_callback('weave,hardcoded', hard_callback, strategy)
107
+ end
108
+
109
+ metrics_engine = self.metrics_engine
110
+ request_hook = Sqreen::Graft::Hook['Sqreen::ShrinkWrap#call', strategy]
111
+ @hooks << request_hook
112
+ request_hook.add do
113
+ before('wave,meta,request', rank: -100000, mandatory: true) do |_call|
114
+ next unless Sqreen.instrumentation_ready
115
+
116
+ uuid = SecureRandom.uuid
117
+ now = Sqreen::Graft::Timer.read
118
+ Thread.current[:sqreen_http_request] = {
119
+ uuid: uuid,
120
+ start_time: now,
121
+ time_budget: Sqreen.performance_budget,
122
+ time_budget_expended: false,
123
+ timer: Sqreen::Graft::Timer.new("request_#{uuid}"),
124
+ timed_callbacks: [],
125
+ timed_hooks: [],
126
+ timed_hooks_before: [],
127
+ timed_hooks_after: [],
128
+ timed_hooks_raised: [],
129
+ timed_hooks_ensured: [],
130
+ skipped_callbacks: [],
131
+ }
132
+
133
+ Sqreen::Weave.logger.debug { "request.uuid: #{uuid}" }
134
+ end
135
+
136
+ ensured('weave,meta,request', rank: 100000, mandatory: true) do |_call|
137
+ request = Thread.current[:sqreen_http_request]
138
+
139
+ next if request.nil?
140
+
141
+ Thread.current[:sqreen_http_request] = nil
142
+ now = Sqreen::Graft::Timer.read
143
+ utc_now = Time.now.utc
144
+
145
+ request[:timed_callbacks].each do |timer|
146
+ duration = timer.duration
147
+ # stop = now
148
+ # start = now - duration
149
+ timer.tag =~ /weave,rule=(.*)$/ && rule = $1
150
+ timer.tag =~ /@before/ && whence = 'pre'
151
+ timer.tag =~ /@after/ && whence = 'post'
152
+ timer.tag =~ /@raised/ && whence = 'failing'
153
+
154
+ next unless rule && whence
155
+
156
+ # Sqreen::PerformanceNotifications.notify(rule, whence, start, stop)
157
+ # => BinnedMetrics
158
+ metric_name = "sq.#{rule}.#{whence}"
159
+ unless metrics_engine.metric?(metric_name)
160
+ metrics_engine.create_metric(
161
+ 'name' => metric_name,
162
+ 'period' => 60,
163
+ 'kind' => 'Binning',
164
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
165
+ )
166
+ end
167
+ metrics_engine.update(metric_name, now, nil, duration * 1000)
168
+ end
169
+
170
+ metric_name = 'sq.hooks_pre.pre'
171
+ duration = request[:timed_hooks_before].sum(&:duration)
172
+ unless metrics_engine.metric?(metric_name)
173
+ metrics_engine.create_metric(
174
+ 'name' => metric_name,
175
+ 'period' => 60,
176
+ 'kind' => 'Binning',
177
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
178
+ )
179
+ end
180
+ metrics_engine.update(metric_name, now, nil, duration * 1000)
181
+
182
+ metric_name = 'sq.hooks_post.post'
183
+ duration = request[:timed_hooks_after].sum(&:duration)
184
+ unless metrics_engine.metric?(metric_name)
185
+ metrics_engine.create_metric(
186
+ 'name' => metric_name,
187
+ 'period' => 60,
188
+ 'kind' => 'Binning',
189
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
190
+ )
191
+ end
192
+ metrics_engine.update(metric_name, now, nil, duration * 1000)
193
+
194
+ metric_name = 'sq.hooks_failing.failing'
195
+ duration = request[:timed_hooks_raised].sum(&:duration)
196
+ unless metrics_engine.metric?(metric_name)
197
+ metrics_engine.create_metric(
198
+ 'name' => metric_name,
199
+ 'period' => 60,
200
+ 'kind' => 'Binning',
201
+ 'options' => { 'base' => 2.0, 'factor' => 0.1 },
202
+ )
203
+ end
204
+ metrics_engine.update(metric_name, now, nil, duration * 1000)
205
+
206
+ skipped = request[:skipped_callbacks].map(&:name)
207
+ Sqreen::Weave.logger.debug { "request:#{request[:uuid]} callback.skipped.size: #{skipped.count} callback.skipped: [#{skipped.join(', ')}]" }
208
+ timer = request[:timer]
209
+ total = timer.duration
210
+ Sqreen::Weave.logger.debug { "request:#{request[:uuid]} timer.total: #{'%.03fus' % (total * 1_000_000)} timer.size: #{timer.size}" }
211
+ timings = request[:timed_callbacks].map(&:to_s)
212
+ total = request[:timed_callbacks].sum(&:duration)
213
+ Sqreen::Weave.logger.debug { "request:#{request[:uuid]} callback.total: #{'%.03fus' % (total * 1_000_000)} callback.timings: [#{timings.join(', ')}]" }
214
+ timings = request[:timed_hooks].map(&:to_s)
215
+ total = request[:timed_hooks].sum(&:duration)
216
+ Sqreen::Weave.logger.debug { "request:#{request[:uuid]} hook.total: #{'%.03fus' % (total * 1_000_000)} hook.timings: [#{timings.join(', ')}]" }
217
+
218
+ skipped = request[:skipped_callbacks].map(&:name)
219
+ skipped_rule_name = skipped.first && skipped.first =~ /weave,rule=(.*)$/ && $1
220
+ Sqreen.observations_queue.push(['request_overtime', skipped_rule_name, 1, utc_now]) if skipped_rule_name
221
+
222
+ sqreen_request_duration = total
223
+ Sqreen.observations_queue.push(['sq', nil, sqreen_request_duration * 1000, utc_now])
224
+
225
+ request_duration = now - request[:start_time]
226
+ Sqreen.observations_queue.push(['req', nil, request_duration * 1000, utc_now])
227
+
228
+ sqreen_request_ratio = (sqreen_request_duration * 100.0) / (request_duration - sqreen_request_duration)
229
+ Sqreen.observations_queue.push(['pct', nil, sqreen_request_ratio, utc_now])
230
+ end
231
+ end.install
232
+
233
+ ### globally declare instrumentation ready
234
+ Sqreen.instrumentation_ready = true
235
+ end
236
+
237
+ # needed by Sqreen::Runner
238
+ def remove_all_callbacks
239
+ Sqreen.instrumentation_ready = false
240
+
241
+ loop do
242
+ hook = @hooks.pop
243
+ break unless hook
244
+ Sqreen::Weave.logger.debug { "hook.deinstrument: #{hook}" }
245
+ hook.uninstall
246
+ hook.clear
247
+ end
248
+ end
249
+
250
+ # needed by #instrument!
251
+ def add_callback(rule, callback, strategy)
252
+ Sqreen::Weave.logger.debug { "Adding rule: #{rule} callback: #{callback}" }
253
+ klass = callback.klass
254
+ method = callback.method
255
+
256
+ if Sqreen::Graft::HookPoint.new("#{klass}.#{method}").exist?
257
+ hook_point = "#{klass}.#{method}"
258
+ elsif Sqreen::Graft::HookPoint.new("#{klass}##{method}").exist?
259
+ hook_point = "#{klass}##{method}"
260
+ end
261
+
262
+ return if hook_point.nil?
263
+
264
+ priority = callback.priority || 100
265
+ block = callback.respond_to?(:block) ? callback.block : true
266
+ ignore = -> { callback.whitelisted? } if callback.respond_to?(:whitelisted?)
267
+
268
+ hook = Sqreen::Graft::Hook[hook_point, strategy]
269
+ hook.add do
270
+ if callback.pre?
271
+ before(rule, rank: priority, mandatory: !callback.overtimeable, flow: block, ignore: ignore) do |call, b|
272
+ return unless Thread.current[:sqreen_http_request]
273
+
274
+ i = call.instance
275
+ a = call.args
276
+ r = call.remaining
277
+
278
+ Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#pre instance=#{i}" }
279
+ begin
280
+ ret = callback.pre(i, a, r)
281
+ rescue StandardError => e
282
+ Sqreen::Weave.logger.warn { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#pre instance=#{i} => exception=#{e}" }
283
+ if callback.respond_to?(:record_exception)
284
+ callback.record_exception(e)
285
+ else
286
+ Sqreen::RemoteException.record(e)
287
+ end
288
+ end
289
+ Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#pre instance=#{i} => return=#{ret.inspect}" }
290
+
291
+ case ret[:status]
292
+ when :skip, 'skip'
293
+ throw(b, b.return(ret[:new_return_value]).break!) if ret.key?(:new_return_value)
294
+ when :modify_args, 'modify_args'
295
+ throw(b, b.args(ret[:args]))
296
+ when :raise, 'raise'
297
+ throw(b, b.raise(ret[:exception])) if ret.key?(:exception)
298
+ throw(b, b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required.")))
299
+ end unless ret.nil?
300
+ end
301
+ end
302
+
303
+ if callback.post?
304
+ after(rule, rank: -priority, mandatory: !callback.overtimeable, flow: block, ignore: ignore) do |call, b|
305
+ return unless Thread.current[:sqreen_http_request]
306
+
307
+ i = call.instance
308
+ v = call.returned
309
+ a = call.args
310
+ r = call.remaining
311
+
312
+ Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#post instance=#{i}" }
313
+ begin
314
+ ret = callback.post(v, i, a, r)
315
+ rescue StandardError => e
316
+ Sqreen::Weave.logger.warn { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#post instance=#{i} => exception=#{e}" }
317
+ if callback.respond_to?(:record_exception)
318
+ callback.record_exception(e)
319
+ else
320
+ Sqreen::RemoteException.record(e)
321
+ end
322
+ end
323
+ Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#post instance=#{i} => return=#{ret.inspect}" }
324
+
325
+ case ret[:status]
326
+ when :override, 'override'
327
+ throw(b, b.return(ret[:new_return_value])) if ret.key?(:new_return_value)
328
+ when :raise, 'raise'
329
+ throw(b, b.raise(ret[:exception])) if ret.key?(:exception)
330
+ throw(b, b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required.")))
331
+ end unless ret.nil?
332
+ end
333
+ end
334
+
335
+ if callback.failing?
336
+ raised(rule, rank: priority, mandatory: !callback.overtimeable, flow: block, ignore: ignore) do |call, b|
337
+ return unless Thread.current[:sqreen_http_request]
338
+
339
+ i = call.instance
340
+ e = call.raised
341
+ a = call.args
342
+ r = call.remaining
343
+
344
+ Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#failing instance=#{i}" }
345
+ begin
346
+ ret = callback.failing(e, i, a, r)
347
+ rescue StandardError => e
348
+ Sqreen::Weave.logger.warn { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#failing instance=#{i} => exception=#{e}" }
349
+ if callback.respond_to?(:record_exception)
350
+ callback.record_exception(e)
351
+ else
352
+ Sqreen::RemoteException.record(e)
353
+ end
354
+ end
355
+ Sqreen::Weave.logger.debug { "#{rule} klass=#{callback.klass} method=#{callback.method} when=#failing instance=#{i} => return=#{ret.inspect}" }
356
+
357
+ raise e if ret.nil?
358
+
359
+ case ret[:status]
360
+ when :override, 'override'
361
+ throw(b, b.return(ret[:new_return_value])) if ret.key?(:new_return_value)
362
+ when :retry, 'retry'
363
+ throw(b, b.retry)
364
+ when :raise, 'raise'
365
+ throw(b, b.raise(ret[:exception])) if ret.key?(:exception)
366
+ throw(b, b.raise(Sqreen::AttackBlocked.new("Sqreen blocked a security threat (type: #{callback.rule_name}). No action is required.")))
367
+ when :reraise, 'reraise'
368
+ throw(b, b.raise(e))
369
+ else
370
+ throw(b, b.raise(e))
371
+ end unless ret.nil?
372
+ end
373
+ end
374
+ end.install
375
+
376
+ hook
377
+ end
378
+
379
+ # needed by Sqreen::Rules.cb_from_rule
380
+ def valid_method?(klass, method)
381
+ if Sqreen::Graft::HookPoint.new("#{klass}.#{method}").exist?
382
+ Sqreen::Weave.logger.debug { "HookPoint found: #{klass}.#{method}" }
383
+ true
384
+ elsif Sqreen::Graft::HookPoint.new("#{klass}##{method}").exist?
385
+ Sqreen::Weave.logger.debug { "HookPoint found: #{klass}##{method}" }
386
+ true
387
+ else
388
+ Sqreen::Weave.logger.debug { "HookPoint not found: #{klass} #{method}" }
389
+ false
390
+ end
391
+ end
392
+
393
+ # needed by #instrument!
394
+ def hardcoded_callbacks(framework)
395
+ [
396
+ ### callback for performing sec responses based on ip
397
+ ### init redefined to implement smartass way to hook it upon the
398
+ ### framework's middleware #call
399
+ Sqreen::Rules::RunReqStartActions.new(framework),
400
+ ### callback for performing sec responses based on user
401
+ Sqreen::Rules::RunUserActions.new(Sqreen, :identify, 0),
402
+ ### callback for performing sec responses based on user
403
+ Sqreen::Rules::RunUserActions.new(Sqreen, :auth_track, 1),
404
+ ]
405
+ end
406
+ end