0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019 package org.apache.hive.service.cli.thrift;
0020
0021 import java.io.IOException;
0022 import java.io.UnsupportedEncodingException;
0023 import java.security.PrivilegedExceptionAction;
0024 import java.util.Map;
0025 import java.util.Random;
0026 import java.util.Set;
0027 import java.util.concurrent.TimeUnit;
0028
0029 import javax.servlet.ServletException;
0030 import javax.servlet.http.Cookie;
0031 import javax.servlet.http.HttpServletRequest;
0032 import javax.servlet.http.HttpServletResponse;
0033 import javax.ws.rs.core.NewCookie;
0034
0035 import org.apache.commons.codec.binary.Base64;
0036 import org.apache.commons.codec.binary.StringUtils;
0037 import org.apache.commons.logging.Log;
0038 import org.apache.commons.logging.LogFactory;
0039 import org.apache.hadoop.hive.conf.HiveConf;
0040 import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
0041 import org.apache.hadoop.hive.shims.HadoopShims.KerberosNameShim;
0042 import org.apache.hadoop.hive.shims.ShimLoader;
0043 import org.apache.hadoop.security.UserGroupInformation;
0044 import org.apache.hive.service.auth.AuthenticationProviderFactory;
0045 import org.apache.hive.service.auth.AuthenticationProviderFactory.AuthMethods;
0046 import org.apache.hive.service.auth.HiveAuthFactory;
0047 import org.apache.hive.service.auth.HttpAuthUtils;
0048 import org.apache.hive.service.auth.HttpAuthenticationException;
0049 import org.apache.hive.service.auth.PasswdAuthenticationProvider;
0050 import org.apache.hive.service.cli.session.SessionManager;
0051 import org.apache.hive.service.CookieSigner;
0052 import org.apache.thrift.TProcessor;
0053 import org.apache.thrift.protocol.TProtocolFactory;
0054 import org.apache.thrift.server.TServlet;
0055 import org.ietf.jgss.GSSContext;
0056 import org.ietf.jgss.GSSCredential;
0057 import org.ietf.jgss.GSSException;
0058 import org.ietf.jgss.GSSManager;
0059 import org.ietf.jgss.GSSName;
0060 import org.ietf.jgss.Oid;
0061
0062
0063
0064
0065
0066
0067 public class ThriftHttpServlet extends TServlet {
0068
0069 private static final long serialVersionUID = 1L;
0070 public static final Log LOG = LogFactory.getLog(ThriftHttpServlet.class.getName());
0071 private final String authType;
0072 private final UserGroupInformation serviceUGI;
0073 private final UserGroupInformation httpUGI;
0074 private HiveConf hiveConf = new HiveConf();
0075
0076
0077 private CookieSigner signer;
0078 public static final String AUTH_COOKIE = "hive.server2.auth";
0079 private static final Random RAN = new Random();
0080 private boolean isCookieAuthEnabled;
0081 private String cookieDomain;
0082 private String cookiePath;
0083 private int cookieMaxAge;
0084 private boolean isCookieSecure;
0085 private boolean isHttpOnlyCookie;
0086
0087 public ThriftHttpServlet(TProcessor processor, TProtocolFactory protocolFactory,
0088 String authType, UserGroupInformation serviceUGI, UserGroupInformation httpUGI) {
0089 super(processor, protocolFactory);
0090 this.authType = authType;
0091 this.serviceUGI = serviceUGI;
0092 this.httpUGI = httpUGI;
0093 this.isCookieAuthEnabled = hiveConf.getBoolVar(
0094 ConfVars.HIVE_SERVER2_THRIFT_HTTP_COOKIE_AUTH_ENABLED);
0095
0096 if (isCookieAuthEnabled) {
0097
0098 String secret = Long.toString(RAN.nextLong());
0099 LOG.debug("Using the random number as the secret for cookie generation " + secret);
0100 this.signer = new CookieSigner(secret.getBytes());
0101 this.cookieMaxAge = (int) hiveConf.getTimeVar(
0102 ConfVars.HIVE_SERVER2_THRIFT_HTTP_COOKIE_MAX_AGE, TimeUnit.SECONDS);
0103 this.cookieDomain = hiveConf.getVar(ConfVars.HIVE_SERVER2_THRIFT_HTTP_COOKIE_DOMAIN);
0104 this.cookiePath = hiveConf.getVar(ConfVars.HIVE_SERVER2_THRIFT_HTTP_COOKIE_PATH);
0105 this.isCookieSecure = hiveConf.getBoolVar(
0106 ConfVars.HIVE_SERVER2_THRIFT_HTTP_COOKIE_IS_SECURE);
0107 this.isHttpOnlyCookie = hiveConf.getBoolVar(
0108 ConfVars.HIVE_SERVER2_THRIFT_HTTP_COOKIE_IS_HTTPONLY);
0109 }
0110 }
0111
0112 @Override
0113 protected void doPost(HttpServletRequest request, HttpServletResponse response)
0114 throws ServletException, IOException {
0115 String clientUserName = null;
0116 String clientIpAddress;
0117 boolean requireNewCookie = false;
0118
0119 try {
0120
0121
0122 if (isCookieAuthEnabled) {
0123 clientUserName = validateCookie(request);
0124 requireNewCookie = (clientUserName == null);
0125 if (requireNewCookie) {
0126 LOG.info("Could not validate cookie sent, will try to generate a new cookie");
0127 }
0128 }
0129
0130
0131
0132 if (clientUserName == null) {
0133
0134 if (isKerberosAuthMode(authType)) {
0135 clientUserName = doKerberosAuth(request);
0136 }
0137
0138 else {
0139 clientUserName = doPasswdAuth(request, authType);
0140 }
0141 }
0142 LOG.debug("Client username: " + clientUserName);
0143
0144
0145 SessionManager.setUserName(clientUserName);
0146
0147
0148 String doAsQueryParam = getDoAsQueryParam(request.getQueryString());
0149 if (doAsQueryParam != null) {
0150 SessionManager.setProxyUserName(doAsQueryParam);
0151 }
0152
0153 clientIpAddress = request.getRemoteAddr();
0154 LOG.debug("Client IP Address: " + clientIpAddress);
0155
0156 SessionManager.setIpAddress(clientIpAddress);
0157
0158 if (requireNewCookie &&
0159 !authType.equalsIgnoreCase(HiveAuthFactory.AuthTypes.NOSASL.toString())) {
0160 String cookieToken = HttpAuthUtils.createCookieToken(clientUserName);
0161 Cookie hs2Cookie = createCookie(signer.signCookie(cookieToken));
0162
0163 if (isHttpOnlyCookie) {
0164 response.setHeader("SET-COOKIE", getHttpOnlyCookieHeader(hs2Cookie));
0165 } else {
0166 response.addCookie(hs2Cookie);
0167 }
0168 LOG.info("Cookie added for clientUserName " + clientUserName);
0169 }
0170 super.doPost(request, response);
0171 }
0172 catch (HttpAuthenticationException e) {
0173 LOG.error("Error: ", e);
0174
0175 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
0176 if(isKerberosAuthMode(authType)) {
0177 response.addHeader(HttpAuthUtils.WWW_AUTHENTICATE, HttpAuthUtils.NEGOTIATE);
0178 }
0179 response.getWriter().println("Authentication Error: " + e.getMessage());
0180 }
0181 finally {
0182
0183 SessionManager.clearUserName();
0184 SessionManager.clearIpAddress();
0185 SessionManager.clearProxyUserName();
0186 }
0187 }
0188
0189
0190
0191
0192
0193
0194
0195
0196 private String getClientNameFromCookie(Cookie[] cookies) {
0197
0198 String currName, currValue;
0199
0200
0201
0202
0203
0204
0205
0206 for (Cookie currCookie : cookies) {
0207
0208 currName = currCookie.getName();
0209 if (!currName.equals(AUTH_COOKIE)) {
0210
0211 continue;
0212 }
0213
0214 currValue = currCookie.getValue();
0215
0216 currValue = signer.verifyAndExtract(currValue);
0217
0218 if (currValue != null) {
0219 String userName = HttpAuthUtils.getUserNameFromCookieToken(currValue);
0220
0221 if (userName == null) {
0222 LOG.warn("Invalid cookie token " + currValue);
0223 continue;
0224 }
0225
0226 if (LOG.isDebugEnabled()) {
0227 LOG.debug("Validated the cookie for user " + userName);
0228 }
0229 return userName;
0230 }
0231 }
0232
0233 return null;
0234 }
0235
0236
0237
0238
0239
0240
0241
0242 private String toCookieStr(Cookie[] cookies) {
0243 String cookieStr = "";
0244
0245 for (Cookie c : cookies) {
0246 cookieStr += c.getName() + "=" + c.getValue() + " ;\n";
0247 }
0248 return cookieStr;
0249 }
0250
0251
0252
0253
0254
0255
0256
0257
0258
0259 private String validateCookie(HttpServletRequest request) throws UnsupportedEncodingException {
0260
0261 Cookie[] cookies = request.getCookies();
0262
0263 if (cookies == null) {
0264 if (LOG.isDebugEnabled()) {
0265 LOG.debug("No valid cookies associated with the request " + request);
0266 }
0267 return null;
0268 }
0269 if (LOG.isDebugEnabled()) {
0270 LOG.debug("Received cookies: " + toCookieStr(cookies));
0271 }
0272 return getClientNameFromCookie(cookies);
0273 }
0274
0275
0276
0277
0278
0279
0280
0281 private Cookie createCookie(String str) throws UnsupportedEncodingException {
0282 if (LOG.isDebugEnabled()) {
0283 LOG.debug("Cookie name = " + AUTH_COOKIE + " value = " + str);
0284 }
0285 Cookie cookie = new Cookie(AUTH_COOKIE, str);
0286
0287 cookie.setMaxAge(cookieMaxAge);
0288 if (cookieDomain != null) {
0289 cookie.setDomain(cookieDomain);
0290 }
0291 if (cookiePath != null) {
0292 cookie.setPath(cookiePath);
0293 }
0294 cookie.setSecure(isCookieSecure);
0295 return cookie;
0296 }
0297
0298
0299
0300
0301
0302
0303 private static String getHttpOnlyCookieHeader(Cookie cookie) {
0304 NewCookie newCookie = new NewCookie(cookie.getName(), cookie.getValue(),
0305 cookie.getPath(), cookie.getDomain(), cookie.getVersion(),
0306 cookie.getComment(), cookie.getMaxAge(), cookie.getSecure());
0307 return newCookie + "; HttpOnly";
0308 }
0309
0310
0311
0312
0313
0314
0315
0316 private String doPasswdAuth(HttpServletRequest request, String authType)
0317 throws HttpAuthenticationException {
0318 String userName = getUsername(request, authType);
0319
0320 if (!authType.equalsIgnoreCase(HiveAuthFactory.AuthTypes.NOSASL.toString())) {
0321 try {
0322 AuthMethods authMethod = AuthMethods.getValidAuthMethod(authType);
0323 PasswdAuthenticationProvider provider =
0324 AuthenticationProviderFactory.getAuthenticationProvider(authMethod);
0325 provider.Authenticate(userName, getPassword(request, authType));
0326
0327 } catch (Exception e) {
0328 throw new HttpAuthenticationException(e);
0329 }
0330 }
0331 return userName;
0332 }
0333
0334
0335
0336
0337
0338
0339
0340
0341
0342
0343
0344 private String doKerberosAuth(HttpServletRequest request)
0345 throws HttpAuthenticationException {
0346
0347 if (httpUGI != null) {
0348 try {
0349 return httpUGI.doAs(new HttpKerberosServerAction(request, httpUGI));
0350 } catch (Exception e) {
0351 LOG.info("Failed to authenticate with http/_HOST kerberos principal, " +
0352 "trying with hive/_HOST kerberos principal");
0353 }
0354 }
0355
0356 try {
0357 return serviceUGI.doAs(new HttpKerberosServerAction(request, serviceUGI));
0358 } catch (Exception e) {
0359 LOG.error("Failed to authenticate with hive/_HOST kerberos principal");
0360 throw new HttpAuthenticationException(e);
0361 }
0362
0363 }
0364
0365 class HttpKerberosServerAction implements PrivilegedExceptionAction<String> {
0366 HttpServletRequest request;
0367 UserGroupInformation serviceUGI;
0368
0369 HttpKerberosServerAction(HttpServletRequest request,
0370 UserGroupInformation serviceUGI) {
0371 this.request = request;
0372 this.serviceUGI = serviceUGI;
0373 }
0374
0375 @Override
0376 public String run() throws HttpAuthenticationException {
0377
0378 GSSManager manager = GSSManager.getInstance();
0379 GSSContext gssContext = null;
0380 String serverPrincipal = getPrincipalWithoutRealm(
0381 serviceUGI.getUserName());
0382 try {
0383
0384 Oid kerberosMechOid = new Oid("1.2.840.113554.1.2.2");
0385
0386 Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2");
0387
0388 Oid krb5PrincipalOid = new Oid("1.2.840.113554.1.2.2.1");
0389
0390
0391 GSSName serverName = manager.createName(serverPrincipal, krb5PrincipalOid);
0392
0393
0394 GSSCredential serverCreds = manager.createCredential(serverName,
0395 GSSCredential.DEFAULT_LIFETIME,
0396 new Oid[]{kerberosMechOid, spnegoMechOid},
0397 GSSCredential.ACCEPT_ONLY);
0398
0399
0400 gssContext = manager.createContext(serverCreds);
0401
0402 String serviceTicketBase64 = getAuthHeader(request, authType);
0403 byte[] inToken = Base64.decodeBase64(serviceTicketBase64.getBytes());
0404 gssContext.acceptSecContext(inToken, 0, inToken.length);
0405
0406 if (!gssContext.isEstablished()) {
0407 throw new HttpAuthenticationException("Kerberos authentication failed: " +
0408 "unable to establish context with the service ticket " +
0409 "provided by the client.");
0410 }
0411 else {
0412 return getPrincipalWithoutRealmAndHost(gssContext.getSrcName().toString());
0413 }
0414 }
0415 catch (GSSException e) {
0416 throw new HttpAuthenticationException("Kerberos authentication failed: ", e);
0417 }
0418 finally {
0419 if (gssContext != null) {
0420 try {
0421 gssContext.dispose();
0422 } catch (GSSException e) {
0423
0424 }
0425 }
0426 }
0427 }
0428
0429 private String getPrincipalWithoutRealm(String fullPrincipal)
0430 throws HttpAuthenticationException {
0431 KerberosNameShim fullKerberosName;
0432 try {
0433 fullKerberosName = ShimLoader.getHadoopShims().getKerberosNameShim(fullPrincipal);
0434 } catch (IOException e) {
0435 throw new HttpAuthenticationException(e);
0436 }
0437 String serviceName = fullKerberosName.getServiceName();
0438 String hostName = fullKerberosName.getHostName();
0439 String principalWithoutRealm = serviceName;
0440 if (hostName != null) {
0441 principalWithoutRealm = serviceName + "/" + hostName;
0442 }
0443 return principalWithoutRealm;
0444 }
0445
0446 private String getPrincipalWithoutRealmAndHost(String fullPrincipal)
0447 throws HttpAuthenticationException {
0448 KerberosNameShim fullKerberosName;
0449 try {
0450 fullKerberosName = ShimLoader.getHadoopShims().getKerberosNameShim(fullPrincipal);
0451 return fullKerberosName.getShortName();
0452 } catch (IOException e) {
0453 throw new HttpAuthenticationException(e);
0454 }
0455 }
0456 }
0457
0458 private String getUsername(HttpServletRequest request, String authType)
0459 throws HttpAuthenticationException {
0460 String[] creds = getAuthHeaderTokens(request, authType);
0461
0462 if (creds[0] == null || creds[0].isEmpty()) {
0463 throw new HttpAuthenticationException("Authorization header received " +
0464 "from the client does not contain username.");
0465 }
0466 return creds[0];
0467 }
0468
0469 private String getPassword(HttpServletRequest request, String authType)
0470 throws HttpAuthenticationException {
0471 String[] creds = getAuthHeaderTokens(request, authType);
0472
0473 if (creds[1] == null || creds[1].isEmpty()) {
0474 throw new HttpAuthenticationException("Authorization header received " +
0475 "from the client does not contain username.");
0476 }
0477 return creds[1];
0478 }
0479
0480 private String[] getAuthHeaderTokens(HttpServletRequest request,
0481 String authType) throws HttpAuthenticationException {
0482 String authHeaderBase64 = getAuthHeader(request, authType);
0483 String authHeaderString = StringUtils.newStringUtf8(
0484 Base64.decodeBase64(authHeaderBase64.getBytes()));
0485 String[] creds = authHeaderString.split(":");
0486 return creds;
0487 }
0488
0489
0490
0491
0492
0493
0494
0495
0496 private String getAuthHeader(HttpServletRequest request, String authType)
0497 throws HttpAuthenticationException {
0498 String authHeader = request.getHeader(HttpAuthUtils.AUTHORIZATION);
0499
0500 if (authHeader == null || authHeader.isEmpty()) {
0501 throw new HttpAuthenticationException("Authorization header received " +
0502 "from the client is empty.");
0503 }
0504
0505 String authHeaderBase64String;
0506 int beginIndex;
0507 if (isKerberosAuthMode(authType)) {
0508 beginIndex = (HttpAuthUtils.NEGOTIATE + " ").length();
0509 }
0510 else {
0511 beginIndex = (HttpAuthUtils.BASIC + " ").length();
0512 }
0513 authHeaderBase64String = authHeader.substring(beginIndex);
0514
0515 if (authHeaderBase64String == null || authHeaderBase64String.isEmpty()) {
0516 throw new HttpAuthenticationException("Authorization header received " +
0517 "from the client does not contain any data.");
0518 }
0519 return authHeaderBase64String;
0520 }
0521
0522 private boolean isKerberosAuthMode(String authType) {
0523 return authType.equalsIgnoreCase(HiveAuthFactory.AuthTypes.KERBEROS.toString());
0524 }
0525
0526 private static String getDoAsQueryParam(String queryString) {
0527 if (LOG.isDebugEnabled()) {
0528 LOG.debug("URL query string:" + queryString);
0529 }
0530 if (queryString == null) {
0531 return null;
0532 }
0533 Map<String, String[]> params = javax.servlet.http.HttpUtils.parseQueryString( queryString );
0534 Set<String> keySet = params.keySet();
0535 for (String key: keySet) {
0536 if (key.equalsIgnoreCase("doAs")) {
0537 return params.get(key)[0];
0538 }
0539 }
0540 return null;
0541 }
0542
0543 }
0544
0545