You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by wa...@apache.org on 2017/08/22 23:15:44 UTC
[10/42] hadoop git commit: HADOOP-14687. AuthenticatedURL will reuse
bad/expired session cookies. Contributed by Daryn Sharp
HADOOP-14687. AuthenticatedURL will reuse bad/expired session cookies. Contributed by Daryn Sharp
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/c3793102
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/c3793102
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/c3793102
Branch: refs/heads/YARN-3926
Commit: c3793102121767c46091805eae65ef3919a5f368
Parents: 657dd59
Author: Jason Lowe <jl...@apache.org>
Authored: Tue Aug 22 16:50:01 2017 -0500
Committer: Jason Lowe <jl...@apache.org>
Committed: Tue Aug 22 16:50:01 2017 -0500
----------------------------------------------------------------------
.../authentication/client/AuthenticatedURL.java | 184 +++++++++++---
.../client/KerberosAuthenticator.java | 30 +--
.../client/PseudoAuthenticator.java | 5 +-
.../crypto/key/kms/KMSClientProvider.java | 13 -
.../hadoop/http/TestHttpServerWithSpengo.java | 242 ++++++++++++++++++-
5 files changed, 403 insertions(+), 71 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hadoop/blob/c3793102/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java
index 5696ba0..1093d8a 100644
--- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/AuthenticatedURL.java
@@ -19,8 +19,14 @@ import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.HttpCookie;
import java.net.HttpURLConnection;
+import java.net.URI;
import java.net.URL;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -69,14 +75,99 @@ public class AuthenticatedURL {
*/
public static final String AUTH_COOKIE = "hadoop.auth";
- private static final String AUTH_COOKIE_EQ = AUTH_COOKIE + "=";
+ // a lightweight cookie handler that will be attached to url connections.
+ // client code is not required to extract or inject auth cookies.
+ private static class AuthCookieHandler extends CookieHandler {
+ private HttpCookie authCookie;
+ private Map<String, List<String>> cookieHeaders = Collections.emptyMap();
+
+ @Override
+ public synchronized Map<String, List<String>> get(URI uri,
+ Map<String, List<String>> requestHeaders) throws IOException {
+ // call getter so it will reset headers if token is expiring.
+ getAuthCookie();
+ return cookieHeaders;
+ }
+
+ @Override
+ public void put(URI uri, Map<String, List<String>> responseHeaders) {
+ List<String> headers = responseHeaders.get("Set-Cookie");
+ if (headers != null) {
+ for (String header : headers) {
+ List<HttpCookie> cookies;
+ try {
+ cookies = HttpCookie.parse(header);
+ } catch (IllegalArgumentException iae) {
+ // don't care. just skip malformed cookie headers.
+ LOG.debug("Cannot parse cookie header: " + header, iae);
+ continue;
+ }
+ for (HttpCookie cookie : cookies) {
+ if (AUTH_COOKIE.equals(cookie.getName())) {
+ setAuthCookie(cookie);
+ }
+ }
+ }
+ }
+ }
+
+ // return the auth cookie if still valid.
+ private synchronized HttpCookie getAuthCookie() {
+ if (authCookie != null && authCookie.hasExpired()) {
+ setAuthCookie(null);
+ }
+ return authCookie;
+ }
+
+ private synchronized void setAuthCookie(HttpCookie cookie) {
+ final HttpCookie oldCookie = authCookie;
+ // will redefine if new cookie is valid.
+ authCookie = null;
+ cookieHeaders = Collections.emptyMap();
+ boolean valid = cookie != null && !cookie.getValue().isEmpty() &&
+ !cookie.hasExpired();
+ if (valid) {
+ // decrease lifetime to avoid using a cookie soon to expire.
+ // allows authenticators to pre-emptively reauthenticate to
+ // prevent clients unnecessarily receiving a 401.
+ long maxAge = cookie.getMaxAge();
+ if (maxAge != -1) {
+ cookie.setMaxAge(maxAge * 9/10);
+ valid = !cookie.hasExpired();
+ }
+ }
+ if (valid) {
+ // v0 cookies value aren't quoted by default but tomcat demands
+ // quoting.
+ if (cookie.getVersion() == 0) {
+ String value = cookie.getValue();
+ if (!value.startsWith("\"")) {
+ value = "\"" + value + "\"";
+ cookie.setValue(value);
+ }
+ }
+ authCookie = cookie;
+ cookieHeaders = new HashMap<>();
+ cookieHeaders.put("Cookie", Arrays.asList(cookie.toString()));
+ }
+ LOG.trace("Setting token value to {} ({})", authCookie, oldCookie);
+ }
+
+ private void setAuthCookieValue(String value) {
+ HttpCookie c = null;
+ if (value != null) {
+ c = new HttpCookie(AUTH_COOKIE, value);
+ }
+ setAuthCookie(c);
+ }
+ }
/**
* Client side authentication token.
*/
public static class Token {
- private String token;
+ private final AuthCookieHandler cookieHandler = new AuthCookieHandler();
/**
* Creates a token.
@@ -102,7 +193,7 @@ public class AuthenticatedURL {
* @return if a token from the server has been set.
*/
public boolean isSet() {
- return token != null;
+ return cookieHandler.getAuthCookie() != null;
}
/**
@@ -111,7 +202,36 @@ public class AuthenticatedURL {
* @param tokenStr string representation of the tokenStr.
*/
void set(String tokenStr) {
- token = tokenStr;
+ cookieHandler.setAuthCookieValue(tokenStr);
+ }
+
+ /**
+ * Installs a cookie handler for the http request to manage session
+ * cookies.
+ * @param url
+ * @return HttpUrlConnection
+ * @throws IOException
+ */
+ HttpURLConnection openConnection(URL url,
+ ConnectionConfigurator connConfigurator) throws IOException {
+ // the cookie handler is unfortunately a global static. it's a
+ // synchronized class method so we can safely swap the handler while
+ // instantiating the connection object to prevent it leaking into
+ // other connections.
+ final HttpURLConnection conn;
+ synchronized(CookieHandler.class) {
+ CookieHandler current = CookieHandler.getDefault();
+ CookieHandler.setDefault(cookieHandler);
+ try {
+ conn = (HttpURLConnection)url.openConnection();
+ } finally {
+ CookieHandler.setDefault(current);
+ }
+ }
+ if (connConfigurator != null) {
+ connConfigurator.configure(conn);
+ }
+ return conn;
}
/**
@@ -121,7 +241,15 @@ public class AuthenticatedURL {
*/
@Override
public String toString() {
- return token;
+ String value = "";
+ HttpCookie authCookie = cookieHandler.getAuthCookie();
+ if (authCookie != null) {
+ value = authCookie.getValue();
+ if (value.startsWith("\"")) { // tests don't want the quotes.
+ value = value.substring(1, value.length()-1);
+ }
+ }
+ return value;
}
}
@@ -218,27 +346,25 @@ public class AuthenticatedURL {
throw new IllegalArgumentException("token cannot be NULL");
}
authenticator.authenticate(url, token);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- if (connConfigurator != null) {
- conn = connConfigurator.configure(conn);
- }
- injectToken(conn, token);
- return conn;
+
+ // allow the token to create the connection with a cookie handler for
+ // managing session cookies.
+ return token.openConnection(url, connConfigurator);
}
/**
- * Helper method that injects an authentication token to send with a connection.
+ * Helper method that injects an authentication token to send with a
+ * connection. Callers should prefer using
+ * {@link Token#openConnection(URL, ConnectionConfigurator)} which
+ * automatically manages authentication tokens.
*
* @param conn connection to inject the authentication token into.
* @param token authentication token to inject.
*/
public static void injectToken(HttpURLConnection conn, Token token) {
- String t = token.token;
- if (t != null) {
- if (!t.startsWith("\"")) {
- t = "\"" + t + "\"";
- }
- conn.addRequestProperty("Cookie", AUTH_COOKIE_EQ + t);
+ HttpCookie authCookie = token.cookieHandler.getAuthCookie();
+ if (authCookie != null) {
+ conn.addRequestProperty("Cookie", authCookie.toString());
}
}
@@ -258,24 +384,10 @@ public class AuthenticatedURL {
if (respCode == HttpURLConnection.HTTP_OK
|| respCode == HttpURLConnection.HTTP_CREATED
|| respCode == HttpURLConnection.HTTP_ACCEPTED) {
- Map<String, List<String>> headers = conn.getHeaderFields();
- List<String> cookies = headers.get("Set-Cookie");
- if (cookies != null) {
- for (String cookie : cookies) {
- if (cookie.startsWith(AUTH_COOKIE_EQ)) {
- String value = cookie.substring(AUTH_COOKIE_EQ.length());
- int separator = value.indexOf(";");
- if (separator > -1) {
- value = value.substring(0, separator);
- }
- if (value.length() > 0) {
- LOG.trace("Setting token value to {} ({}), resp={}", value,
- token, respCode);
- token.set(value);
- }
- }
- }
- }
+ // cookie handler should have already extracted the token. try again
+ // for backwards compatibility if this method is called on a connection
+ // not opened via this instance.
+ token.cookieHandler.put(null, conn.getHeaderFields());
} else if (respCode == HttpURLConnection.HTTP_NOT_FOUND) {
LOG.trace("Setting token value to null ({}), resp={}", token, respCode);
token.set(null);
http://git-wip-us.apache.org/repos/asf/hadoop/blob/c3793102/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java
index 9bcebc3..942d13c 100644
--- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/KerberosAuthenticator.java
@@ -147,7 +147,6 @@ public class KerberosAuthenticator implements Authenticator {
}
private URL url;
- private HttpURLConnection conn;
private Base64 base64;
private ConnectionConfigurator connConfigurator;
@@ -182,10 +181,7 @@ public class KerberosAuthenticator implements Authenticator {
if (!token.isSet()) {
this.url = url;
base64 = new Base64(0);
- conn = (HttpURLConnection) url.openConnection();
- if (connConfigurator != null) {
- conn = connConfigurator.configure(conn);
- }
+ HttpURLConnection conn = token.openConnection(url, connConfigurator);
conn.setRequestMethod(AUTH_HTTP_METHOD);
conn.connect();
@@ -200,7 +196,7 @@ public class KerberosAuthenticator implements Authenticator {
}
needFallback = true;
}
- if (!needFallback && isNegotiate()) {
+ if (!needFallback && isNegotiate(conn)) {
LOG.debug("Performing our own SPNEGO sequence.");
doSpnegoSequence(token);
} else {
@@ -249,7 +245,7 @@ public class KerberosAuthenticator implements Authenticator {
/*
* Indicates if the response is starting a SPNEGO negotiation.
*/
- private boolean isNegotiate() throws IOException {
+ private boolean isNegotiate(HttpURLConnection conn) throws IOException {
boolean negotiate = false;
if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
@@ -267,7 +263,8 @@ public class KerberosAuthenticator implements Authenticator {
* @throws IOException if an IO error occurred.
* @throws AuthenticationException if an authentication error occurred.
*/
- private void doSpnegoSequence(AuthenticatedURL.Token token) throws IOException, AuthenticationException {
+ private void doSpnegoSequence(final AuthenticatedURL.Token token)
+ throws IOException, AuthenticationException {
try {
AccessControlContext context = AccessController.getContext();
Subject subject = Subject.getSubject(context);
@@ -308,13 +305,15 @@ public class KerberosAuthenticator implements Authenticator {
// Loop while the context is still not established
while (!established) {
+ HttpURLConnection conn =
+ token.openConnection(url, connConfigurator);
outToken = gssContext.initSecContext(inToken, 0, inToken.length);
if (outToken != null) {
- sendToken(outToken);
+ sendToken(conn, outToken);
}
if (!gssContext.isEstablished()) {
- inToken = readToken();
+ inToken = readToken(conn);
} else {
established = true;
}
@@ -337,18 +336,14 @@ public class KerberosAuthenticator implements Authenticator {
} catch (LoginException ex) {
throw new AuthenticationException(ex);
}
- AuthenticatedURL.extractToken(conn, token);
}
/*
* Sends the Kerberos token to the server.
*/
- private void sendToken(byte[] outToken) throws IOException {
+ private void sendToken(HttpURLConnection conn, byte[] outToken)
+ throws IOException {
String token = base64.encodeToString(outToken);
- conn = (HttpURLConnection) url.openConnection();
- if (connConfigurator != null) {
- conn = connConfigurator.configure(conn);
- }
conn.setRequestMethod(AUTH_HTTP_METHOD);
conn.setRequestProperty(AUTHORIZATION, NEGOTIATE + " " + token);
conn.connect();
@@ -357,7 +352,8 @@ public class KerberosAuthenticator implements Authenticator {
/*
* Retrieves the Kerberos token returned by the server.
*/
- private byte[] readToken() throws IOException, AuthenticationException {
+ private byte[] readToken(HttpURLConnection conn)
+ throws IOException, AuthenticationException {
int status = conn.getResponseCode();
if (status == HttpURLConnection.HTTP_OK || status == HttpURLConnection.HTTP_UNAUTHORIZED) {
String authHeader = conn.getHeaderField(WWW_AUTHENTICATE);
http://git-wip-us.apache.org/repos/asf/hadoop/blob/c3793102/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/PseudoAuthenticator.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/PseudoAuthenticator.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/PseudoAuthenticator.java
index 29ca9cf..66c6252 100644
--- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/PseudoAuthenticator.java
+++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/client/PseudoAuthenticator.java
@@ -68,10 +68,7 @@ public class PseudoAuthenticator implements Authenticator {
String paramSeparator = (strUrl.contains("?")) ? "&" : "?";
strUrl += paramSeparator + USER_NAME_EQ + getUserName();
url = new URL(strUrl);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- if (connConfigurator != null) {
- conn = connConfigurator.configure(conn);
- }
+ HttpURLConnection conn = token.openConnection(url, connConfigurator);
conn.setRequestMethod("OPTIONS");
conn.connect();
AuthenticatedURL.extractToken(conn, token);
http://git-wip-us.apache.org/repos/asf/hadoop/blob/c3793102/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java
index af0dd82..9bef32c 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/crypto/key/kms/KMSClientProvider.java
@@ -32,8 +32,6 @@ import org.apache.hadoop.security.Credentials;
import org.apache.hadoop.security.ProviderUtils;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
-import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
-import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.client.ConnectionConfigurator;
import org.apache.hadoop.security.ssl.SSLFactory;
import org.apache.hadoop.security.token.Token;
@@ -522,17 +520,6 @@ public class KMSClientProvider extends KeyProvider implements CryptoExtension,
authRetryCount - 1);
}
}
- try {
- AuthenticatedURL.extractToken(conn, authToken);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Extracted token, authToken={}, its dt={}", authToken,
- authToken.getDelegationToken());
- }
- } catch (AuthenticationException e) {
- // Ignore the AuthExceptions.. since we are just using the method to
- // extract and set the authToken.. (Workaround till we actually fix
- // AuthenticatedURL properly to set authToken post initialization)
- }
HttpExceptionUtils.validateResponse(conn, expectedResponse);
if (conn.getContentType() != null
&& conn.getContentType().trim().toLowerCase()
http://git-wip-us.apache.org/repos/asf/hadoop/blob/c3793102/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java
----------------------------------------------------------------------
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java
index 5239ed6..8f5dd04 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java
@@ -22,9 +22,12 @@ import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.hadoop.net.NetUtils;
import org.apache.hadoop.security.AuthenticationFilterInitializer;
+import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
import org.apache.hadoop.security.authentication.KerberosTestUtils;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
+import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
import org.apache.hadoop.security.authentication.server.AuthenticationToken;
import org.apache.hadoop.security.authentication.util.Signer;
@@ -32,6 +35,7 @@ import org.apache.hadoop.security.authentication.util.SignerSecretProvider;
import org.apache.hadoop.security.authentication.util.StringSignerSecretProviderCreator;
import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.hadoop.security.authorize.ProxyUsers;
+import org.ietf.jgss.GSSException;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -45,7 +49,14 @@ import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashSet;
import java.util.Properties;
+import java.util.Set;
+import javax.security.auth.Subject;
+import javax.servlet.ServletContext;
+
import static org.junit.Assert.assertTrue;
/**
@@ -72,16 +83,25 @@ public class TestHttpServerWithSpengo {
private static MiniKdc testMiniKDC;
private static File secretFile = new File(testRootDir, SECRET_STR);
+ private static UserGroupInformation authUgi;
+
@BeforeClass
public static void setUp() throws Exception {
try {
testMiniKDC = new MiniKdc(MiniKdc.createConf(), testRootDir);
testMiniKDC.start();
testMiniKDC.createPrincipal(
- httpSpnegoKeytabFile, HTTP_USER + "/localhost");
+ httpSpnegoKeytabFile, HTTP_USER + "/localhost", "keytab-user");
} catch (Exception e) {
assertTrue("Couldn't setup MiniKDC", false);
}
+
+ System.setProperty("sun.security.krb5.debug", "true");
+ Configuration conf = new Configuration();
+ SecurityUtil.setAuthenticationMethod(AuthenticationMethod.KERBEROS, conf);
+ UserGroupInformation.setConfiguration(conf);
+ authUgi = UserGroupInformation.loginUserFromKeytabAndReturnUGI(
+ "keytab-user", httpSpnegoKeytabFile.toString());
Writer w = new FileWriter(secretFile);
w.write("secret");
w.close();
@@ -209,6 +229,226 @@ public class TestHttpServerWithSpengo {
}
}
+ @Test
+ public void testSessionCookie() throws Exception {
+ Configuration conf = new Configuration();
+ conf.set(HttpServer2.FILTER_INITIALIZER_PROPERTY,
+ AuthenticationFilterInitializer.class.getName());
+ conf.set(PREFIX + "type", "kerberos");
+ conf.setBoolean(PREFIX + "simple.anonymous.allowed", false);
+ conf.set(PREFIX + "signer.secret.provider",
+ TestSignerSecretProvider.class.getName());
+
+ conf.set(PREFIX + "kerberos.keytab",
+ httpSpnegoKeytabFile.getAbsolutePath());
+ conf.set(PREFIX + "kerberos.principal", httpSpnegoPrincipal);
+ conf.set(PREFIX + "cookie.domain", realm);
+ conf.setBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION,
+ true);
+
+ //setup logs dir
+ System.setProperty("hadoop.log.dir", testRootDir.getAbsolutePath());
+
+ HttpServer2 httpServer = null;
+ // Create http server to test.
+ httpServer = getCommonBuilder()
+ .setConf(conf)
+ .build();
+ httpServer.start();
+
+ // Get signer to encrypt token
+ final Signer signer = new Signer(new TestSignerSecretProvider());
+ final AuthenticatedURL authUrl = new AuthenticatedURL();
+
+ final URL url = new URL("http://" + NetUtils.getHostPortString(
+ httpServer.getConnectorAddress(0)) + "/conf");
+
+ // this illustrates an inconsistency with AuthenticatedURL. the
+ // authenticator is only called when the token is not set. if the
+ // authenticator fails then it must throw an AuthenticationException to
+ // the caller, yet the caller may see 401 for subsequent requests
+ // that require re-authentication like token expiration.
+ final UserGroupInformation simpleUgi =
+ UserGroupInformation.createRemoteUser("simple-user");
+
+ authUgi.doAs(new PrivilegedExceptionAction<Void>() {
+ @Override
+ public Void run() throws Exception {
+ TestSignerSecretProvider.rollSecret();
+ HttpURLConnection conn = null;
+ AuthenticatedURL.Token token = new AuthenticatedURL.Token();
+
+ // initial request should trigger authentication and set the token.
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ String cookie = token.toString();
+
+ // token should not change.
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ Assert.assertEquals(cookie, token.toString());
+
+ // roll secret to invalidate token.
+ TestSignerSecretProvider.rollSecret();
+ conn = authUrl.openConnection(url, token);
+ // this may or may not happen. under normal circumstances the
+ // jdk will silently renegotiate and the client never sees a 401.
+ // however in some cases the jdk will give up doing spnego. since
+ // the token is already set, the authenticator isn't invoked (which
+ // would do the spnego if the jdk doesn't), which causes the client
+ // to see a 401.
+ if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+ // if this happens, the token should be cleared which means the
+ // next request should succeed and receive a new token.
+ Assert.assertFalse(token.isSet());
+ conn = authUrl.openConnection(url, token);
+ }
+
+ // token should change.
+ Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ Assert.assertNotEquals(cookie, token.toString());
+ cookie = token.toString();
+
+ // token should not change.
+ for (int i=0; i < 3; i++) {
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals("attempt"+i,
+ HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ Assert.assertEquals(cookie, token.toString());
+ }
+
+ // blow out the kerberos creds test only auth token is used.
+ Subject s = Subject.getSubject(AccessController.getContext());
+ Set<Object> oldCreds = new HashSet<>(s.getPrivateCredentials());
+ s.getPrivateCredentials().clear();
+
+ // token should not change.
+ for (int i=0; i < 3; i++) {
+ try {
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals("attempt"+i,
+ HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ } catch (AuthenticationException ae) {
+ Assert.fail("attempt"+i+" "+ae);
+ }
+ Assert.assertTrue(token.isSet());
+ Assert.assertEquals(cookie, token.toString());
+ }
+
+ // invalidate token. connections should fail now and token should be
+ // unset.
+ TestSignerSecretProvider.rollSecret();
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals(
+ HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
+ Assert.assertFalse(token.isSet());
+ Assert.assertEquals("", token.toString());
+
+ // restore the kerberos creds, should work again.
+ s.getPrivateCredentials().addAll(oldCreds);
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals(
+ HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ cookie = token.toString();
+
+ // token should not change.
+ for (int i=0; i < 3; i++) {
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals("attempt"+i,
+ HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ Assert.assertEquals(cookie, token.toString());
+ }
+ return null;
+ }
+ });
+
+ simpleUgi.doAs(new PrivilegedExceptionAction<Void>() {
+ @Override
+ public Void run() throws Exception {
+ TestSignerSecretProvider.rollSecret();
+ AuthenticatedURL authUrl = new AuthenticatedURL();
+ AuthenticatedURL.Token token = new AuthenticatedURL.Token();
+ HttpURLConnection conn = null;
+
+ // initial connect with unset token will trigger authenticator which
+ // should fail since we have no creds and leave token unset.
+ try {
+ authUrl.openConnection(url, token);
+ Assert.fail("should fail with no credentials");
+ } catch (AuthenticationException ae) {
+ Assert.assertNotNull(ae.getCause());
+ Assert.assertEquals(GSSException.class, ae.getCause().getClass());
+ GSSException gsse = (GSSException)ae.getCause();
+ Assert.assertEquals(GSSException.NO_CRED, gsse.getMajor());
+ } catch (Throwable t) {
+ Assert.fail("Unexpected exception" + t);
+ }
+ Assert.assertFalse(token.isSet());
+
+ // create a valid token and save its value.
+ token = getEncryptedAuthToken(signer, "valid");
+ String cookie = token.toString();
+
+ // server should accept token. after the request the token should
+ // be set to the same value (ie. server didn't reissue cookie)
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ Assert.assertEquals(cookie, token.toString());
+
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ Assert.assertTrue(token.isSet());
+ Assert.assertEquals(cookie, token.toString());
+
+ // change the secret to effectively invalidate the cookie. see above
+ // regarding inconsistency. the authenticator has no way to know the
+ // token is bad, so the client will encounter a 401 instead of
+ // AuthenticationException.
+ TestSignerSecretProvider.rollSecret();
+ conn = authUrl.openConnection(url, token);
+ Assert.assertEquals(
+ HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
+ Assert.assertFalse(token.isSet());
+ Assert.assertEquals("", token.toString());
+ return null;
+ }
+ });
+ }
+
+ public static class TestSignerSecretProvider extends SignerSecretProvider {
+ static int n = 0;
+ static byte[] secret;
+
+ static void rollSecret() {
+ secret = ("secret[" + (n++) + "]").getBytes();
+ }
+
+ public TestSignerSecretProvider() {
+ }
+
+ @Override
+ public void init(Properties config, ServletContext servletContext,
+ long tokenValidity) throws Exception {
+ rollSecret();
+ }
+
+ @Override
+ public byte[] getCurrentSecret() {
+ return secret;
+ }
+
+ @Override
+ public byte[][] getAllSecrets() {
+ return new byte[][]{secret};
+ }
+ }
private AuthenticatedURL.Token getEncryptedAuthToken(Signer signer,
String user) throws Exception {
---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org