Skip to content

Commit

Permalink
SOLR-16777: Make SchemaDesigner use ConfigSet trust
Browse files Browse the repository at this point in the history
(cherry picked from commit 35d3525)
  • Loading branch information
HoustonPutman committed Jul 13, 2023
1 parent 1e7a35a commit d07751c
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 54 deletions.
2 changes: 1 addition & 1 deletion solr/CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ Bug Fixes

* SOLR-13605: Fix setting client scoped socket and connect timeouts when using HttpSolrClient.Builder.withHttpClient() method. (Eric Pugh, Alex Deparvu)

* SOLR-16777: Fix for Schema Designer blindly trusting potentially malicious configsets (Ishan Chattopadhyaya, Skay)
* SOLR-16777: Schema Designer now correctly manages trust of the ConfigSets it is managing. (Ishan Chattopadhyaya, Skay, Houston Putman)

* SOLR-16771: Fixed behavior and handling of 'unset' logging levels in /admin/info/logging API and related Admin UI (hossman)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ public SolrResourceLoader createCoreResourceLoader(CoreDescriptor cd) {
}

@Override
protected NamedList<Object> loadConfigSetFlags(CoreDescriptor cd, SolrResourceLoader loader)
throws IOException {
protected NamedList<Object> loadConfigSetFlags(SolrResourceLoader loader) throws IOException {
try {
// ConfigSet flags are loaded from the metadata of the ZK node of the configset.
return ConfigSetProperties.readFromResourceLoader(loader, ".");
} catch (Exception ex) {
log.debug("No configSet flags", ex);
Expand Down
42 changes: 28 additions & 14 deletions solr/core/src/java/org/apache/solr/core/ConfigSetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import java.util.regex.Pattern;
import org.apache.solr.cloud.ZkConfigSetService;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.ZkSolrResourceLoader;
import org.apache.solr.common.ConfigNode;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
Expand Down Expand Up @@ -220,6 +219,31 @@ public static void bootstrapConf(CoreContainer cc) throws IOException {
}
}

/**
* Return whether the given configSet is trusted.
*
* @param name name of the configSet
*/
public boolean isConfigSetTrusted(String name) throws IOException {
Map<String, Object> contentMap = getConfigMetadata(name);
return (boolean) contentMap.getOrDefault("trusted", true);
}

/**
* Return whether the configSet used for the given resourceLoader is trusted.
*
* @param coreLoader resourceLoader for a core
*/
public boolean isConfigSetTrusted(SolrResourceLoader coreLoader) throws IOException {
// ConfigSet flags are loaded from the metadata of the ZK node of the configset. (For the
// ZKConfigSetService)
NamedList<?> flags = loadConfigSetFlags(coreLoader);

// Trust if there is no trusted flag (i.e. the ConfigSetApi was not used for this configSet)
// or if the trusted flag is set to "true".
return (flags == null || flags.get("trusted") == null || flags.getBooleanArg("trusted"));
}

/**
* Load the ConfigSet for a core
*
Expand All @@ -233,16 +257,7 @@ public final ConfigSet loadConfigSet(CoreDescriptor dcore) {
try {
// ConfigSet properties are loaded from ConfigSetProperties.DEFAULT_FILENAME file.
NamedList<?> properties = loadConfigSetProperties(dcore, coreLoader);
// ConfigSet flags are loaded from the metadata of the ZK node of the configset.
NamedList<?> flags = loadConfigSetFlags(dcore, coreLoader);

boolean trusted =
(coreLoader instanceof ZkSolrResourceLoader
&& flags != null
&& flags.get("trusted") != null
&& !flags.getBooleanArg("trusted"))
? false
: true;
boolean trusted = isConfigSetTrusted(coreLoader);

SolrConfig solrConfig = createSolrConfig(dcore, coreLoader, trusted);
return new ConfigSet(
Expand Down Expand Up @@ -290,7 +305,7 @@ public ConfigSetService(SolrResourceLoader loader, boolean shareSchema) {
* @return a SolrConfig object
*/
protected SolrConfig createSolrConfig(
CoreDescriptor cd, SolrResourceLoader loader, boolean isTrusted) {
CoreDescriptor cd, SolrResourceLoader loader, boolean isTrusted) throws IOException {
return SolrConfig.readFromResourceLoader(
loader, cd.getConfigName(), isTrusted, cd.getSubstitutableProperties());
}
Expand Down Expand Up @@ -364,8 +379,7 @@ protected NamedList<Object> loadConfigSetProperties(CoreDescriptor cd, SolrResou

/** Return the ConfigSet flags or null if none. */
// TODO should fold into configSetProps -- SOLR-14059
protected NamedList<Object> loadConfigSetFlags(CoreDescriptor cd, SolrResourceLoader loader)
throws IOException {
protected NamedList<Object> loadConfigSetFlags(SolrResourceLoader loader) throws IOException {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ protected InputStream ensureNonEmptyInputStream(SolrQueryRequest req) throws IOE
return contentStreamsIterator.next().getStream();
}

protected boolean isTrusted(Principal userPrincipal, AuthenticationPlugin authPlugin) {
public static boolean isTrusted(Principal userPrincipal, AuthenticationPlugin authPlugin) {
if (authPlugin != null && userPrincipal != null) {
log.debug("Trusted configset request");
return true;
Expand All @@ -118,11 +118,6 @@ protected boolean isTrusted(Principal userPrincipal, AuthenticationPlugin authPl
return false;
}

protected boolean isCurrentlyTrusted(String configName) throws IOException {
Map<String, Object> contentMap = configSetService.getConfigMetadata(configName);
return (boolean) contentMap.getOrDefault("trusted", true);
}

protected void createBaseNode(
ConfigSetService configSetService,
boolean overwritesExisting,
Expand All @@ -146,7 +141,7 @@ protected void createBaseNode(
* Fail if an untrusted request tries to update a trusted ConfigSet
*/
private void ensureOverwritingUntrustedConfigSet(String configName) throws IOException {
boolean isCurrentlyTrusted = isCurrentlyTrusted(configName);
boolean isCurrentlyTrusted = configSetService.isConfigSetTrusted(configName);
if (isCurrentlyTrusted) {
throw new SolrException(
SolrException.ErrorCode.BAD_REQUEST,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public void create(PayloadObj<CreateConfigPayload> obj) throws Exception {

if (!DISABLE_CREATE_AUTH_CHECKS
&& !isTrusted(obj.getRequest().getUserPrincipal(), coreContainer.getAuthenticationPlugin())
&& isCurrentlyTrusted(createConfigPayload.baseConfigSet)) {
&& configSetService.isConfigSetTrusted(createConfigPayload.baseConfigSet)) {
throw new SolrException(
SolrException.ErrorCode.UNAUTHORIZED,
"Can't create a configset with an unauthenticated request from a trusted "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ public void uploadConfigSet(SolrQueryRequest req, SolrQueryResponse rsp) throws

// If the request is doing a full trusted overwrite of an untrusted configSet (overwrite=true,
// cleanup=true), then trust the configSet.
if (cleanup && requestIsTrusted && overwritesExisting && !isCurrentlyTrusted(configSetName)) {
if (cleanup
&& requestIsTrusted
&& overwritesExisting
&& !configSetService.isConfigSetTrusted(configSetName)) {
Map<String, Object> metadata = Collections.singletonMap("trusted", true);
configSetService.setConfigMetadata(configSetName, metadata);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.handler.configsets.ConfigSetAPIBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.RawResponseWriter;
import org.apache.solr.response.SolrQueryResponse;
Expand Down Expand Up @@ -113,9 +114,9 @@ public SchemaDesignerAPI(CoreContainer coreContainer) {
this.coreContainer = coreContainer;
this.schemaSuggester = schemaSuggester;
this.sampleDocLoader = sampleDocLoader;
this.settingsDAO = new SchemaDesignerSettingsDAO(coreContainer);
this.configSetHelper =
new SchemaDesignerConfigSetHelper(this.coreContainer, this.schemaSuggester);
this.settingsDAO = new SchemaDesignerSettingsDAO(coreContainer, configSetHelper);
}

public static SchemaSuggester newSchemaSuggester(CoreContainer coreContainer) {
Expand Down Expand Up @@ -244,13 +245,15 @@ public void updateFileContents(SolrQueryRequest req, SolrQueryResponse rsp)
DefaultSampleDocumentsLoader.streamAsBytes(
extractSingleContentStream(req, true).getStream());
Exception updateFileError = null;
boolean requestIsTrusted =
ConfigSetAPIBase.isTrusted(req.getUserPrincipal(), coreContainer.getAuthenticationPlugin());
if (SOLR_CONFIG_XML.equals(file)) {
// verify the updated solrconfig.xml is valid before saving to ZK (to avoid things blowing up
// later)
try {
InMemoryResourceLoader loader =
new InMemoryResourceLoader(coreContainer, mutableId, SOLR_CONFIG_XML, data);
SolrConfig.readFromResourceLoader(loader, SOLR_CONFIG_XML, true, null);
SolrConfig.readFromResourceLoader(loader, SOLR_CONFIG_XML, requestIsTrusted, null);
} catch (Exception exc) {
updateFileError = exc;
}
Expand All @@ -275,6 +278,11 @@ public void updateFileContents(SolrQueryRequest req, SolrQueryResponse rsp)
throw new IOException(
"Failed to save data in ZK at path: " + zkPath, SolrZkClient.checkInterrupted(e));
}
// If the request is untrusted, and the configSet is trusted, remove the trusted flag on the
// configSet.
if (configSetHelper.isConfigSetTrusted(mutableId) && !requestIsTrusted) {
configSetHelper.removeConfigSetTrust(mutableId);
}

configSetHelper.reloadTempCollection(mutableId, false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -685,14 +685,10 @@ ManagedIndexSchema deleteNestedDocsFieldsIfNeeded(ManagedIndexSchema schema, boo
}

SolrConfig loadSolrConfig(String configSet) {
SolrResourceLoader resourceLoader = cc.getResourceLoader();
ZkSolrResourceLoader zkLoader =
new ZkSolrResourceLoader(
resourceLoader.getInstancePath(),
configSet,
resourceLoader.getClassLoader(),
cc.getZkController());
return SolrConfig.readFromResourceLoader(zkLoader, SOLR_CONFIG_XML, false, null);
ZkSolrResourceLoader zkLoader = zkLoaderForConfigSet(configSet);
boolean trusted = isConfigSetTrusted(configSet);

return SolrConfig.readFromResourceLoader(zkLoader, SOLR_CONFIG_XML, trusted, null);
}

ManagedIndexSchema loadLatestSchema(String configSet) {
Expand Down Expand Up @@ -1221,4 +1217,33 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
}
return baos.toByteArray();
}

public boolean isConfigSetTrusted(String configSetName) {
try {
return cc.getConfigSetService().isConfigSetTrusted(configSetName);
} catch (IOException e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Could not load conf " + configSetName + ": " + e.getMessage(),
e);
}
}

public void removeConfigSetTrust(String configSetName) {
try {
Map<String, Object> metadata = Collections.singletonMap("trusted", false);
cc.getConfigSetService().setConfigMetadata(configSetName, metadata);
} catch (IOException e) {
throw new SolrException(
SolrException.ErrorCode.SERVER_ERROR,
"Could not remove trusted flag for configSet " + configSetName + ": " + e.getMessage(),
e);
}
}

protected ZkSolrResourceLoader zkLoaderForConfigSet(final String configSet) {
SolrResourceLoader loader = cc.getResourceLoader();
return new ZkSolrResourceLoader(
loader.getInstancePath(), configSet, loader.getClassLoader(), cc.getZkController());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.update.processor.UpdateRequestProcessorChain;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.data.Stat;
Expand All @@ -47,15 +46,15 @@ class SchemaDesignerSettingsDAO implements SchemaDesignerConstants {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

private final CoreContainer cc;
private final SchemaDesignerConfigSetHelper configSetHelper;

SchemaDesignerSettingsDAO(CoreContainer cc) {
SchemaDesignerSettingsDAO(CoreContainer cc, SchemaDesignerConfigSetHelper configSetHelper) {
this.cc = cc;
this.configSetHelper = configSetHelper;
}

SchemaDesignerSettings getSettings(String configSet) {
SolrConfig solrConfig =
SolrConfig.readFromResourceLoader(
zkLoaderForConfigSet(configSet), SOLR_CONFIG_XML, true, null);
SolrConfig solrConfig = configSetHelper.loadSolrConfig(configSet);
return getSettings(solrConfig);
}

Expand Down Expand Up @@ -97,12 +96,14 @@ boolean persistIfChanged(String configSet, SchemaDesignerSettings settings) thro
}

if (changed) {
ZkController.persistConfigResourceToZooKeeper(
zkLoaderForConfigSet(configSet),
overlay.getVersion(),
ConfigOverlay.RESOURCE_NAME,
overlay.toByteArray(),
true);
try (ZkSolrResourceLoader resourceLoader = configSetHelper.zkLoaderForConfigSet(configSet)) {
ZkController.persistConfigResourceToZooKeeper(
resourceLoader,
overlay.getVersion(),
ConfigOverlay.RESOURCE_NAME,
overlay.toByteArray(),
true);
}
}

return changed;
Expand Down Expand Up @@ -169,10 +170,4 @@ private boolean hasFieldGuessingURPChain(final SolrConfig solrConfig) {
}
return hasPlugin;
}

private ZkSolrResourceLoader zkLoaderForConfigSet(final String configSet) {
SolrResourceLoader loader = cc.getResourceLoader();
return new ZkSolrResourceLoader(
loader.getInstancePath(), configSet, loader.getClassLoader(), cc.getZkController());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public void testDAO() throws Exception {
.process(cluster.getSolrClient());
CollectionsHandler.waitForActiveCollection(collection, cc, rsp);

SchemaDesignerSettingsDAO dao = new SchemaDesignerSettingsDAO(cc);
SchemaDesignerConfigSetHelper csh = new SchemaDesignerConfigSetHelper(cc, null);
SchemaDesignerSettingsDAO dao = new SchemaDesignerSettingsDAO(cc, csh);
SchemaDesignerSettings settings = dao.getSettings(configSet);
assertNotNull(settings);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ Previously uploaded sample documents are indexed in the temporary collection eve
Click on the btn:[Edit Documents] button on the *Query Results* panel to load a JSON representation of indexed documents into the text area.
====

=== Trusted Config Sets

ConfigSet trust is necessary when loading extra libraries from the `solrconfig.xml` and using certain features of Solr.
Refer to the xref:configuration-guide:configsets-api.adoc#configsets-upload[ConfigSet API] section for more information on trusted configSets.

Using the Schema Designer API has the same effect on the trust of a ConfigSet as using the xref:configuration-guide:configsets-api.adoc#configsets-upload[ConfigSet Upload API].

* If you are authenticated:
** If your source configSet **is** trusted, then the temporary configSets you are modifying will remain trusted.
** If your source configSet **is not** trusted, then the temporary configSet you are modifying will never be trusted.
* If you are unauthenticated:
** The temporary configSet created for you will **not** be trusted.

When <<publish,publishing the temporary configSet>>, the trust of the temporary configSet is used to set the trust of the permanent configSet.

=== Iteratively Post Sample Documents

If you have sample documents spread across multiple files, you can POST them to the Schema Designer API and then load your schema in the Designer UI to design your schema.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ The sysProp `-Dsolr.redaction.system.pattern` has been deprecated, use the above
The `<hiddenSysProps>` solr.xml element under `<metrics>` has been deprecated.
Instead, use the xref:configuration-guide:configuring-solr-xml.adoc#hiddenSysProps[<hiddenSysProps>] tag under `<solr>`, which accepts a comma-separated string.

* The xref:indexing-guide:schema-designer.adoc[] now utilizes the same trust model as the xref:configuration-guide:configsets-api.adoc#configsets-upload[ConfigSet Upload API].

=== Official Docker Image
* The customization of the Official Solr Dockerfile has been changed.
Expand Down

0 comments on commit d07751c

Please sign in to comment.