1
mirror of https://github.com/home-assistant/core synced 2024-09-15 17:29:45 +02:00

Fix httpx client creating a new ssl context with each client (memory leak) (#90191)

* Fix httpx client creating a new ssl context with each client

While working on https://github.com/home-assistant/core/issues/83524
it was discovered that each new httpx client creates a new ssl context

f1157dbc41/httpx/_transports/default.py (L261)

If an ssl context is passed in creating a new one is avoided here

f1157dbc41/httpx/_config.py (L110)

This change makes httpx ssl no-verify behavior match aiohttp ssl no-verify
behavior

6da04694fd/aiohttp/connector.py (L892)

aiohttp solved this by wrapping the code that generates the ssl context
in an lru_cache

* compact
This commit is contained in:
J. Nick Koston 2023-03-23 21:40:47 -10:00 committed by GitHub
parent ca157f4d19
commit 1f2268a878
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 33 additions and 3 deletions

View File

@ -273,7 +273,7 @@ def _async_get_connector(
if verify_ssl:
ssl_context: bool | SSLContext = ssl_util.get_default_context()
else:
ssl_context = False
ssl_context = ssl_util.get_default_no_verify_context()
connector = aiohttp.TCPConnector(
enable_cleanup_closed=True,

View File

@ -11,7 +11,7 @@ from typing_extensions import Self
from homeassistant.const import APPLICATION_NAME, EVENT_HOMEASSISTANT_CLOSE, __version__
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.loader import bind_hass
from homeassistant.util import ssl as ssl_util
from homeassistant.util.ssl import get_default_context, get_default_no_verify_context
from .frame import warn_use
@ -65,8 +65,11 @@ def create_async_httpx_client(
This method must be run in the event loop.
"""
ssl_context = (
get_default_context() if verify_ssl else get_default_no_verify_context()
)
client = HassHttpXAsyncClient(
verify=ssl_util.get_default_context() if verify_ssl else False,
verify=ssl_context,
headers={USER_AGENT: SERVER_SOFTWARE},
**kwargs,
)

View File

@ -1,10 +1,31 @@
"""Helper to create SSL contexts."""
import contextlib
from os import environ
import ssl
import certifi
def create_no_verify_ssl_context() -> ssl.SSLContext:
"""Return an SSL context that does not verify the server certificate.
This is a copy of aiohttp's create_default_context() function, with the
ssl verify turned off.
https://github.com/aio-libs/aiohttp/blob/33953f110e97eecc707e1402daa8d543f38a189b/aiohttp/connector.py#L911
"""
sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
sslcontext.options |= ssl.OP_NO_SSLv2
sslcontext.options |= ssl.OP_NO_SSLv3
sslcontext.check_hostname = False
sslcontext.verify_mode = ssl.CERT_NONE
with contextlib.suppress(AttributeError):
# This only works for OpenSSL >= 1.0.0
sslcontext.options |= ssl.OP_NO_COMPRESSION
sslcontext.set_default_verify_paths()
return sslcontext
def client_context() -> ssl.SSLContext:
"""Return an SSL context for making requests."""
@ -18,6 +39,7 @@ def client_context() -> ssl.SSLContext:
# Create this only once and reuse it
_DEFAULT_SSL_CONTEXT = client_context()
_DEFAULT_NO_VERIFY_SSL_CONTEXT = create_no_verify_ssl_context()
def get_default_context() -> ssl.SSLContext:
@ -25,6 +47,11 @@ def get_default_context() -> ssl.SSLContext:
return _DEFAULT_SSL_CONTEXT
def get_default_no_verify_context() -> ssl.SSLContext:
"""Return the default SSL context that does not verify the server certificate."""
return _DEFAULT_NO_VERIFY_SSL_CONTEXT
def server_context_modern() -> ssl.SSLContext:
"""Return an SSL context following the Mozilla recommendations.