You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2020/08/28 14:51:08 UTC
[knox] branch master updated: KNOX-2413 - Added JWT support in
HadoopAuth provider (#367)
This is an automated email from the ASF dual-hosted git repository.
smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 01d47c5 KNOX-2413 - Added JWT support in HadoopAuth provider (#367)
01d47c5 is described below
commit 01d47c5b34a96b4ebea5e585b1b6d409117d0b88
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Fri Aug 28 16:50:57 2020 +0200
KNOX-2413 - Added JWT support in HadoopAuth provider (#367)
* KNOX-2413 - Added JWT support in HadoopAuth provider
* KNOX-2413 - Continue using JWT if token exists (even if invalid)
---
gateway-provider-security-hadoopauth/pom.xml | 4 ++
.../gateway/hadoopauth/HadoopAuthMessages.java | 6 +++
.../hadoopauth/filter/HadoopAuthFilter.java | 43 ++++++++++++++-
.../hadoopauth/filter/HadoopAuthPostFilter.java | 49 +++++++++++++----
.../hadoopauth/filter/HadoopAuthFilterTest.java | 62 ++++++++++++++++++++++
.../federation/jwt/filter/AbstractJWTFilter.java | 5 ++
.../federation/jwt/filter/JWTFederationFilter.java | 24 +++++----
.../org/apache/knox/gateway/GatewayFilter.java | 17 +++++-
8 files changed, 187 insertions(+), 23 deletions(-)
diff --git a/gateway-provider-security-hadoopauth/pom.xml b/gateway-provider-security-hadoopauth/pom.xml
index 4e0dbb5..7095886 100755
--- a/gateway-provider-security-hadoopauth/pom.xml
+++ b/gateway-provider-security-hadoopauth/pom.xml
@@ -35,6 +35,10 @@
</dependency>
<dependency>
<groupId>org.apache.knox</groupId>
+ <artifactId>gateway-provider-security-jwt</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.knox</groupId>
<artifactId>gateway-server</artifactId>
</dependency>
<dependency>
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
index c7953c4..23c858f 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/HadoopAuthMessages.java
@@ -35,4 +35,10 @@ public interface HadoopAuthMessages {
@Message( level = MessageLevel.DEBUG, text = "Proxy user Authentication failed: {0}" )
void hadoopAuthProxyUserFailed(@StackTrace Throwable t);
+
+ @Message( level = MessageLevel.DEBUG, text = "Initialized the JWT federation filter" )
+ void initializedJwtFilter();
+
+ @Message( level = MessageLevel.DEBUG, text = "Using JWT filter to serve the request" )
+ void useJwtFilter();
}
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
index b3a82e1..2ab789e 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilter.java
@@ -22,10 +22,12 @@ import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.apache.hadoop.security.authorize.ProxyUsers;
import org.apache.hadoop.util.HttpExceptionUtils;
+import org.apache.knox.gateway.GatewayFilter;
import org.apache.knox.gateway.GatewayServer;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.hadoopauth.HadoopAuthMessages;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.ServiceType;
import org.apache.knox.gateway.services.security.AliasService;
@@ -44,6 +46,8 @@ import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
@@ -72,10 +76,13 @@ public class HadoopAuthFilter extends
private static final String QUERY_PARAMETER_DOAS = "doAs";
private static final String PROXYUSER_PREFIX = "hadoop.proxyuser";
+ static final String SUPPORT_JWT = "support.jwt";
+ static final String JWT_PREFIX = "jwt.";
private static final HadoopAuthMessages LOG = MessagesFactory.get(HadoopAuthMessages.class);
private final Set<String> ignoreDoAs = new HashSet<>();
+ private JWTFederationFilter jwtFilter;
@Override
protected Properties getConfiguration(String configPrefix, FilterConfig filterConfig) throws ServletException {
@@ -116,11 +123,34 @@ public class HadoopAuthFilter extends
}
super.init(filterConfig);
+
+ final String supportJwt = filterConfig.getInitParameter(SUPPORT_JWT);
+ final boolean jwtSupported = Boolean.parseBoolean(supportJwt == null ? "false" : supportJwt);
+ if (jwtSupported) {
+ jwtFilter = new JWTFederationFilter();
+ ((GatewayFilter.Holder)filterConfig).removeParamPrefix(JWT_PREFIX);
+ jwtFilter.init(filterConfig);
+ LOG.initializedJwtFilter();
+ }
}
@Override
- protected void doFilter(FilterChain filterChain, HttpServletRequest request,
- HttpServletResponse response) throws IOException, ServletException {
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ if (shouldUseJwtFilter(jwtFilter, (HttpServletRequest) request)) {
+ LOG.useJwtFilter();
+ jwtFilter.doFilter(request, response, filterChain);
+ } else {
+ super.doFilter(request, response, filterChain);
+ }
+ }
+
+ @Override
+ protected void doFilter(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
+ if (shouldUseJwtFilter(jwtFilter, request)) {
+ LOG.useJwtFilter();
+ jwtFilter.doFilter(request, response, filterChain);
+ return;
+ }
/*
* If impersonation is not ignored for the authenticated user, attempt to set a proxied user if
@@ -174,6 +204,11 @@ public class HadoopAuthFilter extends
super.doFilter(filterChain, request, response);
}
+ static boolean shouldUseJwtFilter(JWTFederationFilter jwtFilter, HttpServletRequest request)
+ throws IOException, ServletException {
+ return jwtFilter == null ? false : jwtFilter.getWireToken(request) != null;
+ }
+
/**
* Tests if the authenticated user/service has impersonation enabled based on previously calculated
* data (see {@link #init(FilterConfig)}.
@@ -244,4 +279,8 @@ public class HadoopAuthFilter extends
}
return props;
}
+
+ boolean isJwtSupported() {
+ return jwtFilter != null;
+ }
}
diff --git a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java
index 1913c54..2579083 100755
--- a/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java
+++ b/gateway-provider-security-hadoopauth/src/main/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthPostFilter.java
@@ -17,9 +17,15 @@
*/
package org.apache.knox.gateway.hadoopauth.filter;
+import static org.apache.knox.gateway.hadoopauth.filter.HadoopAuthFilter.JWT_PREFIX;
+import static org.apache.knox.gateway.hadoopauth.filter.HadoopAuthFilter.SUPPORT_JWT;
+import static org.apache.knox.gateway.hadoopauth.filter.HadoopAuthFilter.shouldUseJwtFilter;
+
import java.io.IOException;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
+import java.text.ParseException;
+import java.util.stream.Collectors;
import javax.security.auth.Subject;
import javax.servlet.Filter;
@@ -39,6 +45,8 @@ import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
import org.apache.knox.gateway.filter.AbstractGatewayFilter;
import org.apache.knox.gateway.hadoopauth.HadoopAuthMessages;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
+import org.apache.knox.gateway.GatewayFilter;
import org.apache.knox.gateway.audit.api.Action;
import org.apache.knox.gateway.audit.api.ActionOutcome;
import org.apache.knox.gateway.audit.api.Auditor;
@@ -51,8 +59,17 @@ public class HadoopAuthPostFilter implements Filter {
AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
AuditConstants.KNOX_COMPONENT_NAME );
+ private JWTFederationFilter jwtFilter;
+
@Override
public void init( FilterConfig filterConfig ) throws ServletException {
+ final String supportJwt = filterConfig.getInitParameter(SUPPORT_JWT);
+ final boolean jwtSupported = Boolean.parseBoolean(supportJwt == null ? "false" : supportJwt);
+ if (jwtSupported) {
+ jwtFilter = new JWTFederationFilter();
+ ((GatewayFilter.Holder)filterConfig).removeParamPrefix(JWT_PREFIX);
+ jwtFilter.init(filterConfig);
+ }
}
@Override
@@ -60,23 +77,37 @@ public class HadoopAuthPostFilter implements Filter {
}
@Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- HttpServletRequest httpRequest = (HttpServletRequest)request;
- String principal = httpRequest.getRemoteUser();
- if (principal != null) {
- Subject subject = new Subject();
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ Subject subject = null;
+ if (shouldUseJwtFilter(jwtFilter, (HttpServletRequest) request)) {
+ try {
+ subject = jwtFilter.createSubjectFromToken(jwtFilter.getWireToken(request));
+ } catch (ParseException e) {
+ // NOP: subject remains null -> SC_FORBIDDEN will be returned
+ }
+ } else {
+ final String principal = ((HttpServletRequest) request).getRemoteUser();
+ if (principal != null) {
+ subject = new Subject();
subject.getPrincipals().add(new PrimaryPrincipal(principal));
- log.hadoopAuthAssertedPrincipal(principal);
- auditService.getContext().setUsername( principal ); //KM: Audit Fix
+ }
+ }
+
+ if (subject != null) {
+ log.hadoopAuthAssertedPrincipal(getPrincipalsAsString(subject));
+ auditService.getContext().setUsername(getPrincipalsAsString(subject)); //KM: Audit Fix
String sourceUri = (String)request.getAttribute( AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME );
auditor.audit( Action.AUTHENTICATION , sourceUri, ResourceType.URI, ActionOutcome.SUCCESS );
- doAs(httpRequest, response, chain, subject);
+ doAs(request, response, chain, subject);
} else {
((HttpServletResponse)response).sendError(HttpServletResponse.SC_FORBIDDEN, "User not authenticated");
}
}
+ private String getPrincipalsAsString(Subject subject) {
+ return String.join(",", subject.getPrincipals().stream().map(principal -> principal.getName()).collect(Collectors.toSet()));
+ }
+
private void doAs(final ServletRequest request, final ServletResponse response, final FilterChain chain, Subject subject)
throws IOException, ServletException {
try {
diff --git a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
index e9a6ef6..8c92d03 100644
--- a/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
+++ b/gateway-provider-security-hadoopauth/src/test/java/org/apache/knox/gateway/hadoopauth/filter/HadoopAuthFilterTest.java
@@ -22,6 +22,7 @@ import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
@@ -29,7 +30,11 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import org.apache.knox.gateway.GatewayFilter;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.provider.federation.jwt.filter.AbstractJWTFilter;
+import org.apache.knox.gateway.provider.federation.jwt.filter.JWTFederationFilter;
+import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.topology.Topology;
import org.junit.Test;
@@ -98,6 +103,7 @@ public class HadoopAuthFilterTest {
expect(filterConfig.getInitParameter(GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS))
.andReturn("Knox, hdfs,TesT") // Spacing and case set on purpose
.atLeastOnce();
+ expect(filterConfig.getInitParameter("support.jwt")).andReturn("false").anyTimes();
expect(filterConfig.getServletContext()).andReturn(servletContext).atLeastOnce();
Properties configProperties = createMock(Properties.class);
@@ -135,4 +141,60 @@ public class HadoopAuthFilterTest {
verify(filterConfig, configProperties, hadoopAuthFilter, servletContext);
}
+
+ @Test
+ public void shouldNotUseJwtFilterIfProviderParamIsNotSet() throws Exception {
+ testIfJwtSupported(null);
+ }
+
+ @Test
+ public void shouldNotUseJwtFilterIfProviderParamIsFalse() throws Exception {
+ testIfJwtSupported("false");
+ }
+
+ @Test
+ public void shouldUseJwtFilterIfProviderParamIsTrue() throws Exception {
+ testIfJwtSupported("true");
+ }
+
+ private HadoopAuthFilter testIfJwtSupported(String supportJwt) throws Exception {
+ final GatewayFilter.Holder filterConfig = createMock(GatewayFilter.Holder.class);
+ expect(filterConfig.getInitParameterNames()).andReturn(Collections.enumeration(Collections.emptyList()));
+ expect(filterConfig.getInitParameter(GatewayConfig.PROXYUSER_SERVICES_IGNORE_DOAS)).andReturn("service").atLeastOnce();
+ expect(filterConfig.getInitParameter("config.prefix")).andReturn("some.prefix").atLeastOnce();
+ expect(filterConfig.getInitParameter("support.jwt")).andReturn(supportJwt).anyTimes();
+ final boolean isJwtSupported = Boolean.parseBoolean(supportJwt);
+ if (isJwtSupported) {
+ filterConfig.removeParamPrefix("jwt.");
+ expectLastCall();
+ expect(filterConfig.getInitParameter(JWTFederationFilter.KNOX_TOKEN_AUDIENCES)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(JWTFederationFilter.KNOX_TOKEN_QUERY_PARAM_NAME)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(JWTFederationFilter.JWKS_URL)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(JWTFederationFilter.TOKEN_PRINCIPAL_CLAIM)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(JWTFederationFilter.TOKEN_VERIFICATION_PEM)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(AbstractJWTFilter.JWT_EXPECTED_ISSUER)).andReturn(null).anyTimes();
+ expect(filterConfig.getInitParameter(AbstractJWTFilter.JWT_EXPECTED_SIGALG)).andReturn(null).anyTimes();
+ }
+
+ final ServletContext servletContext = createMock(ServletContext.class);
+ expect(servletContext.getAttribute("signer.secret.provider.object")).andReturn(null).atLeastOnce();
+ if (isJwtSupported) {
+ expect(servletContext.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE)).andReturn(null).anyTimes();
+ }
+ expect(filterConfig.getServletContext()).andReturn(servletContext).atLeastOnce();
+
+ final HadoopAuthFilter hadoopAuthFilter = createMockBuilder(HadoopAuthFilter.class).addMockedMethod("getConfiguration", String.class, FilterConfig.class).withConstructor()
+ .createMock();
+ final Properties config = new Properties();
+ config.put("type", "simple");
+ expect(hadoopAuthFilter.getConfiguration(eq("some.prefix."), eq(filterConfig))).andReturn(config).atLeastOnce();
+
+ replay(servletContext, filterConfig, hadoopAuthFilter);
+
+ hadoopAuthFilter.init(filterConfig);
+
+ assertEquals(isJwtSupported, hadoopAuthFilter.isJwtSupported());
+
+ return hadoopAuthFilter;
+ }
}
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
index 1aeea20..dc222bf 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -61,6 +61,7 @@ import org.apache.knox.gateway.services.security.token.TokenStateService;
import org.apache.knox.gateway.services.security.token.TokenUtils;
import org.apache.knox.gateway.services.security.token.UnknownTokenException;
import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.apache.knox.gateway.services.security.token.impl.JWTToken;
import com.nimbusds.jose.JWSHeader;
@@ -229,6 +230,10 @@ public abstract class AbstractJWTFilter implements Filter {
}
}
+ public Subject createSubjectFromToken(String token) throws ParseException {
+ return createSubjectFromToken(new JWTToken(token));
+ }
+
protected Subject createSubjectFromToken(JWT token) {
String principal = token.getSubject();
String claimvalue = null;
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index e4a7a25..4a7d425 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/knox/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -37,7 +37,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
public static final String KNOX_TOKEN_AUDIENCES = "knox.token.audiences";
public static final String TOKEN_VERIFICATION_PEM = "knox.token.verification.pem";
- private static final String KNOX_TOKEN_QUERY_PARAM_NAME = "knox.token.query.param.name";
+ public static final String KNOX_TOKEN_QUERY_PARAM_NAME = "knox.token.query.param.name";
public static final String TOKEN_PRINCIPAL_CLAIM = "knox.token.principal.claim";
public static final String JWKS_URL = "knox.token.jwks.url";
private static final String BEARER = "Bearer ";
@@ -85,16 +85,7 @@ public class JWTFederationFilter extends AbstractJWTFilter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
- String header = ((HttpServletRequest) request).getHeader("Authorization");
- String wireToken;
- if (header != null && header.startsWith(BEARER)) {
- // what follows the bearer designator should be the JWT token being used to request or as an access token
- wireToken = header.substring(BEARER.length());
- }
- else {
- // check for query param
- wireToken = request.getParameter(paramName);
- }
+ final String wireToken = getWireToken(request);
if (wireToken != null) {
try {
@@ -113,6 +104,17 @@ public class JWTFederationFilter extends AbstractJWTFilter {
}
}
+ public String getWireToken(ServletRequest request) {
+ final String header = ((HttpServletRequest) request).getHeader("Authorization");
+ if (header != null && header.startsWith(BEARER)) {
+ // what follows the bearer designator should be the JWT token being used to request or as an access token
+ return header.substring(BEARER.length());
+ } else {
+ // check for query param
+ return request.getParameter(paramName);
+ }
+ }
+
@Override
protected void handleValidationError(HttpServletRequest request, HttpServletResponse response, int status,
String error) throws IOException {
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java
index 64a0f75..a1012d0 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayFilter.java
@@ -280,7 +280,7 @@ public class GatewayFilter implements Filter {
}
}
- private class Holder implements Filter, FilterConfig {
+ public class Holder implements Filter, FilterConfig {
private Template template;
private String name;
private Map<String,String> params;
@@ -328,6 +328,21 @@ public class GatewayFilter implements Filter {
return value;
}
+ public void removeParamPrefix(String prefix) {
+ if (params != null) {
+ final Set<String> relevantParams = new HashSet<>();
+ params.entrySet().forEach(paramEntry -> {
+ if (paramEntry.getKey().startsWith(prefix)) {
+ relevantParams.add(paramEntry.getKey());
+ }
+ });
+ relevantParams.forEach(param -> {
+ String value = params.remove(param);
+ params.put(param.replace(prefix, ""), value);
+ });
+ }
+ }
+
@Override
public Enumeration<String> getInitParameterNames() {
Enumeration<String> names = null;