HADOOP-10651. Add ability to restrict service access using IP addresses and hostnames. (Benoy Antony)

(cherry picked from commit 20625c8f04)
This commit is contained in:
Benoy Antony 2015-01-08 10:06:48 -08:00
parent d2fbba790a
commit 832ae27f83
4 changed files with 259 additions and 16 deletions

View File

@ -33,6 +33,7 @@ import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.security.KerberosInfo;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.MachineList;
import com.google.common.annotations.VisibleForTesting;
@ -44,6 +45,7 @@ import com.google.common.annotations.VisibleForTesting;
@InterfaceStability.Evolving
public class ServiceAuthorizationManager {
static final String BLOCKED = ".blocked";
static final String HOSTS = ".hosts";
private static final String HADOOP_POLICY_FILE = "hadoop-policy.xml";
@ -51,6 +53,10 @@ public class ServiceAuthorizationManager {
// and second ACL specifies blocked entries.
private volatile Map<Class<?>, AccessControlList[]> protocolToAcls =
new IdentityHashMap<Class<?>, AccessControlList[]>();
// For each class, first MachineList in the array specifies the allowed entries
// and second MachineList specifies blocked entries.
private volatile Map<Class<?>, MachineList[]> protocolToMachineLists =
new IdentityHashMap<Class<?>, MachineList[]>();
/**
* Configuration key for controlling service-level authorization for Hadoop.
@ -85,7 +91,8 @@ public class ServiceAuthorizationManager {
InetAddress addr
) throws AuthorizationException {
AccessControlList[] acls = protocolToAcls.get(protocol);
if (acls == null) {
MachineList[] hosts = protocolToMachineLists.get(protocol);
if (acls == null || hosts == null) {
throw new AuthorizationException("Protocol " + protocol +
" is not known.");
}
@ -115,6 +122,16 @@ public class ServiceAuthorizationManager {
" is not authorized for protocol " + protocol +
", expected client Kerberos principal is " + clientPrincipal);
}
if (addr != null) {
String hostAddress = addr.getHostAddress();
if (hosts.length != 2 || !hosts[0].includes(hostAddress) ||
hosts[1].includes(hostAddress)) {
AUDITLOG.warn(AUTHZ_FAILED_FOR + " for protocol=" + protocol
+ " from host = " + hostAddress);
throw new AuthorizationException("Host " + hostAddress +
" is not authorized for protocol " + protocol) ;
}
}
AUDITLOG.info(AUTHZ_SUCCESSFUL_FOR + user + " for protocol="+protocol);
}
@ -135,6 +152,8 @@ public class ServiceAuthorizationManager {
PolicyProvider provider) {
final Map<Class<?>, AccessControlList[]> newAcls =
new IdentityHashMap<Class<?>, AccessControlList[]>();
final Map<Class<?>, MachineList[]> newMachineLists =
new IdentityHashMap<Class<?>, MachineList[]>();
String defaultAcl = conf.get(
CommonConfigurationKeys.HADOOP_SECURITY_SERVICE_AUTHORIZATION_DEFAULT_ACL,
@ -143,6 +162,13 @@ public class ServiceAuthorizationManager {
String defaultBlockedAcl = conf.get(
CommonConfigurationKeys.HADOOP_SECURITY_SERVICE_AUTHORIZATION_DEFAULT_BLOCKED_ACL, "");
String defaultServiceHostsKey = getHostKey(
CommonConfigurationKeys.HADOOP_SECURITY_SERVICE_AUTHORIZATION_DEFAULT_ACL);
String defaultMachineList = conf.get(defaultServiceHostsKey,
MachineList.WILDCARD_VALUE);
String defaultBlockedMachineList= conf.get(
defaultServiceHostsKey+ BLOCKED, "");
// Parse the config file
Service[] services = provider.getServices();
if (services != null) {
@ -157,11 +183,26 @@ public class ServiceAuthorizationManager {
conf.get(service.getServiceKey() + BLOCKED,
defaultBlockedAcl));
newAcls.put(service.getProtocol(), new AccessControlList[] {acl, blockedAcl});
String serviceHostsKey = getHostKey(service.getServiceKey());
MachineList machineList = new MachineList (conf.get(serviceHostsKey, defaultMachineList));
MachineList blockedMachineList = new MachineList(
conf.get(serviceHostsKey + BLOCKED, defaultBlockedMachineList));
newMachineLists.put(service.getProtocol(),
new MachineList[] {machineList, blockedMachineList});
}
}
// Flip to the newly parsed permissions
protocolToAcls = newAcls;
protocolToMachineLists = newMachineLists;
}
private String getHostKey(String serviceKey) {
int endIndex = serviceKey.lastIndexOf(".");
if (endIndex != -1) {
return serviceKey.substring(0, endIndex)+ HOSTS;
}
return serviceKey;
}
@VisibleForTesting
@ -178,4 +219,19 @@ public class ServiceAuthorizationManager {
public AccessControlList getProtocolsBlockedAcls(Class<?> className) {
return protocolToAcls.get(className)[1];
}
@VisibleForTesting
public Set<Class<?>> getProtocolsWithMachineLists() {
return protocolToMachineLists.keySet();
}
@VisibleForTesting
public MachineList getProtocolsMachineList(Class<?> className) {
return protocolToMachineLists.get(className)[0];
}
@VisibleForTesting
public MachineList getProtocolsBlockedMachineList(Class<?> className) {
return protocolToMachineLists.get(className)[1];
}
}

View File

@ -45,6 +45,7 @@ import com.google.common.net.InetAddresses;
public class MachineList {
public static final Log LOG = LogFactory.getLog(MachineList.class);
public static final String WILDCARD_VALUE = "*";
/**
* InetAddressFactory is used to obtain InetAddress from host.
@ -91,7 +92,7 @@ public class MachineList {
public MachineList(Collection<String> hostEntries, InetAddressFactory addressFactory) {
this.addressFactory = addressFactory;
if (hostEntries != null) {
if ((hostEntries.size() == 1) && (hostEntries.contains("*"))) {
if ((hostEntries.size() == 1) && (hostEntries.contains(WILDCARD_VALUE))) {
all = true;
ipAddresses = null;
hostNames = null;

View File

@ -159,6 +159,31 @@ security.ha.service.protocol.acl | ACL for HAService protocol used by HAAdm
the ability to refresh the service-level authorization configuration to
certain users/groups.
** Access Control using list of ip addresses, host names and ip ranges
Access to a service can be controlled based on the ip address of the client accessing
the service. It is possible to restrict access to a service from a set of machines by
specifying a list of ip addresses, host names and ip ranges. The property name for each service
is derived from the corresponding acl's property name. If the property name of acl is
security.client.protocol.acl, property name for the hosts list will be
security.client.protocol.hosts.
If hosts list is not defined for a service, the value of
<<<security.service.authorization.default.hosts>>> is applied. If
<<<security.service.authorization.default.hosts>>> is not defined, <<<*>>> is applied.
It is possible to specify a blocked list of hosts. Only those machines which are in the
hosts list, but not in the blocked hosts list will be granted access to the service. The property
name is derived by suffixing with ".blocked".
Example: The property name of blocked hosts list for <<<security.client.protocol.hosts>>
will be <<<security.client.protocol.hosts.blocked>>>
If blocked hosts list is not defined for a service, the value of
<<<security.service.authorization.default.hosts.blocked>>> is applied. If
<<<security.service.authorization.default.hosts.blocked>>> is not defined,
empty blocked hosts list is applied.
** Examples
Allow only users <<<alice>>>, <<<bob>>> and users in the <<<mapreduce>>> group to submit

View File

@ -34,6 +34,11 @@ public class TestServiceAuthorization {
private static final String ACL_CONFIG = "test.protocol.acl";
private static final String ACL_CONFIG1 = "test.protocol1.acl";
private static final String ADDRESS = "0.0.0.0";
private static final String HOST_CONFIG = "test.protocol.hosts";
private static final String BLOCKED_HOST_CONFIG = "test.protocol.hosts.blocked";
private static final String AUTHORIZED_IP = "1.2.3.4";
private static final String UNAUTHORIZED_IP = "1.2.3.5";
private static final String IP_RANGE = "10.222.0.0/16,10.113.221.221";
public interface TestProtocol1 extends TestProtocol {};
@ -181,4 +186,160 @@ public class TestServiceAuthorization {
}
}
@Test
public void testMachineList() throws UnknownHostException {
UserGroupInformation drwho =
UserGroupInformation.createUserForTesting("drwho@EXAMPLE.COM",
new String[] { "group1", "group2" });
ServiceAuthorizationManager serviceAuthorizationManager =
new ServiceAuthorizationManager();
Configuration conf = new Configuration ();
conf.set(HOST_CONFIG, "1.2.3.4");
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
try {
serviceAuthorizationManager.authorize(drwho, TestProtocol.class, conf,
InetAddress.getByName(AUTHORIZED_IP));
} catch (AuthorizationException e) {
fail();
}
try {
serviceAuthorizationManager.authorize(drwho, TestProtocol.class, conf,
InetAddress.getByName(UNAUTHORIZED_IP));
fail();
} catch (AuthorizationException e) {
// expects Exception
}
}
@Test
public void testDefaultMachineList() throws UnknownHostException {
UserGroupInformation drwho =
UserGroupInformation.createUserForTesting("drwho@EXAMPLE.COM",
new String[] { "group1", "group2" });
ServiceAuthorizationManager serviceAuthorizationManager =
new ServiceAuthorizationManager();
Configuration conf = new Configuration ();
// test without setting a default MachineList
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
try {
serviceAuthorizationManager.authorize(drwho, TestProtocol.class, conf,
InetAddress.getByName(UNAUTHORIZED_IP));
} catch (AuthorizationException e) {
fail();
}
// test with a default MachineList
conf.set(
"security.service.authorization.default.hosts",
IP_RANGE);
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
try {
serviceAuthorizationManager.authorize(drwho, TestProtocol.class, conf,
InetAddress.getByName(UNAUTHORIZED_IP));
fail();
} catch (AuthorizationException e) {
// expects Exception
}
try {
serviceAuthorizationManager.authorize(drwho, TestProtocol.class, conf,
InetAddress.getByName("10.222.0.0"));
} catch (AuthorizationException e) {
fail();
}
}
@Test
public void testBlockedMachineList() throws UnknownHostException {
UserGroupInformation drwho =
UserGroupInformation.createUserForTesting("drwho@EXAMPLE.COM",
new String[] { "group1", "group2" });
ServiceAuthorizationManager serviceAuthorizationManager =
new ServiceAuthorizationManager();
Configuration conf = new Configuration ();
// test without setting a blocked MachineList
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol.class, conf, InetAddress.getByName("10.222.0.0"));
} catch (AuthorizationException e) {
fail();
}
// now set a blocked MachineList
conf.set(BLOCKED_HOST_CONFIG, IP_RANGE);
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol.class, conf, InetAddress.getByName("10.222.0.0"));
fail();
} catch (AuthorizationException e) {
// expects Exception
}
// reset blocked MachineList
conf.set(BLOCKED_HOST_CONFIG, "");
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol.class, conf, InetAddress.getByName("10.222.0.0"));
} catch (AuthorizationException e) {
fail();
}
}
@Test
public void testDefaultBlockedMachineList() throws UnknownHostException {
UserGroupInformation drwho =
UserGroupInformation.createUserForTesting("drwho@EXAMPLE.COM",
new String[] { "group1", "group2" });
ServiceAuthorizationManager serviceAuthorizationManager =
new ServiceAuthorizationManager();
Configuration conf = new Configuration ();
// test without setting a default blocked MachineList
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol1.class, conf, InetAddress.getByName("10.222.0.0"));
} catch (AuthorizationException e) {
fail();
}
// set a default blocked MachineList and a blocked MachineList for TestProtocol
conf.set(
"security.service.authorization.default.hosts.blocked",
IP_RANGE);
conf.set(BLOCKED_HOST_CONFIG, "1.2.3.4");
serviceAuthorizationManager.refresh(conf, new TestPolicyProvider());
// TestProtocol can be accessed from "10.222.0.0" because it blocks only "1.2.3.4"
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol.class, conf, InetAddress.getByName("10.222.0.0"));
} catch (AuthorizationException e) {
fail();
}
// TestProtocol cannot be accessed from "1.2.3.4"
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol.class, conf, InetAddress.getByName("1.2.3.4"));
fail();
} catch (AuthorizationException e) {
//expects Exception
}
// TestProtocol1 can be accessed from "1.2.3.4" because it uses default block list
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol1.class, conf, InetAddress.getByName("1.2.3.4"));
} catch (AuthorizationException e) {
fail();
}
// TestProtocol1 cannot be accessed from "10.222.0.0",
// because "10.222.0.0" is in default block list
try {
serviceAuthorizationManager.authorize(drwho,
TestProtocol1.class, conf, InetAddress.getByName("10.222.0.0"));
fail();
} catch (AuthorizationException e) {
//expects Exception
}
}
}