moesif_rack 1.4.19 → 1.5.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 +87 -120
- data/lib/moesif_rack/client_ip.rb +61 -102
- data/lib/moesif_rack/governance_rules.rb +483 -0
- data/lib/moesif_rack/moesif_helpers.rb +57 -6
- data/lib/moesif_rack/moesif_middleware.rb +168 -142
- 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 +54 -85
- data/moesif_capture_outgoing/httplog.rb +2 -2
- data/test/config_example.json +1477 -0
- data/test/govrule_example.json +20 -0
- data/test/test_governance_rules.rb +212 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed6afe67a6d8b2aa3c05b23a704d2ccfaadf9afdb078e9d66998f69fd7ed3f45
|
4
|
+
data.tar.gz: 3cb56517e37769f179aabf0f1621c0969259558a46fa8594c017f85098f77423
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91987307ffe2aaf10763dc39e0dce6acc1d91ed8f614144811b2a0d7c579b06d4f4f112cf6de10a983153aa81f45d5dac3450be5c38658af71728656d4426d5c
|
7
|
+
data.tar.gz: f6a677544fd723f0ab6842e5496586d93f22b8ddf77fc5ceb1c4fb98f770ce5443461a66f6c744f03b03a428acfa1c55c669d4f4f09841191c99842a40e614f6
|
@@ -3,134 +3,101 @@ 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
|
+
def initialize(debug)
|
11
|
+
@debug = debug
|
12
|
+
@moesif_helpers = MoesifHelpers.new(debug)
|
13
|
+
@regex_config_helper = RegexConfigHelper.new(debug)
|
14
|
+
end
|
10
15
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
def get_config(api_controller)
|
17
|
+
# Get Application Config
|
18
|
+
config_api_response = api_controller.get_app_config
|
19
|
+
@moesif_helpers.log_debug('new config downloaded')
|
20
|
+
@moesif_helpers.log_debug(config_api_response.to_s)
|
21
|
+
config_api_response
|
22
|
+
rescue MoesifApi::APIException => e
|
23
|
+
if e.response_code.between?(401, 403)
|
24
|
+
@moesif_helpers.log_debug 'Unauthorized access getting application configuration. Please check your Appplication Id.'
|
15
25
|
end
|
26
|
+
@moesif_helpers.log_debug 'Error getting application configuration, with status code:'
|
27
|
+
@moesif_helpers.log_debug e.response_code
|
28
|
+
rescue StandardError => e
|
29
|
+
@moesif_helpers.log_debug e.to_s
|
30
|
+
end
|
16
31
|
|
17
|
-
|
18
|
-
|
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
|
32
|
+
def parse_configuration(config_api_response)
|
33
|
+
# Parse configuration object and return Etag, sample rate and last updated time
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
# Rails return gzipped compressed response body, so decompressing it and getting JSON response body
|
40
|
-
response_body = decompress_gzip_body(config_api_response)
|
41
|
-
@moesif_helpers.log_debug(response_body.to_s)
|
42
|
-
|
43
|
-
# Check if response body is not nil
|
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
|
58
|
-
end
|
35
|
+
# Rails return gzipped compressed response body, so decompressing it and getting JSON response body
|
36
|
+
response_body = @moesif_helpers.decompress_gzip_body(config_api_response)
|
37
|
+
@moesif_helpers.log_debug(response_body.to_json)
|
59
38
|
|
60
|
-
|
61
|
-
|
62
|
-
begin
|
63
|
-
# Check if response body is not nil
|
64
|
-
if !config_api_response.nil? then
|
65
|
-
@moesif_helpers.log_debug("Getting sample rate for user #{user_id} company #{company_id}")
|
66
|
-
@moesif_helpers.log_debug(config_api_response.to_s)
|
67
|
-
|
68
|
-
# Get Regex Sampling rate
|
69
|
-
regex_config = config_api_response.fetch('regex_config', nil)
|
70
|
-
|
71
|
-
if !regex_config.nil? and !event_model.nil?
|
72
|
-
config_mapping = @regex_config_helper.prepare_config_mapping(event_model)
|
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
|
78
|
-
|
79
|
-
# Get user sample rate object
|
80
|
-
user_sample_rate = config_api_response.fetch('user_sample_rate', nil)
|
81
|
-
|
82
|
-
# Get company sample rate object
|
83
|
-
company_sample_rate = config_api_response.fetch('company_sample_rate', nil)
|
84
|
-
|
85
|
-
# Get sample rate for the user if exist
|
86
|
-
if !user_id.nil? && !user_sample_rate.nil? && user_sample_rate.key?(user_id)
|
87
|
-
return user_sample_rate.fetch(user_id)
|
88
|
-
end
|
89
|
-
|
90
|
-
# Get sample rate for the company if exist
|
91
|
-
if !company_id.nil? && !company_sample_rate.nil? && company_sample_rate.key?(company_id)
|
92
|
-
return company_sample_rate.fetch(company_id)
|
93
|
-
end
|
94
|
-
|
95
|
-
# Return sample rate
|
96
|
-
return config_api_response.fetch('sample_rate', 100)
|
97
|
-
else
|
98
|
-
@moesif_helpers.log_debug 'Assuming default behavior as response body is nil - '
|
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
|
39
|
+
# Check if response body is not nil
|
40
|
+
return response_body, config_api_response.headers[:x_moesif_config_etag], Time.now.utc unless response_body.nil?
|
107
41
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
@moesif_helpers.log_debug 'Error while decompressing the response body'
|
128
|
-
@moesif_helpers.log_debug exception.to_s
|
129
|
-
return nil
|
130
|
-
end
|
131
|
-
end
|
42
|
+
# Return Etag, sample rate and last updated time
|
43
|
+
|
44
|
+
@moesif_helpers.log_debug 'Response body is nil, assuming default behavior'
|
45
|
+
# Response body is nil, so assuming default behavior
|
46
|
+
[nil, nil, Time.now.utc]
|
47
|
+
rescue StandardError => e
|
48
|
+
@moesif_helpers.log_debug 'Error while parsing the configuration object, assuming default behavior'
|
49
|
+
@moesif_helpers.log_debug e.to_s
|
50
|
+
# Assuming default behavior
|
51
|
+
[nil, nil, Time.now.utc]
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_sampling_percentage(event_model, config_api_response, user_id, company_id)
|
55
|
+
# Get sampling percentage
|
56
|
+
|
57
|
+
# Check if response body is not nil
|
58
|
+
if !config_api_response.nil?
|
59
|
+
@moesif_helpers.log_debug("Getting sample rate for user #{user_id} company #{company_id}")
|
60
|
+
@moesif_helpers.log_debug(config_api_response.to_s)
|
132
61
|
|
133
|
-
|
134
|
-
|
62
|
+
# Get Regex Sampling rate
|
63
|
+
regex_config = config_api_response.fetch('regex_config', nil)
|
64
|
+
|
65
|
+
if !regex_config.nil? and !event_model.nil?
|
66
|
+
config_mapping = @regex_config_helper.prepare_config_mapping(event_model)
|
67
|
+
regex_sample_rate = @regex_config_helper.fetch_sample_rate_on_regex_match(regex_config,
|
68
|
+
config_mapping)
|
69
|
+
return regex_sample_rate unless regex_sample_rate.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get user sample rate object
|
73
|
+
user_sample_rate = config_api_response.fetch('user_sample_rate', nil)
|
74
|
+
|
75
|
+
# Get company sample rate object
|
76
|
+
company_sample_rate = config_api_response.fetch('company_sample_rate', nil)
|
77
|
+
|
78
|
+
# Get sample rate for the user if exist
|
79
|
+
if !user_id.nil? && !user_sample_rate.nil? && user_sample_rate.key?(user_id)
|
80
|
+
return user_sample_rate.fetch(user_id)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Get sample rate for the company if exist
|
84
|
+
if !company_id.nil? && !company_sample_rate.nil? && company_sample_rate.key?(company_id)
|
85
|
+
return company_sample_rate.fetch(company_id)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return sample rate
|
89
|
+
config_api_response.fetch('sample_rate', 100)
|
90
|
+
else
|
91
|
+
@moesif_helpers.log_debug 'Assuming default behavior as response body is nil - '
|
92
|
+
100
|
135
93
|
end
|
94
|
+
rescue StandardError => e
|
95
|
+
@moesif_helpers.log_debug 'Error while geting sampling percentage, assuming default behavior'
|
96
|
+
@moesif_helpers.log_debug e.to_s
|
97
|
+
100
|
98
|
+
end
|
99
|
+
|
100
|
+
def calculate_weight(sample_rate)
|
101
|
+
sample_rate == 0 ? 1 : (100 / sample_rate).floor
|
102
|
+
end
|
136
103
|
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
|