You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add convenience kms_connect_callback helpers HTTPProxyKMSConnect and AsyncHTTPProxyKMSConnect.
The helpers tunnel CSFLE/QE KMS connections through a forward proxy with HTTP CONNECT, optionally using TLS.
Support a validated headers parameter (e.g. Proxy-Authorization) for authenticated proxies; names must be RFC 7230 tokens and hosts/headers are checked to prevent request-line injection.
Most of the changes are the tests.
Test Plan
New unit and integration tests for both helpers.
Checklist
Checklist for Author
Did you update the changelog (if necessary)?
Is there test coverage?
Is any followup work tracked in a JIRA ticket? If so, add link(s).
Checklist for Reviewer
Does the title of the PR reference a JIRA Ticket?
Do you fully understand the implementation? (Would you be comfortable explaining how this code works to someone else?)
Is all relevant documentation (README or docstring) updated?
Add HTTPProxyKMSConnect and AsyncHTTPProxyKMSConnect, ready-made
kms_connect_callback implementations that tunnel KMS connections
through a forward proxy with HTTP CONNECT, optionally over TLS to the
proxy. A headers parameter supports proxies that require
authentication (e.g. Proxy-Authorization: Basic); header names and
values are validated at construction to prevent request-line
injection.
The helpers are configured with a proxy_url (e.g.
http://proxy.example.com:8080). An https scheme implies TLS to the
proxy, defaulting to ssl.create_default_context(). Userinfo in the URL
is percent-decoded and sent as a Proxy-Authorization basic auth header.
The helpers live in the shared pymongo/_kms_connect_shared.py extracted
by PYTHON-6154, re-exported from pymongo.encryption_options.
urllib.parse.urlsplit raises ValueError for malformed URLs, e.g. an
unmatched IPv6 bracket, which escaped the helper constructors instead of
the ConfigurationError the other invalid proxy URLs raise. Normalize it
with the same message the invalid port raises, and cover the malformed
bracket forms in the URL validation test.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Header validation accepts a trailing newline, DNS resolution can exceed the connection deadline, and integration tests assume proxy services that their setup does not start.
Proxy DNS resolution can exceed the connection timeout
pymongo/_kms_connect_shared.py:323
getaddrinfo runs before any call to _remaining(deadline), so a slow or stuck proxy DNS lookup can block the synchronous helper past context.timeout. In the asynchronous helper, the caller can time out while the executor thread remains blocked in DNS, consuming a worker until resolution returns. Use a bounded resolution approach that accounts for the same connection deadline, or otherwise make this timeout limitation explicit and avoid leaving unbounded executor work.
Avoid shutting down both relay directions on half-close
pymongo/_kms_connect_shared.py:269
A half-close in either direction ends that relay, but SHUT_RDWR on dst also cuts off the opposite relay, and closing src here can close a socket that relay is still using. For example, if the driver finishes sending its request and half-closes, the proxy's KMS reply may be lost. Propagate normal EOF with a write-side shutdown and coordinate socket closure until both directions are finished; reserve full shutdown for teardown or unrecoverable errors.
Include DNS resolution within the connection timeout
pymongo/_kms_connect_shared.py:328
getaddrinfo() runs synchronously before the socket timeout is set. If proxy DNS stalls, the helper can exceed context.timeout: the synchronous callback stays blocked, and the asynchronous helper keeps an executor thread occupied even if a caller with timeoutMS stops waiting. Bound resolution as part of the connection budget, or make the timeout limitation explicit if DNS cannot be interrupted.
Explicit proxy port zero is incorrectly replaced by a default
pymongo/_kms_connect_shared.py:174
An explicit :0 parses as port 0, but port or ... replaces it with 80 (or 443 for HTTPS). That silently sends the CONNECT request, including any configured proxy credentials, to a different port than requested. Default only when split.port is None; reject zero if it is not a valid proxy port.
Proxy DNS resolution can exceed the connection deadline
pymongo/_kms_connect_shared.py:347
When proxy DNS is slow, this synchronous getaddrinfo runs before any deadline check. The synchronous helper can block past context.timeout, and the asynchronous helper can leave an executor worker occupied even if its caller stops waiting. Bound proxy name resolution or the overall operation to the remaining connect budget; a socket timeout applied after DNS returns cannot enforce it.
Async KMS connection can wait past the deadline
pymongo/_kms_connect_shared.py:397
The captured deadline does not limit this await: when the default executor is backed up or proxy DNS stalls, asyncio.shield(future) can wait past context.timeout. For explicit encryption there is no separate CSOT wait in _connect_kms, so the KMS operation can hang beyond its connect budget. Wait only for the remaining deadline here, and close a socket returned by the executor after either timeout or cancellation.
Redact proxy credentials from URL validation errors
pymongo/_kms_connect_shared.py:178
Validation errors include proxy_url!r, which exposes userinfo such as user:password if a configured URL has an invalid port (and other malformed-URL paths do the same). These exceptions may be logged with the proxy password intact. Redact userinfo in every URL-validation error, or report only the invalid component.
Prevent post-validation CONNECT header mutation
pymongo/_kms_connect_shared.py:188
self.headers is a mutable public dict after validation. A caller can later set callback.headers["X-Trace"] = "ok\r\nInjected: yes" or add a Host field; _tunnel serializes the modified values without rechecking them, bypassing the CONNECT request-injection guard. Store an immutable validated mapping for transmission or validate immediately before constructing the request.
Reject control characters in CONNECT header values
pymongo/_kms_connect_shared.py:201
The value check allows NUL, vertical tab, and other control characters besides CR/LF, and _tunnel sends those bytes in CONNECT headers. Such values are invalid HTTP field values and can be interpreted inconsistently by proxies. Reject disallowed control characters (while permitting legal space and horizontal tab) before storing the headers.
# The executor task timed out itself; report it directly.
raise
# The budget ran out with the thread still busy. The thread
# runs on regardless, so close the socket it returns, and
# report the deadline.
future.add_done_callback(_close_late_socket)
raise socket.timeout("timed out connecting through the proxy") from None
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PYTHON-6147
Changes in this PR
kms_connect_callbackhelpersHTTPProxyKMSConnectandAsyncHTTPProxyKMSConnect.The helpers tunnel CSFLE/QE KMS connections through a forward proxy with HTTP CONNECT, optionally using TLS.
headersparameter (e.g.Proxy-Authorization) for authenticated proxies; names must be RFC 7230 tokens and hosts/headers are checked to prevent request-line injection.Test Plan
Checklist
Checklist for Author
Checklist for Reviewer