Skip to content
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,6 +76,7 @@
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;
Expand All @@ -88,13 +93,16 @@
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 @@ -103,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 @@ -113,6 +122,7 @@ public KerberosAuthenticator(
@JsonProperty("excludedPaths") List<String> excludedPaths,
@JsonProperty("cookieSignatureSecret") String cookieSignatureSecret,
@JsonProperty("authorizerName") String authorizerName,
@JsonProperty("name") String name,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new property - please add to docs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is injected by the Jackson from the list of authenticators. user does not set this

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good.

@JacksonInject @Self DruidNode node
)
{
Expand All @@ -123,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 @@ -257,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 @@ -337,11 +348,19 @@ public Principal getUserPrincipal()
!token.isExpired() && token.getExpires() > 0,
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 @@ -354,7 +373,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 @@ -455,6 +474,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 @@ -648,6 +683,16 @@ private static void tokenToAuthCookie(
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("=");
Expand Down Expand Up @@ -675,6 +720,6 @@ private static void tokenToAuthCookie(
}

sb.append("; HttpOnly");
resp.addHeader("Set-Cookie", sb.toString());
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 @@ -125,7 +125,7 @@ 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_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED)).andReturn(null).anyTimes();
EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
Expand Down Expand Up @@ -245,7 +245,7 @@ public TaskStatus apply(String input)
Assert.assertTrue(taskRunner.getRunningTasks().size() == 3);
List<TaskStatusPlus> responseObjects = (List) overlordResource
.getCompleteTasks(null, req).getEntity();

Assert.assertEquals(2, responseObjects.size());
Assert.assertEquals(tasksIds.get(1), responseObjects.get(0).getId());
Assert.assertEquals(tasksIds.get(2), responseObjects.get(1).getId());
Expand Down Expand Up @@ -367,7 +367,7 @@ public void testGetTaskStatus() throws Exception
);
Assert.assertEquals(new TaskStatusResponse("othertask", null), taskStatusResponse2);
}

@Test
public void testGetRunningTasksByDataSource()
{
Expand Down Expand Up @@ -412,7 +412,7 @@ public void testGetRunningTasksByDataSourceNeg()
Assert.assertTrue(taskStorageQueryAdapter.getTask("id_2").isPresent());
List<TaskRunnerWorkItem> responseObjects = (List) overlordResource.getRunningTasksByDataSource("ds_NA", req)
.getEntity();

Assert.assertEquals(0, responseObjects.size());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public void setUp() throws Exception
EasyMock.expect(req.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
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(request.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).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 @@ -166,7 +166,7 @@ public List<String> getDataSources()
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).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 @@ -350,7 +350,7 @@ public void testSpecGetAllHistory()
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).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 @@ -463,7 +463,7 @@ public void testSpecGetAllHistoryWithAuthFailureFiltering()
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).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 @@ -554,7 +554,7 @@ public void testSpecGetHistory()
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).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 @@ -652,7 +652,7 @@ public void testSpecGetHistoryWithAuthFailure() throws Exception
EasyMock.expect(request.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).atLeastOnce();
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 @@ -113,6 +116,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 @@ -126,7 +130,8 @@ public AsyncQueryForwardingServlet(
@Router DruidHttpClientConfig httpClientConfig,
ServiceEmitter emitter,
RequestLogger requestLogger,
GenericQueryMetricsFactory queryMetricsFactory
GenericQueryMetricsFactory queryMetricsFactory,
AuthenticatorMapper authenticatorMapper
)
{
this.warehouse = warehouse;
Expand All @@ -138,6 +143,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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not wrong user needs to make sure that he defines a property like this for every authenticator -
druid.auth.authenticator..name=
If it possible to infer the name without making the user add this property ?
If not lets make sure we document this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is coming from the list of authenticators and injected at the Authenticator module.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good.

authenticator.decorateProxyRequest(
clientRequest,
proxyResponse,
proxyRequest
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you log an error here if the authenticator is not found?

} else {
log.error("Can not find Authenticator with Name [%s]", authenticationResult.getAuthenticatedBy());
}
}
super.sendProxyRequest(
clientRequest,
proxyResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class AllowAllAuthenticator implements Authenticator
public static final AuthenticationResult ALLOW_ALL_RESULT = new AuthenticationResult(
AuthConfig.ALLOW_ALL_NAME,
AuthConfig.ALLOW_ALL_NAME,
null
AuthConfig.ALLOW_ALL_NAME, null
);

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void doFilter(
if (allowUnauthenticatedHttpOptions) {
httpReq.setAttribute(
AuthConfig.DRUID_AUTHENTICATION_RESULT,
new AuthenticationResult(AuthConfig.ALLOW_ALL_NAME, AuthConfig.ALLOW_ALL_NAME, null)
new AuthenticationResult(AuthConfig.ALLOW_ALL_NAME, AuthConfig.ALLOW_ALL_NAME, null, null)
);
} else {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
Expand Down
Loading