/** * 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.hive.hcatalog.templeton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.hadoop.security.Groups; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * When WebHCat is run with doAs query parameter this class ensures that user making the * call is allowed to impersonate doAs user and is making a call from authorized host. */ final class ProxyUserSupport { private static final Logger LOG = LoggerFactory.getLogger(ProxyUserSupport.class); private static final String CONF_PROXYUSER_PREFIX = "webhcat.proxyuser."; private static final String CONF_GROUPS_SUFFIX = ".groups"; private static final String CONF_HOSTS_SUFFIX = ".hosts"; private static final Set<String> WILD_CARD = Collections.unmodifiableSet(new HashSet<String>(0)); private static final Map<String, Set<String>> proxyUserGroups = new HashMap<String, Set<String>>(); private static final Map<String, Set<String>> proxyUserHosts = new HashMap<String, Set<String>>(); static void processProxyuserConfig(AppConfig conf) { for(Map.Entry<String, String> confEnt : conf) { if(confEnt.getKey().startsWith(CONF_PROXYUSER_PREFIX) && confEnt.getKey().endsWith(CONF_GROUPS_SUFFIX)) { //process user groups for which doAs is authorized String proxyUser = confEnt.getKey().substring(CONF_PROXYUSER_PREFIX.length(), confEnt.getKey().lastIndexOf(CONF_GROUPS_SUFFIX)); Set<String> groups; if("*".equals(confEnt.getValue())) { groups = WILD_CARD; if(LOG.isDebugEnabled()) { LOG.debug("User [" + proxyUser + "] is authorized to do doAs any user."); } } else if(confEnt.getValue() != null && confEnt.getValue().trim().length() > 0) { groups = new HashSet<String>(Arrays.asList(confEnt.getValue().trim().split(","))); if(LOG.isDebugEnabled()) { LOG.debug("User [" + proxyUser + "] is authorized to do doAs for users in the following groups: [" + confEnt.getValue().trim() + "]"); } } else { groups = Collections.emptySet(); if(LOG.isDebugEnabled()) { LOG.debug("User [" + proxyUser + "] is authorized to do doAs for users in the following groups: []"); } } proxyUserGroups.put(proxyUser, groups); } else if(confEnt.getKey().startsWith(CONF_PROXYUSER_PREFIX) && confEnt.getKey().endsWith(CONF_HOSTS_SUFFIX)) { //process hosts from which doAs requests are authorized String proxyUser = confEnt.getKey().substring(CONF_PROXYUSER_PREFIX.length(), confEnt.getKey().lastIndexOf(CONF_HOSTS_SUFFIX)); Set<String> hosts; if("*".equals(confEnt.getValue())) { hosts = WILD_CARD; if(LOG.isDebugEnabled()) { LOG.debug("User [" + proxyUser + "] is authorized to do doAs from any host."); } } else if(confEnt.getValue() != null && confEnt.getValue().trim().length() > 0) { String[] hostValues = confEnt.getValue().trim().split(","); hosts = new HashSet<String>(); for(String hostname : hostValues) { String nhn = normalizeHostname(hostname); if(nhn != null) { hosts.add(nhn); } } if(LOG.isDebugEnabled()) { LOG.debug("User [" + proxyUser + "] is authorized to do doAs from the following hosts: [" + confEnt.getValue().trim() + "]"); } } else { hosts = Collections.emptySet(); if(LOG.isDebugEnabled()) { LOG.debug("User [" + proxyUser + "] is authorized to do doAs from the following hosts: []"); } } proxyUserHosts.put(proxyUser, hosts); } } } /** * Verifies a that proxyUser is making the request from authorized host and that doAs user * belongs to one of the groups for which proxyUser is allowed to impersonate users. * * @param proxyUser user name of the proxy (logged in) user. * @param proxyHost host the proxy user is making the request from. * @param doAsUser user the proxy user is impersonating. * @throws NotAuthorizedException thrown if the user is not allowed to perform the proxyuser request. */ static void validate(String proxyUser, String proxyHost, String doAsUser) throws NotAuthorizedException { assertNotEmpty(proxyUser, "proxyUser", "If you're attempting to use user-impersonation via a proxy user, please make sure that " + CONF_PROXYUSER_PREFIX + "#USER#" + CONF_HOSTS_SUFFIX + " and " + CONF_PROXYUSER_PREFIX + "#USER#" + CONF_GROUPS_SUFFIX + " are configured correctly"); assertNotEmpty(proxyHost, "proxyHost", "If you're attempting to use user-impersonation via a proxy user, please make sure that " + CONF_PROXYUSER_PREFIX + proxyUser + CONF_HOSTS_SUFFIX + " and " + CONF_PROXYUSER_PREFIX + proxyUser + CONF_GROUPS_SUFFIX + " are configured correctly"); assertNotEmpty(doAsUser, Server.DO_AS_PARAM); LOG.debug(MessageFormat.format("Authorization check proxyuser [{0}] host [{1}] doAs [{2}]", proxyUser, proxyHost, doAsUser)); if (proxyUserHosts.containsKey(proxyUser)) { proxyHost = normalizeHostname(proxyHost); validateRequestorHost(proxyUser, proxyHost); validateGroup(proxyUser, doAsUser); } else { throw new NotAuthorizedException(MessageFormat.format( "User [{0}] not defined as proxyuser", proxyUser)); } } private static void validateRequestorHost(String proxyUser, String hostname) throws NotAuthorizedException { Set<String> validHosts = proxyUserHosts.get(proxyUser); if (validHosts == WILD_CARD) { return; } if (validHosts == null || !validHosts.contains(hostname)) { throw new NotAuthorizedException(MessageFormat.format( "Unauthorized host [{0}] for proxyuser [{1}]", hostname, proxyUser)); } } private static void validateGroup(String proxyUser, String doAsUser) throws NotAuthorizedException { Set<String> validGroups = proxyUserGroups.get(proxyUser); if(validGroups == WILD_CARD) { return; } else if(validGroups == null || validGroups.isEmpty()) { throw new NotAuthorizedException( MessageFormat.format( "Unauthorized proxyuser [{0}] for doAsUser [{1}], not in proxyuser groups", proxyUser, doAsUser)); } Groups groupsInfo = new Groups(Main.getAppConfigInstance()); try { List<String> userGroups = groupsInfo.getGroups(doAsUser); for (String g : validGroups) { if (userGroups.contains(g)) { return; } } } catch (IOException ex) {//thrown, for example, if there is no such user on the system LOG.warn(MessageFormat.format("Unable to get list of groups for doAsUser [{0}].", doAsUser), ex); } throw new NotAuthorizedException( MessageFormat.format( "Unauthorized proxyuser [{0}] for doAsUser [{1}], not in proxyuser groups", proxyUser, doAsUser)); } private static String normalizeHostname(String name) { try { InetAddress address = InetAddress.getByName( "localhost".equalsIgnoreCase(name) ? null : name); return address.getCanonicalHostName(); } catch (UnknownHostException ex) { LOG.warn(MessageFormat.format("Unable to normalize hostname [{0}]", name)); return null; } } /** * Check that a string is not null and not empty. If null or empty * throws an IllegalArgumentException. * * @param str value. * @param name parameter name for the exception message. * @return the given value. */ private static String assertNotEmpty(String str, String name) { return assertNotEmpty(str, name, null); } /** * Check that a string is not null and not empty. If null or empty * throws an IllegalArgumentException. * * @param str value. * @param name parameter name for the exception message. * @param info additional information to be printed with the exception message * @return the given value. */ private static String assertNotEmpty(String str, String name, String info) { if (str == null) { throw new IllegalArgumentException( name + " cannot be null" + (info == null ? "" : ", " + info)); } if (str.length() == 0) { throw new IllegalArgumentException( name + " cannot be empty" + (info == null ? "" : ", " + info)); } return str; } }