You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by is...@apache.org on 2019/07/15 09:43:28 UTC

[lucene-solr] branch branch_8x updated (6504bab -> ee4495f)

This is an automated email from the ASF dual-hosted git repository.

ishan pushed a change to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git.


    from 6504bab  Update solr/CHANGES.txt for SOLR-13588 and SOLR-13602.
     new a7a3fab  SOLR-13472: Forwarded requests should skip authorization on receiving nodes
     new ee4495f  SOLR-13619: Kerberos plugin to forward original user principal

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 solr/CHANGES.txt                                   |  5 ++
 .../org/apache/solr/request/SolrRequestInfo.java   | 22 +++++-
 .../org/apache/solr/security/KerberosFilter.java   | 58 +++++++++++++++-
 .../org/apache/solr/security/KerberosPlugin.java   | 23 +++++-
 .../security/RuleBasedAuthorizationPlugin.java     | 18 +++++
 .../java/org/apache/solr/servlet/HttpSolrCall.java | 81 +++++++++++++---------
 6 files changed, 172 insertions(+), 35 deletions(-)


[lucene-solr] 01/02: SOLR-13472: Forwarded requests should skip authorization on receiving nodes

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit a7a3fab8c881a4d5ad4ab56cd544052dcb22d073
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Mon Jul 15 14:43:41 2019 +0530

    SOLR-13472: Forwarded requests should skip authorization on receiving nodes
---
 solr/CHANGES.txt                                   |  2 +
 .../java/org/apache/solr/servlet/HttpSolrCall.java | 73 +++++++++++++---------
 2 files changed, 45 insertions(+), 30 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 7a86334..f960edf 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -196,6 +196,8 @@ Bug Fixes
 
 * SOLR-13532: Fix http timeout and error logging bugs in RecoveryStrategy. (Suril Shah, hossman)
 
+* SOLR-13472: Forwarded requests should skip authorization on receiving nodes (adfel, Ishan Chattopadhyaya)
+
 Other Changes
 ----------------------
 
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index 0ca46d0..2ed00f4 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -464,6 +464,35 @@ public class HttpSolrCall {
     }
   }
 
+  Action authorize() throws IOException {
+    AuthorizationContext context = getAuthCtx();
+    log.debug("AuthorizationContext : {}", context);
+    AuthorizationResponse authResponse = cores.getAuthorizationPlugin().authorize(context);
+    if (authResponse.statusCode == AuthorizationResponse.PROMPT.statusCode) {
+      Map<String, String> headers = (Map) getReq().getAttribute(AuthenticationPlugin.class.getName());
+      if (headers != null) {
+        for (Map.Entry<String, String> e : headers.entrySet()) response.setHeader(e.getKey(), e.getValue());
+      }
+      log.debug("USER_REQUIRED "+req.getHeader("Authorization")+" "+ req.getUserPrincipal());
+      if (shouldAudit(EventType.REJECTED)) {
+        cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.REJECTED, req, context));
+      }
+    }
+    if (!(authResponse.statusCode == HttpStatus.SC_ACCEPTED) && !(authResponse.statusCode == HttpStatus.SC_OK)) {
+      log.info("USER_REQUIRED auth header {} context : {} ", req.getHeader("Authorization"), context);
+      sendError(authResponse.statusCode,
+          "Unauthorized request, Response code: " + authResponse.statusCode);
+      if (shouldAudit(EventType.UNAUTHORIZED)) {
+        cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.UNAUTHORIZED, req, context));
+      }
+      return RETURN;
+    }
+    if (shouldAudit(EventType.AUTHORIZED)) {
+      cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.AUTHORIZED, req, context));
+    }
+    return null;
+  }
+  
   /**
    * This method processes the request.
    */
@@ -491,36 +520,20 @@ public class HttpSolrCall {
 
     try {
       init();
-      /* Authorize the request if
-       1. Authorization is enabled, and
-       2. The requested resource is not a known static file
-        */
-      if (cores.getAuthorizationPlugin() != null && shouldAuthorize()) {
-        AuthorizationContext context = getAuthCtx();
-        log.debug("AuthorizationContext : {}", context);
-        AuthorizationResponse authResponse = cores.getAuthorizationPlugin().authorize(context);
-        if (authResponse.statusCode == AuthorizationResponse.PROMPT.statusCode) {
-          Map<String, String> headers = (Map) getReq().getAttribute(AuthenticationPlugin.class.getName());
-          if (headers != null) {
-            for (Map.Entry<String, String> e : headers.entrySet()) response.setHeader(e.getKey(), e.getValue());
-          }
-          log.debug("USER_REQUIRED "+req.getHeader("Authorization")+" "+ req.getUserPrincipal());
-          if (shouldAudit(EventType.REJECTED)) {
-            cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.REJECTED, req, context));
-          }
-        }
-        if (!(authResponse.statusCode == HttpStatus.SC_ACCEPTED) && !(authResponse.statusCode == HttpStatus.SC_OK)) {
-          log.info("USER_REQUIRED auth header {} context : {} ", req.getHeader("Authorization"), context);
-          sendError(authResponse.statusCode,
-              "Unauthorized request, Response code: " + authResponse.statusCode);
-          if (shouldAudit(EventType.UNAUTHORIZED)) {
-            cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.UNAUTHORIZED, req, context));
-          }
-          return RETURN;
-        }
-        if (shouldAudit(EventType.AUTHORIZED)) {
-          cores.getAuditLoggerPlugin().doAudit(new AuditEvent(EventType.AUTHORIZED, req, context));
-        }
+
+      // Perform authorization here, if:
+      //    (a) Authorization is enabled, and
+      //    (b) The requested resource is not a known static file
+      //    (c) And this request should be handled by this node (see NOTE below)
+      // NOTE: If the query is to be handled by another node, then let that node do the authorization.
+      // In case of authentication using BasicAuthPlugin, for example, the internode request
+      // is secured using PKI authentication and the internode request here will contain the
+      // original user principal as a payload/header, using which the receiving node should be
+      // able to perform the authorization.
+      if (cores.getAuthorizationPlugin() != null && shouldAuthorize()
+          && !(action == REMOTEQUERY || action == FORWARD)) {
+        Action authorizationAction = authorize();
+        if (authorizationAction != null) return authorizationAction;
       }
 
       HttpServletResponse resp = response;


[lucene-solr] 02/02: SOLR-13619: Kerberos plugin to forward original user principal

Posted by is...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ishan pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit ee4495f33bc433cba6582aaf8160cfc16009460f
Author: Ishan Chattopadhyaya <is...@apache.org>
AuthorDate: Mon Jul 15 15:10:07 2019 +0530

    SOLR-13619: Kerberos plugin to forward original user principal
---
 solr/CHANGES.txt                                   |  3 ++
 .../org/apache/solr/request/SolrRequestInfo.java   | 22 +++++++-
 .../org/apache/solr/security/KerberosFilter.java   | 58 +++++++++++++++++++++-
 .../org/apache/solr/security/KerberosPlugin.java   | 23 ++++++++-
 .../security/RuleBasedAuthorizationPlugin.java     | 18 +++++++
 .../java/org/apache/solr/servlet/HttpSolrCall.java |  8 ++-
 6 files changed, 127 insertions(+), 5 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index f960edf..f1bd2e2 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -198,6 +198,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
 ----------------------
 
diff --git a/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java b/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
index 5995f2b..fb658da 100644
--- a/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
+++ b/solr/core/src/java/org/apache/solr/request/SolrRequestInfo.java
@@ -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
diff --git a/solr/core/src/java/org/apache/solr/security/KerberosFilter.java b/solr/core/src/java/org/apache/solr/security/KerberosFilter.java
index 8f97695..1939642 100644
--- a/solr/core/src/java/org/apache/solr/security/KerberosFilter.java
+++ b/solr/core/src/java/org/apache/solr/security/KerberosFilter.java
@@ -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 {
diff --git a/solr/core/src/java/org/apache/solr/security/KerberosPlugin.java b/solr/core/src/java/org/apache/solr/security/KerberosPlugin.java
index 8bc5625..47b5ba2 100644
--- a/solr/core/src/java/org/apache/solr/security/KerberosPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/KerberosPlugin.java
@@ -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);
@@ -236,6 +243,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);
   }
diff --git a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java
index 8b4a65c..e574179 100644
--- a/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPlugin.java
@@ -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());
diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
index 2ed00f4..546a195 100644
--- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
+++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
@@ -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;