Skip to content

THRIFT-6233: Use the peer-address matcher in TSSLServerSocket on every Python - #3839

Merged
Jens-G merged 1 commit into
apache:masterfrom
slachiewicz:THRIFT-6233
Sep 20, 2026
Merged

Jens-G merged 1 commit into
apache:masterfrom
slachiewicz:THRIFT-6233

Conversation

@slachiewicz

@slachiewicz slachiewicz commented Sep 11, 2026

Copy link
Copy Markdown
Member

JIRA: THRIFT-6233
Client: py

TSSLServerSocket checks a client certificate against the address the connection came from, whenever cert_reqs asks for a certificate. Which function did the check depended on the Python version. Python 3.12 and later used sslcompat.match_peer_ipaddress. Python 3.11 and earlier used ssl.match_hostname. The two disagree. Only the former reduces an IPv4-mapped peer (THRIFT-6201). Only the latter falls back to the commonName when the certificate has no subjectAltName. A dual-stack listener (host=None) reports an IPv4 client as ::ffff:127.0.0.1. So a certificate that carries 127.0.0.1 was accepted on 3.12 and refused on 3.10. THRIFT-3660 hid that in 2016 by listing the mapped address in client_v3.crt. #3857 removed that entry, because Go 1.27 refuses to load a certificate with one. Since then the cross-test Python server, which listens dual-stack with client_v3.crt as its CA, refuses every IPv4 client on Python 3.11 and earlier.

The server only matches an IP address, so match_peer_ipaddress is now its default on every version. The check stays on by default and looks only at IP subjectAltName records. An IPv4-mapped peer matches the plain address on every version. A certificate without an IP subjectAltName is refused, where ssl.match_hostname compared the commonName with the address string. DNS records are not matched, because a server has no name for its client. To decide which subjects may connect, pass a validate_callback. It receives the getpeercert() dictionary and the peer address. The docstring shows a callback that refuses on the subject. TSSLSocket on the client side is unchanged.

The backports.ssl_match_hostname branch in sslcompat._optional_dependencies and the ValueError in TSSLServerSocket.__init__ that named it are removed. #3863 (THRIFT-6265) already dropped the setup.py dependency. The library requires Python 3.10, so nothing below 3.5 reaches them.

The tests drive TSSLServerSocket.accept() under the running interpreter:

  • client_v3.crt is accepted.
  • client.crt, which has no subjectAltName, is refused.
  • A client that arrives over IPv4 at a dual-stack listener is accepted with client_v3.crt, which no longer carries a mapped entry. This is the cross-test server's configuration.
  • A caller-supplied callback that refuses on the subject is honoured, and it receives the certificate and the peer address.

lib/py/README.md records the change for 0.25.0.

Verified: test/test_sslsocket.py passes against this branch on Python 3.10, 3.11, 3.12, 3.13 and 3.14. Against master's library, the default-callback test and the dual-stack test fail on 3.10 and 3.11. flake8 --config setup.cfg reports the same 19 pre-existing E501 lines as master and none in the new code.

This change was created with AI assistance.

@slachiewicz

This comment was marked as resolved.

@slachiewicz
slachiewicz marked this pull request as ready for review September 15, 2026 21:08
@slachiewicz
slachiewicz marked this pull request as draft September 16, 2026 13:12
@slachiewicz slachiewicz changed the title THRIFT-6233: Make the peer-address check on TSSLServerSocket opt-in THRIFT-6233: Use the peer-address matcher in TSSLServerSocket on every Python Sep 16, 2026
…y Python

Client: py

TSSLServerSocket checks a client certificate against the address the
connection came from. On Python 3.12 and later it used
sslcompat.match_peer_ipaddress. On 3.11 and earlier it used
ssl.match_hostname. The two disagree. Only the former reduces an
IPv4-mapped peer (THRIFT-6201). Only the latter falls back to the
commonName when the certificate has no subjectAltName. A dual-stack
listener reports an IPv4 client as ::ffff:127.0.0.1, so a certificate
that carries 127.0.0.1 was accepted on 3.12 and refused on 3.10.
THRIFT-3660 hid that in 2016 by listing the mapped address in
client_v3.crt. THRIFT-6275 removed that entry, because Go 1.27 refuses
to load a certificate with one. Since then the cross-test Python server,
which listens dual-stack with client_v3.crt as its CA, refuses every
IPv4 client on 3.11 and earlier.

The server only matches an IP address, so match_peer_ipaddress is now
its default on every version. The check runs whenever cert_reqs asks for
a client certificate. It looks only at IP subjectAltName records. Which
subjects may connect is the application's decision, made in a
validate_callback; the docstring now shows one. TSSLSocket on the client
side is unchanged.

The backports.ssl_match_hostname branch in sslcompat, and the ValueError
that named it, are removed. THRIFT-6265 already dropped the setup.py
dependency. The library requires Python 3.10, so nothing below 3.5 can
reach them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@slachiewicz
slachiewicz marked this pull request as ready for review September 18, 2026 23:14
@Jens-G
Jens-G merged commit 09b177a into apache:master Sep 20, 2026
106 checks passed
@slachiewicz
slachiewicz deleted the THRIFT-6233 branch September 20, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants