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)); > + } > + > +} > + > >
