This is an automated email from the ASF dual-hosted git repository.

alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git

commit d49b34aff722fac813ca147eb9cb9a0d263a2f70
Author: Zoltan Chovan <zcho...@cloudera.com>
AuthorDate: Mon May 22 16:37:39 2023 +0000

    [jwt] switching JWT verification to KeyBasedJwtVerifier
    
    Switching JWT verification to use KeyBasedJwtVerifier instead of
    PerAccountKeyBasedJwtVerifier, as the latter relies on having an OIDC
    discovery endpoint available even if a jwks_url is set. Fixing the jwks
    discovery via an OIDC server will be done in a later patch.
    
    This change also moves the initialisation of the JWTVerifier from
    startup to the VerifyToken() method. In order to test this a new option
    'start_jwks' for the ExternalMiniCluster was introduced.
    
    Change-Id: Ic1f166807bfcf7051bda7843e186eacfbe379eba
    Reviewed-on: http://gerrit.cloudera.org:8080/19910
    Tested-by: Kudu Jenkins
    Reviewed-by: Alexey Serbin <ale...@apache.org>
---
 src/kudu/integration-tests/security-itest.cc   | 68 +++++++++++++++++++++++---
 src/kudu/mini-cluster/external_mini_cluster.cc | 16 +++---
 src/kudu/mini-cluster/external_mini_cluster.h  |  6 +++
 src/kudu/rpc/messenger.cc                      |  2 -
 src/kudu/rpc/negotiation-test.cc               | 10 ++--
 src/kudu/server/server_base.cc                 |  9 ++--
 src/kudu/util/jwt-util-test.cc                 |  6 ++-
 src/kudu/util/jwt-util.cc                      | 37 ++++++++------
 src/kudu/util/jwt-util.h                       | 36 +++++++++++---
 src/kudu/util/jwt.h                            |  2 +-
 src/kudu/util/mini_oidc.cc                     | 39 ++++++++++++---
 src/kudu/util/mini_oidc.h                      | 11 +++++
 12 files changed, 189 insertions(+), 53 deletions(-)

diff --git a/src/kudu/integration-tests/security-itest.cc 
b/src/kudu/integration-tests/security-itest.cc
index 7604696dc..a560b0707 100644
--- a/src/kudu/integration-tests/security-itest.cc
+++ b/src/kudu/integration-tests/security-itest.cc
@@ -580,11 +580,11 @@ TEST_F(SecurityITest, TestJwtMiniCluster) {
 
   const auto* const kSubject = "kudu-user";
   const auto configure_builder_for =
-      [&] (const string& account_id, KuduClientBuilder* b, const uint64_t 
delay_ms) {
+      [&] (const string& account_id, KuduClientBuilder* b, const uint64_t 
delay_ms, bool is_valid) {
     for (auto i = 0; i < cluster_->num_masters(); ++i) {
       
b->add_master_server_addr(cluster_->master(i)->bound_rpc_addr().ToString());
     }
-    b->jwt(cluster_->oidc()->CreateJwt(account_id, kSubject, true));
+    b->jwt(cluster_->oidc()->CreateJwt(account_id, kSubject, is_valid));
     b->require_authentication(true);
     b->trusted_certificate(cluster_cert_pem);
     SleepFor(MonoDelta::FromMilliseconds(delay_ms));
@@ -594,7 +594,7 @@ TEST_F(SecurityITest, TestJwtMiniCluster) {
     SCOPED_TRACE("Valid JWT");
     KuduClientBuilder valid_builder;
     shared_ptr<KuduClient> client;
-    configure_builder_for(kValidAccount, &valid_builder, 0);
+    configure_builder_for(kValidAccount, &valid_builder, 0, true);
     ASSERT_OK(valid_builder.Build(&client));
     vector<string> tables;
     ASSERT_OK(client->ListTables(&tables));
@@ -603,7 +603,7 @@ TEST_F(SecurityITest, TestJwtMiniCluster) {
     SCOPED_TRACE("Invalid JWT");
     KuduClientBuilder invalid_builder;
     shared_ptr<KuduClient> client;
-    configure_builder_for(kInvalidAccount, &invalid_builder, 0);
+    configure_builder_for(kInvalidAccount, &invalid_builder, 0, false);
     Status s = invalid_builder.Build(&client);
     ASSERT_TRUE(s.IsRuntimeError()) << s.ToString();
     ASSERT_STR_CONTAINS(s.ToString(), "FATAL_INVALID_JWT");
@@ -612,7 +612,7 @@ TEST_F(SecurityITest, TestJwtMiniCluster) {
     SCOPED_TRACE("Expired JWT");
     KuduClientBuilder timeout_builder;
     shared_ptr<KuduClient> client;
-    configure_builder_for(kValidAccount, &timeout_builder, 3 * kLifetimeMs);
+    configure_builder_for(kValidAccount, &timeout_builder, 3 * kLifetimeMs, 
true);
     Status s = timeout_builder.Build(&client);
     ASSERT_TRUE(s.IsRuntimeError()) << s.ToString();
     ASSERT_STR_CONTAINS(s.ToString(), "token expired");
@@ -699,6 +699,7 @@ TEST_F(SecurityITest, TestJwtMiniClusterWithInvalidCert) {
                                                         ca_certificate_file));
 
   cluster_opts_.mini_oidc_options = std::move(oidc_opts);
+
   ASSERT_OK(StartCluster());
 
   string cluster_cert_pem;
@@ -718,7 +719,7 @@ TEST_F(SecurityITest, TestJwtMiniClusterWithInvalidCert) {
   shared_ptr<KuduClient> client;
   auto s = client_builder.Build(&client);
   ASSERT_TRUE(s.IsRuntimeError()) << s.ToString();
-  ASSERT_STR_CONTAINS(s.ToString(), "Error initializing JWT helper: Failed to 
load JWKS");
+  ASSERT_STR_CONTAINS(s.ToString(), "Not authorized: Failed to load JWKS");
 }
 
 TEST_F(SecurityITest, TestJwtMiniClusterWithUntrustedCert) {
@@ -748,6 +749,7 @@ TEST_F(SecurityITest, TestJwtMiniClusterWithUntrustedCert) {
   oidc_opts.server_certificate = certificate_file;
 
   cluster_opts_.mini_oidc_options = std::move(oidc_opts);
+
   ASSERT_OK(StartCluster());
 
   string cluster_cert_pem;
@@ -767,9 +769,61 @@ TEST_F(SecurityITest, TestJwtMiniClusterWithUntrustedCert) 
{
   shared_ptr<KuduClient> client;
   auto s = client_builder.Build(&client);
   ASSERT_TRUE(s.IsRuntimeError()) << s.ToString();
-  ASSERT_STR_CONTAINS(s.ToString(), "Error initializing JWT helper: Failed to 
load JWKS");
+  ASSERT_STR_CONTAINS(s.ToString(), "Not authorized: Failed to load JWKS");
 }
 
+TEST_F(SecurityITest, TestJwtMiniClusterWithNonWorkingJWKS) {
+  cluster_opts_.enable_kerberos = false;
+  cluster_opts_.num_tablet_servers = 0;
+  cluster_opts_.start_jwks = false;
+  cluster_opts_.enable_client_jwt = true;
+
+  MiniOidcOptions oidc_opts;
+  const auto* const kValidAccount = "valid";
+  const auto* const kSubject = "kudu-user";
+  oidc_opts.account_ids = {
+    { kValidAccount, true }
+  };
+
+  // Set up certificates for the JWKS server
+  string ca_certificate_file;
+  string private_key_file;
+  string certificate_file;
+  ASSERT_OK(CreateTestSSLCertWithChainSignedByRoot(GetTestDataDirectory(),
+                                                   &certificate_file,
+                                                   &private_key_file,
+                                                   &ca_certificate_file));
+  // set the certs and private key for the jwks webserver
+  oidc_opts.private_key_file = private_key_file;
+  oidc_opts.server_certificate = certificate_file;
+  // set the ca_cert (jwks certificate verification is enabled by default)
+  
cluster_opts_.extra_master_flags.push_back(Substitute("--trusted_certificate_file=$0",
+                                                        ca_certificate_file));
+
+  cluster_opts_.mini_oidc_options = std::move(oidc_opts);
+  ASSERT_OK(StartCluster());
+
+  string cluster_cert_pem;
+  ASSERT_EVENTUALLY([&] {
+    ASSERT_OK(FetchClusterCACert(&cluster_cert_pem));
+  });
+  ASSERT_FALSE(cluster_cert_pem.empty());
+
+  KuduClientBuilder client_builder;
+  for (auto i = 0; i < cluster_->num_masters(); ++i) {
+    
client_builder.add_master_server_addr(cluster_->master(i)->bound_rpc_addr().ToString());
+  }
+  client_builder.jwt(cluster_->oidc()->CreateJwt(kValidAccount, kSubject, 
true));
+  client_builder.trusted_certificate(cluster_cert_pem);
+  client_builder.require_authentication(true);
+
+  shared_ptr<KuduClient> client;
+  auto s = client_builder.Build(&client);
+  ASSERT_TRUE(s.IsRuntimeError()) << s.ToString();
+  ASSERT_STR_CONTAINS(s.ToString(), "Not authorized: Failed to load JWKS");
+}
+
+
 TEST_F(SecurityITest, TestWorldReadableKeytab) {
   const string credentials_name = GetTestPath("insecure.keytab");
   NO_FATALS(CreateWorldReadableFile(credentials_name));
diff --git a/src/kudu/mini-cluster/external_mini_cluster.cc 
b/src/kudu/mini-cluster/external_mini_cluster.cc
index 079857852..f16224968 100644
--- a/src/kudu/mini-cluster/external_mini_cluster.cc
+++ b/src/kudu/mini-cluster/external_mini_cluster.cc
@@ -276,12 +276,15 @@ Status ExternalMiniCluster::Start() {
   std::shared_ptr<JwtVerifier> jwt_verifier = nullptr;
   if (opts_.enable_client_jwt) {
     oidc_.reset(new MiniOidc(opts_.mini_oidc_options));
-    // Set up certificates for the JWKS server
-    RETURN_NOT_OK_PREPEND(oidc_->Start(), "Failed to start OIDC endpoints");
+    string jwks_url = "default.url";
+    if (opts_.start_jwks) {
+      RETURN_NOT_OK_PREPEND(oidc_->Start(), "Failed to start OIDC endpoints");
+      jwks_url = oidc_->jwks_url();
+    }
     jwt_verifier =
-        std::make_shared<PerAccountKeyBasedJwtVerifier>(oidc_->url(),
-                                                        true,
-                                                        
opts_.mini_oidc_options.server_certificate);
+        std::make_shared<KeyBasedJwtVerifier>(jwks_url,
+                                              /* 
jwks_verify_server_certificate */ true,
+                                              
opts_.mini_oidc_options.server_certificate);
 
   }
 
@@ -738,7 +741,8 @@ Status ExternalMiniCluster::CreateMaster(const 
vector<HostPort>& master_rpc_addr
   }
   if (opts_.enable_client_jwt) {
     flags.emplace_back("--enable_jwt_token_auth=true");
-    flags.emplace_back(Substitute("--jwks_url=$0", oidc_->url()));
+    flags.emplace_back(Substitute("--jwks_url=$0",
+                       (opts_.start_jwks) ? oidc_->jwks_url() : 
"default.url"));
   }
   if (!opts_.master_alias_prefix.empty()) {
     flags.emplace_back(Substitute("--host_for_tests=$0.$1",
diff --git a/src/kudu/mini-cluster/external_mini_cluster.h 
b/src/kudu/mini-cluster/external_mini_cluster.h
index 41dd7076d..f715b3feb 100644
--- a/src/kudu/mini-cluster/external_mini_cluster.h
+++ b/src/kudu/mini-cluster/external_mini_cluster.h
@@ -323,6 +323,12 @@ struct ExternalMiniClusterOptions {
   //
   // Default: false
   bool enable_client_jwt;
+
+  // When set to false, the jwks server is not started, so JWT verification 
will not be possible.
+  // Only effective if enable_client_jwt is set to true.
+  //
+  // Default: true
+  bool start_jwks = true;
 };
 
 // A mini-cluster made up of subprocesses running each of the daemons
diff --git a/src/kudu/rpc/messenger.cc b/src/kudu/rpc/messenger.cc
index afda86c77..9c711a6a1 100644
--- a/src/kudu/rpc/messenger.cc
+++ b/src/kudu/rpc/messenger.cc
@@ -45,7 +45,6 @@
 #include "kudu/rpc/service_if.h"
 #include "kudu/security/tls_context.h"
 #include "kudu/security/token_verifier.h"
-#include "kudu/util/jwt.h"
 #include "kudu/util/flags.h"
 #include "kudu/util/metrics.h"
 #include "kudu/util/monotime.h"
@@ -97,7 +96,6 @@ Status MessengerBuilder::Build(shared_ptr<Messenger>* msgr) {
                                  
std::mem_fn(&Messenger::AllExternalReferencesDropped));
   if (jwt_verifier_) {
     new_msgr->jwt_verifier_ = std::move(jwt_verifier_);
-    RETURN_NOT_OK(new_msgr->mutable_jwt_verifier()->Init());
   }
   RETURN_NOT_OK(ParseTriState("--rpc_authentication",
                               rpc_authentication_,
diff --git a/src/kudu/rpc/negotiation-test.cc b/src/kudu/rpc/negotiation-test.cc
index 84504afcd..a501b0616 100644
--- a/src/kudu/rpc/negotiation-test.cc
+++ b/src/kudu/rpc/negotiation-test.cc
@@ -252,7 +252,7 @@ TEST_P(TestNegotiation, TestNegotiation) {
   std::shared_ptr<JwtVerifier> jwt_verifier;
   if (desc.server.jwt) {
     jwt_verifier = std::make_shared<kudu::KeyBasedJwtVerifier>(
-        JoinPathSegments(jwt_test_dir, jwks_file_name), /* is_local_file */ 
true);
+        JoinPathSegments(jwt_test_dir, jwks_file_name));
     ASSERT_OK(jwt_verifier-> Init());
   }
   optional<security::JwtRawPB> jwt_token;
@@ -1556,8 +1556,8 @@ static void RunTimeoutExpectingServer(unique_ptr<Socket> 
socket) {
   string jwt_test_dir = JoinPathSegments(kudu::GetTestDataDirectory(), "jwt");
   string jwt_data = kudu::CreateTestJWT(true);
   ASSERT_OK(kudu::CreateTestJWKSFile(jwt_test_dir, jwks_file_name));
-  kudu::KeyBasedJwtVerifier jwt_verifier(
-      JoinPathSegments(jwt_test_dir, jwks_file_name), true);
+  kudu::KeyBasedJwtVerifier jwt_verifier(JoinPathSegments(jwt_test_dir, 
jwks_file_name));
+
   CHECK_OK(jwt_verifier.Init());
   ServerNegotiation server_negotiation(std::move(socket), &tls_context,
                                        &token_verifier, &jwt_verifier, 
RpcEncryption::OPTIONAL,
@@ -1597,8 +1597,8 @@ static void 
RunTimeoutNegotiationServer(unique_ptr<Socket> socket) {
   string jwt_test_dir = JoinPathSegments(kudu::GetTestDataDirectory(), "jwt");
   string jwt_data = kudu::CreateTestJWT(true);
   ASSERT_OK(kudu::CreateTestJWKSFile(jwt_test_dir, jwks_file_name));
-  kudu::KeyBasedJwtVerifier jwt_verifier(
-      JoinPathSegments(jwt_test_dir, jwks_file_name), true);
+  kudu::KeyBasedJwtVerifier jwt_verifier(JoinPathSegments(jwt_test_dir, 
jwks_file_name));
+
   CHECK_OK(jwt_verifier.Init());
   ServerNegotiation server_negotiation(std::move(socket), &tls_context,
                                        &token_verifier, &jwt_verifier, 
RpcEncryption::OPTIONAL,
diff --git a/src/kudu/server/server_base.cc b/src/kudu/server/server_base.cc
index b19f68cfe..1dcae38dd 100644
--- a/src/kudu/server/server_base.cc
+++ b/src/kudu/server/server_base.cc
@@ -795,11 +795,12 @@ Status ServerBase::Init() {
   if (FLAGS_enable_jwt_token_auth) {
     if (!FLAGS_jwks_url.empty()) {
       jwt_verifier =
-          std::make_shared<PerAccountKeyBasedJwtVerifier>(FLAGS_jwks_url,
-                                                          
FLAGS_jwks_verify_server_certificate,
-                                                          
FLAGS_trusted_certificate_file);
+          std::make_shared<KeyBasedJwtVerifier>(FLAGS_jwks_url,
+                                                
FLAGS_jwks_verify_server_certificate,
+                                                
FLAGS_trusted_certificate_file);
     } else if (!FLAGS_jwks_file_path.empty()) {
-      jwt_verifier = 
std::make_shared<KeyBasedJwtVerifier>(FLAGS_jwks_file_path, true);
+      jwt_verifier =
+          std::make_shared<KeyBasedJwtVerifier>(FLAGS_jwks_file_path);
     } else {
       LOG(WARNING) << Substitute("JWT authentication enabled, but neither 
'jwks_url' or "
           "'jwks_file_path' are set!");
diff --git a/src/kudu/util/jwt-util-test.cc b/src/kudu/util/jwt-util-test.cc
index 892743c4b..c3655964c 100644
--- a/src/kudu/util/jwt-util-test.cc
+++ b/src/kudu/util/jwt-util-test.cc
@@ -919,7 +919,10 @@ TEST(JwtUtilTest, VerifyJWKSUrl) {
       
"BZiHQeHO38ydRMWIeto78pV2s9sf1CdwVwycuJOfnKY_-M5-fl1hW_25kSTNt33L57a5BgbGZ1sabWP3AD__-HYD2muR"
       "klbfyYn_ghqjL7ihY2ECaZzZ0Utw",
       encoded_token);
-  KeyBasedJwtVerifier jwt_verifier(jwks_server.url(), /*is_local_file*/false);
+  KeyBasedJwtVerifier jwt_verifier(jwks_server.url(),
+                                   /*jwks_verify_server_certificate*/ false,
+                                   /*jwks_ca_certificate*/ "");
+
   ASSERT_OK(jwt_verifier.Init());
   string subject;
   ASSERT_OK(jwt_verifier.VerifyToken(encoded_token, &subject));
@@ -940,6 +943,7 @@ TEST(JwtUtilTest, VerifyOIDCDiscoveryEndpoint) {
   };
   MiniOidc oidc(std::move(opts));
   ASSERT_OK(oidc.Start());
+
   const PerAccountKeyBasedJwtVerifier jwt_verifier(oidc.url(),
                                                    
/*jwks_verify_server_certificate*/ false,
                                                    /*jwks_ca_certificate*/ "");
diff --git a/src/kudu/util/jwt-util.cc b/src/kudu/util/jwt-util.cc
index b391d5e2e..3ce227d02 100644
--- a/src/kudu/util/jwt-util.cc
+++ b/src/kudu/util/jwt-util.cc
@@ -810,16 +810,17 @@ void 
JWTHelper::TokenDeleter::operator()(JWTHelper::JWTDecodedToken* token) cons
 }
 
 Status JWTHelper::Init(const std::string& jwks_file_path) {
-  return Init(jwks_file_path,
-              /*jwks_verify_server_certificate*/ false,
-              /*is_local_file*/ true);
+  jwks_mgr_.reset(new JWKSMgr());
+  RETURN_NOT_OK(jwks_mgr_->Init(jwks_file_path,
+                                /*jwks_verify_server_certificate*/ false,
+                                /*is_local_file*/ true));
+  if (!initialized_) initialized_ = true;
+  return Status::OK();
 }
 
-Status JWTHelper::Init(const std::string& jwks_uri, bool 
jwks_verify_server_certificate,
-                       bool is_local_file) {
+Status JWTHelper::Init(const std::string& jwks_uri, bool 
jwks_verify_server_certificate) {
   jwks_mgr_.reset(new JWKSMgr());
-  RETURN_NOT_OK(jwks_mgr_->Init(jwks_uri, jwks_verify_server_certificate,
-                                is_local_file));
+  RETURN_NOT_OK(jwks_mgr_->Init(jwks_uri, jwks_verify_server_certificate, 
false));
   if (!initialized_) initialized_ = true;
   return Status::OK();
 }
@@ -967,11 +968,21 @@ Status JWTHelper::GetCustomClaimUsername(const 
JWTDecodedToken* decoded_token,
   return Status::OK();
 }
 
-Status KeyBasedJwtVerifier::Init() {
-  return jwt_->Init(jwks_uri_, /*jwks_verify_server_certificate*/ false, 
is_local_file_);
+Status KeyBasedJwtVerifier::Init() const {
+  if (!jwt_->IsInitialised()) {
+    if (is_local_file_) {
+      return jwt_->Init(jwks_uri_);
+    }
+
+    return jwt_->Init(jwks_uri_, jwks_verify_server_certificate_);
+  }
+
+  return Status::OK();
 }
 
 Status KeyBasedJwtVerifier::VerifyToken(const string& bytes_raw, string* 
subject) const {
+  RETURN_NOT_OK(Init());
+
   JWTHelper::UniqueJWTDecodedToken decoded_token;
   RETURN_NOT_OK(JWTHelper::Decode(bytes_raw, decoded_token));
   RETURN_NOT_OK(jwt_->Verify(decoded_token.get()));
@@ -1044,8 +1055,7 @@ Status 
PerAccountKeyBasedJwtVerifier::JWTHelperForToken(const JWTHelper::JWTDeco
   // refreshes into a single thread or threadpool.
   auto new_helper = std::make_shared<JWTHelper>();
   RETURN_NOT_OK_PREPEND(new_helper->Init(jwks_uri,
-                                         jwks_verify_server_certificate_,
-                                         /*is_local_file*/ false),
+                                         jwks_verify_server_certificate_),
                         "Error initializing JWT helper");
 
   {
@@ -1057,11 +1067,10 @@ Status 
PerAccountKeyBasedJwtVerifier::JWTHelperForToken(const JWTHelper::JWTDeco
   return Status::OK();
 }
 
-Status PerAccountKeyBasedJwtVerifier::Init() {
+Status PerAccountKeyBasedJwtVerifier::Init() const {
   for (auto& [account_id, verifier] : jwt_by_account_id_) {
     RETURN_NOT_OK(verifier->Init(Substitute("$0?accountId=$1", oidc_uri_, 
account_id),
-                                            jwks_verify_server_certificate_,
-                                            /*is_local_file*/ false));
+                                            jwks_verify_server_certificate_));
   }
   return Status::OK();
 }
diff --git a/src/kudu/util/jwt-util.h b/src/kudu/util/jwt-util.h
index daba84eb1..0831828d7 100644
--- a/src/kudu/util/jwt-util.h
+++ b/src/kudu/util/jwt-util.h
@@ -68,8 +68,7 @@ class JWTHelper {
 
   // Load JWKS from a given local JSON file or URL. Returns an error if 
problems were
   // encountered.
-  Status Init(const std::string& jwks_uri, bool jwks_verify_server_certificate,
-              bool is_local_file);
+  Status Init(const std::string& jwks_uri, bool 
jwks_verify_server_certificate);
 
   // Decode the given JWT token. The decoding result is stored in 
decoded_token_out.
   // Return Status::OK if the decoding is successful.
@@ -86,6 +85,8 @@ class JWTHelper {
   static Status GetCustomClaimUsername(const JWTDecodedToken* decoded_token,
       const std::string& custom_claim_username, std::string& username);
 
+  bool IsInitialised() { return initialized_; }
+
   // Return snapshot of JWKS.
   std::shared_ptr<const JWKSSnapshot> GetJWKS() const;
 
@@ -104,33 +105,54 @@ class JWTHelper {
 // JwtVerifier implementation that uses JWKS to verify tokens.
 class KeyBasedJwtVerifier : public JwtVerifier {
  public:
-  KeyBasedJwtVerifier(std::string jwks_uri, bool is_local_file)
+  explicit KeyBasedJwtVerifier(const std::string& jwks_file_path)
+      : jwt_(JWTHelper::GetInstance()),
+        jwks_uri_(std::move(jwks_file_path)),
+        is_local_file_(true),
+        jwks_verify_server_certificate_(false),
+        jwks_ca_certificate_("") {
+  }
+
+  KeyBasedJwtVerifier(std::string jwks_uri, bool 
jwks_verify_server_certificate,
+                      const std::string& jwks_ca_certificate)
       : jwt_(JWTHelper::GetInstance()),
         jwks_uri_(std::move(jwks_uri)),
-        is_local_file_(is_local_file) {
+        is_local_file_(false),
+        jwks_verify_server_certificate_(jwks_verify_server_certificate),
+        jwks_ca_certificate_(jwks_ca_certificate) {
   }
+
   ~KeyBasedJwtVerifier() override = default;
-  Status Init() override;
+
+  Status Init() const override;
+
   Status VerifyToken(const std::string& bytes_raw, std::string* subject) const 
override;
+
  private:
   JWTHelper* jwt_;
+
   std::string jwks_uri_;
+
   bool is_local_file_;
 
+  const bool jwks_verify_server_certificate_;
+
+  const std::string jwks_ca_certificate_;
+
   DISALLOW_COPY_AND_ASSIGN(KeyBasedJwtVerifier);
 };
 
 class PerAccountKeyBasedJwtVerifier : public JwtVerifier {
  public:
   explicit PerAccountKeyBasedJwtVerifier(std::string oidc_uri, bool 
jwks_verify_server_certificate,
-                                         const std::string jwks_ca_certificate)
+                                         const std::string& 
jwks_ca_certificate)
       : oidc_uri_(std::move(oidc_uri)),
         jwks_verify_server_certificate_(jwks_verify_server_certificate),
         jwks_ca_certificate_(jwks_ca_certificate) {}
 
   ~PerAccountKeyBasedJwtVerifier() override = default;
 
-  Status Init() override;
+  Status Init() const override;
 
   Status VerifyToken(const std::string& bytes_raw, std::string* subject) const 
override;
 
diff --git a/src/kudu/util/jwt.h b/src/kudu/util/jwt.h
index b4f321e86..43a56ba30 100644
--- a/src/kudu/util/jwt.h
+++ b/src/kudu/util/jwt.h
@@ -28,7 +28,7 @@ namespace kudu {
 class JwtVerifier {
  public:
   virtual ~JwtVerifier() {}
-  virtual Status Init() = 0;
+  virtual Status Init() const = 0;
   // Verifies a JWT, which is passed as bytes_raw, then extracts the subject 
from the verified
   // token and returns it by pointer in subject. The returned pointer is owned 
by the caller.
   virtual Status VerifyToken(const std::string& bytes_raw, std::string* 
subject) const = 0;
diff --git a/src/kudu/util/mini_oidc.cc b/src/kudu/util/mini_oidc.cc
index a6296d3a5..36103f0e5 100644
--- a/src/kudu/util/mini_oidc.cc
+++ b/src/kudu/util/mini_oidc.cc
@@ -107,6 +107,9 @@ Status MiniOidc::Start() {
 
   jwks_server_.reset(new Webserver(jwks_opts));
 
+  const string JWKS_ENDPOINT = "jwks.json";
+
+  // For clients with oidc discovery
   for (const auto& [account_id, valid] : options_.account_ids) {
     jwks_server_->RegisterPrerenderedPathHandler(
         Substitute("/jwks/$0", account_id),
@@ -126,6 +129,26 @@ Status MiniOidc::Start() {
         /*is_styled*/ false,
         /*is_on_nav_bar*/ false);
   }
+
+  // for clients without oidc discovery
+  jwks_server_->RegisterPrerenderedPathHandler(
+      "/jwks.json",
+      "jwks",
+      [&](const Webserver::WebRequest&  /*req*/, 
Webserver::PrerenderedWebResponse* resp) {
+        // NOTE: 'kKid1' points at a valid key, while 'kKid2' points at an
+        // invalid key.
+        resp->output << Substitute(kJwksRsaFileFormat, kKid1,
+                                   "RS256",
+                                   kRsaPubKeyJwkN,
+                                   kRsaPubKeyJwkE, kKid2,
+                                   "RS256",
+                                   kRsaInvalidPubKeyJwkN,
+                                   kRsaPubKeyJwkE),
+        resp->status_code = HttpStatusCode::Ok;
+      },
+      /*is_styled*/ false,
+      /*is_on_nav_bar*/ false);
+
   LOG(INFO) << "Starting JWKS server";
   RETURN_NOT_OK(jwks_server_->Start());
   vector<Sockaddr> advertised_addrs;
@@ -138,9 +161,13 @@ Status MiniOidc::Start() {
     protocol = "http";
   }
 
-  const string jwks_url = Substitute("$0://localhost:$1/jwks",
-                                     protocol,
-                                     advertised_addrs[0].port());
+  jwks_url_ = Substitute("$0://localhost:$1/jwks.json",
+                         protocol,
+                         advertised_addrs[0].port());
+
+  const string discovery_jwks_url = Substitute("$0://localhost:$1/jwks",
+                         protocol,
+                         advertised_addrs[0].port());
 
   // Now start the OIDC Discovery server that points to the JWKS endpoints.
   WebserverOptions oidc_opts;
@@ -152,8 +179,9 @@ Status MiniOidc::Start() {
       "openid-configuration",
       // Pass the 'accountId' query arguments to return a response that
       // points to the JWKS endpoint for the account.
-      [jwks_url](const Webserver::WebRequest& req, 
Webserver::PrerenderedWebResponse* resp) {
-        OidcDiscoveryHandler(req, resp, jwks_url);
+      [discovery_jwks_url](const Webserver::WebRequest& req,
+                           Webserver::PrerenderedWebResponse* resp) {
+        OidcDiscoveryHandler(req, resp, discovery_jwks_url);
       },
       /*is_styled*/ false,
       /*is_on_nav_bar*/ false);
@@ -179,7 +207,6 @@ void MiniOidc::Stop() {
 
 string MiniOidc::CreateJwt(const string& account_id, const string& subject, 
bool is_valid) {
   auto lifetime = std::chrono::milliseconds(options_.lifetime_ms);
-
   return jwt::create()
       .set_issuer(Substitute("auth0/$0", account_id))
       .set_type("JWT")
diff --git a/src/kudu/util/mini_oidc.h b/src/kudu/util/mini_oidc.h
index b4eff0d0e..541e5791b 100644
--- a/src/kudu/util/mini_oidc.h
+++ b/src/kudu/util/mini_oidc.h
@@ -21,6 +21,8 @@
 #include <string>
 #include <unordered_map>
 
+#include <glog/logging.h>
+
 #include "kudu/gutil/port.h"
 #include "kudu/util/status.h"
 
@@ -84,9 +86,16 @@ class MiniOidc {
   std::string CreateJwt(const std::string& account_id,
                         const std::string& subject,
                         bool is_valid);
+
   const std::string& url() const {
+    DCHECK(!oidc_url_.empty());
     return oidc_url_;
   }
+
+  const std::string& jwks_url() const {
+    DCHECK(!jwks_url_.empty());
+    return jwks_url_;
+  }
  private:
   MiniOidcOptions options_;
 
@@ -95,6 +104,8 @@ class MiniOidc {
   std::string oidc_url_;
 
   std::unique_ptr<Webserver> jwks_server_;
+
+  std::string jwks_url_;
 };
 
 } // namespace kudu

Reply via email to