knox-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dillido...@apache.org
Subject git commit: patch to fix KNOX-89, Knox doing SPNego with Hadoop for every client request is not scalable
Date Wed, 21 Aug 2013 18:19:55 GMT
Updated Branches:
  refs/heads/master 0f763c6bb -> e36fc4a31


patch to fix KNOX-89, Knox doing SPNego with Hadoop for every client request is not scalable


Project: http://git-wip-us.apache.org/repos/asf/incubator-knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-knox/commit/e36fc4a3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-knox/tree/e36fc4a3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-knox/diff/e36fc4a3

Branch: refs/heads/master
Commit: e36fc4a3151d208e4327132f2bffb9e383d2c060
Parents: 0f763c6
Author: Dilli Dorai Arumugam <darumugam@hortonworks.com>
Authored: Tue Aug 20 22:37:49 2013 -0700
Committer: Dilli Dorai Arumugam <darumugam@hortonworks.com>
Committed: Wed Aug 21 11:12:19 2013 -0700

----------------------------------------------------------------------
 .../home/templates/krb5JAASLogin.conf           |  31 +--
 .../apache/hadoop/gateway/GatewayMessages.java  |   6 +
 .../gateway/dispatch/AppCookieManager.java      | 190 +++++++++++++++++++
 .../gateway/dispatch/HttpClientDispatch.java    | 109 ++++++-----
 .../gateway/dispatch/AppCookieManagerTest.java  |  53 ++++++
 5 files changed, 312 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/e36fc4a3/gateway-release/home/templates/krb5JAASLogin.conf
----------------------------------------------------------------------
diff --git a/gateway-release/home/templates/krb5JAASLogin.conf b/gateway-release/home/templates/krb5JAASLogin.conf
index cfb4d19..d9f8d7b 100644
--- a/gateway-release/home/templates/krb5JAASLogin.conf
+++ b/gateway-release/home/templates/krb5JAASLogin.conf
@@ -17,20 +17,6 @@
 *
 * IMPORTANT: REPLACE EXAMPLE.COM and keyTab file location with your site specific values
 */
-com.sun.security.jgss.login {
-    com.sun.security.auth.module.Krb5LoginModule required 
-    renewTGT=true
-    doNotPrompt=true
-    useKeyTab=true
-    keyTab="/etc/knox/conf/knox.service.keytab"
-    principal="knox@EXAMPLE.COM"
-    isInitiator=true
-    storeKey=true
-    useTicketCache=true
-    client=true 
-    debug=true;
-};
- 
 com.sun.security.jgss.initiate {
     com.sun.security.auth.module.Krb5LoginModule required 
     renewTGT=true
@@ -41,20 +27,5 @@ com.sun.security.jgss.initiate {
     isInitiator=true
     storeKey=true
     useTicketCache=true
-    client=true 
-    debug=true;
-};
-
-com.sun.security.jgss.accept {
-    com.sun.security.auth.module.Krb5LoginModule required 
-    renewTGT=true
-    doNotPrompt=true
-    useKeyTab=true
-    keyTab="/etc/knox/conf/knox.service.keytab"
-    principal="knox@EXAMPLE.COM"
-    isInitiator=true
-    storeKey=true
-    useTicketCache=true
-    client=true 
-    debug=true;
+    client=true;
 };

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/e36fc4a3/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
index 3337b19..34ef775 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
@@ -260,4 +260,10 @@ public interface GatewayMessages {
 
   @Message( level = MessageLevel.ERROR, text = "Failed to get map from Json string {0}: {1}"
)
   void failedToGetMapFromJsonString( String json, @StackTrace( level = MessageLevel.DEBUG
) Exception e );
+  
+  @Message( level = MessageLevel.INFO, text = "Successful Knox->Hadoop SPNegotiation authentication
for URL: {0}" )
+  void successfulSPNegoAuthn(String uri);
+  
+  @Message( level = MessageLevel.ERROR, text = "Failed Knox->Hadoop SPNegotiation authentication
for URL: {0}" )
+  void failedSPNegoAuthn(String uri);
 }

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/e36fc4a3/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/AppCookieManager.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/AppCookieManager.java
b/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/AppCookieManager.java
new file mode 100644
index 0000000..d0f0ecc
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/AppCookieManager.java
@@ -0,0 +1,190 @@
+/**
+ * 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.dispatch;
+
+import java.io.IOException;
+import java.net.URI;
+import java.security.Principal;
+
+import org.apache.hadoop.gateway.GatewayMessages;
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.http.Header;
+import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.params.AuthPolicy;
+import org.apache.http.impl.auth.SPNegoSchemeFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+
+/**
+ * Handles SPNego authentication as a client of hadoop service, caches
+ * hadoop.auth cookie returned by hadoop service on successful SPNego
+ * authentication. Refreshes hadoop.auth cookie on demand if the cookie has
+ * expired.
+ * 
+ */
+public class AppCookieManager {
+
+  static final String HADOOP_AUTH = "hadoop.auth";
+  private static final String HADOOP_AUTH_EQ = "hadoop.auth=";
+  private static final String SET_COOKIE = "Set-Cookie";
+
+  private static GatewayMessages LOG = MessagesFactory.get(GatewayMessages.class);
+
+  private static final EmptyJaasCredentials EMPTY_JAAS_CREDENTIALS = new EmptyJaasCredentials();
+
+  String appCookie;
+
+  /**
+   * Utility method to excerise AppCookieManager directly
+   * @param args element 0 of args should be a URL to hadoop service protected by SPengo
+   * @throws IOException in case of errors
+   */
+  public static void main(String[] args) throws IOException {
+    HttpUriRequest outboundRequest = new HttpGet(args[0]);
+    new AppCookieManager().getAppCookie(outboundRequest, false);
+  }
+
+  public AppCookieManager() {
+  }
+
+  /**
+   * Fetches hadoop.auth cookie from hadoop service authenticating using SpNego
+   * 
+   * @param outboundRequest
+   *          out going request
+   * @param refresh
+   *          flag indicating whether to refresh the cached cookie
+   * @return hadoop.auth cookie from hadoop service authenticating using SpNego
+   * @throws IOException
+   *           in case of errors
+   */
+  public String getAppCookie(HttpUriRequest outboundRequest, boolean refresh)
+      throws IOException {
+
+    URI uri = outboundRequest.getURI();
+    String scheme = uri.getScheme();
+    String host = uri.getHost();
+    int port = uri.getPort();
+    String path = uri.getPath();
+    if (!refresh) {
+      if (appCookie != null) {
+        return appCookie;
+      }
+    }
+
+    DefaultHttpClient client = new DefaultHttpClient();
+    SPNegoSchemeFactory spNegoSF = new SPNegoSchemeFactory(
+    /* stripPort */true);
+    // spNegoSF.setSpengoGenerator(new BouncySpnegoTokenGenerator());
+    client.getAuthSchemes().register(AuthPolicy.SPNEGO, spNegoSF);
+    client.getCredentialsProvider().setCredentials(
+        new AuthScope(/* host */null, /* port */-1, /* realm */null),
+        EMPTY_JAAS_CREDENTIALS);
+
+    clearAppCookie();
+    String hadoopAuthCookie = null;
+    HttpResponse httpResponse = null;
+    try {
+      HttpHost httpHost = new HttpHost(host, port, scheme);
+      HttpRequest httpRequest = new HttpOptions(path);
+      httpResponse = client.execute(httpHost, httpRequest);
+      Header[] headers = httpResponse.getHeaders(SET_COOKIE);
+      hadoopAuthCookie = getHadoopAuthCookieValue(headers);
+      if (hadoopAuthCookie == null) {
+        LOG.failedSPNegoAuthn(uri.toString());
+        throw new IOException(
+            "SPNego authn failed, can not get hadoop.auth cookie");
+      }
+    } finally {
+      if (httpResponse != null) {
+        HttpEntity entity = httpResponse.getEntity();
+        if (entity != null) {
+          entity.getContent().close();
+        }
+      }
+
+    }
+    LOG.successfulSPNegoAuthn(uri.toString());
+    hadoopAuthCookie = HADOOP_AUTH_EQ + quote(hadoopAuthCookie);
+    setAppCookie(hadoopAuthCookie);
+    return appCookie;
+  }
+
+  /**
+   * Returns the cached app cookie
+   * 
+   * @return the cached app cookie, can be null
+   */
+  public String getCachedKnoxAppCookie() {
+    return appCookie;
+  }
+  
+  private void setAppCookie(String appCookie) {
+    this.appCookie = appCookie;
+  }
+
+  private void clearAppCookie() {
+    appCookie = null;
+  }
+  
+  static String quote(String s) {
+    return s == null ? s : "\"" + s + "\"";
+  }
+
+  static String getHadoopAuthCookieValue(Header[] headers) {
+    if (headers == null) {
+      return null;
+    }
+    for (Header header : headers) {
+      HeaderElement[] elements = header.getElements();
+      for (HeaderElement element : elements) {
+        String cookieName = element.getName();
+        if (cookieName.equals(HADOOP_AUTH)) {
+          if (element.getValue() != null) {
+            String trimmedVal = element.getValue().trim();
+            if (!trimmedVal.isEmpty()) {
+              return trimmedVal;
+            }
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+  private static class EmptyJaasCredentials implements Credentials {
+
+    public String getPassword() {
+      return null;
+    }
+
+    public Principal getUserPrincipal() {
+      return null;
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/e36fc4a3/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/HttpClientDispatch.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/HttpClientDispatch.java
b/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/HttpClientDispatch.java
index 2d38aea..ac8d85c 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/HttpClientDispatch.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/dispatch/HttpClientDispatch.java
@@ -17,7 +17,15 @@
  */
 package org.apache.hadoop.gateway.dispatch;
 
-import org.apache.commons.io.IOUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.Principal;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.hadoop.gateway.GatewayMessages;
 import org.apache.hadoop.gateway.GatewayResources;
 import org.apache.hadoop.gateway.config.GatewayConfig;
@@ -26,7 +34,7 @@ import org.apache.hadoop.gateway.i18n.resources.ResourcesFactory;
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
-import org.apache.http.auth.AuthScope;
+import org.apache.http.HttpStatus;
 import org.apache.http.auth.Credentials;
 import org.apache.http.client.methods.HttpDelete;
 import org.apache.http.client.methods.HttpGet;
@@ -34,39 +42,32 @@ import org.apache.http.client.methods.HttpOptions;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.params.AuthPolicy;
 import org.apache.http.entity.BufferedHttpEntity;
-import org.apache.http.entity.ByteArrayEntity;
 import org.apache.http.entity.ContentType;
 import org.apache.http.entity.InputStreamEntity;
-import org.apache.http.impl.auth.SPNegoSchemeFactory;
 import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.protocol.BasicHttpContext;
-import org.apache.http.protocol.HttpContext;
-
-import javax.activation.MimeType;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.Charset;
-import java.security.Principal;
+import org.apache.http.message.BasicHeader;
 
 /**
  *
  */
 public class HttpClientDispatch extends AbstractGatewayDispatch {
-
-  private static final String CT_APP_WWW_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
-  private static final String CT_APP_XML = "application/xml";
+  
+  // private static final String CT_APP_WWW_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
+  // private static final String CT_APP_XML = "application/xml";
+  private static final String Q_DELEGATION_EQ = "?delegation=";
+  private static final String AMP_DELEGATION_EQ = "&delegation=";
+  private static final String COOKIE = "Cookie";
+  private static final String SET_COOKIE = "Set-Cookie";
+  private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
+  private static final String NEGOTIATE = "Negotiate";
 
   private static GatewayMessages LOG = MessagesFactory.get( GatewayMessages.class );
   private static GatewayResources RES = ResourcesFactory.get( GatewayResources.class );
-  private static final EmptyJaasCredentials EMPTY_JAAS_CREDENTIALS = new EmptyJaasCredentials();
   private static final int REPLAY_BUFFER_MAX_SIZE = 1024 * 1024; // limit to 1MB
 
+  private AppCookieManager appCookieManager = new AppCookieManager();;
+  
   protected void executeRequest(
       HttpUriRequest outboundRequest,
       HttpServletRequest inboundRequest,
@@ -74,22 +75,46 @@ public class HttpClientDispatch extends AbstractGatewayDispatch {
           throws IOException {
     LOG.dispatchRequest( outboundRequest.getMethod(), outboundRequest.getURI() );
     DefaultHttpClient client = new DefaultHttpClient();
-    
-    if ("true".equals(System.getProperty(GatewayConfig.HADOOP_KERBEROS_SECURED))) {
-      SPNegoSchemeFactory nsf = new SPNegoSchemeFactory(/* stripPort */ true);
-      // nsf.setSpengoGenerator(new BouncySpnegoTokenGenerator());
-      client.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
 
-      client.getCredentialsProvider().setCredentials(
-          new AuthScope(/* host */ null, /* port */ -1, /* realm */ null),
-          EMPTY_JAAS_CREDENTIALS);
-    }
-
-    HttpContext localContext = new BasicHttpContext();
-    
     HttpResponse inboundResponse;
     try {
-      inboundResponse = client.execute(outboundRequest, localContext);
+      String query = outboundRequest.getURI().getQuery();
+      if (!"true".equals(System.getProperty(GatewayConfig.HADOOP_KERBEROS_SECURED))) {
+        // Hadoop cluster not Kerberos enabled
+        inboundResponse = client.execute(outboundRequest);
+      } else if (query.contains(Q_DELEGATION_EQ) ||
+        // query string carries delegation token
+        query.contains(AMP_DELEGATION_EQ)) {
+        inboundResponse = client.execute(outboundRequest);
+      } else { 
+        // Kerberos secured, no delegation token in query string
+        outboundRequest.removeHeaders(COOKIE);
+        String appCookie = appCookieManager.getCachedKnoxAppCookie();
+        if (appCookie != null) {
+          outboundRequest.addHeader(new BasicHeader(COOKIE, appCookie));
+        }
+        inboundResponse = client.execute(outboundRequest);
+        // if inBoundResponse has status 401 and header WWW-Authenticate: Negoitate
+        // refresh hadoop.auth.cookie and attempt one more time
+        int statusCode = inboundResponse.getStatusLine().getStatusCode();
+        if (statusCode == HttpStatus.SC_UNAUTHORIZED ) {
+          Header[] wwwAuthHeaders = inboundResponse.getHeaders(WWW_AUTHENTICATE) ;
+          if (wwwAuthHeaders != null && wwwAuthHeaders.length != 0 && 
+              wwwAuthHeaders[0].getValue().trim().startsWith(NEGOTIATE)) {
+            appCookie = appCookieManager.getAppCookie(outboundRequest, true);
+            outboundRequest.removeHeaders(COOKIE);
+            outboundRequest.addHeader(new BasicHeader(COOKIE, appCookie));
+            client = new DefaultHttpClient();
+            inboundResponse = client.execute(outboundRequest);
+          } else {
+            // no supported authentication type found
+            // we would let the original response propogate
+          }
+        } else {
+          // not a 401 Unauthorized status code
+          // we would let the original response propogate
+        }
+      }
     } catch (IOException e) {
       // we do not want to expose back end host. port end points to clients, see JIRA KNOX-58
       LOG.dispatchServiceConnectionException( outboundRequest.getURI(), e );
@@ -101,6 +126,9 @@ public class HttpClientDispatch extends AbstractGatewayDispatch {
     Header[] headers = inboundResponse.getAllHeaders();
     for( Header header : headers ) {
       String name = header.getName();
+      if (name.equals(SET_COOKIE) || name.equals(WWW_AUTHENTICATE)) {
+        continue;
+      }
       String value = header.getValue();
       outboundResponse.addHeader( name, value );
     }
@@ -239,18 +267,5 @@ public class HttpClientDispatch extends AbstractGatewayDispatch {
 //    }
 //
 //  }
-  
-  private static class EmptyJaasCredentials implements Credentials {
-
-    public String getPassword() {
-      return null;
-    }
-
-    public Principal getUserPrincipal() {
-      return null;
-    }
-
-  }
-  
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-knox/blob/e36fc4a3/gateway-server/src/test/java/org/apache/hadoop/gateway/dispatch/AppCookieManagerTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/dispatch/AppCookieManagerTest.java
b/gateway-server/src/test/java/org/apache/hadoop/gateway/dispatch/AppCookieManagerTest.java
new file mode 100644
index 0000000..704c41c
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/dispatch/AppCookieManagerTest.java
@@ -0,0 +1,53 @@
+/**
+ * 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.dispatch;
+
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.apache.http.Header;
+import org.apache.http.message.BasicHeader;
+import org.junit.Test;
+
+public class AppCookieManagerTest {
+
+  @Test
+  public void getCachedKnoxAppCookie() {
+    assertNull(new AppCookieManager().getCachedKnoxAppCookie());
+  }
+
+  @Test
+  public void getHadoopAuthCookieValueWithNullHeaders() {
+    assertNull(AppCookieManager.getHadoopAuthCookieValue(null));
+  }
+  
+  @Test
+  public void getHadoopAuthCookieValueWitEmptylHeaders() {
+    assertNull(AppCookieManager.getHadoopAuthCookieValue(new Header[0]));
+  }
+  
+  @Test
+  public void getHadoopAuthCookieValueWithValidlHeaders() {
+    Header[] headers = new Header[1];
+    headers[0] = new BasicHeader("Set-Cookie", AppCookieManager.HADOOP_AUTH + "=dummyvalue");
+    assertNotNull(AppCookieManager.getHadoopAuthCookieValue(headers));
+  }
+  
+}
+


Mime
View raw message