Skip to content

Commit 06f2879

Browse files
Only trust last entry in x-forwarded-for by default (ansible#575)
1 parent 5c15cec commit 06f2879

File tree

2 files changed

+28
-11
lines changed

2 files changed

+28
-11
lines changed

ansible_base/lib/utils/requests.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ def get_remote_host(request: HttpRequest) -> Optional[str]:
1515
return value[0] if value else None
1616

1717

18+
def split_header(value: str) -> list[str]:
19+
values = []
20+
for a_value in value.split(','):
21+
a_value = a_value.strip()
22+
if a_value:
23+
values.append(a_value)
24+
return values
25+
26+
1827
def get_remote_hosts(request: HttpRequest, get_first_only: bool = False) -> list[str]:
1928
'''
2029
Get all IPs from the allowed headers
@@ -27,24 +36,32 @@ def get_remote_hosts(request: HttpRequest, get_first_only: bool = False) -> list
2736

2837
headers = get_setting('REMOTE_HOST_HEADERS', ['REMOTE_ADDR', 'REMOTE_HOST'])
2938

39+
for header in headers:
40+
for value in split_header(request.META.get(header, '')):
41+
remote_hosts.append(value)
42+
3043
# If we are connected to from a trusted proxy then we can add some additional headers
3144
try:
3245
if 'HTTP_X_TRUSTED_PROXY' in request.META:
3346
if validate_x_trusted_proxy_header(request.META['HTTP_X_TRUSTED_PROXY']):
34-
headers.insert(0, 'HTTP_X_FORWARDED_FOR')
35-
headers.insert(0, 'HTTP_X_ENVOY_EXTERNAL_ADDRESS')
47+
# The last entry in x-forwarded-for from envoy can be trusted implicitly
48+
# https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-for
49+
values = split_header(request.META.get('HTTP_X_FORWARDED_FOR', ''))
50+
if values:
51+
remote_hosts.insert(0, values[-1])
52+
53+
# x-envoy-external-address can always be trusted when coming from envoy
54+
# https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-envoy-external-address
55+
for value in reversed(split_header(request.META.get('HTTP_X_ENVOY_EXTERNAL_ADDRESS', ''))):
56+
remote_hosts.insert(0, value)
3657
else:
3758
logger.error("Unable to use headers from trusted proxy because shared secret was invalid!")
3859
except Exception:
3960
logger.exception("Failed to validate HTTP_X_TRUSTED_PROXY")
4061

41-
for header in headers:
42-
for value in request.META.get(header, '').split(','):
43-
value = value.strip()
44-
if value:
45-
if get_first_only:
46-
return [value]
47-
remote_hosts.append(value)
62+
if get_first_only and len(remote_hosts) > 0:
63+
return [remote_hosts[0]]
64+
4865
return remote_hosts
4966

5067

test_app/tests/lib/utils/test_requests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_get_remote_hosts_no_headers():
5252
},
5353
{'REMOTE_ADDR': '9.10.11.12', 'REMOTE_HOST': 'localhost, example.com'},
5454
False,
55-
['5.6.7.8', '9.10.11.12', '1.2.3.4', '5.6.7.8', '9.10.11.12', 'localhost', 'example.com'],
55+
['5.6.7.8', '9.10.11.12', '5.6.7.8', '9.10.11.12', 'localhost', 'example.com'],
5656
),
5757
# Complicated example w/o the trusted_proxy header
5858
(
@@ -100,7 +100,7 @@ def test_get_remote_hosts_alternate_headers_behind_trusted_proxy(rsa_keypair):
100100
request = RequestFactory().get('/hello', headers=headers)
101101
with override_settings(REMOTE_HOST_HEADERS=['HTTP_X_JOHNS_HEADER']):
102102
remote_hosts = get_remote_hosts(request, get_first_only=False)
103-
assert remote_hosts == ['5.6.7.8', '9.10.11.12', '1.2.3.4', '5.6.7.8', 'a.b.c.d']
103+
assert remote_hosts == ['5.6.7.8', '9.10.11.12', '5.6.7.8', 'a.b.c.d']
104104

105105

106106
@mock.patch("ansible_base.lib.utils.requests.validate_x_trusted_proxy_header", side_effect=Exception)

0 commit comments

Comments
 (0)