/* * Copyright © 2014 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.security.auth; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.guice.ConfigModule; import co.cask.cdap.common.guice.DiscoveryRuntimeModule; import co.cask.cdap.common.guice.IOModule; import co.cask.cdap.common.guice.ZKClientModule; import co.cask.cdap.common.io.Codec; import co.cask.cdap.common.utils.ImmutablePair; import co.cask.cdap.security.guice.SecurityModules; import com.google.common.base.Stopwatch; import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.zookeeper.MiniZooKeeperCluster; import org.apache.twill.zookeeper.ZKClientService; import org.apache.zookeeper.ZooDefs; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static org.junit.Assert.assertEquals; /** * Tests covering the {@link DistributedKeyManager} implementation. */ public class DistributedKeyManagerTest extends TestTokenManager { private static final Logger LOG = LoggerFactory.getLogger(DistributedKeyManagerTest.class); private static MiniZooKeeperCluster zkCluster; private static Injector injector1; private static Injector injector2; @BeforeClass public static void setup() throws Exception { HBaseTestingUtility testUtil = new HBaseTestingUtility(); zkCluster = testUtil.startMiniZKCluster(); String zkConnectString = testUtil.getConfiguration().get(HConstants.ZOOKEEPER_QUORUM) + ":" + zkCluster.getClientPort(); LOG.info("Running ZK cluster at " + zkConnectString); CConfiguration cConf1 = CConfiguration.create(); cConf1.set(Constants.Zookeeper.QUORUM, zkConnectString); CConfiguration cConf2 = CConfiguration.create(); cConf2.set(Constants.Zookeeper.QUORUM, zkConnectString); injector1 = Guice.createInjector(new ConfigModule(cConf1, testUtil.getConfiguration()), new IOModule(), new SecurityModules().getDistributedModules(), new ZKClientModule(), new DiscoveryRuntimeModule().getDistributedModules()); injector2 = Guice.createInjector(new ConfigModule(cConf2, testUtil.getConfiguration()), new IOModule(), new SecurityModules().getDistributedModules(), new ZKClientModule(), new DiscoveryRuntimeModule().getDistributedModules()); } @AfterClass public static void tearDown() throws Exception { zkCluster.shutdown(); } @Test public void testKeyDistribution() throws Exception { DistributedKeyManager manager1 = getKeyManager(injector1, true); DistributedKeyManager manager2 = getKeyManager(injector2, false); TimeUnit.MILLISECONDS.sleep(1000); TestingTokenManager tokenManager1 = new TestingTokenManager(manager1, injector1.getInstance(AccessTokenIdentifierCodec.class)); TestingTokenManager tokenManager2 = new TestingTokenManager(manager2, injector2.getInstance(AccessTokenIdentifierCodec.class)); tokenManager1.startAndWait(); tokenManager2.startAndWait(); long now = System.currentTimeMillis(); AccessTokenIdentifier ident1 = new AccessTokenIdentifier("testuser", Lists.newArrayList("users", "admins"), now, now + 60 * 60 * 1000); AccessToken token1 = tokenManager1.signIdentifier(ident1); // make sure the second token manager has the secret key required to validate the signature tokenManager2.waitForKey(tokenManager1.getCurrentKey().getKeyId(), 2000, TimeUnit.MILLISECONDS); tokenManager2.validateSecret(token1); tokenManager2.waitForCurrentKey(2000, TimeUnit.MILLISECONDS); AccessToken token2 = tokenManager2.signIdentifier(ident1); tokenManager1.validateSecret(token2); assertEquals(token1.getIdentifier().getUsername(), token2.getIdentifier().getUsername()); assertEquals(token1.getIdentifier().getGroups(), token2.getIdentifier().getGroups()); assertEquals(token1, token2); tokenManager1.stopAndWait(); tokenManager2.stopAndWait(); } @Test public void testGetACLs() throws Exception { CConfiguration kerbConf = CConfiguration.create(); kerbConf.set(Constants.Security.KERBEROS_ENABLED, "true"); kerbConf.set(Constants.Security.CFG_CDAP_MASTER_KRB_PRINCIPAL, "prinicpal@REALM.NET"); kerbConf.set(Constants.Security.CFG_CDAP_MASTER_KRB_KEYTAB_PATH, "/path/to/keytab"); Assert.assertEquals(ZooDefs.Ids.CREATOR_ALL_ACL, DistributedKeyManager.getACLs(kerbConf)); CConfiguration noKerbConf = CConfiguration.create(); noKerbConf.unset(Constants.Security.CFG_CDAP_MASTER_KRB_PRINCIPAL); Assert.assertEquals(ZooDefs.Ids.OPEN_ACL_UNSAFE, DistributedKeyManager.getACLs(noKerbConf)); } @Override protected ImmutablePair<TokenManager, Codec<AccessToken>> getTokenManagerAndCodec() { try { DistributedKeyManager keyManager = getKeyManager(injector1, true); TokenManager tokenManager = new TokenManager(keyManager, injector1.getInstance(AccessTokenIdentifierCodec.class)); tokenManager.startAndWait(); return new ImmutablePair<TokenManager, Codec<AccessToken>>(tokenManager, injector1.getInstance(AccessTokenCodec.class)); } catch (Exception e) { throw Throwables.propagate(e); } } private DistributedKeyManager getKeyManager(Injector injector, boolean expectLeader) throws Exception { ZKClientService zk = injector.getInstance(ZKClientService.class); zk.startAndWait(); WaitableDistributedKeyManager keyManager = new WaitableDistributedKeyManager(injector.getInstance(CConfiguration.class), injector.getInstance(Key.get(new TypeLiteral<Codec<KeyIdentifier>>() { })), zk); keyManager.startAndWait(); if (expectLeader) { keyManager.waitForLeader(5000, TimeUnit.MILLISECONDS); } return keyManager; } private static class WaitableDistributedKeyManager extends DistributedKeyManager { public WaitableDistributedKeyManager(CConfiguration conf, Codec<KeyIdentifier> codec, ZKClientService zk) { super(conf, codec, zk, Lists.newArrayList(ZooDefs.Ids.OPEN_ACL_UNSAFE)); } public void waitForLeader(long duration, TimeUnit unit) throws InterruptedException, TimeoutException { Stopwatch timer = new Stopwatch().start(); do { if (!leader.get()) { unit.sleep(duration / 10); } } while (!leader.get() && timer.elapsedTime(unit) < duration); if (!leader.get()) { throw new TimeoutException("Timed out waiting to become leader"); } } public KeyIdentifier getCurrentKey() { return currentKey; } public boolean hasKey(int keyId) { return super.hasKey(keyId); } } private static class TestingTokenManager extends TokenManager { private TestingTokenManager(KeyManager keyManager, Codec<AccessTokenIdentifier> identifierCodec) { super(keyManager, identifierCodec); } public KeyIdentifier getCurrentKey() { if (keyManager instanceof WaitableDistributedKeyManager) { return ((WaitableDistributedKeyManager) keyManager).getCurrentKey(); } return null; } public void waitForKey(int keyId, long duration, TimeUnit unit) throws InterruptedException, TimeoutException { if (keyManager instanceof WaitableDistributedKeyManager) { WaitableDistributedKeyManager waitKeyManager = (WaitableDistributedKeyManager) keyManager; Stopwatch timer = new Stopwatch().start(); boolean hasKey = false; do { hasKey = waitKeyManager.hasKey(keyId); if (!hasKey) { unit.sleep(duration / 10); } } while (!hasKey && timer.elapsedTime(unit) < duration); if (!hasKey) { throw new TimeoutException("Timed out waiting for key " + keyId); } } } public void waitForCurrentKey(long duration, TimeUnit unit) throws InterruptedException, TimeoutException { if (keyManager instanceof WaitableDistributedKeyManager) { WaitableDistributedKeyManager waitKeyManager = (WaitableDistributedKeyManager) keyManager; Stopwatch timer = new Stopwatch().start(); boolean hasKey = false; do { hasKey = waitKeyManager.getCurrentKey() != null; if (!hasKey) { unit.sleep(duration / 10); } } while (!hasKey && timer.elapsedTime(unit) < duration); if (!hasKey) { throw new TimeoutException("Timed out waiting for current key to be set"); } } } } }