Return-Path: X-Original-To: apmail-accumulo-commits-archive@www.apache.org Delivered-To: apmail-accumulo-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id F078A104BE for ; Fri, 13 Feb 2015 19:33:25 +0000 (UTC) Received: (qmail 50436 invoked by uid 500); 13 Feb 2015 19:33:26 -0000 Delivered-To: apmail-accumulo-commits-archive@accumulo.apache.org Received: (qmail 50397 invoked by uid 500); 13 Feb 2015 19:33:26 -0000 Mailing-List: contact commits-help@accumulo.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@accumulo.apache.org Delivered-To: mailing list commits@accumulo.apache.org Received: (qmail 50388 invoked by uid 99); 13 Feb 2015 19:33:25 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 13 Feb 2015 19:33:25 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id BA804E0534; Fri, 13 Feb 2015 19:33:25 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: elserj@apache.org To: commits@accumulo.apache.org Date: Fri, 13 Feb 2015 19:33:25 -0000 Message-Id: <5f6fb70a98c84cd3af3ebccb397e8250@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [1/7] accumulo git commit: ACCUMULO-3513 Add delegation token support for kerberos configurations Repository: accumulo Updated Branches: refs/heads/master 7ae2e5afd -> 2c9833171 http://git-wip-us.apache.org/repos/asf/accumulo/blob/2c983317/test/src/test/java/org/apache/accumulo/test/functional/KerberosIT.java ---------------------------------------------------------------------- diff --git a/test/src/test/java/org/apache/accumulo/test/functional/KerberosIT.java b/test/src/test/java/org/apache/accumulo/test/functional/KerberosIT.java index 3d48657..75b1199 100644 --- a/test/src/test/java/org/apache/accumulo/test/functional/KerberosIT.java +++ b/test/src/test/java/org/apache/accumulo/test/functional/KerberosIT.java @@ -18,16 +18,24 @@ package org.apache.accumulo.test.functional; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.File; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.PrivilegedExceptionAction; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import org.apache.accumulo.core.client.AccumuloException; import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.BatchScanner; import org.apache.accumulo.core.client.BatchWriter; import org.apache.accumulo.core.client.BatchWriterConfig; import org.apache.accumulo.core.client.Connector; @@ -35,22 +43,29 @@ import org.apache.accumulo.core.client.Scanner; import org.apache.accumulo.core.client.TableExistsException; import org.apache.accumulo.core.client.TableNotFoundException; import org.apache.accumulo.core.client.admin.CompactionConfig; +import org.apache.accumulo.core.client.admin.DelegationTokenConfig; +import org.apache.accumulo.core.client.security.tokens.DelegationToken; import org.apache.accumulo.core.client.security.tokens.KerberosToken; import org.apache.accumulo.core.client.security.tokens.PasswordToken; import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.data.Key; import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.core.data.Range; import org.apache.accumulo.core.data.Value; import org.apache.accumulo.core.metadata.MetadataTable; import org.apache.accumulo.core.metadata.RootTable; +import org.apache.accumulo.core.security.AuthenticationTokenIdentifier; import org.apache.accumulo.core.security.Authorizations; import org.apache.accumulo.core.security.ColumnVisibility; import org.apache.accumulo.core.security.SystemPermission; import org.apache.accumulo.core.security.TablePermission; import org.apache.accumulo.harness.AccumuloIT; +import org.apache.accumulo.harness.MiniClusterConfigurationCallback; import org.apache.accumulo.harness.MiniClusterHarness; import org.apache.accumulo.harness.TestingKdc; +import org.apache.accumulo.minicluster.ServerType; import org.apache.accumulo.minicluster.impl.MiniAccumuloClusterImpl; +import org.apache.accumulo.minicluster.impl.MiniAccumuloConfigImpl; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.CommonConfigurationKeysPublic; import org.apache.hadoop.minikdc.MiniKdc; @@ -63,6 +78,7 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Iterables; import com.google.common.collect.Sets; /** @@ -104,7 +120,17 @@ public class KerberosIT extends AccumuloIT { @Before public void startMac() throws Exception { MiniClusterHarness harness = new MiniClusterHarness(); - mac = harness.create(this, new PasswordToken("unused"), kdc); + mac = harness.create(this, new PasswordToken("unused"), kdc, new MiniClusterConfigurationCallback() { + + @Override + public void configureMiniCluster(MiniAccumuloConfigImpl cfg, Configuration coreSite) { + Map site = cfg.getSiteConfig(); + site.put(Property.INSTANCE_ZK_TIMEOUT.getKey(), "10s"); + cfg.setSiteConfig(site); + } + + }); + mac.getConfig().setNumTservers(1); mac.start(); // Enabled kerberos auth @@ -133,7 +159,7 @@ public class KerberosIT extends AccumuloIT { } // and the ability to modify the root and metadata tables - for (String table : Arrays.asList(RootTable.NAME, MetadataTable.NAME)){ + for (String table : Arrays.asList(RootTable.NAME, MetadataTable.NAME)) { assertTrue(conn.securityOperations().hasTablePermission(conn.whoami(), table, TablePermission.ALTER_TABLE)); } } @@ -304,6 +330,226 @@ public class KerberosIT extends AccumuloIT { assertFalse("Had more results from iterator", iter.hasNext()); } + @Test + public void testDelegationToken() throws Exception { + final String tableName = getUniqueNames(1)[0]; + + // Login as the "root" user + UserGroupInformation root = UserGroupInformation.loginUserFromKeytabAndReturnUGI(kdc.getClientPrincipal(), kdc.getClientKeytab().getAbsolutePath()); + log.info("Logged in as {}", kdc.getClientPrincipal()); + + final int numRows = 100, numColumns = 10; + + // As the "root" user, open up the connection and get a delegation token + final DelegationToken delegationToken = root.doAs(new PrivilegedExceptionAction() { + @Override + public DelegationToken run() throws Exception { + Connector conn = mac.getConnector(kdc.getClientPrincipal(), new KerberosToken()); + log.info("Created connector as {}", kdc.getClientPrincipal()); + assertEquals(kdc.getClientPrincipal(), conn.whoami()); + + conn.tableOperations().create(tableName); + BatchWriter bw = conn.createBatchWriter(tableName, new BatchWriterConfig()); + for (int r = 0; r < numRows; r++) { + Mutation m = new Mutation(Integer.toString(r)); + for (int c = 0; c < numColumns; c++) { + String col = Integer.toString(c); + m.put(col, col, col); + } + bw.addMutation(m); + } + bw.close(); + + return conn.securityOperations().getDelegationToken(new DelegationTokenConfig()); + } + }); + + // The above login with keytab doesn't have a way to logout, so make a fake user that won't have krb credentials + UserGroupInformation userWithoutPrivs = UserGroupInformation.createUserForTesting("fake_user", new String[0]); + int recordsSeen = userWithoutPrivs.doAs(new PrivilegedExceptionAction() { + @Override + public Integer run() throws Exception { + Connector conn = mac.getConnector(kdc.getClientPrincipal(), delegationToken); + + BatchScanner bs = conn.createBatchScanner(tableName, Authorizations.EMPTY, 2); + bs.setRanges(Collections.singleton(new Range())); + int recordsSeen = Iterables.size(bs); + bs.close(); + return recordsSeen; + } + }); + + assertEquals(numRows * numColumns, recordsSeen); + } + + @Test + public void testDelegationTokenAsDifferentUser() throws Exception { + // Login as the "root" user + UserGroupInformation.loginUserFromKeytab(kdc.getClientPrincipal(), kdc.getClientKeytab().getAbsolutePath()); + log.info("Logged in as {}", kdc.getClientPrincipal()); + + // As the "root" user, open up the connection and get a delegation token + Connector conn = mac.getConnector(kdc.getClientPrincipal(), new KerberosToken()); + log.info("Created connector as {}", kdc.getClientPrincipal()); + assertEquals(kdc.getClientPrincipal(), conn.whoami()); + final DelegationToken delegationToken = conn.securityOperations().getDelegationToken(new DelegationTokenConfig()); + + // The above login with keytab doesn't have a way to logout, so make a fake user that won't have krb credentials + UserGroupInformation userWithoutPrivs = UserGroupInformation.createUserForTesting("fake_user", new String[0]); + try { + // Use the delegation token to try to log in as a different user + userWithoutPrivs.doAs(new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + mac.getConnector("some_other_user", delegationToken); + return null; + } + }); + fail("Using a delegation token as a different user should throw an exception"); + } catch (UndeclaredThrowableException e) { + Throwable cause = e.getCause(); + assertNotNull(cause); + // We should get an AccumuloSecurityException from trying to use a delegation token for the wrong user + assertTrue("Expected cause to be AccumuloSecurityException, but was " + cause.getClass(), cause instanceof AccumuloSecurityException); + } + } + + @Test(expected = AccumuloSecurityException.class) + public void testGetDelegationTokenDenied() throws Exception { + String newUser = testName.getMethodName(); + final File newUserKeytab = new File(kdc.getKeytabDir(), newUser + ".keytab"); + if (newUserKeytab.exists()) { + newUserKeytab.delete(); + } + + // Create a new user + kdc.createPrincipal(newUserKeytab, newUser); + + newUser = kdc.qualifyUser(newUser); + + // Login as a normal user + UserGroupInformation.loginUserFromKeytab(newUser, newUserKeytab.getAbsolutePath()); + + // As the "root" user, open up the connection and get a delegation token + Connector conn = mac.getConnector(newUser, new KerberosToken()); + log.info("Created connector as {}", newUser); + assertEquals(newUser, conn.whoami()); + + conn.securityOperations().getDelegationToken(new DelegationTokenConfig()); + } + + @Test + public void testRestartedMasterReusesSecretKey() throws Exception { + // Login as the "root" user + UserGroupInformation root = UserGroupInformation.loginUserFromKeytabAndReturnUGI(kdc.getClientPrincipal(), kdc.getClientKeytab().getAbsolutePath()); + log.info("Logged in as {}", kdc.getClientPrincipal()); + + // As the "root" user, open up the connection and get a delegation token + final DelegationToken delegationToken1 = root.doAs(new PrivilegedExceptionAction() { + @Override + public DelegationToken run() throws Exception { + Connector conn = mac.getConnector(kdc.getClientPrincipal(), new KerberosToken()); + log.info("Created connector as {}", kdc.getClientPrincipal()); + assertEquals(kdc.getClientPrincipal(), conn.whoami()); + + DelegationToken token = conn.securityOperations().getDelegationToken(new DelegationTokenConfig()); + + assertTrue("Could not get tables with delegation token", mac.getConnector(kdc.getClientPrincipal(), token).tableOperations().list().size() > 0); + + return token; + } + }); + + log.info("Stopping master"); + mac.getClusterControl().stop(ServerType.MASTER); + Thread.sleep(5000); + log.info("Restarting master"); + mac.getClusterControl().start(ServerType.MASTER); + + // Make sure our original token is still good + root.doAs(new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + Connector conn = mac.getConnector(kdc.getClientPrincipal(), delegationToken1); + + assertTrue("Could not get tables with delegation token", conn.tableOperations().list().size() > 0); + + return null; + } + }); + + // Get a new token, so we can compare the keyId on the second to the first + final DelegationToken delegationToken2 = root.doAs(new PrivilegedExceptionAction() { + @Override + public DelegationToken run() throws Exception { + Connector conn = mac.getConnector(kdc.getClientPrincipal(), new KerberosToken()); + log.info("Created connector as {}", kdc.getClientPrincipal()); + assertEquals(kdc.getClientPrincipal(), conn.whoami()); + + DelegationToken token = conn.securityOperations().getDelegationToken(new DelegationTokenConfig()); + + assertTrue("Could not get tables with delegation token", mac.getConnector(kdc.getClientPrincipal(), token).tableOperations().list().size() > 0); + + return token; + } + }); + + // A restarted master should reuse the same secret key after a restart if the secret key hasn't expired (1day by default) + assertEquals(delegationToken1.getIdentifier().getKeyId(), delegationToken2.getIdentifier().getKeyId()); + } + + @Test(expected = AccumuloException.class) + public void testDelegationTokenWithInvalidLifetime() throws Throwable { + // Login as the "root" user + UserGroupInformation root = UserGroupInformation.loginUserFromKeytabAndReturnUGI(kdc.getClientPrincipal(), kdc.getClientKeytab().getAbsolutePath()); + log.info("Logged in as {}", kdc.getClientPrincipal()); + + // As the "root" user, open up the connection and get a delegation token + try { + root.doAs(new PrivilegedExceptionAction() { + @Override + public DelegationToken run() throws Exception { + Connector conn = mac.getConnector(kdc.getClientPrincipal(), new KerberosToken()); + log.info("Created connector as {}", kdc.getClientPrincipal()); + assertEquals(kdc.getClientPrincipal(), conn.whoami()); + + // Should fail + return conn.securityOperations().getDelegationToken(new DelegationTokenConfig().setTokenLifetime(Long.MAX_VALUE, TimeUnit.MILLISECONDS)); + } + }); + } catch (UndeclaredThrowableException e) { + Throwable cause = e.getCause(); + if (null != cause) { + throw cause; + } else { + throw e; + } + } + } + + @Test + public void testDelegationTokenWithReducedLifetime() throws Throwable { + // Login as the "root" user + UserGroupInformation root = UserGroupInformation.loginUserFromKeytabAndReturnUGI(kdc.getClientPrincipal(), kdc.getClientKeytab().getAbsolutePath()); + log.info("Logged in as {}", kdc.getClientPrincipal()); + + // As the "root" user, open up the connection and get a delegation token + final DelegationToken dt = root.doAs(new PrivilegedExceptionAction() { + @Override + public DelegationToken run() throws Exception { + Connector conn = mac.getConnector(kdc.getClientPrincipal(), new KerberosToken()); + log.info("Created connector as {}", kdc.getClientPrincipal()); + assertEquals(kdc.getClientPrincipal(), conn.whoami()); + + return conn.securityOperations().getDelegationToken(new DelegationTokenConfig().setTokenLifetime(5, TimeUnit.MINUTES)); + } + }); + + AuthenticationTokenIdentifier identifier = dt.getIdentifier(); + assertTrue("Expected identifier to expire in no more than 5 minutes: " + identifier, + identifier.getExpirationDate() - identifier.getIssueDate() <= (5 * 60 * 1000)); + } + /** * Creates a table, adds a record to it, and then compacts the table. A simple way to make sure that the system user exists (since the master does an RPC to * the tserver which will create the system user if it doesn't already exist).