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.lib.lang.XException; 022 import org.apache.hadoop.lib.server.BaseService; 023 import org.apache.hadoop.lib.server.ServiceException; 024 import org.apache.hadoop.lib.service.Groups; 025 import org.apache.hadoop.lib.service.ProxyUser; 026 import org.apache.hadoop.lib.util.Check; 027 import org.slf4j.Logger; 028 import org.slf4j.LoggerFactory; 029 030 import java.io.IOException; 031 import java.net.InetAddress; 032 import java.security.AccessControlException; 033 import java.text.MessageFormat; 034 import java.util.Arrays; 035 import java.util.HashMap; 036 import java.util.HashSet; 037 import java.util.List; 038 import java.util.Map; 039 import java.util.Set; 040 041 public 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 }