sqreen 0.1.0.pre → 0.7.01461158029
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/CODE_OF_CONDUCT.md +22 -0
- data/README.md +77 -0
- data/Rakefile +40 -0
- data/lib/sqreen.rb +67 -0
- data/lib/sqreen/binding_accessor.rb +184 -0
- data/lib/sqreen/ca.crt +72 -0
- data/lib/sqreen/callback_tree.rb +78 -0
- data/lib/sqreen/callbacks.rb +120 -0
- data/lib/sqreen/capped_queue.rb +23 -0
- data/lib/sqreen/condition_evaluator.rb +169 -0
- data/lib/sqreen/conditionable.rb +50 -0
- data/lib/sqreen/configuration.rb +151 -0
- data/lib/sqreen/context.rb +22 -0
- data/lib/sqreen/deliveries/batch.rb +80 -0
- data/lib/sqreen/deliveries/simple.rb +36 -0
- data/lib/sqreen/detect.rb +14 -0
- data/lib/sqreen/detect/shell_injection.rb +61 -0
- data/lib/sqreen/detect/sql_injection.rb +115 -0
- data/lib/sqreen/event.rb +16 -0
- data/lib/sqreen/events/attack.rb +60 -0
- data/lib/sqreen/events/remote_exception.rb +53 -0
- data/lib/sqreen/exception.rb +31 -0
- data/lib/sqreen/frameworks.rb +40 -0
- data/lib/sqreen/frameworks/generic.rb +243 -0
- data/lib/sqreen/frameworks/rails.rb +155 -0
- data/lib/sqreen/frameworks/rails3.rb +36 -0
- data/lib/sqreen/frameworks/sinatra.rb +34 -0
- data/lib/sqreen/frameworks/sqreen_test.rb +26 -0
- data/lib/sqreen/instrumentation.rb +504 -0
- data/lib/sqreen/log.rb +116 -0
- data/lib/sqreen/metrics.rb +6 -0
- data/lib/sqreen/metrics/average.rb +39 -0
- data/lib/sqreen/metrics/base.rb +41 -0
- data/lib/sqreen/metrics/collect.rb +22 -0
- data/lib/sqreen/metrics/sum.rb +20 -0
- data/lib/sqreen/metrics_store.rb +94 -0
- data/lib/sqreen/parsers/sql.rb +98 -0
- data/lib/sqreen/parsers/sql_tokenizer.rb +266 -0
- data/lib/sqreen/parsers/unix.rb +110 -0
- data/lib/sqreen/payload_creator.rb +132 -0
- data/lib/sqreen/performance_notifications.rb +86 -0
- data/lib/sqreen/performance_notifications/log.rb +36 -0
- data/lib/sqreen/performance_notifications/metrics.rb +36 -0
- data/lib/sqreen/performance_notifications/newrelic.rb +36 -0
- data/lib/sqreen/remote_command.rb +82 -0
- data/lib/sqreen/rule_attributes.rb +25 -0
- data/lib/sqreen/rule_callback.rb +97 -0
- data/lib/sqreen/rules.rb +116 -0
- data/lib/sqreen/rules_callbacks.rb +29 -0
- data/lib/sqreen/rules_callbacks/binding_accessor_metrics.rb +79 -0
- data/lib/sqreen/rules_callbacks/count_http_codes.rb +18 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches.rb +24 -0
- data/lib/sqreen/rules_callbacks/crawler_user_agent_matches_metrics.rb +25 -0
- data/lib/sqreen/rules_callbacks/execjs.rb +136 -0
- data/lib/sqreen/rules_callbacks/headers_insert.rb +20 -0
- data/lib/sqreen/rules_callbacks/inspect_rule.rb +20 -0
- data/lib/sqreen/rules_callbacks/matcher_rule.rb +103 -0
- data/lib/sqreen/rules_callbacks/rails_parameters.rb +14 -0
- data/lib/sqreen/rules_callbacks/record_request_context.rb +23 -0
- data/lib/sqreen/rules_callbacks/reflected_xss.rb +40 -0
- data/lib/sqreen/rules_callbacks/regexp_rule.rb +36 -0
- data/lib/sqreen/rules_callbacks/shell.rb +33 -0
- data/lib/sqreen/rules_callbacks/shell_env.rb +32 -0
- data/lib/sqreen/rules_callbacks/sql.rb +41 -0
- data/lib/sqreen/rules_callbacks/system_shell.rb +25 -0
- data/lib/sqreen/rules_callbacks/url_matches.rb +25 -0
- data/lib/sqreen/rules_callbacks/user_agent_matches.rb +22 -0
- data/lib/sqreen/rules_signature.rb +142 -0
- data/lib/sqreen/runner.rb +312 -0
- data/lib/sqreen/runtime_infos.rb +127 -0
- data/lib/sqreen/session.rb +340 -0
- data/lib/sqreen/stats.rb +18 -0
- data/lib/sqreen/version.rb +6 -0
- metadata +95 -34
@@ -0,0 +1,36 @@
|
|
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/frameworks/rails'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Frameworks
|
8
|
+
# Handle Rails 3 specifics
|
9
|
+
class Rails3Framework < RailsFramework
|
10
|
+
def root
|
11
|
+
Rails.root
|
12
|
+
end
|
13
|
+
|
14
|
+
def prevent_startup
|
15
|
+
res = super
|
16
|
+
return res if res
|
17
|
+
return :rails_console if defined?(Rails::Console)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def instrument_when_ready!(instrumentor, rules)
|
22
|
+
config = Rails.configuration
|
23
|
+
if config.cache_classes
|
24
|
+
instrumentor.instrument!(rules, self)
|
25
|
+
else
|
26
|
+
# FIXME: What needs to be done if no active_record?
|
27
|
+
# (probably related to SQREEN-219)
|
28
|
+
frm = self
|
29
|
+
ActiveSupport.on_load(:active_record) do
|
30
|
+
instrumentor.instrument!(rules, frm)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
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
|
+
require 'sqreen/frameworks/generic'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Frameworks
|
8
|
+
# Handle Sinatra specific functions
|
9
|
+
class SinatraFramework < GenericFramework
|
10
|
+
def framework_infos
|
11
|
+
h = super
|
12
|
+
h[:framework_type] = 'Sinatra'
|
13
|
+
h[:framework_version] = Sinatra::VERSION
|
14
|
+
h
|
15
|
+
end
|
16
|
+
|
17
|
+
def db_settings(options = {})
|
18
|
+
adapter = options[:connection_adapter]
|
19
|
+
return nil unless adapter
|
20
|
+
|
21
|
+
begin
|
22
|
+
adapter_name = adapter.class.const_get 'ADAPTER_NAME'
|
23
|
+
rescue
|
24
|
+
# FIXME: we may want to log that
|
25
|
+
Sqreen.log.error 'cannot find ADAPTER_NAME'
|
26
|
+
return nil
|
27
|
+
end
|
28
|
+
db_type = DB_MAPPING[adapter_name]
|
29
|
+
db_infos = { :name => adapter_name }
|
30
|
+
[db_type, db_infos]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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/frameworks/generic'
|
5
|
+
|
6
|
+
module Sqreen
|
7
|
+
module Frameworks
|
8
|
+
# Rails related framework code
|
9
|
+
class SqreenTestFramework < GenericFramework
|
10
|
+
def framework_infos
|
11
|
+
{
|
12
|
+
:framework_type => 'SqreenTest',
|
13
|
+
:framework_version => '0.1',
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def client_ip
|
18
|
+
'127.0.0.1'
|
19
|
+
end
|
20
|
+
|
21
|
+
def request_infos
|
22
|
+
{}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,504 @@
|
|
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/callback_tree'
|
5
|
+
require 'sqreen/log'
|
6
|
+
require 'sqreen/stats'
|
7
|
+
require 'sqreen/exception'
|
8
|
+
require 'sqreen/performance_notifications'
|
9
|
+
require 'sqreen/events/remote_exception'
|
10
|
+
require 'sqreen/rules_signature'
|
11
|
+
require 'set'
|
12
|
+
|
13
|
+
# How to override a class method:
|
14
|
+
#
|
15
|
+
# class Cache
|
16
|
+
#
|
17
|
+
# def self.get3
|
18
|
+
# puts "GET3"
|
19
|
+
# end
|
20
|
+
# def self.get
|
21
|
+
# puts "GET"
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# class << Cache # Change context to metaclass of Cache
|
26
|
+
# def get_modified
|
27
|
+
# puts "GET MODIFI"
|
28
|
+
# end
|
29
|
+
# alias_method :get_not_modified, :get
|
30
|
+
# alias_method :get, :get_modified
|
31
|
+
# end
|
32
|
+
|
33
|
+
module Sqreen
|
34
|
+
class Instrumentation
|
35
|
+
@@override_semaphore = Mutex.new
|
36
|
+
|
37
|
+
## Overriden methods and callbacks globals
|
38
|
+
@@overriden_methods = []
|
39
|
+
@@registered_callbacks = CBTree.new
|
40
|
+
|
41
|
+
def self.semaphore
|
42
|
+
@@override_semaphore
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.callbacks
|
46
|
+
@@registered_callbacks
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.overriden
|
50
|
+
@@overriden_methods
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.callback_wrapper_pre(klass, method, instance, *args, &block)
|
54
|
+
Instrumentation.guard_call(method, []) do
|
55
|
+
callbacks = @@registered_callbacks.get(klass, method, :pre)
|
56
|
+
|
57
|
+
returns = []
|
58
|
+
callbacks.each do |cb|
|
59
|
+
rule = cb.rule_name if cb.respond_to?(:rule_name)
|
60
|
+
Sqreen.log.debug { "running pre cb #{cb}" }
|
61
|
+
Sqreen::PerformanceNotifications.instrument("Callbacks/#{rule || cb.class.name}/pre") do
|
62
|
+
begin
|
63
|
+
res = cb.send(:pre, instance, *args, &block)
|
64
|
+
if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
|
65
|
+
Sqreen.log.debug do
|
66
|
+
"#{cb} cannot block, overriding return value"
|
67
|
+
end
|
68
|
+
res = nil
|
69
|
+
elsif res.is_a?(Hash)
|
70
|
+
res[:rule_name] = rule
|
71
|
+
end
|
72
|
+
returns << res
|
73
|
+
rescue => e
|
74
|
+
Sqreen.log.error "we catch an exception: #{e.inspect}"
|
75
|
+
Sqreen.log.error e.backtrace
|
76
|
+
if cb.respond_to?(:record_exception)
|
77
|
+
cb.record_exception(e)
|
78
|
+
else
|
79
|
+
Sqreen::RemoteException.record(e)
|
80
|
+
end
|
81
|
+
next
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
returns
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.callback_wrapper_post(klass, method, return_val, instance, *args, &block)
|
90
|
+
Instrumentation.guard_call(method, []) do
|
91
|
+
callbacks = @@registered_callbacks.get(klass, method, :post)
|
92
|
+
|
93
|
+
returns = []
|
94
|
+
callbacks.reverse_each do |cb|
|
95
|
+
rule = cb.rule_name if cb.respond_to?(:rule_name)
|
96
|
+
Sqreen.log.debug { "running post cb #{cb}" }
|
97
|
+
Sqreen::PerformanceNotifications.instrument("Callbacks/#{rule || cb.class.name}/post") do
|
98
|
+
begin
|
99
|
+
res = cb.send(:post, return_val, instance, *args, &block)
|
100
|
+
if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
|
101
|
+
Sqreen.log.debug do
|
102
|
+
"#{cb} cannot block, overriding return value"
|
103
|
+
end
|
104
|
+
res = nil
|
105
|
+
elsif res.is_a?(Hash)
|
106
|
+
res[:rule_name] = rule
|
107
|
+
end
|
108
|
+
returns << res
|
109
|
+
rescue => e
|
110
|
+
Sqreen.log.error "we catch an exception: #{e.inspect}"
|
111
|
+
Sqreen.log.error e.backtrace
|
112
|
+
if cb.respond_to?(:record_exception)
|
113
|
+
cb.record_exception(e)
|
114
|
+
else
|
115
|
+
Sqreen::RemoteException.record(e)
|
116
|
+
end
|
117
|
+
next
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
returns
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.callback_wrapper_failing(exception, klass, method, instance, *args, &block)
|
126
|
+
Instrumentation.guard_call(method, []) do
|
127
|
+
callbacks = @@registered_callbacks.get(klass, method, :failing)
|
128
|
+
|
129
|
+
returns = []
|
130
|
+
callbacks.each do |cb|
|
131
|
+
rule = cb.rule_name if cb.respond_to?(:rule_name)
|
132
|
+
Sqreen.log.debug { "running failing cb #{cb}" }
|
133
|
+
Sqreen::PerformanceNotifications.instrument("Callbacks/#{rule || cb.class.name}/failing") do
|
134
|
+
begin
|
135
|
+
res = cb.send(:failing, exception, instance, *args, &block)
|
136
|
+
if !res.nil? && cb.respond_to?(:block) && (!cb.block && !Sqreen.config_get(:block_all_rules))
|
137
|
+
Sqreen.log.debug do
|
138
|
+
"#{cb} cannot block, overriding return value"
|
139
|
+
end
|
140
|
+
res = nil
|
141
|
+
elsif res.is_a?(Hash)
|
142
|
+
res[:rule_name] = rule
|
143
|
+
end
|
144
|
+
returns << res
|
145
|
+
rescue => e
|
146
|
+
Sqreen.log.error "we catch an exception: #{e.inspect}"
|
147
|
+
Sqreen.log.error e.backtrace
|
148
|
+
if cb.respond_to?(:record_exception)
|
149
|
+
cb.record_exception(e)
|
150
|
+
else
|
151
|
+
Sqreen::RemoteException.record(e)
|
152
|
+
end
|
153
|
+
next
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
returns
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.guard_call(method, retval)
|
162
|
+
if @sqreen_in_instr && @sqreen_in_instr.member?(method)
|
163
|
+
return retval
|
164
|
+
else
|
165
|
+
@sqreen_in_instr ||= Set.new
|
166
|
+
@sqreen_in_instr.add(method)
|
167
|
+
r = yield
|
168
|
+
@sqreen_in_instr.delete(method)
|
169
|
+
return r
|
170
|
+
end
|
171
|
+
rescue Exception => e
|
172
|
+
@sqreen_in_instr.delete(method)
|
173
|
+
raise e
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.define_callback_method(meth, original_meth, klass_name)
|
177
|
+
proc do |*args, &block|
|
178
|
+
Sqreen.stats.callbacks_calls += 1
|
179
|
+
|
180
|
+
skip = false
|
181
|
+
result = nil
|
182
|
+
|
183
|
+
# pre callback
|
184
|
+
returns = Instrumentation.callback_wrapper_pre(klass_name,
|
185
|
+
meth,
|
186
|
+
self,
|
187
|
+
*args,
|
188
|
+
&block)
|
189
|
+
returns.each do |ret|
|
190
|
+
next unless ret.is_a? Hash
|
191
|
+
case ret[:status]
|
192
|
+
when :skip, 'skip'
|
193
|
+
skip = true
|
194
|
+
result = ret[:new_return_value] if ret.key? :new_return_value
|
195
|
+
next
|
196
|
+
when :raise, 'raise'
|
197
|
+
fail Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
return result if skip
|
202
|
+
begin
|
203
|
+
result = send(original_meth, *args, &block)
|
204
|
+
rescue => e
|
205
|
+
returns = Instrumentation.callback_wrapper_failing(e, klass_name,
|
206
|
+
meth,
|
207
|
+
self,
|
208
|
+
*args,
|
209
|
+
&block)
|
210
|
+
will_retry = false
|
211
|
+
will_raise = returns.empty?
|
212
|
+
returns.each do |ret|
|
213
|
+
will_raise = true if ret.nil?
|
214
|
+
next unless ret.is_a? Hash
|
215
|
+
case ret[:status]
|
216
|
+
when :override, 'override'
|
217
|
+
result = ret[:new_return_value] if ret.key? :new_return_value
|
218
|
+
when :retry, 'retry'
|
219
|
+
will_retry = true
|
220
|
+
else # :reraise, 'reraise'
|
221
|
+
will_raise = true
|
222
|
+
end
|
223
|
+
end
|
224
|
+
raise e if will_raise
|
225
|
+
retry if will_retry
|
226
|
+
result
|
227
|
+
else
|
228
|
+
|
229
|
+
# post callback
|
230
|
+
returns = Instrumentation.callback_wrapper_post(klass_name,
|
231
|
+
meth,
|
232
|
+
result,
|
233
|
+
self,
|
234
|
+
*args,
|
235
|
+
&block)
|
236
|
+
returns.each do |ret|
|
237
|
+
next unless ret.is_a? Hash
|
238
|
+
case ret[:status]
|
239
|
+
when :raise, 'raise'
|
240
|
+
fail Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
|
241
|
+
when :override, 'override'
|
242
|
+
result = ret[:new_return_value]
|
243
|
+
else
|
244
|
+
next
|
245
|
+
end
|
246
|
+
end
|
247
|
+
result
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def override_class_method(klass, meth)
|
253
|
+
# FIXME: This is somehow ugly. We should reduce the amount of
|
254
|
+
# `evaled` code.
|
255
|
+
str = " class << #{klass}
|
256
|
+
|
257
|
+
original = '#{meth}'.to_sym
|
258
|
+
saved_meth_name = '#{get_saved_method_name(meth)}'.to_sym
|
259
|
+
new_method = '#{meth}_modified'.to_sym
|
260
|
+
|
261
|
+
alias_method saved_meth_name, original
|
262
|
+
|
263
|
+
p = Instrumentation.define_callback_method(original, saved_meth_name,
|
264
|
+
#{klass})
|
265
|
+
define_method(new_method, p)
|
266
|
+
|
267
|
+
private new_method
|
268
|
+
|
269
|
+
method_kind = nil
|
270
|
+
case
|
271
|
+
when public_method_defined?(original)
|
272
|
+
method_kind = :public
|
273
|
+
when protected_method_defined?(original)
|
274
|
+
method_kind = :protected
|
275
|
+
when private_method_defined?(original)
|
276
|
+
method_kind = :private
|
277
|
+
end
|
278
|
+
alias_method original, new_method
|
279
|
+
send(method_kind, original)
|
280
|
+
private saved_meth_name
|
281
|
+
end "
|
282
|
+
eval str
|
283
|
+
end
|
284
|
+
|
285
|
+
def unoverride_instance_method(obj, meth)
|
286
|
+
saved_meth_name = get_saved_method_name(meth)
|
287
|
+
|
288
|
+
method_kind = nil
|
289
|
+
obj.class_eval do
|
290
|
+
# Note: As a lambda the following will crash ruby 2.2.3p173
|
291
|
+
case
|
292
|
+
when public_method_defined?(meth)
|
293
|
+
method_kind = :public
|
294
|
+
when protected_method_defined?(meth)
|
295
|
+
method_kind = :protected
|
296
|
+
when private_method_defined?(meth)
|
297
|
+
method_kind = :private
|
298
|
+
end
|
299
|
+
alias_method meth, saved_meth_name
|
300
|
+
send(method_kind, meth)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def get_saved_method_name(meth)
|
305
|
+
"#{meth}_not_modified".to_sym
|
306
|
+
end
|
307
|
+
|
308
|
+
def override_instance_method(klass_name, meth)
|
309
|
+
saved_meth_name = get_saved_method_name(meth)
|
310
|
+
new_method = "#{meth}_modified".to_sym
|
311
|
+
|
312
|
+
p = Instrumentation.define_callback_method(meth, saved_meth_name,
|
313
|
+
klass_name)
|
314
|
+
method_kind = nil
|
315
|
+
klass_name.class_eval do
|
316
|
+
alias_method saved_meth_name, meth
|
317
|
+
|
318
|
+
define_method(new_method, p)
|
319
|
+
|
320
|
+
case
|
321
|
+
when public_method_defined?(meth)
|
322
|
+
method_kind = :public
|
323
|
+
when protected_method_defined?(meth)
|
324
|
+
method_kind = :protected
|
325
|
+
when private_method_defined?(meth)
|
326
|
+
method_kind = :private
|
327
|
+
end
|
328
|
+
alias_method meth, new_method
|
329
|
+
private saved_meth_name
|
330
|
+
private new_method
|
331
|
+
send(method_kind, meth)
|
332
|
+
end
|
333
|
+
saved_meth_name
|
334
|
+
end
|
335
|
+
|
336
|
+
# WARNING We do not actually remove `meth`
|
337
|
+
def unoverride_class_method(klass, meth)
|
338
|
+
saved_meth_name = get_saved_method_name(meth)
|
339
|
+
|
340
|
+
eval "method_kind = nil; class << #{klass}
|
341
|
+
new_method = '#{meth}_modified'.to_sym
|
342
|
+
case
|
343
|
+
when public_method_defined?(#{meth.to_sym.inspect})
|
344
|
+
method_kind = :public
|
345
|
+
when protected_method_defined?(original)
|
346
|
+
method_kind = :protected
|
347
|
+
when private_method_defined?(#{meth.to_sym.inspect})
|
348
|
+
method_kind = :private
|
349
|
+
end
|
350
|
+
alias_method #{meth.to_sym.inspect}, #{saved_meth_name.to_sym.inspect}
|
351
|
+
send(method_kind, #{meth.to_sym.inspect})
|
352
|
+
end "
|
353
|
+
end
|
354
|
+
|
355
|
+
if RUBY_VERSION < '1.9'
|
356
|
+
def adjust_method_name(method)
|
357
|
+
method.to_s
|
358
|
+
end
|
359
|
+
else
|
360
|
+
def adjust_method_name(method)
|
361
|
+
method
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def is_instance_method?(klass, method)
|
366
|
+
method = adjust_method_name(method)
|
367
|
+
klass.instance_methods.include?(method) ||
|
368
|
+
klass.private_instance_methods.include?(method)
|
369
|
+
end
|
370
|
+
|
371
|
+
def is_class_method?(klass, method)
|
372
|
+
method = adjust_method_name(method)
|
373
|
+
klass.singleton_methods.include? method
|
374
|
+
end
|
375
|
+
|
376
|
+
def add_callback(cb)
|
377
|
+
@@override_semaphore.synchronize do
|
378
|
+
klass = cb.klass
|
379
|
+
method = cb.method
|
380
|
+
key = [klass, method]
|
381
|
+
|
382
|
+
already_overriden = @@overriden_methods.include? key
|
383
|
+
|
384
|
+
if !already_overriden
|
385
|
+
if is_class_method?(klass, method)
|
386
|
+
Sqreen.log.debug "overriding class method for #{cb}"
|
387
|
+
success = override_class_method(klass, method)
|
388
|
+
elsif is_instance_method?(klass, method)
|
389
|
+
Sqreen.log.debug "overriding instance method for #{cb}"
|
390
|
+
success = override_instance_method(klass, method)
|
391
|
+
else
|
392
|
+
# FIXME: Override define_method and other dynamic ways to
|
393
|
+
# The following should be monitored to make sure we
|
394
|
+
# don't forget dynamically added methods:
|
395
|
+
# - define_method
|
396
|
+
# - method_added
|
397
|
+
# - method_missing
|
398
|
+
# ...
|
399
|
+
#
|
400
|
+
msg = "#{cb} is neither singleton or instance"
|
401
|
+
raise Sqreen::NotImplementedYet, msg
|
402
|
+
end
|
403
|
+
|
404
|
+
@@overriden_methods += [key] if success
|
405
|
+
else
|
406
|
+
Sqreen.log.debug "#{key} was already overriden"
|
407
|
+
end
|
408
|
+
|
409
|
+
@@registered_callbacks.add(cb)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def remove_callback(cb)
|
414
|
+
@@override_semaphore.synchronize do
|
415
|
+
remove_callback_no_lock(cb)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def remove_callback_no_lock(cb)
|
420
|
+
klass = cb.klass
|
421
|
+
method = cb.method
|
422
|
+
|
423
|
+
key = [klass, method]
|
424
|
+
|
425
|
+
already_overriden = @@overriden_methods.include? key
|
426
|
+
unless already_overriden
|
427
|
+
Sqreen.log.debug "#{key} not overriden, returning"
|
428
|
+
return
|
429
|
+
end
|
430
|
+
|
431
|
+
defined_cbs = @@registered_callbacks.get(klass, method)
|
432
|
+
|
433
|
+
nb_removed = 0
|
434
|
+
defined_cbs.each do |found_cb|
|
435
|
+
if found_cb == cb
|
436
|
+
Sqreen.log.debug "Removing callback #{found_cb}"
|
437
|
+
@@registered_callbacks.remove(found_cb)
|
438
|
+
nb_removed += 1
|
439
|
+
else
|
440
|
+
Sqreen.log.debug "Not removing callback #{found_cb} (remains #{defined_cbs.size} cbs)"
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
return unless nb_removed == defined_cbs.size
|
445
|
+
|
446
|
+
Sqreen.log.debug "Removing overriden method #{key}"
|
447
|
+
@@overriden_methods.delete(key)
|
448
|
+
|
449
|
+
if is_class_method?(klass, method)
|
450
|
+
unoverride_class_method(klass, method)
|
451
|
+
elsif is_instance_method?(klass, method)
|
452
|
+
unoverride_instance_method(klass, method)
|
453
|
+
else
|
454
|
+
# FIXME: Override define_method and other dynamic ways to
|
455
|
+
# The following should be monitored to make sure we
|
456
|
+
# don't forget dynamically added methods:
|
457
|
+
# - define_method
|
458
|
+
# - method_added
|
459
|
+
# - method_missing
|
460
|
+
# ...
|
461
|
+
#
|
462
|
+
msg = "#{cb} is neither singleton or instance"
|
463
|
+
raise Sqreen::NotImplementedYet, msg
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
def remove_all_callbacks
|
468
|
+
@@override_semaphore.synchronize do
|
469
|
+
@@registered_callbacks.entries.each do |cb|
|
470
|
+
remove_callback_no_lock(cb)
|
471
|
+
end
|
472
|
+
Sqreen.instrumentation_ready = false
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
attr_accessor :metrics_engine
|
477
|
+
|
478
|
+
# Instrument the application code using the rules
|
479
|
+
# @param rules [Array<Hash>] Rules to instrument
|
480
|
+
# @param metrics_engine [MetricsStore] Metric storage facility
|
481
|
+
def instrument!(rules, framework)
|
482
|
+
verifier = nil
|
483
|
+
if Sqreen.features['rules_signature'] &&
|
484
|
+
Sqreen.config_get(:rules_verify_signature) &&
|
485
|
+
!defined?(::JRUBY_VERSION)
|
486
|
+
verifier = Sqreen::RulesSignature.new
|
487
|
+
else
|
488
|
+
Sqreen.log.debug('Rules signature is not enabled')
|
489
|
+
end
|
490
|
+
remove_all_callbacks # Force cb tree to be empty before instrumenting
|
491
|
+
rules.each do |rule|
|
492
|
+
rcb = Sqreen::Rules.cb_from_rule(rule, metrics_engine, verifier)
|
493
|
+
next unless rcb
|
494
|
+
rcb.framework = framework
|
495
|
+
add_callback(rcb)
|
496
|
+
end
|
497
|
+
Sqreen.instrumentation_ready = true
|
498
|
+
end
|
499
|
+
|
500
|
+
def initialize(metrics_engine = nil)
|
501
|
+
self.metrics_engine = metrics_engine
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|