diff --git a/CHANGES.rst b/CHANGES.rst index 88b983e4bf..b8fc14522f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,11 @@ Changes in Apache Libcloud 3.9.2 Common ~~~~~~ +- Respect the ``no_proxy`` / ``NO_PROXY`` environment variable so an explicitly + configured proxy is bypassed for matching hosts. + (GITHUB-2077) + [Sanjay Santhanam - @Sanjays2402] + - Move tests to python 3.12. (#2152) [Miguel Caballer - @micafer] diff --git a/libcloud/http.py b/libcloud/http.py index 4c1c1b01e4..21c0a0d4a8 100644 --- a/libcloud/http.py +++ b/libcloud/http.py @@ -22,6 +22,7 @@ import requests from requests.adapters import HTTPAdapter +from requests.utils import should_bypass_proxies import libcloud.security from libcloud.utils.py3 import urlparse @@ -112,6 +113,28 @@ def set_http_proxy(self, proxy_url): "https": proxy_url, } + + def _proxies_for_url(self, url): + """ + Return the proxy mapping to use for ``url``. + + An explicitly configured proxy is skipped when the target host matches + the ``no_proxy`` / ``NO_PROXY`` environment variable, so libcloud + behaves consistently with other HTTP clients. + + :param url: Absolute request URL. + :type url: ``str`` + + :rtype: ``dict`` or ``None`` + """ + if not self.session.proxies: + return None + + if should_bypass_proxies(url, no_proxy=None): + return {} + + return None + def _parse_proxy_url(self, proxy_url): """ Parse and validate a proxy URL. @@ -231,6 +254,7 @@ def request(self, method, url, body=None, headers=None, raw=False, stream=False, verify=self.verification, timeout=self.session.timeout, hooks=hooks, + proxies=self._proxies_for_url(url), ) def prepared_request(self, method, url, body=None, headers=None, raw=False, stream=False): diff --git a/libcloud/test/test_connection.py b/libcloud/test/test_connection.py index a7bf1d7c8e..416cee401f 100644 --- a/libcloud/test/test_connection.py +++ b/libcloud/test/test_connection.py @@ -161,6 +161,18 @@ def test_constructor(self): {"http": "https://127.0.0.6:3129", "https": "https://127.0.0.6:3129"}, ) + def test_proxy_is_bypassed_for_no_proxy_hosts(self): + # Regression test for GITHUB-2077: an explicitly configured proxy must + # not be used for hosts listed in the no_proxy environment variable. + os.environ["no_proxy"] = "internal.example.com" + self.addCleanup(os.environ.pop, "no_proxy", None) + + conn = LibcloudConnection(host="internal.example.com", port=443) + conn.set_http_proxy("http://proxy.example.com:3128") + + self.assertEqual(conn._proxies_for_url("https://internal.example.com/path"), {}) + self.assertIsNone(conn._proxies_for_url("https://other.example.com/path")) + def test_proxy_environment_variables_respected(self): """ Test that proxy environment variables are respected by the underlying Requests library