Java tutorial
/* * 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.accumulo.server.security; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.accumulo.core.conf.AccumuloConfiguration; import org.apache.accumulo.core.conf.Property; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; 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>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_ALLOWED_USER_IMPERSONATION * @see Property#INSTANCE_RPC_SASL_ALLOWED_HOST_IMPERSONATION */ public class UserImpersonation { private static final Logger log = LoggerFactory.getLogger(UserImpersonation.class); private static final Set<String> ALWAYS_TRUE = new AlwaysTrueSet<>(); private static final String ALL = "*", USERS = "users", HOSTS = "hosts"; public static class AlwaysTrueSet<T> implements Set<T> { @Override public int size() { throw new UnsupportedOperationException(); } @Override public boolean isEmpty() { throw new UnsupportedOperationException(); } @Override public boolean contains(Object o) { return true; } @Override public Iterator<T> iterator() { throw new UnsupportedOperationException(); } @Override public Object[] toArray() { throw new UnsupportedOperationException(); } @Override public <E> E[] toArray(E[] a) { throw new UnsupportedOperationException(); } @Override public boolean add(T e) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean containsAll(Collection<?> c) { return true; } @Override public boolean addAll(Collection<? extends T> c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } public static class UsersWithHosts { private Set<String> users = new HashSet<>(), hosts = new HashSet<>(); private boolean allUsers, allHosts; public UsersWithHosts() { allUsers = allHosts = false; } public UsersWithHosts(Set<String> users, Set<String> hosts) { this(); this.users = users; this.hosts = hosts; } public Set<String> getUsers() { if (allUsers) { return ALWAYS_TRUE; } return users; } public Set<String> getHosts() { if (allHosts) { return ALWAYS_TRUE; } return hosts; } public boolean acceptsAllUsers() { return allUsers; } public void setAcceptAllUsers(boolean allUsers) { this.allUsers = allUsers; } public boolean acceptsAllHosts() { return allHosts; } public void setAcceptAllHosts(boolean allHosts) { this.allHosts = allHosts; } public void setUsers(Set<String> users) { this.users = users; allUsers = false; } public void setHosts(Set<String> hosts) { this.hosts = hosts; allHosts = false; } } private final Map<String, UsersWithHosts> proxyUsers; @SuppressWarnings("deprecation") public UserImpersonation(AccumuloConfiguration conf) { 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 : configProperties.entrySet()) { String aclKey = entry.getKey().substring(configKey.length()); int index = aclKey.lastIndexOf('.'); if (-1 == index) { throw new RuntimeException("Expected 2 elements in key suffix: " + aclKey); } final String remoteUser = aclKey.substring(0, index).trim(), usersOrHosts = aclKey.substring(index + 1).trim(); UsersWithHosts usersWithHosts = proxyUsers.get(remoteUser); if (null == usersWithHosts) { usersWithHosts = new UsersWithHosts(); proxyUsers.put(remoteUser, usersWithHosts); } if (USERS.equals(usersOrHosts)) { String userString = entry.getValue().trim(); if (ALL.equals(userString)) { usersWithHosts.setAcceptAllUsers(true); } else if (!usersWithHosts.acceptsAllUsers()) { Set<String> users = usersWithHosts.getUsers(); if (null == users) { users = new HashSet<>(); usersWithHosts.setUsers(users); } String[] userValues = StringUtils.split(userString, ','); users.addAll(Arrays.<String>asList(userValues)); } } else if (HOSTS.equals(usersOrHosts)) { String hostsString = entry.getValue().trim(); if (ALL.equals(hostsString)) { usersWithHosts.setAcceptAllHosts(true); } else if (!usersWithHosts.acceptsAllHosts()) { Set<String> hosts = usersWithHosts.getHosts(); if (null == hosts) { hosts = new HashSet<>(); usersWithHosts.setHosts(hosts); } String[] hostValues = StringUtils.split(hostsString, ','); hosts.addAll(Arrays.<String>asList(hostValues)); } } else { log.debug("Ignoring key " + aclKey); } } } public UsersWithHosts get(String remoteUser) { return proxyUsers.get(remoteUser); } }