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