mirror of https://github.com/apache/lucene.git
SOLR-7849: Solr-managed inter-node authentication when authentication enabled
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1694217 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
eb24e46781
commit
4112979808
|
@ -183,6 +183,8 @@ New Features
|
|||
|
||||
* SOLR-7766: support creation of a coreless collection via createNodeSet=EMPTY (Christine Poerschke)
|
||||
|
||||
* SOLR-7849: Solr-managed inter-node authentication when authentication enabled (Noble Paul)
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
|
@ -53,6 +52,8 @@ import org.apache.solr.logging.MDCLoggingContext;
|
|||
import org.apache.solr.request.SolrRequestHandler;
|
||||
import org.apache.solr.security.AuthorizationPlugin;
|
||||
import org.apache.solr.security.AuthenticationPlugin;
|
||||
import org.apache.solr.security.HttpClientInterceptorPlugin;
|
||||
import org.apache.solr.security.PKIAuthenticationPlugin;
|
||||
import org.apache.solr.update.UpdateShardHandler;
|
||||
import org.apache.solr.util.DefaultSolrThreadFactory;
|
||||
import org.apache.solr.util.FileUtils;
|
||||
|
@ -61,6 +62,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -94,6 +96,8 @@ public class CoreContainer {
|
|||
protected CollectionsHandler collectionsHandler = null;
|
||||
private InfoHandler infoHandler;
|
||||
|
||||
private PKIAuthenticationPlugin pkiAuthenticationPlugin;
|
||||
|
||||
protected Properties containerProperties;
|
||||
|
||||
private ConfigSetService coreConfigService;
|
||||
|
@ -124,8 +128,6 @@ public class CoreContainer {
|
|||
public static final String COLLECTIONS_HANDLER_PATH = "/admin/collections";
|
||||
public static final String INFO_HANDLER_PATH = "/admin/info";
|
||||
|
||||
final public static String AUTHENTICATION_PLUGIN_PROP = "authenticationPlugin";
|
||||
|
||||
private PluginBag<SolrRequestHandler> containerHandlers = new PluginBag<>(SolrRequestHandler.class, null);
|
||||
|
||||
private boolean asyncSolrCoreLoad;
|
||||
|
@ -264,9 +266,15 @@ public class CoreContainer {
|
|||
}
|
||||
if (authenticationPlugin != null) {
|
||||
authenticationPlugin.init(authenticationConfig);
|
||||
addHttpConfigurer(authenticationPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
private void addHttpConfigurer(Object authcPlugin) {
|
||||
log.info("addHttpConfigurer()");//TODO no commit
|
||||
if (authcPlugin instanceof HttpClientInterceptorPlugin) {
|
||||
// Setup HttpClient to use the plugin's configurer for internode communication
|
||||
HttpClientConfigurer configurer = authenticationPlugin.getDefaultConfigurer();
|
||||
HttpClientConfigurer configurer = ((HttpClientInterceptorPlugin) authcPlugin).getClientConfigurer();
|
||||
HttpClientUtil.setConfigurer(configurer);
|
||||
|
||||
// The default http client of the core container's shardHandlerFactory has already been created and
|
||||
|
@ -274,9 +282,14 @@ public class CoreContainer {
|
|||
// http client configurer to set it up for internode communication.
|
||||
log.info("Reconfiguring the shard handler factory and update shard handler.");
|
||||
if (getShardHandlerFactory() instanceof HttpShardHandlerFactory) {
|
||||
((HttpShardHandlerFactory)getShardHandlerFactory()).reconfigureHttpClient(configurer);
|
||||
((HttpShardHandlerFactory) getShardHandlerFactory()).reconfigureHttpClient(configurer);
|
||||
}
|
||||
getUpdateShardHandler().reconfigureHttpClient(configurer);
|
||||
} else {
|
||||
if (pkiAuthenticationPlugin != null) {
|
||||
log.info("PKIAuthenticationPlugin is managing internode requests");
|
||||
addHttpConfigurer(pkiAuthenticationPlugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,6 +330,10 @@ public class CoreContainer {
|
|||
return containerProperties;
|
||||
}
|
||||
|
||||
public PKIAuthenticationPlugin getPkiAuthenticationPlugin() {
|
||||
return pkiAuthenticationPlugin;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Initialization / Cleanup
|
||||
//-------------------------------------------------------------------
|
||||
|
@ -348,6 +365,7 @@ public class CoreContainer {
|
|||
hostName = cfg.getNodeName();
|
||||
|
||||
zkSys.initZooKeeper(this, solrHome, cfg.getCloudConfig());
|
||||
if(isZooKeeperAware()) pkiAuthenticationPlugin = new PKIAuthenticationPlugin(this, zkSys.getZkController().getNodeName());
|
||||
|
||||
initializeAuthenticationPlugin();
|
||||
|
||||
|
@ -361,6 +379,8 @@ public class CoreContainer {
|
|||
containerHandlers.put(INFO_HANDLER_PATH, infoHandler);
|
||||
coreAdminHandler = createHandler(cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class);
|
||||
containerHandlers.put(CORES_HANDLER_PATH, coreAdminHandler);
|
||||
if(pkiAuthenticationPlugin != null)
|
||||
containerHandlers.put(PKIAuthenticationPlugin.PATH, pkiAuthenticationPlugin.getRequestHandler());
|
||||
|
||||
coreConfigService = ConfigSetService.createConfigSetService(cfg, loader, zkSys.zkController);
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import org.apache.solr.common.params.SolrParams;
|
|||
import org.apache.solr.common.util.ContentStream;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -97,6 +98,8 @@ public interface SolrQueryRequest {
|
|||
public Map<String,Object> getJSON();
|
||||
|
||||
public void setJSON(Map<String,Object> json);
|
||||
|
||||
public Principal getUserPrincipal();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.apache.solr.common.util.ContentStream;
|
|||
import org.apache.solr.core.SolrCore;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
@ -175,4 +176,8 @@ public abstract class SolrQueryRequestBase implements SolrQueryRequest, Closeabl
|
|||
this.json = json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
package org.apache.solr.security;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
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 java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.attribute.UserPrincipal;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.http.auth.BasicUserPrincipal;
|
||||
import org.apache.solr.client.solrj.impl.HttpClientConfigurer;
|
||||
|
||||
/*
|
||||
|
@ -31,11 +39,27 @@ import org.apache.solr.client.solrj.impl.HttpClientConfigurer;
|
|||
*/
|
||||
public abstract class AuthenticationPlugin implements Closeable {
|
||||
|
||||
final public static String AUTHENTICATION_PLUGIN_PROP = "authenticationPlugin";
|
||||
|
||||
/**
|
||||
* This is called upon loading up of a plugin, used for setting it up.
|
||||
* @param pluginConfig Config parameters, possibly from a ZK source
|
||||
*/
|
||||
public abstract void init(Map<String, Object> pluginConfig);
|
||||
|
||||
protected void forward(String user, ServletRequest req, ServletResponse rsp,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
if(user != null) {
|
||||
final Principal p = new BasicUserPrincipal(user);
|
||||
req = new HttpServletRequestWrapper((HttpServletRequest) req) {
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return p;
|
||||
}
|
||||
};
|
||||
}
|
||||
chain.doFilter(req,rsp);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method must authenticate the request. Upon a successful authentication, this
|
||||
|
@ -51,13 +75,5 @@ public abstract class AuthenticationPlugin implements Closeable {
|
|||
public abstract void doAuthenticate(ServletRequest request, ServletResponse response,
|
||||
FilterChain filterChain) throws Exception;
|
||||
|
||||
/**
|
||||
*
|
||||
* @return Returns an instance of a HttpClientConfigurer to be used for configuring the
|
||||
* httpclients for use with SolrJ clients.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public abstract HttpClientConfigurer getDefaultConfigurer();
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package org.apache.solr.security;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
import org.apache.solr.client.solrj.impl.HttpClientConfigurer;
|
||||
|
||||
public interface HttpClientInterceptorPlugin {
|
||||
/**
|
||||
*
|
||||
* @return Returns an instance of a HttpClientConfigurer to be used for configuring the
|
||||
* httpclients for use with SolrJ clients.
|
||||
*
|
||||
* @lucene.experimental
|
||||
*/
|
||||
public HttpClientConfigurer getClientConfigurer();
|
||||
}
|
|
@ -50,7 +50,7 @@ import org.slf4j.LoggerFactory;
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
public class KerberosPlugin extends AuthenticationPlugin {
|
||||
public class KerberosPlugin extends AuthenticationPlugin implements HttpClientInterceptorPlugin {
|
||||
static final Logger log = LoggerFactory.getLogger(KerberosPlugin.class);
|
||||
|
||||
HttpClientConfigurer kerberosConfigurer = new Krb5HttpClientConfigurer();
|
||||
|
@ -121,7 +121,7 @@ public class KerberosPlugin extends AuthenticationPlugin {
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpClientConfigurer getDefaultConfigurer() {
|
||||
public HttpClientConfigurer getClientConfigurer() {
|
||||
return kerberosConfigurer;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,299 @@
|
|||
package org.apache.solr.security;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Principal;
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.apache.http.HttpException;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpRequestInterceptor;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.auth.BasicUserPrincipal;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.apache.solr.client.solrj.impl.HttpClientConfigurer;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.common.util.Base64;
|
||||
import org.apache.solr.common.util.ExecutorUtil;
|
||||
import org.apache.solr.common.util.StrUtils;
|
||||
import org.apache.solr.common.util.Utils;
|
||||
import org.apache.solr.core.CoreContainer;
|
||||
import org.apache.solr.handler.RequestHandlerBase;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.request.SolrRequestHandler;
|
||||
import org.apache.solr.request.SolrRequestInfo;
|
||||
import org.apache.solr.response.SolrQueryResponse;
|
||||
import org.apache.solr.util.CryptoKeys;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
public class PKIAuthenticationPlugin extends AuthenticationPlugin implements HttpClientInterceptorPlugin {
|
||||
static final Logger log = LoggerFactory.getLogger(PKIAuthenticationPlugin.class);
|
||||
private final Map<String, PublicKey> keyCache = new ConcurrentHashMap<>();
|
||||
private CryptoKeys.RSAKeyPair keyPair = new CryptoKeys.RSAKeyPair();
|
||||
private final CoreContainer cores;
|
||||
private int maxValidity = 5000;
|
||||
private final String myNodeName;
|
||||
|
||||
public PKIAuthenticationPlugin(CoreContainer cores, String nodeName) {
|
||||
this.cores = cores;
|
||||
myNodeName = nodeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Map<String, Object> pluginConfig) {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws Exception {
|
||||
|
||||
String requestURI = ((HttpServletRequest) request).getRequestURI();
|
||||
if (requestURI.endsWith(PATH)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
long receivedTime = System.currentTimeMillis();
|
||||
String header = ((HttpServletRequest) request).getHeader(HEADER);
|
||||
if (header == null) {
|
||||
log.error("No SolrAuth header present");
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> authInfo = StrUtils.splitWS(header, false);
|
||||
if (authInfo.size() < 2) {
|
||||
log.error("Invalid SolrAuth Header");
|
||||
return;
|
||||
}
|
||||
|
||||
String nodeName = authInfo.get(0);
|
||||
String cipher = authInfo.get(1);
|
||||
|
||||
byte[] decipher = decipherData(nodeName, cipher);
|
||||
if (decipher == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String s = new String(decipher, StandardCharsets.UTF_8).trim();
|
||||
List<String> pcs = StrUtils.splitWS(s, false);
|
||||
if (pcs.size() < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String userName = pcs.get(0);
|
||||
String timeStr = pcs.get(1);
|
||||
try {
|
||||
long timeMillis = Long.parseLong(timeStr);
|
||||
if ((receivedTime - timeMillis) > maxValidity) {
|
||||
log.error("Invalid key ");
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Invalid time " + timeStr, e);
|
||||
return;
|
||||
}
|
||||
final Principal principal = "$".equals(userName) ?
|
||||
SU :
|
||||
new BasicUserPrincipal(userName);
|
||||
|
||||
filterChain.doFilter(getWrapper((HttpServletRequest) request, principal), response);
|
||||
}
|
||||
|
||||
private static HttpServletRequestWrapper getWrapper(final HttpServletRequest request, final Principal principal) {
|
||||
return new HttpServletRequestWrapper(request) {
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return principal;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] decipherData(String nodeName, String cipherBase64) {
|
||||
boolean freshKey = false;
|
||||
PublicKey key = keyCache.get(nodeName);
|
||||
if (key == null) {
|
||||
key = getRemotePublicKey(nodeName);
|
||||
freshKey = true;
|
||||
}
|
||||
|
||||
try {
|
||||
return CryptoKeys.decryptRSA(Base64.base64ToByteArray(cipherBase64), key);
|
||||
} catch (InvalidKeyException e) {
|
||||
if (!freshKey) {
|
||||
key = getRemotePublicKey(nodeName);
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return CryptoKeys.decryptRSA(Base64.base64ToByteArray(cipherBase64), key);
|
||||
} catch (Exception e1) {
|
||||
log.error("Error decrypting");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error decrypting");
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
PublicKey getRemotePublicKey(String nodename) {
|
||||
String url = cores.getZkController().getZkStateReader().getBaseUrlForNodeName(nodename);
|
||||
try {
|
||||
HttpResponse rsp = cores.getUpdateShardHandler().getHttpClient().execute(new HttpGet(url + PATH + "?wt=json&omitHeader=true"));
|
||||
byte[] bytes = EntityUtils.toByteArray(rsp.getEntity());
|
||||
Map m = (Map) Utils.fromJSON(bytes);
|
||||
String key = (String) m.get("key");
|
||||
if (key == null) {
|
||||
log.error("No key available from " + url + PATH);
|
||||
return null;
|
||||
}
|
||||
PublicKey pubKey = CryptoKeys.deserializeX509PublicKey(key);
|
||||
keyCache.put(nodename, pubKey);
|
||||
return pubKey;
|
||||
} catch (Exception e) {
|
||||
log.error("Exception trying to get public key from : " + url, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private HttpHeaderClientConfigurer clientConfigurer = new HttpHeaderClientConfigurer();
|
||||
|
||||
@Override
|
||||
public HttpClientConfigurer getClientConfigurer() {
|
||||
return clientConfigurer;
|
||||
}
|
||||
|
||||
public SolrRequestHandler getRequestHandler() {
|
||||
return new RequestHandlerBase() {
|
||||
@Override
|
||||
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
|
||||
rsp.add("key", keyPair.getPublicKeyStr());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Return the public key of this server";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean needsAuthorization(HttpServletRequest req) {
|
||||
if (req.getUserPrincipal() == SU) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class HttpHeaderClientConfigurer extends HttpClientConfigurer implements
|
||||
HttpRequestInterceptor {
|
||||
|
||||
@Override
|
||||
public void configure(DefaultHttpClient httpClient, SolrParams config) {
|
||||
super.configure(httpClient, config);
|
||||
httpClient.addRequestInterceptor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
|
||||
if (disabled()) return;
|
||||
setHeader(httpRequest);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void setHeader(HttpRequest httpRequest) {
|
||||
SolrRequestInfo reqInfo = getRequestInfo();
|
||||
String usr = null;
|
||||
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
|
||||
}
|
||||
|
||||
String s = usr + " " + System.currentTimeMillis();
|
||||
|
||||
byte[] payload = s.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] payloadCipher = keyPair.encrypt(ByteBuffer.wrap(payload));
|
||||
String base64Cipher = Base64.byteArrayToBase64(payloadCipher);
|
||||
httpRequest.setHeader(HEADER, myNodeName + " " + base64Cipher);
|
||||
}
|
||||
|
||||
boolean isSolrThread() {
|
||||
return ExecutorUtil.isSolrServerThread();
|
||||
}
|
||||
|
||||
SolrRequestInfo getRequestInfo() {
|
||||
return SolrRequestInfo.getRequestInfo();
|
||||
}
|
||||
|
||||
boolean disabled() {
|
||||
return cores.getAuthenticationPlugin() == null ||
|
||||
cores.getAuthenticationPlugin() instanceof HttpClientInterceptorPlugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return keyPair.getPublicKeyStr();
|
||||
}
|
||||
|
||||
public static final String HEADER = "SolrAuth";
|
||||
public static final String PATH = "/admin/info/key";
|
||||
public static final String NODE_IS_USER = "$";
|
||||
// special principal to denote the cluster member
|
||||
private static final Principal SU = new BasicUserPrincipal("$");
|
||||
}
|
|
@ -104,6 +104,7 @@ import org.apache.solr.security.AuthorizationContext;
|
|||
import org.apache.solr.security.AuthorizationContext.CollectionRequest;
|
||||
import org.apache.solr.security.AuthorizationContext.RequestType;
|
||||
import org.apache.solr.security.AuthorizationResponse;
|
||||
import org.apache.solr.security.PKIAuthenticationPlugin;
|
||||
import org.apache.solr.servlet.SolrDispatchFilter.Action;
|
||||
import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;
|
||||
import org.apache.solr.servlet.cache.Method;
|
||||
|
@ -417,7 +418,7 @@ public class HttpSolrCall {
|
|||
1. Authorization is enabled, and
|
||||
2. The requested resource is not a known static file
|
||||
*/
|
||||
if (cores.getAuthorizationPlugin() != null) {
|
||||
if (cores.getAuthorizationPlugin() != null && shouldAuthorize()) {
|
||||
AuthorizationContext context = getAuthCtx();
|
||||
log.info(context.toString());
|
||||
AuthorizationResponse authResponse = cores.getAuthorizationPlugin().authorize(context);
|
||||
|
@ -484,6 +485,15 @@ public class HttpSolrCall {
|
|||
|
||||
}
|
||||
|
||||
private boolean shouldAuthorize() {
|
||||
if(PKIAuthenticationPlugin.PATH.equals(path)) return false;
|
||||
//admin/info/key is the path where public key is exposed . it is always unsecured
|
||||
if( cores.getPkiAuthenticationPlugin() != null && req.getUserPrincipal() != null){
|
||||
return cores.getPkiAuthenticationPlugin().needsAuthorization(req);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy() {
|
||||
try {
|
||||
if (solrReq != null) {
|
||||
|
|
|
@ -24,7 +24,6 @@ import javax.servlet.ServletRequest;
|
|||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
@ -34,16 +33,11 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.solr.client.solrj.impl.HttpClientConfigurer;
|
||||
import org.apache.solr.client.solrj.impl.HttpClientUtil;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.common.SolrException.ErrorCode;
|
||||
import org.apache.solr.common.cloud.SolrZkClient;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.util.ExecutorUtil;
|
||||
import org.apache.solr.core.CoreContainer;
|
||||
import org.apache.solr.core.NodeConfig;
|
||||
|
@ -52,6 +46,7 @@ import org.apache.solr.core.SolrResourceLoader;
|
|||
import org.apache.solr.core.SolrXmlConfig;
|
||||
import org.apache.solr.request.SolrRequestInfo;
|
||||
import org.apache.solr.security.AuthenticationPlugin;
|
||||
import org.apache.solr.security.PKIAuthenticationPlugin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -66,7 +61,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
|
|||
protected volatile CoreContainer cores;
|
||||
|
||||
protected String abortErrorMessage = null;
|
||||
protected final CloseableHttpClient httpClient = HttpClientUtil.createClient(new ModifiableSolrParams());
|
||||
protected HttpClient httpClient;
|
||||
private ArrayList<Pattern> excludePatterns;
|
||||
|
||||
/**
|
||||
|
@ -112,14 +107,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
|
|||
ExecutorUtil.addThreadLocalProvider(SolrRequestInfo.getInheritableThreadLocalProvider());
|
||||
|
||||
this.cores = createCoreContainer(solrHome, extraProperties);
|
||||
|
||||
if (this.cores.getAuthenticationPlugin() != null) {
|
||||
HttpClientConfigurer configurer = this.cores.getAuthenticationPlugin().getDefaultConfigurer();
|
||||
if (configurer != null) {
|
||||
configurer.configure((DefaultHttpClient) httpClient, new ModifiableSolrParams());
|
||||
}
|
||||
}
|
||||
|
||||
this.httpClient = cores.getUpdateShardHandler().getHttpClient();
|
||||
log.info("user.dir=" + System.getProperty("user.dir"));
|
||||
}
|
||||
catch( Throwable t ) {
|
||||
|
@ -180,13 +168,9 @@ public class SolrDispatchFilter extends BaseSolrFilter {
|
|||
|
||||
@Override
|
||||
public void destroy() {
|
||||
try {
|
||||
if (cores != null) {
|
||||
cores.shutdown();
|
||||
cores = null;
|
||||
}
|
||||
} finally {
|
||||
IOUtils.closeQuietly(httpClient);
|
||||
if (cores != null) {
|
||||
cores.shutdown();
|
||||
cores = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +182,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
|
|||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain, boolean retry) throws IOException, ServletException {
|
||||
if (!(request instanceof HttpServletRequest)) return;
|
||||
|
||||
AtomicReference<ServletRequest> wrappedRequest = new AtomicReference();
|
||||
AtomicReference<ServletRequest> wrappedRequest = new AtomicReference<>();
|
||||
if (!authenticateRequest(request, response, wrappedRequest)) { // the response and status code have already been sent
|
||||
return;
|
||||
}
|
||||
|
@ -254,6 +238,10 @@ public class SolrDispatchFilter extends BaseSolrFilter {
|
|||
if (authenticationPlugin == null) {
|
||||
return true;
|
||||
} else {
|
||||
//special case when solr is securing inter-node requests
|
||||
String header = ((HttpServletRequest) request).getHeader(PKIAuthenticationPlugin.HEADER);
|
||||
if (header != null && cores.getPkiAuthenticationPlugin() != null)
|
||||
authenticationPlugin = cores.getPkiAuthenticationPlugin();
|
||||
try {
|
||||
log.debug("Request to authenticate: {}, domain: {}, port: {}", request, request.getLocalName(), request.getLocalPort());
|
||||
// upon successful authentication, this should call the chain's next filter.
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.nio.charset.Charset;
|
|||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -158,7 +159,7 @@ public class SolrRequestParsers
|
|||
// Pick the parser from the request...
|
||||
ArrayList<ContentStream> streams = new ArrayList<>(1);
|
||||
SolrParams params = parser.parseParamsAndFillStreams( req, streams );
|
||||
SolrQueryRequest sreq = buildRequestFrom( core, params, streams, getRequestTimer(req) );
|
||||
SolrQueryRequest sreq = buildRequestFrom(core, params, streams, getRequestTimer(req), req);
|
||||
|
||||
// Handlers and login will want to know the path. If it contains a ':'
|
||||
// the handler could use it for RESTful URLs
|
||||
|
@ -170,14 +171,13 @@ public class SolrRequestParsers
|
|||
}
|
||||
return sreq;
|
||||
}
|
||||
|
||||
public SolrQueryRequest buildRequestFrom( SolrCore core, SolrParams params, Collection<ContentStream> streams ) throws Exception
|
||||
{
|
||||
return buildRequestFrom( core, params, streams, new RTimer() );
|
||||
|
||||
public SolrQueryRequest buildRequestFrom(SolrCore core, SolrParams params, Collection<ContentStream> streams) throws Exception {
|
||||
return buildRequestFrom(core, params, streams, new RTimer(), null);
|
||||
}
|
||||
|
||||
private SolrQueryRequest buildRequestFrom( SolrCore core, SolrParams params, Collection<ContentStream> streams, RTimer requestTimer ) throws Exception
|
||||
{
|
||||
private SolrQueryRequest buildRequestFrom(SolrCore core, SolrParams params, Collection<ContentStream> streams,
|
||||
RTimer requestTimer, final HttpServletRequest req) throws Exception {
|
||||
// The content type will be applied to all streaming content
|
||||
String contentType = params.get( CommonParams.STREAM_CONTENTTYPE );
|
||||
|
||||
|
@ -222,8 +222,13 @@ public class SolrRequestParsers
|
|||
streams.add( stream );
|
||||
}
|
||||
}
|
||||
|
||||
SolrQueryRequestBase q = new SolrQueryRequestBase( core, params, requestTimer ) { };
|
||||
|
||||
SolrQueryRequestBase q = new SolrQueryRequestBase(core, params, requestTimer) {
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return req == null ? null : req.getUserPrincipal();
|
||||
}
|
||||
};
|
||||
if( streams != null && streams.size() > 0 ) {
|
||||
q.setContentStreams( streams );
|
||||
}
|
||||
|
|
|
@ -24,12 +24,16 @@ import javax.crypto.spec.IvParameterSpec;
|
|||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
@ -252,4 +256,91 @@ public final class CryptoKeys {
|
|||
}
|
||||
}
|
||||
|
||||
public static PublicKey deserializeX509PublicKey(String pubKey) {
|
||||
try {
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.base64ToByteArray(pubKey));
|
||||
return keyFactory.generatePublic(publicKeySpec);
|
||||
} catch (Exception e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decryptRSA(byte[] buffer, PublicKey pubKey) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
|
||||
Cipher rsaCipher = null;
|
||||
try {
|
||||
rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
|
||||
} catch (Exception e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
|
||||
}
|
||||
rsaCipher.init(Cipher.DECRYPT_MODE, pubKey);
|
||||
return rsaCipher.doFinal(buffer, 0, buffer.length);
|
||||
|
||||
}
|
||||
|
||||
public static class RSAKeyPair {
|
||||
private final String pubKeyStr;
|
||||
private final PublicKey publicKey;
|
||||
private final PrivateKey privateKey;
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
|
||||
public RSAKeyPair() {
|
||||
KeyPairGenerator keyGen = null;
|
||||
try {
|
||||
keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
|
||||
}
|
||||
keyGen.initialize(512);
|
||||
java.security.KeyPair keyPair = keyGen.genKeyPair();
|
||||
privateKey = keyPair.getPrivate();
|
||||
publicKey = keyPair.getPublic();
|
||||
|
||||
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
|
||||
publicKey.getEncoded());
|
||||
|
||||
pubKeyStr = Base64.byteArrayToBase64(x509EncodedKeySpec.getEncoded());
|
||||
}
|
||||
|
||||
public String getPublicKeyStr() {
|
||||
return pubKeyStr;
|
||||
}
|
||||
|
||||
public byte[] encrypt(ByteBuffer buffer) {
|
||||
try {
|
||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
|
||||
rsaCipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||
return rsaCipher.doFinal(buffer.array(),buffer.position(), buffer.limit());
|
||||
} catch (Exception e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
|
||||
}
|
||||
}
|
||||
public byte[] signSha256(byte[] bytes) throws InvalidKeyException, SignatureException {
|
||||
Signature dsa = null;
|
||||
try {
|
||||
dsa = Signature.getInstance("SHA256withRSA");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
|
||||
}
|
||||
dsa.initSign(privateKey);
|
||||
dsa.update(bytes,0,bytes.length);
|
||||
return dsa.sign();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
RSAKeyPair keyPair = new RSAKeyPair();
|
||||
System.out.println(keyPair.getPublicKeyStr());
|
||||
PublicKey pk = deserializeX509PublicKey(keyPair.getPublicKeyStr());
|
||||
byte[] payload = "Hello World!".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] encrypted = keyPair.encrypt(ByteBuffer.wrap(payload));
|
||||
String cipherBase64 = Base64.byteArrayToBase64(encrypted);
|
||||
System.out.println("encrypted: "+ cipherBase64);
|
||||
System.out.println("signed: "+ Base64.byteArrayToBase64(keyPair.signSha256(payload)));
|
||||
System.out.println("decrypted "+ new String(decryptRSA(encrypted , pk), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.apache.lucene.util.LuceneTestCase.SuppressSysoutChecks;
|
|||
import org.apache.solr.client.solrj.impl.HttpClientConfigurer;
|
||||
import org.apache.solr.common.params.SolrParams;
|
||||
import org.apache.solr.security.AuthenticationPlugin;
|
||||
import org.apache.solr.security.HttpClientInterceptorPlugin;
|
||||
import org.apache.solr.util.RevertDefaultThreadHandlerRule;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
|
@ -110,7 +111,7 @@ public class TestAuthenticationFramework extends TestMiniSolrCloudCluster {
|
|||
super.tearDown();
|
||||
}
|
||||
|
||||
public static class MockAuthenticationPlugin extends AuthenticationPlugin {
|
||||
public static class MockAuthenticationPlugin extends AuthenticationPlugin implements HttpClientInterceptorPlugin {
|
||||
private static Logger log = LoggerFactory.getLogger(MockAuthenticationPlugin.class);
|
||||
|
||||
public static String expectedUsername = "solr";
|
||||
|
@ -135,7 +136,7 @@ public class TestAuthenticationFramework extends TestMiniSolrCloudCluster {
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpClientConfigurer getDefaultConfigurer() {
|
||||
public HttpClientConfigurer getClientConfigurer() {
|
||||
return new MockClientConfigurer();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package org.apache.solr.security;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class MockAuthenticationPlugin extends AuthenticationPlugin {
|
||||
static Predicate<ServletRequest> predicate;
|
||||
|
||||
@Override
|
||||
public void init(Map<String, Object> pluginConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAuthenticate(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
|
||||
String user = null;
|
||||
if (predicate != null) {
|
||||
if (predicate.test(request)) {
|
||||
user = (String) request.getAttribute(Principal.class.getName());
|
||||
request.removeAttribute(Principal.class.getName());
|
||||
}
|
||||
}
|
||||
forward(user, request, response, filterChain);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
|
||||
}
|
||||
}
|
|
@ -20,19 +20,34 @@ package org.apache.solr.security;
|
|||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MockAuthorizationPlugin implements AuthorizationPlugin{
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(MockAuthorizationPlugin.class);
|
||||
HashSet<String> denyUsers;
|
||||
|
||||
static final HashSet<String> denyUsers = new HashSet<>();
|
||||
static Predicate<AuthorizationContext> predicate;
|
||||
|
||||
@Override
|
||||
public AuthorizationResponse authorize(AuthorizationContext context) {
|
||||
log.info("User request: " + context.getParams().get("uname"));
|
||||
if(denyUsers.contains(context.getParams().get("uname")))
|
||||
String uname = context.getUserPrincipal()== null? null : context.getUserPrincipal().getName();
|
||||
if(predicate != null){
|
||||
try {
|
||||
predicate.test(context);
|
||||
return new AuthorizationResponse(200);
|
||||
} catch (SolrException e) {
|
||||
return new AuthorizationResponse(e.code());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(uname == null) uname = context.getParams().get("uname");
|
||||
log.info("User request: " + uname);
|
||||
if(denyUsers.contains(uname))
|
||||
return new AuthorizationResponse(403);
|
||||
else
|
||||
return new AuthorizationResponse(200);
|
||||
|
@ -40,9 +55,6 @@ public class MockAuthorizationPlugin implements AuthorizationPlugin{
|
|||
|
||||
@Override
|
||||
public void init(Map<String, Object> initInfo) {
|
||||
denyUsers = new HashSet();
|
||||
denyUsers.add("user1");
|
||||
denyUsers.add("user2");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
package org.apache.solr.security;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.security.Principal;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.client.solrj.request.QueryRequest;
|
||||
import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
|
||||
import org.apache.solr.common.cloud.ZkStateReader;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.common.util.Utils;
|
||||
import org.apache.solr.request.LocalSolrQueryRequest;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.apache.solr.common.util.Utils.makeMap;
|
||||
|
||||
@SolrTestCaseJ4.SuppressSSL
|
||||
public class PKIAuthenticationIntegrationTest extends AbstractFullDistribZkTestBase {
|
||||
final private Logger log = LoggerFactory.getLogger(PKIAuthenticationIntegrationTest.class);
|
||||
|
||||
static final int TIMEOUT = 10000;
|
||||
|
||||
public void distribSetUp() throws Exception {
|
||||
super.distribSetUp();
|
||||
|
||||
byte[] bytes = Utils.toJSON(makeMap("authorization", singletonMap("class", MockAuthorizationPlugin.class.getName()),
|
||||
"authentication", singletonMap("class", MockAuthenticationPlugin.class.getName())));
|
||||
|
||||
try (ZkStateReader zkStateReader = new ZkStateReader(zkServer.getZkAddress(),
|
||||
TIMEOUT, TIMEOUT)) {
|
||||
zkStateReader.getZkClient().create(ZkStateReader.SOLR_SECURITY_CONF_PATH, bytes, CreateMode.PERSISTENT, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPkiAuth() throws Exception {
|
||||
waitForThingsToLevelOut(10);
|
||||
log.info("Starting test");
|
||||
ModifiableSolrParams params = new ModifiableSolrParams();
|
||||
params.add("q", "*:*");
|
||||
params.add("__user", "solr");
|
||||
params.add("__pwd", "SolrRocks");
|
||||
// This should work fine.
|
||||
final AtomicInteger count = new AtomicInteger();
|
||||
|
||||
|
||||
MockAuthorizationPlugin.predicate = new Predicate<AuthorizationContext>() {
|
||||
@Override
|
||||
public boolean test(AuthorizationContext context) {
|
||||
if ("/select".equals(context.getResource())) {
|
||||
Principal principal = context.getUserPrincipal();
|
||||
log.info("principalIs : {}", principal);
|
||||
if (principal != null && principal.getName().equals("solr")) {
|
||||
count.incrementAndGet();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
MockAuthenticationPlugin.predicate = new Predicate<ServletRequest>() {
|
||||
@Override
|
||||
public boolean test(ServletRequest servletRequest) {
|
||||
String s = ((HttpServletRequest) servletRequest).getQueryString();
|
||||
if (s != null && s.contains("__user=solr") && s.contains("__pwd=SolrRocks")) {
|
||||
servletRequest.setAttribute(Principal.class.getName(), "solr");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
QueryRequest query = new QueryRequest(params);
|
||||
LocalSolrQueryRequest lsqr = new LocalSolrQueryRequest(null, new ModifiableSolrParams()) {
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
query.process(cloudClient);
|
||||
log.info("count :{}", count);
|
||||
assertTrue(count.get() > 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void distribTearDown() throws Exception {
|
||||
super.distribTearDown();
|
||||
MockAuthenticationPlugin.predicate = null;
|
||||
MockAuthorizationPlugin.predicate = null;
|
||||
}
|
||||
|
||||
}
|
|
@ -45,6 +45,8 @@ public class TestAuthorizationFramework extends AbstractFullDistribZkTestBase {
|
|||
|
||||
@Test
|
||||
public void authorizationFrameworkTest() throws Exception {
|
||||
MockAuthorizationPlugin.denyUsers.add("user1");
|
||||
MockAuthorizationPlugin.denyUsers.add("user1");
|
||||
waitForThingsToLevelOut(10);
|
||||
log.info("Starting test");
|
||||
ModifiableSolrParams params = new ModifiableSolrParams();
|
||||
|
@ -60,4 +62,11 @@ public class TestAuthorizationFramework extends AbstractFullDistribZkTestBase {
|
|||
} catch (Exception e) {}
|
||||
log.info("Ending test");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void distribTearDown() throws Exception {
|
||||
super.distribTearDown();
|
||||
MockAuthorizationPlugin.denyUsers.clear();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
package org.apache.solr.security;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.auth.BasicUserPrincipal;
|
||||
import org.apache.http.message.BasicHttpRequest;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.common.params.ModifiableSolrParams;
|
||||
import org.apache.solr.core.CoreContainer;
|
||||
import org.apache.solr.request.LocalSolrQueryRequest;
|
||||
import org.apache.solr.request.SolrRequestInfo;
|
||||
import org.apache.solr.response.SolrQueryResponse;
|
||||
import org.apache.solr.util.CryptoKeys;
|
||||
import org.easymock.EasyMock;
|
||||
import org.easymock.IAnswer;
|
||||
|
||||
import static org.easymock.EasyMock.getCurrentArguments;
|
||||
|
||||
public class TestPKIAuthenticationPlugin extends SolrTestCaseJ4 {
|
||||
|
||||
static class MockPKIAuthenticationPlugin extends PKIAuthenticationPlugin {
|
||||
SolrRequestInfo solrRequestInfo;
|
||||
|
||||
Map<String, PublicKey> remoteKeys = new HashMap<>();
|
||||
|
||||
public MockPKIAuthenticationPlugin(CoreContainer cores, String node) {
|
||||
super(cores, node);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
SolrRequestInfo getRequestInfo() {
|
||||
return solrRequestInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
PublicKey getRemotePublicKey(String nodename) {
|
||||
return remoteKeys.get(nodename);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isSolrThread() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void test() throws Exception {
|
||||
AtomicReference<Principal> principal = new AtomicReference<>();
|
||||
String nodeName = "node_x_233";
|
||||
|
||||
final MockPKIAuthenticationPlugin mock = new MockPKIAuthenticationPlugin(null, nodeName);
|
||||
LocalSolrQueryRequest localSolrQueryRequest = new LocalSolrQueryRequest(null, new ModifiableSolrParams()) {
|
||||
@Override
|
||||
public Principal getUserPrincipal() {
|
||||
return principal.get();
|
||||
}
|
||||
};
|
||||
mock.remoteKeys.put(nodeName, CryptoKeys.deserializeX509PublicKey(mock.getPublicKey()));
|
||||
principal.set(new BasicUserPrincipal("solr"));
|
||||
mock.solrRequestInfo = new SolrRequestInfo(localSolrQueryRequest, new SolrQueryResponse());
|
||||
BasicHttpRequest request = new BasicHttpRequest("GET", "http://localhost:56565");
|
||||
mock.setHeader(request);
|
||||
final AtomicReference<Header> header = new AtomicReference<>();
|
||||
header.set(request.getFirstHeader(PKIAuthenticationPlugin.HEADER));
|
||||
assertNotNull(header.get());
|
||||
assertTrue(header.get().getValue().startsWith(nodeName));
|
||||
final AtomicReference<ServletRequest> wrappedRequestByFilter = new AtomicReference<>();
|
||||
HttpServletRequest mockReq = createMockRequest(header);
|
||||
FilterChain filterChain = new FilterChain() {
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse)
|
||||
throws IOException, ServletException {
|
||||
wrappedRequestByFilter.set(servletRequest);
|
||||
}
|
||||
};
|
||||
mock.doAuthenticate(mockReq, null, filterChain);
|
||||
|
||||
assertNotNull(wrappedRequestByFilter.get());
|
||||
assertEquals("solr", ((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal().getName());
|
||||
|
||||
//test 2
|
||||
principal.set(null); // no user
|
||||
header.set(null);
|
||||
wrappedRequestByFilter.set(null);//
|
||||
request = new BasicHttpRequest("GET", "http://localhost:56565");
|
||||
mock.setHeader(request);
|
||||
assertNull(request.getFirstHeader(PKIAuthenticationPlugin.HEADER));
|
||||
mock.doAuthenticate(mockReq, null, filterChain);
|
||||
assertNotNull(wrappedRequestByFilter.get());
|
||||
assertNull(((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal());
|
||||
|
||||
//test 3 . No user request . Request originated from Solr
|
||||
mock.solrRequestInfo = null;
|
||||
header.set(null);
|
||||
wrappedRequestByFilter.set(null);
|
||||
request = new BasicHttpRequest("GET", "http://localhost:56565");
|
||||
mock.setHeader(request);
|
||||
header.set(request.getFirstHeader(PKIAuthenticationPlugin.HEADER));
|
||||
assertNotNull(header.get());
|
||||
assertTrue(header.get().getValue().startsWith(nodeName));
|
||||
|
||||
mock.doAuthenticate(mockReq, null, filterChain);
|
||||
assertNotNull(wrappedRequestByFilter.get());
|
||||
assertEquals("$", ((HttpServletRequest) wrappedRequestByFilter.get()).getUserPrincipal().getName());
|
||||
|
||||
}
|
||||
|
||||
private HttpServletRequest createMockRequest(final AtomicReference<Header> header) {
|
||||
HttpServletRequest mockReq = EasyMock.createMock(HttpServletRequest.class);
|
||||
EasyMock.reset(mockReq);
|
||||
mockReq.getHeader(EasyMock.anyObject(String.class));
|
||||
EasyMock.expectLastCall().andAnswer(new IAnswer<String>() {
|
||||
@Override
|
||||
public String answer() throws Throwable {
|
||||
if (PKIAuthenticationPlugin.HEADER.equals(getCurrentArguments()[0])) {
|
||||
if (header.get() == null) return null;
|
||||
return header.get().getValue();
|
||||
} else return null;
|
||||
}
|
||||
}).anyTimes();
|
||||
mockReq.getUserPrincipal();
|
||||
EasyMock.expectLastCall().andAnswer(new IAnswer<Principal>() {
|
||||
@Override
|
||||
public Principal answer() throws Throwable {
|
||||
return null;
|
||||
}
|
||||
}).anyTimes();
|
||||
|
||||
mockReq.getRequestURI();
|
||||
EasyMock.expectLastCall().andAnswer(new IAnswer<String>() {
|
||||
@Override
|
||||
public String answer() throws Throwable {
|
||||
return "/collection1/select";
|
||||
}
|
||||
}).anyTimes();
|
||||
|
||||
EasyMock.replay(mockReq);
|
||||
return mockReq;
|
||||
}
|
||||
}
|
|
@ -52,6 +52,10 @@ public class Base64 {
|
|||
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||
};
|
||||
|
||||
public static String byteArrayToBase64(byte[] a) {
|
||||
return byteArrayToBase64(a, 0, a.length);
|
||||
}
|
||||
|
||||
public static String byteArrayToBase64(byte[] a, int offset, int len) {
|
||||
int aLen = len;
|
||||
int numFullGroups = aLen / 3;
|
||||
|
|
|
@ -193,6 +193,7 @@ public class ExecutorUtil {
|
|||
super.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
isServerPool.set(Boolean.TRUE);
|
||||
if (ctx != null) {
|
||||
for (int i = 0; i < providersCopy.size(); i++) providersCopy.get(i).set(ctx.get(i));
|
||||
}
|
||||
|
@ -214,6 +215,7 @@ public class ExecutorUtil {
|
|||
log.error("Uncaught exception {} thrown by thread: {}", t, currentThread.getName(), submitterStackTrace);
|
||||
throw t;
|
||||
} finally {
|
||||
isServerPool.remove();
|
||||
if (threadContext != null && !threadContext.isEmpty()) {
|
||||
MDC.setContextMap(threadContext);
|
||||
} else {
|
||||
|
@ -228,4 +230,11 @@ public class ExecutorUtil {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static final ThreadLocal<Boolean> isServerPool = new ThreadLocal<>();
|
||||
|
||||
public static boolean isSolrServerThread() {
|
||||
return Boolean.TRUE.equals(isServerPool.get());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue