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

kumaab pushed a commit to branch ranger-2.10
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/ranger-2.10 by this push:
     new 3d1850c6f RANGER-5766: Add support for SPIFFE Ids as usernames in 
Ranger (#1197)
3d1850c6f is described below

commit 3d1850c6f43ea1b16c623e2de905b25bb5998e4b
Author: Abhishek Kumar <[email protected]>
AuthorDate: Mon Aug 31 14:41:31 2026 -0700

    RANGER-5766: Add support for SPIFFE Ids as usernames in Ranger (#1197)
    
    (cherry picked from commit b222989f186a9318f82fd86654d772b6f3662026)
---
 .../apache/ranger/plugin/util/SpiffeIdUtil.java    | 48 +++++--------
 .../ranger/plugin/util/SpiffeIdUtilTest.java       | 80 +++++++---------------
 pdp/conf.dist/ranger-pdp-site.xml                  |  2 +-
 .../pdp/security/HttpHeaderAuthNHandler.java       |  9 ++-
 pdp/src/main/resources/ranger-pdp-default.xml      |  2 +-
 .../pdp/security/HttpHeaderAuthNHandlerTest.java   | 28 +++++---
 .../java/org/apache/ranger/common/StringUtil.java  | 25 +++++++
 .../main/java/org/apache/ranger/rest/UserREST.java |  1 +
 .../web/filter/RangerHeaderPreAuthFilter.java      | 13 ++--
 .../main/resources/conf.dist/ranger-admin-site.xml |  7 +-
 security-admin/src/main/webapp/login.jsp           |  4 ++
 .../main/webapp/react-webapp/src/utils/XAEnums.js  | 11 +++
 .../webapp/react-webapp/src/utils/XAMessages.js    | 12 ++++
 .../users_details/UserForm.jsx                     | 24 +++++--
 .../src/main/webapp/scripts/prelogin/XAPrelogin.js |  6 +-
 .../org/apache/ranger/common/TestStringUtil.java   | 31 ++++++++-
 .../web/filter/TestRangerHeaderPreAuthFilter.java  | 48 +++++++++----
 17 files changed, 214 insertions(+), 137 deletions(-)

diff --git 
a/common-utils/src/main/java/org/apache/ranger/plugin/util/SpiffeIdUtil.java 
b/common-utils/src/main/java/org/apache/ranger/plugin/util/SpiffeIdUtil.java
index 5c303364b..418fe1388 100644
--- a/common-utils/src/main/java/org/apache/ranger/plugin/util/SpiffeIdUtil.java
+++ b/common-utils/src/main/java/org/apache/ranger/plugin/util/SpiffeIdUtil.java
@@ -28,33 +28,34 @@
 import java.util.regex.Pattern;
 
 /**
- * Helper for validating a SPIFFE ID and extracting the service-account 
identity from it.
+ * Helper for validating a SPIFFE ID that is used as a service identity.
  *
- * <p>A SPIFFE ID is expected in the exact form {@code 
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>};
- * for service-to-service authentication the receiving server trusts the 
workload
- * identity carried in a header (e.g. {@code x-service-spiffe-id}) and uses
- * the trailing service-account segment as the authenticated principal.
+ * <p>A SPIFFE ID is expected in the general form defined by the SPIFFE-ID 
specification,
+ * {@code spiffe://<trust-domain>/<path>}, where {@code <path>} is one or more 
{@code /}-separated
+ * segments. For service-to-service authentication the receiving server trusts 
the workload identity
+ * carried in a header (e.g. {@code x-service-spiffe-id}) and uses the SPIFFE 
ID itself as the
+ * authenticated principal. No particular path layout (such as the SPIRE 
{@code /ns/<namespace>/sa/<service-account>}
+ * convention) is required.
  */
 public final class SpiffeIdUtil {
     private static final String  HEADER_NAMES_SEP  = ",";
 
     /**
-     * Matches {@code 
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>} using the character
-     * sets from the SPIFFE-ID specification:
+     * Matches {@code spiffe://<trust-domain>/<path>} using the character sets 
from the SPIFFE-ID
+     * specification:
      * <ul>
      *   <li>trust-domain: lowercase letters, digits, {@code .}, {@code -}, 
{@code _} (the spec requires
      *       the trust domain to be lowercase);
-     *   <li>namespace and service-account: ASCII letters (any case), digits, 
{@code .}, {@code -}, {@code _}
-     *       (SPIFFE path segments are case-sensitive).
+     *   <li>path: one or more {@code /}-separated segments of ASCII letters 
(any case), digits,
+     *       {@code .}, {@code -}, {@code _} (SPIFFE path segments are 
case-sensitive).
      * </ul>
-     * All segments must be non-empty. This is an allow-list of safe 
characters, so whitespace, control
-     * characters and {@code /} are excluded, keeping the extracted principal 
well-formed. Per the spec,
-     * the namespace and service-account path segments must not be the 
relative modifiers {@code .} or
-     * {@code ..} (enforced by the leading negative look-aheads). The 
service-account is exposed via the
-     * named group {@code sa}.
+     * At least one path segment is required and every segment must be 
non-empty. This is an allow-list
+     * of safe characters, so whitespace and control characters are excluded, 
keeping the principal
+     * well-formed. Per the spec, no path segment may be the relative modifier 
{@code .} or {@code ..}
+     * (enforced by the per-segment negative look-ahead).
      */
     private static final Pattern SPIFFE_ID_PATTERN = Pattern.compile(
-            
"^spiffe://[a-z0-9._-]+/ns/(?!\\.{1,2}/)[A-Za-z0-9._-]+/sa/(?<sa>(?!\\.{1,2}$)[A-Za-z0-9._-]+)$");
+            "^spiffe://[a-z0-9._-]+(?:/(?!\\.{1,2}(?:/|$))[A-Za-z0-9._-]+)+$");
 
     private SpiffeIdUtil() {
         // to block instantiation
@@ -96,26 +97,11 @@ public static boolean isValidSpiffeId(String spiffeId) {
         return matchSpiffeId(spiffeId) != null;
     }
 
-    /**
-     * Extracts the service-account name from a SPIFFE ID such as
-     * {@code spiffe://my-cluster/ns/service-namespace/sa/service-sa}. The 
value must match the exact
-     * SPIFFE ID format (see {@link #isValidSpiffeId(String)}); otherwise 
{@code null} is returned.
-     *
-     * @param spiffeId the raw SPIFFE ID value from the request header
-     * @return the service-account segment (e.g. {@code service-sa}), or
-     *         {@code null} if the input is blank or not a well-formed SPIFFE 
ID
-     */
-    public static String extractServiceAccount(String spiffeId) {
-        Matcher matcher = matchSpiffeId(spiffeId);
-
-        return matcher != null ? matcher.group("sa") : null;
-    }
-
     /**
      * Matches the given value against the expected SPIFFE ID format, 
computing the {@link Matcher} once.
      *
      * @param spiffeId the raw SPIFFE ID value from the request header
-     * @return a matched {@link Matcher} (with groups available) if the value 
is a well-formed SPIFFE ID;
+     * @return a matched {@link Matcher} if the value is a well-formed SPIFFE 
ID;
      *         {@code null} if the value is blank or does not match
      */
     private static Matcher matchSpiffeId(String spiffeId) {
diff --git 
a/common-utils/src/test/java/org/apache/ranger/plugin/util/SpiffeIdUtilTest.java
 
b/common-utils/src/test/java/org/apache/ranger/plugin/util/SpiffeIdUtilTest.java
index acda94d30..88d17a15a 100644
--- 
a/common-utils/src/test/java/org/apache/ranger/plugin/util/SpiffeIdUtilTest.java
+++ 
b/common-utils/src/test/java/org/apache/ranger/plugin/util/SpiffeIdUtilTest.java
@@ -27,7 +27,6 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class SpiffeIdUtilTest {
@@ -46,14 +45,12 @@ void isValidSpiffeId_rejectsMalformed() {
         assertFalse(SpiffeIdUtil.isValidSpiffeId("   "));
         assertFalse(SpiffeIdUtil.isValidSpiffeId("not-a-spiffe-id"));
         
assertFalse(SpiffeIdUtil.isValidSpiffeId("https://my-cluster/ns/n/sa/s";));   // 
wrong scheme
-        
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/sa/service-sa")); 
// missing /ns/
-        assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/n")); 
      // missing /sa/
-        
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns//sa/s"));   // 
empty namespace
+        assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster"));      
      // trust domain only, no path
+        assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/"));     
      // trailing slash, empty path
+        
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns//sa/s"));   // 
empty path segment
         assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe:///ns/n/sa/s"));      
      // empty trust domain
-        
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/n/sa/"));   // 
empty service account
+        
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/n/sa/"));   // 
empty trailing segment
         assertFalse(SpiffeIdUtil.isValidSpiffeId(VALID + "/"));                
      // trailing slash
-        assertFalse(SpiffeIdUtil.isValidSpiffeId(VALID + "/extra"));           
      // extra segment
-        
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/sa/s/ns/n"));  // 
wrong order
     }
 
     @Test
@@ -63,9 +60,6 @@ void isValidSpiffeId_rejectsUnsafeOrIllegalCharacters() {
         
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/prod/sa/service\nsa"));
  // embedded newline
         
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/prod/sa/svc@admin"));
    // illegal char '@'
         
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://My-Cluster/ns/prod/sa/service-sa"));
   // trust domain must be lowercase per spec
-
-        
assertNull(SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/prod/sa/service
 sa"));
-        
assertNull(SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/prod/sa/service\nsa"));
     }
 
     @Test
@@ -76,73 +70,47 @@ void isValidSpiffeId_rejectsRelativePathModifierSegments() {
         
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/prod/sa/."));  
       // service-account '.'
         
assertFalse(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/prod/sa/..")); 
       // service-account '..'
 
-        
assertNull(SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/prod/sa/."));
-        
assertNull(SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/../sa/service-sa"));
-
         // Dots inside a segment (not a bare '.'/'..') remain valid.
         
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/prod/sa/svc.v1"));
-        assertEquals("svc.v1", 
SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/..prod/sa/svc.v1"));
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/..prod/sa/svc.v1"));
     }
 
     @Test
     void isValidSpiffeId_acceptsSpecAllowedCharacters() {
         // SPIFFE path segments are case-sensitive and allow letters (any 
case), digits, '.', '-', '_'.
         
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster.example.com/ns/service-namespace/sa/service-sa"));
-        assertEquals("Service-SA", 
SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/Prod/sa/Service-SA"));
 // mixed case
-        assertEquals("svc_01", 
SpiffeIdUtil.extractServiceAccount("spiffe://cluster1/ns/ns_2/sa/svc_01"));     
      // underscore
-        assertEquals("svc-01", 
SpiffeIdUtil.extractServiceAccount("spiffe://cluster1/ns/ns-2/sa/svc-01"));     
      // hyphen/digits
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/Prod/sa/Service-SA"));
 // mixed case
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://cluster1/ns/ns_2/sa/svc_01"));
       // underscore
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://cluster1/ns/ns-2/sa/svc-01"));
       // hyphen/digits
     }
 
     @Test
-    void extractServiceAccount_returnsSaOnlyForValidId() {
-        assertEquals("service-sa", SpiffeIdUtil.extractServiceAccount(VALID));
-        assertEquals("service-sa", SpiffeIdUtil.extractServiceAccount("  " + 
VALID + "  "));
-    }
-
-    @Test
-    void extractServiceAccount_returnsNullForMalformedId() {
-        assertNull(SpiffeIdUtil.extractServiceAccount(null));
-        assertNull(SpiffeIdUtil.extractServiceAccount(""));
-        assertNull(SpiffeIdUtil.extractServiceAccount("not-a-spiffe-id"));
-        
assertNull(SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/sa/service-sa"));
-        
assertNull(SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/n/sa/"));
-        assertNull(SpiffeIdUtil.extractServiceAccount(VALID + "/extra"));
-    }
-
-    @Test
-    void extractServiceAccount_handlesRealisticProductionIds() {
+    void isValidSpiffeId_handlesRealisticProductionIds() {
         // Kubernetes trust domain with a DNS-style cluster name and typical 
namespace/service-account names.
-        assertEquals("nginx-ingress",
-                
SpiffeIdUtil.extractServiceAccount("spiffe://prod-cluster.k8s.example.com/ns/ingress-nginx/sa/nginx-ingress"));
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://prod-cluster.k8s.example.com/ns/ingress-nginx/sa/nginx-ingress"));
 
         // Cloud/mesh style trust domain (e.g. the header value from the PR 
description).
-        assertEquals("service-sa",
-                
SpiffeIdUtil.extractServiceAccount("spiffe://my-cluster/ns/service-namespace/sa/service-sa"));
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/service-namespace/sa/service-sa"));
 
         // Istio-style default service account in an application namespace.
-        assertEquals("default",
-                
SpiffeIdUtil.extractServiceAccount("spiffe://cluster.local/ns/payments/sa/default"));
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://cluster.local/ns/payments/sa/default"));
 
         // Namespace and service-account with digits and hyphens, as commonly 
generated by platforms.
-        assertEquals("spark-driver-01",
-                
SpiffeIdUtil.extractServiceAccount("spiffe://data-plane.internal/ns/team-analytics-42/sa/spark-driver-01"));
-
-        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://prod-cluster.k8s.example.com/ns/ingress-nginx/sa/nginx-ingress"));
-        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://cluster.local/ns/payments/sa/default"));
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://data-plane.internal/ns/team-analytics-42/sa/spark-driver-01"));
     }
 
     @Test
-    void rejectsSpecValidButNonConformingSpiffeId() {
-        // These are well-formed SPIFFE IDs per the SPIFFE spec (valid scheme 
+ trust domain + path),
-        // but they do not follow the expected 
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
-        // layout, so Ranger must reject them.
-        String workloadPath = "spiffe://example.org/workload/frontend"; // 
arbitrary path, no /ns//sa/
-        String nsOnly       = "spiffe://example.org/ns/prod";           // has 
/ns/ but no /sa/ segment
-
-        assertFalse(SpiffeIdUtil.isValidSpiffeId(workloadPath));
-        assertFalse(SpiffeIdUtil.isValidSpiffeId(nsOnly));
-        assertNull(SpiffeIdUtil.extractServiceAccount(workloadPath));
-        assertNull(SpiffeIdUtil.extractServiceAccount(nsOnly));
+    void acceptsSpecValidNonSpireLayoutSpiffeId() {
+        // Well-formed SPIFFE IDs per the spec (valid scheme + trust domain + 
one or more path segments)
+        // that do not follow the SPIRE 
spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
+        // convention. Since the full SPIFFE ID is used as the principal, 
these must be accepted.
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://example.org/workload/frontend"));
 // arbitrary path
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://example.org/ns/prod"));       
    // /ns/ but no /sa/
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/sa/service-sa"));  
    // /sa/ but no /ns/
+        assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/ns/n"));  
             // single-segment style
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://example.org/single"));        
    // single path segment
+        
assertTrue(SpiffeIdUtil.isValidSpiffeId("spiffe://my-cluster/sa/s/ns/n"));      
    // segments in any order
+        assertTrue(SpiffeIdUtil.isValidSpiffeId(VALID + "/extra"));            
             // additional trailing segment
     }
 
     @Test
diff --git a/pdp/conf.dist/ranger-pdp-site.xml 
b/pdp/conf.dist/ranger-pdp-site.xml
index 9d67b8eab..6f3152f41 100644
--- a/pdp/conf.dist/ranger-pdp-site.xml
+++ b/pdp/conf.dist/ranger-pdp-site.xml
@@ -149,7 +149,7 @@
   <property>
     <name>ranger.pdp.authn.header.spiffe</name>
     <value></value>
-    <description>Comma-separated list of HTTP header name(s) carrying a SPIFFE 
ID (e.g. spiffe://cluster/ns/&lt;ns&gt;/sa/&lt;service-account&gt;). A single 
header name is also valid. The trailing service-account segment is used as the 
authenticated principal. When multiple headers are configured, the first one 
carrying a well-formed SPIFFE ID is used. Consulted only when the username 
header is absent.</description>
+    <description>Comma-separated list of HTTP header name(s) carrying a SPIFFE 
ID (e.g. spiffe://cluster/ns/&lt;ns&gt;/sa/&lt;service-account&gt;). A single 
header name is also valid. The full SPIFFE ID is used as the authenticated 
principal. When multiple headers are configured, the first one carrying a 
well-formed SPIFFE ID is used. Consulted only when the username header is 
absent.</description>
   </property>
 
   <!-- JWT bearer token authentication for incoming REST requests -->
diff --git 
a/pdp/src/main/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandler.java 
b/pdp/src/main/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandler.java
index 1761a0938..a6ff8e147 100644
--- 
a/pdp/src/main/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandler.java
+++ 
b/pdp/src/main/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandler.java
@@ -71,13 +71,12 @@ public Result authenticate(HttpServletRequest request, 
HttpServletResponse respo
         }
 
         for (String spiffeHeader : spiffeHeaders) {
-            String spiffeId       = request.getHeader(spiffeHeader);
-            String serviceAccount = 
SpiffeIdUtil.extractServiceAccount(spiffeId);
+            String spiffeId = 
StringUtils.trimToNull(request.getHeader(spiffeHeader));
 
-            if (StringUtils.isNotBlank(serviceAccount)) {
-                LOG.debug("authenticate(): service-account={} (from SPIFFE 
header {})", serviceAccount, spiffeHeader);
+            if (SpiffeIdUtil.isValidSpiffeId(spiffeId)) {
+                LOG.debug("authenticate(): spiffeId={} (from SPIFFE header 
{})", spiffeId, spiffeHeader);
 
-                return Result.authenticated(serviceAccount, AUTH_TYPE_SPIFFE);
+                return Result.authenticated(spiffeId, AUTH_TYPE_SPIFFE);
             } else if (StringUtils.isNotBlank(spiffeId)) {
                 LOG.warn("SPIFFE header '{}' value is not a well-formed SPIFFE 
ID", spiffeHeader);
             }
diff --git a/pdp/src/main/resources/ranger-pdp-default.xml 
b/pdp/src/main/resources/ranger-pdp-default.xml
index 9d67b8eab..6f3152f41 100644
--- a/pdp/src/main/resources/ranger-pdp-default.xml
+++ b/pdp/src/main/resources/ranger-pdp-default.xml
@@ -149,7 +149,7 @@
   <property>
     <name>ranger.pdp.authn.header.spiffe</name>
     <value></value>
-    <description>Comma-separated list of HTTP header name(s) carrying a SPIFFE 
ID (e.g. spiffe://cluster/ns/&lt;ns&gt;/sa/&lt;service-account&gt;). A single 
header name is also valid. The trailing service-account segment is used as the 
authenticated principal. When multiple headers are configured, the first one 
carrying a well-formed SPIFFE ID is used. Consulted only when the username 
header is absent.</description>
+    <description>Comma-separated list of HTTP header name(s) carrying a SPIFFE 
ID (e.g. spiffe://cluster/ns/&lt;ns&gt;/sa/&lt;service-account&gt;). A single 
header name is also valid. The full SPIFFE ID is used as the authenticated 
principal. When multiple headers are configured, the first one carrying a 
well-formed SPIFFE ID is used. Consulted only when the username header is 
absent.</description>
   </property>
 
   <!-- JWT bearer token authentication for incoming REST requests -->
diff --git 
a/pdp/src/test/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandlerTest.java
 
b/pdp/src/test/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandlerTest.java
index e122516bd..d48cb3e90 100644
--- 
a/pdp/src/test/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandlerTest.java
+++ 
b/pdp/src/test/java/org/apache/ranger/pdp/security/HttpHeaderAuthNHandlerTest.java
@@ -77,11 +77,12 @@ void testAuthenticate_usesSpiffeHeaderWhenUsernameAbsent() {
         handler.init(config);
 
         // Realistic production SPIFFE ID: DNS-style Kubernetes cluster trust 
domain + namespace/service-account.
-        HttpServletRequest     request = requestWithHeader("X-Spiffe-Id", 
"spiffe://prod-cluster.k8s.example.com/ns/ingress-nginx/sa/nginx-ingress");
-        PdpAuthNHandler.Result result  = handler.authenticate(request, null);
+        String                 spiffeId = 
"spiffe://prod-cluster.k8s.example.com/ns/ingress-nginx/sa/nginx-ingress";
+        HttpServletRequest     request  = requestWithHeader("X-Spiffe-Id", 
spiffeId);
+        PdpAuthNHandler.Result result   = handler.authenticate(request, null);
 
         assertEquals(PdpAuthNHandler.Result.Status.AUTHENTICATED, 
result.getStatus());
-        assertEquals("nginx-ingress", result.getUserName());
+        assertEquals(spiffeId, result.getUserName());
         assertEquals(HttpHeaderAuthNHandler.AUTH_TYPE_SPIFFE, 
result.getAuthType());
     }
 
@@ -118,13 +119,15 @@ void 
testAuthenticate_multipleSpiffeHeadersUsesFirstValid() {
 
         Map<String, String> headers = new HashMap<>();
 
+        String spiffeId = 
"spiffe://my-cluster/ns/service-namespace/sa/service-sa";
+
         headers.put("X-Spiffe-Id", "not-a-spiffe-id");
-        headers.put("X-Workload-Id", 
"spiffe://my-cluster/ns/service-namespace/sa/service-sa");
+        headers.put("X-Workload-Id", spiffeId);
 
         PdpAuthNHandler.Result result = 
handler.authenticate(requestWithHeaders(headers), null);
 
         assertEquals(PdpAuthNHandler.Result.Status.AUTHENTICATED, 
result.getStatus());
-        assertEquals("service-sa", result.getUserName());
+        assertEquals(spiffeId, result.getUserName());
         assertEquals(HttpHeaderAuthNHandler.AUTH_TYPE_SPIFFE, 
result.getAuthType());
     }
 
@@ -145,7 +148,7 @@ void testAuthenticate_malformedSpiffeHeaderSkips() {
     }
 
     @Test
-    void testAuthenticate_specValidButNonConformingSpiffeHeaderSkips() {
+    void 
testAuthenticate_specValidNonSpireLayoutSpiffeHeaderAuthenticatesFullId() {
         HttpHeaderAuthNHandler handler = new HttpHeaderAuthNHandler();
         Properties             config  = new Properties();
 
@@ -153,12 +156,15 @@ void 
testAuthenticate_specValidButNonConformingSpiffeHeaderSkips() {
 
         handler.init(config);
 
-        // Valid SPIFFE ID per the SPIFFE spec, but not in the expected 
/ns/<ns>/sa/<sa> layout.
-        HttpServletRequest     request = requestWithHeader("X-Spiffe-Id", 
"spiffe://example.org/workload/frontend");
-        PdpAuthNHandler.Result result  = handler.authenticate(request, null);
+        // Valid SPIFFE ID per the SPIFFE spec that does not use the SPIRE 
/ns/<ns>/sa/<sa> layout;
+        // the full SPIFFE ID is used as the authenticated principal.
+        String                 spiffeId = 
"spiffe://example.org/workload/frontend";
+        HttpServletRequest     request  = requestWithHeader("X-Spiffe-Id", 
spiffeId);
+        PdpAuthNHandler.Result result   = handler.authenticate(request, null);
 
-        assertEquals(PdpAuthNHandler.Result.Status.SKIP, result.getStatus());
-        assertNull(result.getUserName());
+        assertEquals(PdpAuthNHandler.Result.Status.AUTHENTICATED, 
result.getStatus());
+        assertEquals(spiffeId, result.getUserName());
+        assertEquals(HttpHeaderAuthNHandler.AUTH_TYPE_SPIFFE, 
result.getAuthType());
     }
 
     @Test
diff --git 
a/security-admin/src/main/java/org/apache/ranger/common/StringUtil.java 
b/security-admin/src/main/java/org/apache/ranger/common/StringUtil.java
index 870e35664..d924695f3 100644
--- a/security-admin/src/main/java/org/apache/ranger/common/StringUtil.java
+++ b/security-admin/src/main/java/org/apache/ranger/common/StringUtil.java
@@ -47,6 +47,10 @@ public class StringUtil implements Serializable {
        static final public String VALIDATION_IP_ADDRESS = "[\\d\\.\\%\\:]*";
        static final public String WILDCARD_ASTERISK = "*";
 
+       // When enabled, ':' is treated as a valid login-id character so that 
SPIFFE IDs (e.g. spiffe://spiffe.example.com/ns/sales/sa/trino) can be used as 
usernames.
+       public static final String PROP_SPIFFE_AS_USERNAME_ENABLED = 
"ranger.admin.spiffe.as.username.enabled";
+       public static final String VALIDATION_LOGINID_SPIFFE       = 
"^([A-Za-z0-9_]|[À-ſ])([a-z0-9,._\\-+/@=: ]|[À-ſ])+$";
+
        static HashMap<String, Pattern> compiledRegEx = new HashMap<String, 
Pattern>();
 
        String[] invalidNames = null;
@@ -267,4 +271,25 @@ public static boolean 
containsIgnoreCase(Collection<String> values, String str)
                return false;
        }
 
+       /**
+        * Whether SPIFFE IDs are allowed as usernames. Controlled by the
+        * {@link #PROP_SPIFFE_AS_USERNAME_ENABLED} config (disabled by 
default). When enabled,
+        * the login-id validation allows the ':' character used in SPIFFE IDs.
+        *
+        * @return true if using SPIFFE IDs as usernames is enabled
+        */
+       public static boolean isSpiffeAsUsernameEnabled() {
+               return 
PropertiesUtil.getBooleanProperty(PROP_SPIFFE_AS_USERNAME_ENABLED, false);
+       }
+
+       /**
+        * Returns the login-id validation regex in effect. When using SPIFFE 
IDs as usernames
+        * is enabled, ':' is treated as a valid character (see {@link 
#VALIDATION_LOGINID_SPIFFE}).
+        *
+        * @return the login-id validation regex
+        */
+       public static String getLoginIdValidationRegEx() {
+               return isSpiffeAsUsernameEnabled() ? VALIDATION_LOGINID_SPIFFE 
: VALIDATION_LOGINID;
+       }
+
 }
diff --git a/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java 
b/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java
index 4708b8638..402180af7 100644
--- a/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java
+++ b/security-admin/src/main/java/org/apache/ranger/rest/UserREST.java
@@ -285,6 +285,7 @@ public VXPortalUser getUserProfile(@Context 
HttpServletRequest request) {
                        Map<String, String> configProperties  = new HashMap<>();
                        Long inactivityTimeout = 
PropertiesUtil.getLongProperty("ranger.service.inactivity.timeout", 15*60);
                        configProperties.put("inactivityTimeout", 
Long.toString(inactivityTimeout));
+                       configProperties.put("spiffeAsUsernameEnabled", 
Boolean.toString(StringUtil.isSpiffeAsUsernameEnabled()));
                        VXPortalUser userProfile = 
userManager.getUserProfileByLoginId();
                        userProfile.setConfigProperties(configProperties);
                        return userProfile;
diff --git 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerHeaderPreAuthFilter.java
 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerHeaderPreAuthFilter.java
index 3cf984f7d..800bf92f5 100644
--- 
a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerHeaderPreAuthFilter.java
+++ 
b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerHeaderPreAuthFilter.java
@@ -110,8 +110,8 @@ public void doFilter(ServletRequest request, 
ServletResponse response, FilterCha
 
     /**
      * Resolves the principal from trusted headers. The username header (user 
identity) takes
-     * precedence; when it is absent, the SPIFFE header (service identity) is 
used and
-     * the trailing service-account segment of the SPIFFE ID becomes the 
principal.
+     * precedence; when it is absent, the SPIFFE header (service identity) is 
used and the
+     * full SPIFFE ID becomes the principal (SPIFFE IDs are used as usernames 
in Ranger).
      */
     private String resolvePrincipal(HttpServletRequest httpRequest) {
         String username = StringUtils.isNotBlank(userNameHeaderName) ? 
StringUtils.trimToNull(httpRequest.getHeader(userNameHeaderName)) : null;
@@ -121,13 +121,12 @@ private String resolvePrincipal(HttpServletRequest 
httpRequest) {
         }
 
         for (String spiffeHeaderName : spiffeHeaderNames) {
-            String spiffeId       = 
StringUtils.trimToNull(httpRequest.getHeader(spiffeHeaderName));
-            String serviceAccount = 
SpiffeIdUtil.extractServiceAccount(spiffeId);
+            String spiffeId = 
StringUtils.trimToNull(httpRequest.getHeader(spiffeHeaderName));
 
-            if (StringUtils.isNotBlank(serviceAccount)) {
-                LOG.debug("Resolved service-account '{}' from SPIFFE header 
'{}'", serviceAccount, spiffeHeaderName);
+            if (SpiffeIdUtil.isValidSpiffeId(spiffeId)) {
+                LOG.debug("Resolved SPIFFE ID '{}' from header '{}'", 
spiffeId, spiffeHeaderName);
 
-                return serviceAccount;
+                return spiffeId;
             } else if (StringUtils.isNotBlank(spiffeId)) {
                 LOG.warn("SPIFFE header '{}' value is not a well-formed SPIFFE 
ID", spiffeHeaderName);
             }
diff --git a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml 
b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
index 5493b69ba..1a9c40caf 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
@@ -326,12 +326,17 @@
        <property>
                <name>ranger.admin.authn.header.spiffe</name>
                <value></value>
-               <description>Comma-separated list of HTTP header name(s) 
carrying a SPIFFE ID (e.g. 
spiffe://cluster/ns/&lt;ns&gt;/sa/&lt;service-account&gt;). A single header 
name is also valid. The trailing service-account segment is used as the 
authenticated principal. When multiple headers are configured, the first one 
carrying a well-formed SPIFFE ID is used. Consulted only when the username 
header is absent.</description>
+               <description>Comma-separated list of HTTP header name(s) 
carrying a SPIFFE ID (e.g. 
spiffe://cluster/ns/&lt;ns&gt;/sa/&lt;service-account&gt;). A single header 
name is also valid. The full SPIFFE ID is used as the authenticated principal. 
When multiple headers are configured, the first one carrying a well-formed 
SPIFFE ID is used. Consulted only when the username header is 
absent.</description>
        </property>
        <property>
                <name>ranger.admin.authn.header.requestid</name>
                <value></value>
        </property>
+       <property>
+               <name>ranger.admin.spiffe.as.username.enabled</name>
+               <value>false</value>
+               <description>When enabled, ':' is accepted as a valid character 
in usernames so that SPIFFE IDs (e.g. 
spiffe://spiffe.example.com/ns/sales/sa/trino) can be used as service-account 
usernames. Disabled by default.</description>
+       </property>
        <!-- Trusted header auth properties end -->
        <!-- Kerberos Properties starts-->      
        <property>
diff --git a/security-admin/src/main/webapp/login.jsp 
b/security-admin/src/main/webapp/login.jsp
index b25cb78ad..8ac693c7e 100644
--- a/security-admin/src/main/webapp/login.jsp
+++ b/security-admin/src/main/webapp/login.jsp
@@ -14,6 +14,7 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
+<%@ page import="org.apache.ranger.common.StringUtil" %>
 <!DOCTYPE html>
 <!--[if lt IE 7]><html class="no-js lt-ie9 lt-ie8 lt-ie7"><![endif]-->
 <!--[if IE 7]><html class="no-js lt-ie9 lt-ie8"><![endif]-->
@@ -32,6 +33,9 @@
         <link rel="stylesheet" href="styles/font-awesome.min.css">
         <link href="styles/xa.css" media="all" rel="stylesheet" 
type="text/css" >
         <script src="libs/bower/jquery/js/core-lib.js" ></script>
+        <script type="text/javascript">
+            window.rangerSpiffeAsUsernameEnabled = <%= 
StringUtil.isSpiffeAsUsernameEnabled() %>;
+        </script>
         <script src="scripts/prelogin/XAPrelogin.js" ></script>
         <script type="text/javascript">
             $(document).ready(function() {
diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js 
b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
index b2cf730b6..d762c2173 100644
--- a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
+++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js
@@ -536,6 +536,8 @@ export const RegexValidation = {
   NAME_VALIDATION: {
     regexExpressionForName:
       /^([A-Za-z0-9_]|[\u00C0-\u017F])([a-z0-9,._\-+/@= ]|[\u00C0-\u017F])+$/i,
+    regexExpressionForUserName:
+      /^([A-Za-z0-9_]|[\u00C0-\u017F])([a-z0-9,._\-+/@=: 
]|[\u00C0-\u017F])+$/i,
     regexExpressionForFirstAndLastName:
       /^([A-Za-z0-9_]|[\u00C0-\u017F])([a-zA-Z0-9\s_. -@]|[\u00C0-\u017F])+$/i,
     regexforNameValidation: /^[a-zA-Z0-9_-][a-zA-Z0-9\s_-]{0,254}$/,
@@ -553,6 +555,15 @@ export const RegexValidation = {
         3. Name length should be greater than one.
       </>
     ),
+    userNameValidationMessage: (
+      <>
+        1. Name should be start with alphabet / numeric / underscore / non-us
+        characters.
+        <br />
+        2. Allowed special character ,._-+/@=: and space. <br />
+        3. Name length should be greater than one.
+      </>
+    ),
     regexforNameValidationMessage:
       "Name should not start with space, it should be less than 256 characters 
and special characters are not allowed(except _ - and space).",
     secondaryNameValidationMessage: (
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js 
b/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js
index ee7b010ea..46942cee6 100644
--- a/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js
+++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAMessages.js
@@ -36,6 +36,18 @@ export const RegexMessage = {
         </p>
       </>
     ),
+    userNameValidationMsgWithSpiffe: (
+      <>
+        <p className="pd-10 mb-0" style={{ fontSize: "small" }}>
+          1. User name should be start with alphabet / numeric / underscore /
+          non-us characters.
+          <br />
+          2. Allowed special character ,._-+/@=: and space.
+          <br />
+          3. Name length should be greater than one.
+        </p>
+      </>
+    ),
     passwordvalidationinfomessage:
       "Password should be minimum 8 characters ,atleast one uppercase letter, 
one lowercase letter and one numeric. For FIPS environment password should be 
minimum 14 characters with atleast one uppercase letter, one special 
characters, one lowercase letter and one numeric.",
     emailvalidationinfomessage: (
diff --git 
a/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
 
b/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
index 68de67e09..b8580e90f 100644
--- 
a/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
+++ 
b/security-admin/src/main/webapp/react-webapp/src/views/UserGroupRoleListing/users_details/UserForm.jsx
@@ -53,6 +53,10 @@ const INITIAL_STATE = {
   preventUnBlock: false
 };
 
+// SPIFFE IDs (e.g. spiffe://spiffe.example.com/ns/sales/sa/trino) contain 
':', which is only permitted in usernames when the admin config 
`ranger.admin.spiffe.as.username.enabled` is enabled.
+const isSpiffeAsUsernameEnabled = () =>
+  getUserProfile()?.configProperties?.spiffeAsUsernameEnabled === "true";
+
 const PromtDialog = (props) => {
   const { isDirtyField, isUnblock } = props;
   usePrompt("Are you sure you want to leave", isDirtyField && !isUnblock);
@@ -343,12 +347,14 @@ function UserForm(props) {
     if (!values.name) {
       errors.name = "Required";
     } else {
-      if (
-        !RegexValidation.NAME_VALIDATION.regexExpressionForName.test(
-          values.name
-        )
-      ) {
-        errors.name = RegexValidation.NAME_VALIDATION.nameValidationMessage;
+      const spiffeEnabled = isSpiffeAsUsernameEnabled();
+      const userNameRegex = spiffeEnabled
+        ? RegexValidation.NAME_VALIDATION.regexExpressionForUserName
+        : RegexValidation.NAME_VALIDATION.regexExpressionForName;
+      if (!userNameRegex.test(values.name)) {
+        errors.name = spiffeEnabled
+          ? RegexValidation.NAME_VALIDATION.userNameValidationMessage
+          : RegexValidation.NAME_VALIDATION.nameValidationMessage;
       }
     }
     if (!values.password && !isEditView) {
@@ -467,7 +473,11 @@ function UserForm(props) {
                       <InfoIcon
                         css="input-box-info-icon"
                         position="right"
-                        message={RegexMessage.MESSAGE.userNameValidationMsg}
+                        message={
+                          isSpiffeAsUsernameEnabled()
+                            ? 
RegexMessage.MESSAGE.userNameValidationMsgWithSpiffe
+                            : RegexMessage.MESSAGE.userNameValidationMsg
+                        }
                       />
 
                       {meta.error && meta.touched && (
diff --git a/security-admin/src/main/webapp/scripts/prelogin/XAPrelogin.js 
b/security-admin/src/main/webapp/scripts/prelogin/XAPrelogin.js
index 28aab6523..e80c25ca7 100644
--- a/security-admin/src/main/webapp/scripts/prelogin/XAPrelogin.js
+++ b/security-admin/src/main/webapp/scripts/prelogin/XAPrelogin.js
@@ -45,7 +45,11 @@ function doLogin() {
        }
 
        var regexEmail = 
/^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
-       var regexPlain = /^([A-Za-z0-9_]|[\u00C0-\u017F])([a-z0-9,._\-+/@= 
]|[\u00C0-\u017F])+$/i;
+       // ':' is only accepted in usernames (for SPIFFE IDs) when the admin 
config ranger.admin.spiffe.as.username.enabled is turned on.
+       var spiffeAsUsername = (typeof window !== 'undefined' && 
window.rangerSpiffeAsUsernameEnabled === true);
+       var regexPlain = spiffeAsUsername
+               ? /^([A-Za-z0-9_]|[\u00C0-\u017F])([a-z0-9,._\-+/@=: 
]|[\u00C0-\u017F])+$/i
+               : /^([A-Za-z0-9_]|[\u00C0-\u017F])([a-z0-9,._\-+/@= 
]|[\u00C0-\u017F])+$/i;
        
        if(!regexPlain.test(userName)){
                if(!regexEmail.test(userName)){
diff --git 
a/security-admin/src/test/java/org/apache/ranger/common/TestStringUtil.java 
b/security-admin/src/test/java/org/apache/ranger/common/TestStringUtil.java
index 5514b1440..20eb89ffd 100644
--- a/security-admin/src/test/java/org/apache/ranger/common/TestStringUtil.java
+++ b/security-admin/src/test/java/org/apache/ranger/common/TestStringUtil.java
@@ -179,7 +179,36 @@ public void testIsValidName(){
                boolean value = stringUtil.isValidName(name);
                Assert.assertTrue(value);
        }
-       
+
+       @Test
+       public void testDefaultLoginIdRegExRejectsColon() {
+               // SPIFFE ID support is disabled by default, so ':' must not be 
a valid login-id character.
+               String spiffeId = 
"spiffe://spiffe.example.com/ns/sales/sa/trino";
+               
Assert.assertFalse(stringUtil.validateString(StringUtil.VALIDATION_LOGINID, 
spiffeId));
+               
Assert.assertFalse(stringUtil.validateString(StringUtil.VALIDATION_LOGINID, 
"user:name"));
+       }
+
+       @Test
+       public void testSpiffeLoginIdRegExAllowsSpiffeId() {
+               // SPIFFE IDs are used as service-account usernames in k8s 
deployments.
+               String spiffeId = 
"spiffe://spiffe.example.com/ns/sales/sa/trino";
+               
Assert.assertTrue(stringUtil.validateString(StringUtil.VALIDATION_LOGINID_SPIFFE,
 spiffeId));
+               
Assert.assertTrue(stringUtil.validateString(StringUtil.VALIDATION_LOGINID_SPIFFE,
 "user:name"));
+       }
+
+       @Test
+       public void testLoginIdRegExRejectsInvalidCharacter() {
+               
Assert.assertFalse(stringUtil.validateString(StringUtil.VALIDATION_LOGINID, 
"user#name"));
+               
Assert.assertFalse(stringUtil.validateString(StringUtil.VALIDATION_LOGINID_SPIFFE,
 "user#name"));
+       }
+
+       @Test
+       public void testGetLoginIdValidationRegExDefaultsToNonSpiffe() {
+               // With the config disabled (default), the effective regex is 
the non-SPIFFE one.
+               Assert.assertFalse(StringUtil.isSpiffeAsUsernameEnabled());
+               Assert.assertEquals(StringUtil.VALIDATION_LOGINID, 
StringUtil.getLoginIdValidationRegEx());
+       }
+
        @Test
        public void testIsValidNameNull(){
                String name=null;
diff --git 
a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerHeaderPreAuthFilter.java
 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerHeaderPreAuthFilter.java
index 675970dd2..5103d836b 100644
--- 
a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerHeaderPreAuthFilter.java
+++ 
b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerHeaderPreAuthFilter.java
@@ -153,7 +153,7 @@ public void doFilter(ServletRequest req, ServletResponse 
res) {
     }
 
     @Test
-    public void 
testDoFilter_enabled_withSpiffeHeader_setsServiceAccountAuthentication() throws 
Exception {
+    public void 
testDoFilter_enabled_withSpiffeHeader_setsSpiffeIdAuthentication() throws 
Exception {
         
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED,
 "true");
         
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_USERNAME_HEADER_NAME,
 "X-Forwarded-User");
         
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_SPIFFE_HEADER_NAME,
 "X-Spiffe-Id");
@@ -164,14 +164,17 @@ public void 
testDoFilter_enabled_withSpiffeHeader_setsServiceAccountAuthenticati
         filter.userMgr = userMgr;
         filter.initialize();
 
-        
when(userMgr.getRolesByLoginId("nginx-ingress")).thenReturn(Collections.singletonList("ROLE_USER"));
+        // The full SPIFFE ID is the username in Ranger, so roles are looked 
up by the whole SPIFFE ID.
+        String spiffeId = 
"spiffe://prod-cluster.k8s.example.com/ns/ingress-nginx/sa/nginx-ingress";
+
+        
when(userMgr.getRolesByLoginId(spiffeId)).thenReturn(Collections.singletonList("ROLE_USER"));
 
         HttpServletRequest  request  = mock(HttpServletRequest.class);
         HttpServletResponse response = mock(HttpServletResponse.class);
 
         when(request.getHeader("X-Forwarded-User")).thenReturn(null);
         // Realistic production SPIFFE ID: DNS-style Kubernetes cluster trust 
domain + namespace/service-account.
-        
when(request.getHeader("X-Spiffe-Id")).thenReturn("spiffe://prod-cluster.k8s.example.com/ns/ingress-nginx/sa/nginx-ingress");
+        when(request.getHeader("X-Spiffe-Id")).thenReturn(spiffeId);
 
         FilterChain chain = new FilterChain() {
             @Override
@@ -182,7 +185,7 @@ public void doFilter(ServletRequest req, ServletResponse 
res) {
                 assertTrue(auth instanceof RangerAuthenticationToken);
                 RangerAuthenticationToken rangerAuth = 
(RangerAuthenticationToken) auth;
                 assertEquals(XXAuthSession.AUTH_TYPE_TRUSTED_PROXY, 
rangerAuth.getAuthType());
-                assertEquals("nginx-ingress", auth.getName());
+                assertEquals(spiffeId, auth.getName());
             }
         };
 
@@ -234,13 +237,15 @@ public void 
testDoFilter_enabled_multipleSpiffeHeaders_usesFirstValid() throws E
         filter.userMgr = userMgr;
         filter.initialize();
 
-        
when(userMgr.getRolesByLoginId("service-sa")).thenReturn(Collections.singletonList("ROLE_USER"));
+        String spiffeId = 
"spiffe://my-cluster/ns/service-namespace/sa/service-sa";
+
+        
when(userMgr.getRolesByLoginId(spiffeId)).thenReturn(Collections.singletonList("ROLE_USER"));
 
         HttpServletRequest  request  = mock(HttpServletRequest.class);
         HttpServletResponse response = mock(HttpServletResponse.class);
 
         when(request.getHeader("X-Spiffe-Id")).thenReturn("not-a-spiffe-id");
-        
when(request.getHeader("X-Workload-Id")).thenReturn("spiffe://my-cluster/ns/service-namespace/sa/service-sa");
+        when(request.getHeader("X-Workload-Id")).thenReturn(spiffeId);
 
         FilterChain chain = new FilterChain() {
             @Override
@@ -248,7 +253,7 @@ public void doFilter(ServletRequest req, ServletResponse 
res) {
                 org.springframework.security.core.Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
 
                 assertNotNull(auth);
-                assertEquals("service-sa", auth.getName());
+                assertEquals(spiffeId, auth.getName());
             }
         };
 
@@ -280,7 +285,7 @@ public void 
testDoFilter_enabled_malformedSpiffeHeader_passesThrough() throws Ex
     }
 
     @Test
-    public void 
testDoFilter_enabled_specValidButNonConformingSpiffeHeader_passesThrough() 
throws Exception {
+    public void 
testDoFilter_enabled_specValidNonSpireLayoutSpiffeHeader_authenticatesFullId() 
throws Exception {
         
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_HEADER_AUTH_ENABLED,
 "true");
         
PropertiesUtil.getPropertiesMap().put(RangerHeaderPreAuthFilter.PROP_SPIFFE_HEADER_NAME,
 "X-Spiffe-Id");
 
@@ -290,18 +295,31 @@ public void 
testDoFilter_enabled_specValidButNonConformingSpiffeHeader_passesThr
         filter.userMgr = userMgr;
         filter.initialize();
 
+        // Valid SPIFFE ID per the SPIFFE spec that does not use the SPIRE 
/ns/<ns>/sa/<sa> layout.
+        // The full SPIFFE ID is used as the authenticated principal.
+        String spiffeId = "spiffe://example.org/workload/frontend";
+
+        
when(userMgr.getRolesByLoginId(spiffeId)).thenReturn(Collections.singletonList("ROLE_USER"));
+
         HttpServletRequest  request  = mock(HttpServletRequest.class);
         HttpServletResponse response = mock(HttpServletResponse.class);
-        FilterChain         chain    = mock(FilterChain.class);
 
-        // Valid SPIFFE ID per the SPIFFE spec, but not in the expected 
/ns/<ns>/sa/<sa> layout.
-        
when(request.getHeader("X-Spiffe-Id")).thenReturn("spiffe://example.org/workload/frontend");
+        when(request.getHeader("X-Spiffe-Id")).thenReturn(spiffeId);
 
-        filter.doFilter(request, response, chain);
+        FilterChain chain = new FilterChain() {
+            @Override
+            public void doFilter(ServletRequest req, ServletResponse res) {
+                org.springframework.security.core.Authentication auth = 
SecurityContextHolder.getContext().getAuthentication();
 
-        verify(chain).doFilter(request, response);
-        verify(userMgr, never()).getRolesByLoginId(anyString());
-        assertNull(SecurityContextHolder.getContext().getAuthentication());
+                assertNotNull(auth);
+                assertTrue(auth instanceof RangerAuthenticationToken);
+                RangerAuthenticationToken rangerAuth = 
(RangerAuthenticationToken) auth;
+                assertEquals(XXAuthSession.AUTH_TYPE_TRUSTED_PROXY, 
rangerAuth.getAuthType());
+                assertEquals(spiffeId, auth.getName());
+            }
+        };
+
+        filter.doFilter(request, response, chain);
     }
 
     @Test

Reply via email to