HADOOP-13251. Authenticate with Kerberos credentials when renewing KMS delegation token. Contributed by Xiao Chen.

(cherry picked from commit 771f798edf)

 Conflicts:
	hadoop-common-project/hadoop-kms/src/test/java/org/apache/hadoop/crypto/key/kms/server/TestKMS.java
This commit is contained in:
Andrew Wang 2016-06-27 18:20:56 -07:00
parent f73e748754
commit d8a69c8737
5 changed files with 154 additions and 96 deletions

View File

@ -101,7 +101,7 @@ public class UserGroupInformation {
* @param immediate true if we should login without waiting for ticket window
*/
@VisibleForTesting
static void setShouldRenewImmediatelyForTests(boolean immediate) {
public static void setShouldRenewImmediatelyForTests(boolean immediate) {
shouldRenewImmediatelyForTests = immediate;
}
@ -328,7 +328,7 @@ public class UserGroupInformation {
@InterfaceAudience.Private
@VisibleForTesting
static void reset() {
public static void reset() {
authenticationMethod = null;
conf = null;
groups = null;

View File

@ -58,6 +58,7 @@ public abstract class DelegationTokenAuthenticator implements Authenticator {
private static final String HTTP_PUT = "PUT";
public static final String OP_PARAM = "op";
private static final String OP_PARAM_EQUALS = OP_PARAM + "=";
public static final String DELEGATION_TOKEN_HEADER =
"X-Hadoop-Delegation-Token";
@ -285,13 +286,22 @@ public abstract class DelegationTokenAuthenticator implements Authenticator {
}
url = new URL(sb.toString());
AuthenticatedURL aUrl = new AuthenticatedURL(this, connConfigurator);
org.apache.hadoop.security.token.Token<AbstractDelegationTokenIdentifier>
dt = null;
if (token instanceof DelegationTokenAuthenticatedURL.Token
&& operation.requiresKerberosCredentials()) {
// Unset delegation token to trigger fall-back authentication.
dt = ((DelegationTokenAuthenticatedURL.Token) token).getDelegationToken();
((DelegationTokenAuthenticatedURL.Token) token).setDelegationToken(null);
}
try {
HttpURLConnection conn = aUrl.openConnection(url, token);
conn.setRequestMethod(operation.getHttpMethod());
HttpExceptionUtils.validateResponse(conn, HttpURLConnection.HTTP_OK);
if (hasResponse) {
String contentType = conn.getHeaderField(CONTENT_TYPE);
contentType = (contentType != null) ? StringUtils.toLowerCase(contentType)
: null;
contentType =
(contentType != null) ? StringUtils.toLowerCase(contentType) : null;
if (contentType != null &&
contentType.contains(APPLICATION_JSON_MIME)) {
try {
@ -308,6 +318,11 @@ public abstract class DelegationTokenAuthenticator implements Authenticator {
url.getAuthority(), operation));
}
}
} finally {
if (dt != null) {
((DelegationTokenAuthenticatedURL.Token) token).setDelegationToken(dt);
}
}
return ret;
}

View File

@ -30,6 +30,8 @@ import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager;
import org.apache.hadoop.security.token.delegation.ZKDelegationTokenSecretManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.annotations.VisibleForTesting;
@ -41,6 +43,8 @@ import com.google.common.annotations.VisibleForTesting;
@InterfaceAudience.Private
@InterfaceStability.Evolving
public class DelegationTokenManager {
private static final Logger LOG =
LoggerFactory.getLogger(DelegationTokenManager.class);
public static final String ENABLE_ZK_KEY = "zk-dt-secret-manager.enable";
@ -156,6 +160,7 @@ public class DelegationTokenManager {
@SuppressWarnings("unchecked")
public Token<? extends AbstractDelegationTokenIdentifier> createToken(
UserGroupInformation ugi, String renewer) {
LOG.debug("Creating token with ugi:{}, renewer:{}.", ugi, renewer);
renewer = (renewer == null) ? ugi.getShortUserName() : renewer;
String user = ugi.getUserName();
Text owner = new Text(user);
@ -175,6 +180,7 @@ public class DelegationTokenManager {
public long renewToken(
Token<? extends AbstractDelegationTokenIdentifier> token, String renewer)
throws IOException {
LOG.debug("Renewing token:{} with renewer:{}.", token, renewer);
return secretManager.renewToken(token, renewer);
}
@ -182,6 +188,7 @@ public class DelegationTokenManager {
public void cancelToken(
Token<? extends AbstractDelegationTokenIdentifier> token,
String canceler) throws IOException {
LOG.debug("Cancelling token:{} with canceler:{}.", token, canceler);
canceler = (canceler != null) ? canceler :
verifyToken(token).getShortUserName();
secretManager.cancelToken(token, canceler);

View File

@ -585,7 +585,7 @@ $H3 KMS Delegation Token Configuration
KMS supports delegation tokens to authenticate to the key providers from processes without Kerberos credentials.
KMS delegation token authentication extends the default Hadoop authentication. See [Hadoop Auth](../hadoop-auth/index.html) page for more details.
KMS delegation token authentication extends the default Hadoop authentication. Same as Hadoop authentication, KMS delegation tokens must not be fetched or renewed using delegation token authentication. See [Hadoop Auth](../hadoop-auth/index.html) page for more details.
Additionally, KMS delegation token secret manager can be configured with the following properties:

View File

@ -35,10 +35,6 @@ import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.hadoop.security.Credentials;
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.Authenticator;
import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
import org.apache.hadoop.security.authentication.client.PseudoAuthenticator;
import org.apache.hadoop.security.authorize.AuthorizationException;
import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
import org.apache.hadoop.security.token.Token;
@ -76,11 +72,6 @@ import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Callable;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
public class TestKMS {
private static final Logger LOG = LoggerFactory.getLogger(TestKMS.class);
@ -260,6 +251,8 @@ public class TestKMS {
if (kdc != null) {
kdc.stop();
}
UserGroupInformation.setShouldRenewImmediatelyForTests(false);
UserGroupInformation.reset();
}
private <T> T doAs(String user, final PrivilegedExceptionAction<T> action)
@ -1747,68 +1740,102 @@ public class TestKMS {
@Test
public void testDelegationTokensOpsSimple() throws Exception {
final Configuration conf = new Configuration();
final Authenticator mock = mock(PseudoAuthenticator.class);
testDelegationTokensOps(conf, mock);
testDelegationTokensOps(conf, false);
}
@Test
public void testDelegationTokensOpsKerberized() throws Exception {
final Configuration conf = new Configuration();
conf.set("hadoop.security.authentication", "kerberos");
final Authenticator mock = mock(KerberosAuthenticator.class);
testDelegationTokensOps(conf, mock);
testDelegationTokensOps(conf, true);
}
private void testDelegationTokensOps(Configuration conf,
final Authenticator mockAuthenticator) throws Exception {
final boolean useKrb) throws Exception {
UserGroupInformation.setConfiguration(conf);
File confDir = getTestDir();
conf = createBaseKMSConf(confDir);
if (useKrb) {
conf.set("hadoop.kms.authentication.type", "kerberos");
conf.set("hadoop.kms.authentication.kerberos.keytab",
keytab.getAbsolutePath());
conf.set("hadoop.kms.authentication.kerberos.principal",
"HTTP/localhost");
conf.set("hadoop.kms.authentication.kerberos.name.rules", "DEFAULT");
}
writeConf(confDir, conf);
doNothing().when(mockAuthenticator).authenticate(any(URL.class),
any(AuthenticatedURL.Token.class));
runServer(null, null, confDir, new KMSCallable<Void>() {
@Override
public Void call() throws Exception {
Configuration conf = new Configuration();
URI uri = createKMSUri(getKMSUrl());
KeyProvider kp = createProvider(uri, conf);
conf.set(KeyProviderFactory.KEY_PROVIDER_PATH,
final Configuration clientConf = new Configuration();
final URI uri = createKMSUri(getKMSUrl());
clientConf.set(KeyProviderFactory.KEY_PROVIDER_PATH,
createKMSUri(getKMSUrl()).toString());
doAs("client", new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
KeyProvider kp = createProvider(uri, clientConf);
// test delegation token retrieval
KeyProviderDelegationTokenExtension kpdte =
KeyProviderDelegationTokenExtension.
createKeyProviderDelegationTokenExtension(kp);
Credentials credentials = new Credentials();
final Token<?>[] tokens = kpdte.addDelegationTokens(
UserGroupInformation.getCurrentUser().getUserName(), credentials);
final Credentials credentials = new Credentials();
final Token<?>[] tokens =
kpdte.addDelegationTokens("client1", credentials);
Assert.assertEquals(1, credentials.getAllTokens().size());
InetSocketAddress kmsAddr = new InetSocketAddress(getKMSUrl().getHost(),
InetSocketAddress kmsAddr =
new InetSocketAddress(getKMSUrl().getHost(),
getKMSUrl().getPort());
Assert.assertEquals(KMSClientProvider.TOKEN_KIND,
credentials.getToken(SecurityUtil.buildTokenService(kmsAddr)).
getKind());
// After this point, we're supposed to use the delegation token to auth.
doThrow(new IOException("Authenticator should not fall back"))
.when(mockAuthenticator).authenticate(any(URL.class),
any(AuthenticatedURL.Token.class));
// test delegation token renewal
boolean renewed = false;
// Test non-renewer user cannot renew.
for (Token<?> token : tokens) {
if (!(token.getKind().equals(KMSClientProvider.TOKEN_KIND))) {
LOG.info("Skipping token {}", token);
continue;
}
LOG.info("Got dt for " + uri + "; " + token);
long tokenLife = token.renew(conf);
try {
token.renew(clientConf);
Assert.fail("client should not be allowed to renew token with"
+ "renewer=client1");
} catch (Exception e) {
GenericTestUtils.assertExceptionContains(
"tries to renew a token with renewer", e);
}
}
final UserGroupInformation otherUgi;
if (useKrb) {
UserGroupInformation
.loginUserFromKeytab("client1", keytab.getAbsolutePath());
otherUgi = UserGroupInformation.getLoginUser();
} else {
otherUgi = UserGroupInformation.createUserForTesting("client1",
new String[] {"other group"});
}
try {
// test delegation token renewal via renewer
otherUgi.doAs(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
boolean renewed = false;
for (Token<?> token : tokens) {
if (!(token.getKind()
.equals(KMSClientProvider.TOKEN_KIND))) {
LOG.info("Skipping token {}", token);
continue;
}
LOG.info("Got dt for " + uri + "; " + token);
long tokenLife = token.renew(clientConf);
LOG.info("Renewed token of kind {}, new lifetime:{}",
token.getKind(), tokenLife);
Thread.sleep(100);
long newTokenLife = token.renew(conf);
long newTokenLife = token.renew(clientConf);
LOG.info("Renewed token of kind {}, new lifetime:{}",
token.getKind(), newTokenLife);
Assert.assertTrue(newTokenLife > tokenLife);
@ -1818,25 +1845,34 @@ public class TestKMS {
// test delegation token cancellation
for (Token<?> token : tokens) {
if (!(token.getKind().equals(KMSClientProvider.TOKEN_KIND))) {
if (!(token.getKind()
.equals(KMSClientProvider.TOKEN_KIND))) {
LOG.info("Skipping token {}", token);
continue;
}
LOG.info("Got dt for " + uri + "; " + token);
token.cancel(conf);
token.cancel(clientConf);
LOG.info("Cancelled token of kind {}", token.getKind());
doNothing().when(mockAuthenticator).
authenticate(any(URL.class), any(AuthenticatedURL.Token.class));
try {
token.renew(conf);
Assert.fail("should not be able to renew a canceled token");
token.renew(clientConf);
Assert
.fail("should not be able to renew a canceled token");
} catch (Exception e) {
LOG.info("Expected exception when trying to renew token", e);
LOG.info("Expected exception when renewing token", e);
}
}
return null;
}
});
return null;
} finally {
otherUgi.logoutUserFromKeytab();
}
}
});
return null;
}
});
}
@Test