Skip to content

Commit

Permalink
HADOOP-11187 NameNode - KMS communication fails after a long period o…
Browse files Browse the repository at this point in the history
…f inactivity. Contributed by Arun Suresh.
  • Loading branch information
atm committed Nov 6, 2014
1 parent 86eb27b commit ef5af4f
Show file tree
Hide file tree
Showing 10 changed files with 49 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
import org.apache.hadoop.security.authentication.util.Signer;
import org.apache.hadoop.security.authentication.util.SignerException;
import org.apache.hadoop.security.authentication.util.RandomSignerSecretProvider;
Expand All @@ -36,6 +37,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.security.Principal;
import java.text.SimpleDateFormat;
Expand Down Expand Up @@ -565,6 +567,13 @@ public Principal getUserPrincipal() {
if (!httpResponse.isCommitted()) {
createAuthCookie(httpResponse, "", getCookieDomain(),
getCookiePath(), 0, isHttps);
// If response code is 401. Then WWW-Authenticate Header should be
// present.. reset to 403 if not found..
if ((errCode == HttpServletResponse.SC_UNAUTHORIZED)
&& (!httpResponse.containsHeader(
KerberosAuthenticator.WWW_AUTHENTICATE))) {
errCode = HttpServletResponse.SC_FORBIDDEN;
}
if (authenticationEx == null) {
httpResponse.sendError(errCode, "Authentication required");
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Properties;

Expand All @@ -30,6 +31,8 @@
*/
public interface AuthenticationHandler {

public static final String WWW_AUTHENTICATE = "WWW-Authenticate";

/**
* Returns the authentication type of the authentication handler.
* <p/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ public AuthenticationToken authenticate(HttpServletRequest request, final HttpSe
String authorization = request.getHeader(KerberosAuthenticator.AUTHORIZATION);

if (authorization == null || !authorization.startsWith(KerberosAuthenticator.NEGOTIATE)) {
response.setHeader(KerberosAuthenticator.WWW_AUTHENTICATE, KerberosAuthenticator.NEGOTIATE);
response.setHeader(WWW_AUTHENTICATE, KerberosAuthenticator.NEGOTIATE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
if (authorization == null) {
LOG.trace("SPNEGO starting");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@

import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.client.PseudoAuthenticator;

import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.NameValuePair;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
Expand Down Expand Up @@ -54,6 +54,9 @@ public class PseudoAuthenticationHandler implements AuthenticationHandler {
public static final String ANONYMOUS_ALLOWED = TYPE + ".anonymous.allowed";

private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");

private static final String PSEUDO_AUTH = "PseudoAuth";

private boolean acceptAnonymous;
private String type;

Expand Down Expand Up @@ -181,7 +184,9 @@ public AuthenticationToken authenticate(HttpServletRequest request, HttpServletR
if (getAcceptAnonymous()) {
token = AuthenticationToken.ANONYMOUS;
} else {
throw new AuthenticationException("Anonymous requests are disallowed");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setHeader(WWW_AUTHENTICATE, PSEUDO_AUTH);
token = null;
}
} else {
token = new AuthenticationToken(userName, userName, getType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,9 @@ public void testAnonymousDisallowed() throws Exception {
URL url = new URL(auth.getBaseURL());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
Assert.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode());
Assert.assertEquals("Anonymous requests are disallowed", conn.getResponseMessage());
Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
Assert.assertTrue(conn.getHeaderFields().containsKey("WWW-Authenticate"));
Assert.assertEquals("Authentication required", conn.getResponseMessage());
} finally {
auth.stop();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,11 +537,11 @@ public Object answer(InvocationOnMock invocation) throws Throwable {
}
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());

Mockito.when(response.containsHeader("WWW-Authenticate")).thenReturn(true);
filter.doFilter(request, response, chain);

Mockito.verify(response).sendError(
HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
Mockito.verify(response).setHeader("WWW-Authenticate", "dummyauth");
} finally {
filter.destroy();
}
Expand Down Expand Up @@ -852,6 +852,7 @@ public void testDoFilterAuthenticatedExpired() throws Exception {
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});

HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
Mockito.when(response.containsHeader("WWW-Authenticate")).thenReturn(true);
FilterChain chain = Mockito.mock(FilterChain.class);

verifyUnauthorized(filter, request, response, chain);
Expand Down Expand Up @@ -930,6 +931,7 @@ public void testDoFilterAuthenticatedInvalidType() throws Exception {
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});

HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
Mockito.when(response.containsHeader("WWW-Authenticate")).thenReturn(true);
FilterChain chain = Mockito.mock(FilterChain.class);

verifyUnauthorized(filter, request, response, chain);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.util.Properties;

public class TestPseudoAuthenticationHandler {
Expand Down Expand Up @@ -74,12 +75,8 @@ public void testAnonymousOff() throws Exception {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);

handler.authenticate(request, response);
Assert.fail();
} catch (AuthenticationException ex) {
// Expected
} catch (Exception ex) {
Assert.fail();
AuthenticationToken token = handler.authenticate(request, response);
Assert.assertNull(token);
} finally {
handler.destroy();
}
Expand Down
3 changes: 3 additions & 0 deletions hadoop-common-project/hadoop-common/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ Release 2.7.0 - UNRELEASED
HADOOP-11272. Allow ZKSignerSecretProvider and
ZKDelegationTokenSecretManager to use the same curator client. (Arun Suresh via atm)

HADOOP-11187 NameNode - KMS communication fails after a long period of
inactivity. (Arun Suresh via atm)

Release 2.6.0 - UNRELEASED

INCOMPATIBLE CHANGES
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
public class KMSClientProvider extends KeyProvider implements CryptoExtension,
KeyProviderDelegationTokenExtension.DelegationTokenExtension {

private static final String INVALID_SIGNATURE = "Invalid signature";

private static final String ANONYMOUS_REQUESTS_DISALLOWED = "Anonymous requests are disallowed";

public static final String TOKEN_KIND = "kms-dt";
Expand Down Expand Up @@ -453,7 +455,8 @@ private <T> T call(HttpURLConnection conn, Map jsonOutput,
throw ex;
}
if ((conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN
&& conn.getResponseMessage().equals(ANONYMOUS_REQUESTS_DISALLOWED))
&& (conn.getResponseMessage().equals(ANONYMOUS_REQUESTS_DISALLOWED) ||
conn.getResponseMessage().contains(INVALID_SIGNATURE)))
|| conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Ideally, this should happen only when there is an Authentication
// failure. Unfortunately, the AuthenticationFilter returns 403 when it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,7 @@ public void testKMSAuthFailureRetry() throws Exception {
keytab.getAbsolutePath());
conf.set("hadoop.kms.authentication.kerberos.principal", "HTTP/localhost");
conf.set("hadoop.kms.authentication.kerberos.name.rules", "DEFAULT");
conf.set("hadoop.kms.authentication.token.validity", "1");

for (KMSACLs.Type type : KMSACLs.Type.values()) {
conf.set(type.getAclConfigKey(), type.toString());
Expand Down Expand Up @@ -930,11 +931,16 @@ public Void call() throws Exception {
@Override
public Void run() throws Exception {
KMSClientProvider kp = new KMSClientProvider(uri, conf);

kp.createKey("k0", new byte[16],
new KeyProvider.Options(conf));
// This happens before rollover
kp.createKey("k1", new byte[16],
new KeyProvider.Options(conf));
makeAuthTokenStale(kp);
// Atleast 2 rollovers.. so should induce signer Exception
Thread.sleep(3500);
kp.createKey("k2", new byte[16],
new KeyProvider.Options(conf));
new KeyProvider.Options(conf));
return null;
}
});
Expand All @@ -958,15 +964,16 @@ public Void run() throws Exception {
KMSClientProvider kp = new KMSClientProvider(uri, conf);
kp.createKey("k3", new byte[16],
new KeyProvider.Options(conf));
makeAuthTokenStale(kp);
// Atleast 2 rollovers.. so should induce signer Exception
Thread.sleep(3500);
try {
kp.createKey("k4", new byte[16],
new KeyProvider.Options(conf));
Assert.fail("Shoud fail since retry count == 0");
Assert.fail("This should not succeed..");
} catch (IOException e) {
Assert.assertTrue(
"HTTP exception must be a 403 : " + e.getMessage(), e
.getMessage().contains("403"));
"HTTP exception must be a 401 : " + e.getMessage(), e
.getMessage().contains("401"));
}
return null;
}
Expand All @@ -976,19 +983,6 @@ public Void run() throws Exception {
});
}

private void makeAuthTokenStale(KMSClientProvider kp) throws Exception {
Field tokF = KMSClientProvider.class.getDeclaredField("authToken");
tokF.setAccessible(true);
DelegationTokenAuthenticatedURL.Token delToken =
(DelegationTokenAuthenticatedURL.Token) tokF.get(kp);
String oldTokStr = delToken.toString();
Method setM =
AuthenticatedURL.Token.class.getDeclaredMethod("set", String.class);
setM.setAccessible(true);
String newTokStr = oldTokStr.replaceAll("e=[^&]*", "e=1000");
setM.invoke(((AuthenticatedURL.Token)delToken), newTokStr);
}

@Test
public void testACLs() throws Exception {
Configuration conf = new Configuration();
Expand Down

0 comments on commit ef5af4f

Please sign in to comment.