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.log;
019
020import java.io.IOException;
021import java.net.HttpURLConnection;
022import java.net.URL;
023import java.net.URLConnection;
024
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
027import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
028import org.apache.hadoop.security.authentication.client.AuthenticatedURL.Token;
029import org.apache.hadoop.security.authentication.client.AuthenticationException;
030import org.apache.hadoop.security.authentication.client.Authenticator;
031import org.apache.log4j.Logger;
032
033/**
034 * 1. Returns a usable https connection if an authenticator is configured;
035 * otherwise returns the given connection as is.
036 *
037 * 2. Caches a authentication token and re-uses it for connections long as it is
038 * valid.
039 *
040 * 3. Performs authentication via configured authenticator only if the token
041 * becomes invalid.
042 * <p>
043 * TODO
044 * 1. This code is not cluster aware (it always watches if security is enabled
045 * or not on the current cluster)
046 *
047 * 2. This code is not user aware (the authenticator will pick up creds of the
048 * process uid that is running this code)
049 */
050public class SecureHTTPURLConnectionProvider {
051  private static final Logger LOG = Logger.getLogger(SecureHTTPURLConnectionProvider.class);
052
053  private static Token token = new Token();
054
055  private static Authenticator authenticator = null;
056
057  static {
058    Configuration conf = new Configuration();
059    Class<? extends Authenticator> clazz = conf.getClass(
060        CommonConfigurationKeysPublic.LOG_LEVEL_AUTHENTICATOR_CLASS,
061        null, Authenticator.class);
062
063    if (clazz != null) {
064      try {
065        authenticator = clazz.getDeclaredConstructor()
066          .newInstance();
067      } catch (Exception e) {
068        throw new RuntimeException(e);
069      }
070    }
071  }
072
073  public static URLConnection openConnection(URL url) throws IOException {
074    if (authenticator == null) {
075      return url.openConnection();
076    }
077
078    synchronized (SecureHTTPURLConnectionProvider.class) {
079
080      HttpURLConnection conn = null;
081      if (token.isSet()) { // try the existing token
082        conn = (HttpURLConnection) url.openConnection();
083        AuthenticatedURL.injectToken(conn, token);
084        conn.connect();
085        if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
086          token = new Token(); // refresh the token on auth failure
087          if (LOG.isDebugEnabled()) {
088            LOG.debug("Received HTTP " + conn.getResponseCode() + ". Created a new token.");
089          }
090        } else {
091          if (LOG.isDebugEnabled()) {
092            LOG.debug("Received HTTP " + conn.getResponseCode() + ". Token still good..");
093          }
094        }
095      }
096
097      if (!token.isSet()) { // authenticate only if the token is not set
098        LOG.debug("Token not set. Peforming authentication..");
099        AuthenticatedURL authenticatedURL = new AuthenticatedURL(authenticator);
100        try {
101          conn = authenticatedURL.openConnection(url, token);
102        } catch (AuthenticationException e) {
103          LOG.error("Authentication failed while connecting to URL: " + url.toString());
104        }
105      }
106
107      return conn;
108    }
109  }
110}