moesif_rack 1.4.19 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|