Skip to content

Commit 4cf653b

Browse files
author
Inbal Tako
committed
Clean ip extraction
1 parent b9fc52b commit 4cf653b

File tree

8 files changed

+114
-137
lines changed

8 files changed

+114
-137
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.3.2
1+
0.3.3

securenative/config/configuration_manager.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class ConfigurationManager(object):
99
DEFAULT_CONFIG_FILE = "securenative.ini"
10-
CUSTOM_CONFIG_FILE_ENV_NAME = "SECURENATIVE_COMFIG_FILE"
10+
CUSTOM_CONFIG_FILE_ENV_NAME = "SECURENATIVE_CONFIG_FILE"
1111
config = ConfigParser()
1212

1313
@classmethod
@@ -35,8 +35,12 @@ def _get_resource_path(cls, env_name):
3535
@classmethod
3636
def _get_env_or_default(cls, properties, key, default):
3737
if os.environ.get(key):
38+
if "," in os.environ.get(key):
39+
return os.environ.get(key).split(",")
3840
return os.environ.get(key)
3941
if properties.get(key):
42+
if "," in properties.get(key):
43+
return properties.get(key).split(",")
4044
return properties.get(key)
4145
return default
4246

securenative/utils/ip_utils.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,16 @@ def is_ip_address(ip_address):
1717

1818
@staticmethod
1919
def is_valid_public_ip(ip_address):
20-
try:
21-
socket.inet_aton(ip_address)
22-
except socket.error:
23-
return False
20+
ip = ipaddress.ip_address(ip_address)
2421

25-
ip = ipaddress.IPv4Address(ip_address)
26-
if ip.is_loopback \
27-
or not ip.is_global \
28-
or ip.is_private \
29-
or ip.is_link_local \
30-
or ip.is_multicast \
31-
or ip.is_reserved \
32-
or ip.is_unspecified:
22+
if ip is ipaddress.IPv4Address:
23+
if not ip.is_loopback and not ip.is_reserved and not ip.is_unspecified:
24+
return True
3325
return False
34-
return True
26+
27+
if not ip.is_unspecified:
28+
return True
29+
return False
3530

3631
@staticmethod
3732
def is_loop_back(ip_address):

securenative/utils/request_utils.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from securenative.utils.ip_utils import IpUtils
2+
3+
14
class RequestUtils(object):
25
SECURENATIVE_COOKIE = "_sn"
36
SECURENATIVE_HEADER = "x-securenative"
7+
IP_HEADERS = ["x-forwarded-for", "x-client-ip", "x-real-ip", "x-forwarded", "x-cluster-client-ip", "forwarded-for", "forwarded", "via"]
48

59
@staticmethod
610
def get_secure_header_from_request(headers):
@@ -15,25 +19,35 @@ def get_client_ip_from_request(request, options):
1519
for header in options.proxy_headers:
1620
try:
1721
if request.environ.get(header) is not None:
18-
return request.environ.get(header)
22+
ips = request.environ.get(header).split(',')
23+
return RequestUtils.get_valid_ip(ips)
1924
if request.headers[header] is not None:
20-
return request.headers[header]
25+
ips = request.headers[header].split(',')
26+
return RequestUtils.get_valid_ip(ips)
2127
except Exception:
2228
try:
2329
if request.headers[header] is not None:
24-
return request.headers[header]
30+
ips = request.headers[header].split(',')
31+
return RequestUtils.get_valid_ip(ips)
2532
except Exception:
2633
continue
2734

35+
for header in RequestUtils.IP_HEADERS:
36+
try:
37+
ips = request.headers[header].split(',')
38+
return RequestUtils.get_valid_ip(ips)
39+
except Exception:
40+
continue
41+
2842
try:
2943
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
3044
if x_forwarded_for:
31-
ip = x_forwarded_for.split(',')[-1].strip()
45+
ip = x_forwarded_for.split(',')[0].strip()
3246
else:
3347
ip = request.META.get('REMOTE_ADDR')
3448

3549
if ip is None or ip == "":
36-
ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', ""))
50+
ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', "")).split(',')[0].strip()
3751

3852
return ip
3953
except Exception:
@@ -45,3 +59,24 @@ def get_remote_ip_from_request(request):
4559
return request.raw._original_response.fp.raw._sock.getpeername()[0]
4660
except Exception:
4761
return ""
62+
63+
@staticmethod
64+
def get_valid_ip(ips):
65+
if isinstance(ips, list):
66+
for ip in ips:
67+
ip = ip.strip()
68+
if IpUtils.is_valid_public_ip(ip):
69+
return ip
70+
71+
# No public ip found check for no loopback
72+
for ip in ips:
73+
ip = ip.strip()
74+
if not IpUtils.is_loop_back(ip):
75+
return ip
76+
77+
ip = ips.strip()
78+
if IpUtils.is_valid_public_ip(ip):
79+
return ip
80+
81+
if IpUtils.is_loop_back(ip):
82+
return ip

securenative/utils/version_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ class VersionUtils(object):
22

33
@staticmethod
44
def get_version():
5-
return "0.3.2"
5+
return "0.3.3"

tests/api_manager_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def test_track_event(self):
5050

5151
@responses.activate
5252
def test_should_timeout_on_post(self):
53-
options = SecureNativeOptions(api_key="YOUR_API_KEY", auto_send=True, timeout=0,
53+
options = SecureNativeOptions(api_key="YOUR_API_KEY", auto_send=True, timeout=0.000001,
5454
api_url="https://api.securenative-stg.com/collector/api/v1")
5555

5656
responses.add(responses.POST, "https://api.securenative-stg.com/collector/api/v1/verify",

tests/configuration_manager_test.py

Lines changed: 34 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,13 @@ class ConfigurationManagerTest(unittest.TestCase):
1212
def setUp(self):
1313
self.config_file_path = "/tmp/securenative.ini"
1414

15-
def create_ini_file(self, conf):
16-
os.environ["SECURENATIVE_COMFIG_FILE"] = self.config_file_path
17-
15+
def clean_settings(self):
1816
try:
1917
os.remove(self.config_file_path)
2018
except FileNotFoundError:
2119
pass
2220

23-
config = configparser.ConfigParser()
24-
25-
for key, value in conf.items():
26-
config.set("DEFAULT", key.upper(), value)
27-
28-
with open(self.config_file_path, "w") as configfile:
29-
config.write(configfile)
30-
31-
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
32-
def test_parse_config_file_correctly(self):
3321
try:
34-
os.remove(self.config_file_path)
3522
del os.environ["SECURENATIVE_API_KEY"]
3623
del os.environ["SECURENATIVE_API_URL"]
3724
del os.environ["SECURENATIVE_INTERVAL"]
@@ -41,11 +28,29 @@ def test_parse_config_file_correctly(self):
4128
del os.environ["SECURENATIVE_DISABLE"]
4229
del os.environ["SECURENATIVE_LOG_LEVEL"]
4330
del os.environ["SECURENATIVE_FAILOVER_STRATEGY"]
44-
except FileNotFoundError:
45-
pass
31+
del os.environ["SECURENATIVE_PROXY_HEADERS"]
4632
except KeyError:
4733
pass
4834

35+
def create_ini_file(self, conf):
36+
os.environ["SECURENATIVE_CONFIG_FILE"] = self.config_file_path
37+
38+
try:
39+
os.remove(self.config_file_path)
40+
except FileNotFoundError:
41+
pass
42+
43+
config = configparser.ConfigParser()
44+
45+
for key, value in conf.items():
46+
config.set("DEFAULT", key.upper(), value)
47+
48+
with open(self.config_file_path, "w") as configfile:
49+
config.write(configfile)
50+
51+
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
52+
def test_parse_config_file_correctly(self):
53+
self.clean_settings()
4954
config = {
5055
"SECURENATIVE_API_KEY": "SOME_API_KEY",
5156
"SECURENATIVE_APP_NAME": "SOME_APP_NAME",
@@ -57,7 +62,8 @@ def test_parse_config_file_correctly(self):
5762
"SECURENATIVE_AUTO_SEND": "True",
5863
"SECURENATIVE_DISABLE": "False",
5964
"SECURENATIVE_LOG_LEVEL": "Critical",
60-
"SECURENATIVE_FAILOVER_STRATEGY": "fail-closed"
65+
"SECURENATIVE_FAILOVER_STRATEGY": "fail-closed",
66+
"SECURENATIVE_PROXY_HEADERS": "CF-Connecting-IP,Some-Random-Ip"
6167
}
6268

6369
self.create_ini_file(config)
@@ -73,25 +79,11 @@ def test_parse_config_file_correctly(self):
7379
self.assertEqual(options.log_level, "Critical")
7480
self.assertEqual(options.max_events, "100")
7581
self.assertEqual(options.timeout, "1500")
82+
self.assertEqual(options.proxy_headers, ["CF-Connecting-IP", "Some-Random-Ip"])
7683

7784
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
7885
def test_ignore_unknown_config_in_properties_file(self):
79-
try:
80-
os.remove(self.config_file_path)
81-
del os.environ["SECURENATIVE_API_KEY"]
82-
del os.environ["SECURENATIVE_API_URL"]
83-
del os.environ["SECURENATIVE_INTERVAL"]
84-
del os.environ["SECURENATIVE_MAX_EVENTS"]
85-
del os.environ["SECURENATIVE_TIMEOUT"]
86-
del os.environ["SECURENATIVE_AUTO_SEND"]
87-
del os.environ["SECURENATIVE_DISABLE"]
88-
del os.environ["SECURENATIVE_LOG_LEVEL"]
89-
del os.environ["SECURENATIVE_FAILOVER_STRATEGY"]
90-
except FileNotFoundError:
91-
pass
92-
except KeyError:
93-
pass
94-
86+
self.clean_settings()
9587
config = {
9688
"SECURENATIVE_TIMEOUT": "1500",
9789
"SECURENATIVE_UNKNOWN_KEY": "SOME_UNKNOWN_KEY"
@@ -105,22 +97,7 @@ def test_ignore_unknown_config_in_properties_file(self):
10597

10698
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
10799
def test_handle_invalid_config_file(self):
108-
try:
109-
os.remove(self.config_file_path)
110-
del os.environ["SECURENATIVE_API_KEY"]
111-
del os.environ["SECURENATIVE_API_URL"]
112-
del os.environ["SECURENATIVE_INTERVAL"]
113-
del os.environ["SECURENATIVE_MAX_EVENTS"]
114-
del os.environ["SECURENATIVE_TIMEOUT"]
115-
del os.environ["SECURENATIVE_AUTO_SEND"]
116-
del os.environ["SECURENATIVE_DISABLE"]
117-
del os.environ["SECURENATIVE_LOG_LEVEL"]
118-
del os.environ["SECURENATIVE_FAILOVER_STRATEGY"]
119-
except FileNotFoundError:
120-
pass
121-
except KeyError:
122-
pass
123-
100+
self.clean_settings()
124101
config = {"bla": "bla"}
125102

126103
self.create_ini_file(config)
@@ -130,27 +107,12 @@ def test_handle_invalid_config_file(self):
130107

131108
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
132109
def ignore_invalid_config_file_entries(self):
133-
try:
134-
os.remove(self.config_file_path)
135-
del os.environ["SECURENATIVE_API_KEY"]
136-
del os.environ["SECURENATIVE_API_URL"]
137-
del os.environ["SECURENATIVE_INTERVAL"]
138-
del os.environ["SECURENATIVE_MAX_EVENTS"]
139-
del os.environ["SECURENATIVE_TIMEOUT"]
140-
del os.environ["SECURENATIVE_AUTO_SEND"]
141-
del os.environ["SECURENATIVE_DISABLE"]
142-
del os.environ["SECURENATIVE_LOG_LEVEL"]
143-
del os.environ["SECURENATIVE_FAILOVER_STRATEGY"]
144-
except FileNotFoundError:
145-
pass
146-
except KeyError:
147-
pass
148-
110+
self.clean_settings()
149111
config = {
150112
"SECURENATIVE_API_KEY": 1,
151113
"SECURENATIVE_API_URL": 3,
152114
"SECURENATIVE_TIMEOUT": "bad timeout",
153-
"SECURENATIVE_FAILOVER_STRATEGY": "fail-what"
115+
"SECURENATIVE_FAILOVER_STRATEGY": "fail-what",
154116
}
155117

156118
self.create_ini_file(config)
@@ -161,22 +123,7 @@ def ignore_invalid_config_file_entries(self):
161123

162124
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
163125
def test_load_default_config(self):
164-
try:
165-
os.remove(self.config_file_path)
166-
del os.environ["SECURENATIVE_API_KEY"]
167-
del os.environ["SECURENATIVE_API_URL"]
168-
del os.environ["SECURENATIVE_INTERVAL"]
169-
del os.environ["SECURENATIVE_MAX_EVENTS"]
170-
del os.environ["SECURENATIVE_TIMEOUT"]
171-
del os.environ["SECURENATIVE_AUTO_SEND"]
172-
del os.environ["SECURENATIVE_DISABLE"]
173-
del os.environ["SECURENATIVE_LOG_LEVEL"]
174-
del os.environ["SECURENATIVE_FAILOVER_STRATEGY"]
175-
except FileNotFoundError:
176-
pass
177-
except KeyError:
178-
pass
179-
126+
self.clean_settings()
180127
options = ConfigurationManager.load_config(None)
181128

182129
self.assertIsNotNone(options)
@@ -189,24 +136,11 @@ def test_load_default_config(self):
189136
self.assertEqual(str(options.disable), "False")
190137
self.assertEqual(options.log_level, "CRITICAL")
191138
self.assertEqual(options.fail_over_strategy, FailOverStrategy.FAIL_OPEN.value)
139+
self.assertEqual(len(options.proxy_headers), 0)
192140

193141
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
194142
def test_get_config_from_env_variables(self):
195-
try:
196-
os.remove(self.config_file_path)
197-
del os.environ["SECURENATIVE_API_KEY"]
198-
del os.environ["SECURENATIVE_API_URL"]
199-
del os.environ["SECURENATIVE_INTERVAL"]
200-
del os.environ["SECURENATIVE_MAX_EVENTS"]
201-
del os.environ["SECURENATIVE_TIMEOUT"]
202-
del os.environ["SECURENATIVE_AUTO_SEND"]
203-
del os.environ["SECURENATIVE_DISABLE"]
204-
del os.environ["SECURENATIVE_LOG_LEVEL"]
205-
del os.environ["SECURENATIVE_FAILOVER_STRATEGY"]
206-
except FileNotFoundError:
207-
pass
208-
except KeyError:
209-
pass
143+
self.clean_settings()
210144

211145
os.environ["SECURENATIVE_API_KEY"] = "SOME_ENV_API_KEY"
212146
os.environ["SECURENATIVE_API_URL"] = "SOME_API_URL"
@@ -217,6 +151,7 @@ def test_get_config_from_env_variables(self):
217151
os.environ["SECURENATIVE_DISABLE"] = "True"
218152
os.environ["SECURENATIVE_LOG_LEVEL"] = "Debug"
219153
os.environ["SECURENATIVE_FAILOVER_STRATEGY"] = "fail-closed"
154+
os.environ["SECURENATIVE_PROXY_HEADERS"] = "CF-Connecting-IP"
220155

221156
options = ConfigurationManager.load_config(None)
222157

@@ -229,25 +164,11 @@ def test_get_config_from_env_variables(self):
229164
self.assertEqual(options.disable, "True")
230165
self.assertEqual(options.log_level, "Debug")
231166
self.assertEqual(options.fail_over_strategy, FailOverStrategy.FAIL_CLOSED.value)
167+
self.assertEqual(options.proxy_headers, "CF-Connecting-IP")
232168

233169
@unittest.skipIf(platform.system() == "Windows" or platform.system() == "windows", "test not supported on windows")
234170
def test_default_values_for_invalid_enum_config_props(self):
235-
try:
236-
os.remove(self.config_file_path)
237-
del os.environ["SECURENATIVE_API_KEY"]
238-
del os.environ["SECURENATIVE_API_URL"]
239-
del os.environ["SECURENATIVE_INTERVAL"]
240-
del os.environ["SECURENATIVE_MAX_EVENTS"]
241-
del os.environ["SECURENATIVE_TIMEOUT"]
242-
del os.environ["SECURENATIVE_AUTO_SEND"]
243-
del os.environ["SECURENATIVE_DISABLE"]
244-
del os.environ["SECURENATIVE_LOG_LEVEL"]
245-
del os.environ["SECURENATIVE_FAILOVER_STRATEGY"]
246-
except FileNotFoundError:
247-
pass
248-
except KeyError:
249-
pass
250-
171+
self.clean_settings()
251172
config = {
252173
"SECURENATIVE_FAILOVER_STRATEGY": "fail-something"
253174
}

0 commit comments

Comments
 (0)