sqreen-kit 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: feacd9986438ad22e08e0e752a59058956dd63ddfe00b90bdfd094e030db4ced
4
+ data.tar.gz: 318d9d80e07bcc4336740effe1b243e1f279fdbb5bef84be9dab3425884ae7e0
5
+ SHA512:
6
+ metadata.gz: f43f0e22c84f9d15d749ad4ffaf9078ac9b0d1357d24f8f866cd7af5dfaf395489bd5fb913fdaeacc6b74a195491c42292b34b7c075271c3f4e788d95b105e4d
7
+ data.tar.gz: 91879a34437077558e6efb76196a9dd6b71fbdb0bdcc00c1c5d9e348035d749e8c0fd3b9537e8ec189737c7a0df1c0a5813b4ef0afd20c6fb0858b00aae2620c
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ * Extracted code from Ruby agent
data/LICENSE ADDED
@@ -0,0 +1,3 @@
1
+ Sqreen for Ruby is free-to-use, proprietary software.
2
+
3
+ Please refer to our terms for more information: https://www.sqreen.com/terms.html
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sqreen/kit'
@@ -0,0 +1 @@
1
+ module Sqreen; end
@@ -0,0 +1,76 @@
1
+ require 'sqreen/kit/configuration'
2
+ require 'sqreen/kit/retry_policy'
3
+ require 'sqreen/kit/http_client'
4
+ require 'sqreen/kit/http_client/authentication_error'
5
+ require 'sqreen/kit/signals/batch_collector'
6
+ require 'sqreen/kit/signals/auth_signals_client'
7
+ require 'sqreen/kit/signals/signals_client'
8
+
9
+ module Sqreen; end
10
+
11
+ module Sqreen
12
+ module Kit
13
+ # Factories using the global configuration
14
+ class << self
15
+ def version
16
+ @version ||= Gem.loaded_specs['sqreen-kit'].version
17
+ end
18
+
19
+ def auth_signals_client
20
+ @auth_signals_client ||=
21
+ Signals::AuthSignalsClient.new(
22
+ signals_client,
23
+ session_key: Configuration.session_key,
24
+ api_key: Configuration.api_key,
25
+ app_name: Configuration.app_name,
26
+ )
27
+ end
28
+
29
+ def signals_client
30
+ @signals_client ||=
31
+ Signals::SignalsClient.new(http_client)
32
+ end
33
+
34
+ def batch_collector
35
+ @batch_collector ||=
36
+ Signals::BatchCollector.new(
37
+ auth_signals_client,
38
+ flush_size: Configuration.batch_flush_size,
39
+ max_batch_size: Configuration.batch_max_size,
40
+ max_delay_s: Configuration.batch_max_delay_s,
41
+ )
42
+ end
43
+
44
+ def reset
45
+ @auth_signals_client = nil
46
+ @signals_client = nil
47
+ @batch_collector = nil
48
+ @http_client = nil
49
+ @retry_policy = nil
50
+ end
51
+
52
+ private
53
+
54
+ def http_client
55
+ @http_client ||=
56
+ HttpClient.new(Configuration.ingestion_url,
57
+ retry_policy,
58
+ proxy_address: Configuration.proxy_address,
59
+ proxy_port: Configuration.proxy_port,
60
+ proxy_user: Configuration.proxy_user,
61
+ proxy_pass: Configuration.proxy_pass,
62
+ connect_timeout: Configuration.connect_timeout,
63
+ read_timeout: Configuration.read_timeout)
64
+ end
65
+
66
+ def retry_policy
67
+ @retry_policy ||=
68
+ RetryPolicy.new(
69
+ max_retries: Configuration.retry_max_retries,
70
+ wait_s: Configuration.retry_max_retries,
71
+ fatal_exceptions: [Sqreen::Kit::HttpClient::AuthenticationError],
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,67 @@
1
+ require 'logger'
2
+
3
+ module Sqreen
4
+ module Kit
5
+ class Configuration
6
+ class << self
7
+ attr_accessor :logger
8
+
9
+ # http client related
10
+ attr_accessor :ingestion_url,
11
+ :proxy_address,
12
+ :proxy_port,
13
+ :proxy_user,
14
+ :proxy_pass,
15
+ :connect_timeout,
16
+ :read_timeout
17
+
18
+ # retry policy
19
+ attr_accessor :retry_max_retries,
20
+ :retry_wait_s
21
+
22
+ # authentication
23
+ attr_accessor :session_key,
24
+ :api_key,
25
+ :app_name
26
+
27
+ # batch collector
28
+ attr_accessor :batch_max_delay_s,
29
+ :batch_flush_size,
30
+ :batch_max_size
31
+
32
+ # @param [String] url
33
+ def proxy_url=(url)
34
+ if url.nil?
35
+ self.proxy_address =
36
+ self.proxy_port =
37
+ self.proxy_user =
38
+ self.proxy_pass = nil
39
+ return
40
+ end
41
+
42
+ parsed = URI.parse(url)
43
+ raise ArgumentError, 'only http proxies are supported' unless parsed.scheme == 'http'
44
+
45
+ self.proxy_address = parsed.host
46
+ self.proxy_port = parsed.port
47
+ self.proxy_user = parsed.user
48
+ self.proxy_port = parsed.port
49
+ end
50
+ end
51
+
52
+ # default values
53
+ def self.set_defaults
54
+ instance_variables.each do |ia|
55
+ instance_variable_set(ia, nil)
56
+ end
57
+ self.logger = ::Logger.new(STDERR)
58
+ logger.level = ::Logger::WARN
59
+ logger.progname = 'sqreen-kit'
60
+
61
+ self.ingestion_url = 'https://ingestion.sqreen.com/'
62
+ end
63
+
64
+ set_defaults
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,160 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ require 'sqreen/kit/loggable'
5
+ require 'sqreen/kit/retry_policy'
6
+ require 'sqreen/kit/http_client/authentication_error'
7
+ require 'sqreen/kit/http_client/unexpected_status_error'
8
+
9
+ module Sqreen
10
+ module Kit
11
+ # An http client doing JSON requests
12
+ class HttpClient
13
+ include Loggable
14
+
15
+ DEFAULT_CONNECT_TIMEOUT_S = 5
16
+ DEFAULT_READ_TIMEOUT_S = 30
17
+
18
+ attr_reader :http
19
+
20
+ # @return [URI]
21
+ attr_reader :base_url
22
+
23
+ # @param server_url [String]
24
+ # @param retry_policy [Sqreen::Kit::RetryPolicy]
25
+ def initialize(server_url, retry_policy, opts = {})
26
+ @base_url = parse_uri(server_url)
27
+
28
+ @retry_policy = retry_policy
29
+
30
+ @http = ::Net::HTTP.new(@base_url.host, @base_url.port,
31
+ opts[:proxy_address] || :ENV,
32
+ opts[:proxy_port], opts[:proxy_user],
33
+ opts[:proxy_pass])
34
+ @http.use_ssl = @base_url.scheme.downcase == 'https'
35
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV['SQREEN_SSL_NO_VERIFY'] # for testing
36
+
37
+ @http.open_timeout = opts[:connect_timeout] || DEFAULT_CONNECT_TIMEOUT_S
38
+ @http.ssl_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT_S
39
+ @http.read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT_S
40
+
41
+ @req_nb = 1
42
+
43
+ @static_headers = init_static_headers
44
+ end
45
+
46
+ # @param path [String]
47
+ # @param data [String]
48
+ # @param headers [Hash{String=>String}]
49
+ def post(path, data, headers = {})
50
+ request(:POST, path, data, headers)
51
+ end
52
+
53
+ def get(path, headers = {})
54
+ request(:GET, path, nil, headers)
55
+ end
56
+
57
+ private
58
+
59
+ # @return [URI::HTTP]
60
+ def parse_uri(uri)
61
+ # This regexp is the Ruby constant URI::PATTERN::HOSTNAME augmented
62
+ # with the _ character that is frequent in Docker linked containers.
63
+ re = '(?:(?:[a-zA-Z\\d](?:[-_a-zA-Z\\d]*[a-zA-Z\\d])?)\\.)*(?:[a-zA-Z](?:[-_a-zA-Z\\d]*[a-zA-Z\\d])?)\\.?'
64
+ parser = URI::Parser.new HOSTNAME: re
65
+ uri += '/' unless uri.end_with? '/'
66
+ parser.parse(uri)
67
+ end
68
+
69
+ def with_retry(&block)
70
+ @retry_policy.execute(&block)
71
+ end
72
+
73
+ def connect
74
+ return if connected?
75
+ logger.warn { "Connecting to #{base_url}..." }
76
+
77
+ begin
78
+ with_retry do
79
+ http.start
80
+ end
81
+ rescue StandardError => e
82
+ logger.warn "Cannot connect to #{base_url}: #{e.message}"
83
+ raise
84
+ else
85
+ logger.warn 'Connection success'
86
+ end
87
+ end
88
+
89
+ def connected?
90
+ http.started?
91
+ end
92
+
93
+ def disconnect
94
+ http.finish if connected?
95
+ end
96
+
97
+ def init_static_headers
98
+ platform = RUBY_PLATFORM
99
+ platform += " #{ENV_JAVA['java.runtime.version']}" if defined?(ENV_JAVA)
100
+ ua_string = "sqreen-kit/#{Sqreen::Kit.version} " \
101
+ "(#{platform}) ruby/#{RUBY_VERSION}"
102
+ ua_string += " jruby/#{JRUBY_VERSION}" if defined?(JRUBY_VERSION)
103
+ { 'User-Agent' => ua_string }
104
+ end
105
+
106
+ def request(method, path, data, headers = {})
107
+ connect
108
+
109
+ now = Time.now
110
+
111
+ headers = @static_headers.merge(headers)
112
+ @req_nb += 1
113
+
114
+ path = (base_url + path).path
115
+
116
+ with_retry do
117
+ logger.debug do
118
+ format('%s %s %s (%s)',
119
+ method, path, data.inspect, headers.inspect)
120
+ end
121
+
122
+ resp = case method
123
+ when :GET
124
+ http.get(path, headers)
125
+ when :POST
126
+ http.post(path, data, headers)
127
+ end
128
+
129
+ result = process_response resp
130
+
131
+ logger.debug do
132
+ format('%s %s (DONE: %s in %f ms)',
133
+ method, path, resp && resp.code, (Time.now - now) * 1000)
134
+ end
135
+
136
+ result
137
+ end # end with_retry
138
+ end
139
+
140
+ # @param [Net::HTTPResponse] resp
141
+ def process_response(resp)
142
+ body = resp.body
143
+ logger.debug { "Body of response: #{body}" }
144
+
145
+ if resp.code == '401' || resp.code == '403'
146
+ raise AuthenticationError.new(resp.code, body)
147
+ end
148
+
149
+ unless %w[200 201 202].include?(resp.code)
150
+ raise UnexpectedStatusError.new(resp.code, body)
151
+ end
152
+
153
+ {
154
+ code: resp.code,
155
+ body: body,
156
+ }
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,13 @@
1
+ require 'sqreen/kit/http_client/unexpected_status_error'
2
+
3
+ module Sqreen
4
+ module Kit
5
+ class HttpClient
6
+ class AuthenticationError < UnexpectedStatusError
7
+ def initialize(*args)
8
+ super
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ module Sqreen
2
+ module Kit
3
+ class HttpClient
4
+ class UnexpectedStatusError < StandardError
5
+ attr_reader :code, :body
6
+
7
+ def initialize(code, body = nil)
8
+ @code = code
9
+ @body = body
10
+ end
11
+
12
+ def message
13
+ "Unexpected status #{code}: #{body}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ require 'sqreen/kit/configuration'
2
+
3
+ module Sqreen
4
+ module Kit
5
+ module Loggable
6
+ private
7
+
8
+ # @return [::Logger]
9
+ def logger
10
+ Configuration.logger
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ require 'sqreen/kit/loggable'
2
+
3
+ module Sqreen
4
+ module Kit
5
+ class RetryPolicy
6
+ include Loggable
7
+
8
+ DEFAULT_RETRIES = 2
9
+ DEFAULT_WAITS_S = 3
10
+ DEFAULT_FATAL_EXCEPTIONS = [].freeze
11
+
12
+ attr_reader :max_retries, :wait_s, :fatal_exceptions
13
+
14
+ # @param opts the parameters of the retry policy
15
+ # @option opts [Integer] the maximum number of tries
16
+ # @option opts [Float] wait_s wait these seconds before a retry
17
+ # @option opts [Array<Class>] exception classes for which no retry will
18
+ # be attempted, besides non-StandardError
19
+ def initialize(opts = {})
20
+ @max_retries = opts[:max_retries] || DEFAULT_RETRIES
21
+ @wait_s = opts[:wait_s] || DEFAULT_WAITS_S
22
+ @fatal_exceptions = opts[:fatal_exceptions] || DEFAULT_FATAL_EXCEPTIONS
23
+ end
24
+
25
+ def execute
26
+ attempt = 1
27
+ begin
28
+ yield
29
+ rescue ::Exception => e # rubocop:disable Lint/RescueException
30
+ logger.warn { "Error on attempt ##{attempt}: #{e.message}" }
31
+ logger.debug { e.backtrace }
32
+ if fatal?(e)
33
+ logger.debug { "Not retrying after seeing exception #{e.class}" }
34
+ raise
35
+ end
36
+ if attempt > max_retries
37
+ logger.debug { "Not retrying anymore after #{attempt} attempts" }
38
+ raise
39
+ end
40
+
41
+ logger.debug { "Will retry after #{wait_s} seconds" }
42
+ sleep(wait_s) unless wait_s.zero?
43
+ attempt += 1
44
+ retry
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def fatal?(exception)
51
+ !exception.is_a?(StandardError) ||
52
+ fatal_exceptions.any? { |ec| exception.is_a?(ec) }
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ require 'sqreen/kit/signals/dto_helper'
2
+
3
+ module Sqreen
4
+ module Kit
5
+ module Signals
6
+ class Actor
7
+ include DtoHelper
8
+
9
+ add_mandatory_attrs :ip_addresses
10
+
11
+ # mandatory
12
+ # @return [Array<String>]
13
+ attr_accessor :ip_addresses
14
+
15
+ # @return String
16
+ attr_accessor :user_agent
17
+
18
+ # @return [Hash{String=>String}]
19
+ attr_accessor :identifiers
20
+
21
+ # @return [Hash{String=>String}]
22
+ attr_accessor :traits
23
+ end
24
+ end
25
+ end
26
+ end