blob: fb861405b81e900afc42b6c4c89f34419a7e5b13 [file] [log] [blame]
/**
* 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.oozie.service;
import org.apache.oozie.ErrorCode;
import org.apache.oozie.util.ParamChecker;
import org.apache.oozie.util.XLog;
import java.io.IOException;
import java.net.InetAddress;
import java.security.AccessControlException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The ProxyUserService checks if a user of a request has proxyuser privileges.
* <p>
* This check is based on the following criteria:
* </p>
* <ul>
* <li>The user of the request must be configured as proxy user in Oozie configuration.</li>
* <li>The user of the request must be making the request from a whitelisted host.</li>
* <li>The user of the request must be making the request on behalf of a user of a whitelisted group.</li>
* </ul>
*/
public class ProxyUserService implements Service {
private static XLog LOG = XLog.getLog(ProxyUserService.class);
public static final String CONF_PREFIX = Service.CONF_PREFIX + "ProxyUserService.proxyuser.";
public static final String GROUPS = ".groups";
public static final String HOSTS = ".hosts";
private Services services;
private Map<String, Set<String>> proxyUserHosts = new HashMap<String, Set<String>>();
private Map<String, Set<String>> proxyUserGroups = new HashMap<String, Set<String>>();
/**
* Returns the service interface.
* @return <code>ProxyUserService</code>
*/
@Override
public Class<? extends Service> getInterface() {
return ProxyUserService.class;
}
/**
* Initializes the service.
*
* @param services services singleton initializing the service.
* @throws ServiceException thrown if the service could not be configured correctly.
*/
@Override
public void init(Services services) throws ServiceException {
this.services = services;
for (Map.Entry<String, String> entry : services.getConf()) {
String key = entry.getKey();
if (key.startsWith(CONF_PREFIX) && key.endsWith(GROUPS)) {
String proxyUser = key.substring(0, key.lastIndexOf(GROUPS));
if (services.getConf().get(proxyUser + HOSTS) == null) {
throw new ServiceException(ErrorCode.E0551, proxyUser + HOSTS);
}
proxyUser = proxyUser.substring(CONF_PREFIX.length());
String value = entry.getValue().trim();
LOG.info("Loading proxyuser settings [{0}]=[{1}]", key, value);
Set<String> values = null;
if (!value.equals("*")) {
values = new HashSet<String>(Arrays.asList(value.split(",")));
}
proxyUserGroups.put(proxyUser, values);
}
if (key.startsWith(CONF_PREFIX) && key.endsWith(HOSTS)) {
String proxyUser = key.substring(0, key.lastIndexOf(HOSTS));
if (services.getConf().get(proxyUser + GROUPS) == null) {
throw new ServiceException(ErrorCode.E0551, proxyUser + GROUPS);
}
proxyUser = proxyUser.substring(CONF_PREFIX.length());
String value = entry.getValue().trim();
LOG.info("Loading proxyuser settings [{0}]=[{1}]", key, value);
Set<String> values = null;
if (!value.equals("*")) {
String[] hosts = value.split(",");
for (int i = 0; i < hosts.length; i++) {
String originalName = hosts[i];
try {
hosts[i] = normalizeHostname(originalName);
}
catch (Exception ex) {
throw new ServiceException(ErrorCode.E0550, originalName, ex.getMessage(), ex);
}
LOG.info(" Hostname, original [{0}], normalized [{1}]", originalName, hosts[i]);
}
values = new HashSet<String>(Arrays.asList(hosts));
}
proxyUserHosts.put(proxyUser, values);
}
}
}
/**
* Verifies a proxyuser.
*
* @param proxyUser user name of the proxy user.
* @param proxyHost host the proxy user is making the request from.
* @param doAsUser user the proxy user is impersonating.
* @throws IOException thrown if an error during the validation has occurred.
* @throws AccessControlException thrown if the user is not allowed to perform the proxyuser request.
*/
public void validate(String proxyUser, String proxyHost, String doAsUser) throws IOException,
AccessControlException {
ParamChecker.notEmpty(proxyUser, "proxyUser",
"If you're attempting to use user-impersonation via a proxy user, please make sure that "
+ "oozie.service.ProxyUserService.proxyuser.#USER#.hosts and "
+ "oozie.service.ProxyUserService.proxyuser.#USER#.groups are configured correctly");
ParamChecker.notEmpty(proxyHost, "proxyHost",
"If you're attempting to use user-impersonation via a proxy user, please make sure that "
+ "oozie.service.ProxyUserService.proxyuser." + proxyUser + ".hosts and "
+ "oozie.service.ProxyUserService.proxyuser." + proxyUser + ".groups are configured correctly");
ParamChecker.notEmpty(doAsUser, "doAsUser");
LOG.debug("Authorization check proxyuser [{0}] host [{1}] doAs [{2}]",
new Object[]{proxyUser, proxyHost, doAsUser});
if (proxyUserHosts.containsKey(proxyUser)) {
proxyHost = normalizeHostname(proxyHost);
validateRequestorHost(proxyUser, proxyHost, proxyUserHosts.get(proxyUser));
validateGroup(proxyUser, doAsUser, proxyUserGroups.get(proxyUser));
}
else {
throw new AccessControlException(MessageFormat.format("User [{0}] not defined as proxyuser", proxyUser));
}
}
private void validateRequestorHost(String proxyUser, String hostname, Set<String> validHosts)
throws IOException, AccessControlException {
if (validHosts != null) {
if (!validHosts.contains(hostname) && !validHosts.contains(normalizeHostname(hostname))) {
throw new AccessControlException(MessageFormat.format("Unauthorized host [{0}] for proxyuser [{1}]",
hostname, proxyUser));
}
}
}
private void validateGroup(String proxyUser, String user, Set<String> validGroups) throws IOException,
AccessControlException {
if (validGroups != null) {
List<String> userGroups = services.get(GroupsService.class).getGroups(user);
for (String g : validGroups) {
if (userGroups.contains(g)) {
return;
}
}
throw new AccessControlException(
MessageFormat.format("Unauthorized proxyuser [{0}] for user [{1}], not in proxyuser groups",
proxyUser, user));
}
}
private String normalizeHostname(String name) {
try {
InetAddress address = InetAddress.getByName(name);
return address.getCanonicalHostName();
}
catch (IOException ex) {
throw new AccessControlException(MessageFormat.format("Could not resolve host [{0}], {1}", name,
ex.getMessage()));
}
}
/**
* Destroys the service.
*/
@Override
public void destroy() {
}
}