-
Notifications
You must be signed in to change notification settings - Fork 142
Openssl hostname verification #1491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
It informs openssl that the configuration is for a tls client, not tls server. It is not really necessary for us, because it is clear what openssl method that we call when connecting that it is a tls client, but I thought it will be a bit more future proof. For more info see: https://docs.openssl.org/master/man3/SSL_set_connect_state/#notes
This assumes that nodes presents a certificate with its ip address in SAN list. This is the same assumption we have for rustls. The only alternatives I can think of is to have a hardcoded domain name accepted for all nodes, or some callback mapping node ip to expected SAN. Since this is not possible for rustls (you specify domain/ip only when conmnecting, not on ClientConfigBuilder, and we can't extend TlsContext::Rustls023 variant), I decided against introducing tests for this or documenting this, and went with the assumption that certs are for node IPs.
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds hostname verification for OpenSSL TLS connections, fixing a security gap where OpenSSL connections were not verifying that the server certificate's subject alternative name (SAN) matched the node's IP address (while rustls already performed this verification).
Key changes:
- Implemented hostname verification for OpenSSL by calling
ssl.param_mut().set_ip(node_address)before establishing the TLS connection - Added
ssl.set_connect_state()call during SSL object initialization to properly configure the connection for client mode - Updated test to verify that connections with mismatched certificate SANs are properly rejected
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| scylla/src/network/tls.rs | Added set_connect_state() call to configure OpenSSL in client mode during SSL object creation |
| scylla/src/network/connection.rs | Implemented hostname verification by setting the node IP address in SSL verification parameters before establishing connection |
| scylla/tests/integration/ccm/tls.rs | Updated test to verify hostname verification now properly rejects connections when certificate SAN doesn't match node IP |
| docs/source/connecting/tls.md | Added documentation section explaining hostname verification approach for both OpenSSL and rustls implementations |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
|
I confirmed with the cloud (@charconstpointer ) that in Cloud certs have node IP in SAN, so it looks like our approach is correct. |
This PR adds a bugfix / missing functionality (it is unclear to me if this classifies as bug) for hostname verification when using openssl.
Why we had hostname verification for rustls, but not openssl?
To create encrypted async sockets we use
tokio-rustlsandtokio-opensslcrates respectively.rustls
In rustls we first create
tokio_rustls::TlsConnectorfromrustls::ClientConfig, then we call itsconnectmethod, which also takesa stream and a
rustls_pki_types::server_name::ServerName(which is either IP or domain name). This domain name will be usedfor hostname verification. That means there is no (easy) way to skip hostname verification when using rustls (and tokio_rustls to wrap the socket).
I think user would need to provide custom certificate verifier in
ClientConfigto bypass that.We always provide node IP as
ServerName, so this approach assumes that node always presents a certificate with its IP in SAN field.openssl
openssl / tokio-openssl API is not so simple.
tokio-opensslhastokio_openssl::SslStreamstruct, which is created fromopenssl::ssl::Ssland a TCP stream.connectmethod on thisSslStreamdoes not take any hostname. The conclusion is clear:openssl::ssl::Sslstruct that we passis responsible for hostname verification, including having a hostname somehow configured.
How does
Sslgets created, and how can hostname verification be configured on it?Configuring
SslI found exactly one way to configure hostname verification on already existing
Sslinstance.To do that, we need to call
param_mut()on it, getting&mut X509VerifyParamRef. This hasset_ipandset_hostnamemethod.This is what this PR does. I added a call to
set_ipwith node ip.I think you'll agree that this is quite non-obvious approach for something as important as hostname verification, so I thought it must not be the only way.
Creating properly configured
SslSslcan be created either fromSslContextRef(this is what the driver uses, because we acceptSslContextfrom the user) or fromConnectConfiguration).SslContext
The first way (that we use) looks like a simple conversion ( https://docs.rs/openssl/0.10.73/openssl/ssl/struct.Ssl.html#method.new ) so for hostname verification to
be enabled there it would already have to be enabled on
SslContext. There is no way to enable it, or provide hostname, onSslContext. It can be done withSslContextBuilderusing the similar approachparam_mutapproach I described above (the method is calledverify_param_muthere),but it is not feasible for us (no way to create builder from context). Dead end.
ConnectConfiguration
As I mentioned, the alternative is to use
ConnectConfiguration. Its into_ssl method takes a hostname (which can also be an ip address).It also has methods to enable disable verification (and sni), advising against disabling it. Looks great!
What does it internally do to set the hostname?
It looks like it depends on the system library, and either uses the param_mut approach, or
set_ex_datawhichSets the extra data at the specified index.(that looks like extremely low-level approach).Good news is that our param_mut approach looks correct.
But in general it seems that
ConnectConfigurationis a more friendly API, which makes me think that we should use it. Can we?It can be created in exactly one way: using
configure()method onSslConnector. What isSslConnector?!Its description:
Huh, so maybe we should be using that? Can we?
What is the flow of creating
SslConnector?You call
SslConnector::builderto create a new builder (SslConnectorBuilder). It implementsDeref<Target=SslContextBuilder>, so it looks like the difference between them comes down to defaults.The good news is that our users can already use this, because
SslConnectorhasinto_contextmethod returningSslContextwhich they can use with the driver.The bad news is that reverse is not true - you can't create
SslConnectorfromSslContext(which makes sense if connector is supposed to provide safer defaults).That means we can't utilize its safer methods that guarantee hostname verification.
Conclusion and what is to be done
This PR
I think this PR is a step in the right direction: hostname verification is an important mechanism and we should use it.
The current approach to it assumes that nodes have their IP in SAN field. I'm not sure this assumption is 100% correct in all deployments,
and I'm not sure who to ask (@elcallio )? From a brief look, I think other drivers also use this assumption.
The only other option I see, since there is no hostname-per-node mechanism, is to hardcode one hostname that all nodes must have in their SAN.
Is that approach used in practice?
I think this is already possible with openssl (but I did not write test for it because I don't know if we need this mechanism), but not rustls.
Given that
SslConnectorseems to be a better API, I added a recommendation for users to use it (withinto_contextmethod) instead ofSslContext.Possible further improvements
We could consider introducing variant of
TlsContextwithSslConnector. Unfortunately, we can't make the current Openssl010 variant an enum itself because of backwards compatibility.If it turns out that "hardcoded domain name" approach is used in practice, we need to
TlsContextvariant for rustls that will also store hostname.The above makes me think it may have been a mistake to implement
TlsContexthe way we did.Maybe the body of its variants should be opaque structs for which we could provide builders? That way we could change the internals of each variant.
We could also consider replacing the enum with trait.
Digression: older tokio-openssl versions
Current tokio-openssl version is 0.6.x, and its behavior is described above.
Version 0.5 had this description:
The interesting part is the one about hostname verification being enabled by default. Back then, this crate had
connectfunction acceptingConnectConfigurationand hostname.I'm not sure why they changed that, and I couldn't find this info. The only reason I can think of is that SslContext is more elastic / universal.
I opened an issue with a question about recommended way to enable hostname verification: tokio-rs/tokio-openssl#52
Fixes #1116
Pre-review checklist
I have provided docstrings for the public items that I want to introduce../docs/source/.Fixes:annotations to PR description.