sqreen 1.17.0 → 1.17.2.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/version'
5
+ require 'sqreen/instrumentation'
6
+ require 'sqreen/session'
7
+ require 'sqreen/runner'
8
+ require 'sqreen/callbacks'
9
+ require 'sqreen/log'
10
+ require 'sqreen/exception'
11
+ require 'sqreen/configuration'
12
+ require 'sqreen/events/attack'
13
+ require 'sqreen/sdk'
14
+ require 'sqreen/dependency/detector'
15
+ require 'sqreen/worker'
16
+ require 'sqreen/web_server'
17
+
18
+ module Sqreen
19
+ module Agent
20
+ module_function
21
+
22
+ def start
23
+ return if Sqreen.to_bool(ENV['SQREEN_DISABLE'])
24
+
25
+ Sqreen::Dependency::Detector.hook do
26
+ Sqreen.log.debug "[#{Process.pid}] Attaching to webserver"
27
+ Sqreen::WebServer.attach do
28
+ Sqreen.log.debug "[#{Process.pid}] Attached to webserver"
29
+ # TODO: maybe in dependency, not here, to separate concerns?
30
+ Sqreen::Dependency::Sentry.ignore_sqreen_exceptions
31
+ Sqreen::Dependency::NewRelic.ignore_sqreen_exceptions
32
+ Sqreen::Worker.start(Sqreen.framework)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -33,20 +33,20 @@ module Sqreen
33
33
  FAILING = 'failing'.freeze
34
34
  COUNT_CALLS = 'sqreen_call_counts'.freeze
35
35
 
36
- def pre_with_count(inst, *args, &block)
37
- ret = pre_without_count(inst, *args, &block)
36
+ def pre_with_count(inst, args, budget = nil, &block)
37
+ ret = pre_without_count(inst, args, budget, &block)
38
38
  count_calls('pre')
39
39
  ret
40
40
  end
41
41
 
42
- def post_with_count(rv, inst, *args, &block)
43
- ret = post_without_count(rv, inst, *args, &block)
42
+ def post_with_count(rv, inst, args, budget = nil, &block)
43
+ ret = post_without_count(rv, inst, args, budget, &block)
44
44
  count_calls('post')
45
45
  ret
46
46
  end
47
47
 
48
- def failing_with_count(rv, inst, *args, &block)
49
- ret = failing_without_count(rv, inst, *args, &block)
48
+ def failing_with_count(rv, inst, args, budget = nil, &block)
49
+ ret = failing_without_count(rv, inst, args, budget, &block)
50
50
  count_calls('failing')
51
51
  ret
52
52
  end
@@ -25,22 +25,22 @@ module Sqreen
25
25
  end
26
26
  end
27
27
 
28
- def pre_with_conditions(inst, *args, &block)
28
+ def pre_with_conditions(inst, args, budget = nil, &block)
29
29
  eargs = [nil, framework, inst, args, @data, nil]
30
30
  return nil if !pre_conditions.nil? && !pre_conditions.evaluate(*eargs)
31
- pre_without_conditions(inst, *args, &block)
31
+ pre_without_conditions(inst, args, budget, &block)
32
32
  end
33
33
 
34
- def post_with_conditions(rv, inst, *args, &block)
34
+ def post_with_conditions(rv, inst, args, budget = nil, &block)
35
35
  eargs = [nil, framework, inst, args, @data, rv]
36
36
  return nil if !post_conditions.nil? && !post_conditions.evaluate(*eargs)
37
- post_without_conditions(rv, inst, *args, &block)
37
+ post_without_conditions(rv, inst, args, budget, &block)
38
38
  end
39
39
 
40
- def failing_with_conditions(rv, inst, *args, &block)
40
+ def failing_with_conditions(rv, inst, args, budget = nil, &block)
41
41
  eargs = [nil, framework, inst, args, @data, rv]
42
42
  return nil if !failing_conditions.nil? && !failing_conditions.evaluate(*eargs)
43
- failing_without_conditions(rv, inst, *args, &block)
43
+ failing_without_conditions(rv, inst, args, budget, &block)
44
44
  end
45
45
 
46
46
  protected
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ module Sqreen
5
+ module Dependency
6
+ def self.const_exist?(name)
7
+ resolve_const(name) && true
8
+ rescue NameError, ArgumentError
9
+ false
10
+ end
11
+
12
+ def self.resolve_const(name)
13
+ raise ArgumentError if name.nil? || name.empty?
14
+
15
+ name.to_s.split('::').inject(Object) { |a, e| a.const_get(e) }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ module Sqreen
5
+ module Dependency
6
+ class Callback
7
+ attr_reader :name
8
+
9
+ def initialize(name = nil, &block)
10
+ @name = name
11
+ @block = block
12
+ @disabled = false
13
+ end
14
+
15
+ def call(*args, &block)
16
+ Sqreen.log.debug "[#{Process.pid}] Callback #{@name} disabled:#{disabled?}"
17
+ return if @disabled
18
+ @block.call(*args, &block)
19
+ end
20
+
21
+ def disable
22
+ @disabled = true
23
+ end
24
+
25
+ def enable
26
+ @disabled = false
27
+ end
28
+
29
+ def disabled?
30
+ @disabled
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,97 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/dependency/hook'
5
+ require 'sqreen/dependency/rails'
6
+ require 'sqreen/dependency/rack'
7
+ require 'sqreen/dependency/sentry'
8
+ require 'sqreen/dependency/new_relic'
9
+
10
+ module Sqreen
11
+ module Dependency
12
+ module Detector
13
+ module_function
14
+
15
+ def start_mode
16
+ if Sqreen::Dependency::Rails.server?
17
+ :rails
18
+ elsif Sqreen::Dependency::Rack.rackup?
19
+ :rackup
20
+ else
21
+ :default
22
+ end
23
+ end
24
+
25
+ def hook(&block)
26
+ Sqreen.log.debug "[#{Process.pid}] Startup command: #{$0}"
27
+
28
+ # ensure middleware presence
29
+
30
+ ActiveSupport.on_load(:before_initialize, :yield => true) do
31
+ Sqreen::Dependency::Rails.insert_sqreen_middlewares
32
+ end if Sqreen::Dependency::Rails.required?
33
+
34
+ Sqreen::Dependency::Hook.add('Rack::Builder#to_app') do
35
+ after do
36
+ Sqreen::Dependency::Rails.inspect_middlewares
37
+ end
38
+ end if Sqreen::Dependency::Rails.required?
39
+
40
+ # ensure startup of thread in request handling processes
41
+
42
+ Sqreen::Dependency::Hook.add('Rack::Builder#to_app') do
43
+ after do |callback, *|
44
+ Sqreen.log.debug "[#{Process.pid}] Start mode #{Sqreen::Dependency::Detector.start_mode}"
45
+ if Sqreen::Dependency::Detector.start_mode == :rails || Sqreen::Dependency::Detector.start_mode == :rackup
46
+
47
+ Sqreen::Dependency::Rack.find_handler do |handler|
48
+ Sqreen::Dependency::Rack.on_run(handler) do
49
+ case handler.name
50
+ when 'Rack::Handler::Puma'
51
+ Sqreen::Dependency::Hook.add('Puma::Launcher#run') do
52
+ before do
53
+ # HACK: Puma master? hack falls apart when not preloading
54
+ # it would think master is not, triggering startup
55
+ Sqreen::WebServer.instance_eval { @master_pid = Process.pid }
56
+ block.call
57
+ # HACK: because to_app callback is disabled, so won't run again in fork
58
+ if Sqreen::WebServer.forking? && !Sqreen::WebServer.preload_app?
59
+ Sqreen::WebServer.after_fork { block.call }
60
+ end
61
+ end
62
+ end
63
+ Sqreen::Dependency::Hook['Puma::Launcher#run'].install
64
+ when 'Rack::Handler::PhusionPassenger'
65
+ # noop, passenger will start his own separate process
66
+ Sqreen.log.debug "[#{Process.pid}] Passenger will start in standalone process"
67
+ when 'Rack::Handler::Unicorn' # unicorn-rails
68
+ Sqreen::Dependency::Hook.add('Unicorn::HttpServer.new') do
69
+ before do
70
+ # BUG: detects single process...
71
+ end
72
+ end.install
73
+ else
74
+ block.call
75
+ end
76
+ end
77
+ end
78
+ else
79
+ block.call
80
+ end
81
+
82
+ # #to_app can be called multiple times, run callback once only
83
+ callback.disable
84
+ end
85
+ end
86
+
87
+ Sqreen::Dependency::Hook['Rack::Builder#to_app'].install
88
+
89
+ # Sqreen::Dependency::Hook.add('Rails::Server#start') do
90
+ # before { }
91
+ # end
92
+ # Sqreen::Dependency::Hook['Rails::Server#start'].install
93
+ # /!\ double instrument Rails < Rack => Rails.start_with -> Rails.start_without -> super -> Rack.start_with -> Rails.start_without
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,102 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/dependency/callback'
5
+ require 'sqreen/dependency/hook_point'
6
+
7
+ module Sqreen
8
+ module Dependency
9
+ class Hook
10
+ @hooks = {}
11
+
12
+ def self.[](hook_point)
13
+ @hooks[hook_point] ||= new(hook_point)
14
+ end
15
+
16
+ def self.add(hook_point, &block)
17
+ self[hook_point].add(&block)
18
+ end
19
+
20
+ attr_reader :point
21
+
22
+ def initialize(hook_point, dependency_test = nil)
23
+ @disabled = false
24
+ @point = hook_point.is_a?(HookPoint) ? hook_point : HookPoint.new(hook_point)
25
+ @before = []
26
+ @after = []
27
+ @raised = []
28
+ @dependency_test = dependency_test || Proc.new { point.exist? }
29
+ end
30
+
31
+ def dependency?
32
+ @dependency_test.call if @dependency_test
33
+ end
34
+
35
+ def add(&block)
36
+ tap { instance_eval(&block) }
37
+ end
38
+
39
+ def callback_name(whence, tag = nil)
40
+ "#{point}@#{whence}" << (tag ? ":#{tag}" : "")
41
+ end
42
+
43
+ def before(tag = nil, &block)
44
+ return @before if block.nil?
45
+
46
+ @before << Callback.new(callback_name(:before, tag), &block)
47
+ end
48
+
49
+ def after(tag = nil, &block)
50
+ return @after if block.nil?
51
+
52
+ @after << Callback.new(callback_name(:after, tag), &block)
53
+ end
54
+
55
+ def raised(tag = nil, &block)
56
+ return @raised if block.nil?
57
+
58
+ @raised << Callback.new(callback_name(:raised, tag), &block)
59
+ end
60
+
61
+ def depends_on(&block)
62
+ @dependency_test = block
63
+ end
64
+
65
+ def enable
66
+ @disabled = false
67
+ end
68
+
69
+ def disable
70
+ @disabled = true
71
+ end
72
+
73
+ def disabled?
74
+ @disabled
75
+ end
76
+
77
+ def install
78
+ unless point.exist?
79
+ Sqreen.log.debug "[#{Process.pid}] #{point} not found"
80
+ return
81
+ end
82
+ Sqreen.log.debug "[#{Process.pid}] Hook #{point}: installing"
83
+
84
+ point.install('sqreen_hook', &Sqreen::Dependency::Hook.wrapper(self))
85
+ end
86
+
87
+ def self.wrapper(hook)
88
+ # pass self to cbs
89
+ Proc.new do |*args, &block|
90
+ Sqreen.log.debug "[#{Process.pid}] Hook #{hook.point} disabled:#{hook.disabled?} caller:#{Kernel.caller[1].inspect}"
91
+ hook.before.each { |c| c.call(c, self, args) } unless hook.disabled?
92
+ begin
93
+ hook.point.apply(self, 'sqreen_hook', *args, &block)
94
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
95
+ hook.raised.each { |c| c.call(c, self, e, args) } unless hook.disabled?
96
+ raise
97
+ end.tap { |v| hook.after.each { |c| c.call(c, self, v, args) } unless hook.disabled? }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,219 @@
1
+ # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/dependency'
5
+
6
+ module Sqreen
7
+ module Dependency
8
+ class HookPointError < StandardError; end
9
+
10
+ class HookPoint
11
+ def self.parse(hook_point)
12
+ klass_name, separator, method_name = hook_point.split(/(\#|\.)/, 2)
13
+
14
+ raise ArgumentError, hook_point if klass_name.nil? || separator.nil? || method_name.nil?
15
+ raise ArgumentError, hook_point unless ['.', '#'].include?(separator)
16
+
17
+ method_kind = separator == '.' ? :klass_method : :instance_method
18
+
19
+ [klass_name.to_sym, method_kind, method_name.to_sym]
20
+ end
21
+
22
+ attr_reader :klass_name, :method_kind, :method_name
23
+
24
+ def initialize(hook_point)
25
+ @klass_name, @method_kind, @method_name = Sqreen::Dependency::HookPoint.parse(hook_point)
26
+ end
27
+
28
+ def to_s
29
+ "#{@klass_name}#{@method_kind == :instance_method ? '#' : '.'}#{@method_name}"
30
+ end
31
+
32
+ def exist?
33
+ return false unless Sqreen::Dependency.const_exist?(@klass_name)
34
+
35
+ if klass_method?
36
+ (klass.methods + klass.protected_methods + klass.private_methods).include?(@method_name)
37
+ elsif instance_method?
38
+ (klass.instance_methods + klass.protected_instance_methods + klass.private_instance_methods).include?(@method_name)
39
+ else
40
+ raise HookPointError, 'unknown hook point kind'
41
+ end
42
+ end
43
+
44
+ def klass
45
+ Sqreen::Dependency.resolve_const(@klass_name)
46
+ end
47
+
48
+ def klass_method?
49
+ @method_kind == :klass_method
50
+ end
51
+
52
+ def private_method?
53
+ if klass_method?
54
+ klass.private_methods.include?(@method_name)
55
+ elsif instance_method?
56
+ klass.private_instance_methods.include?(@method_name)
57
+ else
58
+ raise HookPointError, 'unknown hook point kind'
59
+ end
60
+ end
61
+
62
+ def protected_method?
63
+ if klass_method?
64
+ klass.protected_methods.include?(@method_name)
65
+ elsif instance_method?
66
+ klass.protected_instance_methods.include?(@method_name)
67
+ else
68
+ raise HookPointError, 'unknown hook point kind'
69
+ end
70
+ end
71
+
72
+ def instance_method?
73
+ @method_kind == :instance_method
74
+ end
75
+
76
+ def installed?(suffix)
77
+ if klass_method?
78
+ (klass.methods + klass.protected_methods + klass.private_methods).include?(:"#{method_name}_with_#{suffix}")
79
+ elsif instance_method?
80
+ (klass.instance_methods + klass.protected_instance_methods + klass.private_instance_methods).include?(:"#{method_name}_with_#{suffix}")
81
+ else
82
+ raise HookPointError, 'unknown hook point kind'
83
+ end
84
+ end
85
+
86
+ def apply(obj, suffix, *args, &block)
87
+ obj.send("#{method_name}_without_#{suffix}", *args, &block)
88
+ end
89
+
90
+ def install(suffix, &block)
91
+ if installed?(suffix)
92
+ Sqreen.log.debug "[#{Process.pid}] #{self} already installed"
93
+ end
94
+ unless exist?
95
+ Sqreen.log.debug "[#{Process.pid}] #{self} hook point not found"
96
+ end
97
+
98
+ define(suffix, &block)
99
+ enable(suffix)
100
+ end
101
+
102
+ def uninstall(suffix)
103
+ disable(suffix)
104
+ remove(suffix)
105
+ end
106
+
107
+ def enable(suffix)
108
+ chain(suffix)
109
+ end
110
+
111
+ def disable(suffix)
112
+ unchain(suffix)
113
+ end
114
+
115
+ def disabled?(suffix)
116
+ !chained?(suffix)
117
+ end
118
+
119
+ private
120
+
121
+ def define(suffix, &block)
122
+ hook_point = self
123
+ method_name = @method_name
124
+
125
+ if klass_method?
126
+ klass.singleton_class.instance_eval do
127
+ if hook_point.protected_method?
128
+ private
129
+ elsif hook_point.protected_method?
130
+ protected
131
+ else
132
+ public
133
+ end
134
+
135
+ define_method(:"#{method_name}_with_#{suffix}", &block)
136
+ end
137
+ elsif instance_method?
138
+ klass.class_eval do
139
+ if hook_point.protected_method?
140
+ private
141
+ elsif hook_point.protected_method?
142
+ protected
143
+ else
144
+ public
145
+ end
146
+
147
+ define_method(:"#{method_name}_with_#{suffix}", &block)
148
+ end
149
+ else
150
+ raise HookPointError, 'unknown hook point kind'
151
+ end
152
+ end
153
+
154
+ def remove(suffix)
155
+ method_name = @method_name
156
+
157
+ if klass_method?
158
+ klass.singleton_class.instance_eval do
159
+ remove_method(:"#{method_name}_with_#{suffix}")
160
+ end
161
+ elsif instance_method?
162
+ klass.class_eval do
163
+ remove_method(:"#{method_name}_with_#{suffix}")
164
+ end
165
+ else
166
+ raise HookPointError, 'unknown hook point kind'
167
+ end
168
+ end
169
+
170
+ def chained?(suffix)
171
+ method_name = @method_name
172
+
173
+ if klass_method?
174
+ klass.singleton_class.instance_eval do
175
+ instance_method(:"#{method_name}").original_name == :"#{method_name}_with_#{suffix}"
176
+ end
177
+ elsif instance_method?
178
+ klass.class_eval do
179
+ instance_method(:"#{method_name}").original_name == :"#{method_name}_with_#{suffix}"
180
+ end
181
+ else
182
+ raise HookPointError, 'unknown hook point kind'
183
+ end
184
+ end
185
+
186
+ def chain(suffix)
187
+ method_name = @method_name
188
+
189
+ if klass_method?
190
+ klass.singleton_class.instance_eval do
191
+ alias_method :"#{method_name}_without_#{suffix}", :"#{method_name}"
192
+ alias_method :"#{method_name}", :"#{method_name}_with_#{suffix}"
193
+ end
194
+ elsif instance_method?
195
+ klass.class_eval do
196
+ alias_method :"#{method_name}_without_#{suffix}", :"#{method_name}"
197
+ alias_method :"#{method_name}", :"#{method_name}_with_#{suffix}"
198
+ end
199
+ else
200
+ raise HookPointError, 'unknown hook point kind'
201
+ end
202
+ end
203
+
204
+ def unchain(suffix)
205
+ method_name = @method_name
206
+
207
+ if klass_method?
208
+ klass.singleton_class.instance_eval do
209
+ alias_method :"#{method_name}", :"#{method_name}_without_#{suffix}"
210
+ end
211
+ elsif instance_method?
212
+ klass.class_eval do
213
+ alias_method :"#{method_name}", :"#{method_name}_without_#{suffix}"
214
+ end
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end