Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public AuthenticationResult authenticateJDBCContext(Map<String, Object> context)
}

if (checkCredentials(user, password.toCharArray())) {
return new AuthenticationResult(user, name, null);
return new AuthenticationResult(user, authorizerName, name, null);
} else {
return null;
}
Expand Down Expand Up @@ -173,7 +173,7 @@ public void doFilter(
char[] password = splits[1].toCharArray();

if (checkCredentials(user, password)) {
AuthenticationResult authenticationResult = new AuthenticationResult(user, authorizerName, null);
AuthenticationResult authenticationResult = new AuthenticationResult(user, authorizerName, name, null);
servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public HttpClient createEscalatedClient(HttpClient baseClient)
@Override
public AuthenticationResult createEscalatedAuthenticationResult()
{
return new AuthenticationResult(internalClientUsername, authorizerName, null);
// if you found your self asking why the authenticatedBy field is set to null please read this:
// https://github.com/druid-io/druid/pull/5706#discussion_r185940889
return new AuthenticationResult(internalClientUsername, authorizerName, null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public void testAuth()

updater.setPermissions(AUTHORIZER_NAME, "druidRole", permissions);

AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null);
AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null, null);

Access access = authorizer.authorize(
authenticationResult,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import io.druid.guice.annotations.Self;
import io.druid.java.util.common.StringUtils;
Expand All @@ -41,6 +43,8 @@
import org.apache.hadoop.security.authentication.util.Signer;
import org.apache.hadoop.security.authentication.util.SignerException;
import org.apache.hadoop.security.authentication.util.SignerSecretProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
import sun.security.krb5.EncryptedData;
import sun.security.krb5.EncryptionKey;
import sun.security.krb5.internal.APReq;
Expand Down Expand Up @@ -72,25 +76,33 @@
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.HttpCookie;
import java.security.Principal;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@JsonTypeName("kerberos")
public class KerberosAuthenticator implements Authenticator
{
private static final Logger log = new Logger(KerberosAuthenticator.class);
private static final Pattern HADOOP_AUTH_COOKIE_REGEX = Pattern.compile(".*p=(\\S+)&t=.*");
public static final List<String> DEFAULT_EXCLUDED_PATHS = Collections.emptyList();
public static final String SIGNED_TOKEN_ATTRIBUTE = "signedToken";

private final DruidNode node;
private final String serverPrincipal;
Expand All @@ -99,6 +111,7 @@ public class KerberosAuthenticator implements Authenticator
private final List<String> excludedPaths;
private final String cookieSignatureSecret;
private final String authorizerName;
private final String name;
private LoginContext loginContext;

@JsonCreator
Expand All @@ -109,6 +122,7 @@ public KerberosAuthenticator(
@JsonProperty("excludedPaths") List<String> excludedPaths,
@JsonProperty("cookieSignatureSecret") String cookieSignatureSecret,
@JsonProperty("authorizerName") String authorizerName,
@JsonProperty("name") String name,
@JacksonInject @Self DruidNode node
)
{
Expand All @@ -119,6 +133,7 @@ public KerberosAuthenticator(
this.excludedPaths = excludedPaths == null ? DEFAULT_EXCLUDED_PATHS : excludedPaths;
this.cookieSignatureSecret = cookieSignatureSecret;
this.authorizerName = authorizerName;
this.name = Preconditions.checkNotNull(name);
}

@Override
Expand Down Expand Up @@ -253,7 +268,7 @@ public void doFilter(
if (clientPrincipal != null) {
request.setAttribute(
AuthConfig.DRUID_AUTHENTICATION_RESULT,
new AuthenticationResult(clientPrincipal, authorizerName, null)
new AuthenticationResult(clientPrincipal, authorizerName, name, null)
);
}
}
Expand Down Expand Up @@ -327,11 +342,19 @@ public Principal getUserPrincipal()
createAuthCookie(httpResponse, signedToken, getCookieDomain(),
getCookiePath(), token.getExpires(), isHttps
);
request.setAttribute(SIGNED_TOKEN_ATTRIBUTE, tokenToCookieString(
signedToken,
getCookieDomain(),
getCookiePath(),
token.getExpires(),
!token.isExpired() && token.getExpires() > 0,
isHttps
));
}
// Since this request is validated also set DRUID_AUTHENTICATION_RESULT
request.setAttribute(
AuthConfig.DRUID_AUTHENTICATION_RESULT,
new AuthenticationResult(token.getName(), authorizerName, null)
new AuthenticationResult(token.getName(), authorizerName, name, null)
);
doFilter(filterChain, httpRequest, httpResponse);
}
Expand All @@ -344,7 +367,7 @@ public Principal getUserPrincipal()
errCode = HttpServletResponse.SC_FORBIDDEN;
authenticationEx = ex;
if (log.isDebugEnabled()) {
log.debug("Authentication exception: " + ex.getMessage(), ex);
log.debug(ex, "Authentication exception: " + ex.getMessage());
} else {
log.warn("Authentication exception: " + ex.getMessage());
}
Expand Down Expand Up @@ -439,6 +462,22 @@ private boolean isExcluded(String path)
return false;
}

@Override
public void decorateProxyRequest(
HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest
)
{
Object cookieToken = clientRequest.getAttribute(SIGNED_TOKEN_ATTRIBUTE);
if (cookieToken != null && cookieToken instanceof String) {
log.debug("Found cookie token will attache it to proxyRequest as cookie");
String authResult = (String) cookieToken;
String existingCookies = proxyRequest.getCookies()
.stream()
.map(HttpCookie::toString)
.collect(Collectors.joining(";"));
proxyRequest.header(HttpHeader.COOKIE, Joiner.on(";").join(authResult, existingCookies));
}
}

/**
* Kerberos context configuration for the JDK GSS library. Copied from hadoop-auth's KerberosAuthenticationHandler.
Expand Down Expand Up @@ -604,4 +643,70 @@ private void initializeKerberosLogin() throws ServletException
throw new ServletException(ex);
}
}

/**
* Creates the Hadoop authentication HTTP cookie.
*
* @param resp the response object.
* @param token authentication token for the cookie.
* @param domain the cookie domain.
* @param path the cookie path.
* @param expires UNIX timestamp that indicates the expire date of the
* cookie. It has no effect if its value &lt; 0.
* @param isSecure is the cookie secure?
* @param isCookiePersistent whether the cookie is persistent or not.
*the following code copy/past from Hadoop 3.0.0 copied to avoid compilation issue due to new signature,
* org.apache.hadoop.security.authentication.server.AuthenticationFilter#createAuthCookie
* (
* javax.servlet.http.HttpServletResponse,
* java.lang.String,
* java.lang.String,
* java.lang.String,
* long, boolean, boolean)
*/
private static void tokenToAuthCookie(
HttpServletResponse resp, String token,
String domain, String path, long expires,
boolean isCookiePersistent,
boolean isSecure
)
{
resp.addHeader("Set-Cookie", tokenToCookieString(token, domain, path, expires, isCookiePersistent, isSecure));
}

private static String tokenToCookieString(
String token,
String domain, String path, long expires,
boolean isCookiePersistent,
boolean isSecure
)
{
StringBuilder sb = new StringBuilder(AuthenticatedURL.AUTH_COOKIE)
.append("=");
if (token != null && token.length() > 0) {
sb.append("\"").append(token).append("\"");
}

if (path != null) {
sb.append("; Path=").append(path);
}

if (domain != null) {
sb.append("; Domain=").append(domain);
}

if (expires >= 0 && isCookiePersistent) {
Date date = new Date(expires);
SimpleDateFormat df = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss zzz", Locale.ENGLISH);
df.setTimeZone(TimeZone.getTimeZone("GMT"));
sb.append("; Expires=").append(df.format(date));
}

if (isSecure) {
sb.append("; Secure");
}

sb.append("; HttpOnly");
return sb.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import io.druid.java.util.http.client.HttpClient;
import io.druid.java.util.common.logger.Logger;
import io.druid.java.util.http.client.HttpClient;
import io.druid.server.security.AuthenticationResult;
import io.druid.server.security.Escalator;

Expand Down Expand Up @@ -57,6 +57,9 @@ public HttpClient createEscalatedClient(HttpClient baseClient)
@Override
public AuthenticationResult createEscalatedAuthenticationResult()
{
return new AuthenticationResult(internalClientPrincipal, authorizerName, null);
// if you found your self asking why the authenticatedBy field is set to null please read this:
// https://github.com/druid-io/druid/pull/5706#discussion_r185940889
return new AuthenticationResult(internalClientPrincipal, authorizerName, null, null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ public Access authorize(AuthenticationResult authenticationResult, Resource reso

public void expectAuthorizationTokenCheck()
{
AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null);

AuthenticationResult authenticationResult = new AuthenticationResult("druid", "druid", null, null);
EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).anyTimes();
EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
.andReturn(authenticationResult)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void setUp() throws Exception
req = EasyMock.createMock(HttpServletRequest.class);
EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).anyTimes();
EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(
new AuthenticationResult("druid", "druid", null)
new AuthenticationResult("druid", "druid", null, null)
).anyTimes();
req.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
EasyMock.expectLastCall().anyTimes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public List<String> getDataSources()
EasyMock.expect(supervisorManager.createOrUpdateAndStartSupervisor(spec)).andReturn(true);
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(
new AuthenticationResult("druid", "druid", null)
new AuthenticationResult("druid", "druid", null, null)
).atLeastOnce();
request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
EasyMock.expectLastCall().anyTimes();
Expand Down Expand Up @@ -165,7 +165,7 @@ public List<String> getDataSources()
EasyMock.expect(supervisorManager.getSupervisorSpec("id2")).andReturn(Optional.of(spec2));
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(
new AuthenticationResult("druid", "druid", null)
new AuthenticationResult("druid", "druid", null, null)
).atLeastOnce();
request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
EasyMock.expectLastCall().anyTimes();
Expand Down Expand Up @@ -355,7 +355,7 @@ public void testSpecGetAllHistory() throws Exception
EasyMock.expect(supervisorManager.getSupervisorSpec("id2")).andReturn(Optional.of(spec2)).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(
new AuthenticationResult("druid", "druid", null)
new AuthenticationResult("druid", "druid", null, null)
).atLeastOnce();
request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
EasyMock.expectLastCall().anyTimes();
Expand Down Expand Up @@ -467,7 +467,7 @@ public void testSpecGetAllHistoryWithAuthFailureFiltering() throws Exception
EasyMock.expect(supervisorManager.getSupervisorSpec("id2")).andReturn(Optional.of(spec2)).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(
new AuthenticationResult("wronguser", "druid", null)
new AuthenticationResult("wronguser", "druid", null, null)
).atLeastOnce();
request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
EasyMock.expectLastCall().anyTimes();
Expand Down Expand Up @@ -557,7 +557,7 @@ public void testSpecGetHistory() throws Exception
EasyMock.expect(supervisorManager.getSupervisorHistory()).andReturn(history).times(3);
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(
new AuthenticationResult("druid", "druid", null)
new AuthenticationResult("druid", "druid", null, null)
).atLeastOnce();
request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
EasyMock.expectLastCall().anyTimes();
Expand Down Expand Up @@ -654,7 +654,7 @@ public void testSpecGetHistoryWithAuthFailure() throws Exception
EasyMock.expect(supervisorManager.getSupervisorHistory()).andReturn(history).times(4);
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).atLeastOnce();
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(
new AuthenticationResult("notdruid", "druid", null)
new AuthenticationResult("notdruid", "druid", null, null)
).atLeastOnce();
request.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
EasyMock.expectLastCall().anyTimes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
import io.druid.server.router.QueryHostFinder;
import io.druid.server.router.Router;
import io.druid.server.security.AuthConfig;
import io.druid.server.security.AuthenticationResult;
import io.druid.server.security.Authenticator;
import io.druid.server.security.AuthenticatorMapper;
import org.apache.http.client.utils.URIBuilder;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.Request;
Expand Down Expand Up @@ -112,6 +115,7 @@ private static void handleException(HttpServletResponse response, ObjectMapper o
private final ServiceEmitter emitter;
private final RequestLogger requestLogger;
private final GenericQueryMetricsFactory queryMetricsFactory;
private final AuthenticatorMapper authenticatorMapper;

private HttpClient broadcastClient;

Expand All @@ -125,7 +129,8 @@ public AsyncQueryForwardingServlet(
@Router DruidHttpClientConfig httpClientConfig,
ServiceEmitter emitter,
RequestLogger requestLogger,
GenericQueryMetricsFactory queryMetricsFactory
GenericQueryMetricsFactory queryMetricsFactory,
AuthenticatorMapper authenticatorMapper
)
{
this.warehouse = warehouse;
Expand All @@ -137,6 +142,7 @@ public AsyncQueryForwardingServlet(
this.emitter = emitter;
this.requestLogger = requestLogger;
this.queryMetricsFactory = queryMetricsFactory;
this.authenticatorMapper = authenticatorMapper;
}

@Override
Expand Down Expand Up @@ -313,6 +319,22 @@ protected void sendProxyRequest(
// will log that on the remote node.
clientRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);

// Check if there is an authentication result and use it to decorate the proxy request if needed.
AuthenticationResult authenticationResult = (AuthenticationResult) clientRequest.getAttribute(
AuthConfig.DRUID_AUTHENTICATION_RESULT);
if (authenticationResult != null && authenticationResult.getAuthenticatedBy() != null) {
Authenticator authenticator = authenticatorMapper.getAuthenticatorMap()
.get(authenticationResult.getAuthenticatedBy());
if (authenticator != null) {
authenticator.decorateProxyRequest(
clientRequest,
proxyResponse,
proxyRequest
);
} else {
log.error("Can not find Authenticator with Name [%s]", authenticationResult.getAuthenticatedBy());
}
}
super.sendProxyRequest(
clientRequest,
proxyResponse,
Expand Down
Loading