-
Notifications
You must be signed in to change notification settings - Fork 11
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
Add SSL/TLS support #70
Conversation
daad426
to
adf2370
Compare
scylla-rust-wrapper/src/ssl.rs
Outdated
pub unsafe extern "C" fn cass_ssl_new() -> *const CassSsl { | ||
cass_ssl_new_no_lib_init() | ||
} |
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.
The difference between cass_ssl_new
and cass_ssl_new_no_lib_init
is that the first is supposed to perform some additional initialization. What does this initialization do in the original implementation?
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.
Yes, in cass_ssl_new
, openssl::init()
should be explicitly called. SslContextBuilder::new()
does that anyway, so I thought that should be enough.
scylla-rust-wrapper/src/cluster.rs
Outdated
|
||
let mut ssl_context_builder = SslContextBuilder::new(SslMethod::tls_client()).unwrap(); | ||
|
||
if cass_ssl.trusted_cert.is_some() { |
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.
Use if let Some(trusted_cert) = &cass_ssl.trusted_cert
please, instead of is_some()
+ unwrap()
. The less places which can potentially panic in the code, the better.
scylla-rust-wrapper/src/cluster.rs
Outdated
// This should contain the entire certificate chain starting with the certificate itself. | ||
let cert_chain = cass_ssl.cert_chain.as_ref().unwrap(); | ||
let cert = cert_chain.get(0).unwrap(); | ||
ssl_context_builder.set_certificate(cert).unwrap(); |
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.
There are some ssl_context_builder.set_xyz
operations here which can fail, and you unwrap
them. Are they supposed to always succeed? If they do, please add comments which explain that. If not, then I think that the implementation need to be rethought so that those operations are performed in functions that can return errors.
It is worth noting that the original implementation's cass_cluster_set_ssl
is very simple and doesn't call any functions from the openssl
library. If we are using openssl directly like the original impl, then maybe our implementation of the SSL-related functions can mirror the original ones more closely?
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.
The C++ driver implementation creates an OpenSslContext
and sets the necessary configuration explicitly, however, rust-openssl
obliges to use the SslContextBuilder
to do so, and then consume the builder to create a SslContext
. Another limitation is that SslContextBuilder
cannot be cloned, so it makes us create the builder once and immediately consume it, as it is done in the cass_cluster_set_ssl
. The SslContext
setters, e.g. cass_ssl_add_trusted_cert
, cass_ssl_set_verify_flags
, validate the user-provided configuration, and cass_cluster_set_ssl
only sets them in the SslContextBuilder
, so I think those unwrap
calls should always succeed.
69f663f
to
e209aa0
Compare
V2: The SSL support is reimplemented, this time based on the |
@@ -39,6 +39,7 @@ jobs: | |||
:BasicsTests.*\ | |||
:PreparedTests.*\ | |||
:CassandraTypes/CassandraTypesTests/*.Integration_Cassandra_*\ | |||
:SslNoClusterTests*:SslNoSslOnClusterTests*\ |
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.
@Gor027 Also run tests with Cassandra (not only Scylla 5.0.0) so that we can test (almost?) all SSL tests.
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.
After merging this PR, I will add running SSL-related tests on Cassandra.
e83d197
to
2de589a
Compare
2de589a
to
ba3445d
Compare
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.
There seems to be some error checking/reporting logic missing from the ported function (ssl_log_errors
, ERR_peek_error
).
let trusted_store: *mut X509_STORE = X509_STORE_new(); | ||
|
||
SSL_CTX_set_cert_store(ssl_context, trusted_store); | ||
|
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.
There are some calls missing from the original implementation here:
SSL_CTX_set_verify(ssl_ctx_, SSL_VERIFY_NONE, ssl_no_verify_callback);
// Limit to TLS 1.2 for now. TLS 1.3 has broken the handshake code.
SSL_CTX_set_max_proto_version(ssl_ctx_, TLS1_2_VERSION);
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.
I guess SSL_VERIFY_NONE
is the default mode, but I agree, that it is better to set it manually. I am not sure though, whether should we limit the maximum protocol version, as it works fine currently, perhaps with the latest protocol version available in openssl-sys
.
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.
OK, I agree about the TLS version. The original comment is probably quite old.
scylla-rust-wrapper/src/ssl.rs
Outdated
pub unsafe extern "C" fn cass_ssl_set_verify_flags(ssl: *mut CassSsl, flags: i32) { | ||
let ssl = clone_arced(ssl); | ||
|
||
SSL_CTX_set_verify(ssl.ssl_context, flags, None); |
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.
I don't think this is correct. The flags should be of type CassSslVerifyFlags
, which is a custom enum defined by the driver, which I'm not sure if are a valid argument to SSL_CTX_set_verify
. The original implementation seems to do something different - it stores the flags in a field which is later used in a custom verification logic.
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.
The CassSslVerifyFlags
partially matches with the expected argument for the SSL_CTX_set_verify
. The C++ driver overrides the OpenSSL handshake implementation to verify the client's hostname and IP address, however, OpenSSL 1.0.2 and newer are able to verify hostname and IP addresses themselves. So, the flags CASS_SSL_VERIFY_PEER_IDENTITY
and CASS_SSL_VERIFY_PEER_IDENTITY_DNS
can be either removed or considered to be CASS_SSL_VERIFY_PEER_CERT
. Although, I have tested setting those 2 additional flags while establishing a connection with a Scylla and Cassandra cluster, and it seemed to work.
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.
Here is how CassSslVerifyFlags is defined:
typedef enum CassSslVerifyFlags_ {
CASS_SSL_VERIFY_NONE = 0x00,
CASS_SSL_VERIFY_PEER_CERT = 0x01,
CASS_SSL_VERIFY_PEER_IDENTITY = 0x02,
CASS_SSL_VERIFY_PEER_IDENTITY_DNS = 0x04
} CassSslVerifyFlags;
And here is how the flags for the mode
argument of SSL_CTX_set_verify
are defined:
# define SSL_VERIFY_NONE 0x00
# define SSL_VERIFY_PEER 0x01
# define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02
# define SSL_VERIFY_CLIENT_ONCE 0x04
# define SSL_VERIFY_POST_HANDSHAKE 0x08
According to the man page here: https://linux.die.net/man/3/ssl_ctx_set_verify, SSL_VERIFY_CLIENT_ONCE
and SSL_VERIFY_POST_HANDSHAKE
are ignored in client mode (the driver is a client). CASS_SSL_VERIFY_PEER_IDENTITY
and CASS_SSL_VERIFY_PEER_IDENTITY_DNS
"seem to work" because they map to the values I mentioned in the previous sentence.
Ignoring the flags is not the proper behavior. The driver flags and the SSL flags do not mean the same.
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.
I guess SSL_VERIFY_PEER
also verifies the peer's hostname and IP address. I think we barely can simulate the behavior of the C++ driver in this case, because the handshake(where the certificate verification happens) should occur while trying to establish a session between the client and the database, and this is being done on the Rust driver side.
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.
OK. In that case, let's print a warning to stderr in case one of the non-supported flags is provided and leave a TODO comment in the code. Furthermore, please explicitly map CASS_XYZ
values to SSL_XYZ
values in the code - instead of passing CassSslVerifyFlags
value to the SSL function.
f184658
to
2aa2697
Compare
CASS_SSL_VERIFY_NONE => { | ||
SSL_CTX_set_verify(ssl.ssl_context, SslVerifyMode::NONE.bits(), None) | ||
} | ||
CASS_SSL_VERIFY_PEER_CERT => { |
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.
The CASS_SSL_VERIFY_
constants are flags, they are not mutually exclusive variants of an enum. The user can specify more than one of them by OR-ing them together.
Notice that their values are powers of two:
typedef enum CassSslVerifyFlags_ {
CASS_SSL_VERIFY_NONE = 0x00,
CASS_SSL_VERIFY_PEER_CERT = 0x01,
CASS_SSL_VERIFY_PEER_IDENTITY = 0x02,
CASS_SSL_VERIFY_PEER_IDENTITY_DNS = 0x04
} CassSslVerifyFlags;
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.
Currently, there is no support for flags other than CASS_SSL_VERIFY_NONE
and CASS_SSL_VERIFY_PEER_CERT
, so in my opinion OR-ing with CASS_SSL_VERIFY_PEER_IDENTITY
or CASS_SSL_VERIFY_PEER_IDENTITY_DNS
should not be accepted.
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.
Why not? If the user provides CASS_SSL_VERIFY_PEER_CERT
, then we should enable the functionality provided by this flag. If they additionally provide CASS_SSL_VERIFY_PEER_IDENTITY
, we should print a warning and ignore it, but I don't see why this should disable the first flag.
I agree that it would be the best to return an error when an unsupported flag is provided, but the function doesn't really allow us to do that.
2aa2697
to
9a83f6d
Compare
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.
A few small comments remaining, otherwise LGTM.
scylla-rust-wrapper/src/ssl.rs
Outdated
} | ||
_ => { | ||
// Check at position 1 the bit is set to 1 | ||
if (flags >> 1) & 1 == 1 { |
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.
Why use number literals when you can use constants?
if flags & CASS_SSL_VERIFY_PEER_IDENTITY_DNS != 0 {
// ...
}
CASS_SSL_VERIFY_NONE => { | ||
SSL_CTX_set_verify(ssl.ssl_context, SslVerifyMode::NONE.bits(), None) | ||
} | ||
CASS_SSL_VERIFY_PEER_CERT => { |
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.
Why not? If the user provides CASS_SSL_VERIFY_PEER_CERT
, then we should enable the functionality provided by this flag. If they additionally provide CASS_SSL_VERIFY_PEER_IDENTITY
, we should print a warning and ignore it, but I don't see why this should disable the first flag.
I agree that it would be the best to return an error when an unsupported flag is provided, but the function doesn't really allow us to do that.
edd0a92
to
c230fce
Compare
After logging support is merged into the master, the error checking/reporting functionality can be added to the SSL-related functions. |
Added SSL/TLS support to the driver. The rust driver is now imported with additional "ssl" feature as it is required to enable SSL support in it. Enabled integration tests for ssl, only those that are enabled for C++ driver too.
c230fce
to
7228598
Compare
Pre-review checklist
.github/workflows/build.yaml
ingtest_filter
.This PR is dependent upon #69
This PR adds SSL/TLS support to the driver.
Integration tests that are enabled in the C++ driver and are actually run during
Jenkins
builds, are also enabled for thecpp-rust-driver
. One of the reasons that the SSL tests are disabled forcpp-driver
is that during the setupscylla-ccm
fails while trying to start a Scylla cluster. So, to make sure the SSL properly works, I have also run the integration tests on a Cassandra cluster.