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

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


The following commit(s) were added to refs/heads/master by this push:
     new 00eecabdc [webserver] Mapping Kerberos principals to local usernames
00eecabdc is described below

commit 00eecabdcfe98cee9e954d4e1c10706e5a5c6a15
Author: Gabriella Lotz <[email protected]>
AuthorDate: Thu Jul 10 11:19:45 2025 +0200

    [webserver] Mapping Kerberos principals to local usernames
    
    The REST API was failing authorization checks for authenticated
    Kerberos users due to a mismatch between the authentication and
    authorization username formats. When users authenticate via SPNEGO,
    the webserver extracts the full Kerberos principal
    (e.g., "[email protected]") and passes it directly to the
    authorization layer. However, Ranger policies are configured with
    short usernames (e.g., "test-user"), causing authorization to fail
    even for properly authenticated users with valid policies.
    
    This created an inconsistency where the same user with identical
    Ranger policies could successfully perform operations via the RPC API
    but would be denied when using the REST API. The RPC layer correctly
    maps Kerberos principals to local usernames using
    MapPrincipalToLocalName() before authorization, but the REST API was
    bypassing this step.
    
    This patch adds principal-to-local username mapping to all webserver
    authorization points in webserver.cc. The implementation uses the same
    MapPrincipalToLocalName() function as the RPC layer, ensuring
    consistent behavior across both APIs.
    
    Moreover, the WebRequest struct had an authn_princ field that was
    renamed to username to better reflect its purpose, as it now contains
    the local username after mapping.
    
    Change-Id: Ib25a7886c32cbbef35272cd5799ae84601335a34
    Reviewed-on: http://gerrit.cloudera.org:8080/23153
    Reviewed-by: Marton Greber <[email protected]>
    Tested-by: Marton Greber <[email protected]>
    Reviewed-by: Zoltan Chovan <[email protected]>
---
 src/kudu/master/rest_catalog_path_handlers.cc |  8 ++++----
 src/kudu/server/webserver-test.cc             |  4 ++--
 src/kudu/server/webserver.cc                  | 17 ++++++++++++++---
 src/kudu/util/web_callback_registry.h         |  4 ++--
 4 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/src/kudu/master/rest_catalog_path_handlers.cc 
b/src/kudu/master/rest_catalog_path_handlers.cc
index d013c41b2..c0c81ce2d 100644
--- a/src/kudu/master/rest_catalog_path_handlers.cc
+++ b/src/kudu/master/rest_catalog_path_handlers.cc
@@ -224,7 +224,7 @@ void 
RestCatalogPathHandlers::HandleGetTables(std::ostringstream* output,
                                               HttpStatusCode* status_code) {
   ListTablesRequestPB request;
   ListTablesResponsePB response;
-  optional<string> user = req.authn_principal.empty() ? "default" : 
req.authn_principal;
+  optional<string> user = req.username.empty() ? "default" : req.username;
   Status status = master_->catalog_manager()->ListTables(&request, &response, 
user);
   JsonWriter jw(output, JsonWriter::COMPACT);
 
@@ -266,7 +266,7 @@ void 
RestCatalogPathHandlers::HandlePostTables(ostringstream* output,
                       *status_code,
                       HttpStatusCode::BadRequest);
   }
-  optional<string> user = req.authn_principal.empty() ? "default" : 
req.authn_principal;
+  optional<string> user = req.username.empty() ? "default" : req.username;
   Status status = master_->catalog_manager()->CreateTableWithUser(&request, 
&response, user);
 
   if (!status.ok()) {
@@ -335,7 +335,7 @@ void RestCatalogPathHandlers::HandlePutTable(ostringstream* 
output,
                       HttpStatusCode::BadRequest);
   }
 
-  optional<string> user = req.authn_principal.empty() ? "default" : 
req.authn_principal;
+  optional<string> user = req.username.empty() ? "default" : req.username;
   Status status = master_->catalog_manager()->AlterTableWithUser(request, 
&response, user);
 
   if (!status.ok()) {
@@ -383,7 +383,7 @@ void 
RestCatalogPathHandlers::HandleDeleteTable(ostringstream* output,
   DeleteTableResponsePB response;
   request.mutable_table()->set_table_id(table_id);
   JsonWriter jw(output, JsonWriter::COMPACT);
-  optional<string> user = req.authn_principal.empty() ? "default" : 
req.authn_principal;
+  optional<string> user = req.username.empty() ? "default" : req.username;
   Status status = master_->catalog_manager()->DeleteTableWithUser(request, 
&response, user);
 
   if (status.ok()) {
diff --git a/src/kudu/server/webserver-test.cc 
b/src/kudu/server/webserver-test.cc
index 752c00e04..b68429aa1 100644
--- a/src/kudu/server/webserver-test.cc
+++ b/src/kudu/server/webserver-test.cc
@@ -678,7 +678,7 @@ TEST_F(WebserverTest, TestPutMethodNotAllowed) {
 
 // Test that authenticated principal is correctly passed to the handler.
 static void Handler(const Webserver::WebRequest& req, 
Webserver::PrerenderedWebResponse* resp) {
-  resp->output << req.authn_principal;
+  resp->output << req.username;
 }
 
 class AuthnWebserverTest : public SpnegoWebserverTest {
@@ -713,7 +713,7 @@ TEST_F(AuthnWebserverTest, 
TestAuthenticatedUserPassedToHandler) {
   ASSERT_OK(kdc_->Kinit("alice"));
   curl_.set_auth(CurlAuthType::SPNEGO);
   ASSERT_OK(curl_.FetchURL(Substitute("$0/authn", url_), &buf_));
-  ASSERT_STR_CONTAINS(buf_.ToString(), "[email protected]");
+  ASSERT_STR_CONTAINS(buf_.ToString(), "alice");
 }
 
 TEST_F(AuthnWebserverTest, TestUnauthenticatedBadKeytab) {
diff --git a/src/kudu/server/webserver.cc b/src/kudu/server/webserver.cc
index 453fefb7d..9987c0cf4 100644
--- a/src/kudu/server/webserver.cc
+++ b/src/kudu/server/webserver.cc
@@ -57,6 +57,7 @@
 #include "kudu/gutil/strings/stringpiece.h"
 #include "kudu/gutil/strings/strip.h"
 #include "kudu/gutil/strings/substitute.h"
+#include "kudu/security/init.h"
 #include "kudu/security/gssapi.h"
 #include "kudu/util/easy_json.h"
 #include "kudu/util/env.h"
@@ -616,7 +617,17 @@ sq_callback_result_t Webserver::BeginRequestCallback(
       opts_.spnego_post_authn_callback(authn_princ);
     }
 
-    request_info->remote_user = strdup(authn_princ.c_str());
+    string local_user;
+    s = security::MapPrincipalToLocalName(authn_princ, &local_user);
+    if (!s.ok()) {
+      LOG(WARNING) << "Failed to map Kerberos principal '" << authn_princ
+                   << "' to local name: " << s.ToString();
+      resp.output << s.ToString();
+      resp.status_code = HttpStatusCode::InternalServerError;
+      SendResponse(connection, &resp);
+      return SQ_HANDLED_OK;
+    }
+    request_info->remote_user = strdup(local_user.c_str());
   }
 
   PathHandler* handler = nullptr;
@@ -699,9 +710,9 @@ sq_callback_result_t Webserver::RunPathHandler(
   }
   req.path_params = path_params;
   if (request_info->remote_user != nullptr) {
-    req.authn_principal = request_info->remote_user;
+    req.username = request_info->remote_user;
   } else {
-    req.authn_principal = "";
+    req.username = "";
   }
   for (int i = 0; i < request_info->num_headers; i++) {
     const auto& h = request_info->http_headers[i];
diff --git a/src/kudu/util/web_callback_registry.h 
b/src/kudu/util/web_callback_registry.h
index 83117f684..317537107 100644
--- a/src/kudu/util/web_callback_registry.h
+++ b/src/kudu/util/web_callback_registry.h
@@ -75,8 +75,8 @@ class WebCallbackRegistry {
     // The HTTP request headers.
     ArgumentMap request_headers;
 
-    // The authenticated principal, if any.
-    std::string authn_principal;
+    // The authenticated username, if any.
+    std::string username;
 
     // The raw query string passed in the URL. May be empty.
     std::string query_string;

Reply via email to