This project has retired. For details please refer to its
Attic page.
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 }