Skip to content

Commit 1094b18

Browse files
authored
Merge pull request #31 from securenative/dev
Support ipv6 extraction
2 parents 685d6dd + e3ab356 commit 1094b18

File tree

7 files changed

+127
-22
lines changed

7 files changed

+127
-22
lines changed

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
securenative (0.1.36)
4+
securenative (0.1.37)
55

66
GEM
77
remote: https://rubygems.org/

lib/securenative/utils/request_utils.rb

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require 'ipaddr'
4+
35
module SecureNative
46
module Utils
57
class RequestUtils
@@ -24,14 +26,20 @@ def self.get_client_ip_from_request(request, options)
2426
if h.nil?
2527
h = request.env[self.parse_ip(header)]
2628
end
27-
return h.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless h.nil?
29+
parsed = self.parse_proxy_header(h, header)
30+
if self.validate_ip(parsed)
31+
return parsed
32+
end
2833
rescue NoMethodError
2934
begin
3035
h = request[header]
3136
if h.nil?
3237
h = request.env[self.parse_ip(header)]
3338
end
34-
return h.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless h.nil?
39+
parsed = self.parse_proxy_header(h, header)
40+
if self.validate_ip(parsed)
41+
return parsed
42+
end
3543
rescue NoMethodError
3644
# Ignored
3745
end
@@ -40,36 +48,66 @@ def self.get_client_ip_from_request(request, options)
4048
end
4149

4250
begin
43-
x_forwarded_for = request.env['HTTP_X_FORWARDED_FOR']
44-
return x_forwarded_for.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless x_forwarded_for.nil?
51+
header_value = request.env['HTTP_X_FORWARDED_FOR']
52+
if header_value.include? ','
53+
header_value = ip.split(',')[0]
54+
end
55+
if self.validate_ip(header_value)
56+
return header_value
57+
end
4558
rescue NoMethodError
4659
begin
47-
x_forwarded_for = request['HTTP_X_FORWARDED_FOR']
48-
return x_forwarded_for.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless x_forwarded_for.nil?
60+
header_value = request['HTTP_X_FORWARDED_FOR']
61+
if header_value.include? ','
62+
header_value = ip.split(',')[0]
63+
end
64+
if self.validate_ip(header_value)
65+
return header_value
66+
end
4967
rescue NoMethodError
5068
# Ignored
5169
end
5270
end
5371

5472
begin
55-
x_forwarded_for = request.env['HTTP_X_REAL_IP']
56-
return x_forwarded_for.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless x_forwarded_for.nil?
73+
header_value = request.env['HTTP_X_REAL_IP']
74+
if header_value.include? ','
75+
header_value = ip.split(',')[0]
76+
end
77+
if self.validate_ip(header_value)
78+
return header
79+
end
5780
rescue NoMethodError
5881
begin
59-
x_forwarded_for = request['HTTP_X_REAL_IP']
60-
return x_forwarded_for.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless x_forwarded_for.nil?
82+
header_value = request['HTTP_X_REAL_IP']
83+
if header_value.include? ','
84+
header_value = ip.split(',')[0]
85+
end
86+
if self.validate_ip(header_value)
87+
return header_value
88+
end
6189
rescue NoMethodError
6290
# Ignored
6391
end
6492
end
6593

6694
begin
67-
x_forwarded_for = request.env['REMOTE_ADDR']
68-
return x_forwarded_for.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless x_forwarded_for.nil?
95+
header_value = request.env['REMOTE_ADDR']
96+
if header_value.include? ','
97+
header_value = ip.split(',')[0]
98+
end
99+
if self.validate_ip(header_value)
100+
return header_value
101+
end
69102
rescue NoMethodError
70103
begin
71-
x_forwarded_for = request['REMOTE_ADDR']
72-
return x_forwarded_for.scan(/\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/)[0] unless x_forwarded_for.nil?
104+
header_value = request['REMOTE_ADDR']
105+
if header_value.include? ','
106+
header_value = ip.split(',')[0]
107+
end
108+
if self.validate_ip(header_value)
109+
return header_value
110+
end
73111
rescue NoMethodError
74112
# Ignored
75113
end
@@ -96,6 +134,35 @@ def self.parse_ip(headers)
96134
h = headers.gsub('-', '_')
97135
return PREFIX + h.upcase
98136
end
137+
138+
def self.parse_proxy_header(headers, header_key)
139+
h = headers.gsub(header_key + ': ', '')
140+
if headers.include? ','
141+
h = h.split(',')[0]
142+
end
143+
return h
144+
end
145+
146+
def self.validate_ip(ip)
147+
if ip.nil?
148+
return false
149+
end
150+
151+
begin
152+
ipaddr = IPAddr.new(ip)
153+
if ipaddr.ipv4?
154+
return true
155+
end
156+
157+
if ipaddr.ipv6?
158+
return true
159+
end
160+
rescue Exception
161+
# Ignored
162+
end
163+
164+
return false
165+
end
99166
end
100167
end
101168
end

lib/securenative/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module SecureNative
4-
VERSION = '0.1.36'
4+
VERSION = '0.1.37'
55
end

spec/securenative/spec_api_manager.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
5656
'Authorization' => 'YOUR_API_KEY',
5757
'Content-Type' => 'application/json',
58-
'Sn-Version' => '0.1.36',
58+
'Sn-Version' => '0.1.37',
5959
'User-Agent' => 'SecureNative-ruby'
6060
}
6161
).to_return(status: 200, body: '', headers: {})

spec/securenative/spec_event_manager.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def initialize
2929
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
3030
'Authorization' => 'YOUR_API_KEY',
3131
'Content-Type' => 'application/json',
32-
'Sn-Version' => '0.1.36',
32+
'Sn-Version' => '0.1.37',
3333
'User-Agent' => 'SecureNative-ruby'
3434
})
3535
.to_return(status: 200, body: '', headers: {})
@@ -53,7 +53,7 @@ def initialize
5353
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
5454
'Authorization' => 'YOUR_API_KEY',
5555
'Content-Type' => 'application/json',
56-
'Sn-Version' => '0.1.36',
56+
'Sn-Version' => '0.1.37',
5757
'User-Agent' => 'SecureNative-ruby'
5858
})
5959
.to_return(status: 401, body: '', headers: {})
@@ -74,7 +74,7 @@ def initialize
7474
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
7575
'Authorization' => 'YOUR_API_KEY',
7676
'Content-Type' => 'application/json',
77-
'Sn-Version' => '0.1.36',
77+
'Sn-Version' => '0.1.37',
7878
'User-Agent' => 'SecureNative-ruby'
7979
})
8080
.to_return(status: 500, body: '', headers: {})

spec/securenative/spec_http_client.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
1616
'Authorization' => 'YOUR_API_KEY',
1717
'Content-Type' => 'application/json',
18-
'Sn-Version' => '0.1.36',
18+
'Sn-Version' => '0.1.37',
1919
'User-Agent' => 'SecureNative-ruby'
2020
}).to_return(status: 200, body: '', headers: {})
2121

spec/securenative/utils/spec_request_utils.rb

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
require 'rspec'
66

77
RSpec.describe SecureNative::Utils::RequestUtils do
8-
it 'extract a request with proxy headers' do
8+
it 'extract a request with proxy headers ipv4' do
99
options = SecureNative::Options.new
1010
options.proxy_headers = [
1111
'CF-Connecting-IP'
@@ -23,4 +23,42 @@
2323

2424
expect(client_ip).to eq('203.0.113.1')
2525
end
26+
27+
it 'extract a request with proxy headers ipv6' do
28+
options = SecureNative::Options.new
29+
options.proxy_headers = [
30+
'CF-Connecting-IP'
31+
]
32+
33+
stub_request(:get, 'http://www.example.com/')
34+
.with(headers: {
35+
'Accept' => '*/*',
36+
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
37+
'User-Agent' => 'Ruby'
38+
}).to_return(status: 200, body: '', headers: { 'CF-Connecting-IP' => 'CF-Connecting-IP: f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d' })
39+
40+
request = Net::HTTP.get_response('www.example.com', '/')
41+
client_ip = SecureNative::Utils::RequestUtils.get_client_ip_from_request(request, options)
42+
43+
expect(client_ip).to eq('f71f:5bf9:25ff:1883:a8c4:eeff:7b80:aa2d')
44+
end
45+
46+
it 'extract a request with proxy headers multiple ipv4' do
47+
options = SecureNative::Options.new
48+
options.proxy_headers = [
49+
'CF-Connecting-IP'
50+
]
51+
52+
stub_request(:get, 'http://www.example.com/')
53+
.with(headers: {
54+
'Accept' => '*/*',
55+
'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
56+
'User-Agent' => 'Ruby'
57+
}).to_return(status: 200, body: '', headers: { 'CF-Connecting-IP' => 'CF-Connecting-IP: 141.246.115.116, 203.0.113.1, 12.34.56.3' })
58+
59+
request = Net::HTTP.get_response('www.example.com', '/')
60+
client_ip = SecureNative::Utils::RequestUtils.get_client_ip_from_request(request, options)
61+
62+
expect(client_ip).to eq('141.246.115.116')
63+
end
2664
end

0 commit comments

Comments
 (0)