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