Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
import org.apache.commons.lang3.StringUtils;
import org.apache.ranger.authz.handler.RangerAuth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
Expand All @@ -35,6 +37,7 @@
* Default implementation of Ranger JWT authentication
*/
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";

Expand Down Expand Up @@ -81,12 +84,42 @@ public RangerAuth authenticate(HttpServletRequest httpServletRequest) {
String jwtAuthHeaderStr = getJwtAuthHeader(httpServletRequest);
String jwtCookieStr = StringUtils.isBlank(jwtAuthHeaderStr) ? getJwtCookie(httpServletRequest) : null;
String doAsUser = httpServletRequest.getParameter(DO_AS_PARAMETER);
String username = authenticate(jwtAuthHeaderStr, jwtCookieStr, doAsUser);
// authenticate against the JWT first to get the real (token-verified) user
String realUser = authenticate(jwtAuthHeaderStr, jwtCookieStr);

if (username != null) {
rangerAuth = new RangerAuth(username, RangerAuth.AuthType.JWT_JWKS);
}
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.AuthType.JWT_JWKS);
}
return rangerAuth;
}

protected boolean isProxyEnabled() {
return false;
}

protected boolean authorizeProxyUser(String realUser, String doAsUser, String remoteAddr) {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public void initialize(final Properties config) throws Exception {

public abstract ConfigurableJWTProcessor<SecurityContext> getJwtProcessor(JWSKeySelector<SecurityContext> keySelector);

protected String authenticate(final String jwtAuthHeader, final String jwtCookie, final String doAsUser) {
protected String authenticate(final String jwtAuthHeader, final String jwtCookie) {
if (LOG.isDebugEnabled()) {
LOG.debug("===>>> RangerJwtAuthHandler.authenticate()");
}
Expand All @@ -119,17 +119,10 @@ protected String authenticate(final String jwtAuthHeader, final String jwtCookie
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);
}
return userName;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
*/
package org.apache.ranger.security.web.filter;

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;
Expand Down Expand Up @@ -75,6 +79,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);
}
Expand Down Expand Up @@ -117,6 +124,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
*/
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;
Expand All @@ -38,6 +40,7 @@
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;
Expand Down Expand Up @@ -120,4 +123,54 @@ public void testDoFilter_leavesAuthenticationNullWhenAuthenticateReturnsNull()

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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@
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;
Expand Down Expand Up @@ -124,4 +129,124 @@ public void testDoFilter_skipsJwtWhenSsoEnabled() throws IOException, ServletExc
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);
}
}