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 }