Skip to content

Commit 83db0b9

Browse files
authored
[Entitlements] Expand and update README (#125395) (#126267)
This PR expands the README for the entitlements package; it adds information for Elasticsearch developers on how the system is structured, how to build policies, what to do when new code "does not work" because it's bumping into a NotEntitledException, and how to apply a policy patch in case of an emergency.
1 parent d3ababc commit 83db0b9

File tree

1 file changed

+187
-6
lines changed

1 file changed

+187
-6
lines changed

libs/entitlement/README.md

Lines changed: 187 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,192 @@
1-
### Entitlement library
1+
# Entitlements
22

3-
This module implements mechanisms to grant and check permissions under the _entitlements_ system.
3+
This module implements mechanisms to grant and check permissions under the _Entitlements_ system.
44

5-
The entitlements system provides an alternative to the legacy `SecurityManager` system, which is deprecated for removal.
6-
The `entitlement-agent` instruments sensitive class library methods with calls to this module, in order to enforce the controls.
5+
The entitlements system provides an alternative to the legacy Java Security Manager;
6+
Elasticsearch (ES) has previously made heavy use of the Java Security Manager to minimize the risk of security vulnerabilities impact. The Java Security Manager has been [deprecated for removal since Java 17](https://openjdk.org/jeps/411) (Sept 2021) and has been [removed in JDK 24](https://openjdk.org/jeps/486) (March 2025). Without an alternative, the removal of the Java Security Manager would have left Elasticsearch users more susceptible to future security vulnerabilities.
77

8-
This feature is currently under development, and it is completely disabled by default (the agent is not loaded). To enable it, run Elasticsearch with
8+
The goal of _entitlements_ is to protect certain sensitive operations on resources, and the JVM itself, from unexpected and unwanted access, e.g. to limit the scope of potential remote code execution (RCE) vulnerabilities.
9+
10+
In practice, an entitlement allows code to call a well-defined set of corresponding JDK methods; without the entitlement code calling into those JDK methods is blocked and gets a `NotEntitledException`.
11+
12+
## Structure
13+
14+
All the code implementing Entitlements can be found under this directory. The `agent` module bootstraps the entitlement lib, and uses it to instruments sensitive JDK class library methods using a `InstrumentationService`. The current implementation of the instrumentation service uses ASM and is located under `asm-provider`.
15+
16+
`InstrumentationService` transform JDK methods to start with a call to check entitlements. The entitlement checker is defined in the `bridge`, which is patched into `java.base` at runtime because it must exist in the platform classloader.
17+
18+
The entitlement checker is implemented in the entitlement lib, which the `bridge` grabs reflectively. `PolicyManager` is where most checks are actually done. The entitlement lib also contains the implementation of the data objects used to define Entitlements (`Policy`, `Scope` and all classes implementing the `Entitlement` interface) as well as the logic for handling them (`PolicyParser`, `PolicyUtils`).
19+
20+
![Alt text](./entitlements-loading.svg)
21+
22+
## Policies
23+
24+
A `Policy` is associated with a single `component` (i.e. Elasticsearch module/plugin or server) and represents the entitlements allowed for a particular `Scope` (i.e. Java module).
25+
26+
Entitlements are divided into 3 categories:
27+
- available everywhere (Elasticsearch module/plugin or server)
28+
- available only to Elasticsearch modules
29+
- not externally available: can be used only to specify entitlements for modules in the server layer.
30+
31+
In order to help developers adding the correct entitlements to a policy, the name of the component, the scope name (Java module) and the name of the missing entitlement are specified in the `NotEntitledException` message:
32+
```
33+
NotEntitledException: component [(server)], module [org.apache.lucene.misc], class [class org.apache.lucene.misc.store.DirectIODirectory], entitlement [read_store_attributes]
34+
```
35+
36+
### How to add an Elasticsearch module/plugin policy
37+
38+
A policy is defined in an `entitlements-policy.yaml` file within an Elasticsearch module/plugin under `src/main/plugin-metadata`. Policy files contain lists of entitlements that should be allowed, grouped by Java module name, which acts as the policy scope. For example, the `transport-netty4` Elasticsearch module's policy file contains an entitlement to accept `inbound_network` connections, limited to the `io.netty.transport` and `io.netty.common` Java modules.
39+
40+
Elasticsearch modules/plugins that are not yet modularized (i.e. do not have `module-info.java`) will need to use single `ALL-UNNAMED` scope. For example, the `reindex` Elasticsearch module's policy file contains a single `ALL-UNNAMED` scope, with an entitlement to perform `outbound_network`; all code in `reindex` will be able to connect to the network. It is not possible to use the `ALL-UNNAMED` scope for modularized modules/plugins.
41+
42+
How to add an Entitlements plugin policy is described in the official Elasticsearch docs on how to [create a classic plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/creating-classic-plugins.html). The list of entitlements available to plugins is also described there.
43+
44+
For Elasticsearch modules, the process is the same. In addition to the entitlements available for plugins, Elasticsearch modules can specify the additional entitlements:
45+
46+
#### `create_class_loader`
47+
Allows code to construct a Java ClassLoader.
48+
49+
#### `write_all_system_properties`
50+
This entitlement is similar to `write_system_properties`, but it's not necessary to specify the property names that code in the scope can write: all properties can be written by code with this entitlement.
51+
52+
#### `inbound_network`
53+
This entitlement is currently available to plugins too; however, we plan to make it internally available only as soon as we can. It will remain available to Elasticsearch modules.
54+
55+
### How to add a server layer entitlement
56+
57+
Entitlements for modules in the server layer are grouped in a "server policy"; this policy is builtin into Elasticsearch, expressed in Java code in `EntitlementInitialization` (see `EntitlementInitialization#createPolicyManager`). As such, it can use entitlements that are not externally available, namely `ReadStoreAttributesEntitlement` and `ExitVMEntitlement`.
58+
59+
In order to add an entitlement, first look if the scope is already present in the server policy. If it's not present, add one. If it is, add an instance of the correct entitlement class to the list of entitlements for that scope.
60+
There is a direct mapping between the entitlement name and the Entitlement class: the name is written in snake case (e.g. `example_name`), the corresponding class has the same name but in Pascal Case with the addition of a `Entitlement` suffix (e.g. `ExampleNameEntitlement`).
61+
62+
For example, to fix the `NotEntitledException` from the example above:
63+
```java
64+
new Scope(
65+
"org.apache.lucene.misc",
66+
List.of(
67+
new FilesEntitlement(List.of(FileData.ofRelativePath(Path.of(""), DATA, READ_WRITE))),
68+
new ReadStoreAttributesEntitlement() // <- add this new entitlement
69+
)
70+
)
71+
```
72+
In any case, before adding a `server` entitlement or make any change to the server layer policy, please consult with the Core/Infra team.
73+
74+
### Always denied
75+
76+
Finally, there are some actions that are always denied; these actions do not have an associated entitlement, they are blocked with no option to allow them via a policy. Examples are: spawning a new process, manipulating files via file descriptors, starting a HTTP server, changing the locale, timezone, in/out/err streams, the default exception handler, etc.
77+
78+
## Tips
79+
80+
### What to do when you have a NotEntitledException
81+
82+
You can realize the code you are developing bumps into a `NotEntitledException`; that means your code (or code you are referencing from a 3rd party library) is attempting to perform a sensitive action, and it does not have an entitlement for that, so we are blocking it.
83+
84+
A `NotEntitledException` could be handled by your code/by your library (via a try-catch, usually of `SecurityException`); in that case, you will still see a WARN log for the "Not entitled" call.
85+
86+
To distinguish these two cases, check the stack trace of the `NotEntitledException` and look for a frame that catches the exception.
87+
88+
If you find such a frame, then this `NotEntitledException` could be benign: the code knows how to handle a `SecurityException` and continue with an alternative strategy. In this case, the remedy is probably to suppress the warning.
89+
90+
If you do not find such a frame, then the remedy is to grant the entitlement.
91+
If the entitlement should or could not be granted (e.g. because the offending code is trying to start a process), then you need to modify your code so it does not perform the sensitive (and forbidden) operation anymore.
92+
93+
#### Suppress a benign warning
94+
95+
For a benign `NotEntitledException` that is caught, we probably want to ignore the warning.
96+
Double-check with Core/Infra before embarking on this, because it's a nontrivial amount of work, which goes beyond changes to Elasticsearch and may involve multiple PRs in multiple repos. We want to make sure this is the way to go before spending time on it.
97+
98+
Suppressing the warning involves adding a setting to the `log4j2.properties` files; you can follow [this PR](https://github.com/elastic/elasticsearch/pull/124883) as an example. Use a consistent naming convention, e.g. `logger.entitlements_<plugin_name>.name`. Avoid using extra dots, use `_` instead.
99+
100+
Each component has its own `log4j2.properties` file. Place the file in `src/main/config`: the build process will take care of bundling the file.
101+
102+
#### Patching a policy via system properties
103+
104+
In an emergency, policies for Elasticsearch modules and plugins, and for the server layer modules, can be patched via a system property.
105+
The system property is in the form `es.entitlements.policy.<plugin name>` (`es.entitlements.policy.server` for the server layer policy), and accepts a versioned policy:
106+
```yaml
107+
versions:
108+
- version1
109+
- versionN
110+
policy:
111+
<a standard entitlement policy>
112+
```
113+
For example:
114+
```yaml
115+
versions:
116+
- 9.1.0
117+
policy:
118+
ALL-UNNAMED:
119+
- set_https_connection_properties
120+
- outbound_network
121+
- files:
122+
- relative_path: ".config/gcloud"
123+
relative_to: home
124+
mode: read
125+
```
126+
127+
The versioned policy needs to be base64 encoded, e.g. by placing the policy in a file like `plugin-patch.yaml` and the `base64` command line tool which is included in many OSes:
9128
```shell
10-
./gradlew run --entitlements
129+
base64 -i plugin-patch.yaml
130+
```
131+
The base64 string will then need to be passed via the command line to ES.
132+
For example, to pass the above policy to a test cluster via gradle run:
133+
```shell
134+
./gradlew run --debug-jvm -Dtests.jvm.argline="-Des.entitlements.policy.repository-gcs=dmVyc2lvbnM6CiAgLSA5LjEuMApwb2xpY3k6CiAgQUxMLVVOTkFNRUQ6CiAgICAtIHNldF9odHRwc19jb25uZWN0aW9uX3Byb3BlcnRpZXMKICAgIC0gb3V0Ym91bmRfbmV0d29yawogICAgLSBmaWxlczoKICAgICAgLSByZWxhdGl2ZV9wYXRoOiAiLmNvbmZpZy9nY2xvdWQiCiAgICAgICAgcmVsYXRpdmVfdG86IGhvbWUKICAgICAgICBtb2RlOiByZWFkCg=="
135+
```
136+
The versions listed in the policy are string-matched against the Elasticsearch version as returned by `Build.version().current()`. It is possible to specify any number of versions.
137+
138+
The patch policy will be merged into the existing policy; in other words, entitlements specified in the patch policy will be **added** to the existing policy.
139+
140+
For example, if you add an entitlement to an existing scope:
141+
```yaml
142+
versions:
143+
- 9.1.0
144+
policy:
145+
java.desktop:
146+
- manage_threads
147+
```
148+
with base64
149+
```
150+
dmVyc2lvbnM6CiAgLSA5LjEuMApwb2xpY3k6CiAgamF2YS5kZXNrdG9wOgogICAgLSBtYW5hZ2VfdGhyZWFkcw==
151+
```
152+
That policy is parsed and used to patch the existing entitlements to `java.desktop`, so at the end that module will have both the `load_native_libraries` (from the server layer embedded policy) and the `manage_threads` entitlements (from the patch).
153+
154+
It is also possible to modify a current entitlement; for `files` and `write_system_properties`, this means that the 2 entitlements (from the patch and from the embedded policy) will be **merged**, taking fields from both of them, so you can grant access to additional files, upgrade access to `read_write`, or add a system property.
155+
You can also add an entitlement to a new scope. It is not possible to remove an entitlement or a scope, or remove fields from an entitlement (e.g. remove access to a path or downgrade access to read-only).
156+
157+
If the policy is parsed and applied correctly, a INFO log will be displayed:
158+
```
159+
[INFO ][o.e.e.r.p.PolicyUtils ] [runTask-0] Using policy patch for layer [server]
11160
```
161+
If you try to add an invalid policy (syntax error, wrong scope, etc.) the patch will be discarded and Elasticsearch will run with the embedded policy. Same if the version does not match. In that case, you’ll see a WARN log:
162+
```
163+
[WARN ][o.e.e.r.p.PolicyUtils ] [runTask-0] Found a policy patch with invalid content. The patch will not be applied. Layer [server]
164+
java.lang.IllegalStateException: Invalid module name in policy: layer [server] does not have module [java.xml]; available modules [...]; policy path [<patch>]
165+
```
166+
167+
IMPORTANT: this patching mechanism is intended to be used **only** for emergencies; once a missing entitlement is identified, the fix needs to be applied to the codebase, by raising a PR or submitting a bug via Github so that the bundled policies can be fixed.
168+
169+
### How to migrate a from a Java Security Manager Policy to an entitlement policy
170+
171+
Translating Java Security Permissions to Entitlements is usually not too difficult;
172+
- many permissions are not used anymore. The Entitlement system is targeting sensitive actions we identified as crucial to our code; any other permission is not checked anymore. Also, we do not have any entitlement related to reflection or access checks: Elasticsearch runs modularized, and we leverage and trust the Java module mechanism to enforce access and visibility.
173+
Examples of permissions that do not have an Entitlement equivalent:
174+
- `java.net.NetPermission "getProxySelector"`, `java.util.PropertyPermission "<name>", "read"` or `java.lang.RuntimePermission "getClassLoader"`: we do not care about anything that "reads" or "gets" something. We care about writing or setting (except network and files);
175+
- `javax.security.auth.*Permission` or `java.security.SecurityPermission`: currently, we do not have any equivalent to authn/authz permissions. This could change in a future release.
176+
- `java.lang.reflect.ReflectPermission "suppressAccessChecks";` or `java.lang.RuntimePermission "accessDeclaredMembers"`: we rely on Java module encapsulation to protect sensitive classes and methods.
177+
- `java.net.SocketPermission "*", "resolve"`
178+
- some permissions have a 1-1 translation. Examples:
179+
- `java.net.SocketPermission "*", "connect"` translates to `outgoing_network`
180+
- `java.net.SocketPermission "*", "accept"` or `listen` translates to `incoming_network`
181+
- `java.lang.RuntimePermission "createClassLoader"` translates to `create_class_loader`
182+
- `java.io.FilePermission` translates to `files`
183+
- `java.util.PropertyPermission "<name>", "write"` translates to `write_system_properties` (`write_all_system_properties` in case `<name>` is `"*"`)
184+
- `java.lang.RuntimePermission "setContextClassLoader"` translates to `manage_threads`
185+
- `java.lang.RuntimePermission "loadLibrary*"` translates to `load_native_libraries`
186+
- some permissions need more investigation:
187+
- `java.lang.RuntimePermission "setFactory"`: most of the methods that used to be guarded by this permission are always denied; some are always granted. The only equivalent in the entitlement system is `set_https_connection_properties`, for methods like `HttpsURLConnection.setSSLSocketFactory` that can be used to change a HTTPS connection properties after the connection object has been created.
188+
189+
Note however that there is a key difference in the policy check model between Security Manager and Entitlements. This means that translating a Security Manager policy to an Entitlement policy may not be a 1-1 mapping from Permissions to Entitlements. Security Manager used to do a full-stack check, truncated by `doPrivileged` blocks; Entitlements check the "first untrusted frame". This means that some Permissions that needed to be granted with Security Manager may not need the equivalent entitlement; conversely, code that used `doPrivileged` under the Security Manager model might have not needed a Permission, but might need an Entitlement now to run correctly.
190+
191+
Finally, a word on scopes: the Security Manager model used either general grants, or granted some permission to a specific codebase, e.g. `grant codeBase "${codebase.netty-transport}"`.
192+
In Entitlements, there is no option for a general grant: you must identify to which module a particular entitlement needs to be granted (except for non-modular plugins, for which everything falls under `ALL-UNNAMED`). If the Security Manager policy specified a codebase, it's usually easy to find the correct module, otherwise it might be tricky and require deeper investigation.

0 commit comments

Comments
 (0)