First commit, Dilli?
Wahoo - Congrats!
On Wed, Aug 21, 2013 at 2:19 PM, <[email protected]> wrote:
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 <[email protected]>
Authored: Tue Aug 20 22:37:49 2013 -0700
Committer: Dilli Dorai Arumugam <[email protected]>
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="[email protected]"
- 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="[email protected]"
- 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));
+ }
+
+}
+