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