HADOOP-11187 NameNode - KMS communication fails after a long period of inactivity. Contributed by Arun Suresh.
This commit is contained in:
parent
86eb27ba1d
commit
ef5af4f8de
|
@ -17,6 +17,7 @@ import org.apache.hadoop.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.classification.InterfaceStability;
|
import org.apache.hadoop.classification.InterfaceStability;
|
||||||
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
|
import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
|
||||||
import org.apache.hadoop.security.authentication.client.AuthenticationException;
|
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.Signer;
|
||||||
import org.apache.hadoop.security.authentication.util.SignerException;
|
import org.apache.hadoop.security.authentication.util.SignerException;
|
||||||
import org.apache.hadoop.security.authentication.util.RandomSignerSecretProvider;
|
import org.apache.hadoop.security.authentication.util.RandomSignerSecretProvider;
|
||||||
|
@ -36,6 +37,7 @@ import javax.servlet.http.Cookie;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
@ -565,6 +567,13 @@ public class AuthenticationFilter implements Filter {
|
||||||
if (!httpResponse.isCommitted()) {
|
if (!httpResponse.isCommitted()) {
|
||||||
createAuthCookie(httpResponse, "", getCookieDomain(),
|
createAuthCookie(httpResponse, "", getCookieDomain(),
|
||||||
getCookiePath(), 0, isHttps);
|
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) {
|
if (authenticationEx == null) {
|
||||||
httpResponse.sendError(errCode, "Authentication required");
|
httpResponse.sendError(errCode, "Authentication required");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.apache.hadoop.security.authentication.client.AuthenticationException;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ import java.util.Properties;
|
||||||
*/
|
*/
|
||||||
public interface AuthenticationHandler {
|
public interface AuthenticationHandler {
|
||||||
|
|
||||||
|
public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the authentication type of the authentication handler.
|
* Returns the authentication type of the authentication handler.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|
|
@ -331,7 +331,7 @@ public class KerberosAuthenticationHandler implements AuthenticationHandler {
|
||||||
String authorization = request.getHeader(KerberosAuthenticator.AUTHORIZATION);
|
String authorization = request.getHeader(KerberosAuthenticator.AUTHORIZATION);
|
||||||
|
|
||||||
if (authorization == null || !authorization.startsWith(KerberosAuthenticator.NEGOTIATE)) {
|
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);
|
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
if (authorization == null) {
|
if (authorization == null) {
|
||||||
LOG.trace("SPNEGO starting");
|
LOG.trace("SPNEGO starting");
|
||||||
|
|
|
@ -15,13 +15,13 @@ package org.apache.hadoop.security.authentication.server;
|
||||||
|
|
||||||
import org.apache.hadoop.security.authentication.client.AuthenticationException;
|
import org.apache.hadoop.security.authentication.client.AuthenticationException;
|
||||||
import org.apache.hadoop.security.authentication.client.PseudoAuthenticator;
|
import org.apache.hadoop.security.authentication.client.PseudoAuthenticator;
|
||||||
|
|
||||||
import org.apache.http.client.utils.URLEncodedUtils;
|
import org.apache.http.client.utils.URLEncodedUtils;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -54,6 +54,9 @@ public class PseudoAuthenticationHandler implements AuthenticationHandler {
|
||||||
public static final String ANONYMOUS_ALLOWED = TYPE + ".anonymous.allowed";
|
public static final String ANONYMOUS_ALLOWED = TYPE + ".anonymous.allowed";
|
||||||
|
|
||||||
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
|
private static final Charset UTF8_CHARSET = Charset.forName("UTF-8");
|
||||||
|
|
||||||
|
private static final String PSEUDO_AUTH = "PseudoAuth";
|
||||||
|
|
||||||
private boolean acceptAnonymous;
|
private boolean acceptAnonymous;
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
@ -181,7 +184,9 @@ public class PseudoAuthenticationHandler implements AuthenticationHandler {
|
||||||
if (getAcceptAnonymous()) {
|
if (getAcceptAnonymous()) {
|
||||||
token = AuthenticationToken.ANONYMOUS;
|
token = AuthenticationToken.ANONYMOUS;
|
||||||
} else {
|
} else {
|
||||||
throw new AuthenticationException("Anonymous requests are disallowed");
|
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
response.setHeader(WWW_AUTHENTICATE, PSEUDO_AUTH);
|
||||||
|
token = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
token = new AuthenticationToken(userName, userName, getType());
|
token = new AuthenticationToken(userName, userName, getType());
|
||||||
|
|
|
@ -63,8 +63,9 @@ public class TestPseudoAuthenticator {
|
||||||
URL url = new URL(auth.getBaseURL());
|
URL url = new URL(auth.getBaseURL());
|
||||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||||
conn.connect();
|
conn.connect();
|
||||||
Assert.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode());
|
Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, conn.getResponseCode());
|
||||||
Assert.assertEquals("Anonymous requests are disallowed", conn.getResponseMessage());
|
Assert.assertTrue(conn.getHeaderFields().containsKey("WWW-Authenticate"));
|
||||||
|
Assert.assertEquals("Authentication required", conn.getResponseMessage());
|
||||||
} finally {
|
} finally {
|
||||||
auth.stop();
|
auth.stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -537,11 +537,11 @@ public class TestAuthenticationFilter {
|
||||||
}
|
}
|
||||||
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());
|
).when(chain).doFilter(Mockito.<ServletRequest>anyObject(), Mockito.<ServletResponse>anyObject());
|
||||||
|
|
||||||
|
Mockito.when(response.containsHeader("WWW-Authenticate")).thenReturn(true);
|
||||||
filter.doFilter(request, response, chain);
|
filter.doFilter(request, response, chain);
|
||||||
|
|
||||||
Mockito.verify(response).sendError(
|
Mockito.verify(response).sendError(
|
||||||
HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
|
HttpServletResponse.SC_UNAUTHORIZED, "Authentication required");
|
||||||
Mockito.verify(response).setHeader("WWW-Authenticate", "dummyauth");
|
|
||||||
} finally {
|
} finally {
|
||||||
filter.destroy();
|
filter.destroy();
|
||||||
}
|
}
|
||||||
|
@ -852,6 +852,7 @@ public class TestAuthenticationFilter {
|
||||||
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
|
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
|
||||||
|
|
||||||
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(response.containsHeader("WWW-Authenticate")).thenReturn(true);
|
||||||
FilterChain chain = Mockito.mock(FilterChain.class);
|
FilterChain chain = Mockito.mock(FilterChain.class);
|
||||||
|
|
||||||
verifyUnauthorized(filter, request, response, chain);
|
verifyUnauthorized(filter, request, response, chain);
|
||||||
|
@ -930,6 +931,7 @@ public class TestAuthenticationFilter {
|
||||||
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
|
Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie});
|
||||||
|
|
||||||
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(response.containsHeader("WWW-Authenticate")).thenReturn(true);
|
||||||
FilterChain chain = Mockito.mock(FilterChain.class);
|
FilterChain chain = Mockito.mock(FilterChain.class);
|
||||||
|
|
||||||
verifyUnauthorized(filter, request, response, chain);
|
verifyUnauthorized(filter, request, response, chain);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.mockito.Mockito;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
public class TestPseudoAuthenticationHandler {
|
public class TestPseudoAuthenticationHandler {
|
||||||
|
@ -74,12 +75,8 @@ public class TestPseudoAuthenticationHandler {
|
||||||
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
||||||
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
|
||||||
handler.authenticate(request, response);
|
AuthenticationToken token = handler.authenticate(request, response);
|
||||||
Assert.fail();
|
Assert.assertNull(token);
|
||||||
} catch (AuthenticationException ex) {
|
|
||||||
// Expected
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Assert.fail();
|
|
||||||
} finally {
|
} finally {
|
||||||
handler.destroy();
|
handler.destroy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -412,6 +412,9 @@ Release 2.7.0 - UNRELEASED
|
||||||
HADOOP-11272. Allow ZKSignerSecretProvider and
|
HADOOP-11272. Allow ZKSignerSecretProvider and
|
||||||
ZKDelegationTokenSecretManager to use the same curator client. (Arun Suresh via atm)
|
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
|
Release 2.6.0 - UNRELEASED
|
||||||
|
|
||||||
INCOMPATIBLE CHANGES
|
INCOMPATIBLE CHANGES
|
||||||
|
|
|
@ -81,6 +81,8 @@ import com.google.common.base.Preconditions;
|
||||||
public class KMSClientProvider extends KeyProvider implements CryptoExtension,
|
public class KMSClientProvider extends KeyProvider implements CryptoExtension,
|
||||||
KeyProviderDelegationTokenExtension.DelegationTokenExtension {
|
KeyProviderDelegationTokenExtension.DelegationTokenExtension {
|
||||||
|
|
||||||
|
private static final String INVALID_SIGNATURE = "Invalid signature";
|
||||||
|
|
||||||
private static final String ANONYMOUS_REQUESTS_DISALLOWED = "Anonymous requests are disallowed";
|
private static final String ANONYMOUS_REQUESTS_DISALLOWED = "Anonymous requests are disallowed";
|
||||||
|
|
||||||
public static final String TOKEN_KIND = "kms-dt";
|
public static final String TOKEN_KIND = "kms-dt";
|
||||||
|
@ -453,7 +455,8 @@ public class KMSClientProvider extends KeyProvider implements CryptoExtension,
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
if ((conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN
|
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) {
|
|| conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
|
||||||
// Ideally, this should happen only when there is an Authentication
|
// Ideally, this should happen only when there is an Authentication
|
||||||
// failure. Unfortunately, the AuthenticationFilter returns 403 when it
|
// failure. Unfortunately, the AuthenticationFilter returns 403 when it
|
||||||
|
|
|
@ -900,6 +900,7 @@ public class TestKMS {
|
||||||
keytab.getAbsolutePath());
|
keytab.getAbsolutePath());
|
||||||
conf.set("hadoop.kms.authentication.kerberos.principal", "HTTP/localhost");
|
conf.set("hadoop.kms.authentication.kerberos.principal", "HTTP/localhost");
|
||||||
conf.set("hadoop.kms.authentication.kerberos.name.rules", "DEFAULT");
|
conf.set("hadoop.kms.authentication.kerberos.name.rules", "DEFAULT");
|
||||||
|
conf.set("hadoop.kms.authentication.token.validity", "1");
|
||||||
|
|
||||||
for (KMSACLs.Type type : KMSACLs.Type.values()) {
|
for (KMSACLs.Type type : KMSACLs.Type.values()) {
|
||||||
conf.set(type.getAclConfigKey(), type.toString());
|
conf.set(type.getAclConfigKey(), type.toString());
|
||||||
|
@ -930,11 +931,16 @@ public class TestKMS {
|
||||||
@Override
|
@Override
|
||||||
public Void run() throws Exception {
|
public Void run() throws Exception {
|
||||||
KMSClientProvider kp = new KMSClientProvider(uri, conf);
|
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],
|
kp.createKey("k1", new byte[16],
|
||||||
new KeyProvider.Options(conf));
|
new KeyProvider.Options(conf));
|
||||||
makeAuthTokenStale(kp);
|
// Atleast 2 rollovers.. so should induce signer Exception
|
||||||
|
Thread.sleep(3500);
|
||||||
kp.createKey("k2", new byte[16],
|
kp.createKey("k2", new byte[16],
|
||||||
new KeyProvider.Options(conf));
|
new KeyProvider.Options(conf));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -958,15 +964,16 @@ public class TestKMS {
|
||||||
KMSClientProvider kp = new KMSClientProvider(uri, conf);
|
KMSClientProvider kp = new KMSClientProvider(uri, conf);
|
||||||
kp.createKey("k3", new byte[16],
|
kp.createKey("k3", new byte[16],
|
||||||
new KeyProvider.Options(conf));
|
new KeyProvider.Options(conf));
|
||||||
makeAuthTokenStale(kp);
|
// Atleast 2 rollovers.. so should induce signer Exception
|
||||||
|
Thread.sleep(3500);
|
||||||
try {
|
try {
|
||||||
kp.createKey("k4", new byte[16],
|
kp.createKey("k4", new byte[16],
|
||||||
new KeyProvider.Options(conf));
|
new KeyProvider.Options(conf));
|
||||||
Assert.fail("Shoud fail since retry count == 0");
|
Assert.fail("This should not succeed..");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Assert.assertTrue(
|
Assert.assertTrue(
|
||||||
"HTTP exception must be a 403 : " + e.getMessage(), e
|
"HTTP exception must be a 401 : " + e.getMessage(), e
|
||||||
.getMessage().contains("403"));
|
.getMessage().contains("401"));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -976,19 +983,6 @@ public class TestKMS {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
@Test
|
||||||
public void testACLs() throws Exception {
|
public void testACLs() throws Exception {
|
||||||
Configuration conf = new Configuration();
|
Configuration conf = new Configuration();
|
||||||
|
|
Loading…
Reference in New Issue