From b0d3c877e30312820124cac2eff737fddac9e484 Mon Sep 17 00:00:00 2001 From: Arpit Agarwal Date: Tue, 20 Feb 2018 18:18:58 -0800 Subject: [PATCH] HADOOP-12897. KerberosAuthenticator.authenticate to include URL on IO failures. Contributed by Ajay Kumar. --- .../client/KerberosAuthenticator.java | 80 ++++++++++++------- .../client/TestKerberosAuthenticator.java | 29 +++++++ .../hadoop/http/TestHttpServerWithSpengo.java | 5 +- .../org/apache/hadoop/log/TestLogLevel.java | 18 ++++- .../web/TestWebDelegationToken.java | 4 +- 5 files changed, 101 insertions(+), 35 deletions(-) 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 942d13c82c..64d43307ff 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 @@ -13,6 +13,8 @@ */ package org.apache.hadoop.security.authentication.client; +import com.google.common.annotations.VisibleForTesting; +import java.lang.reflect.Constructor; import org.apache.commons.codec.binary.Base64; import org.apache.hadoop.security.authentication.server.HttpConstants; import org.apache.hadoop.security.authentication.util.AuthToken; @@ -177,41 +179,65 @@ public void setConnectionConfigurator(ConnectionConfigurator configurator) { */ @Override public void authenticate(URL url, AuthenticatedURL.Token token) - throws IOException, AuthenticationException { + throws IOException, AuthenticationException { if (!token.isSet()) { this.url = url; base64 = new Base64(0); - HttpURLConnection conn = token.openConnection(url, connConfigurator); - conn.setRequestMethod(AUTH_HTTP_METHOD); - conn.connect(); - - boolean needFallback = false; - if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - LOG.debug("JDK performed authentication on our behalf."); - // If the JDK already did the SPNEGO back-and-forth for - // us, just pull out the token. - AuthenticatedURL.extractToken(conn, token); - if (isTokenKerberos(token)) { - return; + try { + HttpURLConnection conn = token.openConnection(url, connConfigurator); + conn.setRequestMethod(AUTH_HTTP_METHOD); + conn.connect(); + + boolean needFallback = false; + if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { + LOG.debug("JDK performed authentication on our behalf."); + // If the JDK already did the SPNEGO back-and-forth for + // us, just pull out the token. + AuthenticatedURL.extractToken(conn, token); + if (isTokenKerberos(token)) { + return; + } + needFallback = true; } - needFallback = true; - } - if (!needFallback && isNegotiate(conn)) { - LOG.debug("Performing our own SPNEGO sequence."); - doSpnegoSequence(token); - } else { - LOG.debug("Using fallback authenticator sequence."); - Authenticator auth = getFallBackAuthenticator(); - // Make sure that the fall back authenticator have the same - // ConnectionConfigurator, since the method might be overridden. - // Otherwise the fall back authenticator might not have the information - // to make the connection (e.g., SSL certificates) - auth.setConnectionConfigurator(connConfigurator); - auth.authenticate(url, token); + if (!needFallback && isNegotiate(conn)) { + LOG.debug("Performing our own SPNEGO sequence."); + doSpnegoSequence(token); + } else { + LOG.debug("Using fallback authenticator sequence."); + Authenticator auth = getFallBackAuthenticator(); + // Make sure that the fall back authenticator have the same + // ConnectionConfigurator, since the method might be overridden. + // Otherwise the fall back authenticator might not have the + // information to make the connection (e.g., SSL certificates) + auth.setConnectionConfigurator(connConfigurator); + auth.authenticate(url, token); + } + } catch (IOException ex){ + throw wrapExceptionWithMessage(ex, + "Error while authenticating with endpoint: " + url); + } catch (AuthenticationException ex){ + throw wrapExceptionWithMessage(ex, + "Error while authenticating with endpoint: " + url); } } } + @VisibleForTesting + static T wrapExceptionWithMessage( + T exception, String msg) { + Class exceptionClass = exception.getClass(); + try { + Constructor ctor = exceptionClass + .getConstructor(String.class); + Throwable t = ctor.newInstance(msg); + return (T) (t.initCause(exception)); + } catch (Throwable e) { + LOG.debug("Unable to wrap exception of type {}, it has " + + "no (String) constructor.", exceptionClass, e); + return exception; + } + } + /** * If the specified URL does not support SPNEGO authentication, a fallback {@link Authenticator} will be used. *

diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java index 7db53bae06..4aabb34fa5 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java +++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/client/TestKerberosAuthenticator.java @@ -20,6 +20,9 @@ import static org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler.KEYTAB; import static org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler.NAME_RULES; +import java.io.IOException; +import java.nio.charset.CharacterCodingException; +import javax.security.sasl.AuthenticationException; import org.apache.hadoop.minikdc.KerberosSecurityTestcase; import org.apache.hadoop.security.authentication.KerberosTestUtils; import org.apache.hadoop.security.authentication.server.AuthenticationFilter; @@ -218,4 +221,30 @@ public Void call() throws Exception { }); } + @Test(timeout = 60000) + public void testWrapExceptionWithMessage() { + IOException ex; + ex = new IOException("Induced exception"); + ex = KerberosAuthenticator.wrapExceptionWithMessage(ex, "Error while " + + "authenticating with endpoint: localhost"); + Assert.assertEquals("Induced exception", ex.getCause().getMessage()); + Assert.assertEquals("Error while authenticating with endpoint: localhost", + ex.getMessage()); + + ex = new AuthenticationException("Auth exception"); + ex = KerberosAuthenticator.wrapExceptionWithMessage(ex, "Error while " + + "authenticating with endpoint: localhost"); + Assert.assertEquals("Auth exception", ex.getCause().getMessage()); + Assert.assertEquals("Error while authenticating with endpoint: localhost", + ex.getMessage()); + + // Test for Exception with no (String) constructor + // redirect the LOG to and check log message + ex = new CharacterCodingException(); + Exception ex2 = KerberosAuthenticator.wrapExceptionWithMessage(ex, + "Error while authenticating with endpoint: localhost"); + Assert.assertTrue(ex instanceof CharacterCodingException); + Assert.assertTrue(ex.equals(ex2)); + } + } 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 8f5dd042e1..e1d7302c9a 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 @@ -383,8 +383,9 @@ public Void run() throws Exception { 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.class, + ae.getCause().getCause().getClass()); + GSSException gsse = (GSSException)ae.getCause().getCause(); Assert.assertEquals(GSSException.NO_CRED, gsse.getMajor()); } catch (Throwable t) { Assert.fail("Unexpected exception" + t); diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/log/TestLogLevel.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/log/TestLogLevel.java index 30bf72626f..16b4071ed8 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/log/TestLogLevel.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/log/TestLogLevel.java @@ -356,7 +356,10 @@ public void testLogLevelByHttp() throws Exception { fail("A HTTPS Client should not have succeeded in connecting to a " + "HTTP server"); } catch (SSLException e) { - GenericTestUtils.assertExceptionContains("Unrecognized SSL message", e); + GenericTestUtils.assertExceptionContains("Error while authenticating " + + "with endpoint", e); + GenericTestUtils.assertExceptionContains("Unrecognized SSL message", e + .getCause()); } } @@ -374,7 +377,10 @@ public void testLogLevelByHttpWithSpnego() throws Exception { fail("A HTTPS Client should not have succeeded in connecting to a " + "HTTP server"); } catch (SSLException e) { - GenericTestUtils.assertExceptionContains("Unrecognized SSL message", e); + GenericTestUtils.assertExceptionContains("Error while authenticating " + + "with endpoint", e); + GenericTestUtils.assertExceptionContains("Unrecognized SSL message", e + .getCause()); } } @@ -393,8 +399,10 @@ public void testLogLevelByHttps() throws Exception { fail("A HTTP Client should not have succeeded in connecting to a " + "HTTPS server"); } catch (SocketException e) { + GenericTestUtils.assertExceptionContains("Error while authenticating " + + "with endpoint", e); GenericTestUtils.assertExceptionContains( - "Unexpected end of file from server", e); + "Unexpected end of file from server", e.getCause()); } } @@ -413,8 +421,10 @@ public void testLogLevelByHttpsWithSpnego() throws Exception { fail("A HTTP Client should not have succeeded in connecting to a " + "HTTPS server"); } catch (SocketException e) { + GenericTestUtils.assertExceptionContains("Error while authenticating " + + "with endpoint", e); GenericTestUtils.assertExceptionContains( - "Unexpected end of file from server", e); + "Unexpected end of file from server", e.getCause()); } } } \ No newline at end of file diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java index c564b975f5..1fcc6faac6 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/token/delegation/web/TestWebDelegationToken.java @@ -364,7 +364,7 @@ private void testDelegationTokenAuthenticatorCalls(final boolean useQS) aUrl.getDelegationToken(nonAuthURL, token, FOO_USER); Assert.fail(); } catch (Exception ex) { - Assert.assertTrue(ex.getMessage().contains("401")); + Assert.assertTrue(ex.getCause().getMessage().contains("401")); } aUrl.getDelegationToken(authURL, token, FOO_USER); @@ -776,7 +776,7 @@ private void testKerberosDelegationTokenAuthenticator( aUrl.getDelegationToken(url, token, FOO_USER, doAsUser); Assert.fail(); } catch (AuthenticationException ex) { - Assert.assertTrue(ex.getMessage().contains("GSSException")); + Assert.assertTrue(ex.getCause().getMessage().contains("GSSException")); } doAsKerberosUser("client", keytabFile.getAbsolutePath(),