You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2017/02/08 03:03:03 UTC
knox git commit: KNOX-873 - JWTFederationFilter must Validate
Expected Audiences
Repository: knox
Updated Branches:
refs/heads/master 521a542d2 -> e90109f37
KNOX-873 - JWTFederationFilter must Validate Expected Audiences
Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/e90109f3
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/e90109f3
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/e90109f3
Branch: refs/heads/master
Commit: e90109f37f287d0705c0e772357b13665ea776b4
Parents: 521a542
Author: Larry McCay <lm...@hortonworks.com>
Authored: Tue Feb 7 22:02:49 2017 -0500
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Tue Feb 7 22:02:49 2017 -0500
----------------------------------------------------------------------
.../jwt/deploy/JWTFederationContributor.java | 11 ++
.../jwt/filter/AbstractJWTFilter.java | 116 +++++++++++++++++++
.../jwt/filter/JWTFederationFilter.java | 44 ++++---
.../jwt/filter/SSOCookieFederationFilter.java | 65 +----------
.../org/apache/hadoop/gateway/shell/KnoxSh.java | 35 +++---
5 files changed, 180 insertions(+), 91 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/knox/blob/e90109f3/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/JWTFederationContributor.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/JWTFederationContributor.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/JWTFederationContributor.java
index 466ae6b..ec70531 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/JWTFederationContributor.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/deploy/JWTFederationContributor.java
@@ -24,7 +24,10 @@ import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
import org.apache.hadoop.gateway.topology.Provider;
import org.apache.hadoop.gateway.topology.Service;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
public class JWTFederationContributor extends ProviderDeploymentContributorBase {
@@ -46,6 +49,14 @@ public class JWTFederationContributor extends ProviderDeploymentContributorBase
@Override
public void contributeFilter( DeploymentContext context, Provider provider, Service service, ResourceDescriptor resource, List<FilterParamDescriptor> params ) {
+ // blindly add all the provider params as filter init params
+ if (params == null) {
+ params = new ArrayList<FilterParamDescriptor>();
+ }
+ Map<String, String> providerParams = provider.getParams();
+ for(Entry<String, String> entry : providerParams.entrySet()) {
+ params.add( resource.createFilterParam().name( entry.getKey().toLowerCase() ).value( entry.getValue() ) );
+ }
resource.addFilter().name( getName() ).role( getRole() ).impl( FILTER_CLASSNAME ).params( params );
}
}
http://git-wip-us.apache.org/repos/asf/knox/blob/e90109f3/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
new file mode 100644
index 0000000..9119436
--- /dev/null
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/AbstractJWTFilter.java
@@ -0,0 +1,116 @@
+/**
+ * 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.hadoop.gateway.provider.federation.jwt.filter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.security.auth.Subject;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.provider.federation.jwt.JWTMessages;
+import org.apache.hadoop.gateway.security.PrimaryPrincipal;
+import org.apache.hadoop.gateway.services.security.token.TokenServiceException;
+import org.apache.hadoop.gateway.services.security.token.impl.JWTToken;
+
+/**
+ *
+ */
+public abstract class AbstractJWTFilter implements Filter {
+ static JWTMessages log = MessagesFactory.get( JWTMessages.class );
+ protected List<String> audiences = null;
+
+ public abstract void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException;
+
+ /**
+ *
+ */
+ public AbstractJWTFilter() {
+ super();
+ }
+
+ /**
+ * @param expectedAudiences
+ * @return
+ */
+ protected List<String> parseExpectedAudiences(String expectedAudiences) {
+ ArrayList<String> audList = null;
+ // setup the list of valid audiences for token validation
+ if (expectedAudiences != null) {
+ // parse into the list
+ String[] audArray = expectedAudiences.split(",");
+ audList = new ArrayList<String>();
+ for (String a : audArray) {
+ audList.add(a);
+ }
+ }
+ return audList;
+ }
+
+ protected boolean tokenIsStillValid(JWTToken jwtToken) {
+ // if there is no expiration data then the lifecycle is tied entirely to
+ // the cookie validity - otherwise ensure that the current time is before
+ // the designated expiration time
+ Date expires = jwtToken.getExpiresDate();
+ return (expires == null || expires != null && new Date().before(expires));
+ }
+
+ /**
+ * Validate whether any of the accepted audience claims is present in the
+ * issued token claims list for audience. Override this method in subclasses
+ * in order to customize the audience validation behavior.
+ *
+ * @param jwtToken
+ * the JWT token where the allowed audiences will be found
+ * @return true if an expected audience is present, otherwise false
+ */
+ protected boolean validateAudiences(JWTToken jwtToken) {
+ boolean valid = false;
+
+ String[] tokenAudienceList = jwtToken.getAudienceClaims();
+ // if there were no expected audiences configured then just
+ // consider any audience acceptable
+ if (audiences == null) {
+ valid = true;
+ } else {
+ // if any of the configured audiences is found then consider it
+ // acceptable
+ if (tokenAudienceList != null) {
+ for (String aud : tokenAudienceList) {
+ if (audiences.contains(aud)) {
+ log.jwtAudienceValidated();
+ valid = true;
+ break;
+ }
+ }
+ }
+ }
+ return valid;
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/knox/blob/e90109f3/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
index 9a95421..33af374 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/JWTFederationFilter.java
@@ -17,7 +17,6 @@
*/
package org.apache.hadoop.gateway.provider.federation.jwt.filter;
-import org.apache.commons.logging.Log;
import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
import org.apache.hadoop.gateway.provider.federation.jwt.JWTMessages;
import org.apache.hadoop.gateway.security.PrimaryPrincipal;
@@ -40,12 +39,13 @@ import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
-import java.text.ParseException;
+import java.util.Date;
import java.util.HashSet;
import java.util.Set;
-public class JWTFederationFilter implements Filter {
+public class JWTFederationFilter extends AbstractJWTFilter {
+ public static final String KNOX_TOKEN_AUDIENCES = "knox.token.audiences";
private static final String BEARER = "Bearer ";
private static JWTMessages log = MessagesFactory.get( JWTMessages.class );
private JWTokenAuthority authority = null;
@@ -54,6 +54,12 @@ public class JWTFederationFilter implements Filter {
public void init( FilterConfig filterConfig ) throws ServletException {
GatewayServices services = (GatewayServices) filterConfig.getServletContext().getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
authority = (JWTokenAuthority) services.getService(GatewayServices.TOKEN_SERVICE);
+
+ // expected audiences or null
+ String expectedAudiences = filterConfig.getInitParameter(KNOX_TOKEN_AUDIENCES);
+ if (expectedAudiences != null) {
+ audiences = parseExpectedAudiences(expectedAudiences);
+ }
}
public void destroy() {
@@ -66,12 +72,7 @@ public class JWTFederationFilter implements Filter {
// what follows the bearer designator should be the JWT token being used to request or as an access token
String wireToken = header.substring(BEARER.length());
JWTToken token;
-// try {
- token = new JWTToken(wireToken);
-// token = JWTToken.parseToken(wireToken);
-// } catch (ParseException e) {
-// throw new ServletException("ParseException encountered while processing the JWT token: ", e);
-// }
+ token = new JWTToken(wireToken);
boolean verified = false;
try {
verified = authority.verifyToken(token);
@@ -79,12 +80,26 @@ public class JWTFederationFilter implements Filter {
log.unableToVerifyToken(e);
}
if (verified) {
- // TODO: validate expiration
- // confirm that audience matches intended target - which for this filter must be KNOXSSO
+ // confirm that issue matches intended target - which for this filter must be KNOXSSO
if (token.getIssuer().equals("KNOXSSO")) {
- // TODO: verify that the user requesting access to the service/resource is authorized for it - need scopes?
- Subject subject = createSubjectFromToken(token);
- continueWithEstablishedSecurityContext(subject, (HttpServletRequest)request, (HttpServletResponse)response, chain);
+ // if there is no expiration data then the lifecycle is tied entirely to
+ // the cookie validity - otherwise ensure that the current time is before
+ // the designated expiration time
+ if (tokenIsStillValid(token)) {
+ boolean audValid = validateAudiences(token);
+ if (audValid) {
+ Subject subject = createSubjectFromToken(token);
+ continueWithEstablishedSecurityContext(subject, (HttpServletRequest)request, (HttpServletResponse)response, chain);
+ }
+ else {
+ log.failedToValidateAudience();
+ ((HttpServletResponse) response).sendError(400, "Bad request: missing required token audience");
+ }
+ }
+ else {
+ log.tokenHasExpired();
+ ((HttpServletResponse) response).sendError(400, "Bad request: token has expired");
+ }
}
else {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
@@ -98,7 +113,6 @@ public class JWTFederationFilter implements Filter {
}
else {
// no token provided in header
- // TODO: may have to check cookie and url as well before sending error
((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
return; //break filter chain
}
http://git-wip-us.apache.org/repos/asf/knox/blob/e90109f3/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
----------------------------------------------------------------------
diff --git a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
index 6286655..68e8f7b 100644
--- a/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
+++ b/gateway-provider-security-jwt/src/main/java/org/apache/hadoop/gateway/provider/federation/jwt/filter/SSOCookieFederationFilter.java
@@ -21,10 +21,8 @@ import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
-import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
-import java.util.List;
import java.util.Set;
import javax.security.auth.Subject;
@@ -47,8 +45,8 @@ import org.apache.hadoop.gateway.services.security.token.JWTokenAuthority;
import org.apache.hadoop.gateway.services.security.token.TokenServiceException;
import org.apache.hadoop.gateway.services.security.token.impl.JWTToken;
-public class SSOCookieFederationFilter implements Filter {
- private static JWTMessages log = MessagesFactory.get( JWTMessages.class );
+public class SSOCookieFederationFilter extends AbstractJWTFilter implements Filter {
+ static JWTMessages log = MessagesFactory.get( JWTMessages.class );
private static final String ORIGINAL_URL_QUERY_PARAM = "originalUrl=";
public static final String SSO_COOKIE_NAME = "sso.cookie.name";
public static final String SSO_EXPECTED_AUDIENCES = "sso.expected.audiences";
@@ -57,7 +55,6 @@ public class SSOCookieFederationFilter implements Filter {
protected JWTokenAuthority authority = null;
private String cookieName = null;
- private List<String> audiences = null;
private String authenticationProviderUrl = null;
@Override
@@ -90,27 +87,10 @@ public class SSOCookieFederationFilter implements Filter {
}
}
- /**
- * @param expectedAudiences
- * @return
- */
- private List<String> parseExpectedAudiences(String expectedAudiences) {
- ArrayList<String> audList = null;
- // setup the list of valid audiences for token validation
- if (expectedAudiences != null) {
- // parse into the list
- String[] audArray = expectedAudiences.split(",");
- audList = new ArrayList<String>();
- for (String a : audArray) {
- audList.add(a);
- }
- }
- return audList;
- }
-
public void destroy() {
}
+ @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String wireToken = null;
@@ -135,11 +115,7 @@ public class SSOCookieFederationFilter implements Filter {
try {
verified = authority.verifyToken(token);
if (verified) {
- Date expires = token.getExpiresDate();
- // if there is no expiration data then the lifecycle is tied entirely to
- // the cookie validity - otherwise ensure that the current time is before
- // the designated expiration time
- if (expires == null || expires != null && new Date().before(expires)) {
+ if (tokenIsStillValid(token)) {
boolean audValid = validateAudiences(token);
if (audValid) {
Subject subject = createSubjectFromToken(token);
@@ -211,39 +187,6 @@ public class SSOCookieFederationFilter implements Filter {
return (originalQueryString == null) ? "" : "?" + originalQueryString;
}
- /**
- * Validate whether any of the accepted audience claims is present in the
- * issued token claims list for audience. Override this method in subclasses
- * in order to customize the audience validation behavior.
- *
- * @param jwtToken
- * the JWT token where the allowed audiences will be found
- * @return true if an expected audience is present, otherwise false
- */
- protected boolean validateAudiences(JWTToken jwtToken) {
- boolean valid = false;
-
- String[] tokenAudienceList = jwtToken.getAudienceClaims();
- // if there were no expected audiences configured then just
- // consider any audience acceptable
- if (audiences == null) {
- valid = true;
- } else {
- // if any of the configured audiences is found then consider it
- // acceptable
- if (tokenAudienceList != null) {
- for (String aud : tokenAudienceList) {
- if (audiences.contains(aud)) {
- log.jwtAudienceValidated();
- valid = true;
- break;
- }
- }
- }
- }
- return valid;
- }
-
private void sendUnauthorized(ServletResponse response) throws IOException {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
http://git-wip-us.apache.org/repos/asf/knox/blob/e90109f3/gateway-shell/src/main/java/org/apache/hadoop/gateway/shell/KnoxSh.java
----------------------------------------------------------------------
diff --git a/gateway-shell/src/main/java/org/apache/hadoop/gateway/shell/KnoxSh.java b/gateway-shell/src/main/java/org/apache/hadoop/gateway/shell/KnoxSh.java
index 1e57d95..954a640 100644
--- a/gateway-shell/src/main/java/org/apache/hadoop/gateway/shell/KnoxSh.java
+++ b/gateway-shell/src/main/java/org/apache/hadoop/gateway/shell/KnoxSh.java
@@ -45,8 +45,8 @@ import org.apache.hadoop.gateway.util.JsonUtils;
*/
public class KnoxSh {
- private static final String USAGE_PREFIX = "KnoxCLI {cmd} [options]";
- static final private String COMMANDS =
+ private static final String USAGE_PREFIX = "KnoxSh {cmd} [options]";
+ final static private String COMMANDS =
" [--help]\n" +
" [" + KnoxInit.USAGE + "]\n" +
" [" + KnoxDestroy.USAGE + "]\n" +
@@ -160,7 +160,7 @@ public class KnoxSh {
private class KnoxInit extends Command {
- public static final String USAGE = "init";
+ public static final String USAGE = "init --gateway topology-url";
public static final String DESC = "Initializes a Knox token session.";
@Override
@@ -200,7 +200,7 @@ public class KnoxSh {
Files.setPosixFilePermissions(Paths.get(System.getProperty("user.home") + "/.knoxtokencache"), perms);
}
catch(HadoopException he) {
- System.out.println("Failuire to acquire token. Please verify your credentials and Knox URL and try again.");
+ System.out.println("Failure to acquire token. Please verify your credentials and Knox URL and try again.");
}
session.shutdown();
}
@@ -214,8 +214,8 @@ public class KnoxSh {
private class KnoxDestroy extends Command {
- public static final String USAGE = "version";
- public static final String DESC = "Displays Knox version information.";
+ public static final String USAGE = "destroy";
+ public static final String DESC = "Destroys an Knox token session.";
@Override
public void execute() throws Exception {
@@ -232,18 +232,23 @@ public class KnoxSh {
private class KnoxList extends Command {
- public static final String USAGE = "version";
- public static final String DESC = "Displays Knox version information.";
+ public static final String USAGE = "list";
+ public static final String DESC = "Displays Knox token details.";
@Override
public void execute() throws Exception {
- String tokenfile = readFile(
- System.getProperty("user.home") +
- File.separator + ".knoxtokencache");
-
- if (tokenfile != null) {
- Map<String, String> json = JsonUtils.getMapFromJsonString(tokenfile);
- displayTokenDetails(json);
+ String tokenFilePath = System.getProperty("user.home") +
+ File.separator + ".knoxtokencache";
+ if (new File(tokenFilePath).exists()) {
+ String tokenfile = readFile(tokenFilePath);
+
+ if (tokenfile != null) {
+ Map<String, String> json = JsonUtils.getMapFromJsonString(tokenfile);
+ displayTokenDetails(json);
+ }
+ }
+ else {
+ System.out.println("Knox token cache does not exist. Please login with init.");
}
}