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