Skip to content

Commit

Permalink
SOLR-14569: Configuring a shardHandlerFactory on the /select requestH…
Browse files Browse the repository at this point in the history
…andler results in HTTP 401 when searching on alias in secured Solr. (apache#574)

Conflicts:
	.gitignore
	solr/core/src/java/org/apache/solr/core/CoreContainer.java
  • Loading branch information
markrmiller committed Feb 8, 2022
1 parent 9a11380 commit d2de09d
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 19 deletions.
1 change: 1 addition & 0 deletions .stignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
3 changes: 3 additions & 0 deletions solr/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ Bug Fixes
* SOLR-15587: Don't use the UrlScheme singleton on the client-side to determine cluster's scheme,
only get from ClusterState provider impl (Timothy Potter)

* SOLR-14569: Configuring a shardHandlerFactory on the /select requestHandler results in HTTP 401 when searching on alias
in secured Solr. (Isabelle Giguere, Jason Gerlowski, Anshum Gupta, Mark Mark Miller)

================== 8.11.1 ==================

Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
Expand Down
39 changes: 24 additions & 15 deletions solr/core/src/java/org/apache/solr/core/CoreContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ public CoreLoadFailure(CoreDescriptor cd, Exception loadFailure) {
private volatile InfoHandler infoHandler;
protected volatile ConfigSetsHandler configSetsHandler = null;

private volatile PKIAuthenticationPlugin pkiAuthenticationPlugin;
private volatile PKIAuthenticationPlugin pkiAuthenticationSecurityBuilder;

protected volatile Properties containerProperties;

Expand Down Expand Up @@ -516,13 +516,19 @@ private void setupHttpClientForAuthPlugin(Object authcPlugin) {
// Setup HttpClient for internode communication
HttpClientBuilderPlugin builderPlugin = ((HttpClientBuilderPlugin) authcPlugin);
SolrHttpClientBuilder builder = builderPlugin.getHttpClientBuilder(HttpClientUtil.getHttpClientBuilder());
shardHandlerFactory.setSecurityBuilder(builderPlugin);
updateShardHandler.setSecurityBuilder(builderPlugin);

// The default http client of the core container's shardHandlerFactory has already been created and
// configured using the default httpclient configurer. We need to reconfigure it using the plugin's
// http client configurer to set it up for internode communication.
log.debug("Reconfiguring HttpClient settings.");

// this caused plugins like KerberosPlugin to register it's intercepts, but this intercept logic is also
// handled by the pki authentication code when it decideds to let the plugin handle auth via it's intercept
// - so you would end up with two intercepts
// -->
// shardHandlerFactory.setSecurityBuilder(builderPlugin); // calls setup for the authcPlugin
// updateShardHandler.setSecurityBuilder(builderPlugin);
// <--

// This should not happen here at all - it's only currently required due to its affect on http1 clients
// in a test or two incorrectly counting on it for their configuration.
// -->

SolrHttpClientContextBuilder httpClientBuilder = new SolrHttpClientContextBuilder();
if (builder.getCredentialsProviderProvider() != null) {
Expand All @@ -545,13 +551,16 @@ public Lookup<AuthSchemeProvider> getAuthSchemeRegistry() {
}

HttpClientUtil.setHttpClientRequestContextBuilder(httpClientBuilder);

// <--
}

// Always register PKI auth interceptor, which will then delegate the decision of who should secure
// each request to the configured authentication plugin.
if (pkiAuthenticationPlugin != null && !pkiAuthenticationPlugin.isInterceptorRegistered()) {
pkiAuthenticationPlugin.getHttpClientBuilder(HttpClientUtil.getHttpClientBuilder());
shardHandlerFactory.setSecurityBuilder(pkiAuthenticationPlugin);
updateShardHandler.setSecurityBuilder(pkiAuthenticationPlugin);
if (pkiAuthenticationSecurityBuilder != null && !pkiAuthenticationSecurityBuilder.isInterceptorRegistered()) {
pkiAuthenticationSecurityBuilder.getHttpClientBuilder(HttpClientUtil.getHttpClientBuilder());
shardHandlerFactory.setSecurityBuilder(pkiAuthenticationSecurityBuilder);
updateShardHandler.setSecurityBuilder(pkiAuthenticationSecurityBuilder);
}
}

Expand Down Expand Up @@ -606,8 +615,8 @@ public Properties getContainerProperties() {
return containerProperties;
}

public PKIAuthenticationPlugin getPkiAuthenticationPlugin() {
return pkiAuthenticationPlugin;
public PKIAuthenticationPlugin getPkiAuthenticationSecurityBuilder() {
return pkiAuthenticationSecurityBuilder;
}

public SolrMetricManager getMetricManager() {
Expand Down Expand Up @@ -716,10 +725,10 @@ public void load() {

zkSys.initZooKeeper(this, cfg.getCloudConfig());
if (isZooKeeperAware()) {
pkiAuthenticationPlugin = new PKIAuthenticationPlugin(this, zkSys.getZkController().getNodeName(),
pkiAuthenticationSecurityBuilder = new PKIAuthenticationPlugin(this, zkSys.getZkController().getNodeName(),
(PublicKeyHandler) containerHandlers.get(PublicKeyHandler.PATH));
// use deprecated API for back-compat, remove in 9.0
pkiAuthenticationPlugin.initializeMetrics(
pkiAuthenticationSecurityBuilder.initializeMetrics(
solrMetricsContext.metricManager, solrMetricsContext.registry, solrMetricsContext.tag, "/authentication/pki");
TracerConfigurator.loadTracer(loader, cfg.getTracerConfiguratorPluginInfo(), getZkController().getZkStateReader());
packageLoader = new PackageLoader(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public void preClose(SolrCore core) {
public void postClose(SolrCore core) {
}
});
shardHandlerFactory.setSecurityBuilder(core.getCoreContainer().getPkiAuthenticationSecurityBuilder());
}

if (core.getCoreContainer().isZooKeeperAware()) {
Expand Down
4 changes: 2 additions & 2 deletions solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -618,8 +618,8 @@ private boolean shouldAuthorize() {
if(PublicKeyHandler.PATH.equals(path)) return false;
//admin/info/key is the path where public key is exposed . it is always unsecured
if ("/".equals(path) || "/solr/".equals(path)) return false; // Static Admin UI files must always be served
if (cores.getPkiAuthenticationPlugin() != null && req.getUserPrincipal() != null) {
boolean b = cores.getPkiAuthenticationPlugin().needsAuthorization(req);
if (cores.getPkiAuthenticationSecurityBuilder() != null && req.getUserPrincipal() != null) {
boolean b = cores.getPkiAuthenticationSecurityBuilder().needsAuthorization(req);
log.debug("PkiAuthenticationPlugin says authorization required : {} ", b);
return b;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,8 +508,8 @@ private boolean authenticateRequest(HttpServletRequest request, HttpServletRespo
return true;
}
String header = request.getHeader(PKIAuthenticationPlugin.HEADER);
if (header != null && cores.getPkiAuthenticationPlugin() != null)
authenticationPlugin = cores.getPkiAuthenticationPlugin();
if (header != null && cores.getPkiAuthenticationSecurityBuilder() != null)
authenticationPlugin = cores.getPkiAuthenticationSecurityBuilder();
try {
if (log.isDebugEnabled()) {
log.debug("Request to authenticate: {}, domain: {}, port: {}", request, request.getLocalName(), request.getLocalPort());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
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.
-->
<schema name="minimal" version="1.1">
<fieldType name="string" class="solr.StrField"/>
<fieldType name="int" class="${solr.tests.IntegerFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<dynamicField name="*" type="string" indexed="true" stored="true"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<dynamicField name="*_s" type="string" indexed="true" stored="true" />
<uniqueKey>id</uniqueKey>
</schema>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.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.
-->

<!-- Minimal solrconfig.xml with /select, /admin and /update only -->

<config>

<dataDir>${solr.data.dir:}</dataDir>

<directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<schemaFactory class="ClassicIndexSchemaFactory"/>

<luceneMatchVersion>${tests.luceneMatchVersion:LATEST}</luceneMatchVersion>

<updateHandler class="solr.DirectUpdateHandler2">
<commitWithin>
<softCommit>${solr.commitwithin.softcommit:true}</softCommit>
</commitWithin>
<updateLog class="${solr.ulog:solr.UpdateLog}"></updateLog>
</updateHandler>

<requestHandler name="/select" class="solr.SearchHandler">
<shardHandlerFactory class="HttpShardHandlerFactory">
<int name="socketTimeOut">1000</int>
<int name="connTimeOut">5000</int>
<int name="corePoolSize">5</int>
</shardHandlerFactory>
<lst name="defaults">
<str name="echoParams">explicit</str>
<str name="indent">true</str>
<str name="df">text</str>
</lst>
</requestHandler>

<indexConfig>
<mergeScheduler class="${solr.mscheduler:org.apache.lucene.index.ConcurrentMergeScheduler}"/>
: </indexConfig>
</config>

Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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 org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.Http2SolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.cloud.SolrCloudAuthTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
* Similar to BasicAuthOnSingleNodeTest, but using a different configset, to test SOLR-14569
*/
public class AuthWithShardHandlerFactoryOverrideTest extends SolrCloudAuthTestCase {

private static final String COLLECTION = "authCollection";
private static final String ALIAS = "alias";

private static final String SECURITY_CONF = "{\n" +
" \"authentication\":{\n" +
" \"blockUnknown\": true,\n" +
" \"class\":\"solr.BasicAuthPlugin\",\n" +
" \"credentials\":{\"solr\":\"EEKn7ywYk5jY8vG9TyqlG2jvYuvh1Q7kCCor6Hqm320= 6zkmjMjkMKyJX6/f0VarEWQujju5BzxZXub6WOrEKCw=\"}\n" +
" },\n" +
" \"authorization\":{\n" +
" \"class\":\"solr.RuleBasedAuthorizationPlugin\",\n" +
" \"permissions\":[\n" +
" {\"name\":\"all\", \"role\":\"admin\"}\n" +
" ],\n" +
" \"user-role\":{\"solr\":\"admin\"}\n" +
" }\n" +
"}";

@Before
public void setupCluster() throws Exception {
configureCluster(1)
.addConfig("conf", configset("cloud-minimal_override-shardHandler"))
.withSecurityJson(SECURITY_CONF)
.configure();
CollectionAdminRequest.createCollection(COLLECTION, "conf", 4, 1)
.setMaxShardsPerNode(100)
.setBasicAuthCredentials("solr", "solr")
.process(cluster.getSolrClient());

CollectionAdminRequest.createAlias(ALIAS, COLLECTION)
.setBasicAuthCredentials("solr", "solr")
.process(cluster.getSolrClient());

cluster.waitForActiveCollection(COLLECTION, 4, 4);

JettySolrRunner jetty = cluster.getJettySolrRunner(0);
jetty.stop();
cluster.waitForJettyToStop(jetty);
jetty.start();
cluster.waitForAllNodes(30);
cluster.waitForActiveCollection(COLLECTION, 4, 4);
}

@Override
@After
public void tearDown() throws Exception {
cluster.shutdown();
super.tearDown();
}

@Test
public void collectionTest() throws Exception {
try (Http2SolrClient client = new Http2SolrClient.Builder(cluster.getJettySolrRunner(0).getBaseUrl().toString())
.build()){

for (int i = 0; i < 30; i++) {
SolrResponse response = new QueryRequest(params("q", "*:*"))
.setBasicAuthCredentials("solr", "solr").process(client, COLLECTION);
// likely to be non-null, even if an error occurred
assertNotNull(response);
assertNotNull(response.getResponse());
// now we know we have results
assertNotNull(response.getResponse().get("response"));
}
}
}

@Test
public void aliasTest() throws Exception {
try (Http2SolrClient client = new Http2SolrClient.Builder(cluster.getJettySolrRunner(0).getBaseUrl().toString())
.build()){

for (int i = 0; i < 30; i++) {
SolrResponse response = new QueryRequest(params("q", "*:*"))
.setBasicAuthCredentials("solr", "solr").process(client, ALIAS);
// likely to be non-null, even if an error occurred
assertNotNull(response);
assertNotNull(response.getResponse());
// now we know we have results
assertNotNull(response.getResponse().get("response"));
}
}
}
}

0 comments on commit d2de09d

Please sign in to comment.