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