| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.druid.security.kerberos; |
| |
| import com.google.common.util.concurrent.FutureCallback; |
| import com.google.common.util.concurrent.Futures; |
| import com.google.common.util.concurrent.ListenableFuture; |
| import com.google.common.util.concurrent.SettableFuture; |
| import org.apache.druid.java.util.common.concurrent.Execs; |
| import org.apache.druid.java.util.common.logger.Logger; |
| import org.apache.druid.java.util.http.client.AbstractHttpClient; |
| import org.apache.druid.java.util.http.client.HttpClient; |
| import org.apache.druid.java.util.http.client.Request; |
| import org.apache.druid.java.util.http.client.response.HttpResponseHandler; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.jboss.netty.handler.codec.http.HttpHeaders; |
| import org.joda.time.Duration; |
| |
| import java.net.CookieManager; |
| import java.net.URI; |
| import java.security.PrivilegedExceptionAction; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ExecutorService; |
| |
| public class KerberosHttpClient extends AbstractHttpClient |
| { |
| private static final Logger log = new Logger(KerberosHttpClient.class); |
| |
| private final HttpClient delegate; |
| private final CookieManager cookieManager; |
| private final ExecutorService exec = Execs.singleThreaded("test-%s"); |
| private final String internalClientPrincipal; |
| private final String internalClientKeytab; |
| |
| public KerberosHttpClient(HttpClient delegate, String internalClientPrincipal, String internalClientKeytab) |
| { |
| this.delegate = delegate; |
| this.cookieManager = new CookieManager(); |
| this.internalClientPrincipal = internalClientPrincipal; |
| this.internalClientKeytab = internalClientKeytab; |
| } |
| |
| @Override |
| public <Intermediate, Final> ListenableFuture<Final> go( |
| Request request, |
| HttpResponseHandler<Intermediate, Final> httpResponseHandler, |
| Duration duration |
| ) |
| { |
| final SettableFuture<Final> retVal = SettableFuture.create(); |
| inner_go(request, httpResponseHandler, duration, retVal); |
| return retVal; |
| } |
| |
| |
| private <Intermediate, Final> void inner_go( |
| final Request request, |
| final HttpResponseHandler<Intermediate, Final> httpResponseHandler, |
| final Duration duration, |
| final SettableFuture<Final> future |
| ) |
| { |
| try { |
| final String host = request.getUrl().getHost(); |
| final URI uri = request.getUrl().toURI(); |
| |
| |
| Map<String, List<String>> cookieMap = cookieManager.get(uri, Collections.emptyMap()); |
| for (Map.Entry<String, List<String>> entry : cookieMap.entrySet()) { |
| request.addHeaderValues(entry.getKey(), entry.getValue()); |
| } |
| final boolean should_retry_on_unauthorized_response; |
| |
| if (DruidKerberosUtil.needToSendCredentials(cookieManager.getCookieStore(), uri)) { |
| // No Cookies for requested URI, authenticate user and add authentication header |
| log.debug( |
| "No Auth Cookie found for URI[%s]. Existing Cookies[%s] Authenticating... ", |
| uri, |
| cookieManager.getCookieStore().getCookies() |
| ); |
| DruidKerberosUtil.authenticateIfRequired(internalClientPrincipal, internalClientKeytab); |
| UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); |
| String challenge = currentUser.doAs(new PrivilegedExceptionAction<String>() |
| { |
| @Override |
| public String run() throws Exception |
| { |
| return DruidKerberosUtil.kerberosChallenge(host); |
| } |
| }); |
| request.setHeader(HttpHeaders.Names.AUTHORIZATION, "Negotiate " + challenge); |
| should_retry_on_unauthorized_response = false; |
| } else { |
| should_retry_on_unauthorized_response = true; |
| log.debug("Found Auth Cookie found for URI[%s].", uri); |
| } |
| |
| ListenableFuture<RetryResponseHolder<Final>> internalFuture = delegate.go( |
| request, |
| new RetryIfUnauthorizedResponseHandler<Intermediate, Final>( |
| new ResponseCookieHandler(request.getUrl().toURI(), cookieManager, httpResponseHandler) |
| ), |
| duration |
| ); |
| |
| Futures.addCallback(internalFuture, new FutureCallback<RetryResponseHolder<Final>>() |
| { |
| @Override |
| public void onSuccess(RetryResponseHolder<Final> result) |
| { |
| if (should_retry_on_unauthorized_response && result.shouldRetry()) { |
| log.info("Preparing for Retry"); |
| // remove Auth cookie |
| DruidKerberosUtil.removeAuthCookie(cookieManager.getCookieStore(), uri); |
| // clear existing cookie |
| request.setHeader("Cookie", ""); |
| inner_go(request.copy(), httpResponseHandler, duration, future); |
| } else { |
| log.debug("Not retrying and returning future response"); |
| future.set(result.getObj()); |
| } |
| } |
| |
| @Override |
| public void onFailure(Throwable t) |
| { |
| future.setException(t); |
| } |
| }, exec); |
| } |
| catch (Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| } |