mirror of https://github.com/apache/lucene.git
SOLR-13619: Kerberos plugin to forward original user principal
This commit is contained in:
parent
7e0af71c1e
commit
26ede632e6
|
@ -223,6 +223,9 @@ Bug Fixes
|
|||
|
||||
* SOLR-13472: Forwarded requests should skip authorization on receiving nodes (adfel, Ishan Chattopadhyaya)
|
||||
|
||||
* SOLR-13619: Kerberos plugin to pass original user principal to avoid 403 on nodes not hosting a collection
|
||||
(adfel, Ishan Chattopadhyaya, noble)
|
||||
|
||||
Other Changes
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.apache.solr.common.params.CommonParams;
|
|||
import org.apache.solr.common.util.ExecutorUtil;
|
||||
import org.apache.solr.handler.component.ResponseBuilder;
|
||||
import org.apache.solr.response.SolrQueryResponse;
|
||||
import org.apache.solr.servlet.SolrDispatchFilter;
|
||||
import org.apache.solr.util.TimeZoneUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -46,6 +47,7 @@ public class SolrRequestInfo {
|
|||
protected TimeZone tz;
|
||||
protected ResponseBuilder rb;
|
||||
protected List<Closeable> closeHooks;
|
||||
protected SolrDispatchFilter.Action action;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
|
@ -86,11 +88,21 @@ public class SolrRequestInfo {
|
|||
this.req = req;
|
||||
this.rsp = rsp;
|
||||
}
|
||||
public SolrRequestInfo(HttpServletRequest httpReq, SolrQueryResponse rsp) {
|
||||
public SolrRequestInfo(SolrQueryRequest req, SolrQueryResponse rsp, SolrDispatchFilter.Action action) {
|
||||
this(req, rsp);
|
||||
this.setAction(action);
|
||||
}
|
||||
|
||||
public SolrRequestInfo(HttpServletRequest httpReq, SolrQueryResponse rsp) {
|
||||
this.httpRequest = httpReq;
|
||||
this.rsp = rsp;
|
||||
}
|
||||
|
||||
public SolrRequestInfo(HttpServletRequest httpReq, SolrQueryResponse rsp, SolrDispatchFilter.Action action) {
|
||||
this(httpReq, rsp);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public Principal getUserPrincipal() {
|
||||
if (req != null) return req.getUserPrincipal();
|
||||
if (httpRequest != null) return httpRequest.getUserPrincipal();
|
||||
|
@ -149,6 +161,14 @@ public class SolrRequestInfo {
|
|||
}
|
||||
}
|
||||
|
||||
public SolrDispatchFilter.Action getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(SolrDispatchFilter.Action action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public static ExecutorUtil.InheritableThreadLocalProvider getInheritableThreadLocalProvider() {
|
||||
return new ExecutorUtil.InheritableThreadLocalProvider() {
|
||||
@Override
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.apache.solr.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.security.Principal;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
|
@ -25,13 +27,24 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
|
||||
import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
|
||||
import org.apache.solr.core.CoreContainer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class KerberosFilter extends AuthenticationFilter {
|
||||
|
||||
private final Locale defaultLocale = Locale.getDefault();
|
||||
|
||||
private final CoreContainer coreContainer;
|
||||
|
||||
public KerberosFilter(CoreContainer coreContainer) {
|
||||
this.coreContainer = coreContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig conf) throws ServletException {
|
||||
|
@ -51,13 +64,56 @@ public class KerberosFilter extends AuthenticationFilter {
|
|||
newAuthHandler.setAuthHandler(authHandler);
|
||||
}
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
@Override
|
||||
protected void doFilter(FilterChain filterChain, HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException, ServletException {
|
||||
Locale.setDefault(defaultLocale);
|
||||
|
||||
request = substituteOriginalUserRequest(request);
|
||||
|
||||
super.doFilter(filterChain, request, response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If principal is an admin user, i.e. has ALL permissions (e.g. request coming from Solr
|
||||
* node), and "originalUserPrincipal" is specified, then set originalUserPrincipal
|
||||
* as the principal. This is the case in forwarded/remote requests
|
||||
* through KerberosPlugin. This is needed because the original node that received
|
||||
* this request did not perform any authorization, and hence we are the first ones
|
||||
* to authorize the request (and we need the original user principal to do so).
|
||||
* @return Substituted request, if applicable, or the original request
|
||||
*/
|
||||
private HttpServletRequest substituteOriginalUserRequest(HttpServletRequest request) {
|
||||
final HttpServletRequest originalRequest = request;
|
||||
AuthorizationPlugin authzPlugin = coreContainer.getAuthorizationPlugin();
|
||||
if (authzPlugin instanceof RuleBasedAuthorizationPlugin) {
|
||||
RuleBasedAuthorizationPlugin ruleBased = (RuleBasedAuthorizationPlugin) authzPlugin;
|
||||
if (request.getHeader(KerberosPlugin.ORIGINAL_USER_PRINCIPAL_HEADER) != null &&
|
||||
ruleBased.doesUserHavePermission(request.getUserPrincipal().getName(), PermissionNameProvider.Name.ALL)) {
|
||||
request = new HttpServletRequestWrapper(request) {
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
String originalUserPrincipal = originalRequest.getHeader(KerberosPlugin.ORIGINAL_USER_PRINCIPAL_HEADER);
|
||||
log.info("Substituting user principal from {} to {}.", originalRequest.getUserPrincipal(), originalUserPrincipal);
|
||||
return new Principal() {
|
||||
@Override
|
||||
public String getName() {
|
||||
return originalUserPrincipal;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return originalUserPrincipal;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain filterChain) throws IOException, ServletException {
|
||||
|
|
|
@ -33,6 +33,8 @@ import com.fasterxml.jackson.core.JsonGenerator;
|
|||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.collections.iterators.IteratorEnumeration;
|
||||
import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationHandler;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.solr.client.solrj.impl.Http2SolrClient;
|
||||
import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
|
||||
import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
|
||||
|
@ -41,6 +43,8 @@ import org.apache.solr.common.SolrException;
|
|||
import org.apache.solr.common.SolrException.ErrorCode;
|
||||
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
|
||||
import org.apache.solr.core.CoreContainer;
|
||||
import org.apache.solr.request.SolrRequestInfo;
|
||||
import org.apache.solr.servlet.SolrDispatchFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -71,6 +75,8 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
|
|||
public static final String IMPERSONATOR_DO_AS_HTTP_PARAM = "doAs";
|
||||
public static final String IMPERSONATOR_USER_NAME = "solr.impersonator.user.name";
|
||||
|
||||
public static final String ORIGINAL_USER_PRINCIPAL_HEADER = "originalUserPrincipal";
|
||||
|
||||
static final String DELEGATION_TOKEN_ZK_CLIENT =
|
||||
"solr.kerberos.delegation.token.zk.client";
|
||||
|
||||
|
@ -177,7 +183,7 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
|
|||
// pass an attribute-enabled context in order to pass the zkClient
|
||||
// and because the filter may pass a curator instance.
|
||||
} else {
|
||||
kerberosFilter = new KerberosFilter();
|
||||
kerberosFilter = new KerberosFilter(coreContainer);
|
||||
}
|
||||
log.info("Params: "+params);
|
||||
|
||||
|
@ -226,6 +232,7 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
|
|||
FilterChain chain) throws Exception {
|
||||
log.debug("Request to authenticate using kerberos: "+req);
|
||||
kerberosFilter.doFilter(req, rsp, chain);
|
||||
|
||||
String requestContinuesAttr = (String)req.getAttribute(RequestContinuesRecorderAuthenticationHandler.REQUEST_CONTINUES_ATTR);
|
||||
if (requestContinuesAttr == null) {
|
||||
log.warn("Could not find " + RequestContinuesRecorderAuthenticationHandler.REQUEST_CONTINUES_ATTR);
|
||||
|
@ -235,6 +242,20 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean interceptInternodeRequest(HttpRequest httpRequest, HttpContext httpContext) {
|
||||
SolrRequestInfo info = SolrRequestInfo.getRequestInfo();
|
||||
if (info != null && (info.getAction() == SolrDispatchFilter.Action.FORWARD ||
|
||||
info.getAction() == SolrDispatchFilter.Action.REMOTEQUERY)) {
|
||||
if (info.getUserPrincipal() != null) {
|
||||
log.info("Setting original user principal: {}", info.getUserPrincipal().getName());
|
||||
httpRequest.setHeader(ORIGINAL_USER_PRINCIPAL_HEADER, info.getUserPrincipal().getName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SolrHttpClientBuilder getHttpClientBuilder(SolrHttpClientBuilder builder) {
|
||||
return kerberosBuilder.getBuilder(builder);
|
||||
|
|
|
@ -189,6 +189,24 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
|
|||
return MatchStatus.FORBIDDEN;
|
||||
}
|
||||
|
||||
public boolean doesUserHavePermission(String user, PermissionNameProvider.Name permission) {
|
||||
Set<String> roles = usersVsRoles.get(user);
|
||||
if (roles != null) {
|
||||
for (String role: roles) {
|
||||
if (mapping.get(null) == null) continue;
|
||||
List<Permission> permissions = mapping.get(null).get(null);
|
||||
if (permissions != null) {
|
||||
for (Permission p: permissions) {
|
||||
if (permission.equals(p.wellknownName) && p.role.contains(role)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Map<String, Object> initInfo) {
|
||||
mapping.put(null, new WildCardSupportMap());
|
||||
|
|
|
@ -137,6 +137,8 @@ import static org.apache.solr.servlet.SolrDispatchFilter.Action.RETURN;
|
|||
public class HttpSolrCall {
|
||||
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
|
||||
|
||||
public static final String ORIGINAL_USER_PRINCIPAL_HEADER = "originalUserPrincipal";
|
||||
|
||||
public static final Random random;
|
||||
static {
|
||||
// We try to make things reproducible in the context of our tests by initializing the random instance
|
||||
|
@ -414,10 +416,12 @@ public class HttpSolrCall {
|
|||
if (path.equals(req.getServletPath())) {
|
||||
// avoid endless loop - pass through to Restlet via webapp
|
||||
action = PASSTHROUGH;
|
||||
SolrRequestInfo.getRequestInfo().setAction(action);
|
||||
return;
|
||||
} else {
|
||||
// forward rewritten URI (without path prefix and core/collection name) to Restlet
|
||||
action = FORWARD;
|
||||
SolrRequestInfo.getRequestInfo().setAction(action);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -542,7 +546,7 @@ public class HttpSolrCall {
|
|||
handleAdminRequest();
|
||||
return RETURN;
|
||||
case REMOTEQUERY:
|
||||
SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req, new SolrQueryResponse()));
|
||||
SolrRequestInfo.setRequestInfo(new SolrRequestInfo(req, new SolrQueryResponse(), action));
|
||||
remoteQuery(coreUrl + path, resp);
|
||||
return RETURN;
|
||||
case PROCESS:
|
||||
|
@ -558,7 +562,7 @@ public class HttpSolrCall {
|
|||
* QueryResponseWriter is selected and we get the correct
|
||||
* Content-Type)
|
||||
*/
|
||||
SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, solrRsp));
|
||||
SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, solrRsp, action));
|
||||
execute(solrRsp);
|
||||
if (shouldAudit()) {
|
||||
EventType eventType = solrRsp.getException() == null ? EventType.COMPLETED : EventType.ERROR;
|
||||
|
|
Loading…
Reference in New Issue