001 /** 002 * Copyright (c) 2010 Yahoo! Inc. All rights reserved. 003 * Licensed under the Apache License, Version 2.0 (the "License"); 004 * you may not use this file except in compliance with the License. 005 * You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software 010 * distributed under the License is distributed on an "AS IS" BASIS, 011 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 * See the License for the specific language governing permissions and 013 * limitations under the License. See accompanying LICENSE file. 014 */ 015 package org.apache.oozie.service; 016 017 import org.apache.hadoop.mapred.JobClient; 018 import org.apache.hadoop.mapred.JobConf; 019 import org.apache.hadoop.fs.FileSystem; 020 import org.apache.hadoop.fs.Path; 021 import org.apache.hadoop.conf.Configuration; 022 import org.apache.hadoop.security.UserGroupInformation; 023 import org.apache.hadoop.security.token.Token; 024 import org.apache.hadoop.filecache.DistributedCache; 025 import org.apache.hadoop.mapreduce.security.token.delegation.DelegationTokenIdentifier; 026 import org.apache.hadoop.io.Text; 027 import org.apache.oozie.util.XLog; 028 import org.apache.oozie.util.XConfiguration; 029 import org.apache.oozie.util.ParamChecker; 030 import org.apache.oozie.ErrorCode; 031 import org.apache.oozie.service.HadoopAccessorService; 032 import org.apache.oozie.service.HadoopAccessorException; 033 import org.apache.oozie.service.Service; 034 import org.apache.oozie.service.ServiceException; 035 036 import java.io.IOException; 037 import java.net.URI; 038 import java.net.URISyntaxException; 039 import java.security.PrivilegedExceptionAction; 040 import java.util.concurrent.ConcurrentMap; 041 import java.util.concurrent.ConcurrentHashMap; 042 043 /** 044 * The HadoopAccessorService returns HadoopAccessor instances configured to work on behalf of a user-group. <p/> The 045 * default accessor used is the base accessor which just injects the UGI into the configuration instance used to 046 * create/obtain JobClient and ileSystem instances. <p/> The HadoopAccess class to use can be configured in the 047 * <code>oozie-site.xml</code> using the <code>oozie.service.HadoopAccessorService.accessor.class</code> property. 048 */ 049 public class KerberosHadoopAccessorService extends HadoopAccessorService { 050 051 public static final String CONF_PREFIX = Service.CONF_PREFIX + "HadoopAccessorService."; 052 053 public static final String KERBEROS_AUTH_ENABLED = CONF_PREFIX + "kerberos.enabled"; 054 public static final String KERBEROS_KEYTAB = CONF_PREFIX + "keytab.file"; 055 public static final String KERBEROS_PRINCIPAL = CONF_PREFIX + "kerberos.principal"; 056 057 private ConcurrentMap<String, UserGroupInformation> userUgiMap; 058 059 public void init(Configuration serviceConf) throws ServiceException { 060 boolean kerberosAuthOn = serviceConf.getBoolean(KERBEROS_AUTH_ENABLED, true); 061 XLog.getLog(getClass()).info("Oozie Kerberos Authentication [{0}]", (kerberosAuthOn) ? "enabled" : "disabled"); 062 if (kerberosAuthOn) { 063 try { 064 String keytabFile = serviceConf.get(KERBEROS_KEYTAB, 065 System.getProperty("user.home") + "/oozie.keytab").trim(); 066 if (keytabFile.length() == 0) { 067 throw new ServiceException(ErrorCode.E0026, KERBEROS_KEYTAB); 068 } 069 String principal = serviceConf.get(KERBEROS_PRINCIPAL, "oozie/localhost@LOCALHOST"); 070 if (principal.length() == 0) { 071 throw new ServiceException(ErrorCode.E0026, KERBEROS_PRINCIPAL); 072 } 073 Configuration conf = new Configuration(); 074 conf.set("hadoop.security.authentication", "kerberos"); 075 UserGroupInformation.setConfiguration(conf); 076 UserGroupInformation.loginUserFromKeytab(principal, keytabFile); 077 XLog.getLog(getClass()).info("Got Kerberos ticket, keytab [{0}], Oozie principal principal [{1}]", 078 keytabFile, principal); 079 } 080 catch (ServiceException ex) { 081 throw ex; 082 } 083 catch (Exception ex) { 084 throw new ServiceException(ErrorCode.E0100, getClass().getName(), ex.getMessage(), ex); 085 } 086 } 087 else { 088 Configuration conf = new Configuration(); 089 conf.set("hadoop.security.authentication", "simple"); 090 UserGroupInformation.setConfiguration(conf); 091 } 092 userUgiMap = new ConcurrentHashMap<String, UserGroupInformation>(); 093 } 094 095 public void destroy() { 096 userUgiMap = null; 097 super.destroy(); 098 } 099 100 private UserGroupInformation getUGI(String user) throws IOException { 101 UserGroupInformation ugi = userUgiMap.get(user); 102 if (ugi == null) { 103 // taking care of a race condition, the latest UGI will be discarded 104 ugi = UserGroupInformation.createProxyUser(user, UserGroupInformation.getLoginUser()); 105 userUgiMap.putIfAbsent(user, ugi); 106 } 107 return ugi; 108 } 109 110 /** 111 * Return a JobClient created with the provided user/group. 112 * 113 * @param conf JobConf with all necessary information to create the JobClient. 114 * @return JobClient created with the provided user/group. 115 * @throws HadoopAccessorException if the client could not be created. 116 */ 117 public JobClient createJobClient(String user, String group, final JobConf conf) throws HadoopAccessorException { 118 ParamChecker.notEmpty(user, "user"); 119 ParamChecker.notEmpty(group, "group"); 120 validateJobTracker(conf.get("mapred.job.tracker")); 121 try { 122 UserGroupInformation ugi = getUGI(user); 123 JobClient jobClient = ugi.doAs(new PrivilegedExceptionAction<JobClient>() { 124 public JobClient run() throws Exception { 125 return new JobClient(conf); 126 } 127 }); 128 Token<DelegationTokenIdentifier> mrdt = jobClient.getDelegationToken(new Text("mr token")); 129 conf.getCredentials().addToken(new Text("mr token"), mrdt); 130 return jobClient; 131 } 132 catch (InterruptedException ex) { 133 throw new HadoopAccessorException(ErrorCode.E0902, ex); 134 } 135 catch (IOException ex) { 136 throw new HadoopAccessorException(ErrorCode.E0902, ex); 137 } 138 } 139 140 /** 141 * Return a FileSystem created with the provided user/group. 142 * 143 * @param conf Configuration with all necessary information to create the FileSystem. 144 * @return FileSystem created with the provided user/group. 145 * @throws HadoopAccessorException if the filesystem could not be created. 146 */ 147 public FileSystem createFileSystem(String user, String group, final Configuration conf) 148 throws HadoopAccessorException { 149 ParamChecker.notEmpty(user, "user"); 150 ParamChecker.notEmpty(group, "group"); 151 try { 152 validateNameNode(new URI(conf.get("fs.default.name")).getAuthority()); 153 UserGroupInformation ugi = getUGI(user); 154 return ugi.doAs(new PrivilegedExceptionAction<FileSystem>() { 155 public FileSystem run() throws Exception { 156 Configuration defaultConf = new Configuration(); 157 XConfiguration.copy(conf, defaultConf); 158 return FileSystem.get(defaultConf); 159 } 160 }); 161 } 162 catch (InterruptedException ex) { 163 throw new HadoopAccessorException(ErrorCode.E0902, ex); 164 } 165 catch (IOException ex) { 166 throw new HadoopAccessorException(ErrorCode.E0902, ex); 167 } 168 catch (URISyntaxException ex) { 169 throw new HadoopAccessorException(ErrorCode.E0902, ex); 170 } 171 } 172 173 /** 174 * Return a FileSystem created with the provided user/group for the specified URI. 175 * 176 * @param uri file system URI. 177 * @param conf Configuration with all necessary information to create the FileSystem. 178 * @return FileSystem created with the provided user/group. 179 * @throws HadoopAccessorException if the filesystem could not be created. 180 */ 181 public FileSystem createFileSystem(String user, String group, final URI uri, final Configuration conf) 182 throws HadoopAccessorException { 183 ParamChecker.notEmpty(user, "user"); 184 ParamChecker.notEmpty(group, "group"); 185 validateNameNode(uri.getAuthority()); 186 try { 187 UserGroupInformation ugi = getUGI(user); 188 return ugi.doAs(new PrivilegedExceptionAction<FileSystem>() { 189 public FileSystem run() throws Exception { 190 Configuration defaultConf = new Configuration(); 191 XConfiguration.copy(conf, defaultConf); 192 return FileSystem.get(uri, defaultConf); 193 } 194 }); 195 } 196 catch (InterruptedException ex) { 197 throw new HadoopAccessorException(ErrorCode.E0902, ex); 198 } 199 catch (IOException ex) { 200 throw new HadoopAccessorException(ErrorCode.E0902, ex); 201 } 202 } 203 204 205 public void addFileToClassPath(String user, String group, final Path file, final Configuration conf) 206 throws IOException { 207 ParamChecker.notEmpty(user, "user"); 208 ParamChecker.notEmpty(group, "group"); 209 try { 210 UserGroupInformation ugi = getUGI(user); 211 ugi.doAs(new PrivilegedExceptionAction<Void>() { 212 public Void run() throws Exception { 213 Configuration defaultConf = new Configuration(); 214 XConfiguration.copy(conf, defaultConf); 215 //Doing this NOP add first to have the FS created and cached 216 DistributedCache.addFileToClassPath(file, defaultConf); 217 218 DistributedCache.addFileToClassPath(file, conf); 219 return null; 220 } 221 }); 222 223 } 224 catch (InterruptedException ex) { 225 throw new IOException(ex); 226 } 227 228 } 229 230 }