This is an automated email from the ASF dual-hosted git repository.
achennaka pushed a commit to branch branch-1.18.x
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/branch-1.18.x by this push:
new 39b870735 [webserver] Add support for TLS 1.3
39b870735 is described below
commit 39b870735b9b51a3db461f91a8ed9e7a9a1d86f2
Author: Attila Bukor <[email protected]>
AuthorDate: Wed Jul 9 19:26:03 2025 +0200
[webserver] Add support for TLS 1.3
This commit patches Squeasel to honor the minimum TLS version even if
TLSv1.3 is set and adds a new ciphersuites option in addition to
ciphers, which is used for TLS 1.3.
I added a new test that requires TLS 1.3, but the new ciphersuites
option was tested only manually with openssl s_client, as curl doesn't
seem to support requiring specific ciphers.
Change-Id: Iba21d62e22962c782ff8013d805b31ff058d9245
Reviewed-on: http://gerrit.cloudera.org:8080/23149
Reviewed-by: Alexey Serbin <[email protected]>
Tested-by: Attila Bukor <[email protected]>
(cherry picked from commit f6fbcde39f48fc8ae119a6ecce03a8a4254d8a02)
Reviewed-on: http://gerrit.cloudera.org:8080/23253
Reviewed-by: Abhishek Chennaka <[email protected]>
Tested-by: Abhishek Chennaka <[email protected]>
---
src/kudu/server/webserver-test.cc | 23 ++++++
src/kudu/server/webserver.cc | 2 +
src/kudu/server/webserver_options.cc | 27 ++++++-
src/kudu/server/webserver_options.h | 1 +
src/kudu/util/curl_util.cc | 41 +++++++++++
src/kudu/util/curl_util.h | 20 +++++
thirdparty/download-thirdparty.sh | 6 +-
thirdparty/patches/squeasel-tls-min-version.patch | 90 +++++++++++++++++++++++
8 files changed, 205 insertions(+), 5 deletions(-)
diff --git a/src/kudu/server/webserver-test.cc
b/src/kudu/server/webserver-test.cc
index fca030fad..241f6ebf6 100644
--- a/src/kudu/server/webserver-test.cc
+++ b/src/kudu/server/webserver-test.cc
@@ -119,6 +119,9 @@ class WebserverTest : public KuduTest {
if (use_ssl()) {
SetSslOptions(&opts);
cert_path_ = opts.certificate_file;
+ if (use_tls1_3()) {
+ opts.tls_min_protocol = "TLSv1.3";
+ }
}
if (use_htpasswd()) SetHTPasswdOptions(&opts);
MaybeSetupSpnego(&opts);
@@ -156,6 +159,7 @@ class WebserverTest : public KuduTest {
virtual bool enable_doc_root() const { return true; }
virtual bool use_ssl() const { return false; }
virtual bool use_htpasswd() const { return false; }
+ virtual bool use_tls1_3() const { return false; }
EasyCurl curl_;
faststring buf_;
@@ -171,6 +175,11 @@ class SslWebserverTest : public WebserverTest {
bool use_ssl() const override { return true; }
};
+class Tls13WebserverTest : public SslWebserverTest {
+ protected:
+ bool use_tls1_3() const override { return true; }
+};
+
class PasswdWebserverTest : public WebserverTest {
protected:
bool use_htpasswd() const override { return true; }
@@ -507,6 +516,20 @@ TEST_F(SslWebserverTest, TestSSL) {
ASSERT_STR_CONTAINS(buf_.ToString(), "Kudu");
}
+TEST_F(Tls13WebserverTest, TestTlsMinVersion) {
+ FLAGS_trusted_certificate_file = cert_path_;
+ curl_.set_tls_max_version(TlsVersion::TLSv1_2);
+
+ Status s = curl_.FetchURL(url_, &buf_);
+ ASSERT_TRUE(s.IsNetworkError()) << s.ToString();
+ ASSERT_STR_CONTAINS(s.ToString(), "TLS connect error");
+
+ curl_.set_tls_min_version(TlsVersion::TLSv1_3);
+ curl_.set_tls_max_version(TlsVersion::ANY);
+
+ ASSERT_OK(curl_.FetchURL(url_, &buf_));
+}
+
TEST_F(SslWebserverTest, StrictTransportSecurtyPolicyHeaders) {
constexpr const char* const kHstsHeader = "Strict-Transport-Security";
// Since the server uses a self-signed TLS certificate, disable the cert
diff --git a/src/kudu/server/webserver.cc b/src/kudu/server/webserver.cc
index 61012abd1..a66bd5b9d 100644
--- a/src/kudu/server/webserver.cc
+++ b/src/kudu/server/webserver.cc
@@ -328,6 +328,8 @@ Status Webserver::Start() {
options.emplace_back(opts_.tls_ciphers);
options.emplace_back("ssl_min_version");
options.emplace_back(opts_.tls_min_protocol);
+ options.emplace_back("ssl_ciphersuites");
+ options.emplace_back(opts_.tls_ciphersuites);
}
if (!opts_.authentication_domain.empty()) {
diff --git a/src/kudu/server/webserver_options.cc
b/src/kudu/server/webserver_options.cc
index 880c45709..92cdcc035 100644
--- a/src/kudu/server/webserver_options.cc
+++ b/src/kudu/server/webserver_options.cc
@@ -29,6 +29,7 @@
#include "kudu/security/security_flags.h"
#include "kudu/util/flag_tags.h"
#include "kudu/util/flag_validators.h"
+#include "kudu/util/string_case.h"
using std::string;
@@ -115,9 +116,16 @@ DEFINE_string(webserver_tls_ciphers,
"for more information.");
TAG_FLAG(webserver_tls_ciphers, advanced);
+DEFINE_string(webserver_tls_ciphersuites,
+ kudu::security::SecurityDefaults::kDefaultTlsCipherSuites,
+ "The cipher suite preferences to use for TLSv1.3 webserver HTTPS
connections. "
+ "Uses the OpenSSL cipher preference list format. See man (1)
ciphers "
+ "for more information.");
+TAG_FLAG(webserver_tls_ciphersuites, advanced);
+
DEFINE_string(webserver_tls_min_protocol,
kudu::security::SecurityDefaults::kDefaultTlsMinVersion,
"The minimum protocol version to allow when for webserver HTTPS "
- "connections. May be one of 'TLSv1', 'TLSv1.1', or 'TLSv1.2'.");
+ "connections. May be one of 'TLSv1', 'TLSv1.1', 'TLSv1.2', or
'TLSv1.3'.");
TAG_FLAG(webserver_tls_min_protocol, advanced);
DEFINE_bool(webserver_require_spnego, false,
@@ -125,9 +133,9 @@ DEFINE_bool(webserver_require_spnego, false,
"using SPNEGO.");
TAG_FLAG(webserver_require_spnego, stable);
-namespace kudu {
+namespace {
-static bool ValidateTlsFlags() {
+bool ValidateTlsFlags() {
bool has_cert = !FLAGS_webserver_certificate_file.empty();
bool has_key = !FLAGS_webserver_private_key_file.empty();
bool has_passwd = !FLAGS_webserver_private_key_password_cmd.empty();
@@ -147,6 +155,18 @@ static bool ValidateTlsFlags() {
}
GROUP_FLAG_VALIDATOR(webserver_tls_options, ValidateTlsFlags);
+bool ValidateTlsMinVersion(const char* /* flagname */, const string& ver) {
+ return kudu::iequals(ver, "TLSv1") ||
+ kudu::iequals(ver, "TLSv1.1") ||
+ kudu::iequals(ver, "TLSv1.2") ||
+ kudu::iequals(ver, "TLSv1.3");
+}
+DEFINE_validator(webserver_tls_min_protocol, &ValidateTlsMinVersion);
+
+}; // anonymous namespace
+
+namespace kudu {
+
// Returns KUDU_HOME if set, otherwise we won't serve any static files.
static string GetDefaultDocumentRoot() {
char* kudu_home = getenv("KUDU_HOME");
@@ -166,6 +186,7 @@ WebserverOptions::WebserverOptions()
authentication_domain(FLAGS_webserver_authentication_domain),
password_file(FLAGS_webserver_password_file),
tls_ciphers(FLAGS_webserver_tls_ciphers),
+ tls_ciphersuites(FLAGS_webserver_tls_ciphersuites),
tls_min_protocol(FLAGS_webserver_tls_min_protocol),
num_worker_threads(FLAGS_webserver_num_worker_threads),
require_spnego(FLAGS_webserver_require_spnego) {
diff --git a/src/kudu/server/webserver_options.h
b/src/kudu/server/webserver_options.h
index 0058e82c9..2df2243b5 100644
--- a/src/kudu/server/webserver_options.h
+++ b/src/kudu/server/webserver_options.h
@@ -40,6 +40,7 @@ struct WebserverOptions {
std::string authentication_domain;
std::string password_file;
std::string tls_ciphers;
+ std::string tls_ciphersuites;
std::string tls_min_protocol;
uint32_t num_worker_threads;
diff --git a/src/kudu/util/curl_util.cc b/src/kudu/util/curl_util.cc
index 08c32b759..16b35bdf3 100644
--- a/src/kudu/util/curl_util.cc
+++ b/src/kudu/util/curl_util.cc
@@ -233,6 +233,47 @@ Status EasyCurl::DoRequest(const string& url,
CURL_RETURN_NOT_OK(curl_easy_setopt(curl_, CURLOPT_FAILONERROR, 1));
}
+ uint16_t tls_min_version;
+ switch (tls_min_version_) {
+ case TlsVersion::TLSv1:
+ tls_min_version = CURL_SSLVERSION_TLSv1_0;
+ break;
+ case TlsVersion::TLSv1_1:
+ tls_min_version = CURL_SSLVERSION_TLSv1_1;
+ break;
+ case TlsVersion::TLSv1_2:
+ tls_min_version = CURL_SSLVERSION_TLSv1_2;
+ break;
+ case TlsVersion::TLSv1_3:
+ tls_min_version = CURL_SSLVERSION_TLSv1_3;
+ break;
+ case TlsVersion::ANY:
+ tls_min_version = CURL_SSLVERSION_DEFAULT;
+ break;
+ }
+ CURL_RETURN_NOT_OK(curl_easy_setopt(curl_, CURLOPT_SSLVERSION,
tls_min_version));
+
+ uint64_t tls_max_version;
+ switch (tls_max_version_) {
+ case TlsVersion::TLSv1:
+ tls_max_version = CURL_SSLVERSION_MAX_TLSv1_0;
+ break;
+ case TlsVersion::TLSv1_1:
+ tls_max_version = CURL_SSLVERSION_MAX_TLSv1_1;
+ break;
+ case TlsVersion::TLSv1_2:
+ tls_max_version = CURL_SSLVERSION_MAX_TLSv1_2;
+ break;
+ case TlsVersion::TLSv1_3:
+ tls_max_version = CURL_SSLVERSION_MAX_TLSv1_3;
+ break;
+ case TlsVersion::ANY:
+ tls_max_version = CURL_SSLVERSION_MAX_DEFAULT;
+ break;
+ }
+ CURL_RETURN_NOT_OK(curl_easy_setopt(curl_, CURLOPT_SSLVERSION,
tls_max_version));
+
+
// Add headers if specified.
struct curl_slist* curl_headers = nullptr;
auto clean_up_curl_slist = MakeScopedCleanup([&]() {
diff --git a/src/kudu/util/curl_util.h b/src/kudu/util/curl_util.h
index 436eb9794..0596bf29b 100644
--- a/src/kudu/util/curl_util.h
+++ b/src/kudu/util/curl_util.h
@@ -39,6 +39,14 @@ enum class CurlAuthType {
SPNEGO,
};
+enum class TlsVersion {
+ ANY,
+ TLSv1,
+ TLSv1_1,
+ TLSv1_2,
+ TLSv1_3,
+};
+
// Simple wrapper around curl's "easy" interface, allowing the user to
// fetch web pages into memory using a blocking API.
//
@@ -109,6 +117,14 @@ class EasyCurl {
password_ = std::move(password);
}
+ void set_tls_min_version(TlsVersion tls_min_version) {
+ tls_min_version_ = tls_min_version;
+ }
+
+ void set_tls_max_version(TlsVersion tls_max_version) {
+ tls_max_version_ = tls_max_version;
+ }
+
// Enable verbose mode for curl. This dumps debugging output to stderr, so
// is only really useful in the context of tests.
void set_verbose(bool v) {
@@ -196,6 +212,10 @@ class EasyCurl {
CurlAuthType auth_type_ = CurlAuthType::NONE;
+ TlsVersion tls_min_version_ = TlsVersion::ANY;
+
+ TlsVersion tls_max_version_ = TlsVersion::ANY;
+
DISALLOW_COPY_AND_ASSIGN(EasyCurl);
};
diff --git a/thirdparty/download-thirdparty.sh
b/thirdparty/download-thirdparty.sh
index 50bef973d..70365c1f7 100755
--- a/thirdparty/download-thirdparty.sh
+++ b/thirdparty/download-thirdparty.sh
@@ -278,12 +278,13 @@ fetch_and_patch \
"patch -p1 < $TP_DIR/patches/rapidjson-document-assignment-operator-00.patch"
\
"patch -p1 < $TP_DIR/patches/rapidjson-document-assignment-operator-01.patch"
-SQUEASEL_PATCHLEVEL=1
+SQUEASEL_PATCHLEVEL=2
fetch_and_patch \
squeasel-${SQUEASEL_VERSION}.tar.gz \
$SQUEASEL_SOURCE \
$SQUEASEL_PATCHLEVEL \
- "patch -p1 < $TP_DIR/patches/squeasel-handle-openssl-errors.patch"
+ "patch -p1 < $TP_DIR/patches/squeasel-handle-openssl-errors.patch" \
+ "patch -p1 < $TP_DIR/patches/squeasel-tls-min-version.patch"
MUSTACHE_PATCHLEVEL=0
fetch_and_patch \
@@ -519,3 +520,4 @@ fetch_and_patch \
echo "---------------"
echo "Thirdparty dependencies downloaded successfully"
+
diff --git a/thirdparty/patches/squeasel-tls-min-version.patch
b/thirdparty/patches/squeasel-tls-min-version.patch
new file mode 100644
index 000000000..af09fcc2d
--- /dev/null
+++ b/thirdparty/patches/squeasel-tls-min-version.patch
@@ -0,0 +1,90 @@
+commit 5fa72c2724941b76cdb0fef04b2d36b2e1ab6bb3
+Author: Attila Bukor <[email protected]>
+Date: Wed Jul 9 19:28:48 2025 +0200
+
+ Add support for requiring TLS 1.3
+
+diff --git a/squeasel.c b/squeasel.c
+index ff40dcc..eeab9e7 100644
+--- a/squeasel.c
++++ b/squeasel.c
+@@ -170,6 +170,7 @@ static const char *http_500_error = "Internal Server
Error";
+ #endif
+
+ #define OPENSSL_MIN_VERSION_WITH_TLS_1_1 0x10001000L
++#define OPENSSL_MIN_VERSION_WITH_TLS_1_3 0x10101000L
+
+ static const char *month_names[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+@@ -230,7 +231,7 @@ enum {
+ GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
+ EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
SSL_PRIVATE_KEY,
+ SSL_PRIVATE_KEY_PASSWORD, SSL_GLOBAL_INIT, NUM_THREADS, RUN_AS_USER,
REWRITE,
+- HIDE_FILES, REQUEST_TIMEOUT, SSL_VERSION, SSL_CIPHERS, NUM_OPTIONS
++ HIDE_FILES, REQUEST_TIMEOUT, SSL_VERSION, SSL_CIPHERS, SSL_CIPHERSUITES,
NUM_OPTIONS
+ };
+
+ static const char *config_options[] = {
+@@ -264,6 +265,7 @@ static const char *config_options[] = {
+ "request_timeout_ms", "30000",
+ "ssl_min_version", "tlsv1",
+ "ssl_ciphers", NULL,
++ "ssl_ciphersuites", NULL,
+ NULL
+ };
+
+@@ -3908,6 +3910,12 @@ static int set_ssl_option(struct sq_context *ctx) {
+ return 0;
+ }
+ options |= (SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
++ } else if (sq_strcasecmp(ssl_version, "tlsv1.3") == 0) {
++ if (SSLeay() < OPENSSL_MIN_VERSION_WITH_TLS_1_3) {
++ cry(fc(ctx), "Unsupported TLS version: %s", ssl_version);
++ return 0;
++ }
++ options |= (SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2);
+ } else {
+ cry(fc(ctx), "%s: unknown SSL version: %s", __func__, ssl_version);
+ return 0;
+@@ -3957,11 +3965,36 @@ static int set_ssl_option(struct sq_context *ctx) {
+ }
+ }
+
+- if (ctx->config[SSL_CIPHERS] != NULL) {
+- if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, ctx->config[SSL_CIPHERS]) != 1)
{
+- cry(fc(ctx), "SSL_CTX_set_cipher_list: error setting ciphers (%s): %s",
+- ctx->config[SSL_CIPHERS], ssl_error());
+- return 0;
++ if (ctx->config[SSL_CIPHERS] != NULL || ctx->config[SSL_CIPHERSUITES] !=
NULL) {
++ // The sequence of SSL_CTX_set_ciphersuites() and
SSL_CTX_set_cipher_list()
++ // calls below is essential to make sure the TLS engine ends up with
usable,
++ // non-empty set of ciphers in case of early 1.1.1 releases of OpenSSL
(like
++ // OpenSSL 1.1.1 shipped with Ubuntu 18).
++ //
++ // The SSL_CTX_set_ciphersuites() call cares only about TLSv1.3 ciphers,
and
++ // those might be none. From the other side, the implementation of
++ // SSL_CTX_set_cipher_list() verifies that the overall result list of
++ // ciphers is valid and usable, reporting an error otherwise.
++ //
++ // If the sequence is reversed, no error would be reported from
++ // TlsContext::Init() in case of empty list of ciphers for some
early-1.1.1
++ // releases of OpenSSL. That's because SSL_CTX_set_cipher_list() would
see a
++ // non-empty list of default TLSv1.3 ciphers when given an empty list of
++ // TLSv1.2 ciphers, and SSL_CTX_set_ciphersuites() would allow an empty
set
++ // of TLSv1.3 ciphers in a subsequent call.
++ if (ctx->config[SSL_CIPHERSUITES] != NULL) {
++ if (SSL_CTX_set_ciphersuites(ctx->ssl_ctx,
ctx->config[SSL_CIPHERSUITES]) != 1) {
++ cry(fc(ctx), "SSL_CTX_set_ciphersuites: error setting ciphers (%s):
%s",
++ ctx->config[SSL_CIPHERSUITES], ssl_error());
++ return 0;
++ }
++ }
++ if (ctx->config[SSL_CIPHERS] != NULL) {
++ if (SSL_CTX_set_cipher_list(ctx->ssl_ctx, ctx->config[SSL_CIPHERS]) !=
1) {
++ cry(fc(ctx), "SSL_CTX_set_cipher_list: error setting ciphers (%s):
%s",
++ ctx->config[SSL_CIPHERS], ssl_error());
++ return 0;
++ }
+ }
+ #ifndef OPENSSL_NO_ECDH
+ #if OPENSSL_VERSION_NUMBER < 0x10002000L