= Rule-Based Authorization Plugins
// 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.

Solr provides authorization plugins that offer fine-grained user access control to critical Solr APIs and features.

Solr's authentication plugins control whether users can access Solr in a binary fashion.
A user is either authenticated, or they aren't.
For more fine-grained access control, Solr's Rule-Based Authorization Plugins (RBAP) can be used.

[CAUTION]
====
Solr's Admin UI interacts with Solr using its regular APIs.
When rule-based authorization is in use, logged-in users not authorized to access the full range of these APIs may see some sections of the UI that appear blank or "broken".

For best results, the Admin UI should only be accessed by users with full API access.
====

== Rule-Based Auth Concepts

"Users", "roles", and "permissions" play a central role in configuring authorization correctly.

In Rule-Based Authorization, administrators define a series of roles based on the permissions they want those roles to confer.
Users are then assigned one or more roles.

=== Users

The users that RBAP sees come from whatever authentication plugin has been configured.
RBAP is compatible with all of the authentication plugins that Solr ships with out of the box.
It is also compatible with any custom authentication plugins users might write, provided that the plugin sets a user principal on the HttpServletRequest it receives.

The user value seen by RBAP in each case depends on the authentication plugin being used: the Kerberos principal if the xref:kerberos-authentication-plugin.adoc[] is being used, the "sub" JWT claim if the xref:jwt-authentication-plugin.adoc[] is being used, etc.

=== Roles

Roles bridge the gap between users and permissions.
The roles can be used with any of the authentication plugins or with a custom authentication plugin if you have created one.
You will only need to ensure that logged-in users are mapped to the roles defined by the plugin.

There are two implementations of the plugin, which only differ in how the user's roles are obtained:

* `RuleBasedAuthorizationPlugin`: The role-to-user mappings must be defined explicitly in `security.json` for every possible authenticated user.

* `ExternalRoleRuleBasedAuthorizationPlugin`: The role-to-user mappings are managed externally.
This plugin expects the AuthenticationPlugin to provide a Principal that has the roles information as well, implementing the `VerifiedUserRoles` interface.

=== Permissions

Permissions control which roles (and consequently which users) have access to which Solr APIs.

Each permission has two main components: a description of the APIs the permission applies to, and a list of the roles that should be allowed to access to this set of APIs.

Administrators can use permissions from a list of predefined options or define their own custom permissions, are are free to mix and match both.

== Configuring the Rule-Based Authorization Plugins

Like all of Solr's security plugins, configuration for RBAP lives in a file or ZooKeeper node with the name `security.json`.
See xref:authentication-and-authorization-plugins.adoc#configuring-security-json[Configuring security.json] for more information on how to setup `security.json` in your cluster.

Solr offers an <<Authorization API>> for making changes to RBAP configuration.
Authorized administrators should use this to make changes under most circumstances.
Users may also make edits to `security.json` directly if it is stored in ZooKeeper, but this is an expert-level feature and is discouraged in most circumstances.
The API simplifies some aspects of configuration, and provides error feedback that isn't provided when editing ZooKeeper directly.

=== Configuration Syntax

RBAP configuration consists of a small number of required configuration properties.
Each of these lives under the `authorization` top level property in `security.json`

`class`::
+
[%autowidth,frame=none]
|===
s|Required |Default: none
|===
+
The authorization plugin to use.
There are three options: `solr.RuleBasedAuthorizationPlugin`, `solr.ExternalRoleRuleBasedAuthorizationPlugin`, or `solr.MultiAuthRuleBasedAuthorizationPlugin`.

`permissions`::
+
[%autowidth,frame=none]
|===
|Optional |Default: none
|===
+
A JSON array of permission rules used to restrict access to sections of Solr's API.
For example:
+
[source,json]
----
{
  "permissions": [
  { "name": "read", "collection": "techproducts", "role": ["admin", "dev"] },
  { "name": "all", "role": "admin"}
  ]
}
----
+
The syntax for individual permissions is more involved and is treated in greater detail <<Permissions,below>>.

User roles may come from the request itself when you use the `ExternalRoleRuleBasedAuthorizationPlugin` for the `class`.
In this case, skip defining `permissions`.

If you need to hardcode user-role mappings, then define the `RuleBasedAuthorizationPlugin` for the `class` and define the user-role mappings in `security.json` like this:

`user-role`::
+
[%autowidth,frame=none]
|===
|Optional |Default: none
|===
+
A mapping of individual users to their assigned roles.
The value of this parameter is a JSON map, where each property name is a user, and each property value is either the name of a single role or a JSON array of multiple roles that the specified user belongs to.
+
For example:
+
[source,json]
----
{
  "user-role": {
  "user1": "role1",
  "user2": ["role1", "role2"]
  }
}
----

`useShortName`::
+
[%autowidth,frame=none]
|===
|Optional |Default: `false`
|===
+
Determines if user-role mappings will resolve using the full principal or a shortened name provided by the authentication plugin.
For example, the `KerberosAuthPlugin` may provide a full principal as `user@EXAMPLE.COM`, while the corresponding short name would be `user`.
+
For some plugins the principal name and short name may be the same.

=== Example for RuleBasedAuthorizationPlugin and BasicAuth

This example `security.json` shows how the xref:basic-authentication-plugin.adoc[] can work with the `RuleBasedAuthorizationPlugin` plugin:

[source,json]
----
{
  "authentication": {
    "class": "solr.BasicAuthPlugin", <1>
    "blockUnknown": true,
    "credentials": {
      "admin-user": "IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c=",
      "dev-user": "IV0EHq1OnNrj6gvRCwvFwTrZ1+z1oBbnQdiVC3otuq0= Ndd7LKvVBAaZIF0QAVi1ekCfAJXr1GGfLtRUXhgrF8c="
    }
  },
  "authorization": {
    "class": "solr.RuleBasedAuthorizationPlugin", <2>
    "user-role": { <3>
      "admin-user": "admin",
      "dev-user": "dev"
    },
    "permissions": [ <4>
      { "name": "dev-private-collection", "collection": "dev-private", "role": "dev"},
      { "name": "security-read", "role": "admin"},
      { "name": "security-edit", "role": "admin"}
    ]
  }
}
----

<1> Solr is using the Basic Authentication plugin for authentication.
This configuration establishes two users: `admin-user` and `dev-user`.
<2> The `authorization` property begins the authorization configuration.
Solr will use RBAP for authorization.
<3> Two roles are defined: `admin` and `dev`.
Each user belongs to one role: `admin-user` is an `admin`, and `dev-user` is a `dev`.
<4> Three permissions restrict access to Solr.
The first permission (a "custom" permission) indicates that only the `dev` role can read from a special collection with the name `dev-private`.
The last two permissions ("predefined" permissions) indicate that only the `admin` role is permitted to use Solr's security APIs.
See below for more information on permission syntax.

Altogether, this example carves out two restricted areas.
Only `admin-user` can access Solr's Authentication and Authorization APIs, and only `dev-user` can access their `dev-private` collection.
All other APIs are left open, and can be accessed by both users.

=== Example for External Role RuleBasedAuthorizationPlugin with JWT auth

This example `security.json` shows how the xref:jwt-authentication-plugin.adoc[], which pulls user and user roles from JWT claims, can work with the `ExternalRoleRuleBasedAuthorizationPlugin` plugin:

[source,json]
----
{
"authentication":{
   "class": "solr.JWTAuthPlugin", <1>
   "jwksUrl": "https://my.key.server/jwk.json", <2>
   "rolesClaim": "roles" <3>
},
"authorization":{
   "class":"solr.ExternalRoleRuleBasedAuthorizationPlugin", <4>
   "permissions":[{"name":"security-edit",
      "role":"admin"}] <5>
}}
----

Let's walk through this example:

<1> JWT Authentication plugin is enabled.
<2> Public keys will be pulled over HTTPS.
<3> We expect each JWT token to contain a "roles" claim, which will be passed on to Authorization.
<4> External Role Rule-based authorization plugin is enabled.
<5> The 'admin' role has been defined, and it has permission to edit security settings.

Only requests from users having a JWT token with role "admin" will be granted the `security-edit` permission.

=== Multiple Authorization Plugins

If your `security.json` config uses the `MultiAuthPlugin`, you want to use the `MultiAuthRuleBasedAuthorizationPlugin` to use a different authorization plugin for each authentication plugin.

The following example illustrates using the `MultiAuthRuleBasedAuthorizationPlugin` to configure an authorization plugin for the `Basic` and `Bearer` schemes:
[source,json]
----
{
  "authorization": {
    "class": "solr.MultiAuthRuleBasedAuthorizationPlugin",
    "schemes": [
      {
        "scheme": "basic",
        "class": "solr.RuleBasedAuthorizationPlugin",
        "user-role": {
          "k8s-oper": ["k8s"]
        }
      },
      {
        "scheme": "bearer",
        "class": "solr.ExternalRoleRuleBasedAuthorizationPlugin"
      }
    ],
    "permissions": []
  }
}
----

It would be uncommon for the same user account to exist in both plugins.
However, the `MultiAuthRuleBasedAuthorizationPlugin` combines the roles from all plugins together when determining the roles for a user.

Users should take special care to lock down the exact set of endpoints that service accounts need access to when using Basic authentication.
For example, if the `MultiAuthPlugin` allows a `k8s-oper` user to use Basic authentication (while all other users go through OIDC), then
the permissions configured for the `k8s-oper` user should only allow access to specific endpoints, such as `/admin/info/system`.

== Permissions

Solr's Rule-Based Authorization plugin supports a flexible and powerful permission syntax.
RBAP supports two types of permissions, each with a slightly different syntax.

=== Custom Permissions

Administrators can write their own custom permissions that can match requests based on the collection, request handler, HTTP method, particular request parameters, etc.

Each custom permission is a JSON object under the `permissions` parameter, with one or more of the properties below:

`name`::
+
[%autowidth,frame=none]
|===
|Optional |Default: none
|===
+
An identifier for the permission.
+
For custom permissions, this is used only as a clue to administrators about what this permission does.
+
Care must be taken when setting this parameter to avoid colliding with one of Solr's <<Permissions,predefined permissions>>, whose names are reserved.
If this name matches a predefined permission, Solr ignores any other properties set and uses the semantics of the predefined permission instead.

`collection`::
+
[%autowidth,frame=none]
|===
|Optional |Default: `*` (all)
|===
+
Defines the collection(s) the permission applies to.
The value can either be a single collection name, or a JSON array containing multiple collections.
+
The wildcard `*` is used to indicate that this rule applies to all collections.
Similarly the special value `null` can be used to indicate that this permission governs Solr's collection-agnostic ("admin") APIs.
+
[NOTE]
====
The `collection` parameter can only contain values that are real _collection_ names.
It currently cannot be used to match aliases.
+
Aliases are resolved before Solr's security plugins are invoked.
A `collection` parameter given an alias as a value will never match because RBAP will be comparing an alias name to already-resolved collection names.
+
Instead, set a `collection` parameter that contains all collections in the alias concerned (or the `*` wildcard).
====

`path`::
+
[%autowidth,frame=none]
|===
|Optional |Default: _null_
|===
+
Defines the paths the permission applies to.
The value can either be a single path string, or a JSON array containing multiple strings.
+
For APIs that access collections, path values should start after the collection name, and often just look like the request handler (e.g., `"/select"`).
+
For collection-agnostic (aka, "admin") APIs, path values should start at the `"/admin` path segment.
The wildcard `\*` can be used to indicate that this permission applies to all paths.

`method`::
+
[%autowidth,frame=none]
|===
|Optional |Default: `*`
|===
+
Defines the HTTP methods this permission applies to.
Options include `HEAD`, `POST`, `PUT`, `GET`, `DELETE`, and the wildcard `\*`.
Multiple values can also be specified using a JSON array.

`params`::
+
[%autowidth,frame=none]
|===
|Optional |Default: none
|===
+
Defines the query parameters the permission applies to.
The value is a JSON object containing the names and values of request parameters that must be matched for this permission to apply.
+
For example, this parameter could be used to limit the actions a role is allowed to perform with the Collections API.
If the role should only be allowed to perform the LIST or CLUSTERSTATUS requests, you would define this as follows:
+
[source,json]
----
{"params": {
   "action": ["LIST", "CLUSTERSTATUS"]
   }
 }
----
+
The request parameter value can be a simple string or a regular expression.
Use the prefix `REGEX:` to use a regular expression match instead of simpler string matching.
+
If the commands LIST and CLUSTERSTATUS are case insensitive, the example above can be written as follows:
+
[source,json]
----
{"params": {
   "action": ["REGEX:(?i)LIST", "REGEX:(?i)CLUSTERSTATUS"]
 }
}
----

`role`::
+
[%autowidth,frame=none]
|===
s|Required |Default: none
|===
+
Defines which role (or roles) are allowed access to the APIs controlled by this permission.
Multiple values can be specified using a JSON array.
The wildcard `*` can be used to indicate that all roles can access the described functionality.

=== Predefined Permissions

Custom permissions give administrators flexibility in configuring fine-grained access control.
But in an effort to make configuration as simple as possible, RBAP also offers a handful of predefined permissions, which cover many common use-cases.

Administrators invoke a predefined permission by choosing a `name` that matches one of Solr's predefined permission options (listed below).
Solr has its own definition for each of these permissions, and uses this information when checking whether a predefined permission matches an incoming request.
This trades flexibility for simplicity: predefined permissions do not support the `path`, `params`, or `method` properties which custom permissions allow.

The predefined permission names (and their effects) are:

* *security-edit*: this permission is allowed to edit the security configuration, meaning any update action that modifies `security.json` through the APIs will be allowed.
* *security-read*: this permission is allowed to read the security configuration, meaning any action that reads `security.json` settings through the APIs will be allowed.
* *schema-edit*: this permission is allowed to edit a collection's schema using the xref:indexing-guide:schema-api.adoc[].
Note that this allows schema edit permissions for _all_ collections.
If edit permissions should only be applied to specific collections, a custom permission would need to be created.
* *schema-read*: this permission is allowed to read a collection's schema using the xref:indexing-guide:schema-api.adoc[].
Note that this allows schema read permissions for _all_ collections.
If read permissions should only be applied to specific collections, a custom permission would need to be created.
* *config-edit*: this permission is allowed to edit a collection's configuration using the xref:configuration-guide:config-api.adoc[], the xref:configuration-guide:request-parameters-api.adoc[], and other APIs which modify `configoverlay.json`.
Because configs xref:configuration-guide:libs.adoc#lib-directives-in-solrconfig[can add libraries/custom code] from various locations, loading any new code via a trusted SolrConfig is explicitly allowed for users with this permission.
Note that this allows configuration edit permissions for _all_ collections.
If edit permissions should only be applied to specific collections, a custom permission would need to be created.
* *config-read*: this permission is allowed to read a collection's configuration using the xref:configuration-guide:config-api.adoc[], the xref:configuration-guide:request-parameters-api.adoc[], xref:configuration-guide:configsets-api.adoc#configsets-list[Configsets API], the Admin UI's xref:configuration-guide:configuration-files.adoc#files-screen[Files Screen], and other APIs accessing configuration.
Note that this allows configuration read permissions for _all_ collections.
If read permissions should only be applied to specific collections, a custom permission would need to be created.
* *metrics-read*: this permission allows access to Solr's xref:metrics-reporting.adoc#metrics-api[Metrics API], some xref:configuration-guide:implicit-requesthandlers.adoc#admin-handlers[implicit admin handlers] such as `solr/<collection>/admin/mbeans` and `solr/<collection>/admin/segments`, as well as other admin APIs exposing metrics.
* *health*: this permission allows access to Solr's xref:configuration-guide:implicit-requesthandlers.adoc#admin-handlers[Health Check and Ping] endpoints, typically used to monitor whether a node or core is healthy.
* *core-admin-edit*: Core admin commands that can mutate the system state.
* *core-admin-read*: Read operations on the core admin API
* *collection-admin-edit*: this permission is allowed to edit a collection's configuration using the xref:configuration-guide:collections-api.adoc[].
Note that this allows configuration edit permissions for _all_ collections.
If edit permissions should only be applied to specific collections, a custom permission would need to be created.
+
Specifically, the following actions of the Collections API would be allowed:

+
[.lowpadding,cols="1,1,1",frame=none,grid=none,stripes=none]
|===
| CREATE
| RELOAD
| SPLITSHARD
| CREATESHARD
| DELETESHARD
| CREATEALIAS
| DELETEALIAS
| DELETE
| DELETEREPLICA
| ADDREPLICA
| CLUSTERPROP
| MIGRATE
| ADDROLE
| REMOVEROLE
| ADDREPLICAPROP
| DELETEREPLICAPROP
| BALANCESHARDUNIQUE
| REBALANCELEADERS
|===

* *collection-admin-read*: this permission is allowed to read a collection's configuration using the xref:configuration-guide:collections-api.adoc[].
Note that this allows configuration read permissions for _all_ collections.
If read permissions should only be applied to specific collections, a custom permission would need to be created.
+
Specifically, the following actions of the Collections API would be allowed:
+
LIST +
OVERSEERSTATUS +
CLUSTERSTATUS +
REQUESTSTATUS

* *update*: this permission is allowed to perform any update action on any collection.
This includes sending documents for indexing (using an xref:configuration-guide:requesthandlers-searchcomponents.adoc#update-request-handlers[update request handler]).
This applies to all collections by default (`collection:"*"`).
* *read*: this permission is allowed to perform any read action on any collection.
This includes querying using search handlers (using xref:configuration-guide:requesthandlers-searchcomponents.adoc#search-handlers[request handlers]) such as `/select`, `/get`, `/tvrh`, `/terms`, `/clustering`, `/elevate`, `/export`, `/spell`, `/clustering`, and `/sql`.
This applies to all collections by default ( `collection:"*"` ).
* *zk-read* : Permission to read content from ZK (`/api/cluster/zk/data/*` , `/api/cluster/zk/ls/*` )
* *all*: Any requests coming to Solr.

=== Permission Ordering and Resolution

The permission syntax discussed above doesn't do anything to prevent multiple permissions from overlapping and applying to the same Solr APIs.
In cases where multiple permissions match an incoming request, Solr chooses the first matching permission and ignores all others - even if those other permissions would match the incoming request!

Since Solr only uses the first matching permission it finds, it's important for administrators to understand what ordering Solr uses when processing the permission list.

The ordering Solr uses is complex.
Solr tries to check first any permissions which are specific or relevant to the incoming request, only moving on to more general permissions if none of the more-specific ones match.
In effect, this means that different requests may check the same permissions in very different orders.

If the incoming request is collection-agnostic (doesn't apply to a particular collection), Solr checks permissions in the following order:

. Permissions with a `collection` value of `null` and a `path` value matching the request's request handler
. Permissions with a `collection` value of `null` and a `path` value of `*`
. Permissions with a `collection` value of `null` and a `path` value of `null`

If the incoming request is to a collection, Solr checks permissions in the following order:

. Permissions with `collection` and `path` values matching the request specifically (not a wildcard match)
. Permissions with `collection` matching the request specifically, and a `path` value of `*`
. Permissions with `collection` matching the request specifically, and a `path` value of `null`
. Permissions with `path` matching the request specifically, and a `collection` value of `*`
. Permissions with both `collection` and `path` values of `*`.
. Permissions with a `collection` value of `*` and a `path` value of `null`

As an example, consider the permissions below:

[source,json]
----
{"name": "read", "role": "dev"}, <1>
{"name": "coll-read", "path": "/select", "role": "*"}, <2>
{"name": "techproducts-read", "collection": "techproducts", "role": "other", "path": "/select"}, <3>
{"name": "all", "role": "admin"} <4>
----

All of the permissions in this list match `/select` queries.
But different permissions will be used depending on the collection being queried.

For a query to the "techproducts" collection, permission 3 will be used because it specifically targets "techproducts".
Only users with the `other` role will be authorized.

For a query to a collection called `collection1` on the other hand, the most specific permission present is permission 2, so _all_ roles are given access.

== Authorization API

=== Authorization API Endpoint

`/admin/authorization`: takes a set of commands to create permissions, map permissions to roles, and map roles to users.

=== Manage Permissions

Three commands control managing permissions:

* `set-permission`: create a new permission, overwrite an existing permission definition, or assign a pre-defined permission to a role.
* `update-permission`: update some attributes of an existing permission definition.
* `delete-permission`: remove a permission definition.

Created properties can either be custom or predefined.

In addition to the permission syntax discussed above, these commands also allow permissions to have a `before` property, whose value matches the index of the permission that this new permission should be placed before in `security.json`.

The following creates a new permission named "collection-mgr" that is allowed to create and list collections.
The permission will be placed before the "read" permission.
Note also that we have defined `collection` as `null` because requests to the Collections API are never collection-specific.

[source,bash]
curl --user solr:SolrRocks -H 'Content-type:application/json' -d '{
  "set-permission": {"collection": null,
                     "path":"/admin/collections",
                     "params":{"action":["LIST", "CREATE"]},
                     "before": 3,
                     "role": "admin"}
}' http://localhost:8983/solr/admin/authorization

Apply an update permission on all collections to a role called `dev` and read permissions to a role called `guest`:

[source,bash]
curl --user solr:SolrRocks -H 'Content-type:application/json' -d '{
  "set-permission": {"name": "update", "role":"dev"},
  "set-permission": {"name": "read", "role":"guest"}
}' http://localhost:8983/solr/admin/authorization

=== Update or Delete Permissions

Permissions can be accessed using their index in the list.
Use the `/admin/authorization` API to see the existing permissions and their indices.

The following example updates the `'role'` attribute of permission at index `3`:

[source,bash]
curl --user solr:SolrRocks -H 'Content-type:application/json' -d '{
  "update-permission": {"index": 3,
                       "role": ["admin", "dev"]}
}' http://localhost:8983/solr/admin/authorization

The following example deletes permission at index `3`:

[source,bash]
curl --user solr:SolrRocks -H 'Content-type:application/json' -d '{
  "delete-permission": 3
}' http://localhost:8983/solr/admin/authorization


=== Map Roles to Users

A single command allows roles to be mapped to users:

* `set-user-role`: map a user to a permission.

To remove a user's permission, you should set the role to `null`.
There is no command to delete a user role.

The values supplied to the command are simply a user ID and one or more roles the user should have.

For example, the following would grant a user "solr" the "admin" and "dev" roles, and remove all roles from the user ID "harry":

[source,bash]
curl -u solr:SolrRocks -H 'Content-type:application/json' -d '{
   "set-user-role" : {"solr": ["admin","dev"],
                      "harry": null}
}' http://localhost:8983/solr/admin/authorization
