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 }