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