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.hadoop.security.token.delegation.web;
019
020 import com.google.common.base.Preconditions;
021 import org.apache.hadoop.classification.InterfaceAudience;
022 import org.apache.hadoop.classification.InterfaceStability;
023 import org.apache.hadoop.io.Text;
024 import org.apache.hadoop.security.Credentials;
025 import org.apache.hadoop.security.SecurityUtil;
026 import org.apache.hadoop.security.UserGroupInformation;
027 import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
028 import org.apache.hadoop.security.authentication.client.AuthenticationException;
029 import org.apache.hadoop.security.authentication.client.ConnectionConfigurator;
030 import org.apache.hadoop.security.token.TokenIdentifier;
031 import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenIdentifier;
032
033 import java.io.IOException;
034 import java.net.HttpURLConnection;
035 import java.net.InetSocketAddress;
036 import java.net.URL;
037 import java.net.URLEncoder;
038 import java.util.HashMap;
039 import java.util.Map;
040
041 /**
042 * The <code>DelegationTokenAuthenticatedURL</code> is a
043 * {@link AuthenticatedURL} sub-class with built-in Hadoop Delegation Token
044 * functionality.
045 * <p/>
046 * The authentication mechanisms supported by default are Hadoop Simple
047 * authentication (also known as pseudo authentication) and Kerberos SPNEGO
048 * authentication.
049 * <p/>
050 * Additional authentication mechanisms can be supported via {@link
051 * DelegationTokenAuthenticator} implementations.
052 * <p/>
053 * The default {@link DelegationTokenAuthenticator} is the {@link
054 * KerberosDelegationTokenAuthenticator} class which supports
055 * automatic fallback from Kerberos SPNEGO to Hadoop Simple authentication via
056 * the {@link PseudoDelegationTokenAuthenticator} class.
057 * <p/>
058 * <code>AuthenticatedURL</code> instances are not thread-safe.
059 */
060 @InterfaceAudience.Public
061 @InterfaceStability.Unstable
062 public class DelegationTokenAuthenticatedURL extends AuthenticatedURL {
063
064 /**
065 * Constant used in URL's query string to perform a proxy user request, the
066 * value of the <code>DO_AS</code> parameter is the user the request will be
067 * done on behalf of.
068 */
069 static final String DO_AS = "doAs";
070
071 /**
072 * Client side authentication token that handles Delegation Tokens.
073 */
074 @InterfaceAudience.Public
075 @InterfaceStability.Unstable
076 public static class Token extends AuthenticatedURL.Token {
077 private
078 org.apache.hadoop.security.token.Token<AbstractDelegationTokenIdentifier>
079 delegationToken;
080
081 public org.apache.hadoop.security.token.Token<AbstractDelegationTokenIdentifier>
082 getDelegationToken() {
083 return delegationToken;
084 }
085 public void setDelegationToken(
086 org.apache.hadoop.security.token.Token<AbstractDelegationTokenIdentifier> delegationToken) {
087 this.delegationToken = delegationToken;
088 }
089
090 }
091
092 private static Class<? extends DelegationTokenAuthenticator>
093 DEFAULT_AUTHENTICATOR = KerberosDelegationTokenAuthenticator.class;
094
095 /**
096 * Sets the default {@link DelegationTokenAuthenticator} class to use when an
097 * {@link DelegationTokenAuthenticatedURL} instance is created without
098 * specifying one.
099 *
100 * The default class is {@link KerberosDelegationTokenAuthenticator}
101 *
102 * @param authenticator the authenticator class to use as default.
103 */
104 public static void setDefaultDelegationTokenAuthenticator(
105 Class<? extends DelegationTokenAuthenticator> authenticator) {
106 DEFAULT_AUTHENTICATOR = authenticator;
107 }
108
109 /**
110 * Returns the default {@link DelegationTokenAuthenticator} class to use when
111 * an {@link DelegationTokenAuthenticatedURL} instance is created without
112 * specifying one.
113 * <p/>
114 * The default class is {@link KerberosDelegationTokenAuthenticator}
115 *
116 * @return the delegation token authenticator class to use as default.
117 */
118 public static Class<? extends DelegationTokenAuthenticator>
119 getDefaultDelegationTokenAuthenticator() {
120 return DEFAULT_AUTHENTICATOR;
121 }
122
123 private static DelegationTokenAuthenticator
124 obtainDelegationTokenAuthenticator(DelegationTokenAuthenticator dta,
125 ConnectionConfigurator connConfigurator) {
126 try {
127 if (dta == null) {
128 dta = DEFAULT_AUTHENTICATOR.newInstance();
129 dta.setConnectionConfigurator(connConfigurator);
130 }
131 return dta;
132 } catch (Exception ex) {
133 throw new IllegalArgumentException(ex);
134 }
135 }
136
137 private boolean useQueryStringforDelegationToken = false;
138
139 /**
140 * Creates an <code>DelegationTokenAuthenticatedURL</code>.
141 * <p/>
142 * An instance of the default {@link DelegationTokenAuthenticator} will be
143 * used.
144 */
145 public DelegationTokenAuthenticatedURL() {
146 this(null, null);
147 }
148
149 /**
150 * Creates an <code>DelegationTokenAuthenticatedURL</code>.
151 *
152 * @param authenticator the {@link DelegationTokenAuthenticator} instance to
153 * use, if <code>null</code> the default one will be used.
154 */
155 public DelegationTokenAuthenticatedURL(
156 DelegationTokenAuthenticator authenticator) {
157 this(authenticator, null);
158 }
159
160 /**
161 * Creates an <code>DelegationTokenAuthenticatedURL</code> using the default
162 * {@link DelegationTokenAuthenticator} class.
163 *
164 * @param connConfigurator a connection configurator.
165 */
166 public DelegationTokenAuthenticatedURL(
167 ConnectionConfigurator connConfigurator) {
168 this(null, connConfigurator);
169 }
170
171 /**
172 * Creates an <code>DelegationTokenAuthenticatedURL</code>.
173 *
174 * @param authenticator the {@link DelegationTokenAuthenticator} instance to
175 * use, if <code>null</code> the default one will be used.
176 * @param connConfigurator a connection configurator.
177 */
178 public DelegationTokenAuthenticatedURL(
179 DelegationTokenAuthenticator authenticator,
180 ConnectionConfigurator connConfigurator) {
181 super(obtainDelegationTokenAuthenticator(authenticator, connConfigurator),
182 connConfigurator);
183 }
184
185 /**
186 * Sets if delegation token should be transmitted in the URL query string.
187 * By default it is transmitted using the
188 * {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP header.
189 * <p/>
190 * This method is provided to enable WebHDFS backwards compatibility.
191 *
192 * @param useQueryString <code>TRUE</code> if the token is transmitted in the
193 * URL query string, <code>FALSE</code> if the delegation token is transmitted
194 * using the {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP
195 * header.
196 */
197 @Deprecated
198 protected void setUseQueryStringForDelegationToken(boolean useQueryString) {
199 useQueryStringforDelegationToken = useQueryString;
200 }
201
202 /**
203 * Returns if delegation token is transmitted as a HTTP header.
204 *
205 * @return <code>TRUE</code> if the token is transmitted in the URL query
206 * string, <code>FALSE</code> if the delegation token is transmitted using the
207 * {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP header.
208 */
209 public boolean useQueryStringForDelegationToken() {
210 return useQueryStringforDelegationToken;
211 }
212
213 /**
214 * Returns an authenticated {@link HttpURLConnection}, it uses a Delegation
215 * Token only if the given auth token is an instance of {@link Token} and
216 * it contains a Delegation Token, otherwise use the configured
217 * {@link DelegationTokenAuthenticator} to authenticate the connection.
218 *
219 * @param url the URL to connect to. Only HTTP/S URLs are supported.
220 * @param token the authentication token being used for the user.
221 * @return an authenticated {@link HttpURLConnection}.
222 * @throws IOException if an IO error occurred.
223 * @throws AuthenticationException if an authentication exception occurred.
224 */
225 @Override
226 public HttpURLConnection openConnection(URL url, AuthenticatedURL.Token token)
227 throws IOException, AuthenticationException {
228 return (token instanceof Token) ? openConnection(url, (Token) token)
229 : super.openConnection(url ,token);
230 }
231
232 /**
233 * Returns an authenticated {@link HttpURLConnection}. If the Delegation
234 * Token is present, it will be used taking precedence over the configured
235 * <code>Authenticator</code>.
236 *
237 * @param url the URL to connect to. Only HTTP/S URLs are supported.
238 * @param token the authentication token being used for the user.
239 * @return an authenticated {@link HttpURLConnection}.
240 * @throws IOException if an IO error occurred.
241 * @throws AuthenticationException if an authentication exception occurred.
242 */
243 public HttpURLConnection openConnection(URL url, Token token)
244 throws IOException, AuthenticationException {
245 return openConnection(url, token, null);
246 }
247
248 private URL augmentURL(URL url, Map<String, String> params)
249 throws IOException {
250 if (params != null && params.size() > 0) {
251 String urlStr = url.toExternalForm();
252 StringBuilder sb = new StringBuilder(urlStr);
253 String separator = (urlStr.contains("?")) ? "&" : "?";
254 for (Map.Entry<String, String> param : params.entrySet()) {
255 sb.append(separator).append(param.getKey()).append("=").append(
256 param.getValue());
257 separator = "&";
258 }
259 url = new URL(sb.toString());
260 }
261 return url;
262 }
263
264 /**
265 * Returns an authenticated {@link HttpURLConnection}. If the Delegation
266 * Token is present, it will be used taking precedence over the configured
267 * <code>Authenticator</code>. If the <code>doAs</code> parameter is not NULL,
268 * the request will be done on behalf of the specified <code>doAs</code> user.
269 *
270 * @param url the URL to connect to. Only HTTP/S URLs are supported.
271 * @param token the authentication token being used for the user.
272 * @param doAs user to do the the request on behalf of, if NULL the request is
273 * as self.
274 * @return an authenticated {@link HttpURLConnection}.
275 * @throws IOException if an IO error occurred.
276 * @throws AuthenticationException if an authentication exception occurred.
277 */
278 @SuppressWarnings("unchecked")
279 public HttpURLConnection openConnection(URL url, Token token, String doAs)
280 throws IOException, AuthenticationException {
281 Preconditions.checkNotNull(url, "url");
282 Preconditions.checkNotNull(token, "token");
283 Map<String, String> extraParams = new HashMap<String, String>();
284 org.apache.hadoop.security.token.Token<? extends TokenIdentifier> dToken
285 = null;
286 // if we have valid auth token, it takes precedence over a delegation token
287 // and we don't even look for one.
288 if (!token.isSet()) {
289 // delegation token
290 Credentials creds = UserGroupInformation.getCurrentUser().
291 getCredentials();
292 if (!creds.getAllTokens().isEmpty()) {
293 InetSocketAddress serviceAddr = new InetSocketAddress(url.getHost(),
294 url.getPort());
295 Text service = SecurityUtil.buildTokenService(serviceAddr);
296 dToken = creds.getToken(service);
297 if (dToken != null) {
298 if (useQueryStringForDelegationToken()) {
299 // delegation token will go in the query string, injecting it
300 extraParams.put(
301 KerberosDelegationTokenAuthenticator.DELEGATION_PARAM,
302 dToken.encodeToUrlString());
303 } else {
304 // delegation token will go as request header, setting it in the
305 // auth-token to ensure no authentication handshake is triggered
306 // (if we have a delegation token, we are authenticated)
307 // the delegation token header is injected in the connection request
308 // at the end of this method.
309 token.delegationToken = (org.apache.hadoop.security.token.Token
310 <AbstractDelegationTokenIdentifier>) dToken;
311 }
312 }
313 }
314 }
315
316 // proxyuser
317 if (doAs != null) {
318 extraParams.put(DO_AS, URLEncoder.encode(doAs, "UTF-8"));
319 }
320
321 url = augmentURL(url, extraParams);
322 HttpURLConnection conn = super.openConnection(url, token);
323 if (!token.isSet() && !useQueryStringForDelegationToken() && dToken != null) {
324 // injecting the delegation token header in the connection request
325 conn.setRequestProperty(
326 DelegationTokenAuthenticator.DELEGATION_TOKEN_HEADER,
327 dToken.encodeToUrlString());
328 }
329 return conn;
330 }
331
332 /**
333 * Requests a delegation token using the configured <code>Authenticator</code>
334 * for authentication.
335 *
336 * @param url the URL to get the delegation token from. Only HTTP/S URLs are
337 * supported.
338 * @param token the authentication token being used for the user where the
339 * Delegation token will be stored.
340 * @param renewer the renewer user.
341 * @return a delegation token.
342 * @throws IOException if an IO error occurred.
343 * @throws AuthenticationException if an authentication exception occurred.
344 */
345 public org.apache.hadoop.security.token.Token<AbstractDelegationTokenIdentifier>
346 getDelegationToken(URL url, Token token, String renewer)
347 throws IOException, AuthenticationException {
348 return getDelegationToken(url, token, renewer, null);
349 }
350
351 /**
352 * Requests a delegation token using the configured <code>Authenticator</code>
353 * for authentication.
354 *
355 * @param url the URL to get the delegation token from. Only HTTP/S URLs are
356 * supported.
357 * @param token the authentication token being used for the user where the
358 * Delegation token will be stored.
359 * @param renewer the renewer user.
360 * @param doAsUser the user to do as, which will be the token owner.
361 * @return a delegation token.
362 * @throws IOException if an IO error occurred.
363 * @throws AuthenticationException if an authentication exception occurred.
364 */
365 public org.apache.hadoop.security.token.Token<AbstractDelegationTokenIdentifier>
366 getDelegationToken(URL url, Token token, String renewer, String doAsUser)
367 throws IOException, AuthenticationException {
368 Preconditions.checkNotNull(url, "url");
369 Preconditions.checkNotNull(token, "token");
370 try {
371 token.delegationToken =
372 ((KerberosDelegationTokenAuthenticator) getAuthenticator()).
373 getDelegationToken(url, token, renewer, doAsUser);
374 return token.delegationToken;
375 } catch (IOException ex) {
376 token.delegationToken = null;
377 throw ex;
378 }
379 }
380
381 /**
382 * Renews a delegation token from the server end-point using the
383 * configured <code>Authenticator</code> for authentication.
384 *
385 * @param url the URL to renew the delegation token from. Only HTTP/S URLs are
386 * supported.
387 * @param token the authentication token with the Delegation Token to renew.
388 * @throws IOException if an IO error occurred.
389 * @throws AuthenticationException if an authentication exception occurred.
390 */
391 public long renewDelegationToken(URL url, Token token)
392 throws IOException, AuthenticationException {
393 return renewDelegationToken(url, token, null);
394 }
395
396 /**
397 * Renews a delegation token from the server end-point using the
398 * configured <code>Authenticator</code> for authentication.
399 *
400 * @param url the URL to renew the delegation token from. Only HTTP/S URLs are
401 * supported.
402 * @param token the authentication token with the Delegation Token to renew.
403 * @param doAsUser the user to do as, which will be the token owner.
404 * @throws IOException if an IO error occurred.
405 * @throws AuthenticationException if an authentication exception occurred.
406 */
407 public long renewDelegationToken(URL url, Token token, String doAsUser)
408 throws IOException, AuthenticationException {
409 Preconditions.checkNotNull(url, "url");
410 Preconditions.checkNotNull(token, "token");
411 Preconditions.checkNotNull(token.delegationToken,
412 "No delegation token available");
413 try {
414 return ((KerberosDelegationTokenAuthenticator) getAuthenticator()).
415 renewDelegationToken(url, token, token.delegationToken, doAsUser);
416 } catch (IOException ex) {
417 token.delegationToken = null;
418 throw ex;
419 }
420 }
421
422 /**
423 * Cancels a delegation token from the server end-point. It does not require
424 * being authenticated by the configured <code>Authenticator</code>.
425 *
426 * @param url the URL to cancel the delegation token from. Only HTTP/S URLs
427 * are supported.
428 * @param token the authentication token with the Delegation Token to cancel.
429 * @throws IOException if an IO error occurred.
430 */
431 public void cancelDelegationToken(URL url, Token token)
432 throws IOException {
433 cancelDelegationToken(url, token, null);
434 }
435
436 /**
437 * Cancels a delegation token from the server end-point. It does not require
438 * being authenticated by the configured <code>Authenticator</code>.
439 *
440 * @param url the URL to cancel the delegation token from. Only HTTP/S URLs
441 * are supported.
442 * @param token the authentication token with the Delegation Token to cancel.
443 * @param doAsUser the user to do as, which will be the token owner.
444 * @throws IOException if an IO error occurred.
445 */
446 public void cancelDelegationToken(URL url, Token token, String doAsUser)
447 throws IOException {
448 Preconditions.checkNotNull(url, "url");
449 Preconditions.checkNotNull(token, "token");
450 Preconditions.checkNotNull(token.delegationToken,
451 "No delegation token available");
452 try {
453 ((KerberosDelegationTokenAuthenticator) getAuthenticator()).
454 cancelDelegationToken(url, token, token.delegationToken, doAsUser);
455 } finally {
456 token.delegationToken = null;
457 }
458 }
459
460 }