Skip to content

RUBY-3813 Support KMS connect callback for HTTP proxies - #3119

Draft
jamis wants to merge 10 commits into
mongodb:masterfrom
jamis:3813-kms-connect-callback
Draft

jamis wants to merge 10 commits into
mongodb:masterfrom
jamis:3813-kms-connect-callback

Conversation

@jamis

@jamis jamis commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Adds a :kms_connect_callback option to auto-encryption and Mongo::ClientEncryption, so CSFLE/QE users behind an HTTP proxy can route KMS requests through it. The callback gets the KMS host, port, and the remaining timeout, and hands back a socket already connected to that host. The driver wraps that in TLS to the KMS host itself, so SNI and certificate verification still target the real endpoint rather than the proxy.

Socket::SSL can now handshake over a socket it didn't create, and the CSOT timeout now reaches the KMS socket. That last part also exposed a milliseconds-vs-seconds bug that would have made CSOT KMS I/O effectively unbounded, so that's fixed here too.

The six prose tests from the spec need AWS credentials, so they only really run in Evergreen under the test-fle task; locally they skip cleanly. Note: KMS socket operations are now bounded by the remaining operation budget instead of a flat 10 seconds.

jamis added 10 commits October 26, 2026 13:10
…result

feed_kms passed timeout_ms straight to Timeout.timeout, which takes
seconds, so a CSOT budget of 30000 bounded socket reads and writes to
30000 seconds. Convert the budget to seconds and use the CSOT timeout
error class when a budget is in effect.

Also reject a kms_connect_callback that returns nil or a non-IO value
with a clear KmsError, instead of silently dialing the KMS host directly.
build_tcp_socket ran outside the rescue in SSL#initialize, so a failure
in setsockopt or set_socket_options leaked the freshly created socket.
It now closes the socket before re-raising.

Likewise, when create_context raised, a caller-provided
:connected_socket was never closed because @tcp_socket had not been
assigned yet. Close it on that failure path.
connect_callback always opened a plain TCPSocket and wrote a cleartext
CONNECT, so against a proxy started in TLS mode (--ca_file/--cert_file) the
TLS handshake failed with ECONNRESET and spec case 2 could never pass.

Add a tls: keyword to connect_callback. When true, the CONNECT handshake runs
over a TLS connection to the proxy, and the callback returns one end of a
socketpair bridged to the proxy by a background thread. The bridge is needed
because the driver wraps the returned socket in TLS to the KMS host, and Ruby's
OpenSSL::SSL::SSLSocket cannot wrap another SSLSocket.

Also pass extra_options from the auto-encryption client (matching every other
FLE integration spec) and record the deliberate timeout_ms deviation in case 5.
The option docs described the callback's contract but gave no usage example and
did not state the return type precisely. The returned object must be fd-backed
(a real IO or Socket) because the driver wraps it in TLS to the KMS host and
Ruby's OpenSSL requires an fd-backed object; a TLS-to-proxy connection
therefore needs a socketpair bridge.

Add a usage example to ClientEncryption#initialize and reference it from the
Client and AutoEncrypter option docs.
@jamis
jamis requested a review from a team as a code owner October 26, 2026 21:52
@jamis
jamis requested a review from comandeo-mongo October 26, 2026 21:52
@jamis
jamis marked this pull request as draft October 26, 2026 22:35

@comandeo-mongo comandeo-mongo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many tests are red

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants