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

Reply via email to