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; 032 033import java.io.IOException; 034import java.net.HttpURLConnection; 035import java.net.InetSocketAddress; 036import java.net.URL; 037import java.net.URLEncoder; 038import java.util.HashMap; 039import 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 062public 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}