Skip to content

no_proxy setting is not honored in connector #8549

Open
@sta4152

Description

@sta4152

Describe the bug

We try to connect to a kubernetes cluster, residing behind a proxy that is needed for downloading external resources, but not for connecting to the cluster.
In the proxy log we observe access to 10.152.183.1:443, which is the cluster's api server, and whose network is excluded using no_proxy/NO_PROXY environment.
We narrowed it doen to (at least)

if req.proxy:

Here it is asked only, if there is set a proxy. Not considering the target to establish the connection to. And thus not considering the exclusion for the k9s api server address.

To Reproduce

You should be able to test much easier, here is just what we used for testing:

  1. having a kubernetes cluster
  2. create within it a pod having set a http/https_proxy and no_proxy excluding the kubernetes internal networking, e.g. 10.152.183.0/24 (with default k9s networking)
  3. run
api_client = kubernetes.client.api_client.ApiClient()
custom_objects_api = kubernetes.client.CustomObjectsApi(api_client)
await custom_objects_api.get_namespaced_custom_object(
                    group="",
                    version="v1",
                    plural="pods",
                    namespace="default",
                    name="test",
                )```

### Expected behavior

The proxy must not be accessed for addresses mentioned in no_proxy/NO_PROXY

### Logs/tracebacks

```python-traceback
Here: run from within a notebook
Please note the line "941 if req.proxy" by which the branch is chosen to contact the proxy server:

---------------------------------------------------------------------------
ConnectionResetError                      Traceback (most recent call last)
File /opt/conda/lib/python3.11/site-packages/aiohttp/connector.py:1137, in TCPConnector._start_tls_connection(self, underlying_transport, req, timeout, client_error)
   1136 try:
-> 1137     tls_transport = await self._loop.start_tls(
   1138         underlying_transport,
   1139         tls_proto,
   1140         sslcontext,
   1141         server_hostname=req.server_hostname or req.host,
   1142         ssl_handshake_timeout=timeout.total,
   1143     )
   1144 except BaseException:
   1145     # We need to close the underlying transport since
   1146     # `start_tls()` probably failed before it had a
   1147     # chance to do this:

File /opt/conda/lib/python3.11/asyncio/base_events.py:1267, in BaseEventLoop.start_tls(self, transport, protocol, sslcontext, server_side, server_hostname, ssl_handshake_timeout, ssl_shutdown_timeout)
   1266 try:
-> 1267     await waiter
   1268 except BaseException:

File /opt/conda/lib/python3.11/asyncio/sslproto.py:575, in SSLProtocol._on_handshake_complete(self, handshake_exc)
    574 else:
--> 575     raise handshake_exc
    577 peercert = sslobj.getpeercert()

ConnectionResetError: 

The above exception was the direct cause of the following exception:

ClientConnectorError                      Traceback (most recent call last)
Cell In[41], line 15
     13 api_client = kubernetes.client.api_client.ApiClient()
     14 custom_objects_api = kubernetes.client.CustomObjectsApi(api_client)
---> 15 await custom_objects_api.get_namespaced_custom_object(
     16                     group="",
     17                     version="v1",
     18                     plural="pods",
     19                     namespace="default",
     20                     name="test",
     21                 )

File /opt/conda/lib/python3.11/site-packages/kubernetes_asyncio/client/api_client.py:185, in ApiClient.__call_api(self, resource_path, method, path_params, query_params, header_params, body, post_params, files, response_types_map, auth_settings, _return_http_data_only, collection_formats, _preload_content, _request_timeout, _host, _request_auth)
    181     url = _host + resource_path
    183 try:
    184     # perform request and return response
--> 185     response_data = await self.request(
    186         method, url, query_params=query_params, headers=header_params,
    187         post_params=post_params, body=body,
    188         _preload_content=_preload_content,
    189         _request_timeout=_request_timeout)
    190 except ApiException as e:
    191     e.body = e.body.decode('utf-8') if six.PY3 else e.body

File /opt/conda/lib/python3.11/site-packages/kubernetes_asyncio/client/rest.py:198, in RESTClientObject.GET(self, url, headers, query_params, _preload_content, _request_timeout)
    196 async def GET(self, url, headers=None, query_params=None,
    197               _preload_content=True, _request_timeout=None):
--> 198     return (await self.request("GET", url,
    199                                headers=headers,
    200                                _preload_content=_preload_content,
    201                                _request_timeout=_request_timeout,
    202                                query_params=query_params))

File /opt/conda/lib/python3.11/site-packages/kubernetes_asyncio/client/rest.py:182, in RESTClientObject.request(self, method, url, query_params, headers, body, post_params, _preload_content, _request_timeout)
    177         msg = """Cannot prepare a request message for provided
    178                  arguments. Please check that your arguments match
    179                  declared content type."""
    180         raise ApiException(status=0, reason=msg)
--> 182 r = await self.pool_manager.request(**args)
    183 if _preload_content:
    185     data = await r.read()

File /opt/conda/lib/python3.11/site-packages/aiohttp/client.py:581, in ClientSession._request(self, method, str_or_url, params, data, json, cookies, headers, skip_auto_headers, auth, allow_redirects, max_redirects, compress, chunked, expect100, raise_for_status, read_until_eof, proxy, proxy_auth, timeout, verify_ssl, fingerprint, ssl_context, ssl, server_hostname, proxy_headers, trace_request_ctx, read_bufsize, auto_decompress, max_line_size, max_field_size)
    576     async with ceil_timeout(
    577         real_timeout.connect,
    578         ceil_threshold=real_timeout.ceil_threshold,
    579     ):
    580         assert self._connector is not None
--> 581         conn = await self._connector.connect(
    582             req, traces=traces, timeout=real_timeout
    583         )
    584 except asyncio.TimeoutError as exc:
    585     raise ServerTimeoutError(
    586         "Connection timeout " "to host {}".format(url)
    587     ) from exc

File /opt/conda/lib/python3.11/site-packages/aiohttp/connector.py:544, in BaseConnector.connect(self, req, traces, timeout)
    541         await trace.send_connection_create_start()
    543 try:
--> 544     proto = await self._create_connection(req, traces, timeout)
    545     if self._closed:
    546         proto.close()

File /opt/conda/lib/python3.11/site-packages/aiohttp/connector.py:942, in TCPConnector._create_connection(self, req, traces, timeout)
    937 """Create connection.
    938 
    939 Has same keyword arguments as BaseEventLoop.create_connection.
    940 """
    941 if req.proxy:
--> 942     _, proto = await self._create_proxy_connection(req, traces, timeout)
    943 else:
    944     _, proto = await self._create_direct_connection(req, traces, timeout)

File /opt/conda/lib/python3.11/site-packages/aiohttp/connector.py:1379, in TCPConnector._create_proxy_connection(self, req, traces, timeout)
   1369         sslcontext = self._get_ssl_context(req)
   1370         return await self._wrap_create_connection(
   1371             self._factory,
   1372             timeout=timeout,
   (...)
   1376             req=req,
   1377         )
-> 1379     return await self._start_tls_connection(
   1380         # Access the old transport for the last time before it's
   1381         # closed and forgotten forever:
   1382         transport,
   1383         req=req,
   1384         timeout=timeout,
   1385     )
   1386 finally:
   1387     proxy_resp.close()

File /opt/conda/lib/python3.11/site-packages/aiohttp/connector.py:1157, in TCPConnector._start_tls_connection(self, underlying_transport, req, timeout, client_error)
   1155     if exc.errno is None and isinstance(exc, asyncio.TimeoutError):
   1156         raise
-> 1157     raise client_error(req.connection_key, exc) from exc
   1158 except TypeError as type_err:
   1159     # Example cause looks like this:
   1160     # TypeError: transport <asyncio.sslproto._SSLProtocolTransport
   1161     # object at 0x7f760615e460> is not supported by start_tls()
   1163     raise ClientConnectionError(
   1164         "Cannot initialize a TLS-in-TLS connection to host "
   1165         f"{req.host!s}:{req.port:d} through an underlying connection "
   1166         f"to an HTTPS proxy {req.proxy!s} ssl:{req.ssl or 'default'} "
   1167         f"[{type_err!s}]"
   1168     ) from type_err

ClientConnectorError: Cannot connect to host 10.152.183.1:443 ssl:default [None]

Python Version

Python 3.11.6

aiohttp Version

aiohttp 3.9.5

multidict Version

multidict 6.0.5

yarl Version

yarl 1.9.4

OS

Linux testing-0 5.15.0-116-generic #126-Ubuntu SMP Mon Jul 1 10:14:24 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux

Related component

Client

Additional context

No response

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions