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.");
       }
     }