blob: 41760d644ef632079596d92bb00dfa77581cd002 [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.hadoop.conf.Configuration;
import org.apache.hadoop.fs.PathPermissionException;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.service.ServiceStateException;
import org.apache.hadoop.registry.client.api.RegistryConstants;
import org.apache.hadoop.registry.client.api.RegistryOperations;
import org.apache.hadoop.registry.client.api.RegistryOperationsFactory;
import org.apache.hadoop.registry.client.exceptions.NoPathPermissionsException;
import org.apache.hadoop.registry.client.impl.zk.ZKPathDumper;
import org.apache.hadoop.registry.client.impl.RegistryOperationsClient;
import org.apache.hadoop.registry.client.impl.zk.RegistrySecurity;
import org.apache.hadoop.registry.client.impl.zk.ZookeeperConfigOptions;
import org.apache.hadoop.registry.server.integration.RMRegistryOperationsService;
import org.apache.hadoop.registry.server.services.RegistryAdminService;
import org.apache.zookeeper.client.ZooKeeperSaslClient;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.security.auth.login.LoginException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import static org.apache.hadoop.registry.client.api.RegistryConstants.*;
/**
* Verify that the {@link RMRegistryOperationsService} works securely
*/
public class TestSecureRMRegistryOperations extends AbstractSecureRegistryTest {
private static final Logger LOG =
LoggerFactory.getLogger(TestSecureRMRegistryOperations.class);
private Configuration secureConf;
private Configuration zkClientConf;
private UserGroupInformation zookeeperUGI;
@Before
public void setupTestSecureRMRegistryOperations() throws Exception {
startSecureZK();
secureConf = new Configuration();
secureConf.setBoolean(KEY_REGISTRY_SECURE, true);
// create client conf containing the ZK quorum
zkClientConf = new Configuration(secureZK.getConfig());
zkClientConf.setBoolean(KEY_REGISTRY_SECURE, true);
assertNotEmpty(zkClientConf.get(RegistryConstants.KEY_REGISTRY_ZK_QUORUM));
// ZK is in charge
secureConf.set(KEY_REGISTRY_SYSTEM_ACCOUNTS, "sasl:zookeeper@");
zookeeperUGI = loginUGI(ZOOKEEPER, keytab_zk);
}
@After
public void teardownTestSecureRMRegistryOperations() {
}
/**
* Create the RM registry operations as the current user
* @return the service
* @throws LoginException
* @throws FileNotFoundException
*/
public RMRegistryOperationsService startRMRegistryOperations() throws
LoginException, IOException, InterruptedException {
// kerberos
secureConf.set(KEY_REGISTRY_CLIENT_AUTH,
REGISTRY_CLIENT_AUTH_KERBEROS);
secureConf.set(KEY_REGISTRY_CLIENT_JAAS_CONTEXT, ZOOKEEPER_CLIENT_CONTEXT);
RMRegistryOperationsService registryOperations = zookeeperUGI.doAs(
new PrivilegedExceptionAction<RMRegistryOperationsService>() {
@Override
public RMRegistryOperationsService run() throws Exception {
RMRegistryOperationsService operations
= new RMRegistryOperationsService("rmregistry", secureZK);
addToTeardown(operations);
operations.init(secureConf);
LOG.info(operations.bindingDiagnosticDetails());
operations.start();
return operations;
}
});
return registryOperations;
}
/**
* test that ZK can write as itself
* @throws Throwable
*/
@Test
public void testZookeeperCanWriteUnderSystem() throws Throwable {
RMRegistryOperationsService rmRegistryOperations =
startRMRegistryOperations();
RegistryOperations operations = rmRegistryOperations;
operations.mknode(PATH_SYSTEM_SERVICES + "hdfs",
false);
ZKPathDumper pathDumper = rmRegistryOperations.dumpPath(true);
LOG.info(pathDumper.toString());
}
@Test
public void testAnonReadAccess() throws Throwable {
RMRegistryOperationsService rmRegistryOperations =
startRMRegistryOperations();
describe(LOG, "testAnonReadAccess");
RegistryOperations operations =
RegistryOperationsFactory.createAnonymousInstance(zkClientConf);
addToTeardown(operations);
operations.start();
assertFalse("RegistrySecurity.isClientSASLEnabled()==true",
RegistrySecurity.isClientSASLEnabled());
operations.list(PATH_SYSTEM_SERVICES);
}
@Test
public void testAnonNoWriteAccess() throws Throwable {
RMRegistryOperationsService rmRegistryOperations =
startRMRegistryOperations();
describe(LOG, "testAnonNoWriteAccess");
RegistryOperations operations =
RegistryOperationsFactory.createAnonymousInstance(zkClientConf);
addToTeardown(operations);
operations.start();
String servicePath = PATH_SYSTEM_SERVICES + "hdfs";
expectMkNodeFailure(operations, servicePath);
}
@Test
public void testAnonNoWriteAccessOffRoot() throws Throwable {
RMRegistryOperationsService rmRegistryOperations =
startRMRegistryOperations();
describe(LOG, "testAnonNoWriteAccessOffRoot");
RegistryOperations operations =
RegistryOperationsFactory.createAnonymousInstance(zkClientConf);
addToTeardown(operations);
operations.start();
assertFalse("mknode(/)", operations.mknode("/", false));
expectMkNodeFailure(operations, "/sub");
expectDeleteFailure(operations, PATH_SYSTEM_SERVICES, true);
}
/**
* Expect a mknode operation to fail
* @param operations operations instance
* @param path path
* @throws IOException An IO failure other than those permitted
*/
public void expectMkNodeFailure(RegistryOperations operations,
String path) throws IOException {
try {
operations.mknode(path, false);
fail("should have failed to create a node under " + path);
} catch (PathPermissionException expected) {
// expected
} catch (NoPathPermissionsException expected) {
// expected
}
}
/**
* Expect a delete operation to fail
* @param operations operations instance
* @param path path
* @param recursive
* @throws IOException An IO failure other than those permitted
*/
public void expectDeleteFailure(RegistryOperations operations,
String path, boolean recursive) throws IOException {
try {
operations.delete(path, recursive);
fail("should have failed to delete the node " + path);
} catch (PathPermissionException expected) {
// expected
} catch (NoPathPermissionsException expected) {
// expected
}
}
@Test
public void testAlicePathRestrictedAnonAccess() throws Throwable {
RMRegistryOperationsService rmRegistryOperations =
startRMRegistryOperations();
String aliceHome = rmRegistryOperations.initUserRegistry(ALICE);
describe(LOG, "Creating anonymous accessor");
RegistryOperations anonOperations =
RegistryOperationsFactory.createAnonymousInstance(zkClientConf);
addToTeardown(anonOperations);
anonOperations.start();
anonOperations.list(aliceHome);
expectMkNodeFailure(anonOperations, aliceHome + "/anon");
expectDeleteFailure(anonOperations, aliceHome, true);
}
@Test
public void testUserZookeeperHomePathAccess() throws Throwable {
RMRegistryOperationsService rmRegistryOperations =
startRMRegistryOperations();
final String home = rmRegistryOperations.initUserRegistry(ZOOKEEPER);
describe(LOG, "Creating ZK client");
RegistryOperations operations = zookeeperUGI.doAs(
new PrivilegedExceptionAction<RegistryOperations>() {
@Override
public RegistryOperations run() throws Exception {
RegistryOperations operations =
RegistryOperationsFactory.createKerberosInstance(zkClientConf,
ZOOKEEPER_CLIENT_CONTEXT);
addToTeardown(operations);
operations.start();
return operations;
}
});
operations.list(home);
String path = home + "/subpath";
operations.mknode(path, false);
operations.delete(path, true);
}
@Test
public void testUserHomedirsPermissionsRestricted() throws Throwable {
// test that the /users/$user permissions are restricted
RMRegistryOperationsService rmRegistryOperations =
startRMRegistryOperations();
// create Alice's dir, so it should have an ACL for Alice
final String home = rmRegistryOperations.initUserRegistry(ALICE);
List<ACL> acls = rmRegistryOperations.zkGetACLS(home);
ACL aliceACL = null;
for (ACL acl : acls) {
LOG.info(RegistrySecurity.aclToString(acl));
Id id = acl.getId();
if (id.getScheme().equals(ZookeeperConfigOptions.SCHEME_SASL)
&& id.getId().startsWith(ALICE)) {
aliceACL = acl;
break;
}
}
assertNotNull(aliceACL);
assertEquals(RegistryAdminService.USER_HOMEDIR_ACL_PERMISSIONS,
aliceACL.getPerms());
}
@Test
public void testDigestAccess() throws Throwable {
RMRegistryOperationsService registryAdmin =
startRMRegistryOperations();
String id = "username";
String pass = "password";
registryAdmin.addWriteAccessor(id, pass);
List<ACL> clientAcls = registryAdmin.getClientAcls();
LOG.info("Client ACLS=\n{}", RegistrySecurity.aclsToString(clientAcls));
String base = "/digested";
registryAdmin.mknode(base, false);
List<ACL> baseACLs = registryAdmin.zkGetACLS(base);
String aclset = RegistrySecurity.aclsToString(baseACLs);
LOG.info("Base ACLs=\n{}", aclset);
ACL found = null;
for (ACL acl : baseACLs) {
if (ZookeeperConfigOptions.SCHEME_DIGEST.equals(acl.getId().getScheme())) {
found = acl;
break;
}
}
assertNotNull("Did not find digest entry in ACLs " + aclset, found);
zkClientConf.set(KEY_REGISTRY_USER_ACCOUNTS,
"sasl:somebody@EXAMPLE.COM, sasl:other");
RegistryOperations operations =
RegistryOperationsFactory.createAuthenticatedInstance(zkClientConf,
id,
pass);
addToTeardown(operations);
operations.start();
RegistryOperationsClient operationsClient =
(RegistryOperationsClient) operations;
List<ACL> digestClientACLs = operationsClient.getClientAcls();
LOG.info("digest client ACLs=\n{}",
RegistrySecurity.aclsToString(digestClientACLs));
operations.stat(base);
operations.mknode(base + "/subdir", false);
ZKPathDumper pathDumper = registryAdmin.dumpPath(true);
LOG.info(pathDumper.toString());
}
@Test(expected = IllegalArgumentException.class)
public void testNoDigestAuthMissingId() throws Throwable {
RegistryOperationsFactory.createAuthenticatedInstance(zkClientConf,
"",
"pass");
}
@Test(expected = ServiceStateException.class)
public void testNoDigestAuthMissingId2() throws Throwable {
zkClientConf.set(KEY_REGISTRY_CLIENT_AUTH, REGISTRY_CLIENT_AUTH_DIGEST);
zkClientConf.set(KEY_REGISTRY_CLIENT_AUTHENTICATION_ID, "");
zkClientConf.set(KEY_REGISTRY_CLIENT_AUTHENTICATION_PASSWORD, "pass");
RegistryOperationsFactory.createInstance("DigestRegistryOperations",
zkClientConf);
}
@Test(expected = IllegalArgumentException.class)
public void testNoDigestAuthMissingPass() throws Throwable {
RegistryOperationsFactory.createAuthenticatedInstance(zkClientConf,
"id",
"");
}
@Test(expected = ServiceStateException.class)
public void testNoDigestAuthMissingPass2() throws Throwable {
zkClientConf.set(KEY_REGISTRY_CLIENT_AUTH, REGISTRY_CLIENT_AUTH_DIGEST);
zkClientConf.set(KEY_REGISTRY_CLIENT_AUTHENTICATION_ID, "id");
zkClientConf.set(KEY_REGISTRY_CLIENT_AUTHENTICATION_PASSWORD, "");
RegistryOperationsFactory.createInstance("DigestRegistryOperations",
zkClientConf);
}
}