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}