| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.hadoop.registry.secure; |
| |
| import org.apache.commons.io.FileUtils; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.minikdc.MiniKdc; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.authentication.util.KerberosName; |
| import org.apache.hadoop.service.Service; |
| import org.apache.hadoop.service.ServiceOperations; |
| import org.apache.hadoop.registry.RegistryTestHelper; |
| import org.apache.hadoop.registry.client.impl.zk.RegistrySecurity; |
| import org.apache.hadoop.registry.client.impl.zk.ZookeeperConfigOptions; |
| import org.apache.hadoop.registry.server.services.AddingCompositeService; |
| import org.apache.hadoop.registry.server.services.MicroZookeeperService; |
| import org.apache.hadoop.registry.server.services.MicroZookeeperServiceKeys; |
| import org.apache.hadoop.util.Shell; |
| import org.junit.After; |
| import org.junit.AfterClass; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Rule; |
| import org.junit.rules.TestName; |
| import org.junit.rules.Timeout; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.security.auth.Subject; |
| import javax.security.auth.kerberos.KerberosPrincipal; |
| import javax.security.auth.login.LoginContext; |
| import javax.security.auth.login.LoginException; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.security.Principal; |
| import java.util.HashSet; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| /** |
| * Add kerberos tests. This is based on the (JUnit3) KerberosSecurityTestcase |
| * and its test case, <code>TestMiniKdc</code> |
| */ |
| public class AbstractSecureRegistryTest extends RegistryTestHelper { |
| public static final String REALM = "EXAMPLE.COM"; |
| public static final String ZOOKEEPER = "zookeeper"; |
| public static final String ZOOKEEPER_LOCALHOST = "zookeeper/localhost"; |
| public static final String ZOOKEEPER_1270001 = "zookeeper/127.0.0.1"; |
| public static final String ZOOKEEPER_REALM = "zookeeper@" + REALM; |
| public static final String ZOOKEEPER_CLIENT_CONTEXT = ZOOKEEPER; |
| public static final String ZOOKEEPER_SERVER_CONTEXT = "ZOOKEEPER_SERVER"; |
| ; |
| public static final String ZOOKEEPER_LOCALHOST_REALM = |
| ZOOKEEPER_LOCALHOST + "@" + REALM; |
| public static final String ALICE = "alice"; |
| public static final String ALICE_CLIENT_CONTEXT = "alice"; |
| public static final String ALICE_LOCALHOST = "alice/localhost"; |
| public static final String BOB = "bob"; |
| public static final String BOB_CLIENT_CONTEXT = "bob"; |
| public static final String BOB_LOCALHOST = "bob/localhost"; |
| |
| |
| private static final Logger LOG = |
| LoggerFactory.getLogger(AbstractSecureRegistryTest.class); |
| |
| public static final Configuration CONF; |
| |
| static { |
| CONF = new Configuration(); |
| CONF.set("hadoop.security.authentication", "kerberos"); |
| CONF.setBoolean("hadoop.security.authorization", true); |
| } |
| |
| private static final AddingCompositeService classTeardown = |
| new AddingCompositeService("classTeardown"); |
| |
| // static initializer guarantees it is always started |
| // ahead of any @BeforeClass methods |
| static { |
| classTeardown.init(CONF); |
| classTeardown.start(); |
| } |
| |
| public static final String SUN_SECURITY_KRB5_DEBUG = |
| "sun.security.krb5.debug"; |
| |
| private final AddingCompositeService teardown = |
| new AddingCompositeService("teardown"); |
| |
| protected static MiniKdc kdc; |
| protected static File keytab_zk; |
| protected static File keytab_bob; |
| protected static File keytab_alice; |
| protected static File kdcWorkDir; |
| protected static Properties kdcConf; |
| protected static RegistrySecurity registrySecurity; |
| |
| @Rule |
| public final Timeout testTimeout = new Timeout(900000); |
| |
| @Rule |
| public TestName methodName = new TestName(); |
| protected MicroZookeeperService secureZK; |
| protected static File jaasFile; |
| private LoginContext zookeeperLogin; |
| private static String zkServerPrincipal; |
| |
| /** |
| * All class initialization for this test class |
| * @throws Exception |
| */ |
| @BeforeClass |
| public static void beforeSecureRegistryTestClass() throws Exception { |
| registrySecurity = new RegistrySecurity("registrySecurity"); |
| registrySecurity.init(CONF); |
| setupKDCAndPrincipals(); |
| RegistrySecurity.clearJaasSystemProperties(); |
| RegistrySecurity.bindJVMtoJAASFile(jaasFile); |
| initHadoopSecurity(); |
| } |
| |
| @AfterClass |
| public static void afterSecureRegistryTestClass() throws |
| Exception { |
| describe(LOG, "teardown of class"); |
| classTeardown.close(); |
| teardownKDC(); |
| } |
| |
| /** |
| * give our thread a name |
| */ |
| @Before |
| public void nameThread() { |
| Thread.currentThread().setName("JUnit"); |
| } |
| |
| /** |
| * For unknown reasons, the before-class setting of the JVM properties were |
| * not being picked up. This method addresses that by setting them |
| * before every test case |
| */ |
| @Before |
| public void beforeSecureRegistryTest() { |
| |
| } |
| |
| @After |
| public void afterSecureRegistryTest() throws IOException { |
| describe(LOG, "teardown of instance"); |
| teardown.close(); |
| stopSecureZK(); |
| } |
| |
| protected static void addToClassTeardown(Service svc) { |
| classTeardown.addService(svc); |
| } |
| |
| protected void addToTeardown(Service svc) { |
| teardown.addService(svc); |
| } |
| |
| |
| public static void teardownKDC() throws Exception { |
| if (kdc != null) { |
| kdc.stop(); |
| kdc = null; |
| } |
| } |
| |
| /** |
| * Sets up the KDC and a set of principals in the JAAS file |
| * |
| * @throws Exception |
| */ |
| public static void setupKDCAndPrincipals() throws Exception { |
| // set up the KDC |
| File target = new File(System.getProperty("test.dir", "target")); |
| kdcWorkDir = new File(target, "kdc"); |
| kdcWorkDir.mkdirs(); |
| if (!kdcWorkDir.mkdirs()) { |
| assertTrue(kdcWorkDir.isDirectory()); |
| } |
| kdcConf = MiniKdc.createConf(); |
| kdcConf.setProperty(MiniKdc.DEBUG, "true"); |
| kdc = new MiniKdc(kdcConf, kdcWorkDir); |
| kdc.start(); |
| |
| keytab_zk = createKeytab(ZOOKEEPER, "zookeeper.keytab"); |
| keytab_alice = createKeytab(ALICE, "alice.keytab"); |
| keytab_bob = createKeytab(BOB, "bob.keytab"); |
| zkServerPrincipal = Shell.WINDOWS ? ZOOKEEPER_1270001 : ZOOKEEPER_LOCALHOST; |
| |
| StringBuilder jaas = new StringBuilder(1024); |
| jaas.append(registrySecurity.createJAASEntry(ZOOKEEPER_CLIENT_CONTEXT, |
| ZOOKEEPER, keytab_zk)); |
| jaas.append(registrySecurity.createJAASEntry(ZOOKEEPER_SERVER_CONTEXT, |
| zkServerPrincipal, keytab_zk)); |
| jaas.append(registrySecurity.createJAASEntry(ALICE_CLIENT_CONTEXT, |
| ALICE_LOCALHOST , keytab_alice)); |
| jaas.append(registrySecurity.createJAASEntry(BOB_CLIENT_CONTEXT, |
| BOB_LOCALHOST, keytab_bob)); |
| |
| jaasFile = new File(kdcWorkDir, "jaas.txt"); |
| FileUtils.write(jaasFile, jaas.toString()); |
| LOG.info("\n"+ jaas); |
| RegistrySecurity.bindJVMtoJAASFile(jaasFile); |
| } |
| |
| |
| // |
| protected static final String kerberosRule = |
| "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\nDEFAULT"; |
| |
| /** |
| * Init hadoop security by setting up the UGI config |
| */ |
| public static void initHadoopSecurity() { |
| |
| UserGroupInformation.setConfiguration(CONF); |
| |
| KerberosName.setRules(kerberosRule); |
| } |
| |
| /** |
| * Stop the secure ZK and log out the ZK account |
| */ |
| public synchronized void stopSecureZK() { |
| ServiceOperations.stop(secureZK); |
| secureZK = null; |
| logout(zookeeperLogin); |
| zookeeperLogin = null; |
| } |
| |
| |
| public static MiniKdc getKdc() { |
| return kdc; |
| } |
| |
| public static File getKdcWorkDir() { |
| return kdcWorkDir; |
| } |
| |
| public static Properties getKdcConf() { |
| return kdcConf; |
| } |
| |
| /** |
| * Create a secure instance |
| * @param name instance name |
| * @return the instance |
| * @throws Exception |
| */ |
| protected static MicroZookeeperService createSecureZKInstance(String name) |
| throws Exception { |
| String context = ZOOKEEPER_SERVER_CONTEXT; |
| Configuration conf = new Configuration(); |
| |
| File testdir = new File(System.getProperty("test.dir", "target")); |
| File workDir = new File(testdir, name); |
| if (!workDir.mkdirs()) { |
| assertTrue(workDir.isDirectory()); |
| } |
| System.setProperty( |
| ZookeeperConfigOptions.PROP_ZK_SERVER_MAINTAIN_CONNECTION_DESPITE_SASL_FAILURE, |
| "false"); |
| RegistrySecurity.validateContext(context); |
| conf.set(MicroZookeeperServiceKeys.KEY_REGISTRY_ZKSERVICE_JAAS_CONTEXT, |
| context); |
| MicroZookeeperService secureZK = new MicroZookeeperService(name); |
| secureZK.init(conf); |
| LOG.info(secureZK.getDiagnostics()); |
| return secureZK; |
| } |
| |
| /** |
| * Create the keytabl for the given principal, includes |
| * raw principal and $principal/localhost |
| * @param principal principal short name |
| * @param filename filename of keytab |
| * @return file of keytab |
| * @throws Exception |
| */ |
| public static File createKeytab(String principal, |
| String filename) throws Exception { |
| assertNotEmpty("empty principal", principal); |
| assertNotEmpty("empty host", filename); |
| assertNotNull("Null KDC", kdc); |
| File keytab = new File(kdcWorkDir, filename); |
| kdc.createPrincipal(keytab, |
| principal, |
| principal + "/localhost", |
| principal + "/127.0.0.1"); |
| return keytab; |
| } |
| |
| public static String getPrincipalAndRealm(String principal) { |
| return principal + "@" + getRealm(); |
| } |
| |
| protected static String getRealm() { |
| return kdc.getRealm(); |
| } |
| |
| |
| /** |
| * Log in, defaulting to the client context |
| * @param principal principal |
| * @param context context |
| * @param keytab keytab |
| * @return the logged in context |
| * @throws LoginException failure to log in |
| * @throws FileNotFoundException no keytab |
| */ |
| protected LoginContext login(String principal, |
| String context, File keytab) throws LoginException, |
| FileNotFoundException { |
| LOG.info("Logging in as {} in context {} with keytab {}", |
| principal, context, keytab); |
| if (!keytab.exists()) { |
| throw new FileNotFoundException(keytab.getAbsolutePath()); |
| } |
| Set<Principal> principals = new HashSet<Principal>(); |
| principals.add(new KerberosPrincipal(principal)); |
| Subject subject = new Subject(false, principals, new HashSet<Object>(), |
| new HashSet<Object>()); |
| LoginContext login; |
| login = new LoginContext(context, subject, null, |
| KerberosConfiguration.createClientConfig(principal, keytab)); |
| login.login(); |
| return login; |
| } |
| |
| |
| /** |
| * Start the secure ZK instance using the test method name as the path. |
| * As the entry is saved to the {@link #secureZK} field, it |
| * is automatically stopped after the test case. |
| * @throws Exception on any failure |
| */ |
| protected synchronized void startSecureZK() throws Exception { |
| assertNull("Zookeeper is already running", secureZK); |
| |
| zookeeperLogin = login(zkServerPrincipal, |
| ZOOKEEPER_SERVER_CONTEXT, |
| keytab_zk); |
| secureZK = createSecureZKInstance("test-" + methodName.getMethodName()); |
| secureZK.start(); |
| } |
| |
| } |