Repository: accumulo Updated Branches: refs/heads/1.7 81f1c7d80 -> e43e9273e refs/heads/master 860ee35eb -> cf75edb45
ACCUMULO-4135 Add impersonation configuration keys which don't put the principal in the key. Apparently, Ambari has a very hard time handling configuration keys that have '/' characters in them. As such, this breaks the impersonation config keys, as they will near always have a '/' in them (e.g. primary/instance@REALM). This is sad. This commit introduces an alternate strategy for specifying the same configuration items but only using the values. Closes apache/accumulo#67 Project: http://git-wip-us.apache.org/repos/asf/accumulo/repo Commit: http://git-wip-us.apache.org/repos/asf/accumulo/commit/e43e9273 Tree: http://git-wip-us.apache.org/repos/asf/accumulo/tree/e43e9273 Diff: http://git-wip-us.apache.org/repos/asf/accumulo/diff/e43e9273 Branch: refs/heads/1.7 Commit: e43e9273e5297f56b0cc51349ab9bdc25d6e956d Parents: 81f1c7d Author: Josh Elser <els...@apache.org> Authored: Sat Feb 6 16:40:03 2016 -0500 Committer: Josh Elser <els...@apache.org> Committed: Sat Feb 6 16:40:03 2016 -0500 ---------------------------------------------------------------------- .../org/apache/accumulo/core/conf/Property.java | 5 + docs/src/main/asciidoc/chapters/kerberos.txt | 52 ++-- .../server/security/UserImpersonation.java | 97 ++++++- ...redentialsUpdatingInvocationHandlerTest.java | 55 ++-- .../server/security/UserImpersonationTest.java | 259 ++++++++++++++++++- 5 files changed, 422 insertions(+), 46 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/accumulo/blob/e43e9273/core/src/main/java/org/apache/accumulo/core/conf/Property.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/accumulo/core/conf/Property.java b/core/src/main/java/org/apache/accumulo/core/conf/Property.java index 559c460..28cc861 100644 --- a/core/src/main/java/org/apache/accumulo/core/conf/Property.java +++ b/core/src/main/java/org/apache/accumulo/core/conf/Property.java @@ -158,8 +158,13 @@ public enum Property { */ INSTANCE_RPC_SASL_ENABLED("instance.rpc.sasl.enabled", "false", PropertyType.BOOLEAN, "Configures Thrift RPCs to require SASL with GSSAPI which supports Kerberos authentication. Mutually exclusive with SSL RPC configuration."), + @Deprecated INSTANCE_RPC_SASL_PROXYUSERS("instance.rpc.sasl.impersonation.", null, PropertyType.PREFIX, "Prefix that allows configuration of users that are allowed to impersonate other users"), + INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION("instance.rpc.sasl.allowed.user.impersonation", "", PropertyType.STRING, + "One-line configuration property controlling what users are allowed to impersonate other users"), + INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION("instance.rpc.sasl.allowed.host.impersonation", "", PropertyType.STRING, + "One-line configuration property controlling the network locations (hostnames) that are allowed to impersonate other users"), // general properties GENERAL_PREFIX("general.", null, PropertyType.PREFIX, http://git-wip-us.apache.org/repos/asf/accumulo/blob/e43e9273/docs/src/main/asciidoc/chapters/kerberos.txt ---------------------------------------------------------------------- diff --git a/docs/src/main/asciidoc/chapters/kerberos.txt b/docs/src/main/asciidoc/chapters/kerberos.txt index ca482b2..fec9277 100644 --- a/docs/src/main/asciidoc/chapters/kerberos.txt +++ b/docs/src/main/asciidoc/chapters/kerberos.txt @@ -35,7 +35,7 @@ Kerberos implements. In the Java programming language, the language itself also GSSAPI which is leveraged by other applications, like Apache Hadoop and Apache Thrift. SASL, simple authentication and security layer, is a framework for authentication and and security over the network. SASL provides a number of mechanisms for authentication, -one of which is GSSAPI. Thus, SASL provides the transport which authenticates +one of which is GSSAPI. Thus, SASL provides the transport which authenticates using GSSAPI that Kerberos implements. Kerberos is a very complicated software application and is deserving of much @@ -269,29 +269,45 @@ it can only connect to Accumulo as itself. Impersonation, in this context, refer of the proxy to authenticate to Accumulo as itself, but act on behalf of an Accumulo user. Accumulo supports basic impersonation of end-users by a third party via static rules in Accumulo's -site configuration file. +site configuration file. These two properties are semi-colon separated properties which are aligned +by index. This first element in the user impersonation property value matches the first element +in the host impersonation property value, etc. ---- <property> - <name>instance.rpc.sasl.impersonation.$PROXY_USER.users</name> - <value>*</value> + <name>instance.rpc.sasl.allowed.user.impersonation</name> + <value>$PROXY_USER:*</value> </property> <property> - <name>instance.rpc.sasl.impersonation.$PROXY_USER.hosts</name> + <name>instance.rpc.sasl.allowed.host.impersonation</name> <value>*</value> </property> ---- -The value +$PROXY_USER+ is the Kerberos principal of the server which is acting on behalf of a user. -Impersonation is enforced by the Kerberos principal and the host from which the RPC originated. Both -of the above properties expects values which are comma-separated lists. The value of each user in the -list should be the complete Kerberos principal of the user which the give +$PROXY_USER+ can impersonate, -and each value of the hosts list should be the FQDN of the machine which the +$PROXY_USER+ can submit -requests from. +Here, +$PROXY_USER+ can impersonate any user from any host. + +The following is an example of specifying a subset of users +$PROXY_USER+ can impersonate and also +limiting the hosts from which +$PROXY_USER+ can initiate requests from. + +---- +<property> + <name>instance.rpc.sasl.allowed.user.impersonation</name> + <value>$PROXY_USER:user1,user2;$PROXY_USER2:user2,user4</value> +</property> + +<property> + <name>instance.rpc.sasl.allowed.host.impersonation</name> + <value>host1.domain.com,host2.domain.com;*</value> +</property> +---- + +Here, +$PROXY_USER+ can impersonate user1 and user2 only from host1.domain.com or host2.domain.com. ++$PROXY_USER2+ can impersonate user2 and user4 from any host. -Both the hosts and users configuration properties also accept a value of +*+ to denote that any user or host -is acceptable for +$PROXY_USER+. +In these examples, the value +$PROXY_USER+ is the Kerberos principal of the server which is acting on behalf of a user. +Impersonation is enforced by the Kerberos principal and the host from which the RPC originated (from the perspective +of the Accumulo TabletServers/Masters). An asterisk (*) can be used to specify all users or all hosts (depending on the context). ===== Delegation Tokens @@ -299,7 +315,7 @@ Within Accumulo services, the primary task to implement delegation tokens is the of a shared secret among all Accumulo tabletservers and the master. The secret key allows for generation of delegation tokens for users and verification of delegation tokens presented by clients. If a server process is unaware of the secret key used to create a delegation token, the client cannot be authenticated. -As ZooKeeper distribution is an asynchronous operation (typically on the order of seconds), the +As ZooKeeper distribution is an asynchronous operation (typically on the order of seconds), the value for `general.delegation.token.update.interval` should be on the order of hours to days to reduce the likelihood of servers rejecting valid clients because the server did not yet see a new secret key. @@ -422,7 +438,7 @@ JVM to each YARN task is secure, even in multi-tenant instances. ==== Debugging -*Q*: I have valid Kerberos credentials and a correct client configuration file but +*Q*: I have valid Kerberos credentials and a correct client configuration file but I still get errors like: ---- @@ -436,7 +452,7 @@ value, and ensure it matches the value reported by `klist`. ---- $ echo $KRB5CCNAME -$ klist +$ klist Ticket cache: FILE:/tmp/krb5cc_123 Default principal: u...@example.com @@ -462,7 +478,7 @@ diagnose some high-level configuration problem. Client applications can add this hand to the command line and Accumulo server processes or applications started using the `accumulo` script by adding the property to +ACCUMULO_GENERAL_OPTS+ in +$ACCUMULO_CONF_DIR/accumulo-env.sh+. -Additionally, you can increase the log4j levels on +org.apache.hadoop.security+, which includes the +Additionally, you can increase the log4j levels on +org.apache.hadoop.security+, which includes the Hadoop +UserGroupInformation+ class, which will include some high-level debug statements. This can be controlled in your client application, or using +$ACCUMULO_CONF_DIR/generic_logger.xml+ @@ -513,7 +529,7 @@ Caused by: KrbException: Identifier doesn't match expected value (906) ... 25 more ---- -or +or ---- 2015-01-12 14:47:29,440 [server.TThreadPoolServer] ERROR: Error occurred during processing of message. http://git-wip-us.apache.org/repos/asf/accumulo/blob/e43e9273/server/base/src/main/java/org/apache/accumulo/server/security/UserImpersonation.java ---------------------------------------------------------------------- diff --git a/server/base/src/main/java/org/apache/accumulo/server/security/UserImpersonation.java b/server/base/src/main/java/org/apache/accumulo/server/security/UserImpersonation.java index 2a1fd00..97bc858 100644 --- a/server/base/src/main/java/org/apache/accumulo/server/security/UserImpersonation.java +++ b/server/base/src/main/java/org/apache/accumulo/server/security/UserImpersonation.java @@ -35,9 +35,12 @@ import org.slf4j.LoggerFactory; * When SASL is enabled, this parses properties from the site configuration to build up a set of all users capable of impersonating another user, the users * which may be impersonated and the hosts in which the impersonator may issue requests from. * - * <code>rpc_user=>{allowed_accumulo_users=[...], allowed_client_hosts=[...]</code> + * <code>INSTANCE_RPC_SASL_PROXYUSERS=rpc_user={allowed_accumulo_users=[...], allowed_client_hosts=[...]</code> + * <code>INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION=rpc_user:user,user,user;...</code> + * <code>INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION=host,host:host...</code> * - * @see Property#INSTANCE_RPC_SASL_PROXYUSERS + * @see Property#INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION + * @see Property#INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION */ public class UserImpersonation { @@ -170,11 +173,97 @@ public class UserImpersonation { private final Map<String,UsersWithHosts> proxyUsers; + @SuppressWarnings("deprecation") public UserImpersonation(AccumuloConfiguration conf) { - Map<String,String> entries = conf.getAllPropertiesWithPrefix(Property.INSTANCE_RPC_SASL_PROXYUSERS); proxyUsers = new HashMap<>(); + + // Property.INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION is treated as the "new config style" switch + final String userConfig = conf.get(Property.INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION); + if (!Property.INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION.getDefaultValue().equals(userConfig)) { + String hostConfig = conf.get(Property.INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION); + parseOnelineConfiguration(userConfig, hostConfig); + } else { + // Otherwise, assume the old-style + parseMultiPropertyConfiguration(conf.getAllPropertiesWithPrefix(Property.INSTANCE_RPC_SASL_PROXYUSERS)); + } + } + + /** + * Parses the impersonation configuration for all users from a single property. + * + * @param userConfigString + * Semi-colon separated list of {@code remoteUser:alloweduser,alloweduser,...}. + * @param hostConfigString + * Semi-colon separated list of hosts. + */ + private void parseOnelineConfiguration(String userConfigString, String hostConfigString) { + // Pull out the config values, defaulting to at least one value + final String[] userConfigs; + if (userConfigString.trim().isEmpty()) { + userConfigs = new String[] {""}; + } else { + userConfigs = StringUtils.split(userConfigString, ';'); + } + final String[] hostConfigs; + if (hostConfigString.trim().isEmpty()) { + hostConfigs = new String[] {""}; + } else { + hostConfigs = StringUtils.split(hostConfigString, ';'); + } + + if (userConfigs.length != hostConfigs.length) { + String msg = String.format("Should have equal number of user and host impersonation elements in configuration. Got %d and %d elements, respectively.", + userConfigs.length, hostConfigs.length); + throw new IllegalArgumentException(msg); + } + + for (int i = 0; i < userConfigs.length; i++) { + final String userConfig = userConfigs[i]; + final String hostConfig = hostConfigs[i]; + + final String[] splitUserConfig = StringUtils.split(userConfig, ':'); + if (2 != splitUserConfig.length) { + throw new IllegalArgumentException("Expect a single colon-separated pair, but found '" + userConfig + "'"); + } + + final String remoteUser = splitUserConfig[0]; + final String allowedImpersonationsForRemoteUser = splitUserConfig[1]; + final UsersWithHosts usersWithHosts = new UsersWithHosts(); + + proxyUsers.put(remoteUser.trim(), usersWithHosts); + + if (ALL.equals(allowedImpersonationsForRemoteUser)) { + usersWithHosts.setAcceptAllUsers(true); + } else { + String[] allowedUsers = StringUtils.split(allowedImpersonationsForRemoteUser, ","); + Set<String> usersSet = new HashSet<>(); + usersSet.addAll(Arrays.asList(allowedUsers)); + usersWithHosts.setUsers(usersSet); + } + + if (ALL.equals(hostConfig)) { + usersWithHosts.setAcceptAllHosts(true); + } else { + String[] allowedHosts = StringUtils.split(hostConfig, ","); + Set<String> hostsSet = new HashSet<>(); + hostsSet.addAll(Arrays.asList(allowedHosts)); + usersWithHosts.setHosts(hostsSet); + } + } + } + + /** + * Parses all properties that start with {@link Property#INSTANCE_RPC_SASL_PROXYUSERS}. This approach was the original configuration method, but does not work + * with Ambari. + * + * @param configProperties + * The relevant configuration properties for impersonation. + */ + @SuppressWarnings("javadoc") + private void parseMultiPropertyConfiguration(Map<String,String> configProperties) { + @SuppressWarnings("deprecation") final String configKey = Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey(); - for (Entry<String,String> entry : entries.entrySet()) { + for (Entry<String,String> entry : configProperties.entrySet()) { String aclKey = entry.getKey().substring(configKey.length()); int index = aclKey.lastIndexOf('.'); http://git-wip-us.apache.org/repos/asf/accumulo/blob/e43e9273/server/base/src/test/java/org/apache/accumulo/server/rpc/TCredentialsUpdatingInvocationHandlerTest.java ---------------------------------------------------------------------- diff --git a/server/base/src/test/java/org/apache/accumulo/server/rpc/TCredentialsUpdatingInvocationHandlerTest.java b/server/base/src/test/java/org/apache/accumulo/server/rpc/TCredentialsUpdatingInvocationHandlerTest.java index c2d182e..740acd9 100644 --- a/server/base/src/test/java/org/apache/accumulo/server/rpc/TCredentialsUpdatingInvocationHandlerTest.java +++ b/server/base/src/test/java/org/apache/accumulo/server/rpc/TCredentialsUpdatingInvocationHandlerTest.java @@ -17,6 +17,7 @@ package org.apache.accumulo.server.rpc; import java.nio.ByteBuffer; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -24,7 +25,9 @@ import org.apache.accumulo.core.client.impl.thrift.ThriftSecurityException; import org.apache.accumulo.core.client.security.tokens.AuthenticationToken; import org.apache.accumulo.core.client.security.tokens.KerberosToken; import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.ConfigurationCopy; +import org.apache.accumulo.core.conf.DefaultConfiguration; import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.core.security.thrift.TCredentials; import org.junit.After; @@ -32,14 +35,34 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.google.common.base.Predicate; + public class TCredentialsUpdatingInvocationHandlerTest { + private static final DefaultConfiguration DEFAULT_CONFIG = DefaultConfiguration.getInstance(); TCredentialsUpdatingInvocationHandler<Object> proxy; - ConfigurationCopy conf; + ConfigurationCopy cc; + AccumuloConfiguration conf; @Before public void setup() { - conf = new ConfigurationCopy(); + cc = new ConfigurationCopy(); + conf = new AccumuloConfiguration() { + @Override + public String get(Property property) { + String value = cc.get(property); + if (null == value) { + return DEFAULT_CONFIG.get(property); + } + return value; + } + + @Override + public void getProperties(Map<String,String> props, Predicate<String> filter) { + cc.getProperties(props, filter); + } + }; + proxy = new TCredentialsUpdatingInvocationHandler<Object>(new Object(), conf); } @@ -97,8 +120,8 @@ public class TCredentialsUpdatingInvocationHandlerTest { @Test public void testAllowedAnyImpersonationForAnyUser() throws Exception { final String proxyServer = "proxy"; - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", "*"); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", "*"); proxy = new TCredentialsUpdatingInvocationHandler<Object>(new Object(), conf); TCredentials tcreds = new TCredentials("client", KerberosToken.class.getName(), ByteBuffer.allocate(0), UUID.randomUUID().toString()); UGIAssumingProcessor.rpcPrincipal.set(proxyServer); @@ -108,8 +131,8 @@ public class TCredentialsUpdatingInvocationHandlerTest { @Test public void testAllowedImpersonationForSpecificUsers() throws Exception { final String proxyServer = "proxy"; - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", "client1,client2"); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", "client1,client2"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", "*"); proxy = new TCredentialsUpdatingInvocationHandler<Object>(new Object(), conf); TCredentials tcreds = new TCredentials("client1", KerberosToken.class.getName(), ByteBuffer.allocate(0), UUID.randomUUID().toString()); UGIAssumingProcessor.rpcPrincipal.set(proxyServer); @@ -122,8 +145,8 @@ public class TCredentialsUpdatingInvocationHandlerTest { public void testDisallowedImpersonationForUser() throws Exception { final String proxyServer = "proxy"; // let "otherproxy" impersonate, but not "proxy" - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy" + ".users", "*"); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy" + ".hosts", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy" + ".users", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy" + ".hosts", "*"); proxy = new TCredentialsUpdatingInvocationHandler<Object>(new Object(), conf); TCredentials tcreds = new TCredentials("client", KerberosToken.class.getName(), ByteBuffer.allocate(0), UUID.randomUUID().toString()); UGIAssumingProcessor.rpcPrincipal.set(proxyServer); @@ -134,10 +157,10 @@ public class TCredentialsUpdatingInvocationHandlerTest { public void testDisallowedImpersonationForMultipleUsers() throws Exception { final String proxyServer = "proxy"; // let "otherproxy" impersonate, but not "proxy" - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy1" + ".users", "*"); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy1" + ".hosts", "*"); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy2" + ".users", "client1,client2"); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy2" + ".hosts", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy1" + ".users", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy1" + ".hosts", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy2" + ".users", "client1,client2"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + "otherproxy2" + ".hosts", "*"); proxy = new TCredentialsUpdatingInvocationHandler<Object>(new Object(), conf); TCredentials tcreds = new TCredentials("client1", KerberosToken.class.getName(), ByteBuffer.allocate(0), UUID.randomUUID().toString()); UGIAssumingProcessor.rpcPrincipal.set(proxyServer); @@ -147,8 +170,8 @@ public class TCredentialsUpdatingInvocationHandlerTest { @Test public void testAllowedImpersonationFromSpecificHost() throws Exception { final String proxyServer = "proxy", client = "client", host = "host.domain.com"; - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", client); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", host); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", client); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", host); proxy = new TCredentialsUpdatingInvocationHandler<Object>(new Object(), conf); TCredentials tcreds = new TCredentials("client", KerberosToken.class.getName(), ByteBuffer.allocate(0), UUID.randomUUID().toString()); UGIAssumingProcessor.rpcPrincipal.set(proxyServer); @@ -159,8 +182,8 @@ public class TCredentialsUpdatingInvocationHandlerTest { @Test(expected = ThriftSecurityException.class) public void testDisallowedImpersonationFromSpecificHost() throws Exception { final String proxyServer = "proxy", client = "client", host = "host.domain.com"; - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", client); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", host); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".users", client); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + proxyServer + ".hosts", host); proxy = new TCredentialsUpdatingInvocationHandler<Object>(new Object(), conf); TCredentials tcreds = new TCredentials("client", KerberosToken.class.getName(), ByteBuffer.allocate(0), UUID.randomUUID().toString()); UGIAssumingProcessor.rpcPrincipal.set(proxyServer); http://git-wip-us.apache.org/repos/asf/accumulo/blob/e43e9273/server/base/src/test/java/org/apache/accumulo/server/security/UserImpersonationTest.java ---------------------------------------------------------------------- diff --git a/server/base/src/test/java/org/apache/accumulo/server/security/UserImpersonationTest.java b/server/base/src/test/java/org/apache/accumulo/server/security/UserImpersonationTest.java index 0f4159b..7422db4 100644 --- a/server/base/src/test/java/org/apache/accumulo/server/security/UserImpersonationTest.java +++ b/server/base/src/test/java/org/apache/accumulo/server/security/UserImpersonationTest.java @@ -24,8 +24,12 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.ConfigurationCopy; +import org.apache.accumulo.core.conf.DefaultConfiguration; import org.apache.accumulo.core.conf.Property; import org.apache.accumulo.server.security.UserImpersonation.AlwaysTrueSet; import org.apache.accumulo.server.security.UserImpersonation.UsersWithHosts; @@ -33,17 +37,34 @@ import org.junit.Before; import org.junit.Test; import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMap; -/** - * - */ public class UserImpersonationTest { - private ConfigurationCopy conf; + private ConfigurationCopy cc; + private AccumuloConfiguration conf; @Before public void setup() { - conf = new ConfigurationCopy(new HashMap<String,String>()); + cc = new ConfigurationCopy(new HashMap<String,String>()); + conf = new AccumuloConfiguration() { + DefaultConfiguration defaultConfig = DefaultConfiguration.getInstance(); + + @Override + public String get(Property property) { + String value = cc.get(property); + if (null == value) { + return defaultConfig.get(property); + } + return value; + } + + @Override + public void getProperties(Map<String,String> props, Predicate<String> filter) { + cc.getProperties(props, filter); + } + }; } void setValidHosts(String user, String hosts) { @@ -54,8 +75,24 @@ public class UserImpersonationTest { setUsersOrHosts(user, ".users", users); } + @SuppressWarnings("deprecation") void setUsersOrHosts(String user, String suffix, String value) { - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + user + suffix, value); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + user + suffix, value); + } + + void setValidHostsNewConfig(String user, String... hosts) { + cc.set(Property.INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION.getKey(), Joiner.on(';').join(hosts)); + } + + void setValidUsersNewConfig(Map<String,String> remoteToAllowedUsers) { + StringBuilder sb = new StringBuilder(); + for (Entry<String,String> entry : remoteToAllowedUsers.entrySet()) { + if (sb.length() > 0) { + sb.append(";"); + } + sb.append(entry.getKey()).append(":").append(entry.getValue()); + } + cc.set(Property.INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION, sb.toString()); } @Test @@ -76,6 +113,23 @@ public class UserImpersonationTest { } @Test + public void testAnyUserAndHostsNewConfig() { + String server = "server"; + setValidHostsNewConfig(server, "*"); + setValidUsersNewConfig(ImmutableMap.of(server, "*")); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + assertNotNull(uwh); + + assertTrue(uwh.acceptsAllHosts()); + assertTrue(uwh.acceptsAllUsers()); + + assertEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + } + + @Test public void testNoHostByDefault() { String server = "server"; setValidUsers(server, "*"); @@ -92,6 +146,22 @@ public class UserImpersonationTest { } @Test + public void testNoHostByDefaultNewConfig() { + String server = "server"; + setValidUsersNewConfig(ImmutableMap.of(server, "*")); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + assertNotNull(uwh); + + assertFalse(uwh.acceptsAllHosts()); + assertTrue(uwh.acceptsAllUsers()); + + assertNotEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + } + + @Test public void testNoUsersByDefault() { String server = "server"; setValidHosts(server, "*"); @@ -108,6 +178,16 @@ public class UserImpersonationTest { } @Test + public void testNoUsersByDefaultNewConfig() { + String server = "server"; + setValidHostsNewConfig(server, "*"); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + assertNull("Impersonation config should be drive by user element, not host", uwh); + } + + @Test public void testSingleUserAndHost() { String server = "server", host = "single_host.domain.com", client = "single_client"; setValidHosts(server, host); @@ -131,6 +211,29 @@ public class UserImpersonationTest { } @Test + public void testSingleUserAndHostNewConfig() { + String server = "server", host = "single_host.domain.com", client = "single_client"; + setValidHostsNewConfig(server, host); + setValidUsersNewConfig(ImmutableMap.of(server, client)); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + assertNotNull(uwh); + + assertFalse(uwh.acceptsAllHosts()); + assertFalse(uwh.acceptsAllUsers()); + + assertNotEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertNotEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + + assertTrue(uwh.getUsers().contains(client)); + assertTrue(uwh.getHosts().contains(host)); + + assertFalse(uwh.getUsers().contains("some_other_user")); + assertFalse(uwh.getHosts().contains("other_host.domain.com")); + } + + @Test public void testMultipleExplicitUsers() { String server = "server", client1 = "client1", client2 = "client2", client3 = "client3"; setValidHosts(server, "*"); @@ -153,6 +256,28 @@ public class UserImpersonationTest { } @Test + public void testMultipleExplicitUsersNewConfig() { + String server = "server", client1 = "client1", client2 = "client2", client3 = "client3"; + setValidHostsNewConfig(server, "*"); + setValidUsersNewConfig(ImmutableMap.of(server, Joiner.on(',').join(client1, client2, client3))); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + assertNotNull(uwh); + + assertTrue(uwh.acceptsAllHosts()); + assertFalse(uwh.acceptsAllUsers()); + + assertEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertNotEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + + assertTrue(uwh.getUsers().contains(client1)); + assertTrue(uwh.getUsers().contains(client2)); + assertTrue(uwh.getUsers().contains(client3)); + assertFalse(uwh.getUsers().contains("other_client")); + } + + @Test public void testMultipleExplicitHosts() { String server = "server", host1 = "host1", host2 = "host2", host3 = "host3"; setValidHosts(server, Joiner.on(',').join(host1, host2, host3)); @@ -175,6 +300,28 @@ public class UserImpersonationTest { } @Test + public void testMultipleExplicitHostsNewConfig() { + String server = "server", host1 = "host1", host2 = "host2", host3 = "host3"; + setValidHostsNewConfig(server, Joiner.on(',').join(host1, host2, host3)); + setValidUsersNewConfig(ImmutableMap.of(server, "*")); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + assertNotNull(uwh); + + assertFalse(uwh.acceptsAllHosts()); + assertTrue(uwh.acceptsAllUsers()); + + assertNotEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + + assertTrue(uwh.getHosts().contains(host1)); + assertTrue(uwh.getHosts().contains(host2)); + assertTrue(uwh.getHosts().contains(host3)); + assertFalse(uwh.getHosts().contains("other_host")); + } + + @Test public void testMultipleExplicitUsersHosts() { String server = "server", host1 = "host1", host2 = "host2", host3 = "host3", client1 = "client1", client2 = "client2", client3 = "client3"; setValidHosts(server, Joiner.on(',').join(host1, host2, host3)); @@ -202,6 +349,33 @@ public class UserImpersonationTest { } @Test + public void testMultipleExplicitUsersHostsNewConfig() { + String server = "server", host1 = "host1", host2 = "host2", host3 = "host3", client1 = "client1", client2 = "client2", client3 = "client3"; + setValidHostsNewConfig(server, Joiner.on(',').join(host1, host2, host3)); + setValidUsersNewConfig(ImmutableMap.of(server, Joiner.on(',').join(client1, client2, client3))); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + assertNotNull(uwh); + + assertFalse(uwh.acceptsAllHosts()); + assertFalse(uwh.acceptsAllUsers()); + + assertNotEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertNotEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + + assertTrue(uwh.getUsers().contains(client1)); + assertTrue(uwh.getUsers().contains(client2)); + assertTrue(uwh.getUsers().contains(client3)); + assertFalse(uwh.getUsers().contains("other_client")); + + assertTrue(uwh.getHosts().contains(host1)); + assertTrue(uwh.getHosts().contains(host2)); + assertTrue(uwh.getHosts().contains(host3)); + assertFalse(uwh.getHosts().contains("other_host")); + } + + @Test public void testMultipleAllowedImpersonators() { String server1 = "server1", server2 = "server2", host1 = "host1", host2 = "host2", host3 = "host3", client1 = "client1", client2 = "client2", client3 = "client3"; // server1 can impersonate client1 and client2 from host1 or host2 @@ -255,10 +429,79 @@ public class UserImpersonationTest { } @Test + public void testMultipleAllowedImpersonatorsNewConfig() { + String server1 = "server1", server2 = "server2", host1 = "host1", host2 = "host2", host3 = "host3", client1 = "client1", client2 = "client2", client3 = "client3"; + // server1 can impersonate client1 and client2 from host1 or host2 + // server2 can impersonate only client3 from host3 + setValidHostsNewConfig(server1, Joiner.on(',').join(host1, host2), host3); + setValidUsersNewConfig(ImmutableMap.of(server1, Joiner.on(',').join(client1, client2), server2, client3)); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server1); + assertNotNull(uwh); + + assertFalse(uwh.acceptsAllHosts()); + assertFalse(uwh.acceptsAllUsers()); + + assertNotEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertNotEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + + assertTrue(uwh.getUsers().contains(client1)); + assertTrue(uwh.getUsers().contains(client2)); + assertFalse(uwh.getUsers().contains(client3)); + assertFalse(uwh.getUsers().contains("other_client")); + + assertTrue(uwh.getHosts().contains(host1)); + assertTrue(uwh.getHosts().contains(host2)); + assertFalse(uwh.getHosts().contains(host3)); + assertFalse(uwh.getHosts().contains("other_host")); + + uwh = impersonation.get(server2); + assertNotNull(uwh); + + assertFalse(uwh.acceptsAllHosts()); + assertFalse(uwh.acceptsAllUsers()); + + assertNotEquals(AlwaysTrueSet.class, uwh.getHosts().getClass()); + assertNotEquals(AlwaysTrueSet.class, uwh.getUsers().getClass()); + + assertFalse(uwh.getUsers().contains(client1)); + assertFalse(uwh.getUsers().contains(client2)); + assertTrue(uwh.getUsers().contains(client3)); + assertFalse(uwh.getUsers().contains("other_client")); + + assertFalse(uwh.getHosts().contains(host1)); + assertFalse(uwh.getHosts().contains(host2)); + assertTrue(uwh.getHosts().contains(host3)); + assertFalse(uwh.getHosts().contains("other_host")); + + // client3 is not allowed to impersonate anyone + assertNull(impersonation.get(client3)); + } + + @SuppressWarnings("deprecation") + @Test public void testSingleUser() throws Exception { final String server = "server/hostn...@example.com", client = "cli...@example.com"; - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + server + ".users", client); - conf.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + server + ".hosts", "*"); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + server + ".users", client); + cc.set(Property.INSTANCE_RPC_SASL_PROXYUSERS.getKey() + server + ".hosts", "*"); + UserImpersonation impersonation = new UserImpersonation(conf); + + UsersWithHosts uwh = impersonation.get(server); + + assertNotNull(uwh); + + assertTrue(uwh.acceptsAllHosts()); + assertFalse(uwh.acceptsAllUsers()); + + assertTrue(uwh.getUsers().contains(client)); + } + + @Test + public void testSingleUserNewConfig() throws Exception { + final String server = "server/hostn...@example.com", client = "cli...@example.com"; + cc.set(Property.INSTANCE_RPC_SASL_ALLOWED_USER_IMPERSONATION, server + ":" + client); + cc.set(Property.INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION, "*"); UserImpersonation impersonation = new UserImpersonation(conf); UsersWithHosts uwh = impersonation.get(server);