001    /*
002     * Copyright (c) 2011, Cloudera, Inc. All Rights Reserved.
003     *
004     * Cloudera, Inc. licenses this file to you under the Apache License,
005     * Version 2.0 (the "License"). You may not use this file except in
006     * compliance with the License. You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * This software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
011     * CONDITIONS OF ANY KIND, either express or implied. See the License for
012     * the specific language governing permissions and limitations under the
013     * License.
014     */
015    package com.cloudera.lib.service.security;
016    
017    import com.cloudera.lib.lang.XException;
018    import com.cloudera.lib.server.BaseService;
019    import com.cloudera.lib.server.ServiceException;
020    import com.cloudera.lib.service.Groups;
021    import com.cloudera.lib.service.ProxyUser;
022    import com.cloudera.lib.util.Check;
023    import org.slf4j.Logger;
024    import org.slf4j.LoggerFactory;
025    
026    import java.io.IOException;
027    import java.net.InetAddress;
028    import java.security.AccessControlException;
029    import java.text.MessageFormat;
030    import java.util.Arrays;
031    import java.util.HashMap;
032    import java.util.HashSet;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.Set;
036    
037    public class ProxyUserService extends BaseService implements ProxyUser {
038      private static Logger LOG = LoggerFactory.getLogger(ProxyUserService.class);
039    
040      public enum ERROR implements XException.ERROR {
041        PRXU01("Could not normalize host name [{0}], {1}"),
042        PRXU02("Missing [{0}] property");
043    
044        private String template;
045    
046        ERROR(String template) {
047          this.template = template;
048        }
049    
050        @Override
051        public String getTemplate() {
052          return template;
053        }
054      }
055    
056      private static final String PREFIX = "proxyuser";
057      private static final String GROUPS = ".groups";
058      private static final String HOSTS = ".hosts";
059    
060      private Map<String, Set<String>> proxyUserHosts = new HashMap<String, Set<String>>();
061      private Map<String, Set<String>> proxyUserGroups = new HashMap<String, Set<String>>();
062    
063      public ProxyUserService() {
064        super(PREFIX);
065      }
066    
067      @Override
068      public Class getInterface() {
069        return ProxyUser.class;
070      }
071    
072      @Override
073      public Class[] getServiceDependencies() {
074        return new Class[]{Groups.class};
075      }
076    
077      @Override
078      protected void init() throws ServiceException {
079        for (Map.Entry<String, String> entry : getServiceConfig()) {
080          String key = entry.getKey();
081          if (key.endsWith(GROUPS)) {
082            String proxyUser = key.substring(0, key.lastIndexOf(GROUPS));
083            if (getServiceConfig().get(proxyUser + HOSTS) == null) {
084              throw new ServiceException(ERROR.PRXU02, getPrefixedName(proxyUser + HOSTS));
085            }
086            String value = entry.getValue().trim();
087            LOG.info("Loading proxyuser settings [{}]=[{}]", key, value);
088            Set<String> values = null;
089            if (!value.equals("*")) {
090              values = new HashSet<String>(Arrays.asList(value.split(",")));
091            }
092            proxyUserGroups.put(proxyUser, values);
093          }
094          if (key.endsWith(HOSTS)) {
095            String proxyUser = key.substring(0, key.lastIndexOf(HOSTS));
096            if (getServiceConfig().get(proxyUser + GROUPS) == null) {
097              throw new ServiceException(ERROR.PRXU02, getPrefixedName(proxyUser + GROUPS));
098            }
099            String value = entry.getValue().trim();
100            LOG.info("Loading proxyuser settings [{}]=[{}]", 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(ERROR.PRXU01, originalName, ex.getMessage(), ex);
111                }
112                LOG.info("  Hostname, original [{}], normalized [{}]", originalName, hosts[i]);
113              }
114              values = new HashSet<String>(Arrays.asList(hosts));
115            }
116            proxyUserHosts.put(proxyUser, values);
117          }
118        }
119      }
120    
121      @Override
122      public void validate(String proxyUser, String proxyHost, String doAsUser) throws IOException,
123        AccessControlException {
124        Check.notEmpty(proxyUser, "proxyUser");
125        Check.notEmpty(proxyHost, "proxyHost");
126        Check.notEmpty(doAsUser, "doAsUser");
127        LOG.debug("Authorization check proxyuser [{}] host [{}] doAs [{}]",
128                  new Object[]{proxyUser, proxyHost, doAsUser});
129        if (proxyUserHosts.containsKey(proxyUser)) {
130          proxyHost = normalizeHostname(proxyHost);
131          validateRequestorHost(proxyUser, proxyHost, proxyUserHosts.get(proxyUser));
132          validateGroup(proxyUser, doAsUser, proxyUserGroups.get(proxyUser));
133        }
134        else {
135          throw new AccessControlException(MessageFormat.format("User [{0}] not defined as proxyuser", proxyUser));
136        }
137      }
138    
139      private void validateRequestorHost(String proxyUser, String hostname, Set<String> validHosts)
140        throws IOException, AccessControlException {
141        if (validHosts != null) {
142          if (!validHosts.contains(hostname) && !validHosts.contains(normalizeHostname(hostname))) {
143            throw new AccessControlException(MessageFormat.format("Unauthorized host [{0}] for proxyuser [{1}]",
144                                                                  hostname, proxyUser));
145          }
146        }
147      }
148    
149      private void validateGroup(String proxyUser, String user, Set<String> validGroups) throws IOException,
150        AccessControlException {
151        if (validGroups != null) {
152          List<String> userGroups = getServer().get(Groups.class).getGroups(user);
153          for (String g : validGroups) {
154            if (userGroups.contains(g)) {
155              return;
156            }
157          }
158          throw new AccessControlException(
159            MessageFormat.format("Unauthorized proxyuser [{0}] for user [{1}], not in proxyuser groups",
160                                 proxyUser, user));
161        }
162      }
163    
164      private String normalizeHostname(String name) {
165        try {
166          InetAddress address = InetAddress.getByName(name);
167          return address.getCanonicalHostName();
168        }
169        catch (IOException ex) {
170          throw new AccessControlException(MessageFormat.format("Could not resolve host [{0}], {1}", name,
171                                                                ex.getMessage()));
172        }
173      }
174    
175    }