You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by gc...@apache.org on 2016/08/02 23:42:36 UTC

lucene-solr:master: SOLR-9324: Support Secure Impersonation / Proxy User for solr authentication

Repository: lucene-solr
Updated Branches:
  refs/heads/master b3505298a -> e50858c31


SOLR-9324: Support Secure Impersonation / Proxy User for solr authentication


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/e50858c3
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/e50858c3
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/e50858c3

Branch: refs/heads/master
Commit: e50858c314a138e2c2ced50bee9a5c2754929f8b
Parents: b350529
Author: Gregory Chanan <gc...@cloudera.com>
Authored: Mon Jul 25 14:15:48 2016 -0400
Committer: Gregory Chanan <gc...@cloudera.com>
Committed: Tue Aug 2 19:42:10 2016 -0400

----------------------------------------------------------------------
 solr/CHANGES.txt                                |   3 +
 .../security/DelegationTokenKerberosFilter.java |  46 ++-
 .../apache/solr/security/KerberosPlugin.java    | 205 ++++++-----
 .../TestSolrCloudWithDelegationTokens.java      |   9 +-
 .../TestSolrCloudWithSecureImpersonation.java   | 340 +++++++++++++++++++
 ...ramDelegationTokenAuthenticationHandler.java | 109 ------
 .../HttpParamDelegationTokenPlugin.java         | 272 +++++++++++++++
 7 files changed, 776 insertions(+), 208 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e50858c3/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index c2a69fd..538edce 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -111,6 +111,9 @@ New Features
 * SOLR-9279: New boolean comparison function queries comparing numeric arguments: gt, gte, lt, lte, eq
   (Doug Turnbull, David Smiley)
 
+* SOLR-9346: Support Secure Impersonation / Proxy User for solr authentication
+  (Gregory Chanan)
+
 Bug Fixes
 ----------------------
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e50858c3/solr/core/src/java/org/apache/solr/security/DelegationTokenKerberosFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/DelegationTokenKerberosFilter.java b/solr/core/src/java/org/apache/solr/security/DelegationTokenKerberosFilter.java
index 7dbb1ad..ca27861 100644
--- a/solr/core/src/java/org/apache/solr/security/DelegationTokenKerberosFilter.java
+++ b/solr/core/src/java/org/apache/solr/security/DelegationTokenKerberosFilter.java
@@ -18,6 +18,7 @@ package org.apache.solr.security;
 
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
+import java.util.Enumeration;
 import java.util.LinkedList;
 import java.util.List;
 
@@ -36,8 +37,11 @@ import org.apache.curator.framework.CuratorFrameworkFactory;
 import org.apache.curator.framework.api.ACLProvider;
 import org.apache.curator.retry.ExponentialBackoffRetry;
 
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
 import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationFilter;
+import org.apache.hadoop.security.token.delegation.web.HttpUserGroupInformation;
 import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkACLProvider;
@@ -62,6 +66,27 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
     super.init(conf);
   }
 
+  /**
+   * Return the ProxyUser Configuration.  FilterConfig properties beginning with
+   * "solr.impersonator.user.name" will be added to the configuration.
+   */
+  @Override
+  protected Configuration getProxyuserConfiguration(FilterConfig filterConf)
+      throws ServletException {
+    Configuration conf = new Configuration(false);
+
+    Enumeration<?> names = filterConf.getInitParameterNames();
+    while (names.hasMoreElements()) {
+      String name = (String) names.nextElement();
+      if (name.startsWith(KerberosPlugin.IMPERSONATOR_PREFIX)) {
+        String value = filterConf.getInitParameter(name);
+        conf.set(PROXYUSER_PREFIX + "." + name.substring(KerberosPlugin.IMPERSONATOR_PREFIX.length()), value);
+        conf.set(name, value);
+      }
+    }
+    return conf;
+  }
+
   @Override
   public void doFilter(ServletRequest request, ServletResponse response,
       FilterChain filterChain) throws IOException, ServletException {
@@ -76,7 +101,26 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
         return nonNullQueryString;
       }
     };
-    super.doFilter(requestNonNullQueryString, response, filterChain);
+
+    // include Impersonator User Name in case someone (e.g. logger) wants it
+    FilterChain filterChainWrapper = new FilterChain() {
+      @Override
+      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse)
+          throws IOException, ServletException {
+        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
+
+        UserGroupInformation ugi = HttpUserGroupInformation.get();
+        if (ugi != null && ugi.getAuthenticationMethod() == UserGroupInformation.AuthenticationMethod.PROXY) {
+          UserGroupInformation realUserUgi = ugi.getRealUser();
+          if (realUserUgi != null) {
+            httpRequest.setAttribute(KerberosPlugin.IMPERSONATOR_USER_NAME, realUserUgi.getShortUserName());
+          }
+        }
+        filterChain.doFilter(servletRequest, servletResponse);
+      }
+    };
+
+    super.doFilter(requestNonNullQueryString, response, filterChainWrapper);
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e50858c3/solr/core/src/java/org/apache/solr/security/KerberosPlugin.java
----------------------------------------------------------------------
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 1cd476f..d4a2823 100644
--- a/solr/core/src/java/org/apache/solr/security/KerberosPlugin.java
+++ b/solr/core/src/java/org/apache/solr/security/KerberosPlugin.java
@@ -69,7 +69,7 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   Krb5HttpClientBuilder kerberosBuilder = new Krb5HttpClientBuilder();
-  Filter kerberosFilter;
+  private Filter kerberosFilter;
   
   public static final String NAME_RULES_PARAM = "solr.kerberos.name.rules";
   public static final String COOKIE_DOMAIN_PARAM = "solr.kerberos.cookie.domain";
@@ -78,6 +78,7 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
   public static final String KEYTAB_PARAM = "solr.kerberos.keytab";
   public static final String TOKEN_VALID_PARAM = "solr.kerberos.token.valid";
   public static final String COOKIE_PORT_AWARE_PARAM = "solr.kerberos.cookie.portaware";
+  public static final String IMPERSONATOR_PREFIX = "solr.kerberos.impersonator.user.";
   public static final String DELEGATION_TOKEN_ENABLED = "solr.kerberos.delegation.token.enabled";
   public static final String DELEGATION_TOKEN_KIND = "solr.kerberos.delegation.token.kind";
   public static final String DELEGATION_TOKEN_VALIDITY = "solr.kerberos.delegation.token.validity";
@@ -86,18 +87,17 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
       "solr.kerberos.delegation.token.signer.secret.provider.zookeper.path";
   public static final String DELEGATION_TOKEN_SECRET_MANAGER_ZNODE_WORKING_PATH =
       "solr.kerberos.delegation.token.secret.manager.znode.working.path";
+
   public static final String DELEGATION_TOKEN_TYPE_DEFAULT = "solr-dt";
-  
+  public static final String IMPERSONATOR_DO_AS_HTTP_PARAM = "doAs";
+  public static final String IMPERSONATOR_USER_NAME = "solr.impersonator.user.name";
+
   // filled in by Plugin/Filter
   static final String REQUEST_CONTINUES_ATTR =
       "org.apache.solr.security.kerberosplugin.requestcontinues";
   static final String DELEGATION_TOKEN_ZK_CLIENT =
       "solr.kerberos.delegation.token.zk.client";
 
-  // allows test to specify an alternate auth handler
-  @VisibleForTesting
-  public static final String AUTH_HANDLER_PARAM = "solr.kerberos.auth.handler";
-
   private final CoreContainer coreContainer;
 
   public KerberosPlugin(CoreContainer coreContainer) {
@@ -107,105 +107,121 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
   @Override
   public void init(Map<String, Object> pluginConfig) {
     try {
-      Map<String, String> params = new HashMap();
-      putParam(params, "type", AUTH_HANDLER_PARAM, "kerberos");
-      putParam(params, "kerberos.name.rules", NAME_RULES_PARAM, "DEFAULT");
-      putParam(params, "token.valid", TOKEN_VALID_PARAM, "30");
-      putParam(params, "cookie.path", COOKIE_PATH_PARAM, "/");
-      if ("kerberos".equals(params.get("type"))) {
-        putParam(params, "kerberos.principal", PRINCIPAL_PARAM, null);
-        putParam(params, "kerberos.keytab", KEYTAB_PARAM, null);
-      } else {
-        // allow tests which specify AUTH_HANDLER_PARAM to avoid specifying kerberos principal/keytab
-        putParamOptional(params, "kerberos.principal", PRINCIPAL_PARAM);
-        putParamOptional(params, "kerberos.keytab", KEYTAB_PARAM);
-      }
+      FilterConfig conf = getInitFilterConfig(pluginConfig, false);
+      kerberosFilter.init(conf);
+    } catch (ServletException e) {
+      throw new SolrException(ErrorCode.SERVER_ERROR, "Error initializing kerberos authentication plugin: "+e);
+    }
+  }
 
-      String delegationTokenStr = System.getProperty(DELEGATION_TOKEN_ENABLED, null);
-      boolean delegationTokenEnabled =
-          (delegationTokenStr == null) ? false : Boolean.parseBoolean(delegationTokenStr);
-      ZkController controller = coreContainer.getZkController();
-
-      if (delegationTokenEnabled) {
-        putParam(params, "delegation-token.token-kind", DELEGATION_TOKEN_KIND, DELEGATION_TOKEN_TYPE_DEFAULT);
-        if (coreContainer.isZooKeeperAware()) {
-          putParam(params, "signer.secret.provider", DELEGATION_TOKEN_SECRET_PROVIDER, "zookeeper");
-          if ("zookeeper".equals(params.get("signer.secret.provider"))) {
-            String zkHost = controller.getZkServerAddress();
-            putParam(params, "token.validity", DELEGATION_TOKEN_VALIDITY, "36000");
-            params.put("zk-dt-secret-manager.enable", "true");
-            // Note - Curator complains if the znodeWorkingPath starts with /
-            String chrootPath = zkHost.substring(zkHost.indexOf("/"));
-            String relativePath = chrootPath.startsWith("/") ? chrootPath.substring(1) : chrootPath;
-            putParam(params, "zk-dt-secret-manager.znodeWorkingPath",
-                DELEGATION_TOKEN_SECRET_MANAGER_ZNODE_WORKING_PATH,
-                relativePath + SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH + "/zkdtsm");
-            putParam(params, "signer.secret.provider.zookeeper.path",
-                DELEGATION_TOKEN_SECRET_PROVIDER_ZK_PATH, "/token");
-            // ensure krb5 is setup properly before running curator
-            getHttpClientBuilder(SolrHttpClientBuilder.create());
-          }
-        } else {
-          log.info("CoreContainer is not ZooKeeperAware, not setting ZK-related delegation token properties");
+  @VisibleForTesting
+  protected FilterConfig getInitFilterConfig(Map<String, Object> pluginConfig, boolean skipKerberosChecking) {
+    Map<String, String> params = new HashMap();
+    params.put("type", "kerberos");
+    putParam(params, "kerberos.name.rules", NAME_RULES_PARAM, "DEFAULT");
+    putParam(params, "token.valid", TOKEN_VALID_PARAM, "30");
+    putParam(params, "cookie.path", COOKIE_PATH_PARAM, "/");
+    if (!skipKerberosChecking) {
+      putParam(params, "kerberos.principal", PRINCIPAL_PARAM, null);
+      putParam(params, "kerberos.keytab", KEYTAB_PARAM, null);
+    } else {
+      putParamOptional(params, "kerberos.principal", PRINCIPAL_PARAM);
+      putParamOptional(params, "kerberos.keytab", KEYTAB_PARAM);
+    }
+
+    String delegationTokenStr = System.getProperty(DELEGATION_TOKEN_ENABLED, null);
+    boolean delegationTokenEnabled =
+        (delegationTokenStr == null) ? false : Boolean.parseBoolean(delegationTokenStr);
+    ZkController controller = coreContainer.getZkController();
+
+    if (delegationTokenEnabled) {
+      putParam(params, "delegation-token.token-kind", DELEGATION_TOKEN_KIND, DELEGATION_TOKEN_TYPE_DEFAULT);
+      if (coreContainer.isZooKeeperAware()) {
+        putParam(params, "signer.secret.provider", DELEGATION_TOKEN_SECRET_PROVIDER, "zookeeper");
+        if ("zookeeper".equals(params.get("signer.secret.provider"))) {
+          String zkHost = controller.getZkServerAddress();
+          putParam(params, "token.validity", DELEGATION_TOKEN_VALIDITY, "36000");
+          params.put("zk-dt-secret-manager.enable", "true");
+          // Note - Curator complains if the znodeWorkingPath starts with /
+          String chrootPath = zkHost.substring(zkHost.indexOf("/"));
+          String relativePath = chrootPath.startsWith("/") ? chrootPath.substring(1) : chrootPath;
+          putParam(params, "zk-dt-secret-manager.znodeWorkingPath",
+              DELEGATION_TOKEN_SECRET_MANAGER_ZNODE_WORKING_PATH,
+              relativePath + SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH + "/zkdtsm");
+          putParam(params, "signer.secret.provider.zookeeper.path",
+              DELEGATION_TOKEN_SECRET_PROVIDER_ZK_PATH, "/token");
+          // ensure krb5 is setup properly before running curator
+          getHttpClientBuilder(SolrHttpClientBuilder.create());
         }
+      } else {
+        log.info("CoreContainer is not ZooKeeperAware, not setting ZK-related delegation token properties");
       }
+    }
 
-      // Special handling for the "cookie.domain" based on whether port should be
-      // appended to the domain. Useful for situations where multiple solr nodes are
-      // on the same host.
-      String usePortStr = System.getProperty(COOKIE_PORT_AWARE_PARAM, null);
-      boolean needPortAwareCookies = (usePortStr == null) ? false: Boolean.parseBoolean(usePortStr);
-
-      if (!needPortAwareCookies || !coreContainer.isZooKeeperAware()) {
-        putParam(params, "cookie.domain", COOKIE_DOMAIN_PARAM, null);
-      } else { // we need port aware cookies and we are in SolrCloud mode.
-        String host = System.getProperty(COOKIE_DOMAIN_PARAM, null);
-        if (host==null) {
-          throw new SolrException(ErrorCode.SERVER_ERROR, "Missing required parameter '"+COOKIE_DOMAIN_PARAM+"'.");
-        }
-        int port = controller.getHostPort();
-        params.put("cookie.domain", host + ":" + port);
+    // Special handling for the "cookie.domain" based on whether port should be
+    // appended to the domain. Useful for situations where multiple solr nodes are
+    // on the same host.
+    String usePortStr = System.getProperty(COOKIE_PORT_AWARE_PARAM, null);
+    boolean needPortAwareCookies = (usePortStr == null) ? false: Boolean.parseBoolean(usePortStr);
+
+    if (!needPortAwareCookies || !coreContainer.isZooKeeperAware()) {
+      putParam(params, "cookie.domain", COOKIE_DOMAIN_PARAM, null);
+    } else { // we need port aware cookies and we are in SolrCloud mode.
+      String host = System.getProperty(COOKIE_DOMAIN_PARAM, null);
+      if (host==null) {
+        throw new SolrException(ErrorCode.SERVER_ERROR, "Missing required parameter '"+COOKIE_DOMAIN_PARAM+"'.");
       }
+      int port = controller.getHostPort();
+      params.put("cookie.domain", host + ":" + port);
+    }
 
-      final ServletContext servletContext = new AttributeOnlyServletContext();
-      if (delegationTokenEnabled) {
-        kerberosFilter = new DelegationTokenKerberosFilter();
-        // pass an attribute-enabled context in order to pass the zkClient
-        // and because the filter may pass a curator instance.
-        if (controller != null) {
-          servletContext.setAttribute(DELEGATION_TOKEN_ZK_CLIENT, controller.getZkClient());
+    // check impersonator config
+    for (Enumeration e = System.getProperties().propertyNames(); e.hasMoreElements();) {
+      String key = e.nextElement().toString();
+      if (key.startsWith(IMPERSONATOR_PREFIX)) {
+        if (!delegationTokenEnabled) {
+          throw new SolrException(ErrorCode.SERVER_ERROR,
+              "Impersonator configuration requires delegation tokens to be enabled: " + key);
         }
-      } else {
-        kerberosFilter = new KerberosFilter();
+        params.put(key, System.getProperty(key));
       }
-      log.info("Params: "+params);
+    }
+    final ServletContext servletContext = new AttributeOnlyServletContext();
+    if (controller != null) {
+      servletContext.setAttribute(DELEGATION_TOKEN_ZK_CLIENT, controller.getZkClient());
+    }
+    if (delegationTokenEnabled) {
+      kerberosFilter = new DelegationTokenKerberosFilter();
+      // pass an attribute-enabled context in order to pass the zkClient
+      // and because the filter may pass a curator instance.
+    } else {
+      kerberosFilter = new KerberosFilter();
+    }
+    log.info("Params: "+params);
 
-      FilterConfig conf = new FilterConfig() {
-        @Override
-        public ServletContext getServletContext() {
-          return servletContext;
-        }
+    FilterConfig conf = new FilterConfig() {
+      @Override
+      public ServletContext getServletContext() {
+        return servletContext;
+      }
 
-        @Override
-        public Enumeration<String> getInitParameterNames() {
-          return new IteratorEnumeration(params.keySet().iterator());
-        }
+      @Override
+      public Enumeration<String> getInitParameterNames() {
+        return new IteratorEnumeration(params.keySet().iterator());
+      }
 
-        @Override
-        public String getInitParameter(String param) {
-          return params.get(param);
-        }
+      @Override
+      public String getInitParameter(String param) {
+        return params.get(param);
+      }
 
-        @Override
-        public String getFilterName() {
-          return "KerberosFilter";
-        }
-      };
+      @Override
+      public String getFilterName() {
+        return "KerberosFilter";
+      }
+    };
 
-      kerberosFilter.init(conf);
-    } catch (ServletException e) {
-      throw new SolrException(ErrorCode.SERVER_ERROR, "Error initializing kerberos authentication plugin: "+e);
-    }
+    return conf;
   }
 
   private void putParam(Map<String, String> params, String internalParamName, String externalParamName, String defaultValue) {
@@ -260,11 +276,16 @@ public class KerberosPlugin extends AuthenticationPlugin implements HttpClientBu
     return kerberosBuilder.getBuilder(builder);
   }
 
+  @Override
   public void close() {
     kerberosFilter.destroy();
     kerberosBuilder.close();
   }
 
+  protected Filter getKerberosFilter() { return kerberosFilter; }
+
+  protected void setKerberosFilter(Filter kerberosFilter) { this.kerberosFilter = kerberosFilter; }
+
   protected static class AttributeOnlyServletContext implements ServletContext {
     private Map<String, Object> attributes = new HashMap<String, Object>();
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e50858c3/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java b/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
index ae1c439..a58ec8c 100644
--- a/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
+++ b/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithDelegationTokens.java
@@ -30,10 +30,10 @@ import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
-import static org.apache.solr.security.HttpParamDelegationTokenAuthenticationHandler.USER_PARAM;
+import static org.apache.solr.security.HttpParamDelegationTokenPlugin.USER_PARAM;
 
 import org.apache.http.HttpStatus;
-import org.apache.solr.security.HttpParamDelegationTokenAuthenticationHandler;
+import org.apache.solr.security.HttpParamDelegationTokenPlugin;
 import org.apache.solr.security.KerberosPlugin;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -59,10 +59,8 @@ public class TestSolrCloudWithDelegationTokens extends SolrTestCaseJ4 {
 
   @BeforeClass
   public static void startup() throws Exception {
-    System.setProperty("authenticationPlugin", KerberosPlugin.class.getName());
+    System.setProperty("authenticationPlugin", HttpParamDelegationTokenPlugin.class.getName());
     System.setProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED, "true");
-    System.setProperty(KerberosPlugin.AUTH_HANDLER_PARAM,
-        HttpParamDelegationTokenAuthenticationHandler.class.getName());
     System.setProperty("solr.kerberos.cookie.domain", "127.0.0.1");
 
     miniCluster = new MiniSolrCloudCluster(NUM_SERVERS, createTempDir(), buildJettyConfig("/solr"));
@@ -88,7 +86,6 @@ public class TestSolrCloudWithDelegationTokens extends SolrTestCaseJ4 {
     solrClientSecondary = null;
     System.clearProperty("authenticationPlugin");
     System.clearProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED);
-    System.clearProperty(KerberosPlugin.AUTH_HANDLER_PARAM);
     System.clearProperty("solr.kerberos.cookie.domain");
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e50858c3/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithSecureImpersonation.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithSecureImpersonation.java b/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithSecureImpersonation.java
new file mode 100644
index 0000000..1839d32
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/TestSolrCloudWithSecureImpersonation.java
@@ -0,0 +1,340 @@
+/*
+ * 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.solr.cloud;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.client.solrj.SolrClient;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.impl.HttpSolrClient;
+import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.response.CollectionAdminResponse;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.handler.admin.CollectionsHandler;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.HttpParamDelegationTokenPlugin;
+import org.apache.solr.security.KerberosPlugin;
+import org.apache.solr.servlet.SolrRequestParsers;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.apache.solr.security.HttpParamDelegationTokenPlugin.USER_PARAM;
+import static org.apache.solr.security.HttpParamDelegationTokenPlugin.REMOTE_HOST_PARAM;
+import static org.apache.solr.security.HttpParamDelegationTokenPlugin.REMOTE_ADDRESS_PARAM;
+
+public class TestSolrCloudWithSecureImpersonation extends SolrTestCaseJ4 {
+  private static final int NUM_SERVERS = 2;
+  private static MiniSolrCloudCluster miniCluster;
+  private static SolrClient solrClient;
+
+  private static String getUsersFirstGroup() throws Exception {
+    org.apache.hadoop.security.Groups hGroups =
+        new org.apache.hadoop.security.Groups(new Configuration());
+    List<String> g = hGroups.getGroups(System.getProperty("user.name"));
+    return g.get(0);
+  }
+
+  private static Map<String, String> getImpersonatorSettings() throws Exception {
+    Map<String, String> filterProps = new TreeMap<String, String>();
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "noGroups.hosts", "*");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostAnyUser.groups", "*");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostAnyUser.hosts", "*");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "wrongHost.hosts", "1.1.1.1.1.1");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "wrongHost.groups", "*");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "noHosts.groups", "*");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "localHostAnyGroup.groups", "*");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "localHostAnyGroup.hosts", "127.0.0.1");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostUsersGroup.groups", getUsersFirstGroup());
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "anyHostUsersGroup.hosts", "*");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "bogusGroup.groups", "__some_bogus_group");
+    filterProps.put(KerberosPlugin.IMPERSONATOR_PREFIX + "bogusGroup.hosts", "*");
+    return filterProps;
+  }
+
+  @BeforeClass
+  public static void startup() throws Exception {
+    System.setProperty("authenticationPlugin", HttpParamDelegationTokenPlugin.class.getName());
+    System.setProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED, "true");
+
+    System.setProperty("solr.kerberos.cookie.domain", "127.0.0.1");
+    Map<String, String> impSettings = getImpersonatorSettings();
+    for (Map.Entry<String, String> entry : impSettings.entrySet()) {
+      System.setProperty(entry.getKey(), entry.getValue());
+    }
+    System.setProperty("solr.test.sys.prop1", "propone");
+    System.setProperty("solr.test.sys.prop2", "proptwo");
+
+    SolrRequestParsers.DEFAULT.setAddRequestHeadersToContext(true);
+    String solrXml = MiniSolrCloudCluster.DEFAULT_CLOUD_SOLR_XML.replace("</solr>",
+        " <str name=\"collectionsHandler\">" + ImpersonatorCollectionsHandler.class.getName() + "</str>\n" +
+            "</solr>");
+
+    miniCluster = new MiniSolrCloudCluster(NUM_SERVERS, createTempDir(), solrXml, buildJettyConfig("/solr"));
+    JettySolrRunner runner = miniCluster.getJettySolrRunners().get(0);
+    solrClient = new HttpSolrClient.Builder(runner.getBaseUrl().toString()).build();
+  }
+
+  /**
+   * Verify that impersonator info is preserved in the request
+   */
+  public static class ImpersonatorCollectionsHandler extends CollectionsHandler {
+    public static AtomicBoolean called = new AtomicBoolean(false);
+
+    public ImpersonatorCollectionsHandler() { super(); }
+
+    public ImpersonatorCollectionsHandler(final CoreContainer coreContainer) {
+      super(coreContainer);
+    }
+
+    @Override
+    public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+      called.set(true);
+      super.handleRequestBody(req, rsp);
+      String doAs = req.getParams().get(KerberosPlugin.IMPERSONATOR_DO_AS_HTTP_PARAM);
+      if (doAs != null) {
+        HttpServletRequest httpRequest = (HttpServletRequest)req.getContext().get("httpRequest");
+        assertNotNull(httpRequest);
+        String user = (String)httpRequest.getAttribute(USER_PARAM);
+        assertNotNull(user);
+        assertEquals(user, httpRequest.getAttribute(KerberosPlugin.IMPERSONATOR_USER_NAME));
+      }
+    }
+  }
+
+  @AfterClass
+  public static void shutdown() throws Exception {
+    if (miniCluster != null) {
+      miniCluster.shutdown();
+    }
+    miniCluster = null;
+    solrClient.close();
+    solrClient = null;
+    System.clearProperty("authenticationPlugin");
+    System.clearProperty(KerberosPlugin.DELEGATION_TOKEN_ENABLED);
+    System.clearProperty("solr.kerberos.cookie.domain");
+    Map<String, String> impSettings = getImpersonatorSettings();
+    for (Map.Entry<String, String> entry : impSettings.entrySet()) {
+      System.clearProperty(entry.getKey());
+    }
+    System.clearProperty("solr.test.sys.prop1");
+    System.clearProperty("solr.test.sys.prop2");
+    SolrRequestParsers.DEFAULT.setAddRequestHeadersToContext(false);
+  }
+
+  private void create1ShardCollection(String name, String config, MiniSolrCloudCluster solrCluster) throws Exception {
+    CollectionAdminResponse response;
+    CollectionAdminRequest.Create create = new CollectionAdminRequest.Create() {
+      @Override
+      public SolrParams getParams() {
+        ModifiableSolrParams msp = new ModifiableSolrParams(super.getParams());
+        msp.set(USER_PARAM, "user");
+        return msp;
+      }
+    };
+    create.setConfigName(config);
+    create.setCollectionName(name);
+    create.setNumShards(1);
+    create.setReplicationFactor(1);
+    create.setMaxShardsPerNode(1);
+    response = create.process(solrCluster.getSolrClient());
+
+    if (response.getStatus() != 0 || response.getErrorMessages() != null) {
+      fail("Could not create collection. Response" + response.toString());
+    }
+    ZkStateReader zkStateReader = solrCluster.getSolrClient().getZkStateReader();
+    AbstractDistribZkTestBase.waitForRecoveriesToFinish(name, zkStateReader, false, true, 100);
+  }
+
+  private SolrRequest getProxyRequest(String user, String doAs) {
+    return getProxyRequest(user, doAs, null);
+  }
+
+  private SolrRequest getProxyRequest(String user, String doAs, String remoteHost) {
+    return getProxyRequest(user, doAs, remoteHost, null);
+  }
+
+  private SolrRequest getProxyRequest(String user, String doAs, String remoteHost, String remoteAddress) {
+    return new CollectionAdminRequest.List() {
+      @Override
+      public SolrParams getParams() {
+        ModifiableSolrParams params = new ModifiableSolrParams(super.getParams());
+        params.set(USER_PARAM, user);
+        params.set(KerberosPlugin.IMPERSONATOR_DO_AS_HTTP_PARAM, doAs);
+        if (remoteHost != null) params.set(REMOTE_HOST_PARAM, remoteHost);
+        if (remoteAddress != null) params.set(REMOTE_ADDRESS_PARAM, remoteAddress);
+        return params;
+      }
+    };
+  }
+
+  private String getExpectedGroupExMsg(String user, String doAs) {
+    return "User: " + user + " is not allowed to impersonate " + doAs;
+  }
+
+  private String getExpectedHostExMsg(String user) {
+    return "Unauthorized connection for super-user: " + user;
+  }
+
+  @Test
+  public void testProxyNoConfigGroups() throws Exception {
+    try {
+      solrClient.request(getProxyRequest("noGroups","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedGroupExMsg("noGroups", "bar")));
+    }
+  }
+
+  @Test
+  public void testProxyWrongHost() throws Exception {
+    try {
+      solrClient.request(getProxyRequest("wrongHost","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedHostExMsg("wrongHost")));
+    }
+  }
+
+  @Test
+  public void testProxyNoConfigHosts() throws Exception {
+    try {
+      solrClient.request(getProxyRequest("noHosts","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      // FixMe: this should return an exception about the host being invalid,
+      // but a bug (HADOOP-11077) causes an NPE instead.
+      //assertTrue(ex.getMessage().contains(getExpectedHostExMsg("noHosts")));
+    }
+  }
+
+  @Test
+  public void testProxyValidateAnyHostAnyUser() throws Exception {
+    solrClient.request(getProxyRequest("anyHostAnyUser", "bar", null));
+    assertTrue(ImpersonatorCollectionsHandler.called.get());
+  }
+
+  @Test
+  public void testProxyInvalidProxyUser() throws Exception {
+    try {
+      // wrong direction, should fail
+      solrClient.request(getProxyRequest("bar","anyHostAnyUser"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedGroupExMsg("bar", "anyHostAnyUser")));
+    }
+  }
+
+  @Test
+  public void testProxyValidateHost() throws Exception {
+    solrClient.request(getProxyRequest("localHostAnyGroup", "bar"));
+    assertTrue(ImpersonatorCollectionsHandler.called.get());
+  }
+
+
+
+  @Test
+  public void testProxyValidateGroup() throws Exception {
+    solrClient.request(getProxyRequest("anyHostUsersGroup", System.getProperty("user.name"), null));
+    assertTrue(ImpersonatorCollectionsHandler.called.get());
+  }
+
+  @Test
+  public void testProxyUnknownRemote() throws Exception {
+    try {
+      // Use a reserved ip address
+      String nonProxyUserConfiguredIpAddress = "255.255.255.255";
+      solrClient.request(getProxyRequest("localHostAnyGroup", "bar", "unknownhost.bar.foo", nonProxyUserConfiguredIpAddress));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedHostExMsg("localHostAnyGroup")));
+    }
+  }
+
+  @Test
+  public void testProxyInvalidRemote() throws Exception {
+    try {
+      String invalidIpAddress = "-127.-128";
+      solrClient.request(getProxyRequest("localHostAnyGroup","bar", "[ff01::114]", invalidIpAddress));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedHostExMsg("localHostAnyGroup")));
+    }
+  }
+
+  @Test
+  public void testProxyInvalidGroup() throws Exception {
+    try {
+      solrClient.request(getProxyRequest("bogusGroup","bar", null));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      assertTrue(ex.getMessage().contains(getExpectedGroupExMsg("bogusGroup", "bar")));
+    }
+  }
+
+  @Test
+  public void testProxyNullProxyUser() throws Exception {
+    try {
+      solrClient.request(getProxyRequest("","bar"));
+      fail("Expected RemoteSolrException");
+    }
+    catch (HttpSolrClient.RemoteSolrException ex) {
+      // this exception is specific to our implementation, don't check a specific message.
+    }
+  }
+
+  @Test
+  public void testForwarding() throws Exception {
+    String collectionName = "forwardingCollection";
+    File configDir = getFile("solr").toPath().resolve("collection1/conf").toFile();
+    miniCluster.uploadConfigDir(configDir, "conf1");
+    create1ShardCollection(collectionName, "conf1", miniCluster);
+
+    // try a command to each node, one of them must be forwarded
+    for (JettySolrRunner jetty : miniCluster.getJettySolrRunners()) {
+      HttpSolrClient client =
+          new HttpSolrClient.Builder(jetty.getBaseUrl().toString() + "/" + collectionName).build();
+      try {
+        ModifiableSolrParams params = new ModifiableSolrParams();
+        params.set("q", "*:*");
+        params.set(USER_PARAM, "user");
+        client.query(params);
+      } finally {
+        client.close();
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e50858c3/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenAuthenticationHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenAuthenticationHandler.java b/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenAuthenticationHandler.java
deleted file mode 100644
index 7c5c94a..0000000
--- a/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenAuthenticationHandler.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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.solr.security;
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.hadoop.security.authentication.client.AuthenticationException;
-import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
-import org.apache.hadoop.security.authentication.server.AuthenticationToken;
-import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationHandler;
-
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
-
-/**
- * AuthenticationHandler that supports delegation tokens and simple
- * authentication via the "user" http parameter
- */
-public class HttpParamDelegationTokenAuthenticationHandler extends
-    DelegationTokenAuthenticationHandler {
-
-  public static final String USER_PARAM = "user";
-
-  public HttpParamDelegationTokenAuthenticationHandler() {
-    super(new HttpParamAuthenticationHandler());
-  }
-
-  @Override
-  public void init(Properties config) throws ServletException {
-    Properties conf = new Properties();
-    for (Map.Entry entry : config.entrySet()) {
-      conf.setProperty((String) entry.getKey(), (String) entry.getValue());
-    }
-    conf.setProperty(TOKEN_KIND, KerberosPlugin.DELEGATION_TOKEN_TYPE_DEFAULT);
-    super.init(conf);
-  }
- 
-  private static String getHttpParam(HttpServletRequest request, String param) {
-    List<NameValuePair> pairs =
-      URLEncodedUtils.parse(request.getQueryString(), Charset.forName("UTF-8"));
-    for (NameValuePair nvp : pairs) {
-      if(param.equals(nvp.getName())) {
-        return nvp.getValue();
-      }
-    }
-    return null;
-  }
-
-  private static class HttpParamAuthenticationHandler
-      implements AuthenticationHandler {
-
-    @Override
-    public String getType() {
-      return "dummy";
-    }
-
-    @Override
-    public void init(Properties config) throws ServletException {
-    }
-
-    @Override
-    public void destroy() {
-    }
-
-    @Override
-    public boolean managementOperation(AuthenticationToken token,
-        HttpServletRequest request, HttpServletResponse response)
-        throws IOException, AuthenticationException {
-      return false;
-    }
-
-    @Override
-    public AuthenticationToken authenticate(HttpServletRequest request,
-        HttpServletResponse response)
-        throws IOException, AuthenticationException {
-      AuthenticationToken token = null;
-      String userName = getHttpParam(request, USER_PARAM);
-      if (userName != null) {
-        return new AuthenticationToken(userName, userName, "test");
-      } else {
-        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
-        response.setHeader("WWW-Authenticate", "dummy");
-      }
-      return token;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/e50858c3/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenPlugin.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenPlugin.java b/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenPlugin.java
new file mode 100644
index 0000000..7a4f69f
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/security/HttpParamDelegationTokenPlugin.java
@@ -0,0 +1,272 @@
+/*
+ * 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.solr.security;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+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.client.AuthenticationException;
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+import org.apache.hadoop.security.authentication.server.AuthenticationHandler;
+import org.apache.hadoop.security.authentication.server.AuthenticationToken;
+import org.apache.hadoop.security.token.delegation.web.DelegationTokenAuthenticationHandler;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.protocol.HttpContext;
+import org.apache.solr.client.solrj.impl.HttpClientUtil;
+import org.apache.solr.client.solrj.impl.SolrHttpClientBuilder;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.ExecutorUtil;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.request.SolrRequestInfo;
+
+/**
+ * AuthenticationHandler that supports delegation tokens and simple
+ * authentication via the "user" http parameter
+ */
+public class HttpParamDelegationTokenPlugin extends KerberosPlugin {
+  public static final String USER_PARAM = "user"; // http parameter for user authentication
+  public static final String REMOTE_HOST_PARAM = "remoteHost"; // http parameter for indicating remote host
+  public static final String REMOTE_ADDRESS_PARAM = "remoteAddress"; // http parameter for indicating remote address
+  public static final String INTERNAL_REQUEST_HEADER = "internalRequest"; // http header for indicating internal request
+
+  boolean isSolrThread() {
+    return ExecutorUtil.isSolrServerThread();
+  }
+
+  private final HttpRequestInterceptor interceptor = new HttpRequestInterceptor() {
+    @Override
+    public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
+      SolrRequestInfo reqInfo = SolrRequestInfo.getRequestInfo();
+      String usr;
+      if (reqInfo != null) {
+        Principal principal = reqInfo.getReq().getUserPrincipal();
+        if (principal == null) {
+          //this had a request but not authenticated
+          //so we don't not need to set a principal
+          return;
+        } else {
+          usr = principal.getName();
+        }
+      } else {
+        if (!isSolrThread()) {
+          //if this is not running inside a Solr threadpool (as in testcases)
+          // then no need to add any header
+          return;
+        }
+        //this request seems to be originated from Solr itself
+        usr = "$"; //special name to denote the user is the node itself
+      }
+      httpRequest.setHeader(INTERNAL_REQUEST_HEADER, usr);
+    }
+  };
+
+  public HttpParamDelegationTokenPlugin(CoreContainer coreContainer) {
+    super(coreContainer);
+  }
+
+  @Override
+  public void init(Map<String, Object> pluginConfig) {
+    try {
+      final FilterConfig initConf = getInitFilterConfig(pluginConfig, true);
+
+      FilterConfig conf = new FilterConfig() {
+        @Override
+        public ServletContext getServletContext() {
+          return initConf.getServletContext();
+        }
+
+        @Override
+        public Enumeration<String> getInitParameterNames() {
+          return initConf.getInitParameterNames();
+        }
+
+        @Override
+        public String getInitParameter(String param) {
+          if (AuthenticationFilter.AUTH_TYPE.equals(param)) {
+            return HttpParamDelegationTokenAuthenticationHandler.class.getName();
+          }
+          return initConf.getInitParameter(param);
+        }
+
+        @Override
+        public String getFilterName() {
+         return "HttpParamFilter";
+        }
+      };
+      Filter kerberosFilter = new HttpParamToRequestFilter();
+      kerberosFilter.init(conf);
+      setKerberosFilter(kerberosFilter);
+    } catch (ServletException e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+          "Error initializing kerberos authentication plugin: "+e);
+    }
+  }
+
+  @Override
+  public SolrHttpClientBuilder getHttpClientBuilder(SolrHttpClientBuilder builder) {
+    HttpClientUtil.addRequestInterceptor(interceptor);
+    return super.getHttpClientBuilder(builder);
+  }
+
+  @Override
+  public void close() {
+    HttpClientUtil.removeRequestInterceptor(interceptor);
+    super.close();
+  }
+
+  private static String getHttpParam(HttpServletRequest request, String param) {
+    List<NameValuePair> pairs = URLEncodedUtils.parse(request.getQueryString(), Charset.forName("UTF-8"));
+    for (NameValuePair nvp : pairs) {
+      if (param.equals(nvp.getName())) {
+        return nvp.getValue();
+      }
+    }
+    return null;
+  }
+
+  public static class HttpParamDelegationTokenAuthenticationHandler extends
+      DelegationTokenAuthenticationHandler {
+
+    public HttpParamDelegationTokenAuthenticationHandler() {
+      super(new HttpParamAuthenticationHandler());
+    }
+
+    @Override
+    public void init(Properties config) throws ServletException {
+      Properties conf = new Properties();
+      for (Map.Entry entry : config.entrySet()) {
+        conf.setProperty((String) entry.getKey(), (String) entry.getValue());
+      }
+      conf.setProperty(TOKEN_KIND, KerberosPlugin.DELEGATION_TOKEN_TYPE_DEFAULT);
+      super.init(conf);
+    }
+
+    private static class HttpParamAuthenticationHandler implements AuthenticationHandler {
+      @Override
+      public String getType() {
+        return "dummy";
+      }
+
+      @Override
+      public void init(Properties config) throws ServletException {
+      }
+
+      @Override
+      public void destroy() {
+      }
+
+      @Override
+      public boolean managementOperation(AuthenticationToken token,
+                                         HttpServletRequest request, HttpServletResponse response)
+          throws IOException, AuthenticationException {
+        return false;
+      }
+
+      @Override
+      public AuthenticationToken authenticate(HttpServletRequest request,
+                                              HttpServletResponse response)
+          throws IOException, AuthenticationException {
+        AuthenticationToken token = null;
+        String userName = getHttpParam(request, USER_PARAM);
+        if (userName == null) {
+          //check if this is an internal request
+          userName = request.getHeader(INTERNAL_REQUEST_HEADER);
+        }
+        if (userName != null) {
+          return new AuthenticationToken(userName, userName, "test");
+        } else {
+          response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+          response.setHeader("WWW-Authenticate", "dummy");
+        }
+        return token;
+      }
+    }
+  }
+
+  /**
+   * Filter that converts http params to HttpServletRequest params
+   */
+  private static class HttpParamToRequestFilter extends DelegationTokenKerberosFilter {
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+        throws IOException, ServletException {
+      final HttpServletRequest httpRequest = (HttpServletRequest) request;
+      final HttpServletRequestWrapper requestWrapper = new HttpServletRequestWrapper(httpRequest) {
+        @Override
+        public String getRemoteHost() {
+          String param = getHttpParam(httpRequest, REMOTE_HOST_PARAM);
+          return param != null ? param : httpRequest.getRemoteHost();
+        }
+
+        @Override
+        public String getRemoteAddr() {
+          String param = getHttpParam(httpRequest, REMOTE_ADDRESS_PARAM);
+          return param != null ? param : httpRequest.getRemoteAddr();
+        }
+      };
+
+      super.doFilter(requestWrapper, response, chain);
+    }
+
+    @Override
+    protected void doFilter(FilterChain filterChain, HttpServletRequest request,
+                            HttpServletResponse response) throws IOException, ServletException {
+      // remove the filter-specific authentication information, so it doesn't get accidentally forwarded.
+      List<NameValuePair> newPairs = new LinkedList<NameValuePair>();
+      List<NameValuePair> pairs = URLEncodedUtils.parse(request.getQueryString(), Charset.forName("UTF-8"));
+      for (NameValuePair nvp : pairs) {
+        if (!USER_PARAM.equals(nvp.getName())) {
+          newPairs.add(nvp);
+        }
+        else {
+          request.setAttribute(USER_PARAM, nvp.getValue());
+        }
+      }
+      final String queryStringNoUser = URLEncodedUtils.format(newPairs, StandardCharsets.UTF_8);
+      HttpServletRequest requestWrapper = new HttpServletRequestWrapper(request) {
+        @Override
+        public String getQueryString() {
+          return queryStringNoUser;
+        }
+      };
+      super.doFilter(filterChain, requestWrapper, response);
+    }
+  }
+}