elastic / elastic/elasticsearch
_security/user/_has_privileges may appear to return false for some privilges when the expectation is to return true
- Dominant language
- Java
- Stars
- 77.9k
- Forks
- 26.1k
- PR merge metrics
- PR metrics pending
Description
There is an expectation that privileges generally adhere to some hierarchy w.r.t. to their naming.
For example, there is an expectation that if you have permissions to the cluster level privilege `monitor` then you should also have privileges to `monitor_enrich`. This expectation holds for checking action the action names, but does not always hold when checking with `_has_privileges`.
For example, the monitor privilege deconstructed is effectively `cluster:monitor/*`, so when checking action name `cluster:monitor/xpack/enrich/stats` or `cluster:monitor/xpack/enrich/esql/resolve_policy` always matches expectations and returns `true` since we effectively resolve `cluster:monitor/xpack/enrich/stats` against `cluster:monitor/*` and it passes. Technically, this is implemented with Lucene automatons with pseudo code such as:
> Automatons.predicate("cluster:monitor/*").test("cluster:monitor/xpack/enrich/stats")
So the exceptions for the hierarchy in the naming conventions holds true when checking action names (the primary use).
However, you would expect the same hierarchy w.r.t. naming to still hold true when using the `_has_privileges` API. It would be expected, that if you have `monitor` cluster permission and ask the `_has_privileges` API if you have privileges to `monitor_enrich`, you would expect the result to be `true`. But, it is not. The answer is false.
The `_has_privileges` works slightly differently than when checking action names. `_has_privileges` doesn't resolve static strings against Automatons (nor can it). In the `_has_privileges` there are 2 Automatons, not one. One automaton represents `monitor` and another represents `monitor_enrich`. So, if your role allows you permissions to the `monitor` cluster privilege and you check if you have `monitor_enrich` via `_has_privileges` the implementation looks to see if `monitor_enrich` is a subset of `monitor`. So the psuedo code is
> // Returns true if the language of the first parameter is a subset of the language of the second parameter.
Operations.subsetOf(monitor_enrich, monitor)
This may return true or it may return false. It depends on exactly what permissions make up the Automatons. It will return `true` under the following conditions:
* `monitor` Automaton represents `cluster:monitor/*`
* `monitor_enrich` Automaton represents `cluster:monitor/xpack/enrich/*`
In the above case, per the view of the Automaton, `cluster:monitor/xpack/enrich/*` is a subset of `cluster:monitor/*`
The problem arises when the automaton represents multiple permissions and one or more permission is not a direct subset of the first. The following condition will return `false`
* `monitor` Automaton represents `cluster:monitor/*`
* `monitor_enrich` Automaton represents `cluster:monitor/xpack/enrich/*` AND `cluster:admin/xpack/enrich/get`
In the above case, per the view of the Automaton, `cluster:monitor/xpack/enrich/*` AND `cluster:admin/xpack/enrich/get` is NOT a subset of `cluster:monitor/*`
From the Automaton perceptive this correct behavior. However, from the expectations of permissions this is arguably a bug and most certainly defies usage expectations.
The example chosen here is a real example, but there are cases where the expectations of hierarchy w.r.t. to their naming don't roll up correctly when using the `_has_privileges` API .
### Steps to Reproduce
To reproduce:
create a role like this:
```
{
"cluster": ["monitor"]
}
```
Assign that to a user, then with that user call `_security/user/_has_privileges` with
```
{
"cluster": [ "monitor_enrich" ]
}
```
Note the `false` response:
```
"cluster": {
"monitor_enrich": false
},
```
### Logs (if relevant)
_No response_
Contributor guide
Assessment
This issue has not been assessed yet.