sqreen 1.11.3-java → 1.12.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d33d84719c5ef4d77abff52d3f9b3a364eca99359841886d931375cd2268660
4
- data.tar.gz: 0aaa8b8a1b12532240099cc52f619aeaf1204d9c61c9874c8a01a0229d17de34
3
+ metadata.gz: 7a79999b0619b1ce3b8fd31a8ae18744a8afee94f8ca52af3c62c16cb7dbd0c9
4
+ data.tar.gz: b51e44c2dffcc337120c349a99f71b0dcde9bbb4b092701fe6802cb8b7b5a910
5
5
  SHA512:
6
- metadata.gz: add27b4931f0355b3a9d68b6308f5b5a5b3763cfeb983bcbe10d3b265b58c20d7071ebbdce06be0045f2c403bad839b2b00a7d71c9fb4eb0324ccf444eae941a
7
- data.tar.gz: 44fdfe46547a83637c4fb1cd451d69f75caaba0b252a1537a8a82cee137034156a18555a3fb647aad8f73525b14ae4fedbab2ad9a51615dd0ab8b3fe054fc5df
6
+ metadata.gz: 7af93806486c4dc0140660d8b2df9e0fac024c1590e4eb7141271d8909543b05e5d548d92c7df6f4831ae5381f9fa87a2973c85fd232796ca3364a2e208dab73
7
+ data.tar.gz: 5cdc6cb90cf0ceb885cbd6d0aedff61d8aa18962bfd0eebead7f6556cad046dae81b4c4c60ec97ab124007e1fa2938b02cc3f6b01cde85142f3a18af11c54edf
@@ -0,0 +1,203 @@
1
+ # Copyright (c) 2018 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'ipaddr'
5
+ require 'sqreen/log'
6
+ require 'sqreen/exception'
7
+ require 'sqreen/sdk'
8
+ require 'sqreen/frameworks'
9
+
10
+ module Sqreen
11
+ # Implements actions (behavior taken in response to agent signals)
12
+ module Actions
13
+ # Exception for when an unknown action type is gotten from the server
14
+ class UnknownActionType < ::Sqreen::Exception
15
+ attr_reader :action_type
16
+ def initialize(action_type)
17
+ super("no such action type: #{action_type}. Must be one of #{Base.known_types}")
18
+ @action_type = action_type
19
+ end
20
+ end
21
+
22
+ # Where the currently loaded actions are stored. Singleton
23
+ class Repository
24
+ include Singleton
25
+
26
+ def initialize
27
+ @actions = {} # indexed by subclass
28
+ @actions.default_proc = proc { |h, k| h[k] = [] }
29
+ end
30
+
31
+ def <<(action)
32
+ @actions[action.class] << action
33
+ end
34
+
35
+ def [](action_class)
36
+ @actions[action_class]
37
+ end
38
+
39
+ def clear
40
+ @actions.clear
41
+ end
42
+ end
43
+
44
+ # @return [Sqreen::Actions::Base]
45
+ def self.deserialize_action(hash)
46
+ action_type = hash['action']
47
+ raise 'no action type available' unless action_type
48
+
49
+ subclass = Base.get_type_class(action_type)
50
+ raise UnknownActionType, action_type unless subclass
51
+
52
+ id = hash['action_id']
53
+ raise 'no action id available' unless id
54
+
55
+ duration = hash['duration']
56
+ if !duration.nil? && duration <= 0
57
+ Sqreen.log.debug "Action #{id} is already expired"
58
+ return nil
59
+ end
60
+
61
+ subclass.new(id, duration, hash['parameters'] || {})
62
+ end
63
+
64
+ class Base
65
+ attr_reader :id, :expiry
66
+
67
+ def initialize(id, duration)
68
+ @id = id
69
+ @expiry = Time.new + duration unless duration.nil?
70
+ end
71
+
72
+ # See Sqreen::CB for return values
73
+ def run(*args)
74
+ return if expiry && Time.new > expiry
75
+ ret = do_run *args
76
+ unless ret.nil?
77
+ Sqreen.internal_track(event_name,
78
+ 'properties' => event_properties(*args).
79
+ merge('action_id' => id))
80
+ end
81
+ ret
82
+ end
83
+
84
+ protected
85
+
86
+ def do_run(*_args)
87
+ raise ::Sqreen::NotImplementedYet, "do_run not implemented in #{self.class}"
88
+ # implement in subclasses
89
+ end
90
+
91
+ def event_properties(*_run_args)
92
+ raise ::Sqreen::NotImplementedYet, "event_properties not implemented in #{self.class}"
93
+ # implement in subclasses
94
+ end
95
+
96
+ private
97
+
98
+ def event_name
99
+ "sq.action.#{self.class.type_name}"
100
+ end
101
+
102
+ @@subclasses = {}
103
+ class << self
104
+ private :new
105
+
106
+ attr_reader :type_name
107
+
108
+ def get_type_class(name)
109
+ @@subclasses[name]
110
+ end
111
+
112
+ def known_types
113
+ @@subclasses.keys
114
+ end
115
+
116
+ def inherited(subclass)
117
+ class << subclass
118
+ public :new
119
+ end
120
+ end
121
+
122
+ protected
123
+
124
+ def type_name=(name)
125
+ @type_name = name
126
+ @@subclasses[name] = self
127
+ end
128
+ end
129
+ end
130
+
131
+ module IpRanges
132
+ attr_reader :ranges
133
+
134
+ def parse_ip_ranges(params)
135
+ ranges = params['ip_cidr']
136
+ unless ranges && ranges.is_a?(Array) && !ranges.empty?
137
+ raise 'no non-empty ip_cidr array present'
138
+ end
139
+
140
+ @ranges = ranges.map &IPAddr.method(:new)
141
+ end
142
+
143
+ def matches_ip?(client_ip)
144
+ parsed_ip = IPAddr.new client_ip
145
+ found = ranges.find { |r| r.include? parsed_ip }
146
+ return false unless found
147
+
148
+ Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
149
+ true
150
+ end
151
+ end
152
+
153
+ # Block a list of IP address ranges. Standard "raise" behavior.
154
+ class BlockIp < Base
155
+ include IpRanges
156
+ self.type_name = 'block_ip'
157
+
158
+ def initialize(id, duration, params = {})
159
+ super(id, duration)
160
+ parse_ip_ranges params
161
+ end
162
+
163
+ def do_run(client_ip)
164
+ return nil unless matches_ip? client_ip
165
+ e = Sqreen::AttackBlocked.new("Blocked client's IP (action: #{id}). No action is required")
166
+ { :status => :raise, :exception => e }
167
+ end
168
+
169
+ def event_properties(client_ip)
170
+ { 'ip_address' => client_ip }
171
+ end
172
+ end
173
+
174
+ # Block a list of IP address ranges by forcefully redirecting the user
175
+ # to a specific URL.
176
+ class RedirectIp < Base
177
+ include IpRanges
178
+ self.type_name = 'redirect_ip'
179
+
180
+ attr_reader :redirect_url
181
+
182
+ def initialize(id, duration, params = {})
183
+ super(id, duration)
184
+ @redirect_url = params['url']
185
+ raise "no url provided for action #{id}" unless @redirect_url
186
+ parse_ip_ranges params
187
+ end
188
+
189
+ def do_run(client_ip)
190
+ return nil unless matches_ip? client_ip
191
+ Sqreen.log.info "Will request redirect for client with IP #{client_ip} (action: #{id}). "
192
+ {
193
+ :status => :skip,
194
+ :new_return_value => [303, { 'Location' => @redirect_url }, ['']],
195
+ }
196
+ end
197
+
198
+ def event_properties(client_ip)
199
+ { 'ip_address' => client_ip, 'url' => @redirect_url }
200
+ end
201
+ end
202
+ end
203
+ end
@@ -9,13 +9,14 @@ module Sqreen
9
9
  # the value located at the given binding
10
10
  class BindingAccessor
11
11
  PathElem = Struct.new(:kind, :value)
12
- attr_reader :path, :expression, :final_transform
12
+ attr_reader :path, :expression, :final_transform, :transform_args
13
13
 
14
14
  # Expression to be accessed
15
15
  # @param expression [String] expression to read
16
16
  # @param convert [Boolean] wheter to convert objects to
17
17
  # simpler types (Array, Hash, String...)
18
18
  def initialize(expression, convert = false)
19
+ @transform_args = []
19
20
  @final_transform = nil
20
21
  @expression = expression
21
22
  @path = []
@@ -41,6 +42,19 @@ module Sqreen
41
42
  value
42
43
  end
43
44
 
45
+ # implement eql? and hash for uniq
46
+ def hash
47
+ expression.hash ^ @convert.hash
48
+ end
49
+
50
+ def eql?(other)
51
+ self.class == other.class &&
52
+ expression == other.expression &&
53
+ @convert == other.instance_variable_get('@convert')
54
+ end
55
+
56
+ alias == eql?
57
+
44
58
  protected
45
59
 
46
60
  STRING_KIND = 'string'.freeze
@@ -138,8 +152,17 @@ module Sqreen
138
152
  parts.join('|').rstrip
139
153
  end
140
154
 
155
+ TRANSFORM_ARGS_REGEXP = /\(([^)]*)\)\z/
141
156
  def final_transform=(transform)
142
157
  transform.strip!
158
+
159
+ transform.sub!(TRANSFORM_ARGS_REGEXP, '')
160
+ @transform_args = if Regexp.last_match
161
+ Regexp.last_match(1).split(/,\s*/)
162
+ else
163
+ []
164
+ end
165
+
143
166
  unless KNOWN_TRANSFORMS.include?(transform)
144
167
  raise Sqreen::Exception, "Invalid transform #{transform}"
145
168
  end
@@ -277,12 +300,58 @@ module Sqreen
277
300
  end
278
301
  values
279
302
  end
280
- end
303
+
304
+ def concat_keys_and_values(value, max_size = nil)
305
+ return nil if value.nil?
306
+ values = Set.new
307
+ max_size = max_size.to_i if max_size
308
+ res = ''
309
+ descend(value) do |x|
310
+ next unless values.add?(x)
311
+ x = x.to_s
312
+ return res if max_size && res.size + x.size + 1 > max_size
313
+ res << "\n" unless res.empty?
314
+ res << x
315
+ end
316
+ res
317
+ end
318
+
319
+ private
320
+
321
+ def descend(value, max_iter = 1000)
322
+ seen = Set.new
323
+ look_into = [value]
324
+ idx = 0
325
+ until look_into.empty? || max_iter <= idx
326
+ idx += 1
327
+ val = look_into.pop
328
+
329
+ case val
330
+ when Hash
331
+ next unless seen.add?(val.object_id)
332
+ look_into.concat(val.keys)
333
+ look_into.concat(val.values)
334
+ when Array
335
+ next unless seen.add?(val.object_id)
336
+ look_into.concat(val)
337
+ else
338
+ next if val.respond_to?(:seek)
339
+ if val.respond_to?(:each)
340
+ next unless seen.add?(val.object_id)
341
+ val.each { |v| look_into << v }
342
+ else
343
+ yield val
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end # end module Transforms
349
+
281
350
  include Transforms
282
351
  KNOWN_TRANSFORMS = Transforms.public_instance_methods.map(&:to_s)
283
352
 
284
353
  def transform(value)
285
- send(@final_transform, value) if @final_transform
354
+ send(@final_transform, value, *@transform_args) if @final_transform
286
355
  end
287
- end
356
+ end # end class BindingAccessor
288
357
  end
@@ -109,4 +109,59 @@ module Sqreen
109
109
  @block.call
110
110
  end
111
111
  end
112
+
113
+ # Framework-aware callback
114
+ class FrameworkCB < CB
115
+ attr_accessor :framework
116
+
117
+ def whitelisted?
118
+ whitelisted = SharedStorage.get(:whitelisted)
119
+ return whitelisted unless whitelisted.nil?
120
+ framework && !framework.whitelisted_match.nil?
121
+ end
122
+
123
+ # Record an attack event into Sqreen system
124
+ # @param infos [Hash] Additional information about request
125
+ def record_event(infos, at = Time.now.utc)
126
+ return unless framework
127
+ payload = {
128
+ :infos => infos,
129
+ :rulespack_id => rulespack_id,
130
+ :rule_name => rule_name,
131
+ :test => test,
132
+ :time => at,
133
+ }
134
+ if payload_tpl.include?('context')
135
+ payload[:backtrace] = Sqreen::Context.new.bt
136
+ end
137
+ framework.observe(:attacks, payload, payload_tpl)
138
+ end
139
+
140
+ # Record a metric observation
141
+ # @param category [String] Name of the metric observed
142
+ # @param key [String] aggregation key
143
+ # @param observation [Object] data observed
144
+ # @param at [Time] time when observation was made
145
+ def record_observation(category, key, observation, at = Time.now.utc)
146
+ return unless framework
147
+ framework.observe(:observations, [category, key, observation, at], [], false)
148
+ end
149
+
150
+ # Record an exception that just occurred
151
+ # @param exception [Exception] Exception to send over
152
+ # @param infos [Hash] Additional contextual information
153
+ def record_exception(exception, infos = {}, at = Time.now.utc)
154
+ return unless framework
155
+ payload = {
156
+ :exception => exception,
157
+ :infos => infos,
158
+ :rulespack_id => rulespack_id,
159
+ :rule_name => rule_name,
160
+ :test => test,
161
+ :time => at,
162
+ :backtrace => exception.backtrace || Sqreen::Context.bt,
163
+ }
164
+ framework.observe(:sqreen_exceptions, payload)
165
+ end
166
+ end
112
167
  end
@@ -68,7 +68,13 @@ module Sqreen
68
68
 
69
69
  def event_keys(event)
70
70
  return [event_key(event)] unless event.is_a?(Sqreen::RequestRecord)
71
- event.observed.fetch(:attacks, []).map { |e| "att-#{e[:rule_name]}" } + event.observed.fetch(:sqreen_exceptions, []).map { |e| "rex-#{e[:exception].class}" }
71
+ res = []
72
+ res += event.observed.fetch(:attacks, []).map { |e| "att-#{e[:rule_name]}" }
73
+ res += event.observed.fetch(:sqreen_exceptions, []).map { |e| "rex-#{e[:exception].class}" }
74
+ res += event.observed.fetch(:sdk, []).select { |e|
75
+ e[0] == :track
76
+ }.map { |e| "sdk-track".freeze }
77
+ return res
72
78
  end
73
79
 
74
80
  def event_key(event)
@@ -10,6 +10,7 @@ require 'sqreen/events/remote_exception'
10
10
  require 'sqreen/rules_signature'
11
11
  require 'sqreen/shared_storage'
12
12
  require 'sqreen/rules_callbacks/record_request_context'
13
+ require 'sqreen/rules_callbacks/run_req_start_actions'
13
14
  require 'set'
14
15
 
15
16
  # How to override a class method:
@@ -310,6 +311,7 @@ module Sqreen
310
311
  args = ret[:args]
311
312
  when :raise, 'raise'
312
313
  Thread.current[:sqreen_in_use] = false
314
+ raise ret[:exception] if ret.key?(:exception)
313
315
  raise Sqreen::AttackBlocked, "Sqreen blocked a security threat (type: #{ret[:rule_name]}). No action is required."
314
316
  end
315
317
  end
@@ -659,9 +661,16 @@ module Sqreen
659
661
 
660
662
  attr_accessor :metrics_engine
661
663
 
664
+ # @return [Array<Sqreen::CB>]
665
+ def hardcoded_callbacks(framework)
666
+ [
667
+ Sqreen::Rules::RunReqStartActions.new(framework)
668
+ ]
669
+ end
670
+
662
671
  # Instrument the application code using the rules
663
672
  # @param rules [Array<Hash>] Rules to instrument
664
- # @param metrics_engine [MetricsStore] Metric storage facility
673
+ # @param framework [Sqreen::Frameworks::GenericFramework]
665
674
  def instrument!(rules, framework)
666
675
  verifier = nil
667
676
  if Sqreen.features['rules_signature'] &&
@@ -671,13 +680,18 @@ module Sqreen
671
680
  else
672
681
  Sqreen.log.debug('Rules signature is not enabled')
673
682
  end
683
+
674
684
  remove_all_callbacks # Force cb tree to be empty before instrumenting
685
+
675
686
  rules.each do |rule|
676
687
  rcb = Sqreen::Rules.cb_from_rule(rule, self, metrics_engine, verifier)
677
688
  next unless rcb
678
689
  rcb.framework = framework
679
690
  add_callback(rcb)
680
691
  end
692
+
693
+ hardcoded_callbacks(framework).each { |cb| add_callback(cb) }
694
+
681
695
  Sqreen.instrumentation_ready = true
682
696
  end
683
697
 
@@ -10,6 +10,7 @@ module Sqreen
10
10
  :instrumentation_enable => :setup_instrumentation,
11
11
  :instrumentation_remove => :remove_instrumentation,
12
12
  :rules_reload => :reload_rules,
13
+ :actions_reload => :reload_actions,
13
14
  :features_get => :features,
14
15
  :features_change => :change_features,
15
16
  :force_logout => :shutdown,
@@ -19,6 +20,14 @@ module Sqreen
19
20
  :performance_budget => :change_performance_budget,
20
21
  }.freeze
21
22
 
23
+ # wraps output returned by a command that should also result in status: false
24
+ class FailureOutput
25
+ attr_reader :wrapped_output
26
+ def initialize(output)
27
+ @wrapped_output = output
28
+ end
29
+ end
30
+
22
31
  attr_reader :uuid
23
32
 
24
33
  def initialize(json_desc)
@@ -83,11 +92,13 @@ module Sqreen
83
92
  def format_output(output)
84
93
  case output
85
94
  when NilClass
86
- return { :status => false, :reason => 'nil returned' }
95
+ { :status => false, :reason => 'nil returned' }
87
96
  when TrueClass
88
- return { :status => true }
97
+ { :status => true }
98
+ when FailureOutput
99
+ { :status => false, :output => output.wrapped_output }
89
100
  else
90
- return { :status => true, :output => output }
101
+ { :status => true, :output => output }
91
102
  end
92
103
  end
93
104
  end
@@ -14,7 +14,7 @@ require 'sqreen/payload_creator'
14
14
  module Sqreen
15
15
  module Rules
16
16
  # Base class for callback that are initialized by rules from Sqreen
17
- class RuleCB < CB
17
+ class RuleCB < FrameworkCB
18
18
  include Conditionable
19
19
  include CallCountable
20
20
  # If nothing was asked by the rule we will ask for all sections available
@@ -23,7 +23,6 @@ module Sqreen
23
23
  attr_reader :test
24
24
  attr_reader :payload_tpl
25
25
  attr_reader :block
26
- attr_accessor :framework
27
26
 
28
27
  # @params klass [String] class instrumented
29
28
  # @params method [String] method that was instrumented
@@ -48,12 +47,6 @@ module Sqreen
48
47
  @rule[Attrs::RULESPACK_ID]
49
48
  end
50
49
 
51
- def whitelisted?
52
- whitelisted = SharedStorage.get(:whitelisted)
53
- return whitelisted unless whitelisted.nil?
54
- framework && !framework.whitelisted_match.nil?
55
- end
56
-
57
50
  # Recommend taking an action (optionnally adding more data/context)
58
51
  #
59
52
  # This will format the requested action and optionnally
@@ -63,50 +56,6 @@ module Sqreen
63
56
  additional_data.merge(:status => action)
64
57
  end
65
58
 
66
- # Record an attack event into Sqreen system
67
- # @param infos [Hash] Additional information about request
68
- def record_event(infos, at = Time.now.utc)
69
- return unless framework
70
- payload = {
71
- :infos => infos,
72
- :rulespack_id => rulespack_id,
73
- :rule_name => rule_name,
74
- :test => test,
75
- :time => at,
76
- }
77
- if payload_tpl.include?('context')
78
- payload[:backtrace] = Sqreen::Context.new.bt
79
- end
80
- framework.observe(:attacks, payload, payload_tpl)
81
- end
82
-
83
- # Record a metric observation
84
- # @param category [String] Name of the metric observed
85
- # @param key [String] aggregation key
86
- # @param observation [Object] data observed
87
- # @param at [Time] time when observation was made
88
- def record_observation(category, key, observation, at = Time.now.utc)
89
- return unless framework
90
- framework.observe(:observations, [category, key, observation, at], [], false)
91
- end
92
-
93
- # Record an exception that just occurred
94
- # @param exception [Exception] Exception to send over
95
- # @param infos [Hash] Additional contextual information
96
- def record_exception(exception, infos = {}, at = Time.now.utc)
97
- return unless framework
98
- payload = {
99
- :exception => exception,
100
- :infos => infos,
101
- :rulespack_id => rulespack_id,
102
- :rule_name => rule_name,
103
- :test => test,
104
- :time => at,
105
- :backtrace => exception.backtrace || Sqreen::Context.bt,
106
- }
107
- framework.observe(:sqreen_exceptions, payload)
108
- end
109
-
110
59
  def overtime!
111
60
  return false unless @overtimeable
112
61
  Sqreen.log.debug { "rulecb #{self} is overtime!" }
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2018 Sqreen. All Rights Reserved.
2
+ # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
+
4
+ require 'sqreen/rule_callback'
5
+ require 'sqreen/actions'
6
+ require 'sqreen/middleware'
7
+
8
+ module Sqreen
9
+ module Rules
10
+ # Runs actions concerned with whether the request ought to be served
11
+ class RunReqStartActions < FrameworkCB
12
+ def initialize(framework)
13
+ if defined?(Sqreen::Frameworks::SinatraFramework) &&
14
+ framework.is_a?(Sqreen::Frameworks::SinatraFramework)
15
+ super(Sinatra::ExtendedRack, :call)
16
+ elsif defined?(Sqreen::Frameworks::RailsFramework) &&
17
+ framework.is_a?(Sqreen::Frameworks::RailsFramework)
18
+ super(Sqreen::RailsMiddleware, :call)
19
+ else
20
+ # last resort; we won't get nice errors
21
+ super(Sqreen::Middleware, :call)
22
+ end
23
+
24
+ self.framework = framework
25
+ end
26
+
27
+ def whitelisted?
28
+ whitelisted = SharedStorage.get(:whitelisted)
29
+ return whitelisted unless whitelisted.nil?
30
+ framework && !framework.whitelisted_match.nil?
31
+ end
32
+
33
+ def pre(_inst, _args, _budget = nil, &_block)
34
+ return unless framework
35
+ ip = framework.client_ip
36
+ return unless ip
37
+
38
+ actions = actions_repo[Sqreen::Actions::BlockIp] +
39
+ actions_repo[Sqreen::Actions::RedirectIp]
40
+
41
+ actions.each do |act|
42
+ res = run_client_ip_action(act, ip)
43
+ return res unless res.nil?
44
+ end
45
+ nil
46
+ end
47
+
48
+ private
49
+
50
+ # @param action [Sqreen::Actions::Base]
51
+ def run_client_ip_action(action, client_ip)
52
+ action.run client_ip
53
+ end
54
+
55
+ # @return [Sqreen::Actions::Repository]
56
+ def actions_repo
57
+ Sqreen::Actions::Repository.instance
58
+ end
59
+ end
60
+ end
61
+ end
@@ -214,6 +214,7 @@ module Sqreen
214
214
  def remove_instrumentation(_context_infos = {})
215
215
  Sqreen.log.debug 'removing instrumentation'
216
216
  instrumenter.remove_all_callbacks
217
+ Sqreen::Actions::Repository.instance.clear
217
218
  true
218
219
  end
219
220
 
@@ -221,12 +222,43 @@ module Sqreen
221
222
  Sqreen.log.debug 'Reloading rules'
222
223
  rulespack_id, rules = load_rules
223
224
  instrumenter.remove_all_callbacks
225
+ Sqreen::Actions::Repository.instance.clear
224
226
 
225
227
  @framework.instrument_when_ready!(instrumenter, rules)
226
228
  Sqreen.log.debug 'Rules reloaded'
227
229
  rulespack_id.to_s
228
230
  end
229
231
 
232
+ def reload_actions(_context_infos = {})
233
+ Sqreen.log.debug 'Reloading actions'
234
+
235
+ data = session.get_actionspack
236
+
237
+ unless data.respond_to?(:[]) && data['status']
238
+ Sqreen.log.warn('Could not load actions')
239
+ return RemoteCommand::FailureOutput.new(
240
+ :error => 'Could not load actions from /actionspack'
241
+ )
242
+ end
243
+
244
+ action_hashes = data['actions']
245
+ unless action_hashes.respond_to? :each
246
+ Sqreen.log.warn('No action definitions in response')
247
+ return RemoteCommand::FailureOutput.new(
248
+ :error => 'No action definitions in response'
249
+ )
250
+ end
251
+ Sqreen.log.debug("Loading actions from hashes #{action_hashes}")
252
+
253
+ unsupported = load_actions(action_hashes)
254
+
255
+ if unsupported.empty?
256
+ true
257
+ else
258
+ RemoteCommand::FailureOutput.new(:unsupported_actions => unsupported.to_a)
259
+ end
260
+ end
261
+
230
262
  def process_commands(commands, context_infos = {})
231
263
  return if commands.nil? || commands.empty?
232
264
  res = RemoteCommand.process_list(self, commands, context_infos)
@@ -380,5 +412,32 @@ module Sqreen
380
412
  end
381
413
  end
382
414
  end
415
+
416
+ private
417
+
418
+ def load_actions(hashes)
419
+ unsupported = Set.new
420
+
421
+ actions = hashes.map do |h|
422
+ begin
423
+ Sqreen::Actions.deserialize_action(h)
424
+ rescue Sqreen::Actions::UnknownActionType => e
425
+ Sqreen.log.warn("Unsupported action type: #{e.action_type}")
426
+ unsupported << e.action_type
427
+ nil
428
+ rescue => e
429
+ raise Sqreen::Exception, "Invalid action hash: #{h}: #{e.message}"
430
+ end
431
+ end
432
+
433
+ actions = actions.reject(&:nil?)
434
+ Sqreen.log.debug("Will add #{actions.size} valid actions")
435
+
436
+ repos = Sqreen::Actions::Repository.instance
437
+ repos.clear
438
+ actions.each { |action| repos << action }
439
+
440
+ unsupported
441
+ end
383
442
  end
384
443
  end
@@ -3,6 +3,10 @@
3
3
 
4
4
  # Sqreen Namespace
5
5
  module Sqreen
6
+
7
+ SDK_RESERVED_PREFIX = 'sq.'.freeze
8
+ TRACK_PAYLOAD_DATA = ['request'.freeze, 'params'.freeze, 'headers'.freeze].freeze
9
+
6
10
  # Sqreen SDK
7
11
  class << self
8
12
  # Authentication tracking method
@@ -18,5 +22,35 @@ module Sqreen
18
22
  [], false
19
23
  )
20
24
  end
25
+
26
+ def track(event_name, options = {})
27
+ return unless Sqreen.framework
28
+ if event_name.start_with? SDK_RESERVED_PREFIX
29
+ Sqreen.log.warn("Event names starting with '#{SDK_RESERVED_PREFIX}' " \
30
+ 'are reserved. Event ignored.')
31
+ return false
32
+ end
33
+ internal_track(event_name, options)
34
+ end
35
+
36
+ # For internal usage. Users are to call track() instead.
37
+ def internal_track(event_name, options = {})
38
+ properties = options[:properties]
39
+ authentication_keys = options[:user_identifiers]
40
+ timestamp = options[:timestamp] || Time.now.utc
41
+ # Not in SDK v0
42
+ # request = options[:request]
43
+
44
+ args = {}
45
+ args[:authentication_keys] = authentication_keys if authentication_keys
46
+ args[:properties] = properties if properties
47
+
48
+ Sqreen.framework.observe(
49
+ :sdk,
50
+ [:track, timestamp, event_name, :args => args],
51
+ TRACK_PAYLOAD_DATA, true
52
+ )
53
+ true
54
+ end
21
55
  end
22
56
  end
@@ -159,8 +159,9 @@ module Sqreen
159
159
 
160
160
  def do_http_request(method, path, data, headers = {}, max_retry = 2)
161
161
  connect unless connected?
162
+ now = Time.now.utc
162
163
  headers['X-Session-Key'] = @session_id if @session_id
163
- headers['X-Sqreen-Time'] = Time.now.utc.to_f.to_s
164
+ headers['X-Sqreen-Time'] = now.to_f.to_s
164
165
  headers['User-Agent'] = "sqreen-ruby/#{Sqreen::VERSION}"
165
166
  headers['X-Sqreen-Beta'] = format('pid=%d;tid=%s;nb=%d;t=%f',
166
167
  Process.pid,
@@ -206,7 +207,7 @@ module Sqreen
206
207
  Sqreen.log.debug 'warning: empty return value'
207
208
  end
208
209
  end
209
- Sqreen.log.debug format('%s %s (DONE)', method, path)
210
+ Sqreen.log.debug format('%s %s (DONE in %f ms)', method, path, (Time.now.utc - now) * 1000)
210
211
  res
211
212
  end
212
213
 
@@ -264,6 +265,10 @@ module Sqreen
264
265
  'dependencies' => dependencies)
265
266
  end
266
267
 
268
+ def get_actionspack
269
+ resilient_get('actionspack')
270
+ end
271
+
267
272
  def post_request_record(request_record)
268
273
  resilient_post('request_record', request_record.to_hash)
269
274
  end
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
  module Sqreen
4
- VERSION = '1.11.3'.freeze
4
+ VERSION = '1.12.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqreen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.3
4
+ version: 1.12.0
5
5
  platform: java
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-26 00:00:00.000000000 Z
11
+ date: 2018-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: execjs
15
- version_requirements: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 0.3.0
20
14
  requirement: !ruby/object:Gem::Requirement
21
15
  requirements:
22
16
  - - ">="
23
17
  - !ruby/object:Gem::Version
24
18
  version: 0.3.0
19
+ name: execjs
25
20
  prerelease: false
26
21
  type: :runtime
27
- - !ruby/object:Gem::Dependency
28
- name: therubyrhino
29
22
  version_requirements: !ruby/object:Gem::Requirement
30
23
  requirements:
31
24
  - - ">="
32
25
  - !ruby/object:Gem::Version
33
- version: '0'
26
+ version: 0.3.0
27
+ - !ruby/object:Gem::Dependency
34
28
  requirement: !ruby/object:Gem::Requirement
35
29
  requirements:
36
30
  - - ">="
37
31
  - !ruby/object:Gem::Version
38
32
  version: '0'
33
+ name: therubyrhino
39
34
  prerelease: false
40
35
  type: :runtime
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
41
  description: Sqreen is a SaaS based Application protection and monitoring platform that integrates directly into your Ruby applications. Learn more at https://sqreen.io.
42
42
  email: contact@sqreen.io
43
43
  executables: []
@@ -49,6 +49,7 @@ files:
49
49
  - Rakefile
50
50
  - lib/sqreen-alt.rb
51
51
  - lib/sqreen.rb
52
+ - lib/sqreen/actions.rb
52
53
  - lib/sqreen/attack_detected.html
53
54
  - lib/sqreen/binding_accessor.rb
54
55
  - lib/sqreen/ca.crt
@@ -109,6 +110,7 @@ files:
109
110
  - lib/sqreen/rules_callbacks/record_request_context.rb
110
111
  - lib/sqreen/rules_callbacks/reflected_xss.rb
111
112
  - lib/sqreen/rules_callbacks/regexp_rule.rb
113
+ - lib/sqreen/rules_callbacks/run_req_start_actions.rb
112
114
  - lib/sqreen/rules_callbacks/shell_env.rb
113
115
  - lib/sqreen/rules_callbacks/url_matches.rb
114
116
  - lib/sqreen/rules_callbacks/user_agent_matches.rb