-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
tls: Introduce OpenSSL #2569
base: master
Are you sure you want to change the base?
tls: Introduce OpenSSL #2569
Conversation
@elcallio please review Are there any functional differences? Is hot reload of certificates supported? Should we support gnutls and openssl in parallel? |
The only major difference between the OpenSSL vs GnuTLS implementation is how the implementation is configured. With OpenSSL, there are 5 new methods that can be used to control it (compared to the single
There may be some other subtler differences, such as OpenSSL may be stricter about certificate contents (e.g. see 1744b66 - this required the CA cert to have
Yes |
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.
This is quite a big change. I've mostly added nits and some form comments here.
My biggest gripe with it is that library/session config is somewhat different between OSSL/gnutls. The latter uses a single string for config, OSSL is more complicated.
A caller needs to know which impl is used, and adjust his code accordingly.
It makes for more potential bugs when a dev somewhere changes things in one impl, but misses to do it in the other. I.e. we need to make sure CI tests both properly.
@@ -335,6 +335,7 @@ module : private; | |||
#include <seastar/net/virtio.hh> | |||
|
|||
#include "net/native-stack-impl.hh" | |||
#include "net/tls-impl.hh" |
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 is this included here?
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.
This is necessary for when compiling with modules enabled
src/net/tls-impl.hh
Outdated
public: | ||
static std::unique_ptr<connected_socket_impl> get(connected_socket s) { | ||
return std::move(s._csi); | ||
} |
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.
Seems to me this should be just declaration, with impl in impl.cc
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.
This was just moved from tls.cc
to this file.
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. But that was a cc. This is a header.
src/net/tls-impl.hh
Outdated
if (_session && _session.use_count() == 1) { | ||
_session->close(); | ||
} | ||
} |
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.
Same goes for things like this...
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.
same here
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.
Same as above. Code should not be in headers if it can be avoided (even internal, not very included ones)
include/seastar/net/tls.hh
Outdated
@@ -202,13 +212,53 @@ namespace tls { | |||
|
|||
// TODO add methods for certificate verification | |||
|
|||
#ifndef SEASTAR_USE_OPENSSL |
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.
Bit of a negative check. Should this not be SEASTAR_USE_GNUTLS
?
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.
That's actually not set as a compile definitions, but I think it should be. Needs to be added to target_compile_definitions
in CMakeLists.txt
. I'll make that update.
src/net/ossl.cc
Outdated
|
||
// This call is required to lower SSL's security level to permit TLSv1.0 and TLSv1.1 | ||
// See https://www.openssl.org/docs/man3.0/man3/SSL_CTX_set_security_level.html | ||
SSL_CTX_set_security_level(ssl_ctx.get(), 0); |
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.
Should this be unconditional? In the gnutls impl, allowing or not allowing TLS versions is controlled by the prio string. You check min/max version above, does the level always need lowering?
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'll experiment with this and get back to you
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.
Did a little bit of research on this function and what the level controls changes depending on the version of OpenSSL.
For OpenSSL 3.0, setting security level to 3 disables TLS1.0 and below, and setting it to 4 disables TLS1.1 and below (ref).
Starting with OpenSSL 3.1, level 1 disables TLS1.1 and below (ref).
src/net/tls-impl.cc
Outdated
#endif | ||
|
||
#ifdef SEASTAR_USE_OPENSSL | ||
void tls::credentials_builder::set_cipher_string(const sstring& cipher_string) { |
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 place these in the openssl impl file?
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.
That's a good idea, I'll move the definitions to the correct source file
tests/unit/tls_test.cc
Outdated
@@ -161,7 +163,29 @@ SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust_multiple) { | |||
}); | |||
} | |||
|
|||
static void set_priority_string(tls::credentials_builder & b, const sstring & prio, [[maybe_unused]] bool is_tls_v13 = false) { |
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.
Please break this into two (or three) distinct functions and adjust call sites instead. Calling this set_prio more amplifies the issue with library configuration not being equivalent between openssl and gnutls, and is more confusing (imho) at the call sites than just doing different things to accomplish roughly the same.
/* | ||
* Copyright 2015 Cloudius Systems | ||
*/ | ||
#pragma once |
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 is this file in public header dir? Seems to me it should only be required by internal compilation units, so should probably be under src.
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.
It's not in the public header directory, it's in src/net
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.
Doh, sorry, github confused me.
src/net/ossl.cc
Outdated
// This function waits for the _output_pending future to resolve | ||
// If an error occurs, it is saved off into _error and returned | ||
future<> wait_for_output() { | ||
_logger.trace("wait_for_output"); |
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 invent a new logger type when you could just add a formatter for the session object that pretty-prints the local/remote etc, and then just use tls_log::trace("{} wait_for_output")
?
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.
Thanks for the suggestion, great idea!
// during the handshake before the client has fully | ||
// closed its connection, then the get() call will | ||
// succeed by return an empty buffer indicating EOF | ||
BOOST_REQUIRE(res.size() == 0); |
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.
Seems like a good change, but not related to the rest of the PR
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.
This test was being flaky when using OpenSSL. It would pass reliably in release but not in debug.
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.
nit, might want to use BOOST_REQUIRE_EQUAL()
for better postmortem debugging experience. as Boost.Test prints out the lhs and rhs of the comparison f the check fails.
The different configuration options mean one of two:
So I think we should choose one implementation, and deprecate the other after a transition period. gnutls was chosen due to funky licensing and an unstable API from openssl, but I think that's behind us now. Given that, it's better to use the leader in the field rather than a follower. |
GnuTLS is not FIPS enabled, if compiled with it? (I see https://www.gnutls.org/manual/html_node/FIPS140_002d2-mode.html ) - what's missing? |
The FIPS flag for GnuTLS means that GnuTLS will work in a FIPS compliant way (e.g. rejecting any non FIPS approved crypto like DES or GOST), however that doesn't mean that its implementation was validated. GnuTLS doesn't provide a path to build it and maintain validation, OpenSSL does (see above links to OpenSSL's security policy). |
While I can somewhat sympathize with the sentiment, this would effectively mean dropping the way we handle TLS config for clients/applications (i.e. usage of the prio string). Since at least one database application I know of exposes this in its customer config, you'd effectively be asking to require changing all customer TLS configs where such a prio is applied. Not sure how many they are and how complicated the configs are. You said a transition period, but not sure how to handle this, nor enforce a config migration with clients? |
I would not want to force a config migration. Claude says this:
|
Yes, but I am honestly very nervous about writing/maintaining a prio string translator. The mapping is not just cipher to cipher etc, it is a state machine in itself, disabling and adding ciphers, exchange modes etc. |
I agree with that. @tzach do you have any insight about priority string configuration across our fleet? Do we ever diverge from the default? |
Since we did not disable TLSv1.1 by default (not sure why), there's a good chance users do it - https://enterprise.docs.scylladb.com/stable/operating-scylla/security/client-node-encryption.html#priority-string-and-tlsv1-2-1-3-support |
5c709c2
to
fc616ee
Compare
Force push
|
fc616ee
to
1a53361
Compare
Force push
|
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.
Code looks ok, but again, I doubt I can really verify it just by looking. :-(
One huge downside with the openssl code is that imho it seems to make the interface code even cludgier - perhaps partially an aspect of the code it needs to emulate is from a gnutls universe, but I would still argue that a lot of the gnutls interfaces are a bit nicer.
Thus I would worry a little about maintenance here.
src/net/tls-impl.hh
Outdated
public: | ||
static std::unique_ptr<connected_socket_impl> get(connected_socket s) { | ||
return std::move(s._csi); | ||
} |
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. But that was a cc. This is a header.
src/net/tls-impl.hh
Outdated
if (_session && _session.use_count() == 1) { | ||
_session->close(); | ||
} | ||
} |
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.
Same as above. Code should not be in headers if it can be avoided (even internal, not very included ones)
void set_session_resume_mode(session_resume_mode); | ||
void set_priority_string(const sstring&); |
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.
Seems like a gratuitous change?
include/seastar/net/tls.hh
Outdated
@@ -235,6 +247,12 @@ namespace tls { | |||
template<typename Base> | |||
friend class reloadable_credentials; | |||
shared_ptr<impl> _impl; | |||
|
|||
// The following methods are provided so classes that inherity from |
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.
Nit: inherit
src/net/ossl.cc
Outdated
auto dn = extract_dn_information(); | ||
if (dn) { | ||
std::string_view stat_str_view{stat_str}; | ||
if (stat_str_view.ends_with(" ")) { |
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.
Nit: I assume this is an openssl quirk of some sort? Maybe a while instead (instead of assuming just one space), or maybe even a proper back and front whitespace strip?
src/net/ossl.cc
Outdated
} | ||
|
||
auto & min_tls_version = _creds->minimum_tls_version(); | ||
auto & max_tls_version = _creds->maximum_tls_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.
Nit: space between type and ref.
src/net/ossl.cc
Outdated
} | ||
} | ||
|
||
auto get_min_level = [&ssl_ctx]() { |
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.
Nit: Should be "max level" really, as we should consider us setting the max level we can use given the tls version bounds.
src/net/ossl.cc
Outdated
BIO_METHOD* get_method() { | ||
static thread_local bio_method_ptr method_ptr = [] { | ||
auto ptr = tls::create_bio_method(); | ||
if (!ptr) { |
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.
Seems redundant - all paths in create throws if failed...
src/websocket/server.cc
Outdated
@@ -126,6 +130,30 @@ future<> connection::process() { | |||
|
|||
static std::string sha1_base64(std::string_view source) { | |||
unsigned char hash[20]; | |||
|
|||
#ifdef SEASTAR_USE_OPENSSL |
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.
Could we instead make the function an external/internal-header-decl, and move the full impl into gnutls/openssl compilation units? This just adds to an already bad separation of concern/api exposure.
Created tls-impl.cc and tls-impl.h which contains common structures and definitions that are not dependent on the underlying TLS mechanism. These changes set the stage for implementing other TLS providers. Signed-off-by: Michael Boquard <[email protected]>
This commit adds support for using OpenSSL, instead of GnuTLS, as the TLS provider within Seastar. To support this change, the configure script has been updated to allow users to select which cryptographic provider should be used by supply `--crypto-provider` and specificying either `OpenSSL` or `GnuTLS`. The OpenSSL implementation mirrors the GnuTLS implementation. Instead of using callbacks, a custom BIO was created to handle moving data on/off of the OpenSSL SSL session into the Seastar TLS session data sinks. When compiled for OpenSSL, the `certificate_credentials::set_priority_string` method is compiled out and replaced with the following: * `set_cipher_string` * `set_ciphersuites` * `enable_server_precedence` * `set_minimum_tls_version` * `set_maximum_tls_version` These methods are specific to OpenSSL. The github actions have been updated to run the full suite of tests against both cryptographic providers. `src/net/tcp.hh` and `src/websocket/server.cc` have been updated to use OpenSSL instead of GnuTLS, depending upon the build configuration. Signed-off-by: Michael Boquard <[email protected]>
Added pretty-print capabilities to seastar::tls::session for OpenSSL and added a number of log statements that may be helpful if debugging the implementation. Signed-off-by: Michael Boquard <[email protected]>
More recent versions of OpenSSL requrire CA certificates to have CA:true Signed-off-by: Michael Boquard <[email protected]>
Now handling situations where the get() call doesn't throw but does return an empty buffer indicating EOF. Signed-off-by: Michael Boquard <[email protected]>
1a53361
to
2f935ca
Compare
Force push
|
set(Seastar_USE_GNUTLS OFF) | ||
else() | ||
set(Seastar_USE_GNUTLS ON) |
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.
could you please add space after set
and else
? just to be consistent with the rest of this CMake script.
* under the License. | ||
*/ | ||
/* | ||
* Copyright 2015 Cloudius Systems |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
@@ -79,6 +79,8 @@ module seastar; | |||
#include "net/tls-impl.hh" | |||
#endif | |||
|
|||
template <> struct fmt::formatter<seastar::tls::session> : fmt::ostream_formatter {}; |
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.
is it possible to implement the specialization of fmt::fornmatter<seastar::tls::session>
without the operator<<
? it would be better if we can avoid adding more operator<<
, for two reasons:
- avoid adding the unused
operator<<
- avoid the overhead of using a temporary ostream for formatting
seastar::tls::session
.
} | ||
|
||
template<> | ||
struct fmt::formatter<seastar::ossl_errc> : public fmt::formatter<std::string_view> { |
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.
instead of inheriting from fmt::formatter<std::string_view>
, i'd suggest inheriting from fmt::formatter<string_view>
. please see scylladb/scylladb@168ade7 for more details. we switched from this approach to the proposed one in the referenced commit in scylladb a while ago.
@@ -770,6 +772,8 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||
session(session_type t, shared_ptr<tls::certificate_credentials> creds, | |||
std::unique_ptr<net::connected_socket_impl> sock, tls_options options = {}) | |||
: _sock(std::move(sock)) | |||
, _local_address(fmt::format("{}", _sock->local_address())) |
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.
nit, could use fmt::to_string(_sock->local_address())
instead. simpler this way. and slightly more performant.
@@ -770,6 +772,8 @@ class session : public enable_shared_from_this<session>, public session_impl { | |||
session(session_type t, shared_ptr<tls::certificate_credentials> creds, | |||
std::unique_ptr<net::connected_socket_impl> sock, tls_options options = {}) | |||
: _sock(std::move(sock)) | |||
, _local_address(fmt::format("{}", _sock->local_address())) | |||
, _remote_address(fmt::format("{}", _sock->remote_address())) |
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.
ditto.
session.get_type_string(), | ||
session.local_address(), | ||
session.remote_address()); | ||
return os; |
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.
as explained above, i'd prefer avoid adding more operator<<
overloads unless they are actually necessary and used.
return make_ready_future(); | ||
} | ||
return _in.get() | ||
.then([this](buf_type buf) { | ||
// Set EOF if it's empty | ||
tls_log.debug("{} wait_for_input: buffer {}empty", *this, buf.empty() ? "is ": ""); |
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 really understand this logging message:
- if
buf
is empty, we print "wait_for_input: buffer is empty" -- looks good - if
buf
is not empty, we print "wait_for_input: buffer empty" -- what does this mean? shall we print "wait_for_input: buffer is not empty" or "wait_for_input: buffer not empty" instead?
mode: debug | ||
enables: --enable-cxx-modules | ||
enable-ccache: false | ||
crypto_provider: OpenSSL |
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.
piggyback the tests with OpenSSL / GnuTLS might not be a great idea. because, we only enable the test step in
seastar/.github/workflows/test.yaml
Lines 110 to 112 in 613d8b3
- name: Test | |
if: ${{ ! contains(inputs.enables, 'cxx-modules') }} | |
run: ./test.py --mode=${{ inputs.mode }} |
tests.yaml
for building with the OpenSSL backend, and keep the existing job of build_with_cxx_modules
intact.
please allow me to provide more context here: because C++20 modules is currently an experimental feature, not all Seastar facilities are exposed as in the "seastar" C++20 module at this moment, and we only have a single "hello_cxx_module" test for testing the build with C++20 module. none of the unit tests is built with the "seastar" C++20 module at the time of writing.
auto err_code = static_cast<unsigned long>(error_codes.front()); | ||
if (ERR_LIB_SYS == ERR_GET_LIB(err_code)) { | ||
// If the error code belongs to ERR_LIB_SYS, then the error is a system error | ||
// Extract the errno using ERR_GET_REASON and throw a std::generic_category | ||
return std::system_error( | ||
ERR_GET_REASON(err_code), | ||
std::generic_category(), | ||
fmt::format("{}: {}", msg, error_codes)); | ||
} | ||
return std::system_error( | ||
static_cast<int>(err_code), | ||
tls::error_category(), | ||
fmt::format("{}: {}", msg, error_codes)); |
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.
nit, could reduce the indent level by removing the "else" clause, because of the early return above.
|
||
impl() : _creds([] { | ||
auto store = X509_STORE_new(); | ||
if(store == nullptr) { |
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.
could you please add a space after if
?
Introduces OpenSSL as an alternative TLS implementation to GnuTLS. This is a build-time configuration controlled by the CMake variable
Seastar_USE_OPENSSL
. Theconfigure.py
script has been updated to now have a--crypto-provider
option. Valid arguments to that areOpenSSL
andGnuTLS
.This implementation was released in Redpanda v24.2 on July 31st, and has been running on production clusters since.
Redpanda implemented these changes in order to provide a FIPS-compliant build to customers that require it (such as those wishing to undergo FedRAMP evaluation). OpenSSL was selected as it allows implementors to maintain the validation of the cryptographic module even when it's built from source.
modules
No changes have been introduced to enable the FIPS provider for Seastar. It is up to the implementor to enable and use the FIPS cryptographic module if desired.
Fixes: #698