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/<ns>/sa/<service-account>). 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/<ns>/sa/<service-account>). 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/<ns>/sa/<service-account>). 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/<ns>/sa/<service-account>). 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/<ns>/sa/<service-account>). 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/<ns>/sa/<service-account>). 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