001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 package org.apache.oozie.service; 019 020 import org.apache.oozie.ErrorCode; 021 import org.apache.oozie.util.ParamChecker; 022 import org.apache.oozie.util.XLog; 023 024 import java.io.IOException; 025 import java.net.InetAddress; 026 import java.security.AccessControlException; 027 import java.text.MessageFormat; 028 import java.util.Arrays; 029 import java.util.HashMap; 030 import java.util.HashSet; 031 import java.util.List; 032 import java.util.Map; 033 import java.util.Set; 034 035 /** 036 * The ProxyUserService checks if a user of a request has proxyuser privileges. 037 * <p/> 038 * This check is based on the following criteria: 039 * <p/> 040 * <ul> 041 * <li>The user of the request must be configured as proxy user in Oozie configuration.</li> 042 * <li>The user of the request must be making the request from a whitelisted host.</li> 043 * <li>The user of the request must be making the request on behalf of a user of a whitelisted group.</li> 044 * </ul> 045 * <p/> 046 */ 047 public class ProxyUserService implements Service { 048 private static XLog LOG = XLog.getLog(ProxyUserService.class); 049 050 public static final String CONF_PREFIX = Service.CONF_PREFIX + "ProxyUserService.proxyuser."; 051 public static final String GROUPS = ".groups"; 052 public static final String HOSTS = ".hosts"; 053 054 private Services services; 055 private Map<String, Set<String>> proxyUserHosts = new HashMap<String, Set<String>>(); 056 private Map<String, Set<String>> proxyUserGroups = new HashMap<String, Set<String>>(); 057 058 /** 059 * Returns the service interface. 060 * @return <code>ProxyUserService</code> 061 */ 062 @Override 063 public Class<? extends Service> getInterface() { 064 return ProxyUserService.class; 065 } 066 067 /** 068 * Initializes the service. 069 * 070 * @param services services singleton initializing the service. 071 * @throws ServiceException thrown if the service could not be configured correctly. 072 */ 073 @Override 074 public void init(Services services) throws ServiceException { 075 this.services = services; 076 for (Map.Entry<String, String> entry : services.getConf()) { 077 String key = entry.getKey(); 078 if (key.startsWith(CONF_PREFIX) && key.endsWith(GROUPS)) { 079 String proxyUser = key.substring(0, key.lastIndexOf(GROUPS)); 080 if (services.getConf().get(proxyUser + HOSTS) == null) { 081 throw new ServiceException(ErrorCode.E0551, CONF_PREFIX + proxyUser + HOSTS); 082 } 083 proxyUser = proxyUser.substring(CONF_PREFIX.length()); 084 String value = entry.getValue().trim(); 085 LOG.info("Loading proxyuser settings [{0}]=[{1}]", key, value); 086 Set<String> values = null; 087 if (!value.equals("*")) { 088 values = new HashSet<String>(Arrays.asList(value.split(","))); 089 } 090 proxyUserGroups.put(proxyUser, values); 091 } 092 if (key.startsWith(CONF_PREFIX) && key.endsWith(HOSTS)) { 093 String proxyUser = key.substring(0, key.lastIndexOf(HOSTS)); 094 if (services.getConf().get(proxyUser + GROUPS) == null) { 095 throw new ServiceException(ErrorCode.E0551, CONF_PREFIX + proxyUser + GROUPS); 096 } 097 proxyUser = proxyUser.substring(CONF_PREFIX.length()); 098 String value = entry.getValue().trim(); 099 LOG.info("Loading proxyuser settings [{0}]=[{1}]", key, value); 100 Set<String> values = null; 101 if (!value.equals("*")) { 102 String[] hosts = value.split(","); 103 for (int i = 0; i < hosts.length; i++) { 104 String originalName = hosts[i]; 105 try { 106 hosts[i] = normalizeHostname(originalName); 107 } 108 catch (Exception ex) { 109 throw new ServiceException(ErrorCode.E0550, originalName, ex.getMessage(), ex); 110 } 111 LOG.info(" Hostname, original [{0}], normalized [{1}]", originalName, hosts[i]); 112 } 113 values = new HashSet<String>(Arrays.asList(hosts)); 114 } 115 proxyUserHosts.put(proxyUser, values); 116 } 117 } 118 } 119 120 /** 121 * Verifies a proxyuser. 122 * 123 * @param proxyUser user name of the proxy user. 124 * @param proxyHost host the proxy user is making the request from. 125 * @param doAsUser user the proxy user is impersonating. 126 * @throws IOException thrown if an error during the validation has occurred. 127 * @throws AccessControlException thrown if the user is not allowed to perform the proxyuser request. 128 */ 129 public void validate(String proxyUser, String proxyHost, String doAsUser) throws IOException, 130 AccessControlException { 131 ParamChecker.notEmpty(proxyUser, "proxyUser", 132 "If you're attempting to use user-impersonation via a proxy user, please make sure that " 133 + "oozie.service.ProxyUserService.proxyuser.#USER#.hosts and " 134 + "oozie.service.ProxyUserService.proxyuser.#USER#.groups are configured correctly"); 135 ParamChecker.notEmpty(proxyHost, "proxyHost", 136 "If you're attempting to use user-impersonation via a proxy user, please make sure that " 137 + "oozie.service.ProxyUserService.proxyuser." + proxyUser + ".hosts and " 138 + "oozie.service.ProxyUserService.proxyuser." + proxyUser + ".groups are configured correctly"); 139 ParamChecker.notEmpty(doAsUser, "doAsUser"); 140 LOG.debug("Authorization check proxyuser [{0}] host [{1}] doAs [{2}]", 141 new Object[]{proxyUser, proxyHost, doAsUser}); 142 if (proxyUserHosts.containsKey(proxyUser)) { 143 proxyHost = normalizeHostname(proxyHost); 144 validateRequestorHost(proxyUser, proxyHost, proxyUserHosts.get(proxyUser)); 145 validateGroup(proxyUser, doAsUser, proxyUserGroups.get(proxyUser)); 146 } 147 else { 148 throw new AccessControlException(MessageFormat.format("User [{0}] not defined as proxyuser", proxyUser)); 149 } 150 } 151 152 private void validateRequestorHost(String proxyUser, String hostname, Set<String> validHosts) 153 throws IOException, AccessControlException { 154 if (validHosts != null) { 155 if (!validHosts.contains(hostname) && !validHosts.contains(normalizeHostname(hostname))) { 156 throw new AccessControlException(MessageFormat.format("Unauthorized host [{0}] for proxyuser [{1}]", 157 hostname, proxyUser)); 158 } 159 } 160 } 161 162 private void validateGroup(String proxyUser, String user, Set<String> validGroups) throws IOException, 163 AccessControlException { 164 if (validGroups != null) { 165 List<String> userGroups = services.get(GroupsService.class).getGroups(user); 166 for (String g : validGroups) { 167 if (userGroups.contains(g)) { 168 return; 169 } 170 } 171 throw new AccessControlException( 172 MessageFormat.format("Unauthorized proxyuser [{0}] for user [{1}], not in proxyuser groups", 173 proxyUser, user)); 174 } 175 } 176 177 private String normalizeHostname(String name) { 178 try { 179 InetAddress address = InetAddress.getByName(name); 180 return address.getCanonicalHostName(); 181 } 182 catch (IOException ex) { 183 throw new AccessControlException(MessageFormat.format("Could not resolve host [{0}], {1}", name, 184 ex.getMessage())); 185 } 186 } 187 188 /** 189 * Destroys the service. 190 */ 191 @Override 192 public void destroy() { 193 } 194 195 }