nick-boss-tech commented on code in PR #5016:
URL: https://github.com/apache/solr/pull/5016#discussion_r4196711305
##########
solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java:
##########
@@ -219,13 +219,19 @@ public List<String> getCollectionsList() {
/**
* The collection(s) to authorize this request against. In SolrCloud, when a
local core serves the
* request, this is the collection of that core, since that is where the
request executes;
- * requests it sends to other collections are authorized by the receiving
nodes. Otherwise, this
- * is {@link #getCollectionsList()}. Not null.
+ * requests it sends to other collections are authorized by the receiving
nodes. In standalone
+ * mode this is the name of the core serving the request, since there are no
collections.
+ * Otherwise, this is {@link #getCollectionsList()}. Not null.
*/
public List<String> getAuthorizationCollectionsList() {
- if (core == null || !cores.isZooKeeperAware()) {
+ if (core == null) {
return getCollectionsList();
}
+ if (!cores.isZooKeeperAware()) {
+ // Standalone mode has no collections; authorize against the serving
core's name so that
+ // core-scoped authorization rules can match.
+ return List.of(core.getCoreDescriptor().getName());
Review Comment:
🤖 *AI text below* 🤖 *(posted on behalf of Nick Shanin)*
Good question; I checked what the current code actually does with such a
request. Two findings:
First, adding the shard cores to this list would not subject each of them to
its own check. The list this PR authorizes against holds the serving core's
name only in standalone mode
([HttpSolrCall.getAuthorizationCollectionsList](https://github.com/nick-boss-tech/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java#L226-L245)).
[RuleBasedAuthorizationPluginBase.authorize](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java#L111-L124)
returns the decision of the first name in the list that has a governing
permission; a name with no permission mapping is skipped, and the wildcard
mapping is the fallback
([RuleBasedAuthorizationPluginBase](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java#L119-L123)),
so the list
is a set of candidate names for one decision on the whole request, not a set
of checks. Making every name mandatory would change the plugin's semantics for
all callers, cloud included.
Second, the second core is not silently exposed under the default
configuration. The sub-requests a shards fan-out sends carry no user
credentials on the wire under BasicAuth with default settings:
[HttpShardHandler.prepareLBRequest](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandler.java#L204-L218)
attaches the caller's Principal to the outgoing request object, and the client
that sends it, HttpJettySolrClient
([HttpShardHandlerFactory](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/handler/component/HttpShardHandlerFactory.java#L308-L318)
builds it as the fan-out client), copies that principal into a client-side
request attribute rather than a header
([HttpJettySolrClient.decorateRequest](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/solrj-jetty/src/java/org/apache/solr/client/solrj/je
tty/HttpJettySolrClient.java#L634-L656)). The one BasicAuth path that turns
the attribute into an Authorization header runs only when forwardCredentials is
enabled, and it defaults to false
([BasicAuthPlugin.interceptInternodeRequest](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java#L215-L227);
[the
field](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java#L55)).
With blockUnknown at its default true
([BasicAuthPlugin](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java#L54)),
a request without credentials is rejected at authentication:
[doAuthenticate](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java#L167-L179)
answers [4
01](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java#L111-L115)
and returns false without invoking the filter chain, so the sub-request never
reaches the receiving core or its authorization. With blockUnknown=false the
sub-request passes authentication with no principal and is decided by the
target core's own authorization: a role-bearing governing permission denies it,
because a null principal yields USER_REQUIRED
([RuleBasedAuthorizationPluginBase](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java#L296-L301)),
which
[AuthorizationUtils](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/AuthorizationUtils.java#L64-L80)
surfaces as an authorization failure. A target core with no governing
permission at all, and no
wildcard permission either, allows it instead: a name with no permission
mapping yields NO_PERMISSIONS_FOUND
([checkCollPerm](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java#L152-L153)),
and NO_PERMISSIONS_FOUND maps to AuthorizationResponse.OK
([MatchStatus](https://github.com/apache/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/security/RuleBasedAuthorizationPluginBase.java#L402-L407)).
That last case is the plugin's designed treatment of names with no configured
permission, and it behaves the same in cloud mode. The split itself is the one
the method's Javadoc describes for cloud: requests a node sends to other
collections are authorized by the receiving nodes
([HttpSolrCall](https://github.com/nick-boss-tech/solr/blob/4c08aa5751ee4c8742c57a3ed8beeac827816611/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java#L219-L225)).
I also checked the behavior with a temporary probe test (not shipped),
re-run on 2026-10-06 at the current head (4c08aa5751e) in the three
configurations above; its outcomes are recorded in the Proof section of this
PR's description (https://github.com/apache/solr/pull/5016). A plain query on
the serving core answers normally in each configuration. Under the default
configuration the shards variants returned no documents, because the
sub-requests fail at authentication on the receiving core; one variant instead
failed at the front door with 403, because that caller lacked the serving
core's role. Under blockUnknown=false, shards naming the governed core returned
no documents (denied at the authorization stage per the code path above, which
surfaces to the caller as a 401), and shards naming the ungoverned core
returned its document.
So I have left the code as the serving core only, and recorded this in the
PR's Limits. If you think the plugin should move to per-name mandatory checks,
that seems like its own change, and I am glad to take it on as a follow-up.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]