blob: 14a950d29347d1ab2c4379ef850a619afe3573c7 [file] [log] [blame]
/*
* 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();
}
}