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.hadoop.lib.service.security;
020
021import org.apache.hadoop.lib.lang.XException;
022import org.apache.hadoop.lib.server.BaseService;
023import org.apache.hadoop.lib.server.ServiceException;
024import org.apache.hadoop.lib.service.Groups;
025import org.apache.hadoop.lib.service.ProxyUser;
026import org.apache.hadoop.lib.util.Check;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import java.io.IOException;
031import java.net.InetAddress;
032import java.security.AccessControlException;
033import java.text.MessageFormat;
034import java.util.Arrays;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.List;
038import java.util.Map;
039import java.util.Set;
040
041public class ProxyUserService extends BaseService implements ProxyUser {
042  private static Logger LOG = LoggerFactory.getLogger(ProxyUserService.class);
043
044  public enum ERROR implements XException.ERROR {
045    PRXU01("Could not normalize host name [{0}], {1}"),
046    PRXU02("Missing [{0}] property");
047
048    private String template;
049
050    ERROR(String template) {
051      this.template = template;
052    }
053
054    @Override
055    public String getTemplate() {
056      return template;
057    }
058  }
059
060  private static final String PREFIX = "proxyuser";
061  private static final String GROUPS = ".groups";
062  private static final String HOSTS = ".hosts";
063
064  private Map<String, Set<String>> proxyUserHosts = new HashMap<String, Set<String>>();
065  private Map<String, Set<String>> proxyUserGroups = new HashMap<String, Set<String>>();
066
067  public ProxyUserService() {
068    super(PREFIX);
069  }
070
071  @Override
072  public Class getInterface() {
073    return ProxyUser.class;
074  }
075
076  @Override
077  public Class[] getServiceDependencies() {
078    return new Class[]{Groups.class};
079  }
080
081  @Override
082  protected void init() throws ServiceException {
083    for (Map.Entry<String, String> entry : getServiceConfig()) {
084      String key = entry.getKey();
085      if (key.endsWith(GROUPS)) {
086        String proxyUser = key.substring(0, key.lastIndexOf(GROUPS));
087        if (getServiceConfig().get(proxyUser + HOSTS) == null) {
088          throw new ServiceException(ERROR.PRXU02, getPrefixedName(proxyUser + HOSTS));
089        }
090        String value = entry.getValue().trim();
091        LOG.info("Loading proxyuser settings [{}]=[{}]", key, value);
092        Set<String> values = null;
093        if (!value.equals("*")) {
094          values = new HashSet<String>(Arrays.asList(value.split(",")));
095        }
096        proxyUserGroups.put(proxyUser, values);
097      }
098      if (key.endsWith(HOSTS)) {
099        String proxyUser = key.substring(0, key.lastIndexOf(HOSTS));
100        if (getServiceConfig().get(proxyUser + GROUPS) == null) {
101          throw new ServiceException(ERROR.PRXU02, getPrefixedName(proxyUser + GROUPS));
102        }
103        String value = entry.getValue().trim();
104        LOG.info("Loading proxyuser settings [{}]=[{}]", key, value);
105        Set<String> values = null;
106        if (!value.equals("*")) {
107          String[] hosts = value.split(",");
108          for (int i = 0; i < hosts.length; i++) {
109            String originalName = hosts[i];
110            try {
111              hosts[i] = normalizeHostname(originalName);
112            } catch (Exception ex) {
113              throw new ServiceException(ERROR.PRXU01, originalName, ex.getMessage(), ex);
114            }
115            LOG.info("  Hostname, original [{}], normalized [{}]", originalName, hosts[i]);
116          }
117          values = new HashSet<String>(Arrays.asList(hosts));
118        }
119        proxyUserHosts.put(proxyUser, values);
120      }
121    }
122  }
123
124  @Override
125  public void validate(String proxyUser, String proxyHost, String doAsUser) throws IOException,
126    AccessControlException {
127    Check.notEmpty(proxyUser, "proxyUser");
128    Check.notEmpty(proxyHost, "proxyHost");
129    Check.notEmpty(doAsUser, "doAsUser");
130    LOG.debug("Authorization check proxyuser [{}] host [{}] doAs [{}]",
131              new Object[]{proxyUser, proxyHost, doAsUser});
132    if (proxyUserHosts.containsKey(proxyUser)) {
133      proxyHost = normalizeHostname(proxyHost);
134      validateRequestorHost(proxyUser, proxyHost, proxyUserHosts.get(proxyUser));
135      validateGroup(proxyUser, doAsUser, proxyUserGroups.get(proxyUser));
136    } else {
137      throw new AccessControlException(MessageFormat.format("User [{0}] not defined as proxyuser", proxyUser));
138    }
139  }
140
141  private void validateRequestorHost(String proxyUser, String hostname, Set<String> validHosts)
142    throws IOException, AccessControlException {
143    if (validHosts != null) {
144      if (!validHosts.contains(hostname) && !validHosts.contains(normalizeHostname(hostname))) {
145        throw new AccessControlException(MessageFormat.format("Unauthorized host [{0}] for proxyuser [{1}]",
146                                                              hostname, proxyUser));
147      }
148    }
149  }
150
151  private void validateGroup(String proxyUser, String user, Set<String> validGroups) throws IOException,
152    AccessControlException {
153    if (validGroups != null) {
154      List<String> userGroups = getServer().get(Groups.class).getGroups(user);
155      for (String g : validGroups) {
156        if (userGroups.contains(g)) {
157          return;
158        }
159      }
160      throw new AccessControlException(
161        MessageFormat.format("Unauthorized proxyuser [{0}] for user [{1}], not in proxyuser groups",
162                             proxyUser, user));
163    }
164  }
165
166  private String normalizeHostname(String name) {
167    try {
168      InetAddress address = InetAddress.getByName(name);
169      return address.getCanonicalHostName();
170    } catch (IOException ex) {
171      throw new AccessControlException(MessageFormat.format("Could not resolve host [{0}], {1}", name,
172                                                            ex.getMessage()));
173    }
174  }
175
176}