moesif_rack 1.4.19 → 2.0.1
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/lib/moesif_rack/app_config.rb +76 -113
- data/lib/moesif_rack/client_ip.rb +61 -102
- data/lib/moesif_rack/governance_rules.rb +480 -0
- data/lib/moesif_rack/moesif_helpers.rb +33 -6
- data/lib/moesif_rack/moesif_middleware.rb +159 -148
- data/lib/moesif_rack/regex_config_helper.rb +96 -104
- data/lib/moesif_rack/update_company.rb +44 -48
- data/lib/moesif_rack/update_user.rb +44 -48
- data/moesif_capture_outgoing/httplog/adapters/net_http.rb +18 -21
- data/moesif_capture_outgoing/httplog/http_log.rb +57 -107
- data/moesif_capture_outgoing/httplog.rb +2 -2
- data/test/config_example.json +1473 -0
- data/test/govrule_example.json +20 -0
- data/test/test_governance_rules.rb +213 -0
- data/test/{moesif_rack_test.rb → test_moesif_rack.rb} +18 -16
- metadata +30 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe86ff126bcaddeb96d24cc3216864727ba7be4f2e4ba7495b5fd6dda789369a
|
4
|
+
data.tar.gz: 2d1bc431a0a59e77a6f1eac898a78535870a424db164f381a52c61767da6d63d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ed3762486f9223b7562b36e38561f72a156a3be5ab836a0ad5b363d2034cfce912125b44e10b0eb9e330ce22b00cdc0bc8665ac59d90702742b5492dd061698
|
7
|
+
data.tar.gz: 376d83545ec2635c2980351f22fb277a99ac62e878385748d7c33e9767c40ca7731d2670f0c44f531f9350312066534eb593b6b729603dbeaec03ea3f70ac919
|
@@ -3,134 +3,97 @@ require 'json'
|
|
3
3
|
require 'time'
|
4
4
|
require 'zlib'
|
5
5
|
require 'stringio'
|
6
|
-
require_relative './moesif_helpers
|
7
|
-
require_relative './regex_config_helper
|
6
|
+
require_relative './moesif_helpers'
|
7
|
+
require_relative './regex_config_helper'
|
8
8
|
|
9
9
|
class AppConfig
|
10
|
+
attr_accessor :config, :recent_etag, :last_download_time
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def get_config(api_controller)
|
18
|
-
# Get Application Config
|
19
|
-
begin
|
20
|
-
config_api_response = api_controller.get_app_config()
|
21
|
-
@moesif_helpers.log_debug("new config downloaded")
|
22
|
-
@moesif_helpers.log_debug(config_api_response.to_s)
|
23
|
-
return config_api_response
|
24
|
-
rescue MoesifApi::APIException => e
|
25
|
-
if e.response_code.between?(401, 403)
|
26
|
-
@moesif_helpers.log_debug 'Unauthorized access getting application configuration. Please check your Appplication Id.'
|
27
|
-
end
|
28
|
-
@moesif_helpers.log_debug 'Error getting application configuration, with status code:'
|
29
|
-
@moesif_helpers.log_debug e.response_code
|
30
|
-
rescue => e
|
31
|
-
@moesif_helpers.log_debug e.to_s
|
32
|
-
end
|
33
|
-
rescue
|
34
|
-
end
|
12
|
+
def initialize(debug)
|
13
|
+
@debug = debug
|
14
|
+
@moesif_helpers = MoesifHelpers.new(debug)
|
15
|
+
@regex_config_helper = RegexConfigHelper.new(debug)
|
16
|
+
end
|
35
17
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
18
|
+
def should_reload(etag_from_create_event)
|
19
|
+
if @last_download_time.nil?
|
20
|
+
return true
|
21
|
+
elsif Time.now.utc > (@last_download_time + 300)
|
22
|
+
return true
|
23
|
+
elsif !etag_from_create_event.nil? && !@recent_etag.nil?
|
24
|
+
@moesif_helpers.log_debug('comparing if etag from event and recent etag match ' + etag_from_create_event + ' ' + @recent_etag)
|
42
25
|
|
43
|
-
|
44
|
-
if !response_body.nil? then
|
45
|
-
# Return Etag, sample rate and last updated time
|
46
|
-
return response_body, config_api_response.headers[:x_moesif_config_etag], Time.now.utc
|
47
|
-
else
|
48
|
-
@moesif_helpers.log_debug 'Response body is nil, assuming default behavior'
|
49
|
-
# Response body is nil, so assuming default behavior
|
50
|
-
return nil, nil, Time.now.utc
|
51
|
-
end
|
52
|
-
rescue => exception
|
53
|
-
@moesif_helpers.log_debug 'Error while parsing the configuration object, assuming default behavior'
|
54
|
-
@moesif_helpers.log_debug exception.to_s
|
55
|
-
# Assuming default behavior
|
56
|
-
return nil, nil, Time.now.utc
|
57
|
-
end
|
26
|
+
return etag_from_create_event != @recent_etag
|
58
27
|
end
|
28
|
+
@moesif_helpers.log_debug('should skip reload config')
|
29
|
+
return false;
|
30
|
+
end
|
59
31
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
32
|
+
def get_config(api_controller)
|
33
|
+
# Get Application Config
|
34
|
+
@moesif_helpers.log_debug('try to loading etag')
|
35
|
+
config_json, _context = api_controller.get_app_config
|
36
|
+
@config = config_json
|
37
|
+
@recent_etag = _context.response.headers[:x_moesif_config_etag]
|
38
|
+
@last_download_time = Time.now.utc
|
39
|
+
@moesif_helpers.log_debug('new config downloaded')
|
40
|
+
@moesif_helpers.log_debug(config_json.to_s)
|
41
|
+
rescue MoesifApi::APIException => e
|
42
|
+
if e.response_code.between?(401, 403)
|
43
|
+
@moesif_helpers.log_debug 'Unauthorized access getting application configuration. Please check your Appplication Id.'
|
44
|
+
end
|
45
|
+
@moesif_helpers.log_debug 'Error getting application configuration, with status code:'
|
46
|
+
@moesif_helpers.log_debug e.response_code
|
47
|
+
rescue StandardError => e
|
48
|
+
@moesif_helpers.log_debug e.to_s
|
49
|
+
end
|
67
50
|
|
68
|
-
|
69
|
-
|
51
|
+
def get_sampling_percentage(event_model, user_id, company_id)
|
52
|
+
# if we do not have config for some reason we return 100
|
53
|
+
if !@config.nil?
|
54
|
+
# Get sampling percentage
|
55
|
+
@moesif_helpers.log_debug("Getting sample rate for user #{user_id} company #{company_id}")
|
56
|
+
@moesif_helpers.log_debug(@config.to_s)
|
70
57
|
|
71
|
-
|
72
|
-
|
73
|
-
regex_sample_rate = @regex_config_helper.fetch_sample_rate_on_regex_match(regex_config, config_mapping)
|
74
|
-
if !regex_sample_rate.nil?
|
75
|
-
return regex_sample_rate
|
76
|
-
end
|
77
|
-
end
|
58
|
+
# Get Regex Sampling rate
|
59
|
+
regex_config = @config.fetch('regex_config', nil)
|
78
60
|
|
79
|
-
|
80
|
-
|
61
|
+
if !regex_config.nil? and !event_model.nil?
|
62
|
+
config_mapping = @regex_config_helper.prepare_config_mapping(event_model)
|
63
|
+
regex_sample_rate = @regex_config_helper.fetch_sample_rate_on_regex_match(regex_config,
|
64
|
+
config_mapping)
|
65
|
+
return regex_sample_rate unless regex_sample_rate.nil?
|
66
|
+
end
|
81
67
|
|
82
|
-
|
83
|
-
|
68
|
+
# Get user sample rate object
|
69
|
+
user_sample_rate = @config.fetch('user_sample_rate', nil)
|
84
70
|
|
85
|
-
|
86
|
-
|
87
|
-
return user_sample_rate.fetch(user_id)
|
88
|
-
end
|
71
|
+
# Get company sample rate object
|
72
|
+
company_sample_rate = @config.fetch('company_sample_rate', nil)
|
89
73
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
74
|
+
# Get sample rate for the user if exist
|
75
|
+
if !user_id.nil? && !user_sample_rate.nil? && user_sample_rate.key?(user_id)
|
76
|
+
return user_sample_rate.fetch(user_id)
|
77
|
+
end
|
94
78
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
return 100
|
100
|
-
end
|
101
|
-
rescue => exception
|
102
|
-
@moesif_helpers.log_debug 'Error while geting sampling percentage, assuming default behavior'
|
103
|
-
@moesif_helpers.log_debug exception.to_s
|
104
|
-
return 100
|
105
|
-
end
|
106
|
-
end
|
79
|
+
# Get sample rate for the company if exist
|
80
|
+
if !company_id.nil? && !company_sample_rate.nil? && company_sample_rate.key?(company_id)
|
81
|
+
return company_sample_rate.fetch(company_id)
|
82
|
+
end
|
107
83
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
# Create a GZipReader object to read data
|
115
|
-
gzip_reader = Zlib::GzipReader.new(StringIO.new(config_api_response.raw_body.to_s))
|
116
|
-
|
117
|
-
# Read the body
|
118
|
-
uncompressed_string = gzip_reader.read
|
119
|
-
|
120
|
-
# Return the parsed body
|
121
|
-
return JSON.parse( uncompressed_string )
|
122
|
-
else
|
123
|
-
@moesif_helpers.log_debug 'Content Encoding is of type other than gzip, returning nil'
|
124
|
-
return nil
|
125
|
-
end
|
126
|
-
rescue => exception
|
127
|
-
@moesif_helpers.log_debug 'Error while decompressing the response body'
|
128
|
-
@moesif_helpers.log_debug exception.to_s
|
129
|
-
return nil
|
130
|
-
end
|
84
|
+
# Return overall sample rate
|
85
|
+
@config.fetch('sample_rate', 100)
|
86
|
+
else
|
87
|
+
@moesif_helpers.log_debug 'Assuming default behavior as response body is nil - '
|
88
|
+
100
|
131
89
|
end
|
90
|
+
rescue StandardError => e
|
91
|
+
@moesif_helpers.log_debug 'Error while geting sampling percentage, assuming default behavior'
|
92
|
+
@moesif_helpers.log_debug e.to_s
|
93
|
+
100
|
94
|
+
end
|
132
95
|
|
133
|
-
|
134
|
-
|
135
|
-
|
96
|
+
def calculate_weight(sample_rate)
|
97
|
+
sample_rate == 0 ? 1 : (100 / sample_rate).floor
|
98
|
+
end
|
136
99
|
end
|
@@ -1,119 +1,78 @@
|
|
1
|
-
|
2
1
|
def is_ip?(value)
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
ipv4 = /^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/
|
3
|
+
ipv6 = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
|
4
|
+
# We use !! to convert the return value to a boolean
|
5
|
+
!!(value =~ ipv4 or value =~ ipv6)
|
7
6
|
end
|
8
7
|
|
9
8
|
def get_client_ip_from_x_forwarded_for(value)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
if splitted.length == 2
|
32
|
-
forwardedIps << splitted.first
|
33
|
-
end
|
34
|
-
end
|
35
|
-
forwardedIps << e
|
36
|
-
end
|
37
|
-
|
38
|
-
# Sometimes IP addresses in this header can be 'unknown' (http://stackoverflow.com/a/11285650).
|
39
|
-
# Therefore taking the left-most IP address that is not unknown
|
40
|
-
# A Squid configuration directive can also set the value to "unknown" (http://www.squid-cache.org/Doc/config/forwarded_for/)
|
41
|
-
return forwardedIps.find {|e| is_ip?(e) }
|
42
|
-
end
|
43
|
-
rescue
|
44
|
-
return value.encode('utf-8')
|
9
|
+
value = value.encode('utf-8')
|
10
|
+
|
11
|
+
return nil if value.to_s.empty?
|
12
|
+
|
13
|
+
if !value.instance_of?(String)
|
14
|
+
puts('Expected a string, got - ' + value.class.to_s)
|
15
|
+
else
|
16
|
+
# x-forwarded-for may return multiple IP addresses in the format:
|
17
|
+
# "client IP, proxy 1 IP, proxy 2 IP"
|
18
|
+
# Therefore, the right-most IP address is the IP address of the most recent proxy
|
19
|
+
# and the left-most IP address is the IP address of the originating client.
|
20
|
+
# source: http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html
|
21
|
+
# Azure Web App's also adds a port for some reason, so we'll only use the first part (the IP)
|
22
|
+
forwardedIps = []
|
23
|
+
|
24
|
+
value.gsub(/\s+/, '').split(',').each do |e|
|
25
|
+
if e.include?(':')
|
26
|
+
splitted = e.split(':')
|
27
|
+
forwardedIps << splitted.first if splitted.length == 2
|
28
|
+
end
|
29
|
+
forwardedIps << e
|
45
30
|
end
|
31
|
+
|
32
|
+
# Sometimes IP addresses in this header can be 'unknown' (http://stackoverflow.com/a/11285650).
|
33
|
+
# Therefore taking the left-most IP address that is not unknown
|
34
|
+
# A Squid configuration directive can also set the value to "unknown" (http://www.squid-cache.org/Doc/config/forwarded_for/)
|
35
|
+
forwardedIps.find { |e| is_ip?(e) }
|
36
|
+
end
|
37
|
+
rescue StandardError
|
38
|
+
value.encode('utf-8')
|
46
39
|
end
|
47
40
|
|
48
41
|
def get_client_address(env)
|
49
|
-
|
50
|
-
|
51
|
-
if env.key?('HTTP_X_CLIENT_IP')
|
52
|
-
if is_ip?(env['HTTP_X_CLIENT_IP'])
|
53
|
-
return env['HTTP_X_CLIENT_IP']
|
54
|
-
end
|
55
|
-
end
|
42
|
+
# Standard headers used by Amazon EC2, Heroku, and others.
|
43
|
+
return env['HTTP_X_CLIENT_IP'] if env.key?('HTTP_X_CLIENT_IP') && is_ip?(env['HTTP_X_CLIENT_IP'])
|
56
44
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
end
|
45
|
+
# Load-balancers (AWS ELB) or proxies.
|
46
|
+
if env.key?('HTTP_X_FORWARDED_FOR')
|
47
|
+
xForwardedFor = get_client_ip_from_x_forwarded_for(env['HTTP_X_FORWARDED_FOR'])
|
48
|
+
return xForwardedFor if is_ip?(xForwardedFor)
|
49
|
+
end
|
64
50
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
if is_ip?(env['HTTP_CF_CONNECTING_IP'])
|
70
|
-
return env['HTTP_CF_CONNECTING_IP']
|
71
|
-
end
|
72
|
-
end
|
51
|
+
# Cloudflare.
|
52
|
+
# @see https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
|
53
|
+
# CF-Connecting-IP - applied to every request to the origin.
|
54
|
+
return env['HTTP_CF_CONNECTING_IP'] if env.key?('HTTP_CF_CONNECTING_IP') && is_ip?(env['HTTP_CF_CONNECTING_IP'])
|
73
55
|
|
74
|
-
|
75
|
-
|
76
|
-
if is_ip?(env['HTTP_TRUE_CLIENT_IP'])
|
77
|
-
return env['HTTP_TRUE_CLIENT_IP']
|
78
|
-
end
|
79
|
-
end
|
56
|
+
# Akamai and Cloudflare: True-Client-IP.
|
57
|
+
return env['HTTP_TRUE_CLIENT_IP'] if env.key?('HTTP_TRUE_CLIENT_IP') && is_ip?(env['HTTP_TRUE_CLIENT_IP'])
|
80
58
|
|
81
|
-
|
82
|
-
|
83
|
-
if is_ip?(env['HTTP_X_REAL_IP'])
|
84
|
-
return env['HTTP_X_REAL_IP']
|
85
|
-
end
|
86
|
-
end
|
59
|
+
# Default nginx proxy/fcgi; alternative to x-forwarded-for, used by some proxies.
|
60
|
+
return env['HTTP_X_REAL_IP'] if env.key?('HTTP_X_REAL_IP') && is_ip?(env['HTTP_X_REAL_IP'])
|
87
61
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
end
|
95
|
-
end
|
62
|
+
# (Rackspace LB and Riverbed's Stingray)
|
63
|
+
# http://www.rackspace.com/knowledge_center/article/controlling-access-to-linux-cloud-sites-based-on-the-client-ip-address
|
64
|
+
# https://splash.riverbed.com/docs/DOC-1926
|
65
|
+
if env.key?('HTTP_X_CLUSTER_CLIENT_IP') && is_ip?(env['HTTP_X_CLUSTER_CLIENT_IP'])
|
66
|
+
return env['HTTP_X_CLUSTER_CLIENT_IP']
|
67
|
+
end
|
96
68
|
|
97
|
-
|
98
|
-
if is_ip?(env['HTTP_X_FORWARDED'])
|
99
|
-
return env['HTTP_X_FORWARDED']
|
100
|
-
end
|
101
|
-
end
|
69
|
+
return env['HTTP_X_FORWARDED'] if env.key?('HTTP_X_FORWARDED') && is_ip?(env['HTTP_X_FORWARDED'])
|
102
70
|
|
103
|
-
|
104
|
-
if is_ip?(env['HTTP_FORWARDED_FOR'])
|
105
|
-
return env['HTTP_FORWARDED_FOR']
|
106
|
-
end
|
107
|
-
end
|
71
|
+
return env['HTTP_FORWARDED_FOR'] if env.key?('HTTP_FORWARDED_FOR') && is_ip?(env['HTTP_FORWARDED_FOR'])
|
108
72
|
|
109
|
-
|
110
|
-
if is_ip?(env['HTTP_FORWARDED'])
|
111
|
-
return env['HTTP_FORWARDED']
|
112
|
-
end
|
113
|
-
end
|
73
|
+
return env['HTTP_FORWARDED'] if env.key?('HTTP_FORWARDED') && is_ip?(env['HTTP_FORWARDED'])
|
114
74
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
75
|
+
env['REMOTE_ADDR']
|
76
|
+
rescue StandardError
|
77
|
+
env['REMOTE_ADDR']
|
78
|
+
end
|