From 125ee7f787ab3588b444be6a33cc7da7fb1a8501 Mon Sep 17 00:00:00 2001 From: ChinmayHegde24 Date: Tue, 19 May 2026 15:52:43 +0530 Subject: [PATCH 1/2] RANGER-5539: Added auth check for doAsUser parameter --- .../ranger/authz/handler/RangerAuth.java | 6 +- .../jwt/RangerDefaultJwtAuthHandler.java | 44 ++- .../handler/jwt/RangerJwtAuthHandler.java | 11 +- .../web/filter/RangerJwtAuthFilter.java | 37 +++ .../web/filter/TestRangerJwtAuthFilter.java | 176 ++++++++++++ .../web/filter/TestRangerJwtAuthWrapper.java | 252 ++++++++++++++++++ 6 files changed, 507 insertions(+), 19 deletions(-) create mode 100644 security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthFilter.java create mode 100644 security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java diff --git a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/RangerAuth.java b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/RangerAuth.java index b0757ae7d1..fd0e348c20 100644 --- a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/RangerAuth.java +++ b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/RangerAuth.java @@ -18,8 +18,6 @@ */ package org.apache.ranger.authz.handler; -import org.apache.hadoop.security.authentication.server.AuthenticationToken; - public class RangerAuth { public static enum AUTH_TYPE { JWT_JWKS("JWT-JWKS"); @@ -35,8 +33,8 @@ private AUTH_TYPE(String authType) { private AUTH_TYPE type; private boolean isAuthenticated; - public RangerAuth(final AuthenticationToken authenticationToken, AUTH_TYPE type) { - this.userName = authenticationToken.getName(); + public RangerAuth(final String userName, AUTH_TYPE type) { + this.userName = userName; this.isAuthenticated = true; this.type = type; } diff --git a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java index 09adca8048..26cff9847e 100644 --- a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java +++ b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java @@ -25,6 +25,8 @@ import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.security.authentication.server.AuthenticationToken; import org.apache.ranger.authz.handler.RangerAuth; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.nimbusds.jose.proc.JWSKeySelector; import com.nimbusds.jose.proc.SecurityContext; @@ -38,7 +40,7 @@ * */ public class RangerDefaultJwtAuthHandler extends RangerJwtAuthHandler { - + private static final Logger LOG = LoggerFactory.getLogger(RangerDefaultJwtAuthHandler.class); protected static final String AUTHORIZATION_HEADER = "Authorization"; protected static final String DO_AS_PARAMETER = "doAs"; @@ -60,12 +62,34 @@ public RangerAuth authenticate(HttpServletRequest httpServletRequest) { String jwtCookieStr = StringUtils.isBlank(jwtAuthHeaderStr) ? getJwtCookie(httpServletRequest) : null; String doAsUser = httpServletRequest.getParameter(DO_AS_PARAMETER); - AuthenticationToken authenticationToken = authenticate(jwtAuthHeaderStr, jwtCookieStr, doAsUser); - - if (authenticationToken != null) { - rangerAuth = new RangerAuth(authenticationToken, RangerAuth.AUTH_TYPE.JWT_JWKS); + // authenticate against the JWT first to get the real (token-verified) user + AuthenticationToken authToken = authenticate(jwtAuthHeaderStr, jwtCookieStr); + String realUser = authToken != null ? authToken.getName() : null; + + if (realUser != null) { + String effectiveUser = realUser; + + if (StringUtils.isNotBlank(doAsUser)) { + LOG.debug("RangerDefaultJwtAuthHandler.authenticate(): doAs=[{}] requested. isProxyEnabled=[{}]", doAsUser, isProxyEnabled()); + + if (!isProxyEnabled()) { + LOG.warn("doAs [{}] requested but trusted proxy is not enabled. Ignoring doAs, proceeding with real user [{}].", + doAsUser, effectiveUser); + } else { + LOG.debug("RangerDefaultJwtAuthHandler.authenticate(): Calling authorizeProxyUser: realUser=[{}], doAs=[{}], remoteAddr=[{}]", + realUser, doAsUser, httpServletRequest.getRemoteAddr()); + // Check: is realUser authorized to impersonate doAsUser + if (!authorizeProxyUser(realUser, doAsUser, httpServletRequest.getRemoteAddr())) { + LOG.warn("RangerDefaultJwtAuthHandler.authenticate(): doAs=[{}] not authorized for realUser=[{}]. Rejecting.", doAsUser, realUser); + return null; + } + //Checks passed → switch to doAs user + effectiveUser = doAsUser.trim(); + LOG.info("JWT doAs authorized: effectiveUser=[{}], realUser=[{}]", effectiveUser, realUser); + } + } + rangerAuth = new RangerAuth(effectiveUser, RangerAuth.AUTH_TYPE.JWT_JWKS); } - return rangerAuth; } @@ -94,4 +118,12 @@ public static String getJwtCookie(final HttpServletRequest httpServletRequest) { } return jwtCookieStr; } + + protected boolean isProxyEnabled() { + return false; + } + + protected boolean authorizeProxyUser(String realUser, String doAsUser, String remoteAddr) { + return false; + } } diff --git a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java index 30c8063666..13652e2564 100644 --- a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java +++ b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java @@ -101,7 +101,7 @@ public void initialize(final Properties config) throws Exception { } } - protected AuthenticationToken authenticate(final String jwtAuthHeader, final String jwtCookie, final String doAsUser) { + protected AuthenticationToken authenticate(final String jwtAuthHeader, final String jwtCookie) { if (LOG.isDebugEnabled()) { LOG.debug("===>>> RangerJwtAuthHandler.authenticate()"); } @@ -115,17 +115,10 @@ protected AuthenticationToken authenticate(final String jwtAuthHeader, final Str final SignedJWT jwtToken = SignedJWT.parse(serializedJWT); boolean valid = validateToken(jwtToken); if (valid) { - String userName; - - if (StringUtils.isNotBlank(doAsUser)) { - userName = doAsUser.trim(); - } else { - userName = jwtToken.getJWTClaimsSet().getSubject(); - } + String userName = jwtToken.getJWTClaimsSet().getSubject(); if (LOG.isDebugEnabled()) { LOG.debug("RangerJwtAuthHandler.authenticate(): Issuing AuthenticationToken for user: [{}]", userName); - LOG.debug("RangerJwtAuthHandler.authenticate(): Authentication successful for user [{}] and doAs user is [{}]", jwtToken.getJWTClaimsSet().getSubject(), doAsUser); } token = new AuthenticationToken(userName, userName, TYPE); } else { diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java index 5a1251adee..dedbddfe1e 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java +++ b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java @@ -32,6 +32,10 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authorize.AuthorizationException; +import org.apache.hadoop.security.authorize.ProxyUsers; import org.apache.ranger.authz.handler.RangerAuth; import org.apache.ranger.authz.handler.jwt.RangerDefaultJwtAuthHandler; import org.apache.ranger.authz.handler.jwt.RangerJwtAuthHandler; @@ -77,6 +81,9 @@ public void initialize() { config.setProperty(RangerJwtAuthHandler.KEY_JWT_AUDIENCES, PropertiesUtil.getProperty(RangerSSOAuthenticationFilter.JWT_AUDIENCES, "")); super.initialize(config); + + Configuration conf = getProxyuserConfiguration(); + ProxyUsers.refreshSuperUserGroupsConfiguration(conf, "ranger.proxyuser."); } catch (Exception e) { LOG.error("Failed to initialize Ranger Admin JWT Auth Filter.", e); } @@ -121,6 +128,36 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } } + @Override + protected boolean isProxyEnabled() { + return PropertiesUtil.getBooleanProperty("ranger.authentication.allow.trustedproxy", false); + } + + @Override + protected boolean authorizeProxyUser(String realUser, String doAsUser, String remoteAddr) { + try { + UserGroupInformation ugi = UserGroupInformation.createRemoteUser(realUser); + ugi = UserGroupInformation.createProxyUser(doAsUser, ugi); + ProxyUsers.authorize(ugi, remoteAddr); + LOG.debug("RangerJwtAuthFilter.authorizeProxyUser(): ProxyUsers.authorize SUCCEEDED for realUser=[{}], doAs=[{}]", + realUser, doAsUser); + return true; + } catch (AuthorizationException ex) { + LOG.warn("JWT ProxyUsers.authorize failed for doAs=[{}], realUser=[{}]: {}", doAsUser, realUser, ex.getMessage()); + return false; + } + } + + private Configuration getProxyuserConfiguration() { + Configuration conf = new Configuration(false); + PropertiesUtil.getPropertiesMap().forEach((k, v) -> { + if (k.startsWith("ranger.proxyuser.")) { + conf.set(k, v); + } + }); + return conf; + } + @Override public void destroy() { // Empty method diff --git a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthFilter.java b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthFilter.java new file mode 100644 index 0000000000..66ae91d08f --- /dev/null +++ b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthFilter.java @@ -0,0 +1,176 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ranger.security.web.filter; + +import org.apache.hadoop.conf.Configuration; +import org.apache.ranger.authz.handler.RangerAuth; +import org.apache.ranger.common.PropertiesUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.User; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; + +/** + * @generated by Cursor + * @description + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestRangerJwtAuthFilter { + @AfterEach + public void cleanup() { + SecurityContextHolder.clearContext(); + } + + @Test + public void testInit_noopDoesNotThrow() { + RangerJwtAuthFilter filter = new RangerJwtAuthFilter(); + FilterConfig cfg = Mockito.mock(FilterConfig.class); + assertDoesNotThrow(() -> filter.init(cfg)); + } + + @Test + public void testDestroy_noopDoesNotThrow() { + RangerJwtAuthFilter filter = new RangerJwtAuthFilter(); + assertDoesNotThrow(() -> filter.destroy()); + } + + @Test + public void testInitialize_doesNotThrow() { + RangerJwtAuthFilter filter = new RangerJwtAuthFilter(); + assertDoesNotThrow(() -> filter.initialize()); + } + + @Test + public void testDoFilter_setsAuthenticationWhenAuthenticateSucceeds() throws ServletException, IOException { + RangerJwtAuthFilter filter = Mockito.spy(new RangerJwtAuthFilter()); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + ServletResponse res = Mockito.mock(ServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + RangerAuth rangerAuth = new RangerAuth("alice", RangerAuth.AUTH_TYPE.JWT_JWKS); + + doReturn(rangerAuth).when(filter).authenticate(any(HttpServletRequest.class)); + + filter.doFilter(req, res, chain); + + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + assertNotNull(auth); + assertTrue(auth.getPrincipal() instanceof User); + User user = (User) auth.getPrincipal(); + assertEquals("alice", user.getUsername()); + + Collection authorities = auth.getAuthorities(); + assertNotNull(authorities); + assertFalse(authorities.isEmpty()); + } + + @Test + public void testDoFilter_leavesAuthenticationNullWhenAuthenticateReturnsNull() + throws ServletException, IOException { + RangerJwtAuthFilter filter = Mockito.spy(new RangerJwtAuthFilter()); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + ServletResponse res = Mockito.mock(ServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + doReturn(null).when(filter).authenticate(any(HttpServletRequest.class)); + + filter.doFilter(req, res, chain); + + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + void testIsProxyEnabled_defaultFalse() { + PropertiesUtil.getPropertiesMap().remove("ranger.authentication.allow.trustedproxy"); + RangerJwtAuthFilter filter = new RangerJwtAuthFilter(); + assertFalse(filter.isProxyEnabled()); + } + + @Test + void testIsProxyEnabled_trueWhenConfigured() { + PropertiesUtil.getPropertiesMap().put("ranger.authentication.allow.trustedproxy", "true"); + RangerJwtAuthFilter filter = new RangerJwtAuthFilter(); + assertTrue(filter.isProxyEnabled()); + PropertiesUtil.getPropertiesMap().remove("ranger.authentication.allow.trustedproxy"); + } + + @Test + void testAuthorizeProxyUser_returnsFalseWhenNoProxyConfigLoaded() { + RangerJwtAuthFilter filter = new RangerJwtAuthFilter(); + // no proxyuser config loaded into ProxyUsers -> should fail safely + assertFalse(filter.authorizeProxyUser("knoxui", "admin", "10.0.0.1")); + } + + @Test + void testGetProxyuserConfiguration_copiesOnlyProxyuserKeys() throws Exception { + // Arrange: put both proxyuser keys and non-proxyuser keys + PropertiesUtil.getPropertiesMap().put("ranger.proxyuser.knoxui.hosts", "*"); + PropertiesUtil.getPropertiesMap().put("ranger.proxyuser.knoxui.groups", "*"); + PropertiesUtil.getPropertiesMap().put("ranger.some.other.key", "shouldNotBeCopied"); + + RangerJwtAuthFilter filter = new RangerJwtAuthFilter(); + + // Call private getProxyuserConfiguration() via reflection + Method m = RangerJwtAuthFilter.class.getDeclaredMethod("getProxyuserConfiguration"); + m.setAccessible(true); + + Configuration conf = (Configuration) m.invoke(filter); + + // Assert: proxyuser keys copied + assertEquals("*", conf.get("ranger.proxyuser.knoxui.hosts")); + assertEquals("*", conf.get("ranger.proxyuser.knoxui.groups")); + + // Assert: non-proxyuser keys NOT copied + assertNull(conf.get("ranger.some.other.key")); + + // Cleanup + PropertiesUtil.getPropertiesMap().remove("ranger.proxyuser.knoxui.hosts"); + PropertiesUtil.getPropertiesMap().remove("ranger.proxyuser.knoxui.groups"); + PropertiesUtil.getPropertiesMap().remove("ranger.some.other.key"); + } +} \ No newline at end of file diff --git a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java new file mode 100644 index 0000000000..5ff900ce98 --- /dev/null +++ b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ranger.security.web.filter; + +import org.apache.ranger.common.PropertiesUtil; +import org.apache.ranger.common.UserSessionBase; +import org.apache.ranger.security.context.RangerContextHolder; +import org.apache.ranger.security.context.RangerSecurityContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @generated by Cursor + * @description + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestRangerJwtAuthWrapper { + @AfterEach + public void tearDown() { + SecurityContextHolder.clearContext(); + RangerContextHolder.resetSecurityContext(); + RangerContextHolder.resetOpContext(); + PropertiesUtil.getPropertiesMap().remove("ranger.sso.enabled"); + } + + @Test + public void testDoFilter_redirectsToLoginForBrowserWhenNotAuthenticated() throws IOException, ServletException { + System.setProperty("ranger.default.browser-useragents", "Mozilla,Chrome,Opera"); + SecurityContextHolder.clearContext(); + PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); + + RangerJwtAuthWrapper wrapper = Mockito.spy(new RangerJwtAuthWrapper()); + wrapper.initialize(); + RangerJwtAuthFilter jwtFilter = Mockito.mock(RangerJwtAuthFilter.class); + wrapper.rangerJwtAuthFilter = jwtFilter; + + // Set context with SSO disabled + RangerSecurityContext context = new RangerSecurityContext(); + UserSessionBase sessionBase = new UserSessionBase(); + sessionBase.setSSOEnabled(Boolean.FALSE); + context.setUserSession(sessionBase); + RangerContextHolder.setSecurityContext(context); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + when(req.getHeader("User-Agent")).thenReturn("Mozilla/5.0"); + doNothing().when(res).sendRedirect(any(String.class)); + + // Ensure canAuthenticateRequest evaluates true by stubbing Authorization header + when(req.getHeader("Authorization")).thenReturn("Bearer sometoken"); + + wrapper.doFilter(req, res, chain); + + verify(jwtFilter, times(1)).doFilter(any(ServletRequest.class), any(ServletResponse.class), + any(FilterChain.class)); + verify(res, atLeastOnce()).sendRedirect(any(String.class)); + verify(chain, times(1)).doFilter(req, res); + } + + @Test + public void testDoFilter_skipsJwtWhenSsoEnabled() throws IOException, ServletException { + System.setProperty("ranger.default.browser-useragents", "Mozilla,Chrome,Opera"); + + RangerJwtAuthWrapper wrapper = Mockito.spy(new RangerJwtAuthWrapper()); + wrapper.initialize(); + RangerJwtAuthFilter jwtFilter = Mockito.mock(RangerJwtAuthFilter.class); + wrapper.rangerJwtAuthFilter = jwtFilter; + + // SSO enabled in context + RangerSecurityContext context = new RangerSecurityContext(); + UserSessionBase sessionBase = new UserSessionBase(); + sessionBase.setSSOEnabled(Boolean.TRUE); + context.setUserSession(sessionBase); + RangerContextHolder.setSecurityContext(context); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + wrapper.doFilter(req, res, chain); + + verify(jwtFilter, never()).doFilter(any(ServletRequest.class), any(ServletResponse.class), any(FilterChain.class)); + verify(chain, times(1)).doFilter(req, res); + } + + @Test + void testDoFilter_invokesJwtFilter_whenBearerHeaderPresent() throws Exception { + RangerContextHolder.resetSecurityContext(); + SecurityContextHolder.clearContext(); + PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + Mockito.when(req.getHeader("Authorization")).thenReturn("Bearer token"); + + RangerJwtAuthFilter jwt = Mockito.mock(RangerJwtAuthFilter.class); + + RangerJwtAuthWrapper wrapper = new RangerJwtAuthWrapper(); + setField(wrapper, "rangerJwtAuthFilter", jwt); + + wrapper.doFilter(req, res, chain); + + verify(jwt, times(1)).doFilter(req, res, chain); + verify(chain, times(1)).doFilter(req, res); + } + + @Test + void testDoFilter_skipsJwt_whenAlreadyAuthenticated_evenIfBearerHeaderPresent() throws Exception { + PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); + + // mark request authenticated + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken( + "kafka", + "", + Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")))); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + RangerJwtAuthFilter jwt = Mockito.mock(RangerJwtAuthFilter.class); + RangerJwtAuthWrapper wrapper = new RangerJwtAuthWrapper(); + setField(wrapper, "rangerJwtAuthFilter", jwt); + + wrapper.doFilter(req, res, chain); + + verify(jwt, never()).doFilter(req, res, chain); + verify(chain, times(1)).doFilter(req, res); + } + + @Test + void testDoFilter_skipsJwtFilter_whenNoBearerAndNoCookie() throws Exception { + PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + // no bearer header, no cookies + Mockito.when(req.getHeader("Authorization")).thenReturn(null); + Mockito.when(req.getCookies()).thenReturn(null); + + RangerJwtAuthFilter jwt = Mockito.mock(RangerJwtAuthFilter.class); + RangerJwtAuthWrapper wrapper = new RangerJwtAuthWrapper(); + setField(wrapper, "rangerJwtAuthFilter", jwt); + + wrapper.doFilter(req, res, chain); + + verify(jwt, never()).doFilter(req, res, chain); + verify(chain, times(1)).doFilter(req, res); + } + + @Test + void testDoFilter_invokesJwtFilter_whenJwtCookiePresent() throws Exception { + PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + Mockito.when(req.getHeader("Authorization")).thenReturn(null); + Mockito.when(req.getCookies()).thenReturn(new Cookie[] {new Cookie("hadoop-jwt", "abc")}); + RangerJwtAuthFilter jwt = Mockito.mock(RangerJwtAuthFilter.class); + RangerJwtAuthWrapper wrapper = new RangerJwtAuthWrapper(); + setField(wrapper, "rangerJwtAuthFilter", jwt); + + wrapper.doFilter(req, res, chain); + + verify(jwt, times(1)).doFilter(req, res, chain); + verify(chain, times(1)).doFilter(req, res); + } + + @Test + void testDoFilter_redirectsToLogin_whenJwtAttemptedButUnauthenticated_andBrowserAgent() throws Exception { + PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); + System.setProperty("ranger.default.browser-useragents", "Mozilla"); + + HttpServletRequest req = Mockito.mock(HttpServletRequest.class); + HttpServletResponse res = Mockito.mock(HttpServletResponse.class); + FilterChain chain = Mockito.mock(FilterChain.class); + + Mockito.when(req.getHeader("Authorization")).thenReturn("Bearer token"); + Mockito.when(req.getHeader("User-Agent")).thenReturn("Mozilla/5.0"); + + RangerJwtAuthFilter jwt = Mockito.mock(RangerJwtAuthFilter.class); + Mockito.doNothing().when(jwt).doFilter(req, res, chain); + + RangerJwtAuthWrapper wrapper = new RangerJwtAuthWrapper(); + wrapper.initialize(); // loads browser agents from properties/system property + setField(wrapper, "rangerJwtAuthFilter", jwt); + + wrapper.doFilter(req, res, chain); + + verify(res, times(1)).sendRedirect("/login.jsp"); + verify(chain, times(1)).doFilter(req, res); + } + + private static void setField(Object target, String fieldName, Object value) throws Exception { + Field f = target.getClass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(target, value); + } +} \ No newline at end of file From 051fdabff7d767b74b506df8fd52a9829a9647ce Mon Sep 17 00:00:00 2001 From: ChinmayHegde24 Date: Tue, 19 May 2026 16:32:31 +0530 Subject: [PATCH 2/2] RANGER-5588: Removed jwt cookie support from JWT files --- pdp/conf.dist/ranger-pdp-site.xml | 6 ---- .../apache/ranger/pdp/RangerPdpServer.java | 1 - .../ranger/pdp/config/RangerPdpConfig.java | 4 --- .../ranger/pdp/config/RangerPdpConstants.java | 1 - .../ranger/pdp/security/JwtAuthNHandler.java | 6 ++-- pdp/src/main/resources/ranger-pdp-default.xml | 6 ---- .../jwt/RangerDefaultJwtAuthHandler.java | 21 ++----------- .../handler/jwt/RangerJwtAuthHandler.java | 30 ++++--------------- .../web/filter/RangerJwtAuthFilter.java | 2 -- .../web/filter/TestRangerJwtAuthWrapper.java | 26 ++-------------- 10 files changed, 12 insertions(+), 91 deletions(-) diff --git a/pdp/conf.dist/ranger-pdp-site.xml b/pdp/conf.dist/ranger-pdp-site.xml index ddf2ff9346..758573e70e 100644 --- a/pdp/conf.dist/ranger-pdp-site.xml +++ b/pdp/conf.dist/ranger-pdp-site.xml @@ -163,12 +163,6 @@ PEM-encoded public key for verifying JWT signatures. - - ranger.pdp.authn.jwt.cookie.name - hadoop-jwt - Cookie name from which a JWT bearer token may be read. - - ranger.pdp.authn.jwt.audiences diff --git a/pdp/src/main/java/org/apache/ranger/pdp/RangerPdpServer.java b/pdp/src/main/java/org/apache/ranger/pdp/RangerPdpServer.java index a3027184fe..dc74e0716a 100644 --- a/pdp/src/main/java/org/apache/ranger/pdp/RangerPdpServer.java +++ b/pdp/src/main/java/org/apache/ranger/pdp/RangerPdpServer.java @@ -230,7 +230,6 @@ private void addAuthFilter(Context ctx) { authFilterDef.addInitParameter(RangerPdpConstants.PROP_AUTHN_JWT_ENABLED, Boolean.toString(config.isJwtAuthnEnabled())); authFilterDef.addInitParameter(RangerPdpConstants.PROP_AUTHN_JWT_PROVIDER_URL, config.getJwtProviderUrl()); authFilterDef.addInitParameter(RangerPdpConstants.PROP_AUTHN_JWT_PUBLIC_KEY, config.getJwtPublicKey()); - authFilterDef.addInitParameter(RangerPdpConstants.PROP_AUTHN_JWT_COOKIE_NAME, config.getJwtCookieName()); authFilterDef.addInitParameter(RangerPdpConstants.PROP_AUTHN_JWT_AUDIENCES, config.getJwtAudiences()); // Kerberos / SPNEGO diff --git a/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConfig.java b/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConfig.java index 11aea39ea8..68b8e687b3 100644 --- a/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConfig.java +++ b/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConfig.java @@ -156,10 +156,6 @@ public String getJwtPublicKey() { return get(RangerPdpConstants.PROP_AUTHN_JWT_PUBLIC_KEY, ""); } - public String getJwtCookieName() { - return get(RangerPdpConstants.PROP_AUTHN_JWT_COOKIE_NAME, "hadoop-jwt"); - } - public String getJwtAudiences() { return get(RangerPdpConstants.PROP_AUTHN_JWT_AUDIENCES, ""); } diff --git a/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConstants.java b/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConstants.java index c1a4313514..c195185408 100644 --- a/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConstants.java +++ b/pdp/src/main/java/org/apache/ranger/pdp/config/RangerPdpConstants.java @@ -71,7 +71,6 @@ private RangerPdpConstants() { public static final String PROP_AUTHN_JWT_ENABLED = PROP_AUTHN_JWT_PREFIX + "enabled"; public static final String PROP_AUTHN_JWT_PROVIDER_URL = PROP_AUTHN_JWT_PREFIX + "provider.url"; public static final String PROP_AUTHN_JWT_PUBLIC_KEY = PROP_AUTHN_JWT_PREFIX + "public.key"; - public static final String PROP_AUTHN_JWT_COOKIE_NAME = PROP_AUTHN_JWT_PREFIX + "cookie.name"; public static final String PROP_AUTHN_JWT_AUDIENCES = PROP_AUTHN_JWT_PREFIX + "audiences"; // Kerberos/SPNEGO auth diff --git a/pdp/src/main/java/org/apache/ranger/pdp/security/JwtAuthNHandler.java b/pdp/src/main/java/org/apache/ranger/pdp/security/JwtAuthNHandler.java index 4e45c00311..6c9a33840a 100644 --- a/pdp/src/main/java/org/apache/ranger/pdp/security/JwtAuthNHandler.java +++ b/pdp/src/main/java/org/apache/ranger/pdp/security/JwtAuthNHandler.java @@ -33,15 +33,14 @@ /** * Authenticates requests using a JWT bearer token. * - *

Checks for the token in the {@code Authorization: Bearer } header first, - * then in the configured JWT cookie. Delegates signature verification and expiry/audience + *

Checks for the token in the {@code Authorization: Bearer } header + * Delegates signature verification and expiry/audience * checks to {@link RangerDefaultJwtAuthHandler} from the {@code ranger-authn} module. * *

Configuration keys (all prefixed with {@code ranger.pdp.authn.jwt.}): *

    *
  • {@code provider.url} – JWKS endpoint URL (optional if public key is set) *
  • {@code public.key} – PEM-encoded public key (optional if provider URL is set) - *
  • {@code cookie.name} – JWT cookie name (default: {@code hadoop-jwt}) *
  • {@code audiences} – comma-separated list of accepted audiences (optional) *
*/ @@ -58,7 +57,6 @@ public void init(Properties config) throws Exception { copyIfPresent(config, RangerPdpConstants.PROP_AUTHN_JWT_PROVIDER_URL, jwtConfig, RangerDefaultJwtAuthHandler.KEY_PROVIDER_URL); copyIfPresent(config, RangerPdpConstants.PROP_AUTHN_JWT_PUBLIC_KEY, jwtConfig, RangerDefaultJwtAuthHandler.KEY_JWT_PUBLIC_KEY); - copyIfPresent(config, RangerPdpConstants.PROP_AUTHN_JWT_COOKIE_NAME, jwtConfig, RangerDefaultJwtAuthHandler.KEY_JWT_COOKIE_NAME); copyIfPresent(config, RangerPdpConstants.PROP_AUTHN_JWT_AUDIENCES, jwtConfig, RangerDefaultJwtAuthHandler.KEY_JWT_AUDIENCES); delegate = new RangerDefaultJwtAuthHandler(); diff --git a/pdp/src/main/resources/ranger-pdp-default.xml b/pdp/src/main/resources/ranger-pdp-default.xml index ddf2ff9346..758573e70e 100644 --- a/pdp/src/main/resources/ranger-pdp-default.xml +++ b/pdp/src/main/resources/ranger-pdp-default.xml @@ -163,12 +163,6 @@ PEM-encoded public key for verifying JWT signatures.
- - ranger.pdp.authn.jwt.cookie.name - hadoop-jwt - Cookie name from which a JWT bearer token may be read. - - ranger.pdp.authn.jwt.audiences diff --git a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java index 26cff9847e..8fd6c2af0f 100644 --- a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java +++ b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerDefaultJwtAuthHandler.java @@ -19,7 +19,6 @@ package org.apache.ranger.authz.handler.jwt; import javax.servlet.ServletRequest; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -59,11 +58,10 @@ public ConfigurableJWTProcessor getJwtProcessor(JWSKeySelector< public RangerAuth authenticate(HttpServletRequest httpServletRequest) { RangerAuth rangerAuth = null; String jwtAuthHeaderStr = getJwtAuthHeader(httpServletRequest); - String jwtCookieStr = StringUtils.isBlank(jwtAuthHeaderStr) ? getJwtCookie(httpServletRequest) : null; String doAsUser = httpServletRequest.getParameter(DO_AS_PARAMETER); // authenticate against the JWT first to get the real (token-verified) user - AuthenticationToken authToken = authenticate(jwtAuthHeaderStr, jwtCookieStr); + AuthenticationToken authToken = authenticate(jwtAuthHeaderStr); String realUser = authToken != null ? authToken.getName() : null; if (realUser != null) { @@ -96,29 +94,14 @@ public RangerAuth authenticate(HttpServletRequest httpServletRequest) { public static boolean canAuthenticateRequest(final ServletRequest request) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String jwtAuthHeaderStr = getJwtAuthHeader(httpServletRequest); - String jwtCookieStr = StringUtils.isBlank(jwtAuthHeaderStr) ? getJwtCookie(httpServletRequest) : null; - return shouldProceedAuth(jwtAuthHeaderStr, jwtCookieStr); + return shouldProceedAuth(jwtAuthHeaderStr); } public static String getJwtAuthHeader(final HttpServletRequest httpServletRequest) { return httpServletRequest.getHeader(AUTHORIZATION_HEADER); } - public static String getJwtCookie(final HttpServletRequest httpServletRequest) { - String jwtCookieStr = null; - Cookie[] cookies = httpServletRequest.getCookies(); - if (cookies != null) { - for (Cookie cookie : cookies) { - if (cookieName.equals(cookie.getName())) { - jwtCookieStr = cookie.getName() + "=" + cookie.getValue(); - break; - } - } - } - return jwtCookieStr; - } - protected boolean isProxyEnabled() { return false; } diff --git a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java index 13652e2564..5480fcd79a 100644 --- a/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java +++ b/ranger-authn/src/main/java/org/apache/ranger/authz/handler/jwt/RangerJwtAuthHandler.java @@ -53,15 +53,12 @@ public abstract class RangerJwtAuthHandler implements RangerAuthHandler { public static final String TYPE = "ranger-jwt"; // Constant that identifies the authentication mechanism. public static final String KEY_PROVIDER_URL = "jwks.provider-url"; // JWKS provider URL public static final String KEY_JWT_PUBLIC_KEY = "jwt.public-key"; // JWT token provider public key - public static final String KEY_JWT_COOKIE_NAME = "jwt.cookie-name"; // JWT cookie name public static final String KEY_JWT_AUDIENCES = "jwt.audiences"; public static final String JWT_AUTHZ_PREFIX = "Bearer "; protected List audiences = null; protected JWKSource keySource = null; - protected static String cookieName = "hadoop-jwt"; - @Override public void initialize(final Properties config) throws Exception { if (LOG.isDebugEnabled()) { @@ -84,12 +81,6 @@ public void initialize(final Properties config) throws Exception { throw new Exception("RangerJwtAuthHandler: Mandatory configs ('jwks.provider-url' & 'jwt.public-key') are missing, must provide atleast one."); } - // setup custom cookie name if configured - String customCookieName = config.getProperty(KEY_JWT_COOKIE_NAME); - if (customCookieName != null) { - cookieName = customCookieName; - } - // setup audiences if configured String audiencesStr = config.getProperty(KEY_JWT_AUDIENCES); if (StringUtils.isNotBlank(audiencesStr)) { @@ -101,14 +92,14 @@ public void initialize(final Properties config) throws Exception { } } - protected AuthenticationToken authenticate(final String jwtAuthHeader, final String jwtCookie) { + protected AuthenticationToken authenticate(final String jwtAuthHeader) { if (LOG.isDebugEnabled()) { LOG.debug("===>>> RangerJwtAuthHandler.authenticate()"); } AuthenticationToken token = null; - if (shouldProceedAuth(jwtAuthHeader, jwtCookie)) { - String serializedJWT = getJWT(jwtAuthHeader, jwtCookie); + if (shouldProceedAuth(jwtAuthHeader)) { + String serializedJWT = getJWT(jwtAuthHeader); if (StringUtils.isNotBlank(serializedJWT)) { try { @@ -139,22 +130,13 @@ protected AuthenticationToken authenticate(final String jwtAuthHeader, final Str return token; } - protected String getJWT(final String jwtAuthHeader, final String jwtCookie) { + protected String getJWT(final String jwtAuthHeader) { String serializedJWT = null; // try to fetch from AUTH header if (StringUtils.isNotBlank(jwtAuthHeader) && jwtAuthHeader.startsWith(JWT_AUTHZ_PREFIX)) { serializedJWT = jwtAuthHeader.substring(JWT_AUTHZ_PREFIX.length()); } - - // if not found in AUTH header, try to fetch from cookie - if (StringUtils.isBlank(serializedJWT) && StringUtils.isNotBlank(jwtCookie)) { - String[] cookie = jwtCookie.split("="); - if (cookieName.equals(cookie[0])) { - serializedJWT = cookie[1]; - } - } - return serializedJWT; } @@ -310,7 +292,7 @@ protected boolean validateExpiration(final SignedJWT jwtToken) { return valid; } - public static boolean shouldProceedAuth(final String authHeader, final String jwtCookie) { - return (StringUtils.isNotBlank(authHeader) && authHeader.startsWith(JWT_AUTHZ_PREFIX)) || (StringUtils.isNotBlank(jwtCookie) && jwtCookie.startsWith(cookieName)); + public static boolean shouldProceedAuth(final String authHeader) { + return (StringUtils.isNotBlank(authHeader) && authHeader.startsWith(JWT_AUTHZ_PREFIX)); } } diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java index dedbddfe1e..0f985972db 100644 --- a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java +++ b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerJwtAuthFilter.java @@ -76,8 +76,6 @@ public void initialize() { config.setProperty(RangerJwtAuthHandler.KEY_PROVIDER_URL, PropertiesUtil.getProperty(RangerSSOAuthenticationFilter.JWT_AUTH_PROVIDER_URL)); config.setProperty(RangerJwtAuthHandler.KEY_JWT_PUBLIC_KEY, PropertiesUtil.getProperty(RangerSSOAuthenticationFilter.JWT_PUBLIC_KEY, "")); - config.setProperty(RangerJwtAuthHandler.KEY_JWT_COOKIE_NAME, - PropertiesUtil.getProperty(RangerSSOAuthenticationFilter.JWT_COOKIE_NAME, RangerSSOAuthenticationFilter.JWT_COOKIE_NAME_DEFAULT)); config.setProperty(RangerJwtAuthHandler.KEY_JWT_AUDIENCES, PropertiesUtil.getProperty(RangerSSOAuthenticationFilter.JWT_AUDIENCES, "")); super.initialize(config); diff --git a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java index 5ff900ce98..8a6e696a50 100644 --- a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java +++ b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerJwtAuthWrapper.java @@ -37,7 +37,6 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -178,16 +177,15 @@ void testDoFilter_skipsJwt_whenAlreadyAuthenticated_evenIfBearerHeaderPresent() } @Test - void testDoFilter_skipsJwtFilter_whenNoBearerAndNoCookie() throws Exception { + void testDoFilter_skipsJwtFilter_whenNoBearer() throws Exception { PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); HttpServletRequest req = Mockito.mock(HttpServletRequest.class); HttpServletResponse res = Mockito.mock(HttpServletResponse.class); FilterChain chain = Mockito.mock(FilterChain.class); - // no bearer header, no cookies + // no bearer header Mockito.when(req.getHeader("Authorization")).thenReturn(null); - Mockito.when(req.getCookies()).thenReturn(null); RangerJwtAuthFilter jwt = Mockito.mock(RangerJwtAuthFilter.class); RangerJwtAuthWrapper wrapper = new RangerJwtAuthWrapper(); @@ -199,26 +197,6 @@ void testDoFilter_skipsJwtFilter_whenNoBearerAndNoCookie() throws Exception { verify(chain, times(1)).doFilter(req, res); } - @Test - void testDoFilter_invokesJwtFilter_whenJwtCookiePresent() throws Exception { - PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false"); - - HttpServletRequest req = Mockito.mock(HttpServletRequest.class); - HttpServletResponse res = Mockito.mock(HttpServletResponse.class); - FilterChain chain = Mockito.mock(FilterChain.class); - - Mockito.when(req.getHeader("Authorization")).thenReturn(null); - Mockito.when(req.getCookies()).thenReturn(new Cookie[] {new Cookie("hadoop-jwt", "abc")}); - RangerJwtAuthFilter jwt = Mockito.mock(RangerJwtAuthFilter.class); - RangerJwtAuthWrapper wrapper = new RangerJwtAuthWrapper(); - setField(wrapper, "rangerJwtAuthFilter", jwt); - - wrapper.doFilter(req, res, chain); - - verify(jwt, times(1)).doFilter(req, res, chain); - verify(chain, times(1)).doFilter(req, res); - } - @Test void testDoFilter_redirectsToLogin_whenJwtAttemptedButUnauthenticated_andBrowserAgent() throws Exception { PropertiesUtil.getPropertiesMap().put("ranger.sso.enabled", "false");