sqreen 1.20.0 → 1.20.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f18004c730291041540b696217f1c70b449e179328f959705a07a148364bfe9
4
- data.tar.gz: 417f8bb15cbee7faf4f1aa127552e38f0c929255493341d0f683daae37ab9894
3
+ metadata.gz: 7be645cedf514d1af5754fc7244738e17f817aad09761d2da7d7fa032ac55801
4
+ data.tar.gz: f81295c9ac98714284d6368bc5e01c1a857317c8dad98d53366b24e53cc6cc47
5
5
  SHA512:
6
- metadata.gz: f891f7785362829c23028206d6ba656ae3860e8c13cb265a839608c2cf02ba5be6576337c8d2630d1b0c8eb5c69e9dc732d7c68581f9bb472157e900f7a49a15
7
- data.tar.gz: b1e2e6708cfc177b66e5fe25dc85a9839e784dd4afffd49585f06050311d821c3eee9fe06ddff63a4eddabfc94b9307ca54e4a7301ebbcd5fc4d82096ec8efa7
6
+ metadata.gz: 986748fc2c11ee0a6ea7a997d661246d2840e170fdcbe5d7c1221fb189e3b6a9281e4feb1025f58f1ac6712ff10f598caa7d81867a7a62898138e743eadc9262
7
+ data.tar.gz: 39b246ce351e780f539a0d2fe3df9791996d8203056545ab5472f0deec2e1526351bfc9cbb4f8c4cd5c145969db0b177b1b559a7491e4a8a15de4e9dd3721665
@@ -1,3 +1,7 @@
1
+ ## 1.20.1
2
+
3
+ * Add fallback mechanisms when connecting to new Sqreen backend API domains
4
+
1
5
  ## 1.20.0
2
6
 
3
7
  * Enable new instrumentation engine by default
@@ -0,0 +1,20 @@
1
+ require 'digest'
2
+
3
+ module Sqreen
4
+ class AgentMessage
5
+ def initialize(kind, message, id = nil)
6
+ id ||= message + "\x00" + kind
7
+ @hash_hex = Digest::SHA1.hexdigest(id)
8
+ @kind = kind
9
+ @message = message
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ id: @hash_hex,
15
+ kind: @kind,
16
+ message: @message,
17
+ }
18
+ end
19
+ end
20
+ end
@@ -70,3 +70,27 @@ WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
70
70
  4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
71
71
  hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
72
72
  -----END CERTIFICATE-----
73
+ -----BEGIN CERTIFICATE-----
74
+ MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
75
+ EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
76
+ HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
77
+ ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
78
+ MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
79
+ VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
80
+ ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
81
+ dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
82
+ hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
83
+ OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
84
+ 8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
85
+ Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
86
+ hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
87
+ 6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
88
+ DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
89
+ AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
90
+ bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
91
+ ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
92
+ qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
93
+ iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
94
+ 0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
95
+ sSi6
96
+ -----END CERTIFICATE-----
@@ -43,9 +43,9 @@ module Sqreen
43
43
  { :env => :SQREEN_WEAVE_STRATEGY, :name => :weave_strategy,
44
44
  :default => :prepend, :convert => :to_sym },
45
45
  { :env => :SQREEN_URL, :name => :url,
46
- :default => 'https://back.sqreen.io' },
46
+ :default => nil },
47
47
  { :env => :SQREEN_INGESTION_URL, :name => :ingestion_url,
48
- :default => 'https://ingestion.sqreen.com/' },
48
+ :default => nil },
49
49
  { :env => :SQREEN_PROXY_URL, :name => :proxy_url,
50
50
  :default => nil },
51
51
  { :env => :SQREEN_TOKEN, :name => :token,
@@ -78,6 +78,8 @@ module Sqreen
78
78
  :default => nil },
79
79
  { :env => :SQREEN_STRIP_SENSITIVE_REGEX, :name => :strip_sensitive_regex,
80
80
  :default => nil },
81
+ { :env => :SQREEN_NO_SNIFF_DOMAINS, :name => :no_sniff_domains,
82
+ :default => false },
81
83
 
82
84
  ].freeze
83
85
 
@@ -0,0 +1,184 @@
1
+ require 'net/https'
2
+ require 'sqreen/agent_message'
3
+ require 'sqreen/log/loggable'
4
+
5
+ module Sqreen
6
+ class EndpointTesting
7
+ Endpoint = Struct.new(:url, :ca_store)
8
+ class ChosenEndpoints
9
+ def initialize
10
+ @messages = []
11
+ end
12
+
13
+ # @return [Sqreen::EndpointTesting::Endpoint]
14
+ attr_accessor :control
15
+
16
+ # @return [Sqreen::EndpointTesting::Endpoint]
17
+ attr_accessor :ingestion
18
+
19
+ # @return [Array<Sqreen::AgentMessage>]
20
+ attr_reader :messages
21
+
22
+ # @param [Sqreen::AgentMessage] message
23
+ def add_message(message)
24
+ @messages << message
25
+ end
26
+ end
27
+
28
+ MAIN_CONTROL_HOST = 'back.sqreen.com'.freeze
29
+ MAIN_INJECTION_HOST = 'ingestion.sqreen.com'.freeze
30
+ FALLBACK_ENDPOINT_URL = 'https://back.sqreen.io/'.freeze
31
+ GLOBAL_TIMEOUT = 30
32
+
33
+ CONTROL_ERROR_KIND = 'back_sqreen_com_unavailable'.freeze
34
+ INGESTION_ERROR_KIND = 'ingestion_sqreen_com_unavailable'.freeze
35
+
36
+ class << self
37
+ include Log::Loggable
38
+
39
+ # reproduces behaviour before endpoint testing was introduced
40
+ def no_test_endpoints(config_url, config_ingestion_url)
41
+ endpoints = ChosenEndpoints.new
42
+
43
+ endpoints.control = Endpoint.new(
44
+ config_url || "https://#{MAIN_CONTROL_HOST}/", cert_store
45
+ )
46
+ endpoints.ingestion = Endpoint.new(
47
+ config_ingestion_url || "https://#{MAIN_INJECTION_HOST}/", nil
48
+ )
49
+
50
+ endpoints
51
+ end
52
+
53
+ def test_endpoints(proxy_url, config_url, config_ingestion_url)
54
+ proxy_params = create_proxy_params(proxy_url)
55
+
56
+ # execute the tests in separate threads and wait for them
57
+ thread_control = Thread.new do
58
+ thread_main(config_url, proxy_params, MAIN_CONTROL_HOST)
59
+ end
60
+ thread_injection = Thread.new do
61
+ thread_main(config_ingestion_url, proxy_params, MAIN_INJECTION_HOST)
62
+ end
63
+
64
+ wait_for_threads(thread_control, thread_injection)
65
+
66
+ # build and return result
67
+ fallback = Endpoint.new(FALLBACK_ENDPOINT_URL, cert_store)
68
+ endpoints = ChosenEndpoints.new
69
+ endpoints.control = thread_control[:endpoint] || fallback
70
+ endpoints.ingestion = thread_injection[:endpoint] || fallback
71
+
72
+ if thread_control[:endpoint_error]
73
+ msg = AgentMessage.new(CONTROL_ERROR_KIND, thread_control[:endpoint_error])
74
+ endpoints.add_message msg
75
+ end
76
+ if thread_injection[:endpoint_error]
77
+ msg = AgentMessage.new(INGESTION_ERROR_KIND, thread_injection[:endpoint_error])
78
+ endpoints.add_message msg
79
+ end
80
+
81
+ endpoints
82
+ end
83
+
84
+ private
85
+
86
+ def thread_main(configured_url, proxy_params, host)
87
+ res = if configured_url
88
+ Endpoint.new(configured_url, nil)
89
+ else
90
+ EndpointTesting.send(:test_with_store_variants, proxy_params, host)
91
+ end
92
+
93
+ Thread.current[:endpoint] = res
94
+ rescue StandardError => e
95
+ Thread.current[:endpoint_error] = e.message
96
+ end
97
+
98
+ def create_proxy_params(proxy_url)
99
+ return [] unless proxy_url
100
+
101
+ proxy = URI.parse(proxy_url)
102
+
103
+ return [] unless proxy.scheme == 'http'
104
+
105
+ [proxy.host, proxy.port, proxy.user, proxy.password]
106
+ end
107
+
108
+ def test_with_store_variants(proxy_params, server_name)
109
+ # first without custom store
110
+ do_test(proxy_params, server_name, false)
111
+ rescue StandardError => _e
112
+ do_test(proxy_params, server_name, true)
113
+ end
114
+
115
+ # @param [Array] proxy_params
116
+ # @param [String] server_name
117
+ # @param [Boolean] custom_store
118
+ def do_test(proxy_params, server_name, custom_store)
119
+ http = Net::HTTP.new(server_name, 443, *proxy_params)
120
+ http.use_ssl = true
121
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV['SQREEN_SSL_NO_VERIFY']
122
+ http.verify_callback = lambda do |preverify_ok, ctx|
123
+ unless preverify_ok
124
+ logger.warn do
125
+ "Certificate validation failure for certificate issued to " \
126
+ "#{ctx.chain[0].subject}: #{ctx.error_string}"
127
+ end
128
+ end
129
+
130
+ preverify_ok
131
+ end
132
+
133
+ http.open_timeout = 13
134
+ http.ssl_timeout = 7
135
+ http.read_timeout = 7
136
+ http.close_on_empty_response = true
137
+
138
+ http.cert_store = cert_store if custom_store
139
+
140
+ resp = http.get('/ping')
141
+
142
+ logger.info do
143
+ "Got response from #{server_name}'s ping endpoint. " \
144
+ "Status code is #{resp.code} (custom CA store: #{custom_store})"
145
+ end
146
+
147
+ unless resp.code == '200'
148
+ raise "Response code for /ping is #{resp.code}, not 200"
149
+ end
150
+
151
+ Endpoint.new("https://#{server_name}/", http.cert_store)
152
+ rescue StandardError => e
153
+ logger.info do
154
+ "Error in request to #{server_name} " \
155
+ "(custom store: #{custom_store}): #{e.message}"
156
+ end
157
+
158
+ raise "Error in request to #{server_name}: #{e.message}"
159
+ end
160
+
161
+ def wait_for_threads(thread_control, thread_injection)
162
+ deadline = Time.now + GLOBAL_TIMEOUT
163
+ [thread_control, thread_injection].each do |thread|
164
+ rem = deadline - Time.now
165
+ rem = 0.1 if rem < 0.1
166
+ next if thread.join(rem)
167
+ logger.debug { "Timeout for thread #{thread}" }
168
+ thread.kill
169
+ thread[:endpoint_error] = "Timeout doing endpoint testing"
170
+ end
171
+ end
172
+
173
+ def cert_store
174
+ @cert_store ||= begin
175
+ cert_file = File.join(File.dirname(__FILE__), 'ca.crt')
176
+ cert_store = OpenSSL::X509::Store.new
177
+ cert_store.add_file cert_file
178
+
179
+ cert_store
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -23,6 +23,6 @@ module Sqreen::Log::Loggable
23
23
  end
24
24
 
25
25
  def logger
26
- @logger || self.class.logger
26
+ @logger || singleton_class.logger
27
27
  end
28
28
  end
@@ -11,6 +11,7 @@ require 'sqreen/events/attack'
11
11
 
12
12
  require 'sqreen/log'
13
13
 
14
+ require 'sqreen/agent_message'
14
15
  require 'sqreen/rules'
15
16
  require 'sqreen/session'
16
17
  require 'sqreen/remote_command'
@@ -18,6 +19,7 @@ require 'sqreen/capped_queue'
18
19
  require 'sqreen/metrics_store'
19
20
  require 'sqreen/deliveries/simple'
20
21
  require 'sqreen/deliveries/batch'
22
+ require 'sqreen/endpoint_testing'
21
23
  require 'sqreen/performance_notifications/metrics'
22
24
  require 'sqreen/performance_notifications/binned_metrics'
23
25
  require 'sqreen/legacy/instrumentation'
@@ -113,19 +115,23 @@ module Sqreen
113
115
  @next_metrics = []
114
116
  @running = true
115
117
 
118
+ @proxy_url = @configuration.get(:proxy_url)
119
+ chosen_endpoints = determine_endpoints
120
+
116
121
  @token = @configuration.get(:token)
117
122
  @app_name = @configuration.get(:app_name)
118
- @url = @configuration.get(:url)
119
- @proxy_url = @configuration.get(:proxy_url)
123
+ @url = chosen_endpoints.control.url
124
+ @cert_store = chosen_endpoints.control.ca_store
125
+
120
126
  Sqreen.update_whitelisted_paths([])
121
127
  Sqreen.update_whitelisted_ips({})
122
128
  Sqreen.update_performance_budget(nil)
123
- raise(Sqreen::Exception, 'no url found') unless @url
124
129
  raise(Sqreen::TokenNotFoundException, 'no token found') unless @token
125
130
 
126
131
  Sqreen::Kit::Configuration.logger = Sqreen.log
127
- Sqreen::Kit::Configuration.ingestion_url = @configuration.get(:ingestion_url)
128
- Sqreen::Kit::Configuration.proxy_url = @configuration.get(:proxy_url)
132
+ Sqreen::Kit::Configuration.ingestion_url = chosen_endpoints.ingestion.url
133
+ Sqreen::Kit::Configuration.certificate_store = chosen_endpoints.ingestion.ca_store
134
+ Sqreen::Kit::Configuration.proxy_url = @proxy_url
129
135
 
130
136
  register_exit_cb if set_at_exit
131
137
 
@@ -143,6 +149,7 @@ module Sqreen
143
149
 
144
150
  Sqreen.log.debug "Using token #{@token}"
145
151
  response = create_session(session_class)
152
+ post_endpoint_testing_msgs(chosen_endpoints)
146
153
  wanted_features = response.fetch('features', {})
147
154
  conf_initial_features = configuration.get(:initial_features)
148
155
  unless conf_initial_features.nil?
@@ -155,7 +162,7 @@ module Sqreen
155
162
  wanted_features = wanted_features.merge(conf_features)
156
163
  rescue
157
164
  Sqreen.log.warn do
158
- "NOT using invalid inital features #{conf_initial_features}"
165
+ "NOT using invalid initial features #{conf_initial_features}"
159
166
  end
160
167
  end
161
168
  end
@@ -171,7 +178,7 @@ module Sqreen
171
178
  end
172
179
 
173
180
  def create_session(session_class)
174
- @session = session_class.new(@url, @token, @app_name, @proxy_url)
181
+ @session = session_class.new(@url, @cert_store, @token, @app_name, @proxy_url)
175
182
  session.login(@framework)
176
183
  end
177
184
 
@@ -508,6 +515,28 @@ module Sqreen
508
515
 
509
516
  private
510
517
 
518
+ def post_endpoint_testing_msgs(chosen_endpoints)
519
+ chosen_endpoints.messages.each do |msg|
520
+ session.post_agent_message(@framework, msg)
521
+ end
522
+ rescue => e
523
+ Sqreen.log.warn "Error submitting agent message: #{e}"
524
+ RemoteException.record(e)
525
+ end
526
+
527
+ def determine_endpoints
528
+ # there's no sniffing going on; just a misnamed config setting
529
+ if @configuration.get(:no_sniff_domains)
530
+ # reproduces behaviour before endpoint testing was introduced
531
+ EndpointTesting.no_test_endpoints(@configuration.get(:url),
532
+ @configuration.get(:ingestion_url))
533
+ else
534
+ EndpointTesting.test_endpoints(@proxy_url,
535
+ @configuration.get(:url),
536
+ @configuration.get(:ingestion_url))
537
+ end
538
+ end
539
+
511
540
  def load_actions(hashes)
512
541
  unsupported = Set.new
513
542
 
@@ -50,7 +50,7 @@ module Sqreen
50
50
 
51
51
  attr_accessor :request_compression
52
52
 
53
- def initialize(server_url, token, app_name = nil, proxy_url = nil)
53
+ def initialize(server_url, cert_store, token, app_name = nil, proxy_url = nil)
54
54
  @token = token
55
55
  @app_name = app_name
56
56
  @session_id = nil
@@ -73,12 +73,7 @@ module Sqreen
73
73
  @http = Net::HTTP.new(uri.host, uri.port, *proxy_params)
74
74
  @http.use_ssl = use_ssl
75
75
  @http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV['SQREEN_SSL_NO_VERIFY'] # for testing
76
- if use_ssl
77
- cert_file = File.join(File.dirname(__FILE__), 'ca.crt')
78
- cert_store = OpenSSL::X509::Store.new
79
- cert_store.add_file cert_file
80
- @http.cert_store = cert_store
81
- end
76
+ @http.cert_store = cert_store if use_ssl
82
77
  self.use_signals = false
83
78
  end
84
79
 
@@ -240,10 +235,7 @@ module Sqreen
240
235
  end
241
236
 
242
237
  def login(framework)
243
- headers = {
244
- 'x-api-key' => @token,
245
- 'x-app-name' => @app_name || framework.application_name,
246
- }.reject { |k, v| v == nil }
238
+ headers = prelogin_auth_headers(framework)
247
239
 
248
240
  Sqreen.log.warn "Using app name: #{headers['x-app-name']}"
249
241
 
@@ -312,6 +304,11 @@ module Sqreen
312
304
  @evt_sub_strategy.post_batch(events)
313
305
  end
314
306
 
307
+ def post_agent_message(framework, agent_message)
308
+ headers = prelogin_auth_headers(framework)
309
+ post('app_agent_message', agent_message.to_h, headers, 0)
310
+ end
311
+
315
312
  # Perform agent logout
316
313
  # @param retrying [Boolean] whether to try again on error
317
314
  def logout(retrying = true)
@@ -325,5 +322,14 @@ module Sqreen
325
322
  Sqreen.logged_in = false
326
323
  disconnect
327
324
  end
325
+
326
+ private
327
+
328
+ def prelogin_auth_headers(framework)
329
+ {
330
+ 'x-api-key' => @token,
331
+ 'x-app-name' => @app_name || framework.application_name,
332
+ }.reject { |_k, v| v == nil }
333
+ end
328
334
  end
329
335
  end
@@ -4,5 +4,5 @@
4
4
  # Please refer to our terms for more information: https://www.sqreen.com/terms.html
5
5
 
6
6
  module Sqreen
7
- VERSION = '1.20.0'.freeze
7
+ VERSION = '1.20.1'.freeze
8
8
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqreen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.20.0
4
+ version: 1.20.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-18 00:00:00.000000000 Z
11
+ date: 2020-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqreen-backport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.2.0
33
+ version: 0.2.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.2.0
40
+ version: 0.2.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sq_mini_racer
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -93,6 +93,7 @@ files:
93
93
  - lib/sqreen/actions/user_action_class.rb
94
94
  - lib/sqreen/actions/users_index.rb
95
95
  - lib/sqreen/agent.rb
96
+ - lib/sqreen/agent_message.rb
96
97
  - lib/sqreen/aggregated_metric.rb
97
98
  - lib/sqreen/attack_blocked.rb
98
99
  - lib/sqreen/attack_detected.html
@@ -122,6 +123,7 @@ files:
122
123
  - lib/sqreen/dependency/sentry.rb
123
124
  - lib/sqreen/dependency/sinatra.rb
124
125
  - lib/sqreen/encoding_sanitizer.rb
126
+ - lib/sqreen/endpoint_testing.rb
125
127
  - lib/sqreen/error_handling_middleware.rb
126
128
  - lib/sqreen/event.rb
127
129
  - lib/sqreen/events/attack.rb