Back to home page

OSCL-LXR

 
 

    


0001 /**
0002  * Licensed to the Apache Software Foundation (ASF) under one
0003  * or more contributor license agreements.  See the NOTICE file
0004  * distributed with this work for additional information
0005  * regarding copyright ownership.  The ASF licenses this file
0006  * to you under the Apache License, Version 2.0 (the
0007  * "License"); you may not use this file except in compliance
0008  * with the License.  You may obtain a copy of the License at
0009  *
0010  *     http://www.apache.org/licenses/LICENSE-2.0
0011  *
0012  * Unless required by applicable law or agreed to in writing, software
0013  * distributed under the License is distributed on an "AS IS" BASIS,
0014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0015  * See the License for the specific language governing permissions and
0016  * limitations under the License.
0017  */
0018 
0019 package org.apache.hive.service.auth;
0020 
0021 import java.security.AccessControlContext;
0022 import java.security.AccessController;
0023 import java.security.PrivilegedExceptionAction;
0024 import java.util.Arrays;
0025 import java.util.HashMap;
0026 import java.util.HashSet;
0027 import java.util.Map;
0028 import java.util.Random;
0029 import java.util.Set;
0030 import java.util.StringTokenizer;
0031 
0032 import javax.security.auth.Subject;
0033 
0034 import org.apache.commons.codec.binary.Base64;
0035 import org.apache.commons.logging.Log;
0036 import org.apache.commons.logging.LogFactory;
0037 import org.apache.hadoop.hive.shims.ShimLoader;
0038 import org.apache.hadoop.security.UserGroupInformation;
0039 import org.apache.http.protocol.BasicHttpContext;
0040 import org.apache.http.protocol.HttpContext;
0041 import org.ietf.jgss.GSSContext;
0042 import org.ietf.jgss.GSSManager;
0043 import org.ietf.jgss.GSSName;
0044 import org.ietf.jgss.Oid;
0045 
0046 /**
0047  * Utility functions for HTTP mode authentication.
0048  */
0049 public final class HttpAuthUtils {
0050   public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
0051   public static final String AUTHORIZATION = "Authorization";
0052   public static final String BASIC = "Basic";
0053   public static final String NEGOTIATE = "Negotiate";
0054   private static final Log LOG = LogFactory.getLog(HttpAuthUtils.class);
0055   private static final String COOKIE_ATTR_SEPARATOR = "&";
0056   private static final String COOKIE_CLIENT_USER_NAME = "cu";
0057   private static final String COOKIE_CLIENT_RAND_NUMBER = "rn";
0058   private static final String COOKIE_KEY_VALUE_SEPARATOR = "=";
0059   private static final Set<String> COOKIE_ATTRIBUTES =
0060     new HashSet<String>(Arrays.asList(COOKIE_CLIENT_USER_NAME, COOKIE_CLIENT_RAND_NUMBER));
0061 
0062   /**
0063    * @return Stringified Base64 encoded kerberosAuthHeader on success
0064    * @throws Exception
0065    */
0066   public static String getKerberosServiceTicket(String principal, String host,
0067       String serverHttpUrl, boolean assumeSubject) throws Exception {
0068     String serverPrincipal =
0069         ShimLoader.getHadoopThriftAuthBridge().getServerPrincipal(principal, host);
0070     if (assumeSubject) {
0071       // With this option, we're assuming that the external application,
0072       // using the JDBC driver has done a JAAS kerberos login already
0073       AccessControlContext context = AccessController.getContext();
0074       Subject subject = Subject.getSubject(context);
0075       if (subject == null) {
0076         throw new Exception("The Subject is not set");
0077       }
0078       return Subject.doAs(subject, new HttpKerberosClientAction(serverPrincipal, serverHttpUrl));
0079     } else {
0080       // JAAS login from ticket cache to setup the client UserGroupInformation
0081       UserGroupInformation clientUGI =
0082           ShimLoader.getHadoopThriftAuthBridge().getCurrentUGIWithConf("kerberos");
0083       return clientUGI.doAs(new HttpKerberosClientAction(serverPrincipal, serverHttpUrl));
0084     }
0085   }
0086 
0087   /**
0088    * Creates and returns a HS2 cookie token.
0089    * @param clientUserName Client User name.
0090    * @return An unsigned cookie token generated from input parameters.
0091    * The final cookie generated is of the following format :
0092    * {@code cu=<username>&rn=<randomNumber>&s=<cookieSignature>}
0093    */
0094   public static String createCookieToken(String clientUserName) {
0095     StringBuffer sb = new StringBuffer();
0096     sb.append(COOKIE_CLIENT_USER_NAME).append(COOKIE_KEY_VALUE_SEPARATOR).append(clientUserName)
0097       .append(COOKIE_ATTR_SEPARATOR);
0098     sb.append(COOKIE_CLIENT_RAND_NUMBER).append(COOKIE_KEY_VALUE_SEPARATOR)
0099       .append((new Random(System.currentTimeMillis())).nextLong());
0100     return sb.toString();
0101   }
0102 
0103   /**
0104    * Parses a cookie token to retrieve client user name.
0105    * @param tokenStr Token String.
0106    * @return A valid user name if input is of valid format, else returns null.
0107    */
0108   public static String getUserNameFromCookieToken(String tokenStr) {
0109     Map<String, String> map = splitCookieToken(tokenStr);
0110 
0111     if (!map.keySet().equals(COOKIE_ATTRIBUTES)) {
0112       LOG.error("Invalid token with missing attributes " + tokenStr);
0113       return null;
0114     }
0115     return map.get(COOKIE_CLIENT_USER_NAME);
0116   }
0117 
0118   /**
0119    * Splits the cookie token into attributes pairs.
0120    * @param str input token.
0121    * @return a map with the attribute pairs of the token if the input is valid.
0122    * Else, returns null.
0123    */
0124   private static Map<String, String> splitCookieToken(String tokenStr) {
0125     Map<String, String> map = new HashMap<String, String>();
0126     StringTokenizer st = new StringTokenizer(tokenStr, COOKIE_ATTR_SEPARATOR);
0127 
0128     while (st.hasMoreTokens()) {
0129       String part = st.nextToken();
0130       int separator = part.indexOf(COOKIE_KEY_VALUE_SEPARATOR);
0131       if (separator == -1) {
0132         LOG.error("Invalid token string " + tokenStr);
0133         return null;
0134       }
0135       String key = part.substring(0, separator);
0136       String value = part.substring(separator + 1);
0137       map.put(key, value);
0138     }
0139     return map;
0140   }
0141 
0142 
0143   private HttpAuthUtils() {
0144     throw new UnsupportedOperationException("Can't initialize class");
0145   }
0146 
0147   /**
0148    * We'll create an instance of this class within a doAs block so that the client's TGT credentials
0149    * can be read from the Subject
0150    */
0151   public static class HttpKerberosClientAction implements PrivilegedExceptionAction<String> {
0152     public static final String HTTP_RESPONSE = "HTTP_RESPONSE";
0153     public static final String SERVER_HTTP_URL = "SERVER_HTTP_URL";
0154     private final String serverPrincipal;
0155     private final String serverHttpUrl;
0156     private final Base64 base64codec;
0157     private final HttpContext httpContext;
0158 
0159     public HttpKerberosClientAction(String serverPrincipal, String serverHttpUrl) {
0160       this.serverPrincipal = serverPrincipal;
0161       this.serverHttpUrl = serverHttpUrl;
0162       base64codec = new Base64(0);
0163       httpContext = new BasicHttpContext();
0164       httpContext.setAttribute(SERVER_HTTP_URL, serverHttpUrl);
0165     }
0166 
0167     @Override
0168     public String run() throws Exception {
0169       // This Oid for Kerberos GSS-API mechanism.
0170       Oid mechOid = new Oid("1.2.840.113554.1.2.2");
0171       // Oid for kerberos principal name
0172       Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1");
0173       GSSManager manager = GSSManager.getInstance();
0174       // GSS name for server
0175       GSSName serverName = manager.createName(serverPrincipal, krb5PrincipalOid);
0176       // Create a GSSContext for authentication with the service.
0177       // We're passing client credentials as null since we want them to be read from the Subject.
0178       GSSContext gssContext =
0179           manager.createContext(serverName, mechOid, null, GSSContext.DEFAULT_LIFETIME);
0180       gssContext.requestMutualAuth(false);
0181       // Establish context
0182       byte[] inToken = new byte[0];
0183       byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length);
0184       gssContext.dispose();
0185       // Base64 encoded and stringified token for server
0186       return new String(base64codec.encode(outToken));
0187     }
0188   }
0189 }