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 package org.apache.oozie.client; 019 020 import java.io.BufferedReader; 021 import java.io.File; 022 import java.io.FileReader; 023 import java.io.FileWriter; 024 import java.io.IOException; 025 import java.io.Writer; 026 import java.net.HttpURLConnection; 027 import java.net.URL; 028 import java.util.HashMap; 029 import java.util.Map; 030 031 import org.apache.hadoop.security.authentication.client.AuthenticatedURL; 032 import org.apache.hadoop.security.authentication.client.AuthenticationException; 033 import org.apache.hadoop.security.authentication.client.Authenticator; 034 import org.apache.hadoop.security.authentication.client.KerberosAuthenticator; 035 import org.apache.hadoop.security.authentication.client.PseudoAuthenticator; 036 037 /** 038 * This subclass of {@link XOozieClient} supports Kerberos HTTP SPNEGO and simple authentication. 039 */ 040 public class AuthOozieClient extends XOozieClient { 041 042 /** 043 * Java system property to specify a custom Authenticator implementation. 044 */ 045 public static final String AUTHENTICATOR_CLASS_SYS_PROP = "authenticator.class"; 046 047 /** 048 * Java system property that, if set the authentication token will be cached in the user home directory in a hidden 049 * file <code>.oozie-auth-token</code> with user read/write permissions only. 050 */ 051 public static final String USE_AUTH_TOKEN_CACHE_SYS_PROP = "oozie.auth.token.cache"; 052 053 /** 054 * File constant that defines the location of the authentication token cache file. 055 * <p/> 056 * It resolves to <code>${user.home}/.oozie-auth-token</code>. 057 */ 058 public static final File AUTH_TOKEN_CACHE_FILE = new File(System.getProperty("user.home"), ".oozie-auth-token"); 059 060 public static enum AuthType { 061 KERBEROS, SIMPLE 062 } 063 064 private String authOption = null; 065 066 /** 067 * Create an instance of the AuthOozieClient. 068 * 069 * @param oozieUrl the Oozie URL 070 */ 071 public AuthOozieClient(String oozieUrl) { 072 this(oozieUrl, null); 073 } 074 075 /** 076 * Create an instance of the AuthOozieClient. 077 * 078 * @param oozieUrl the Oozie URL 079 * @param authOption the auth option 080 */ 081 public AuthOozieClient(String oozieUrl, String authOption) { 082 super(oozieUrl); 083 this.authOption = authOption; 084 } 085 086 /** 087 * Create an authenticated connection to the Oozie server. 088 * <p/> 089 * It uses Hadoop-auth client authentication which by default supports 090 * Kerberos HTTP SPNEGO, Pseudo/Simple and anonymous. 091 * <p/> 092 * if the Java system property {@link #USE_AUTH_TOKEN_CACHE_SYS_PROP} is set to true Hadoop-auth 093 * authentication token will be cached/used in/from the '.oozie-auth-token' file in the user 094 * home directory. 095 * 096 * @param url the URL to open a HTTP connection to. 097 * @param method the HTTP method for the HTTP connection. 098 * @return an authenticated connection to the Oozie server. 099 * @throws IOException if an IO error occurred. 100 * @throws OozieClientException if an oozie client error occurred. 101 */ 102 @Override 103 protected HttpURLConnection createConnection(URL url, String method) throws IOException, OozieClientException { 104 boolean useAuthFile = System.getProperty(USE_AUTH_TOKEN_CACHE_SYS_PROP, "false").equalsIgnoreCase("true"); 105 AuthenticatedURL.Token readToken = new AuthenticatedURL.Token(); 106 AuthenticatedURL.Token currentToken = new AuthenticatedURL.Token(); 107 108 if (useAuthFile) { 109 readToken = readAuthToken(); 110 if (readToken != null) { 111 currentToken = new AuthenticatedURL.Token(readToken.toString()); 112 } 113 } 114 115 if (currentToken.isSet()) { 116 HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 117 conn.setRequestMethod("OPTIONS"); 118 AuthenticatedURL.injectToken(conn, currentToken); 119 if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) { 120 AUTH_TOKEN_CACHE_FILE.delete(); 121 currentToken = new AuthenticatedURL.Token(); 122 } 123 } 124 125 if (!currentToken.isSet()) { 126 Authenticator authenticator = getAuthenticator(); 127 try { 128 new AuthenticatedURL(authenticator).openConnection(url, currentToken); 129 } 130 catch (AuthenticationException ex) { 131 AUTH_TOKEN_CACHE_FILE.delete(); 132 throw new OozieClientException(OozieClientException.AUTHENTICATION, 133 "Could not authenticate, " + ex.getMessage(), ex); 134 } 135 } 136 if (useAuthFile && !currentToken.equals(readToken)) { 137 writeAuthToken(currentToken); 138 } 139 HttpURLConnection conn = super.createConnection(url, method); 140 AuthenticatedURL.injectToken(conn, currentToken); 141 142 return conn; 143 } 144 145 146 /** 147 * Read a authentication token cached in the user home directory. 148 * <p/> 149 * 150 * @return the authentication token cached in the user home directory, NULL if none. 151 */ 152 protected AuthenticatedURL.Token readAuthToken() { 153 AuthenticatedURL.Token authToken = null; 154 if (AUTH_TOKEN_CACHE_FILE.exists()) { 155 try { 156 BufferedReader reader = new BufferedReader(new FileReader(AUTH_TOKEN_CACHE_FILE)); 157 String line = reader.readLine(); 158 reader.close(); 159 if (line != null) { 160 authToken = new AuthenticatedURL.Token(line); 161 } 162 } 163 catch (IOException ex) { 164 //NOP 165 } 166 } 167 return authToken; 168 } 169 170 /** 171 * Write the current authentication token to the user home directory.authOption 172 * <p/> 173 * The file is written with user only read/write permissions. 174 * <p/> 175 * If the file cannot be updated or the user only ready/write permissions cannot be set the file is deleted. 176 * 177 * @param authToken the authentication token to cache. 178 */ 179 protected void writeAuthToken(AuthenticatedURL.Token authToken) { 180 try { 181 Writer writer = new FileWriter(AUTH_TOKEN_CACHE_FILE); 182 writer.write(authToken.toString()); 183 writer.close(); 184 // sets read-write permissions to owner only 185 AUTH_TOKEN_CACHE_FILE.setReadable(false, false); 186 AUTH_TOKEN_CACHE_FILE.setReadable(true, true); 187 AUTH_TOKEN_CACHE_FILE.setWritable(true, true); 188 } 189 catch (IOException ioe) { 190 // if case of any error we just delete the cache, if user-only 191 // write permissions are not properly set a security exception 192 // is thrown and the file will be deleted. 193 AUTH_TOKEN_CACHE_FILE.delete(); 194 } 195 } 196 197 /** 198 * Return the Hadoop-auth Authenticator to use. 199 * <p/> 200 * It first looks for value of command line option 'auth', if not set it continues to check 201 * {@link #AUTHENTICATOR_CLASS_SYS_PROP} Java system property for Authenticator. 202 * <p/> 203 * It the value of the {@link #AUTHENTICATOR_CLASS_SYS_PROP} is not set it uses 204 * Hadoop-auth <code>KerberosAuthenticator</code> which supports both Kerberos HTTP SPNEGO and Pseudo/simple 205 * authentication. 206 * 207 * @return the Authenticator to use, <code>NULL</code> if none. 208 * 209 * @throws OozieClientException thrown if the authenticator could not be instantiated. 210 */ 211 protected Authenticator getAuthenticator() throws OozieClientException { 212 if (authOption != null) { 213 try { 214 Class<? extends Authenticator> authClass = getAuthenticators().get(authOption.toUpperCase()); 215 if (authClass == null) { 216 throw new OozieClientException(OozieClientException.AUTHENTICATION, 217 "Authenticator class not found [" + authClass + "]"); 218 } 219 return authClass.newInstance(); 220 } 221 catch (IllegalArgumentException iae) { 222 throw new OozieClientException(OozieClientException.AUTHENTICATION, "Invalid options provided for auth: " + authOption 223 + ", (" + AuthType.KERBEROS + " or " + AuthType.SIMPLE + " expected.)"); 224 } 225 catch (InstantiationException ex) { 226 throw new OozieClientException(OozieClientException.AUTHENTICATION, 227 "Could not instantiate Authenticator for option [" + authOption + "], " + 228 ex.getMessage(), ex); 229 } 230 catch (IllegalAccessException ex) { 231 throw new OozieClientException(OozieClientException.AUTHENTICATION, 232 "Could not instantiate Authenticator for option [" + authOption + "], " + 233 ex.getMessage(), ex); 234 } 235 236 } 237 238 String className = System.getProperty(AUTHENTICATOR_CLASS_SYS_PROP, KerberosAuthenticator.class.getName()); 239 if (className != null) { 240 try { 241 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 242 Class<? extends Object> klass = (cl != null) ? cl.loadClass(className) : 243 getClass().getClassLoader().loadClass(className); 244 if (klass == null) { 245 throw new OozieClientException(OozieClientException.AUTHENTICATION, 246 "Authenticator class not found [" + className + "]"); 247 } 248 return (Authenticator) klass.newInstance(); 249 } 250 catch (Exception ex) { 251 throw new OozieClientException(OozieClientException.AUTHENTICATION, 252 "Could not instantiate Authenticator [" + className + "], " + 253 ex.getMessage(), ex); 254 } 255 } 256 else { 257 throw new OozieClientException(OozieClientException.AUTHENTICATION, 258 "Authenticator class not found [" + className + "]"); 259 } 260 } 261 262 /** 263 * Get the map for classes of Authenticator. 264 * Default values are: 265 * null -> KerberosAuthenticator 266 * SIMPLE -> PseudoAuthenticator 267 * KERBEROS -> KerberosAuthenticator 268 * 269 * @return the map for classes of Authenticator 270 * @throws OozieClientException 271 */ 272 protected Map<String, Class<? extends Authenticator>> getAuthenticators() { 273 Map<String, Class<? extends Authenticator>> authClasses = new HashMap<String, Class<? extends Authenticator>>(); 274 authClasses.put(AuthType.KERBEROS.toString(), KerberosAuthenticator.class); 275 authClasses.put(AuthType.SIMPLE.toString(), PseudoAuthenticator.class); 276 authClasses.put(null, KerberosAuthenticator.class); 277 return authClasses; 278 } 279 280 /** 281 * Get authOption 282 * 283 * @return the authOption 284 */ 285 public String getAuthOption() { 286 return authOption; 287 } 288 289 }