blob: 364c750d0b8936d167dee1388e586a8eabfbcfc5 [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.falcon.service;
import org.apache.commons.lang3.StringUtils;
import org.apache.falcon.FalconException;
import org.apache.falcon.util.RuntimeProperties;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 Falcon runtime properties.</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>
* <p>
*/
public class ProxyUserService implements FalconService {
private static final Logger LOG = LoggerFactory.getLogger(ProxyUserService.class);
private Map<String, Set<String>> proxyUserHosts = new HashMap<>();
private Map<String, Set<String>> proxyUserGroups = new HashMap<>();
private static final String CONF_PREFIX = "falcon.service.ProxyUserService.proxyuser.";
private static final String GROUPS = ".groups";
private static final String HOSTS = ".hosts";
public static final String SERVICE_NAME = ProxyUserService.class.getSimpleName();
@Override
public String getName() {
return SERVICE_NAME;
}
/**
* Initializes the service.
* @throws FalconException thrown if the service could not be configured correctly.
*/
@Override
public void init() throws FalconException {
Set<Map.Entry<Object, Object>> entrySet = RuntimeProperties.get().entrySet();
for (Map.Entry<Object, Object> entry : entrySet) {
String key = (String) entry.getKey();
if (key.startsWith(CONF_PREFIX) && key.endsWith(GROUPS)) {
String proxyUser = key.substring(0, key.lastIndexOf(GROUPS));
if (RuntimeProperties.get().getProperty(proxyUser + HOSTS) == null) {
throw new FalconException(proxyUser + HOSTS + " property not set in runtime "
+ "properties. Please add it.");
}
proxyUser = proxyUser.substring(CONF_PREFIX.length());
String value = ((String) entry.getValue()).trim();
LOG.info("Loading proxyuser settings [{}]=[{}]", key, value);
Set<String> values = null;
if (!value.equals("*")) {
values = new HashSet<>(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 (RuntimeProperties.get().getProperty(proxyUser + GROUPS) == null) {
throw new FalconException(proxyUser + GROUPS + " property not set in runtime "
+ "properties. Please add it.");
}
proxyUser = proxyUser.substring(CONF_PREFIX.length());
String value = ((String) entry.getValue()).trim();
LOG.info("Loading proxyuser settings [{}]=[{}]", key, value);
Set<String> values = null;
if (!value.equals("*")) {
String[] hosts = value.split(",");
for (int i = 0; i < hosts.length; i++) {
String hostName = hosts[i];
try {
hosts[i] = normalizeHostname(hostName);
} catch (Exception ex) {
throw new FalconException("Exception normalizing host name: " + hostName + "."
+ ex.getMessage(), ex);
}
LOG.info("Hostname, original [{}], normalized [{}]", hostName, hosts[i]);
}
values = new HashSet<>(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 java.io.IOException thrown if an error during the validation has occurred.
* @throws java.security.AccessControlException thrown if the user is not allowed to perform the proxyuser request.
*/
public void validate(String proxyUser, String proxyHost, String doAsUser) throws IOException {
validateNotEmpty(proxyUser, "proxyUser",
"If you're attempting to use user-impersonation via a proxy user, please make sure that "
+ "falcon.service.ProxyUserService.proxyuser.#USER#.hosts and "
+ "falcon.service.ProxyUserService.proxyuser.#USER#.groups are configured correctly"
);
validateNotEmpty(proxyHost, "proxyHost",
"If you're attempting to use user-impersonation via a proxy user, please make sure that "
+ "falcon.service.ProxyUserService.proxyuser." + proxyUser + ".hosts and "
+ "falcon.service.ProxyUserService.proxyuser." + proxyUser + ".groups are configured correctly"
);
validateNotEmpty(doAsUser, "doAsUser", null);
LOG.debug("Authorization check proxyuser [{}] host [{}] doAs [{}]",
proxyUser, proxyHost, doAsUser);
if (proxyUserHosts.containsKey(proxyUser)) {
validateRequestorHost(proxyUser, proxyHost, proxyUserHosts.get(proxyUser));
validateGroup(proxyUser, doAsUser, proxyUserGroups.get(proxyUser));
} else {
throw new AccessControlException(MessageFormat.format("User [{0}] not defined as proxyuser. Please add it"
+ " to runtime properties.", proxyUser));
}
}
private void validateRequestorHost(String proxyUser, String hostname, Set<String> validHosts)
throws IOException {
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 {
if (validGroups != null) {
List<String> userGroups = Services.get().<GroupsService>getService(GroupsService.SERVICE_NAME)
.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()));
}
}
private static void validateNotEmpty(String str, String name, String info) {
if (StringUtils.isBlank(str)) {
throw new IllegalArgumentException(name + " cannot be null or empty" + (info == null ? "" : ", " + info));
}
}
/**
* Destroys the service.
*/
@Override
public void destroy() {
}
}