# README

* [📖 Docs for Elasticsearch plugin](/develop/elasticsearch)
* [📖 Docs for Kibana plugin](/develop/kibana)

The documentation of an open source product should also be open source! Found a problem? Edit the file directly from GitHub!

## Getting started

* [🚀 Kibana Multi-User with ROR PRO](/develop/examples/multiuser_guide)
* [🚀 Kibana Multi-Tenancy with ROR Enterprise](/develop/examples/multitenancy_guide)
* [🚀 ECK with ROR](/develop/eck)

[⬅️ Elasticsearch plugin project](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin) (Github)


# For Elasticsearch

## Overview

ReadonlyREST is a light-weight Elasticsearch plugin that adds encryption, authentication, authorization and access control capabilities to Elasticsearch embedded REST API. The core of this plugin is an ACL engine that checks each incoming request through a sequence of **rules** a bit like a firewall. There are a dozen rules that can be grouped in sequences of blocks and form a powerful representation of a logic chain.

The Elasticsearch plugin known as `ReadonlyREST Free` is released under the GPLv3 license, or alternatively a commercial license (see [ReadonlyREST Embedded](https://readonlyrest.com/embedded)) and lays the technological foundations for the companion Kibana plugin which is released in two versions: [ReadonlyREST PRO](https://readonlyrest.com/pro) and [ReadonlyREST Enterprise](https://readonlyrest.com/enterprise).

Unlike the Elasticsearch plugin, the Kibana plugins are commercial only. But rely on the Elasticsearch plugin in order to work.

For a description of the Kibana plugins, skip to the [dedicated documentation page](/develop/kibana) instead.

### ReadonlyREST Free plugin for Elasticsearch

In this document, we are going to describe how to operate the Elasticsearch plugin in all its features. Once installed, this plugin will greatly extend the Elasticsearch HTTP API (port 9200), adding numerous extra capabilities:

* **Encryption**: transform the Elasticsearch API from HTTP to HTTPS
* **Authentication**: require credentials
* **Authorization**: declare groups of users, permissions and partial access to indices.
* **Access control**: complex logic can be modeled using an ACL (access control list) written in YAML.
* **Audit events**: a trace of the access requests can be logged to a file or index (or both).

#### Flow of a Search Request

The following diagram models an instance of Elasticsearch with the ReadonlyREST plugin installed and configured with SSL encryption and an ACL with at least one "allow" type ACL block.

![readonlyrest request processing diagram](/files/C6wi5xgsdjWb5TYxbxvF)

1. The User Agent (i.e. cURL, Kibana) sends a search request to Elasticsearch using port 9200 and the HTTPS URL schema.
2. The HTTPS filter in the ReadonlyREST plugin unwraps the SSL layer and hands over the request to the Elasticsearch HTTP stack
3. The HTTP stack in Elasticsearch parses the HTTP request
4. The HTTP handler in Elasticsearch extracts the indices, action, request type, and creates a `SearchRequest` (internal Elasticsearch format).
5. The SearchRequest goes through the ACL (access control list), external systems like LDAP can be asynchronously queried, and an exit result is eventually produced.
6. The exit result is used by the audit event serializer, to write a record to index and/or Elasticsearch log file
7. If no ACL block was matched, or if a `type: forbid` block was matched, ReadonlyREST does not forward the search request to the search engine and creates an "unauthorized" HTTP response.
8. In case the ACL matches a `type: allow` block, the request is forwarded to the search engine
9. The Elasticsearch code creates a search response containing the results of the query
10. The search response is converted to an HTTP response by the Elasticsearch code
11. The HTTP response flows back to ReadonlyREST's HTTPS filter and to the User agent

## Installation and Operations

### Running with Docker

The simplest method to run Elasticsearch with the ReadonlyREST plugin is to use one of our docker images which you can find on [Docker Hub](https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest):

```bash
docker run -u root -p 9200:9200 -e "I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes" -e "KIBANA_USER_PASS=kibana" -e "ADMIN_USER_PASS=admin" -e "discovery.type=single-node" beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
```

OR with [Docker Compose](https://docs.docker.com/compose/):

```yaml
# docker-compose.yml file content
services:

  es-ror:
    image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports:
      - "9200:9200"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
      - KIBANA_USER_PASS=kibana
      - ADMIN_USER_PASS=admin
      - discovery.type=single-node
```

(To run the docker-compose.yml call `docker compose up`)

Any of these methods, runs Elasticsearch container with ReadonlyREST with [init settings](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/develop/docker-image/init-readonlyrest.yml).

When the service is started you can test it using curl or Postman:

```
curl -v -u admin:admin https://localhost:9200
```

#### Customizing ROR settings

You can create locally customized `readonlyrest.yml` file and mount it as a [docker volume](https://docs.docker.com/storage/volumes/). Assuming that your ROR settings file is located in `/tmp/my-readonlyrest.yml` you can use it like that:

```bash
docker run -u root -p 9200:9200 -e "discovery.type=single-node" -e "I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes" -v /tmp/my-readonlyrest.yml:/etc/share/elasticsearch/config/readonlyrest.yml beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
```

OR

```yaml
# docker-compose.yml file content
services:

  es-ror:
    image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports:
      - "9200:9200"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
      - KIBANA_USER_PASS=kibana
      - ADMIN_USER_PASS=admin
      - discovery.type=single-node
    volumes:
      - ./my-readonlyrest.yml:/etc/share/elasticsearch/config/readonlyrest.yml # we assume that the `my-readonlyrest.yml` file is in the same folder as `docker-compose.yml` file is
```

####

### Installing the plugin

To install the ReadonlyREST plugin for Elasticsearch:

#### 1. Obtain the build

From the [official download page](https://readonlyrest.com/download). Select your Elasticsearch version and send yourself a link to the compatible ReadonlyREST zip file.

#### 2. Install the build

```bash
bin/elasticsearch-plugin install file:///tmp/readonlyrest-X.Y.Z_esW.Q.U.zip
```

Notice how we need to type in the format `file://` + absolute path (yes, with three slashes).

```
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
```

When prompted about additional permissions, answer **y**.

#### 3. Patch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra post-installation step**. Depending on the [Elasticsearch version](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/master/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches), this command might tweak the main Elasticsearch installation files and/or copy some jars to `plugins/readonlyrest` directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar patch --I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
```

**⚠️IMPORTANT**: The command above runs in silent mode with the `--I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes` flag. Without this flag, the patcher runs in interactive mode and will prompt you to confirm that you understand and accept the implications of ES patching.

**⚠️IMPORTANT**: for Elasticsearch 8.3.x or newer, the patching operation requires `root` user privileges.

You can verify if Elasticsearch was correctly patched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

Please note that the tool assumes that you run it from the root of your ES installation directory or the default installation directory is `/usr/share/elasticsearch`. But if you want or need, you can instruct it where your Elasticsearch is installed by executing one of the tool's command with the `--es-path` parameter:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar patch --es-path /my/custom/path/to/es/folder
```

or

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify --es-path /my/custom/path/to/es/folder
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 4. Create settings file

Create and edit the `readonlyrest.yml` settings file in the **same directory where `elasticsearch.yml` is found**:

```bash
vim $ES_PATH_CONF/conf/readonlyrest.yml
```

Now write some basic settings, just to get started. In this example, we are going to tell ReadonlyREST to require HTTP Basic Authentication for all the HTTP requests, and return `401 Unauthorized` otherwise.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Require HTTP Basic Auth"
      type: allow
      auth_key: user:password
```

#### 5. Start Elasticsearch

```bash
bin/elasticsearch
```

or:

```bash
service start elasticsearch
```

Depending on your environment.

Now you should be able to see the logs and ReadonlyREST-related lines like the one below:

```
[2018-09-18T13:56:25,275][INFO ][o.e.p.PluginsService     ] [c3RKGFJ] loaded plugin [readonlyrest]
```

#### 7. Test everything is working

The following command should succeed, and the response should show a status code 200.

```bash
curl -vvv -u user:password "http://localhost:9200/_cat/indices"
```

The following command should not succeed, and the response should show a status code 401

```bash
curl -vvv "http://localhost:9200/_cat/indices"
```

### Upgrading the plugin

To upgrade ReadonlyREST for Elasticsearch:

#### 1. Stop Elasticsearch.

Either kill the process manually, or use:

```bash
service stop elasticsearch
```

depending on your environment.

#### 2. Unpatch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra pre-uninstallation step**. This will remove all previously copied jars from ROR's installation directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar unpatch
```

You can verify if Elasticsearch was correctly unpatched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 3. Uninstall ReadonlyREST

```bash
bin/elasticsearch-plugin remove readonlyrest
```

#### 4. Install the new version of ReadonlyREST into Elasticsearch.

```bash
bin/elasticsearch-plugin install file://<download_dir>/readonlyrest-<ROR_VERSION>_es<ES_VERSION>.zip
```

e.g.

```bash
bin/elasticsearch-plugin install file:///tmp/readonlyrest-1.56.0_es8.12.2.zip
```

#### 5. Patch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra post-installation step**. Depending on the [Elasticsearch version](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/master/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches), this command might tweak the main Elasticsearch installation files and/or copy some jars to the `plugins/readonlyrest` directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar patch --I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
```

**⚠️IMPORTANT**: The command above runs in silent mode with the `--I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes` flag. Without this flag, the patcher runs in interactive mode and will prompt you to confirm that you understand and accept the implications of ES patching.

**⚠️IMPORTANT**: For Elasticsearch 8.3.x or newer, the patching operation requires `root` user privileges.

You can verify if Elasticsearch was correctly patched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 6. Restart Elasticsearch

```bash
bin/elasticsearch
```

or:

```bash
service start elasticsearch
```

Depending on your environment.

Now you should be able to see the logs and ReadonlyREST-related lines like the one below:

```
[2024-03-14T20:21:49,589][INFO ][t.b.r.b.RorInstance      ] [ROR_SINGLE_1] ReadonlyREST was loaded ...
```

### Removing the plugin

#### 1. Stop Elasticsearch.

Either kill the process manually, or use:

```bash
service stop elasticsearch
```

depending on your environment.

#### 2. Unpatch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra pre-uninstallation step**. This will remove all previously copied jars from ROR's installation directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar unpatch
```

You can verify if Elasticsearch was correctly unpatched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 3. Uninstall ReadonlyREST from Elasticsearch:

```bash
bin/elasticsearch-plugin remove readonlyrest
```

#### 4. Start Elasticsearch.

```bash
bin/elasticsearch
```

or:

```bash
service start elasticsearch
```

Depending on your environment.

### Upgrading Elasticsearch

The ReadonlyREST plugin version must always match the currently installed Elasticsearch version. As a result, if you want to upgrade Elasticsearch:

1. Before upgrading Elasticsearch, unpatch and uninstall the ReadonlyREST plugin according to the instructions:
   * [Unpatch Elasticsearch and uninstall the plugin](#removing-the-plugin)
2. Upgrade Elasticsearch.
3. After upgrading Elasticsearch, install the matching version of the ReadonlyREST plugin and patch according to the instructions:
   * [Install matching plugin version and patch Elasticsearch](#installing-the-plugin)

{% hint style="warning" %}
Upgrading Elasticsearch without following the instructions above may cause corruption of the ES installation and inability to patch the upgraded version.
{% endhint %}

### Deploying ReadonlyREST in a stable production cluster

Unless some advanced features are being used (see below), this Elasticsearch plugin operates like a lightweight, stateless filter glued in front of Elasticsearch HTTP API. Therefore it's sufficient to install the plugin **only in the nodes that expose the HTTP interface** (port 9200).

Installing ReadonlyREST in a dedicated node has numerous advantages:

* No need to restart all nodes, only the one you have installed the plugin into.
* No need to restart all nodes to update the security settings
* No need to restart all nodes when a security update is out
* Less complexity on the actual cluster nodes.

For example, if we want to move to HTTPS all the traffic coming from Logstash into a 9-node Elasticsearch cluster which has been running stable in production for a while, it's not necessary to install the ReadonlyREST plugin in all the nodes.

Creating a dedicated, lightweight ES node where to install ReadonlyREST:

1. (Optional) [disable the HTTP interface](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-http.html#_disable_http) from all the existing nodes
2. Create a new, lightweight, dedicated node without shards, nor master eligibility.
3. Configure ReadonlyREST with SSL [encryption](#encryption) in the new node
4. Configure Logstash to connect to the new node directly in HTTPS.

#### An exception

**⚠️IMPORTANT** By default when the `fields` [rule](#fields) is used, it's required to install the ReadonlyREST plugin in all the data nodes.

## Elasticsearch Configuration

ReadonlyREST uses two distinct kinds of settings:

* **ACL settings** — the access control rules defined in `readonlyrest.yml` (or stored in an Elasticsearch index). Every node in the cluster must share the same ACL settings. Subscribers of the [PRO](https://readonlyrest.com/pro) or [Enterprise](https://readonlyrest.com/enterprise) Kibana plugin can also reload ACL settings at runtime through the GUI (see [Cluster-wide Settings VS readonlyrest.yml](/develop/kibana#cluster-wide-settings-vs-readonlyrestyml)) or via the [ReadonlyREST API](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/readonlyrest-api.md).
* **Node settings** — settings placed in `elasticsearch.yml` that are specific to each Elasticsearch node. They control how ROR behaves during startup, where to look for ACL settings, and how SSL is configured. These settings are read once at node startup and are not shared between nodes.

The sections below describe the node settings that go into `elasticsearch.yml`.

> **Note:** All ROR node settings described below can also be provided as JVM system properties (e.g. `-Dreadonlyrest.settings.index_name=.my-ror-index`), using the same dot-separated key that appears in the YAML.

### Encryption

SSL/TLS encryption protects data in transit between clients and Elasticsearch. ReadonlyREST supports two independent encryption layers:

1. **External REST API** — client ↔ Elasticsearch traffic (port 9200)
2. **Internode transport** — node ↔ node traffic (port 9300)

#### Choosing between ReadonlyREST SSL and XPack Security SSL

There are two ways to configure SSL in an Elasticsearch cluster running ReadonlyREST:

* **ReadonlyREST SSL** — SSL provided by the ReadonlyREST plugin itself (described in the subsections below).
* **XPack Security SSL** — SSL provided by Elasticsearch's built-in `xpack.security` module.

The choice depends on whether `xpack.security.enabled` is set to `true` or `false` in `elasticsearch.yml`:

| `xpack.security.enabled` | SSL to use         |
| ------------------------ | ------------------ |
| `false`                  | ReadonlyREST SSL   |
| `true`                   | XPack Security SSL |

**Why does this matter?** During its patching step, ReadonlyREST deactivates XPack Security's authentication and authorization features — these are replaced by ROR's ACL engine. However, **XPack SSL is not deactivated**. This means that when `xpack.security.enabled: true`, XPack SSL is still fully active and must be configured through Elasticsearch's standard mechanism, not through ROR.

> **Recommendation:** Because `xpack.security` enables features used by Elasticsearch and Kibana (e.g. API keys, token service, certain Kibana integrations), it should not be disabled without a clear reason. If there is no specific requirement to disable it, prefer leaving `xpack.security.enabled: true` and use XPack Security SSL.

#### XPack Security SSL (when `xpack.security.enabled: true`)

When `xpack.security.enabled` is `true`, configure SSL by following the official Elasticsearch documentation:

* [Set up basic security (internode TLS)](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html)
* [Set up basic security plus HTTPS (REST API TLS)](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup-https.html)

ROR's ACL will handle authentication and authorization, while XPack manages the SSL layer transparently.

#### ReadonlyREST SSL (when `xpack.security.enabled: false`)

The following subsections describe how to configure SSL using ReadonlyREST's own SSL implementation. This applies only when `xpack.security.enabled` is set to `false` in `elasticsearch.yml`.

> **Configuration placement:** All SSL settings — including `http.type`, `transport.type`, `readonlyrest.ssl.*`, and `readonlyrest.ssl_internode.*` — must be placed in `elasticsearch.yml`.

**External REST API**

Encrypts traffic between clients and Elasticsearch on port 9200. Add the following to your `elasticsearch.yml`.

**Keystore option (JKS or PKCS#12):**

```yaml
http.type: ssl_netty4

readonlyrest.ssl.keystore_file: "keystore.jks"        # also accepts .p12 (PKCS#12)
readonlyrest.ssl.keystore_pass: "<keystore-password>"
readonlyrest.ssl.key_pass: "<key-password>"
readonlyrest.ssl.key_alias: "my-server-cert"          # optional; if omitted, ROR uses the first alias found in the keystore

# Optional: restrict accepted TLS versions and cipher suites
readonlyrest.ssl.allowed_protocols: [TLSv1.2, TLSv1.3]
readonlyrest.ssl.allowed_ciphers: [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]

# Optional: mutual TLS — require clients to present a certificate
readonlyrest.ssl.client_authentication: false          # default

# Optional: custom trust anchor for client certificates (defaults to JVM truststore)
readonlyrest.ssl.truststore_file: "truststore.jks"
readonlyrest.ssl.truststore_pass: "<truststore-password>"
```

**PEM option (preferred):**

```yaml
http.type: ssl_netty4

readonlyrest.ssl.server_certificate_key_file: "private_key.pem"
readonlyrest.ssl.server_certificate_file: "fullchain.pem"

# Optional: custom trust anchor for client certificates
readonlyrest.ssl.client_trusted_certificate_file: "trusted_certs.pem"
```

All certificate files must be placed in the same directory as `elasticsearch.yml`.

**Verify SSL is working**

After restarting Elasticsearch, confirm SSL is active by querying the cluster health endpoint:

```bash
# Full validation with a custom CA (self-signed or internal CA)
curl --cacert /path/to/ca-chain.pem \
     https://your-domain:9200/_cluster/health \
     -u admin:your_password
```

If your certificate was issued by a public CA (Let's Encrypt or any commercial CA), the system trust store is sufficient and `--cacert` can be omitted:

```bash
curl https://your-domain:9200/_cluster/health -u admin:your_password
```

Expected result: HTTP 200 with a JSON body containing `"status":"green"` or `"status":"yellow"`.

**Internode communication — transport module**

Encrypts traffic between nodes in the Elasticsearch cluster on port 9300. This configuration must be added to all nodes in the cluster.

**`elasticsearch.yml`:**

```yaml
transport.type: ror_ssl_internode

readonlyrest.ssl_internode.keystore_file: "keystore.jks"   # also accepts .p12 (PKCS#12)
readonlyrest.ssl_internode.keystore_pass: "<keystore-password>"
readonlyrest.ssl_internode.key_pass: "<key-password>"
readonlyrest.ssl_internode.key_alias: "my-node-cert"        # optional; if omitted, ROR uses the first alias found in the keystore
```

The keystore file must be placed in the same directory as `elasticsearch.yml`.

**Internode communication with XPack nodes**

It is possible to set up internode SSL between ROR nodes (with `xpack.security.enabled: false`) and XPack nodes. This requires ES 6.7.0 or newer.

Generate a certificate for the ROR node following the [Elasticsearch certificate generation guide](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html#generate-certificates).

The generated `elastic-certificates.p12` can then be used in the ROR node:

```yaml
readonlyrest.ssl_internode.enable: true
readonlyrest.ssl_internode.keystore_file: "elastic-certificates.p12"
readonlyrest.ssl_internode.keystore_pass: "<keystore-password>"
readonlyrest.ssl_internode.key_pass: "<key-password>"
readonlyrest.ssl_internode.truststore_file: "elastic-certificates.p12"
readonlyrest.ssl_internode.truststore_pass: "<truststore-password>"
readonlyrest.ssl_internode.client_authentication: true    # default: false
readonlyrest.ssl_internode.certificate_verification: true
readonlyrest.ssl_internode.hostname_verification: false   # default: false
```

**Certificate verification**

By default, certificate verification is disabled for internode SSL. This means any certificate is accepted without validation — useful in local or test environments. In production, it is advised to enable this option.

```yaml
readonlyrest.ssl_internode.certificate_verification: true
```

This option applies to internode SSL only.

**Hostname verification**

By default, hostname verification is disabled. This means the hostname or IP address is not checked against the names in the certificate.

> **Production:** Enable hostname verification alongside certificate verification for full transport security.

```yaml
readonlyrest.ssl_internode.hostname_verification: true
```

**Client authentication**

By default, the server does not request a client certificate. When enabled, Elasticsearch verifies the client's identity via mutual TLS.

For external REST API:

```yaml
readonlyrest.ssl.client_authentication: true
```

For internode communication:

```yaml
readonlyrest.ssl_internode.client_authentication: true
```

**Allowed protocols and ciphers**

Optionally, restrict the accepted TLS versions and cipher suites. Connections from clients not supporting the listed values will be dropped.

For external REST API:

```yaml
readonlyrest.ssl.allowed_protocols: [TLSv1.2, TLSv1.3]
readonlyrest.ssl.allowed_ciphers: [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
```

For internode communication:

```yaml
readonlyrest.ssl_internode.allowed_protocols: [TLSv1.2, TLSv1.3]
readonlyrest.ssl_internode.allowed_ciphers: [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
```

ReadonlyREST logs available ciphers and protocols supported by the current JVM at startup:

```
[2018-01-03T10:09:38,683][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Available ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA
[2018-01-03T10:09:38,684][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Restricting to ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
[2018-01-03T10:09:38,684][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Available SSL protocols: TLSv1,TLSv1.1,TLSv1.2
[2018-01-03T10:09:38,685][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Restricting to SSL protocols: TLSv1.2
```

**Custom truststore**

Replaces the default JVM truststore. The truststore file must be placed in the same directory as `elasticsearch.yml`.

For external REST API:

```yaml
readonlyrest.ssl.truststore_file: "truststore.jks"
readonlyrest.ssl.truststore_pass: "<truststore-password>"
```

For internode communication:

```yaml
readonlyrest.ssl_internode.truststore_file: "truststore.jks"
readonlyrest.ssl_internode.truststore_pass: "<truststore-password>"
```

When not specified, ReadonlyREST uses the default JVM truststore.

**Using Let's Encrypt**

Shows how to use Let's Encrypt certificates with ReadonlyREST. The same approach applies to certificates from other providers.

You can use PEM files directly without creating a keystore — see the PEM option in the [External REST API](#external-rest-api) section above. In that case, only step 1 below is needed.

**1. Obtain certificates**

```bash
certbot certonly --standalone -d DOMAIN.TLD -d DOMAIN_2.TLD --email EMAIL@EMAIL.TLD
```

Change to the certificate directory (typically `/etc/letsencrypt/live/DOMAIN.TLD`). The files you need are `fullchain.pem` and `privkey.pem`.

**2. Create a PKCS#12 keystore**

```bash
openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out keystore.p12 -name ror
```

You will be prompted to set a password for the `.p12` file. Remember it — you will need it in the next step.

**3. Convert to JKS (optional)**

Skip this step if you use `keystore.p12` directly — ReadonlyREST supports both PKCS#12 and JKS formats.

```bash
keytool -importkeystore \
  -srckeystore keystore.p12 \
  -srcstoretype PKCS12 \
  -srcstorepass STORE_PASS \       # password set in step 2
  -destkeystore keystore.jks \
  -deststorepass PASSWORD_STORE \  # protects keystore.jks → readonlyrest.ssl.keystore_pass
  -destkeypass PASSWORD_KEYPASS \  # protects the private key entry → readonlyrest.ssl.key_pass
  -alias ror
```

> `PASSWORD_STORE` and `PASSWORD_KEYPASS` can be the same value — most deployments use a single password for simplicity. `STORE_PASS` must match the password set in step 2.

The resulting keystore maps to your ROR configuration as follows:

```yaml
readonlyrest.ssl.keystore_file: "keystore.jks"
readonlyrest.ssl.keystore_pass: "PASSWORD_STORE"   # -deststorepass from step 3
readonlyrest.ssl.key_pass: "PASSWORD_KEYPASS"      # -destkeypass from step 3
```

If you get `java.io.IOException: failed to decrypt safe contents entry: javax.crypto.BadPaddingException: Given final block not properly padded`, the `STORE_PASS` value does not match the password set in step 2.

(Credits for the original JKS tutorial to [Maximilian Boehm](https://maximilian-boehm.com))

**FIPS mode**

If you need FIPS 140-2 compliant SSL, ReadonlyREST supports it via the BouncyCastle library and BCFKS keystore format. See [FIPS mode](/develop/elasticsearch/fips) for setup instructions.

### ACL settings source configuration

By default, ROR looks for ACL settings in a `readonlyrest.yml` file located next to `elasticsearch.yml`, and also watches a dedicated Elasticsearch index for settings updates. The following options let you customize this behavior.

#### Settings file and index

```yaml
readonlyrest:
  settings:
    index_name: .my-ror-index              # default: .readonlyrest
    file_path: /custom/readonlyrest.yml    # default: <ES config dir>/readonlyrest.yml
    max_size: 10 MB                        # default: 3 MB — maximum allowed size of ACL settings loaded from the index
```

#### Index loading strategy

When loading from index (the default), ROR polls the index periodically and retries on failure during startup. Both the poll interval and the startup retry behavior can be tuned:

```yaml
readonlyrest:
  load_from_index:
    poll_interval: 5s                      # how often to check the index for ACL settings changes (default: 5s, set to 0s to disable polling)
    initial_loading_retry_strategy:
      initial_delay: 5s                    # delay before the first attempt to load from the index at startup (default: 5s)
      attempts_interval: 5s               # interval between retry attempts if the index is not yet available (default: 5s)
      attempts_count: 5                   # maximum number of retry attempts before falling back to file (default: 5)
```

Setting `poll_interval` to `0s` disables periodic polling — ROR will load ACL settings from the index once at startup and will not check for changes until the node is restarted.

#### Force loading from file

When set to `true`, ROR will only load ACL settings from the file and will never attempt to read from the Elasticsearch index. This is typically used during recovery when in-index settings have become corrupted — see [Malformed in-index settings](/develop/kibana#malformed-in-index-settings).

```yaml
readonlyrest:
  force_load_from_file: true
```

Default: `false`.

### Request handling during ES startup

Each incoming request to the Elasticsearch node passes to the installed plugin. During Elasticsearch node startup, the plugin rejects incoming requests until it is fully initialized. The plugin rejects such requests with `403` forbidden responses by default.

To change this behavior, add the following to `elasticsearch.yml`:

```yaml
readonlyrest:
  not_started_response_code: 503
  failed_to_start_response_code: 503
```

`not_started_response_code` — HTTP code returned while the plugin has not yet finished starting. Allowed values: `403` (default), `503`.

`failed_to_start_response_code` — HTTP code returned when the plugin failed to start (e.g. due to a malformed ACL). Allowed values: `403` (default), `503`.

## ReadonlyREST ACL

### ACL basics

The core of this plugin is an ACL (access control list). A logic structure very similar to the one found in firewalls. The ACL is part of the plugin configuration, and it's written in YAML.

* The ACL is composed of an *ordered* sequence of named **blocks**
* Each block contains some **rules**, and a policy (forbid or allow)
* HTTP requests run through the blocks, starting from the first,
* The *first* block that satisfies *all the rules* decides if to forbid or allow the request (according to its policy).
* If none of the blocks is matched, the request is rejected

**⚠️IMPORTANT**: The ACL blocks are **evaluated sequentially**, therefore **the ordering of the ACL blocks is crucial**. The order of the rules inside an ACL block instead, is irrelevant.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Block 1 - only Logstash indices are accessible"
      type: allow # <-- default policy type is "allow", so this line could be omitted
      indices: ["logstash-*"] # <-- This is a rule

    - name: "Block 2 - Blocking everything from a network"
      type: forbid
      hosts: ["10.0.0.0/24"] # <-- this is a rule
```

*An Example of the Access Control List (ACL) made of 2 blocks.*

The YAML snippet above, like all of this plugin's settings should be saved inside the `readonlyrest.yml` file. Create this file **on the same path where `elasticsearch.yml` is found**.

**TIP**: If you are a subscriber of the [PRO](https://readonlyrest.com/pro) or [Enterprise](https://readonlyrest.com/enterprise) Kibana plugin, you can edit and refresh the settings through a GUI. For more on this, see the [documentation for the ReadonlyREST plugin for Kibana](/develop/kibana).

### Blocks of rules

Every block **must** have at least the `name` field, and optionally a `type` field valued either "allow" or "forbid". If you omit the `type`, your block will be treated as `type: allow` by default.

Keep in mind that ReadonlyREST ACL is a white list, so by default all request are blocked, unless you specify a block of rules that allows all or some requests.

* `name` will appear in logs, so keep it short and distinctive.
* `type` can be either `allow` or `forbid`. Can be omitted, default is `allow`.

```yaml
    - name: "Block 1 - Allowing anything from localhost"
      type: allow
      # In real life now you should increase the specificity by adding rules here (otherwise this block will allow all requests!)
```

*Example: the simplest example of an allow block.*

#### Unauthorized response configuration

When the request does not match any of the ACL blocks or the request matches the block with the `forbid` policy, the plugin rejects such requests with the `403` response code and `forbidden` content. You can change the content of the response as follows:

```yaml
readonlyrest:
  
  global_settings:
    response_if_req_forbidden: Forbidden by ReadonlyREST ES plugin # custom response for all forbidden requests

  access_control_rules:

    - name: "Block 1"
      type: # extended format for `type` property
        policy: allow
      indices: ["logstash-*"]

    - name: "Block 2"
      type: # extended format for `type` property
        policy: forbid
        # response returned when a request matches 'Block 2' (setting on the block level takes precedence over the global setting)
        response_message: "You are unauthorized to access this resource"
      indices: ["templates-*"]
```

See also [response\_if\_req\_forbidden](#response_if_req_forbidden) section.

### Rules

ReadonlyREST access control rules can be divided into the following categories:

* Authentication & Authorization rules
* Elasticsearch level rules
* Kibana-related rules
* HTTP level rules
* Network level rules

Please refrain from using HTTP level rules to protect certain indices or limit what people can do to an index. The level of control at this level is really coarse, especially because Elasticsearch REST API does not always respect RESTful principles. This makes of HTTP a bad abstraction level to write ACLs in Elasticsearch all together.

The only **clean and exhaustive** way to implement access control is to reason about requests **AFTER ElasticSearch has parsed** them. Only then, the list of affected **indices** and the **action** will be known for sure. See **Elasticsearch level** rules.

#### Authentication & Authorization rules

This section contains description of rules that can be used to authenticate and/or authorize users. Most of the following rules use HTTP Basic Auth, so the credentials are passed with the `Authorization` header and they can be easily decoded when the request is intercepted by a malicious third party. Please note that this authentication method is secure only if SSL is enabled.

**`auth_key`**

`auth_key: sales:p455wd`

It's an authentication rule that accepts [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication). Configure this value *in clear text*. Clients will need to provide the header e.g. `Authorization: Basic c2FsZXM6cDQ1NXdk` where "c2FsZXM6cDQ1NXdk" is Base64 for "sales:p455wd".

**⚠️IMPORTANT**: this rule is handy just for tests, replace it with another rule that hashes credentials, like: `auth_key_sha512`, or `auth_key_unix`.

[Impersonation](/develop/kibana/impersonation) is supported by this rule without an extra configuration.

**`auth_key_sha512`**

`auth_key_sha512: 280ac6f...94bf9`

The authentication rule that accepts [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication). The value is a string like `username:password` *hashed in* [*SHA512*](https://md5calc.com/hash/sha512). Clients will need to provide the usual Authorization header.

There are also available other rules with less secure SHA algorithms `auth_key_sha256` and `auth_key_sha1`.

The rules support also alternative syntax, where only password is hashed, eg:

`auth_key_sha512: "admin:280ac6f...94bf9"`

In the example below `admin` is the username and `280ac6f...94bf9` is the hashed secret.

[Impersonation](/develop/kibana/impersonation) is supported by these rules by default.

**`auth_key_pbkdf2`**

`auth_key_pbkdf2: "KhIxF5EEYkH5GPX51zTRIR4cHqhpRVALSmTaWE18mZEL2KqCkRMeMU4GR848mGq4SDtNvsybtJ/sZBuX6oFaSg=="` # logstash:logstash

`auth_key_pbkdf2: "logstash:JltDNAoXNtc7MIBs2FYlW0o1f815ucj+bel3drdAk2yOufg2PNfQ51qr0EQ6RSkojw/DzrDLFDeXONumzwKjOA=="` # logstash:logstash

The authentication rule that accepts [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication). The value is hashed in the same way as it's done in `auth_key_sha512` rule, but it uses [*PBKDF2*](https://en.wikipedia.org/wiki/PBKDF2) key derivation function. At the moment there is no way to configure it, so during the hash generation, the user has to take into consideration the following PBKDF2 input parameters values:

| Input parameter       | Value                      | Comment                                                                     |
| --------------------- | -------------------------- | --------------------------------------------------------------------------- |
| Pseudorandom function | HmacSHA512                 |                                                                             |
| Salt                  | use hashed value as a salt | eg. hashed value = `logstash:logstash`, use `logstash:logstash` as the salt |
| Iterations count      | 10000                      |                                                                             |
| Derived key length    | 512                        | bits                                                                        |

The hash can be calculated using [this calculator](https://8gwifi.org/pbkdf.jsp) (notice that the salt has to base Base64 encoded).

[Impersonation](/develop/kibana/impersonation) is supported by this rule without an extra configuration.

**`auth_key_unix`**

`auth_key_unix: test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0 # Hashed for "test:test"`

**⚠️IMPORTANT** this hashing algorithm is **very CPU intensive**, so we implemented a caching mechanism around it. However, this will not protect Elasticsearch from a DoS attack with a high number of requests with random credentials.

This is authentication rule that is based on `/etc/shadow` file syntax.

If you configured sha512 encryption with 65535 rounds on your system the hash in /etc/shadow for the account `test:test` will be `test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0`

```yaml
readonlyrest:
  access_control_rules:
    - name: Accept requests from users in group team1 on index1
      groups_any_of: ["team1"]
      indices: ["index1"]

    users:
    - username: test
      auth_key_unix: test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0 #test:test
      groups: ["team1"]
```

You can generate the hash with **mkpasswd** Linux command, you need whois package `apt-get install whois` (or equivalent)

`mkpasswd -m sha-512 -R 65534`

Also you can generate the hash with a python script (works on Linux):

```python
#!/usr/bin/python
import crypt
import random
import sys
import string

def sha512_crypt(password, salt=None, rounds=None):
    if salt is None:
        rand = random.SystemRandom()
        salt = ''.join([rand.choice(string.ascii_letters + string.digits)
                        for _ in range(8)])

    prefix = '$6$'
    if rounds is not None:
        rounds = max(1000, min(999999999, rounds or 5000))
        prefix += 'rounds={0}$'.format(rounds)
    return crypt.crypt(password, prefix + salt)


if __name__ == '__main__':
    if len(sys.argv) > 1:
        print sha512_crypt(sys.argv[1], rounds=65635)
    else:
        print "Argument is missing, <password>"
```

**Finally you have to put your username at the beginning of the hash with ":" separator** `test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0`

For example, `test` is the username and `$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0` is the hash for `test` (the password is identical to the username in this example).

[Impersonation](/develop/kibana/impersonation) is supported by this rule without an extra configuration.

**`token_authentication`**

An authentication rule that accepts a token sent in the HTTP header (`Authorization` by default).

There are two modes of operation: **static token** and **Elasticsearch-native token** (service token or API key).

**Static token**

```yaml
token_authentication:
   type: "static"
   token: "Bearer abc123XYZ"      # required, expected HTTP header content containing the token
   username: "john"               # required, the username assigned after successful authentication
   header: x-custom-authorization # optional, defaults to 'Authorization'
```

The rule matches when the value of the configured header equals the `token` field exactly. For example, for `Authorization: Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2Vu`, the `token` value is `Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2Vu`.

**Elasticsearch service token or API key (Fleet support)**

ROR integrates with Elasticsearch's [service token](https://www.elastic.co/guide/en/elasticsearch/reference/current/service-accounts.html) and [API key](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html) APIs to support Elastic Fleet. When `type` is set to `service-token` or `api-key`, ROR delegates token validation to Elasticsearch rather than comparing against a static value.

```yaml
token_authentication:
   type: "service-token"          # or "api-key"
   username: fleet                # required, the username assigned after successful authentication
   header: x-custom-authorization # optional, defaults to 'Authorization'
```

* `service-token` — validates against Elasticsearch service accounts (used by Fleet Server).
* `api-key` — validates against Elasticsearch API keys (used by Fleet-enrolled agents).

For a complete Fleet setup — including the required `forbid` block for token/API-key management actions and the full set of Fleet index patterns — see the [Elastic Fleet with ReadonlyREST](/develop/elasticsearch/fleet) guide.

For a complete walkthrough including credential flow, the `forbid` block rationale, and a runnable example, see the [Elastic Fleet guide](/develop/examples/fleet).

[Impersonation](/develop/kibana/impersonation) is supported by this rule without an extra configuration.

**`proxy_auth: "*"`**

`proxy_auth: "*"`

Delegated authentication. Trust that a reverse proxy has taken care of authenticating the request and has written the resolved user name into the `X-Forwarded-User` header. The value "\*" in the example, will let this rule match any username value contained in the `X-Forwarded-User` header.

If you are using this technique for authentication using our **Kibana** plugins, don't forget to add this snippet to `conf/kibana.yml`:

`readonlyrest_kbn.proxy_auth_passthrough: true`

So that Kibana will forward the necessary headers to Elasticsearch.

[Impersonation](/develop/kibana/impersonation) is supported by this rule without an extra configuration.

**Groups rules**

The ACL block will match, when the user belongs to groups matching the specified conditions.

The groups rules use the user definitions from [the `users` section](#users-and-groups). In that section, we define static users (and we assign groups to them) or we can authorize dynamic users (and we can map the external groups to the local groups).

* the first step of the groups subrules is authorizing the user
* after this step, we have an authorized user with information about the authorized groups to which the user belongs
* then we check whether the authorized user groups are permitted in context of the rule

**`groups_any_of`**

The ACL block will match when the user belongs to any of the specified groups (boolean OR logic).

Simplified syntax:

```yaml
  groups_any_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    any_of: ["group1", "group2"]
```

**`groups_all_of`**

This rule is very similar to the above defined `groups_any_of` rule, but this time ALL the groups listed in the array are required (boolean AND logic), as opposed to at least one (boolean OR logic) of the `any_of` rule.

Simplified syntax:

```yaml
  groups_all_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    all_of: ["group1", "group2"]
```

**`groups_not_any_of`**

The ACL block will match when the user belongs to NONE of the specified groups.

Simplified syntax:

```yaml
  groups_not_any_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    not_any_of: ["group1", "group2"]
```

Looking at the examples above:

* ACL block will MATCH for user that belongs to `group0`
* ACL block will NOT MATCH for user that belongs only to `group1`
* ACL block will NOT MATCH for user that belongs only to `group2`
* ACL block will NOT MATCH for user that belongs to both `group1` and `group2`
* ACL block will NOT MATCH for user that belongs to `group0`, `group1` and `group2`

**`groups_not_all_of`**

The ACL block will match when the user does not belong to all the specified groups.

Simplified syntax:

```yaml
  groups_not_all_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    not_all_of: ["group1", "group2"]
```

Looking at the example above:

* ACL block will MATCH for user that belongs to `group0`
* ACL block will MATCH for user that belongs only to `group1`
* ACL block will MATCH for user that belongs only to `group2`
* ACL block will NOT MATCH for user that belongs to both `group1` and `group2`
* ACL block will NOT MATCH for user that belongs to `group0`, `group1` and `group2`

**groups\_combined**

Logic conditions can be combined inside a single ACL block. It applies only to combining one positive logic (`all_of`/`any_of`) with one negative logic (`not_all_of`/`not_any_of`) The ACL block will match, when both conditions are met.

```yaml
  groups:
    any_of: ["group1", "group2", "group3"]
    not_all_of: ["group1", "group2"]
```

Looking at the example above:

* ACL block will NOT MATCH for user that belongs only to `group0` (because the `any_of` logic is not satisfied)
* ACL block will MATCH for user that belongs only to `group1` (the `any_of` logic is satisfied, the `not_all_of` too, because the user is not member of `group2`)
* ACL block will MATCH for user that belongs to `group1` and `group3` for the same reason
* ACL block will NOT MATCH for user that belongs to `group1` and `group2` (the `any_of` logic is satisfied, but `not_all_of` is not)

**User management**

In the `users` section, each entry tells us that:

* A given user with a username matching one of patterns in the `username` array ...
* belongs to the local groups listed in the `groups` array (example 1 & 2 below) OR belongs to local groups that are result of ["detailed group mapping"](/develop/elasticsearch/groups-rule-mapping) between local group ID and external groups (example 3 below).
* when they can be authenticated and (if authorization rule is present) authorized by the present rule(s).

In general it looks like this:

```yaml
  ...
  - name: "ACL block with groups rule"
    indices: [x, y]
    groups_any_of: ["local_group1"] # this group ID is defined in the "users" section

  users:
  - username: ["pattern1", "pattern2", ...]
    groups: ["local_group1", "local_group2", ...]
    <any authentication rule except groups rules>: ...

  - username: ["pattern1", "pattern2", ...]
    groups: ["local_group1", "local_group2", ...]
    <any authentication rule except groups rules>: ...
    <optionally_any_authorization_rule>: ...

  - username: ["pattern1", "pattern2", ...]
    groups:
      - local_group1: ["external_group1", "external_group2"]
      - local_group2: ["external_group2"]
    <authentication_with_authorization_rule>: ... # `ldap_auth` or `jwt_auth` or `ror_kbn_auth`
```

For details see [User management](#users-and-groups).

[Impersonation](/develop/kibana/impersonation) support depends on authentication and authorization rules used in `users` section.

For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

**`ldap_authentication`**

simple version: `ldap_authentication: ldap1`

extended version:

```yaml
ldap_authentication:
  name: ldap1
  cache_ttl: 10 sec
```

It handles LDAP authentication only using the configured LDAP connector (here `ldap1`). Check the [LDAP connector section](#ldap-connector) to see how to configure the connector.

**`ldap_authorization`**

```yaml
ldap_authorization:
  name: "ldap1"
  groups_any_of: ["group3"]
  cache_ttl: 10 sec
```

* It handles LDAP authorization only using the configured LDAP connector (here `ldap1`).
* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md#checking-groups-logic)
* It matches when previously authenticated user has groups in LDAP and when he belongs to at least one of the configured `groups_any_of` (OR logic). Alternatively, `all_of`/`not_any_of`/`not_all_of`/combined logic can be used to require users to meet certain conditions concerning group membership, as described [here](#groups_combined)
* **⚠️IMPORTANT** the negative groups logic (`not_any_of`/`not_all_of`) cannot be used, when `server_side_groups_filtering` is enabled for LDAP. In that case please use the combined logic, for example with `any_of` positive logic.
* Check the [LDAP connector section](#ldap-connector) to see how to configure the connector.

**`ldap_auth`**

Shorthand rule that combines `ldap_authentication` and `ldap_authorization` rules together. It handles both authentication and authorization using the configured LDAP connector (here `ldap1`).

```yaml
ldap_auth:
  name: "ldap1"
  groups_any_of: ["group1", "group2"]
```

The same functionality can be achieved using the two rules described below:

```yaml
ldap_authentication: ldap1
ldap_authorization:
  name: "ldap1"
  groups_any_of: ["group1", "group2"] # match when user belongs to at least one group
```

In both `ldap_auth`and `ldap_authorization`, the `groups` clause can be replaced by `group_and` to require the valid LDAP user must belong to all the listed groups:

```yaml
ldap_auth:
  name: "ldap1"
  groups_all_of: ["group1", "group2"] # match when user belongs to ALL listed groups
```

Or equivalently:

```yaml
ldap_authentication: ldap1
ldap_authorization:
  name: "ldap1"
  groups_all_of: ["group1", "group2"] # match when user belongs to ALL listed groups
```

See the dedicated [LDAP section](#ldap-connector)

[Impersonation](/develop/kibana/impersonation) support by LDAP rules requires to add [an extra configuration](/develop/kibana/impersonation#defining-mocks-of-the-external-services-optional).

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

**`jwt_authentication`**

See below, the dedicated [JSON Web Tokens section](#json-web-token-jwt-auth). It's an authentication rule.

[Impersonation](/develop/kibana/impersonation) is not currently supported by this rule.

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token
    kibana:
      access: ro
    jwt_authentication:
      name: "jwt_provider_1"

  jwt:
  - name: jwt_provider_1
    signature_key: "your_signature_min_256_chars"
    user_claim: email
```

**`jwt_authorization`**

See below, the dedicated [JSON Web Tokens section](#json-web-token-jwt-auth). It's an authorization rule.

[Impersonation](/develop/kibana/impersonation) is not currently supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token with a writer group
    kibana:
      access: rw
    jwt_authorization:
      name: "jwt_provider_1"
      groups_any_of: ["writer"]

  - name: Valid JWT token with a viewer and writer groups
    kibana:
      access: rw
    jwt_authorization:
      name: "jwt_provider_1"
      groups_all_of: ["writer", "viewer"]

  jwt:
  - name: jwt_provider_1
    signature_key: "your_signature_min_256_chars"
    group_ids_claim: resource_access.client_app.group_ids
```

**`jwt_auth`**

See below, the dedicated [JSON Web Tokens section](#json-web-token-jwt-auth). It's an authentication and authorization rule at the same time.

[Impersonation](/develop/kibana/impersonation) is not currently supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token with a viewer and writer groups
    kibana:
      access: rw
    jwt_auth:
      name: "jwt_provider_1"
      groups_all_of: ["writer", "viewer"]

  jwt:
  - name: jwt_provider_1
    signature_key: "your_signature_min_256_chars"
    user_claim: email
    group_ids_claim: resource_access.client_app.group_ids
```

**`external_authentication`**

Used to delegate authentication to another server that supports HTTP Basic Auth. See below, the dedicated [External BASIC Auth section](#external-basic-auth)

[Impersonation](/develop/kibana/impersonation) support by this rule requires to add [an extra configuration](/develop/kibana/impersonation#defining-mocks-of-the-external-services-optional).

For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

**`groups_provider_authorization`**

Used to delegate groups resolution for a user to a JSON microservice. See below, the dedicated [Groups Provider Authorization section](#custom-groups-providers)

[Impersonation](/develop/kibana/impersonation) support by this rule requires to add [an extra configuration](/develop/kibana/impersonation#defining-mocks-of-the-external-services-optional).

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

**`ror_kbn_authentication`**

([Enterprise](https://readonlyrest.com/enterprise))

For [Enterprise](https://readonlyrest.com/enterprise) customers only, required for SAML authentication. From ROR's perspective it authenticates users.

```yaml
readonlyrest:
  access_control_rules:
    - name: "ReadonlyREST Enterprise instance"
      ror_kbn_authentication:
        name: "kbn1"

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1" # <- use environmental variables for better security!
```

It handles authentication only using the configured ROR KBN connector (here `kbn1`). Continue reading about this in the kibana plugin documentation, in the dedicated [SAML section](/develop/kibana#saml)

[Impersonation](/develop/kibana/impersonation) is currently not supported by this rule.

**`ror_kbn_authorization`**

([Enterprise](https://readonlyrest.com/enterprise))

For [Enterprise](https://readonlyrest.com/enterprise) customers only. From ROR's perspective it authorizes users.

```yaml
readonlyrest:
  access_control_rules:

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authorization:
        name: "kbn1"
        groups_any_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to at least one of the configured groups

    - name: "ReadonlyREST Enterprise instance #1 - two groups required"
      ror_kbn_authorization:
        name: "kbn1"
        groups_all_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to all configured groups

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1" # <- use environmental variables for better security!

    - name: kbn2
      signature_key: "shared_secret_kibana2" # <- use environmental variables for better security!
```

It handles authorization only using the configured ROR KBN connector (here `kbn1` and `kbn2`). Continue reading about this in the kibana plugin documentation, in the dedicated [SAML section](/develop/kibana#saml)

[Impersonation](/develop/kibana/impersonation) is currently not supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

**`ror_kbn_auth`**

([Enterprise](https://readonlyrest.com/enterprise))

For [Enterprise](https://readonlyrest.com/enterprise) customers only, required for SAML authentication. From ROR's perspective it authenticates and authorize users.

```yaml
readonlyrest:
  access_control_rules:

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to at least one of the configured groups

    - name: "ReadonlyREST Enterprise instance #1 - two groups required"
      ror_kbn_auth:
        name: "kbn1"
        groups_all_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to all configured groups

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1" # <- use environmental variables for better security!

    - name: kbn2
      signature_key: "shared_secret_kibana2" # <- use environmental variables for better security!
```

This authentication and authorization connector represents the secure channel (based on JWT tokens) of signed messages necessary for our Enterprise Kibana plugin to securely pass back to ES the username and groups information coming from browser-driven authentication protocols like SAML

Continue reading about this in the kibana plugin documentation, in the dedicated [SAML section](/develop/kibana#saml)

[Impersonation](/develop/kibana/impersonation) is currently not supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/authorization-rules-details.md)

**`users`**

`users: ["root", "*@mydomain.com"]`

It's NOT an authentication rule, but it can be used to limit access to of specific users whose username is contained or matches the patterns in the array. This rule is independent from the authentication method chosen, so it will work well in conjunction LDAP, JWT, proxy\_auth, and all others. The rule won't be matched if there won't be an authenticated user by some other authentication rule (it means that to make sense, it should always be used in conjunction with some authentication rule).

For example:

```yaml
readonlyrest:
  access_control_rules:
    - name: "JWT auth for viewer group (role), limited to certain usernames"
      kibana:
        access: ro
      users: ["root", "*@mydomain.com"]
      jwt_auth:
        name: "jwt_provider_1"
        groups_any_of: ["viewer"]
```

#### Kibana-related rules

**`kibana`**

The `kibana` rule underpins all ROR Kibana-related settings that may be needed to provide great user experience.

```yaml
kibana:
  access: ro # required
  index: ".kibana_custom_index" # optional
  template_index: ".kibana_template" # optional
  hide_apps: [ "Security", "Enterprise Search"] # optional
  metadata: 
    dept: "@{jwt:tech.beshu.department}"
    alert_message:  "Dear @{acl:current_group} users, you are viewing dashboards for indices @{acl:available_groups}_logstash-*"
```

When `access: api_only` is used, `allowed_api_paths` can additionally be specified:

```yaml
kibana:
  access: api_only # required for allowed_api_paths
  allowed_api_paths: # optional, only valid with access: api_only
    - "^/api/spaces/.*$"
    - http_method: POST
      http_path: "^/api/saved_objects/.*$"
```

The rule consists of several sub-rules:

**`access`**

Enables the minimum set of Elasticsearch `actions` necessary for browsers to sustain a Kibana session, and rejects any other unrelated actions.

This "macro" rule allows the minimum set of actions necessary for a browser to use Kibana. It allows a set of actions towards the designated kibana index (see [`kibana.index`](#index)), plus a stricter subset of read-only actions towards other indices, which are considered "data indices".

The idea is that with one single sub-rule we allow the bare minimum set of index+action combinations necessary to support a Kibana browsing session.

Possible access levels:

* `ro_strict`: the browser has a read-only view on Kibana dashboards and settings and all other indices.
* `ro`: some write requests can go through to the `kibana_index` index so that the UI state in "Discover" can be saved and new short urls can be created.
* `rw`: some more requests will be allowed towards the `kibana_index` index only, so Kibana dashboards and settings can be modified.
* `admin`: like `rw`, but has additional permissions to save security settings in the ReadonlyREST PRO/Enterprise app
* `api_only`: only [Kibana REST API](https://www.elastic.co/guide/en/kibana/current/api.html) actions are allowed, login via browser is always denied.
* `unrestricted`: no action is restricted.

**NB:** The `admin` access level does not mean the user will be allowed to access all indices/actions. It's just like "rw" with settings changes privileges. If you truly require unrestricted access for your Kibana user, including ReadonlyREST PRO/Enterprise app, set `kibana.access: unrestricted`. You can use this rule with the `users` rule to restrict access to selected admins.

This sub-rule is often used with the `indices` rule, to limit the data a user is able to see represented on the dashboards. In that case do not forget to allow the custom kibana index in the `indices` rule!

**`index`**

([Enterprise](https://readonlyrest.com/enterprise))

**Default value is `.kibana`**

Specify to what index we expect Kibana to attempt to read/write its settings (use this together with `kibana.index` setting in the `kibana.yml` file)

This value directly affects how `kibana.access` works because at all the access levels (yes, even admin), `kibana.access` sub-rule will **NOT** match any *write* request in indices that are not the designated kibana index.

If used in conjunction with ReadonlyREST Enterprise, this rule enables **multi tenancy**, because in ReadonlyREST, a tenancy is identified with a set of Kibana configurations, which are by design collected inside a kibana index (default: `.kibana`).

It supports [dynamic variables](#dynamic-variables).

**⚠️IMPORTANT** When you use the `kibana` rule together with the `indices` rule in the same block, you don't have to explicitly allow the Kibana-related indices in the list of allowed indices of the `indices` rule. ROR will do it for you automatically.

Example:

```yaml
- name: "::RW_USER::"
  auth_key: rw_user:pwd
  kibana:
    access: rw
  indices: ["r*"] # .kibana, .kibana_8.10.4, .kibana_task_manager, etc are allowed here, because there is the `kibana` rule present in the same block
```

**`template_index`**

([Enterprise](https://readonlyrest.com/enterprise))

Used to pre-populate tenancies with default kibana objects, like dashboards and visualizations. Thus providing a starting point for new tenants that will avoid the bad user experience of logging for the first time and finding a completely empty Kibana.

It supports [dynamic variables](#dynamic-variables).

**`hide_apps`**

([PRO](https://readonlyrest.com/pro))

Specify which Kibana apps and menu items should be hidden. This feature will work in ReadonlyREST PRO and Enterprise.

For more information on the ROR's Kibana Hide Apps feature, see [Hiding Kibana Apps](/develop/kibana#hiding-kibana-apps).

**`allowed_api_paths`**

**Only valid when `access: api_only`.**

Used to define which parts of [Kibana REST API](https://www.elastic.co/guide/en/kibana/current/api.html) can be used. The sub-rule requires to define a list of [regular expressions](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) which describes the API paths. Additionally, when you would like to restrict only specific HTTP methods of the API, you can use the extended format of the sub-rule:

```yaml
kibana:
  access: api_only
  allowed_api_paths: # optional
    - http_method: POST
      http_path: "^/api/saved_objects/.*$"
```

**`metadata`**

([Enterprise](https://readonlyrest.com/enterprise))

User to define the Custom ROR Kibana Metadata which can be used in [Custom middleware](/develop/kibana#custom-middleware). The `kibana.metadata` in ReadonlyREST settings is an unstructured YAML object.

It supports [dynamic variables](#dynamic-variables).

Sample usage:

```yaml
kibana:
  [...]
  metadata:
     alert_message:  "Dear @{acl:current_group} users, you are viewing dashboards for indices @{acl:available_groups}_logstash-*"
```

`alert_message` Metadata can be used on the Kibana side to display information to the user on login to the Kibana.

Declare custom Kibana JS file `readonlyrest_kbn.kibana_custom_js_inject_file: '/path/to/custom_kibana.js'`. it's injected at the end of the HTML Body tag of the Kibana UI frontend code.

```js
const alertMessage = window.ROR_METADATA.customMetadata && window.ROR_METADATA.customMetadata.alert_message;

if (alertMessage) {
  alert(alertMessage);
}
```

#### Elasticsearch level rules

**`indices`**

`indices: ["sales", "logstash-*"]`

Matches if the request involves a set of indices (or aliases, or data streams) whose name is "sales", or starts with the string "logstash-", or a combination of both.

If a request involves a wildcard (i.e. "logstash-\*", "\*"), this is first expanded to the list of available indices, and then treated normally as follows:

* Requests that do not involve any indices (cluster admin, etc) result in a "match".
* Requests that involve only allowed indices result in a "match".
* Requests that involve a mix of allowed and not-allowed indices, are rewritten to only involve allowed indices, and result in a "match".
* Requests that involve only not-allowed indices result in a "no match". And the ACL evaluation moves on to the next block.

The rejection message and HTTP status code returned to the requester are chosen carefully with the main intent to simulate not-allowed indices do not exist at all.

The rule has also an extended version:

```yaml
indices:
  patterns: ["sales", "logstash-*"]`
  must_involve_indices: false
```

The definition above has the same meaning as the shortest version shown at the beginning of this section. By default the rule will be matched when a request doesn't involve indices (eg. /\_cat/nodes request). But we can change the behaviour by configuring `must_involve_indices: true` - in this case the request above will be rejected by the rule.

**In detail, with examples**

In ReadonlyREST we roughly classify requests as:

* "read": the request will not change the data or the configuration of the cluster
* "write": when allowed, the request changes the internal state of the cluster or the data.

If a **read request** involves some indices they have permissions for and some indices that they do NOT have permission for, the request is **rewritten** to involve only the subset of indices they have permission for. This is behaviour is very useful in Kibana: **different** users can see the **same** dashboards, but they are filled with a different, overlapping, or identical data set (according to the indices permissions).

When the subset of indices is empty, it means that user are not allowed to access requested indices. In multitenancy environment we should consider two options:

* requested indices don't exist
* requested indices exist but the current user is not authorized to access them

For both of these cases ROR is going to return HTTP 404 or HTTP 200 with an empty response. The same behaviour will be observed for ES with ROR disabled (for nonexistent index). If an index does exist, but a user is not authorized to access it, ROR is going to pretend that the index doesn't exist and a response will be the same like the index actually did not exist. See [detailed example](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/index-not-found-examples.md).

It's also worth mentioning, that when `global_settings.prompt_for_basic_auth` is set to `true` (that is disabled by default), ROR will return 401 instead of 404 HTTP status code. It is relevant for users who don't use ROR Kibana's plugin and would like to take advantage of default Kibana's behavior which shows the native browser basic auth dialog, when it receives HTTP 401 response (see [the example](#prompt_for_basic_auth)). If a **write request** wants to write to indices they don't have permission for, the write request is rejected.

**Requests related to templates**

Templates are also connected with indices, but rather indirectly. An index template has index patterns and could also have aliases. During an index template creation or modification, ROR checks if index patterns and aliases, defined in a request body, are allowed. When a user tries to remove or get template by name, ROR checks if the template can be considered as allowed for the user, and based on that information, it allows/forbids to remove or see it. See [details](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/indices-rule-templates.md).

**`actions`**

`actions: ["indices:data/read/*"]`

Match if the request action starts with "indices:data/read/".

In Elasticsearch, each request carries only one action. We extracted from Elasticsearch source code the full list of valid action strings as of all Elasticsearch versions. Please see the [dedicated section to find the actions list of your specific Elasticsearch version](https://github.com/beshu-tech/readonlyrest-docs/tree/master/actionstrings).

Example actions (see above for the full list):

```
 "cluster:admin/data_frame/delete"
 "cluster:admin/data_frame/preview"
 ...
 "cluster:monitor/data_frame/stats/get"
 "cluster:monitor/health"
 "cluster:monitor/main"
 "cluster:monitor/nodes/hot_threads"
 "cluster:monitor/nodes/info"
...
 "indices:admin/aliases"
 "indices:admin/aliases/get"
 "indices:admin/analyze"
...
 "indices:data/read/get"
 "indices:data/read/mget"
 "indices:data/read/msearch"
 "indices:data/read/msearch/template"
 ...
 "indices:data/write/bulk"
 "indices:data/write/bulk_shard_operations[s]"
 "indices:data/write/delete"
 "indices:data/write/delete/byquery"
 "indices:data/write/index"
 "indices:data/write/reindex"
 ...
 many more...
```

**`snapshots`**

`snapshots: ["snap_@{user}_*"]`

Restrict what snapshots names can be saved or restored

**`repositories`**

`repositories: ["repo_@{user}_*"]`

Restrict what repositories can snapshots be saved into

**`data_streams`**

`data_streams: ["ds_@{user}_*"]`

Restrict what data stream names can be created, deleted, or modified

**`filter`**

`filter: '{"query_string":{"query":"user:@{user}"}}'`

This rule enables **Document Level Security (DLS)**. That is: return only the documents that satisfy the boolean query provided as an argument.

This rule lets you filter the results of a read request using a boolean query. You can use *dynamic variables* i.e. `@{user}` (see dedicated paragraph) to inject a user name or some header values in the query, or even environmental variables.

**Example: per-user index segmentation**

In the index "test-dls", each user can only search documents whose field "user" matches their user name. I.e. A user with username "paul" requesting all documents in "test-dls" index, won't see returned a document containing a field `"user": "jeff"` .

```yaml
- name: "::PER-USER INDEX SEGMENTATION::"
  proxy_auth: "*"
  indices: ["test-dls"]
  filter: '{"bool": { "must": { "match": { "user": "@{user}" }}}}'
```

**Example 2: Prevent search of "classified" documents.**

In this example, we want to avoid that users belonging to group "press" can see any document that has a field "access\_level" with the value "CLASSIFIED". And this policy is applied to all indices (no indices rule is specified).

```yaml
- name: "::Press::"
  groups_any_of: ["press"]
  filter: '{"bool": { "must_not": { "match": { "access_level": "CLASSIFIED" }}}}'
```

**⚠️IMPORTANT** The `filter`and `fields` rules will only affect "read" requests, therefore "write" requests **will not match** because otherwise it would implicitly allow clients to "write" without the filtering restriction. For reference, this behaviour is identical to x-pack and search guard.

**⚠️IMPORTANT** Beginning with version 1.27.0 all ROR internal requests from kibana will not match blocks containing `filter` and/or `fields` rules. There requests are used to perform kibana login and dynamic config reload.

If you want to allow write requests (i.e. for Kibana sessions), just duplicate the ACL block, have the first one with `filter` and/or `fields` rule, and the second one without.

**`fields`**

This rule enables **Field Level Security (FLS)**. That is:

* for responses where fields with values are returned (e.g. Search/Get API) - filter and show only allowed fields
* make not allowed fields unsearchable - used in QueryDSL requests (e.g. Search/MSearch API) do not have impact on search result.

In other words: FLS protects from usage some not allowed fields for a certain user. From user's perspective it seems like such fields are nonexistent.

**Definition**

Field rule definition consists of two parts:

* A non empty list of fields (blacklisted or whitelisted) names. Supports wildcards and user runtime variables.
* The FLS engine definition (global setting, optional). See: [engine details](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/fls-engine.md).

**⚠️IMPORTANT** With default FLS engine it's required to install ReadonlyREST plugin in all the data nodes. Different configurations allowing to avoid such requirement are described in [engine details](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/fls-engine.md).

**Field names**

Fields can be defined using two access modes: blacklist and whitelist.

**Blacklist mode (recommended)**

Specifies which fields should not be allowed prefixed with `~` (other fields from mapping become allowed implicitly). Example:

`fields: ["~excluded_fields_prefix_*", "~excluded_field", "~another_excluded_field.nested_field"]`

Return documents but deprived of the fields that:

* start with `excluded_fields_prefix_`
* are equal to `excluded_field`
* are equal to `another_excluded_field.nested_field`

**Whitelist mode**

Specifies which fields should be allowed explicitly (other fields from mapping become not allowed implicitly). Example:

`fields: ["allowed_fields_prefix_*", "_*", "allowed_field.nested_field.text"]`

Return documents deprived of all the fields, except the ones that:

* start with `allowed_fields_prefix_`
* start with underscore
* are equal to `allowed_field.nested_field.text`

**NB:** You can only provide a full black list or white list. Grey lists (i.e. `["~a", "b"]`) are invalid settings and ROR will refuse to boot up if this condition is detected.

Example: hide prices from catalogue indices

```yaml
- name: "External users - hide prices"
  fields: ["~price"]
  indices: ["catalogue_*"]
```

**⚠️IMPORTANT** Any metadata fields e.g. `_id` or `_index` can not be used in `fields` rule.

**⚠️IMPORTANT** The `filter`and `fields` rules will only affect "read" requests, therefore "write" requests **will not match** because otherwise it would implicitly allow clients to "write" without the filtering restriction. For reference, this behaviour is identical to x-pack and search guard.

**⚠️IMPORTANT** Beginning with version 1.27.0 all ROR internal requests from kibana will not match blocks containing `filter` and/or `fields` rules. There requests are used to perform kibana login and dynamic config reload.

If you want to allow write requests (i.e. for Kibana sessions), just duplicate the ACL block, have the first one with `filter` and/or `fields` rule, and the second one without.

**Configuring an ACL with filter/fields rules when using Kibana**

A normal Kibana session interacts with Elasticsearch using a mix of actions which we can roughly group in two macro categories of "read" and "write" actions. However the `fields` and `filter` rules will **only match read requests**. They will also block ROR internal request used to log in to kibana and reload config. This means that a complete Kibana session cannot anymore be entirely matched by a single ACL block like it normally would.

For example, this ACL block would perfectly support a complete Kibana session. That is, 100% of the actions (browser HTTP requests) would be allowed by this ACL block.

```yaml
    - name: "::RW_USER::"
      auth_key: rw_user:pwd
      kibana:
        access: rw
      indices: ["r*"]
```

However, when we introduce a filter (or fields) rule, this block will be able to match only some of the actions (only the "read" ones).

```yaml
    - name: "::RW_USER::"
      auth_key: rw_user:pwd
      kibana:
        access: rw  # <-- won't work because of `filter` rule present in block (it mismatches RW requests)
      indices: ["r*"]
      filter: '{"query_string":{"query":"DestCountry:FR"}}'  # <-- will reject all write requests! :(
```

The solution is to duplicate the block. The first one will intercept (and filter!) the read requests. The second one will intercept the remaining actions. Both ACL blocks together will entirely support a whole Kibana session.

```yaml
    - name: "::RW_USER (filter read requests)::"
      auth_key: rw_user:pwd
      indices: ["r*"] # <-- KIBANA-RELATED INDICES WON"T BE FILTERED HERE!
      filter: '{"query_string":{"query":"DestCountry:FR"}}'

    - name: "::RW_USER (allow remaining requests)::"
      auth_key: rw_user:pwd
      kibana:
        access: rw
      indices: ["r*"] # <-- KIBANA-RELATED INDICES ARE IMPLICITLY ALLOWED! (because of the presence of the `kibana` rule in the same block)
```

**NB:** Look at how we **make sure that the requests to ".kibana" won't get filtered** by specifying an `indices` rule in the first block.

Here is another example, a bit more complex. Look at how we can duplicate the "PERSONAL\_GRP" ACL block so that the read requests to the "r\*" indices can be filtered, and all the other requests can be intercepted by the second rule (which is identical to the one we had before the duplication).

Before adding the `filter` rule:

```yaml
  - name: "::PERSONAL_GRP::"
    groups_any_of: ["Personal"]
    kibana:
      access: rw
      index: ".kibana_@{user}"
      hide_apps: ["readonlyrest_kbn", "timelion"]
    indices: ["r*"]
```

After adding the `filter` rule (using the block duplication strategy).

```yaml
    - name: "::PERSONAL_GRP (FILTERED SEARCH)::"
      groups_any_of: ["Personal"]
      indices: [ "r*" ]
      filter: '{"query_string":{"query":"DestCountry:FR"}}'

    - name: "::PERSONAL_GRP::"
      groups_any_of: ["Personal"]
      indices: ["r*"]
      kibana:
        access: rw
        index: ".kibana_@{user}"
        hide_apps: ["readonlyrest_kbn", "timelion"]
```

**`response_fields`**

This rule allows filtering Elasticsearch responses using a list of fields. It works in very similar way to `fields` rule. In contrast to `fields` rule, which filters out document fields, this rule filters out response fields. It **doesn't make use of Field Level Security (FLS)** and can be applied to every response returned by Elasticsearch.

It can be configured in two modes:

* *whitelist* allowing only the defined fields from the response object
* *blacklist* filtering out (removing) only the defined fields from the response object

**Blacklist mode**

Specifies which fields should be filtered out by adding the \~ prefix to the field name. Other fields in the response will be implicitly allowed. For example:

`response_fields: ["~excluded_fields_prefix_*", "~excluded_field", "~another_excluded_field.nested_field"]`

The above will return the usual response object, but deprived (if found) of the fields that:

* start with `excluded_fields_prefix_`
* are equal to `excluded_field`
* are equal to `another_excluded_field.nested_field`

**Wildcard across nested fields** It's possible to use the `*` character to intercept nested fields. Imagine having this document:

```json
{
   "_index":"kafka-both",
   "_type":"_doc",
   "_id":"460D9",
   "_score":8.649008,
   "_source":{
      "session_id":64124.0,
      "country":"something",
      "resp":{
         "credit_card_confidential": "378282246310005"
         "proc_time":0.02,
         "type":"spelling",
         "raw_text":{
            "proc_time":0.02,
            "system_entities":{
               "phone_number_confidential":[
                  {
                     "unit":"Number",
                     "string":"666",
                     "value":666
                  }
               ]
            }
         }
      }
   }
}
```

You can write this `fields` rule containing a pattern that uses the `*` right after the `~`:

```yml
fields: ["~*_confidential"]
```

Now the search response will omit the string field `credit_card_confidential`, and the whole object `resp.raw_text.phone_number_confidential`, or any other field whose name ends in "\_confidential", regardless of their type or if and how deeply it's nested.

**Whitelist mode**

In this mode rule is configured to filter out each field that isn't defined in the rule.

`response_fields: ["allowed_fields_prefix_*", "_*", "allowed_field.nested_field.text"]`

Return response deprived of all the fields, except the ones that:

* start with `allowed_fields_prefix_`
* start with underscore
* are equal to `allowed_field.nested_field.text`

**NB:** You can only provide a full black list or white list. Grey lists (i.e. `["~a", "b"]`) are invalid settings and ROR will refuse to boot up if this condition is detected.

*Example*: allow only `cluster_name` and `status` field in cluster health response:

Without any filtering response from `/_cluster/health` looks more or less like:

```json
{
    "cluster_name": "ROR_SINGLE",
    "status": "yellow",
    "timed_out": false,
    "number_of_nodes": 1,
    "number_of_data_nodes": 1,
    "active_primary_shards": 2,
    "active_shards": 2,
    "relocating_shards": 0,
    "initializing_shards": 0,
    "unassigned_shards": 2,
    "delayed_unassigned_shards": 0,
    "number_of_pending_tasks": 0,
    "number_of_in_flight_fetch": 0,
    "task_max_waiting_in_queue_millis": 0,
    "active_shards_percent_as_number": 50.0
}
```

but after configuring such rule:

```yaml
- name: "Filter cluster health response"
  uri_re: "^/_cluster/health"
  response_fields: ["cluster_name", "status"]
```

response from above will look like:

```json
{
    "cluster_name": "ROR_SINGLE",
    "status": "yellow"
}
```

**NB:** Any response field can be filtered using this rule.

#### HTTP Level rules

**`x_forwarded_for`**

`x_forwarded_for: ["192.168.1.0/24"]`

Behaves exactly like `hosts`, but gets the source IP address (a.k.a. origin address, `OA` in logs) inside the `X-Forwarded-For` header only (useful replacement to `hosts`rule when requests come through a load balancer like AWS ELB)

**Load balancers**

This is a nice tip if your Elasticsearch is behind a load balancer. If you want to match all the requests that come through the load balancer, use `x_forwarded_for: ["0.0.0.0/0"]`. This will match the requests with a valid IP address as a value of the `X-Forwarded-For` header.

**DNS lookup caching**

It's worth to note that resolutions of DNS are going to be cached by JVM. By default successfully resolved IPs will be cached forever (until Elasticsearch is restarted) for security reasons. However, this may not always be the desired behaviour, and it can be changed by adding the following JVM options either in the jvm.options file or declaring the ES\_JAVA\_OPTS environment variable: `sun.net.inetaddr.ttl=TTL_VALUE` (or/and `sun.net.inetaddr.negative.ttl=TTL_VALUE`). More details about the problem can be found [here](https://www.ibm.com/support/pages/understanding-tuning-and-testing-inetaddress-class-and-cache).

**`methods`**

`methods: [GET, DELETE]`

Match requests with HTTP methods specified in the list. N.B. Elasticsearch HTTP stack does not make any difference between HEAD and GET, so all the HEAD request will appear as GET.

**`headers_and` (or `headers`)**

`headers: ["h1:x*y","~h2:*xy"]`

Match if **all** the HTTP headers in the request match the defined patterns in headers rule. This is useful in conjunction with [proxy\_auth](#proxy_auth), to carry authorization information (i.e. headers: `x-usr-group: admins`).

The `~` sign is a pattern negation, so eg. `~h2:*xy` means: match if h2 header's value does not match the pattern \*xy, or `h2` is not present at all.

**`headers_or`**

`headers_or: ["x-myheader:val*","~header2:*xy"]`

Match if **at least one** the specified HTTP headers `key:value` pairs is matched.

**`uri_re`**

`uri_re: ["^/secret-index/.*", "^/some-index/.*"]`

**☠️HACKY (try to use indices/actions rule instead)**

Match if **at least one** specified regular expression matches requested URI.

**`maxBodyLength`**

`maxBodyLength: 0`

Match requests having a request body length less or equal to an integer. Use `0` to match only requests without body.

**NB**: Elasticsearch HTTP API breaks the specifications, nad GET requests **might** have a body length greater than zero.

**`api_keys`**

`api_keys: [123456, abcdefg]`

A list of api keys expected in the header `X-Api-Key`

**`session_max_idle`**

`session_max_idle: 1h`

**⚠️DEPRECATED** Browser session timeout (via cookie). Example values 1w (one week), 10s (10 seconds), 7d (7 days), etc. NB: not available for Elasticsearch 2.x.

#### Transport level rules

These are the most basic rules. It is possible to allow/forbid requests originating from a list of IP addresses, host names or IP networks (in slash notation).

**`hosts`**

`hosts: ["10.0.0.0/24"]` Match a request whose **origin** IP address (also called origin address, or `OA` in logs) matches one of the specified IP addresses or subnets.

**`accept_x-forwarded-for_header`**

`accept_x-forwarded-for_header: false`

**⚠️DEPRECATED (use `x_forwarded_for instead`)** A modifier for `hosts` rule: if the origin IP won't match, fallback to check the `X-Forwarded-For` header

**`hosts_local`**

`hosts_local: ["127.0.0.1", "127.0.0.2"]` Match a request whose **destination** IP address (called `DA` in logs) matches one of the specified IP addresses or subnets. This finds application when Elasticsearch HTTP API is bound to multiple IP addresses.

#### Ancillary block settings

**`verbosity`**

`verbosity: error`

Don't spam elasticsearch log file printing log lines for requests that match this block. Defaults to `info`.

### Users and Groups

Sometimes we want to make allow/forbid decisions according to the username associated to a HTTP request. The extraction of the user identity (username) can be done via HTTP Basic Auth (Authorization header) or delegated to a reverse proxy (see `proxy_auth` rule).

The validation of the said credentials can be carried on locally with hard coded credential hashes (see `auth_key_sha256` rule), via one or more LDAP server, or we can forward the Authorization header to an external web server and examine the HTTP status code (see `external_authentication`).

Optionally we can introduce the notion of groups (see them as bags of users). The aim of having groups is to write a very specific block once, and being able to allow multiple usernames that satisfy the block.

Groups can be declared and associated to users statically in the readonlyrest.yml file. Alternatively, groups for a given username can be retrieved from an LDAP server or from a LDAP server, or a custom JSON/XML service.

You can mix and match the techniques to satisfy your requirements. For example, you can configure ReadonlyREST to:

* Extract the username from X-Forwarded-User
* Resolve groups associated to said user through a JSON microservice

Another example:

* Extract the username from Authorization header (HTTP Basic Auth)
* Validate said username's password via LDAP server
* resolve groups associated to the user from groups defined in readonlyrest.yml

More examples are shown below together with a sample configuration.

#### Local users and groups

The `groups` rule accepts a list of group IDs. This rule will match if the resolved username (i.e. via `auth_key`) is associated with the given groups.

In this example, usernames `alice` and `claire` are statically associated with group IDs.\
The username `bob` is statically associated with [structered groups](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/structured-groups.md)(a special syntax for defining groups, which may be helpful in the case of the Enterprise Kibana plugin)

```yaml
 access_control_rules:

    - name: Accept requests from users in group team1 on index1
      type: allow  # Optional, defaults to "allow" will omit now on.
      groups_any_of: ["team1"]
      indices: ["index1"]

    - name: Accept requests from users in group team2 on index2
      groups_any_of: ["team2"]
      indices: ["index2"]

    - name: Accept requests from users in groups team1 OR team2 on index3
      groups_any_of: ["team1", "team2"]
      indices: ["index3"]

    - name: Accept requests from users in groups team4 OR team5 on index3
      groups_any_of: ["team4", "team5"]
      indices: ["index3"]

    - name: Accept requests from users in groups team1 AND team2 on index3
      groups_all_of: ["team1", "team2"]
      indices: ["index3"]

    users:

    - username: "alice"
      groups: ["team1"] # group id - a value that ROR operates in groups rules
      auth_key: alice:p455phrase

    - username: "bob"
      # structured group syntax, useful in case of tenancy selector in Kibana Enterprise plugin
      groups: 
      - id: "team2"     # group id
        name: "Team 2"  # group name - `Team 2` will be visible in the tenancy selector for the 'team2' group 
      - id: "team4"     # group id
        name: "Team 4"  # group name - `Team 4` will be visible in the tenancy selector for the 'team4' group 
      auth_key: bob:s3cr37

    - username: "claire"
      groups: ["team1", "team5"] # group ids
      auth_key_sha256: e0bba5fda92dbb0570fd2e729a3c8ed6b1d52b380581f32427a38e396ba28ec6 #claire:p455key
```

*Example: rules are associated to groups (instead of users) and users-group association is declared separately later under `users:`*

#### Group mapping

Sometimes we'd like to take advantage of groups (roles) existing in external systems (like LDAP). We can do that in `users` section too. It's possible to map external groups to local ones. For details see [External to local groups mapping ](/develop/elasticsearch/groups-rule-mapping).

#### Username case sensitivity

ReadonlyREST can cooperate with services that operate in a case-insensitive way. For this case, ROR has a toggleable username case sensitivity option. For details, see the [username\_case\_sensitivity section](#username_case_sensitivity) in Global Settings.

#### Static variables

Anywhere in `readonlyrest.yml` you can use the expression `${env:MY_ENV_VAR}` to replace in place the environmental variables. This is very useful for injecting credentials like LDAP bind passwords, especially in Docker.

For example, here we declare an environment variable, and we write `${env:LDAP_PASSWORD}` in our settings:

```bash
$ export LDAP_PASSWORD=S3cr3tP4ss
$ cat readonlyrest.yml
```

```yaml
ldaps:
  - name: ldap1
    host: "ldap1.example.com"
    port: 389                                                     
    ssl_enabled: false                                            
    ssl_trust_all_certs: true                                     
    bind_dn: "cn=admin,dc=example,dc=com"                         
    bind_password: "${env:LDAP_PASSWORD}"
    users:
      search_user_base_DN: "ou=People,dc=example,dc=com"
```

And ReadonlyREST ES will load "S3cr3tP4ss" as `bind_password`.

#### Dynamic variables

One of the neatest features in ReadonlyREST is that you can use dynamic variables inside most values of the following rules: `data_streams`, `indices`, `users`, `fields`, `filter`, `repositories`, `hosts`, `hosts_local`, `snapshots`, `response_fields`, `uri_re`, `x_forwarded_for`, `hosts_local`, `hosts`, `kibana.index`, `kibana.template_index`, `kibana.metadata`, [groups rules](#groups-rules). The variables are related to different contexts:

* `acl` - the context of data collected in authentication and authorization rules of the current block:
  * `@{acl:user}` gets replaced with the username of the successfully authenticated user. Using this variable is allowed only in blocks where one of the rules is an authentication rule of course it must be a rule different from the one containing the given variable.
  * `@{acl:current_group}` is the group ID explicitly requested by the tenancy selector in ReadonlyREST Enterprise plugin when using multi-tenancy.
  * `@{acl:available_groups}` gets replaced with available group IDs found in the authorization rule (because by default dynamic variables are resolved to a string, the variable resolved value will contain groups surrounded with double quotes and joined with a comma)
* `header` - the context of ES HTTP request headers
  * `@{header:<header_name>}` gets replaced with the value of the HTTP header with name `<header_name>` included in the incoming request (useful when reverse proxies handle authentication)
* `jwt` - the context of JWT header value
  * `@{jwt:<json_path>}` get replaced with value (or values) found in the JWT claim under the given JSON path

**Dynamic variables exploding**

A value resolved from a dynamic variable is a string. Some rules, like `indices` one, have multivalue context (you can configure several indices names in it).

Let's assume we have a request with the header: `APPS: app1,app2,app3`. Doing something like this:

```yaml
indices: ["logstash_@{header:apps}"]
```

We should expect it to be resolved to:

```yaml
indices: ["logstash_app1,app2,app3"]
```

for this particular request. No, it wouldn't be helpful at all. But there is an `explode` function for dynamic variables. Doing:

```yaml
indices: ["logstash_@explode{header:apps}"]
```

we should get:

```yaml
indices: ["logstash_app1", "logstash_app2", "logstash_app3"]
```

which looks more useful!

So, as we've seen, the `explode` attribute of a dynamic variable rule can be used to split a string with comma-separated values into an array of strings. But it can only be used in a rule with multi value context.

**Usage examples**

**Indices from user name**

You can let users authenticate externally, i.e. via LDAP, and use their user name string inside the `indices` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can see only their logstash indices" # i.e. alice can see alice_logstash-20170922
      ldap_authentication:
        name: "myLDAP"
      indices: ["@{acl:user}_logstash-*"] 

    # LDAP connector settings omitted, see LDAP section below..
```

**Indices from available groups**

You can let users authorize externally, i.e. via LDAP, and use their group strings inside the `indices` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can see only logstash indices for their departments" # i.e. alice belongs to 'dev' and 'ops' department groups, so she can see dev_logstash-20170922, ops_logstash-20170922
      ldap_auth:
        name: "myLDAP"
        groups_any_of: ["dev", "ops", "qa"]
      indices: ["@explode{acl:available_groups}_logstash-*"] # i.e when available_groups=[dev, ops] we will get indices: ["dev_logstash-*", "ops_logstash-*"] 

    # LDAP connector settings omitted, see LDAP section below..
```

**Filter from available groups**

You can let users authorize externally, i.e. via LDAP, and use their group strings inside the `filter` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can only see documents related to their departments" # i.e. alice belongs to 'dev' and 'ops' department groups, so she can see documents where "department" field is equal 'dev' or 'ops' 
      ldap_auth:
        name: "myLDAP"
        groups_any_of: ["dev", "ops", "qa"]
      filter: '{ "terms": { "department": [@{acl:available_groups}] }}' # i.e. from available_groups=[dev, ops] we will get filter: '{ "terms": { "department": ["dev","ops"] }}'
      indices: ["logstash-*"]

    # LDAP connector settings omitted, see LDAP section below..
```

**Uri regex matching user's current group**

You can let users authorize externally, i.e. via LDAP, and use their group inside the `uri_re` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can access uri with value containing user's current group, i.e. user with group 'g1' can access: '/path/g1/some_thing'"
      ldap_authorization:
        name: "ldap1"
        groups_any_of: ["g1", "g2", "g3"]
      uri_re: ["^/path/@{acl:current_group}/.*"]

    # LDAP connector settings omitted, see LDAP section below..
```

**Kibana index from headers**

Imagine that we delegate authentication to a reverse proxy, so we know that only authenticated users will ever reach Elasticsearch. We can tell the reverse proxy (i.e. Nginx) to inject a header called `x-nginx-user` containing the username.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Identify a personal kibana index where each user is supposed to save their dashboards"
      kibana:
        access: rw
        index: ".kibana_@{header:x-nginx-user}"
```

**Dynamic variables from JWT claims**

The JWT token is an authentication string passed generally as a header or a query parameter to the web browser. If you squint, you can see it's a concatenation of three base64 encoded strings. If you base64 decode the middle string, you can see the "claims object". That is the object containing the current user's metadata.

Here is an example of JWT claims object.

```javascript
{
  "user": "jdoe",
  "display_name": "John Doe",
  "department": "infosec",
  "allowedIndices": ["x", "y"]
}
```

Here follow some examples of how to use JWT claims as dynamic variables in ReadonlyREST ACL blocks, notice the "jwt:" prefix:

```yaml
# Using JWT claims as dynamic variables
indices: [ "idx_@{jwt:department}", "idx_other" ]
# claims = { "user": "u1", "department": "infosec"}
# -> indices: ["idx_infosec", "idx_other"]

# Using nested values in JWT using JSONPATH as dynamic variables
indices: [ "idx_@{jwt:jsonpath.to.department}", "idx_other"]
# claims = { "jsonpath": {"to": { "department": "infosec" }}}
# -> indices: ["idx_infosec", "idx_other"]

# Referencing array-typed values from JWT claims will expand in a list of strings
indices: [ "idx_@explode{jwt:allowedIndices}", "idx_other"]
# claims = {"username": "u1", "allowedIndices":  ["x", "y"] }
# -> indices: ["idx_x", "idx_y", "idx_other"]

# Explode operator will generate an array of strings from a comma-separated string
indices: ["logstash_@explode{x-indices_csv_string}*", "idx_other"]
# HTTP Headers: [{ "x-indices_csv_string": "a,b"}]
# -> indices: ["logstash_a*", "logstash_b*", "idx_other"]
```

#### Variables functions

A value resolved from a variable may not be valid in some contexts. Sometimes, the value from the variable needs some preprocessing before usage. For example, a HTTP header value containing the uppercase characters is a wrong candidate for the index name because it has to be a lowercase string. We introduced variable functions to overcome these limitations. They allow modification of the variable values during the variable resolution (both, [static](#static-variables) and [dynamic](#dynamic-variables) variables are supported).

With their help, you can use the HTTP header `X-Forwarded-User: James` containing uppercase characters in the `indices` rule:

```yaml
indices: [ 'index_@{header:x-forwarded-user}#{to_lowercase}' ]
# @{header:x-forwarded-user} is replaced by HTTP header value 'James', and then the given function chain (function `to_lowercase` converting all characters to lowercase) is applied to the header value
```

which resolves to:

```yaml
indices: [ 'index_james' ]
```

**Syntax**

In general, functions syntax is as follows:

`function_name("arg1","arg2")`

* `function_name` - a function that you want to apply
* `(...)` - function call parentheses (they may be omitted when the function has no args)
* `arg1`, `arg2` - arguments passed to function. They should be surrounded by `"`. If your argument contains a special character (`"` or `}`), you can escape it with a `\`, e.g. (`function_a("\}")`)

You can chain functions with the `.` operator (functions are applied in order from left to right):

`function_a("arg1").function_b.function_c("arg1")`

To apply functions to the variable, you need to use the `#` operator and enter your code in `{ }` braces:

```
@{--variable-definition--}#{--functions-chain--}
```

```yaml
# Using JWT claims as dynamic variables with variable function
indices: [ "idx_@{jwt:department}#{to_lowercase}", "idx_other" ]
# claims = { "user": "u1", "department": "Infosec"}
# -> indices: ["idx_infosec", "idx_other"]
```

**Supported functions**

Currently, we support functions like this:

* `replace_all(regex,replacement)` - Replaces each substring of the variable string that matches the given regular expression with the given replacement.

  Params:

  * `regex` - the [regular expression](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) to which the variable string is to be matched
  * `replacement` - the string to be substituted for each match

  Usage:

  ```yaml
  indices: [ 'index_@{header:app}#{replace_all("team","group")}' ]
  ```
* `replace_first(regex,replacement)` - Replaces the first substring of the variable string that matches the given regular expression with the given replacement.

  Params:

  * `regex` - the [regular expression](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) to which the variable string is to be matched
  * `replacement` - the string to be substituted for each match

  Usage example:

  ```yaml
  indices: [ 'index_@{header:app}#{replace_first("^team","group")}' ]
  ```
* `to_lowercase` - Converts all characters in the variable string to lower case

  Usage example:

  ```yaml
  indices: [ 'index_@{header:app}#{to_lowercase}' ]
  ```
* `to_uppercase` - Converts all characters in the variable string to upper case Usage example:

  ```yaml
  groups_any_of: [ 'x1', '@{header:group}#{to_uppercase}' ]
  ```

**💡 Didn't find the function you are looking for?**

We can easily extend the function list. If you need any new function/mechanism that cannot be obtained using the supported functions, let us know about it in our [forum](https://forum.readonlyrest.com/). We will consider adding the proper implementation.

**Variable function aliases**

Sometimes, the function chain may be very complex or occur multiple times in ACL. In this case, you can use a `function aliases` to simplify configuration management. The function alias allows you to export your function chain outside the ACL. Then you can substitute your function via alias `func(alias_name)`.

Let's assume that we have the following configuration, and we want to introduce some function aliases:

```yaml
readonlyrest:
   access_control_rules:
      - name: Alice
        indices: ['index_@{header:group}#{to_lowercase.replace_all("\\d","x")}']
        auth_key: alice:p455phrase

      - name: Bob
        indices: ['index_@{header:group}#{to_lowercase.replace_all("\\d","x").replace_first("^team","")}']
        auth_key: bob:s3cr37
```

You can define function aliases in the `readonlyrest.variables_function_aliases` section and substitute functions code with `func(alias)`:

```yaml
readonlyrest:
   variables_function_aliases:
      - custom_replace: 'to_lowercase.replace_all("\\d","x")' # convert to lower case and replace digits with x
      - skip_team_prefix: 'replace_first("^team","")'

   access_control_rules:
      - name: Alice
        indices: ['index_@{header:group}#{func(custom_replace)}']
        auth_key: alice:p455phrase

      - name: Bob
        indices: ['index_@{header:group}#{func(custom_replace).func(skip_team_prefix)}']
        auth_key: bob:s3cr37
```

#### LDAP connector

The authentication and authorization rules for LDAP (`ldap_auth`, `ldap_authentication`, `ldap_authorization`) defined in the rules section, always need to contain a reference by name to one LDAP connector. One or more LDAP connectors need to be defined in the section "ldaps" of the ACL.

**Configuration notes**

If you would like to experiment with LDAP and need a development server, you can stand up an OpenLDAP server configuring it using our schema file, which can be found in [our tests](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/develop/core/src/test/resources/test_example.ldif)).

**Technical configuration**

There are also plenty of technical settings which can be useful:

* an LDAP server address:
  * single host:
    * `host` (String, required) - LDAP server address
    * `port` (Integer, optional, default: `389`) - LDAP server port
    * `ssl_enabled` (Boolean, optional, default: `true`) - enables or disables SSL for LDAP connection
  * several hosts:
    * `hosts` (List, required) - list of LDAP server addresses. The address should look like this `ldap://[HOST]:[PORT]` or/and `ldaps://[HOST]:[PORT]`
    * `ha` (enum: \[`FAILOVER`, `ROUND_ROBIN`], optional, default: `FAILOVER`) - provides high availability strategy for LDAP
  * auto-discovery:
    * `server_discovery` (Boolean|YAML object, optional, default: `false`) - for details see [LDAP server discovery section](#ldap-server-discovery)
* `connection_pool_size` (Integer, optional, default: `30`) - indicates how many connections LDAP connector should create to LDAP server
* `connection_timeout` (Duration, optional, default: `10 sec`) - instructs connector how long it should wait for the connection to LDAP server
* `request_timeout` (Duration, optional, default: `10 sec`) - instructs connector how long it should wait for receiving a whole response from LDAP server
* `connection_health_check_interval` (Duration, optional, default: `120 sec`) - defines how often the LDAP connection pool should perform health checks on idle connections. Health checks proactively detect and replace stale connections before they cause authentication failures.
* `connection_max_age` (Duration, optional, default: `10 min`) - defines the maximum age of a connection in the pool. Connections older than this value are automatically replaced with fresh ones, preventing stale connection issues. This works in conjunction with `connection_health_check_interval` to maintain a healthy connection pool.
* `ssl_trust_all_certs` (Boolean, optional, default: `false`) - if it is set to `true`, untrusted certificates will be accepted
* `ignore_ldap_connectivity_problems` (Boolean, optional, default: `false`) - when it is set to `true`, it allows ROR to function even when LDAP server is unreachable. Rules using unreachable LDAP servers won't match. By default, ROR starts only after it's able to connect to each server
* `cache_ttl` (Duration, optional, default: `0 sec`) - tells how long LDAP connector should cache queries results (for default see [caching section](#caching))
* `circuit_breaker` (YAML object, optional, default: `max_retries: 10`, `reset_duration: 10 sec`) - for details see [circuit breaker section](#circuit-breaker)

**Query configuration**

Usually, we would like to configure three main things for defining the way LDAP users and groups are queried:

1. a way to **authenticate client** (LDAP binding; used by all LDAP rules):
   * `bind_dn` (string, optional, default: \[not present]) - a username used to connect to the LDAP service. We can skip this setting when our LDAP service allows for anonymous binding
   * `bind_password` (string, optional, default: \[not present]) - a password used to connect to the LDAP service. We can skip this setting when our LDAP service allows for anonymous binding
2. a way to **search users**. In ROR it can be done using the following YAML keys (under the `users` section) (used by all LDAP rules):
   * `search_user_base_DN` (string, required) - should refer to the base Distinguished Name of the users to be authenticated
   * `user_id_attribute` (string, optional, default: `uid`) - should refer to a unique ID for the user within the base DN
   * `skip_user_search` (boolean, optional, default: `false`) - when you set `user_id_attribute: "cn"` you may want to skip the user search. This optimizes the authentication, which is done in two steps (searching for a user DN and authenticating the user with a given DN). If you configure it to be `true`, the user's DN will be `cn={user_login},{search_user_base_DN}`.
3. a way to **search user groups** (NOT used by [`ldap_authentication`](#ldap_authentication) rule). You can configure all properties under the `groups` section in LDAP connector configuration.

   In ROR, depending on LDAP schema, a relation between users and groups can be defined in:

   1. Group entry - it has an attribute that refers to User entries (`mode: search_groups_in_group_entries`this is the default):
      * `search_groups_base_DN` (required) - should refer to the base Distinguished ID of the groups to which these users may belong
      * `group_id_attribute` (string, optional, default: `cn`) - is the LDAP group object attribute that contains the IDs of the ROR groups
      * `group_name_attribute` (string, optional, default: group\_id\_attribute) - is the LDAP group object attribute that contains the [name](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/structured-groups.md) of the ROR groups
      * `unique_member_attribute` (string, optional, default: `uniqueMember`) - is the LDAP group object attribute that contains the IDs of the ROR groups
      * `group_search_filter` (string, optional, default: `(cn=*)`) - is the LDAP search filter (or filters) to limit the user groups returned by LDAP. By default, this filter will be joined (with `&`) with `unique_member_attribute=user_dn` filter resulting in this LDAP search filter: `(&YOUR_GROUP_SEARCH_FILTER(unique_member_attribute=user_dn))`.
      * `group_attribute_is_dn` (boolean, optional, default: `true`) -
        * when `true` the search filter will look like that: `(&YOUR_GROUP_SEARCH_FILTER(unique_member_attribute={USER_DN}))`
        * then `false` the search filer will look like that: `(&YOUR_GROUP_SEARCH_FILTER(unique_member_attribute={USER_ID_ATTRIBUTE_VALUE}))`
      * `server_side_groups_filtering` (boolean, optional, default: `false`) - by default ROR's LDAP connector asks for all groups of the given user. The group filtering is done on ROR's side. It allows ROR to cache them efficiently. But in some cases (e.g. when the user has hundreds of groups), it's better to filter them on the LDAP server side. If this setting is `true`, LDAP will only be queried for a certain subset of the user groups (defined by the `groups_any_of`/`groups_all_of` subrule of the `ldap_authorization`/`ldap_auth` rule). Note, however, that ONLY the returned subset of the user's groups is cached. See [groups caching details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/caching.md#group-caching) for a deep explanation.
      * `nested_groups_depth` (positive int, optional, no default) - it defines how deep ROR should ask LDAP to extract the nested LDAP groups. See the [nested groups support section](#nested-ldap-groups-support) for details.
   2. User entry - it has an attribute that refers to Group entries (`mode: search_groups_in_user_entries` has to be set to use this strategy):
      * `search_groups_base_DN` (string, required) - should refer to the base Distinguished ID of the groups to which these users may belong
      * `group_id_attribute` (string, optional, default: `cn`) - is the LDAP group object attribute that contains the IDs of the ROR groups
      * `groups_from_user_attribute` (string, optional, default: `memberOf`) - is the LDAP user object attribute that contains the names of the ROR groups
      * `group_search_filter` (string, optional, default: `(objectClass=*)`) is the LDAP search filter (or filters) to limit the user groups returned by LDAP
      * `nested_groups_depth` (positive int, optional, no default) - it defines how deep ROR should ask LDAP to extract the nested LDAP groups. When this setting is configured, ROR needs to know what group's attribute holds the parent group ID - it can be set using `unique_member_attribute` (the default is the `uniqueMember` value). See the [nested groups support section](#nested-ldap-groups-support) for details.

Examples:

```
group_search_filter: "(objectClass=group)"
group_search_filter: "(objectClass=group)(cn=application*)"
group_search_filter: "(cn=*)" # basically no group filtering
```

**Caching**

Too many calls made by ROR to our LDAP service can sometimes be problematic (eg. when one LDAP connector is used in many rules). The problem can be simply solved by using caching functionality. Caching can be configured per LDAP connector or per LDAP rule (see [`ldap_auth`](#ldap_auth), [`ldap_authentication`](#ldap_authentication), [`ldap_authorization`](#ldap_authorization) rules). By default cache is disabled. We can enable it by setting `cache_ttl` > `0 sec`. In the cache will be stored only results of successful requests - info about authentication results and/or returned LDAP groups for the given credentials. When LDAP connector level cache is used any rule that uses the connector can take advantage of cached results. When we configure `cache_ttl` at the LDAP rule level, the results of LDAP calls made by the rule will be stored in the cache. Other LDAP rules won't have access to this cache. See [caching details](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/caching.md) for deep explanation.

**Circuit Breaker**

The LDAP connector is equipped by default with a circuit breaker functionality. The circuit breaker can disable the connector from sending new requests to the server when it doesn't respond properly. After receiving a configurable number of failed responses in a row, the circuit breaker feature disables sending any new requests by terminating them immediately with an exception. After a configurable amount of time, the circuit breaker feature allows one request to pass again. If it succeeds, the connector goes back to normal operation. If not, a test request is sent again after a configurable amount of time. A general description of the concept could be found on [wiki](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) and more about specific implementation could be found in [library documentation](https://monix.io/docs/current/catnap/circuit-breaker.html).

The circuit breaker feature can be customized to adapt to specific needs using the following configuration parameters:

* `max_retries` is the number of failed responses in a row that will trigger the circuit breaker.
* `reset_duration` defines how long the circuit breaker feature will block the incoming requests before starting to send one test request. to the LDAP server.

**LDAP Server discovery**

The LDAP connector can get all LDAP hostnames from the DNS server rather than from the configuration file. By default `_ldap._tcp` SRV records are used for that, but any other SRV record can be configured.

The simplest configuration example of an LDAP connector instance using server discovery is:

```yaml
    - name: ldap
      server_discovery: true
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

This configuration is using the system DNS to fetch all the `_ldap._tcp` SRV records which are expected to contain the hostname and port of all the LDAP servers we should connect to. Each SRV record also has priority and weight assigned to it which determine the order in which they should be contacted. Records with a lower priority value will be used before those with a higher priority value. The weight will be used if there are multiple service records with the same priority, and it controls how likely each record is to be chosen. A record with a weight of 2 is twice as likely to be chosen as a record with the same priority and a weight of 1.

The server discovery mechanism can be optionally configured further, by adding a few more configuration parameters, all of which are optional:

* `record_name` - DNS SRV record name. By default it's `_ldap._tcp`, but could be `_ldap._tcp.domainname` or any custom value.
* `dns_url` - Address of non-default DNS server in form `dns://IP[:PORT]`. By default, the system DNS is used.
* `ttl` - DNS cache timeout. Specifies how long values from DNS will be kept in the cache. Default is 1h.
* `use_ssl` - Use `true` when SSL should be used for LDAP connections. Default is `false` which means that SSL won't be used.

Example:

```yaml
    - name: ldap
      server_discovery:
        record_name: "_ldap._tcp.example.com"
        dns_url: "dns://192.168.1.100"
        ttl: "3 hours"
        use_ssl: true
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

**Nested LDAP groups support**

Let's imagine we have the following groups in LDAP:

* `employees`
* `it`
* `developers`
* `sales`
* `managers`

And there is `John Dev` who is assigned to `developers` groups and `Alize Man` whos group is `managers`.

Moreover, we know that:

* to `it` group belongs all users who are `developers`
* to `sales` group belongs all users who are `managers`
* to `employees` groups belongs all users who are from `it` or `sales`

The groups configuration like the above, in ROR, we call nested LDAP groups.

By default, when ROR searches for eg. `John Dev`'s groups, it is going to get the `developers` group only. But taking into consideration the nested groups configuration, we know that `John Dev` belongs to the following groups: `developers`, `it` & `employees`. Sometimes it'd be nice to have them all available in ROR's LDAP authorization rule.

ROR extracts nested groups by making additional search queries to LDAP. It asks LDAP: *tell me which groups `developers` belongs to?*. LDAP should return: `it`. Then ROR asks again: *so, tell me which groups `it` belongs to?*. LDAP answers: `employees`. And again, ROR asks: *Tell me which groups `empoyees` belongs to?*. LDAP should say: *no groups found*. And this is the point where ROR stops. ROR did additional 3 queries to LDAP to establish that `John Dev` belongs additionally to `it` and `employees` group.

As you probably noticed, enabling nested groups extraction can be costly. ROR obviously tries to do its best to reduce the cost eg. by caching (if cache is enabled) or extracting each unique group always only once during the groups call handling. To reduce the cost more, you can define the depth of the extraction by providing `nested_groups_depth` (the presence of the setting enables the feature, so you have to configure it to enable the nested groups extraction).

Let's say we configured it like that: `nested_groups_depth: 1`. In the example above ROR asks only once: *tell me which groups `developers` belongs to?*. After the LDAP's response: `it` there won't be any more queries. That's because of the depth equaled 1.

**ROR with LDAP - examples**

In this example, users' credentials are validated via LDAP. The groups associated with each validated user, are resolved using the same LDAP server.

**Simpler: authentication and authorization in one rule**

```yaml
readonlyrest:

    access_control_rules:

    - name: Accept requests from users in group team1 on index1
      type: allow                                           # Optional, defaults to "allow", will omit from now on.
      ldap_auth:
        name: "ldap1"                                       # ldap name from below 'ldaps' section
        groups_any_of: ["g1", "g2"]                                # group within 'ou=Groups,dc=example,dc=com'
      indices: ["index1"]

    - name: Accept requests from users in group team2 on index2
      ldap_auth:
        name: "ldap2"
        groups_any_of: ["g3"]
        cache_ttl_in_sec: 60
      indices: ["index2"]

    ldaps:

    - name: ldap1
      host: "ldap1.example.com"
      port: 389
      ssl_enabled: false
      ssl_trust_all_certs: true
      ignore_ldap_connectivity_problems: true
      bind_dn: "cn=admin,dc=example,dc=com"
      bind_password: "password"
      users:
        search_user_base_DN: "ou=People,dc=example,dc=com"
        user_id_attribute: "uid"
      groups:
        mode: 'search_groups_in_group_entries'                # available options: 'search_groups_in_group_entries' (default), 'search_groups_in_user_entries' 
        search_groups_base_DN: "ou=Groups,dc=example,dc=com"
        unique_member_attribute: "uniqueMember"                   
        group_search_filter: "(objectClass=group)(cn=application*)"
        group_id_attribute: "cn"
      connection_pool_size: 20
      connection_timeout: 1s
      request_timeout: 2s
      connection_health_check_interval: 30s
      connection_max_age: 5min
      cache_ttl: 60s                                            
      circuit_breaker:                                        
        max_retries: 2                                           
        reset_duration: 5s                                       

    # High availability LDAP settings (using "hosts", rather than "host")
    - name: ldap2
      hosts:
      - "ldaps://ssl-ldap2.foo.com:636"
      - "ldaps://ssl-ldap3.foo.com:636"
      ha: "ROUND_ROBIN"
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"

    # Server discovery variant
    - name: ldap3
      server_discovery: true
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:  
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

**Advanced: authentication and authorization in separate rules**

```yaml
readonlyrest:
  
  global_settings:
    response_if_req_forbidden: Forbidden by ReadonlyREST ES plugin

  access_control_rules:

  - name: Accept requests to index1 from users with valid LDAP credentials, belonging to LDAP group'team1'
    ldap_authentication: "ldap1"
    ldap_authorization:
      name: "ldap1"                                       # ldap name from 'ldaps' section
      groups_any_of: ["g1", "g2"]                         # group within 'ou=Groups,dc=example dc=com'
    indices: ["index1"]

  - name: Accept requests to index2 from users with valid LDAP credentials, belonging to LDAP group 'team2'
    ldap_authentication:
      name: "ldap2"
      cache_ttl: 60s
    ldap_authorization:
      name: "ldap2"
      groups_any_of: ["g3"]
      cache_ttl: 60s
    indices: ["index2"]

  ldaps:

  - name: ldap1
    host: "ldap1.example.com"
    port: 389
    ssl_enabled: false
    ssl_trust_all_certs: true
    ignore_ldap_connectivity_problems: true
    bind_dn: "cn=admin,dc=example,dc=com"
    bind_password: "password"
    users:
      search_user_base_DN: "ou=People,dc=example,dc=com"
      user_id_attribute: "uid"
    groups:
      search_groups_base_DN: "ou=Groups,dc=example,dc=com"
      unique_member_attribute: "uniqueMember"                   
    connection_pool_size: 20                                  
    connection_timeout: 1s                                   
    request_timeout: 2s
    connection_health_check_interval: 30s
    connection_max_age: 5min                                      
    cache_ttl: 60s                                            

  # High availability LDAP settings (using "hosts", rather than "host")
  - name: ldap2
    hosts:
    - "ldaps://ssl-ldap2.foo.com:636"
    - "ldaps://ssl-ldap3.foo.com:636"
    ha: "ROUND_ROBIN"
    users: 
      search_user_base_DN: "ou=People,dc=example2,dc=com"
    groups:
      search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

#### External Basic Auth

ReadonlyREST will forward the received `Authorization` header to a website of choice and evaluate the returned HTTP status code to verify the provided credentials. This is useful if you already have a web server with all the credentials configured and the credentials are passed over the `Authorization` header.

```yaml
readonlyrest:
  access_control_rules:

  - name: "::Tweets::"
    methods: GET
    indices: ["twitter"]
    external_authentication: "ext1"

  - name: "::Facebook posts::"
    methods: GET
    indices: ["facebook"]
    external_authentication:
      service: "ext2"
      cache_ttl_in_sec: 60

  external_authentication_service_configs:

  - name: "ext1"
    authentication_endpoint: "http://external-website1:8080/auth1"
    success_status_code: 200
    cache_ttl_in_sec: 60
    http_connection_settings:
      validate: false # SSL certificate validation (default to true)
      connection_timeout_in_sec: 1           # default 2
      socket_timeout_in_sec: 2               # default 5
      connection_request_timeout_in_sec: 1   # default 5  
      connection_pool_size: 20               # default 30

  - name: "ext2"
    authentication_endpoint: "http://external-website2:8080/auth2"
    success_status_code: 204
    cache_ttl_in_sec: 60
```

To define an external authentication service the user should specify:

* `name` for service (then this name is used as id in `service` attribute of `external_authentication` rule)
* `authentication_endpoint` (GET request)
* `success_status_code` - authentication response success status code

Cache can be defined at the service level or/and at the rule level. In the example, both are shown, but you might opt for setting up either.

#### Custom groups providers

This external authorization connector makes it possible to resolve to what groups a users belong, using an external JSON or XML service.

```yaml
readonlyrest:
  access_control_rules:

  - name: "::Tweets::"
    methods: GET
    indices: ["twitter"]
    proxy_auth:
      proxy_auth_config: "proxy1"
      users: ["*"]
    groups_provider_authorization:
      user_groups_provider: "GroupsService"
      groups_any_of: ["group3"]

  - name: "::Facebook posts::"
    methods: GET
    indices: ["facebook"]
    proxy_auth:
      proxy_auth_config: "proxy1"
      users: ["*"]
    groups_provider_authorization:
      user_groups_provider: "GroupsService"
      groups_any_of: ["group1"]
      cache_ttl_in_sec: 60

  proxy_auth_configs:

  - name: "proxy1"
    user_id_header: "X-Auth-Token"                         

  user_groups_providers:

  - name: GroupsService
    groups_endpoint: "http://localhost:8080/groups"
    auth_token_name: "token"
    auth_token_passed_as: QUERY_PARAM                              # HEADER OR QUERY_PARAM
    response_groups_ids_json_path: "$..groups[?(@.id)].id"         # JSON-path style, see https://github.com/json-path/JsonPath
    response_groups_names_json_path: "$..groups[?(@.name)].name"   # optional, JSON-path style, see https://github.com/json-path/JsonPath
    cache_ttl_in_sec: 60
    http_connection_settings:
      connection_timeout_in_sec: 1                        
      socket_timeout_in_sec: 2                            
      connection_request_timeout_in_sec: 2                
      connection_pool_size: 20                            
```

In example above, a user is authenticated by reverse proxy and then external service is asked for groups for that user. If groups returned by the service contain any group declared in `groups` list, user is authorized and rule matches.

Also in this rule, the `groups` clause can be replaced by `group_and` to require the user must belong to all the listed groups:

```yaml
  groups_provider_authorization:
    user_groups_provider: "GroupsService"
    groups_all_of: ["group1", "group2"] # match when user belongs to ALL listed groups
```

To define user groups provider you should specify:

* `name` - (string, required) - identifier of the service which needs to be passed in the `groups_provider_authorization` rule (`user_groups_provider` attribute)
* `groups_endpoint` - (string, required) - service with groups endpoint
* `auth_token_name` - (string, required) - user identifier will be passed with this name
* `auth_token_passed_as` - (string, required, can be one of `HEADER` or `QUERY_PARAM`) - the way how user identifier is passed to the service
* `http_method` - (string, optional, can be one of `GET` (default), `POST`) - HTTP method used to send request
* `response_group_ids_json_path`,`response_groups_json_path` (string, required) - response can be unrestricted, but you have to specify [JSON Path](https://github.com/json-path/JsonPath) for group ID list
* `response_group_names_json_path` (string, optional, default: `response_group_ids_json_path`)- [JSON Path](https://github.com/json-path/JsonPath) for [groups name](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/structured-groups.md) list (both arrays, available at `response_group_ids_json_path` and `response_group_names_json_path`, have to have the same length and have the same order)

As usual, the cache behaviour can be defined at service level or/and at rule level.

#### JSON Web Token (JWT) Auth

The information about the username can be extracted from the "claims" inside a JSON Web Token. Here is an example.

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token with a viewer group
    kibana:
      access: ro
    jwt_auth:
      name: "jwt_provider_1"
      groups_any_of: ["viewer"]

  - name: Valid JWT token with a writer group
    kibana:
      access: rw
    jwt_auth:
      name: "jwt_provider_1"
      groups_any_of: ["writer"]

  - name: Valid JWT token with a viewer and writer groups
    kibana:
      access: rw
    jwt_auth:
      name: "jwt_provider_1"
      groups_all_of: ["writer", "viewer"]

  jwt:
  - name: jwt_provider_1
    signature_algo: HMAC # can be NONE, RSA, HMAC (default), and EC
    signature_key: "your_signature_min_256_chars"
    user_claim: email
    group_ids_claim: resource_access.client_app.group_ids # JSON-path style, see https://github.com/json-path/JsonPath
    group_names_claim: resource_access.client_app.group_names # optional, JSON-path style, see https://github.com/json-path/JsonPath
    header_name: Authorization
```

You can verify groups assigned to the user with the groups logic (`groups_any_of`/`groups_all_of`/`groups_not_any_of`/`groups_not_all_of`/`groups_combined`) described in the [Groups logic](#user_belongs_to_groups) section.

To define JWT provider, you need to provide:

* `name` - (string, required) - identifier of the JWT provider, which needs to be passed in the `jwt_auth` rule
* `user_claim` - (string, optional) - indicates which field in the JSON will be interpreted as the username. To define the claim path, use [JSON-path](https://github.com/json-path/JsonPath) syntax.
* `group_ids_claim` (string, optional) - indicates which field in the JSON will be interpreted as the group ID. To define the claim path, use [JSON-path](https://github.com/json-path/JsonPath) syntax.
* `group_names_claim` (string, optional, defaults to `group_ids_claim`) - indicates which field in the JSON will be interpreted as the [group name](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/details/structured-groups.md). To define the claim path, use [JSON-path](https://github.com/json-path/JsonPath) syntax.
* `header_name` (string, optional, defaults to `Authorization`) - HTTP header name carrying the JWT Token, can be used if we expect the JWT Token in a custom header (i.e. [Google Cloud IAP signed headers](https://cloud.google.com/iap/docs/signed-headers-howto)).
* `signature_key` (string, required) - shared secret between the issuer of the JWT and ReadonlyREST. It is used to verify the cryptographical "paternity" of the message.
* `signature_algo` (string, optional, can be one of `NONE`, `RSA`, `HMAC` (default), and `EC`) - indicates the family of cryptographic algorithms used to validate the JWT.

**⚠️IMPORTANT**: As described above, both claim names (`user_claim` and `group_ids_claim`) are optional, but:

* `jwt_authentication` rule requires `user_claim` to be defined in the JWT provider
* `jwt_authorization` rule requires `group_ids_claim` to be defined in the JWT provider
* `jwt_auth` rule requires both those settings

**Accepted signature\_algo values**

The value of this configuration represents the cryptographic family of the JWT protocol. Use the below table to tell what value you should configure, given a JWT token sample. You can decode sample JWT token using an [online tool](https://jwt.io/).

| Algorithm declared in JWT token | `signature_algo` value |
| ------------------------------- | ---------------------- |
| NONE                            | **None**               |
| HS256                           | **HMAC**               |
| HS384                           | **HMAC**               |
| HS512                           | **HMAC**               |
| RS256                           | **RSA**                |
| RS384                           | **RSA**                |
| RS512                           | **RSA**                |
| PS256                           | **RSA**                |
| PS384                           | **RSA**                |
| PS512                           | **RSA**                |
| ES256                           | **EC**                 |
| ES384                           | **EC**                 |
| ES512                           | **EC**                 |

### Audit

ReadonlyREST can gather audit events that contain information regarding a request and its processing by the system, which can then be forwarded to predefined outputs. You can use the available information from the audit events to construct interesting visual representations, such as Kibana dashboards or any other visualization tool. For details see [Audit configuration](/develop/elasticsearch/audit).

### Other settings

#### Disabling ReadonlyREST ACL

The ReadonlyREST ACL can be temporarily disabled without uninstalling the plugin by setting `readonlyrest.enable: false` in the configuration. The default value is `true`. When disabled, all requests will bypass the ACL rules.

Example:

```yaml
readonlyrest:
  enable: false
```

#### Global settings

The `readonlyrest.global_settings` section contains various settings that affect different parts of the ACL:

**`prompt_for_basic_auth`**

When set to `true`, ROR will return HTTP 401 instead of 403 when authentication fails. This prompts browsers to show a basic auth dialog. This is particularly useful when not using ReadonlyREST Kibana plugin and wanting to take advantage of Kibana's default behavior. Defaults to `false`. But we don't recommend to change this default behaviour.

Example:

```yaml
readonlyrest:
  global_settings:
    prompt_for_basic_auth: true
```

**`response_if_req_forbidden`**

Customize the response message returned when a request is forbidden by any ACL block. This can be overridden at the block level using the `type.response_message` setting (see section on [Unauthorized response configuration](#unauthorized-response-configuration)). Defaults to "Forbidden by ReadonlyREST ES plugin".

Example:

```yaml
readonlyrest:
  global_settings:
    response_if_req_forbidden: "You shall not pass!"
```

**`fls_engine`**

Specifies which Field Level Security engine to use for document filtering. Can be either "es\_with\_lucene" (default) or "es". This setting determines how ReadonlyREST handles field-level security with the [`fields` rule](#fields).

* **es\_with\_lucene** (default): Hybrid approach where most FLS operations are handled by Elasticsearch, with Lucene as a fallback for complex cases. Provides full functionality but requires ReadonlyREST to be installed on all nodes.
* **es**: FLS is handled only by Elasticsearch without Lucene fallback. This mode doesn't require ReadonlyREST on all nodes but has limitations for certain request types.

For detailed information about capabilities and limitations of each engine, see [FLS engine documentation](/develop/elasticsearch/fls-engine).

Example:

```yaml
readonlyrest:
  global_settings:
    fls_engine: es
```

**`username_case_sensitivity`**

Controls username comparison behavior across all authentication rules. Can be either "case\_sensitive" (default) or "case\_insensitive". Useful when integrating with case-insensitive systems.

Example:

```yaml
readonlyrest:
  global_settings:
    username_case_sensitivity: case_insensitive
```

**`users_section_duplicate_usernames_detection`**

When enabled, ROR validates the `users` section during startup to ensure there are no duplicate usernames defined. This helps prevent configuration errors. Defaults to `true`. In some scenarios you may want to disable it.

Example:

```yaml
readonlyrest:
  global_settings:
    users_section_duplicate_usernames_detection: false
```

### ACL Troubleshooting

The main issues seen in support cases:

* Bad ordering or ACL blocks. Remember that the ACL is evaluated sequentially, block by block. And the first block whose rules all match is accepted.
* Users don't know how to read the `HIS` field in the logs, which instead is crucial because it contains a trace of the evaluation of rules and blocks.
* LDAP configuration: LDAP is tricky to configure in any system. Configure ES root logger to `DEBUG` editing `$ES_PATH_CONF/config/log4j2.properties` to see a trace of the LDAP messages.

#### Interpreting ACL logs

ReadonlyREST prints a log line for each incoming request (this can be selectively avoided on ACL block level using the `verbosity` rule).

**Allowed requests**

This is an example of a request that matched an ACL block (allowed) and has been let through to Elasticsearch.

> ALLOWED by { name: 'Admins', policy: ALLOW, rules: \[groups\_any\_of, kibana] } req={ ID:44d12d75-4340-4e3e-9507-5bb439db9d80-1159548962#7510, TYP:SearchRequest, CGR:\<N/A>, USR:admin, BRS:true, ACT:indices:data/read/search, OA:192.168.65.1/32, XFF:null, DA:172.19.0.2/32, IDX:*, MET:GET, PTH:/\_search, CNT:\<N/A>, HDR:Accept=*/*, User-Agent=curl/8.7.1, Host=localhost:19200, Authorization=, HIS:\[KIBANA: NOT\_MATCHED (AUTH\_FAIL (Username mismatch)) -> RULES:\[auth\_key->false]], \[Admins: MATCHED -> RULES:\[groups\_any\_of->true, kibana->true] RESOLVED:\[user=admin;group=Administrators;av\_groups=Administrators;indices=*;kibana\_idx=.kibana]], }

**Explanation**

The log line immediately states that this request has been allowed by an ACL block called "Admins". Immediately follows a summary of the requests' anatomy. The format is semi-structured, and it's intended for humans to read quickly, it's not JSON, or anything else.

Similar information gets logged in JSON format via [audit events](#audit) feature described ealier.

Here is a glossary:

* `ID`: ReadonlyREST-level request id
* `TYP`: String, the name of the Java class that internally represent the request type (very useful for debug)
* `CGR`: String, the request carries a "current group" header (used for multi-tenancy).
* `USR`: String, the user name ReadonlyREST was able to extract from Basic Auth, JWT, LDAP, or other methods as specified in the ACL.
* `BRS`: Boolean, an heuristic attempt to tell if the request comes from a browser.
* `ACT`: String, the elasticsearch level action associated with the request. For a list of actions, see our [actions rule docs](#actions).
* `OA`: IP Address, originating address (source address) of the TCP connection underlying the http session.
* `IDX`: Strings array: the list of indices affected by this request.
* `MET`: String, HTTP Method
* `CNT`: String, HTTP body content. Comes as a summary of its length, full body of the request is available in debug mode.
* `HDR`: String array, list of HTTP headers, headers' content is available in debug mode.
* `HIS`: Chronologically ordered history of the ACL blocks and their rules being evaluated. When a block is `NOT_MATCHED`, the denial cause appears in parentheses after the block name (e.g. `AUTH_FAIL(...)`, `GROUPS_AUTH_FAIL(...)`, `AUTHZ_FAIL`, `IDX_NOT_FOUND`). See [Denial causes in HIS](#denial-causes-in-his) for a full reference.

In the example, the block `Admins` is allowing the request because all the rules in this block evaluate to `true`.

**Forbidden requests**

This is an example of a request that gets forbidden by ReadonlyREST ACL.

```
FORBIDDEN by default req={ ID:af26efdb-9193-424d-8dc9-d2cda617842a-1466512324#7967, TYP:SearchRequest, CGR:<N/A>, USR:admin (attempted), BRS:true, ACT:indices:data/read/search, OA:192.168.65.1/32, XFF:null, DA:172.19.0.2/32, IDX:*, MET:GET, PTH:/_search, CNT:<N/A>, HDR:Accept=*/*, User-Agent=curl/8.7.1, Host=localhost:19200, Authorization=<OMITTED>, HIS:[KIBANA: NOT_MATCHED (AUTH_FAIL (Username mismatch)) -> RULES:[auth_key->false]], [Admins: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Invalid password); {user1,user2}:GROUPS_AUTH_FAIL (No user's groups allowed))) -> RULES:[groups_any_of->false]], [End users: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Invalid password); {user1,user2}:AUTH_FAIL (Username mismatch))) -> RULES:[groups_any_of->false]], [Business users: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Invalid password); user1:AUTH_FAIL (Username mismatch); user2:GROUPS_AUTH_FAIL (No user's groups allowed))) -> RULES:[groups_any_of->false]] }
```

The above rule gets forbidden "by default". This means that no ACL block has matched the request, so ReadonlyREST's default policy of rejection takes effect.

**Requests finished with INDEX NOT FOUND**

This is an example of such request:

```
INDEX NOT FOUND req={ ID:5cdbd3ec-2093-426d-85c9-b2d0be7361b5-746941746#8477, TYP:GetIndexRequest, CGR:<N/A>, USR:user1 (attempted), BRS:true, ACT:indices:admin/get, OA:192.168.65.1/32, XFF:null, DA:172.19.0.2/32, IDX:nonexistent, MET:GET, PTH:/nonexistent, CNT:<N/A>, HDR:Accept=*/*, User-Agent=curl/8.7.1, Host=localhost:19200, Authorization=<OMITTED>, HIS:[KIBANA: NOT_MATCHED (AUTH_FAIL (Username mismatch)) -> RULES:[auth_key->false]], [Admins: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Username mismatch); {user1,user2}:GROUPS_AUTH_FAIL (No user's groups allowed))) -> RULES:[groups_any_of->false]], [End users: NOT_MATCHED (IDX_NOT_FOUND) -> RULES:[groups_any_of->true, kibana->true, indices->false]], [Business users: NOT_MATCHED (IDX_NOT_FOUND) -> RULES:[groups_any_of->true, kibana->true, indices->false]] }
```

The state above is only possible for read-only ES requests (ES requests which don't change ES cluster state) for a block containing an `indices` rule. If all other rules within the block are matched, but only the `indices` rule is mismatched, the final state of the block is forbidden due to an index not found.

**Denial causes in `HIS`**

When a block is `NOT_MATCHED`, a denial cause appears in parentheses after the block name. These causes make it straightforward to distinguish between authentication failures (wrong credentials) and authorization failures (missing permissions) without additional debugging.

| Cause                         | Meaning                                                                                                                                            |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_FAIL(details)`          | Authentication failed. The human-readable `details` string describes the specific reason (e.g. wrong username, bad password, missing credentials). |
| `GROUPS_AUTH_FAIL(details)`   | Groups-based authorization failed. The `details` string describes which user definitions were tried and why each one was rejected.                 |
| `AUTHZ_FAIL`                  | A non-authentication rule (e.g. `indices`, `actions`) caused the block to be rejected.                                                             |
| `IDX_NOT_FOUND`               | All auth rules matched but the requested index does not exist. Applies only to read-only requests.                                                 |
| `ALIAS_NOT_FOUND`             | All auth rules matched but the requested alias does not exist. Applies only to read-only requests.                                                 |
| `TPL_NOT_FOUND`               | All auth rules matched but the requested index template does not exist. Applies only to read-only requests.                                        |
| `IMPERSONATION_NOT_SUPPORTED` | The rule being evaluated does not support impersonation, which was attempted by the request.                                                       |
| `IMPERSONATION_NOT_ALLOWED`   | Impersonation was attempted but the impersonator is not allowed to impersonate the target user under this block.                                   |

**`blocks_history` in audit logs**

The same per-block information is also available in structured form in the `blocks_history` field of audit log entries. Each element in the array represents one evaluated ACL block:

```json
"blocks_history": [
  {
    "block_name": "KIBANA",
    "matched": false,
    "forbidden_cause": "AUTH_FAIL(Username mismatch)"
  },
  {
    "block_name": "Admins",
    "matched": true,
    "forbidden_cause": null
  }
]
```

Each entry has three fields:

* `block_name` — the name of the ACL block
* `matched` — `true` if the block permitted the request, `false` if it was rejected
* `forbidden_cause` — the denial reason in the same format as `HIS`, or `null` if the block matched

#### Enabling debug logs

You can configure Elasticsearch logging by editing the `$ES_PATH_CONF/log4j2.properties` file. See [the official Elasticsearch logging documentation](https://www.elastic.co/docs/deploy-manage/deploy/self-managed/configure-elasticsearch#logging) for details.

**Global debug mode**

To enable debug logging globally, set the root logger level to `debug`:

```
rootLogger.level = debug
```

**Only ReadonlyREST debug mode**

To enable debug logging only for ReadonlyREST, append the following to `log4j2.properties`:

```
logger.ror.name=tech.beshu.ror
logger.ror.level=debug
```

**Trick: log requests to different files**

Use the following `log4j2.properties` snippet to write ReadonlyREST ACL/request logs to a dedicated rolling file:

```
# ReadonlyREST ACL/request log -> separate rolling file

appender.readonlyrest_acl_rolling.type = RollingFile
appender.readonlyrest_acl_rolling.name = readonlyrest_acl_rolling
appender.readonlyrest_acl_rolling.fileName = ${sys:es.logs}_readonlyrest_acl.log
appender.readonlyrest_acl_rolling.filePattern = ${sys:es.logs}_readonlyrest_acl-%d{yyyy-MM-dd}.log.gz

appender.readonlyrest_acl_rolling.layout.type = PatternLayout
appender.readonlyrest_acl_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%n

appender.readonlyrest_acl_rolling.policies.type = Policies
appender.readonlyrest_acl_rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.readonlyrest_acl_rolling.policies.time.interval = 1
appender.readonlyrest_acl_rolling.policies.time.modulate = true

logger.readonlyrest_acl.name = tech.beshu.ror
logger.readonlyrest_acl.level = info
logger.readonlyrest_acl.appenderRef.readonlyrest_acl_rolling.ref = readonlyrest_acl_rolling
logger.readonlyrest_acl.additivity = false

# Optional: exclude noisy service users
logger.readonlyrest_acl.filter.regex.type = RegexFilter
logger.readonlyrest_acl.filter.regex.regex = .*USR:(kibana|beat|logstash),.*
logger.readonlyrest_acl.filter.regex.onMatch = DENY
logger.readonlyrest_acl.filter.regex.onMismatch = ACCEPT
```

This configuration keeps ReadonlyREST ACL/request entries out of the main Elasticsearch log and writes them to a separate daily-rotated file instead. ReadonlyREST logs one line per incoming request, and the tech.beshu.ror logger is the logger to target for this purpose.

## Licensing

### GPLv3 License

ReadonlyREST Free (Elasticsearch plugin) is released under the GPLv3 license. For what this kind of software concerns, this is identical to GPLv2, that is, you can treat ReadonlyREST as you would treat Linux code. The big difference from Linux is that here you can ask for a commercial license and stop thinking about legal implications.

Here is a practical summary of what dealing with GPLv3 means:

#### You CAN

* Distribute for free or commercially a version (partial or total) of this software (along with its license and attributions) as part of a product or solution that is also **released under GPL-compatible license**. Please notify us if you do so.
* Use a modified version **internally to your company** without making your changes available under the GPLv3 license.
* Distribute for free or commercially a modified version (partial or total) of this software, provided that the source is contributed back as pull request to

  the [original project](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin) or publicly made available under the GPLv3 or compatible license.

#### You CANNOT

* Sell or give away a modified version of the plugin (or parts of it, or any derived work) without publishing the modified source under GPLv3 compatible licenses.
* Modify the code for a paying client without immediately contributing your changes back to this project's GitHub as a pull request, or alternatively publicly release said fork under GPLv3 or compatible license.

#### GPLv3 license FAQ

**1. Q**: I sell a proprietary software solution that already includes many other OSS components (i.e. Elasticsearch). Can I bundle also ReadonlyREST into it?

> **A**: No, GPLv3 does not allow it. But hey, no problem, just go for the [Enterprise subscription](https://readonlyrest.com/enterprise).

**2. Q**: I have a SaaS and we want to use a version of ReadonlyREST for Elasticsearch (as is, or modified), do I need a commercial license?

> **A**: No, you don't. Go for it! However if you are using Kibana, consider the [Enterprise offer](https://readonlyrest.com/enterprise) which includes multi-tenancy.

**3. Q**: I'm a consultant and I will charge my customer for modifying this software and they will not sell it as a product or part of their product.

> **A**: This is fine with GPLv3.

### Dual-license

Please don't hesitate to [contact us](mailto:info@readonlyrest.com) for a re-licensed copy of this source. Your success is what makes this project worthwhile, don't let legal issues slow you down.

See [commercial license FAQ page](/develop/commercial) for more information.


# Audit configuration

ReadonlyREST can collect audit events containing information about a request and how the system has handled it and send them to configured outputs. Here is an example of the data points contained in each audit event. We can leverage all this information to build interesting Kibana dashboards, or any other visualization.

```json
{
    "error_message": null,
    "headers": [
      "Accept",
      "Authorization",
      "content-length",
      "Host",
      "User-Agent"
    ],
    "acl_history": "[[::LOGSTASH::->[auth_key->false]], [kibana->[auth_key->false]], [::RO::->[auth_key->false]], [::RW::->[kibana->true, indices->true, auth_key->true]]]",
    "origin": "127.0.0.1",
    "final_state": "ALLOWED",
    "task_id": 1158,
    "type": "SearchRequest",
    "req_method": "GET",
    "path": "/readonlyrest_audit-2017-06-29/_search?pretty",
    "indices": [
      "readonlyrest_audit-2017-06-29"
    ],
    "@timestamp": "2017-06-30T09:41:58Z",
    "content_len_kb": 0,
    "error_type": null,
    "processingMillis": 0,
    "action": "indices:data/read/search",
    "matched_block": "::RW::",
    "id": "933409190-292622897#1158",
    "content_len": 0,
    "logged_user": "simone",
    "presented_identity": "simone"
  }
```

## Configuration

The audit outputs are disabled by default. To enable them, add `audit.enabled: true` and optionally configure `audit.outputs`.

**Note**: Even when `audit.enabled` is `false` or not set, the built-in ACL log is a special case — it writes a human-readable decision line to Elasticsearch logs for every request by default. See [The default ACL log](#the-default-acl-log) below.

The following is the explicit equivalent of the default behaviour when no `audit` section is configured at all:

```yaml
readonlyrest:
  audit:
    enabled: false                 # audit outputs disabled
    default_acl_log_enabled: true  # ACL log still fires for every request
```

When `audit.enabled: true` and no `outputs` are specified, ROR defaults to storing events in a local Elasticsearch index.

### Global audit settings

```yaml
readonlyrest:
  audit:
    enabled: true                   # enable/disable the entire audit subsystem
    default_acl_log_enabled: true   # enable/disable the built-in ACL log (default: true)
    outputs:
    - type: index
      name: my-index-sink           # optional name, used for per-block sink routing
```

| Setting                   | Default                | Description                                      |
| ------------------------- | ---------------------- | ------------------------------------------------ |
| `enabled`                 | `false`                | Master switch for the audit subsystem            |
| `default_acl_log_enabled` | `true`                 | Controls the built-in ACL log output (see below) |
| `outputs`                 | default `index` output | List of audit outputs                            |

Each entry in `outputs` accepts an optional `name` field. Names are only needed for per-block routing: when you want a specific block to send events to only a subset of outputs, you reference them by name using `enabled_audit_sinks` or `disabled_audit_sinks` (see [Block-level audit control](#block-level-audit-control)). If you do not need per-block routing, you can omit `name` from all outputs.

### The default ACL log

When `default_acl_log_enabled: true` (the default), ROR writes a human-readable ACL decision line to Elasticsearch logs for every request, using the logger named `tech.beshu.ror.accesscontrol.logging.AccessControlListLoggingDecorator`. This happens regardless of whether any `outputs` are configured.

The default ACL log is exposed as a named output with the reserved name `default_acl_log`. You can use this name in block-level `enabled_audit_sinks` and `disabled_audit_sinks` to include or exclude it from per-block routing:

```yaml
readonlyrest:
  audit:
    enabled: true
    default_acl_log_enabled: true
    outputs:
    - type: index
      name: my-index-sink

  access_control_rules:

  - name: High-volume service account
    auth_key: svc:secret
    audit:
      # send events only to the index, skip the ACL log line for this noisy block
      enabled_audit_sinks: [my-index-sink]

  - name: Admin users
    auth_key: admin:admin
    # No audit section — all outputs active with default settings
```

To replace the default ACL log with a custom one — for example to send it to a different file — disable it globally and add a `log` output with the `acl` serializer:

```yaml
readonlyrest:
  audit:
    enabled: true
    default_acl_log_enabled: false   # turn off the built-in ACL log
    outputs:
    - type: log
      name: custom-acl-log
      logger_name: my.custom.acl.logger
      serializer:
        type: acl                    # reproduces the built-in ACL log format
```

`logger_name` is the log4j2 logger name that ROR uses when writing to this output (default: `readonlyrest_audit`). Setting a custom value lets you route these log lines to a dedicated appender in `log4j2.properties` — for example to write them to a separate file. See [Custom logging settings via log4j2](#custom-logging-settings-via-log4j2) for an example appender configuration.

The `acl` serializer produces exactly the same single-line human-readable format as the built-in ACL log: a concise decision summary that includes the request identity, matched block, final state, and key request details. The format is unchanged compared to previous versions.

### Block-level audit control

The `audit` section inside each `access_control_rules` block lets you tune audit behaviour per block.

```yaml
access_control_rules:
- name: Example block
  auth_key: user:pass
  audit:
    enabled: true               # default: true — set to false to suppress all audit for this block
    log_allowed_events: true    # default: true — set to false to suppress allowed-request events
    enabled_audit_sinks: []     # whitelist: only these named sinks receive events from this block
    disabled_audit_sinks: []    # blacklist: all sinks except these receive events from this block
```

| Setting                | Default     | Description                                                                                                                                                                 |
| ---------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`              | `true`      | When `false`, no audit events are emitted when this block is matched, regardless of global settings                                                                         |
| `log_allowed_events`   | `true`      | When `false`, allowed requests matched by this block are not written to audit. Denied requests, errors, and index-not-found responses are always written                    |
| `enabled_audit_sinks`  | (all sinks) | Whitelist of sink names. Only the listed sinks receive events from this block. Use sink `name` values from `audit.outputs`, plus `default_acl_log` for the built-in ACL log |
| `disabled_audit_sinks` | (none)      | Blacklist of sink names. All sinks except the listed ones receive events from this block                                                                                    |

`enabled_audit_sinks` and `disabled_audit_sinks` are mutually exclusive — you cannot specify both on the same block.

**⚠️IMPORTANT**: When `audit.enabled: false` for a specific block, there will be no audit events at all when that block is matched — this suppresses both custom outputs and the default ACL log. **This is a change in behaviour from previous versions**, where block-level `audit: {enabled: false}` only suppressed the ES audit sinks while the ACL log continued to write.

#### Per-sink routing example

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index
      name: security-index
    - type: log
      name: ops-log

  access_control_rules:

  - name: Security-sensitive block
    auth_key: admin:admin
    audit:
      # Only write to the security index, skip the ops log and default ACL log
      enabled_audit_sinks: [security-index]

  - name: Noisy read-only block
    auth_key: reader:pass
    audit:
      # Skip allowed events entirely, errors still get written
      log_allowed_events: false
      # Write to ops log only, skip security index for this block
      enabled_audit_sinks: [ops-log]

  - name: Regular block
    auth_key: user:pass
    # No audit section = all sinks active with default settings
```

### Multiple outputs

You can configure multiple audit outputs, including mixing output types:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index
      name: audit-index
    - type: log
      name: audit-log
    - type: data_stream
      name: audit-stream
      enabled: false   # this output is defined but currently disabled
```

Each output can be individually toggled with `enabled: true/false` (default: `true`).

### Backward compatibility

The `verbosity: error` and `verbosity: info` block-level settings from earlier versions are still accepted. They are treated as aliases for `audit: {log_allowed_events: false}` and `audit: {log_allowed_events: true}` respectively.

All other global audit settings (`audit.enabled`, `audit.outputs` and their sub-settings) are unchanged. Existing configurations that do not use the new settings will continue to work without modification. The `default_acl_log_enabled` setting defaults to `true`, so the ACL log continues to fire exactly as before for configurations that do not set it explicitly.

### The 'index' output specific configurations

#### Custom audit indices name and time granularity

By default, the ReadonlyREST audit index name template is `readonlyrest_audit-YYYY-MM-DD`. You can customize the name template using the `index_template` settings.

Example: tell ROR to write on the monthly index.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index
      index_template: "'custom-prefix'-yyyy-MM"  # <--monthly pattern
  ...
```

**⚠️IMPORTANT**: Notice the single quotes inside the double-quoted expression. This is the same syntax used for [Java's SimpleDateFormat](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html).

#### Custom audit cluster

It's possible to set up a custom audit cluster responsible for storing audit events. When a custom cluster is specified, items will be sent to defined cluster nodes instead of the local one.

**⚠️IMPORTANT**: Audit events are sent to audit nodes using a round-robin strategy. All audit nodes must belong to the same Elasticsearch cluster. Otherwise, each audit cluster will contain only a subset of audit events. If you intend to send audit events to multiple clusters, define one output per Elasticsearch cluster.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index
      cluster: ["https://user1:password@auditNode1:9200", "https://user2:password@auditNode2:9200"]
  ...
```

Setting `audit.cluster` is optional, it accepts a non-empty list of audit cluster nodes URIs.

### The 'data\_stream' output specific configurations

#### Custom audit data stream name

To change the default data stream name `readonlyrest_audit`, add the following configuration to your `readonlyrest.yml` config:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
      - type: data_stream
        data_stream: "custom_audit_data_stream"
```

Here, `custom_audit_data_stream` is the Elasticsearch data stream where audit events will be stored.

If the specified data stream does not exist, it will be automatically created by the ReadonlyREST plugin. This creation process includes setting up the following components, each dedicated specifically to the configured data stream:

* A dedicated Index Lifecycle Policy `({{data-stream-name}}-lifecycle-policy)`.
* Necessary index settings and mappings (component templates: `{{data-stream-name}}-mappings` and `{{data-stream-name}}-settings`).
* A customized Index Template (`{{data-stream-name}}-template`).

#### Custom audit cluster

It's possible to set a custom audit cluster responsible for audit events storage. When a custom cluster is specified, items will be sent to defined cluster nodes instead of the local one.

**⚠️IMPORTANT**: Audit events are sent to audit nodes using a round-robin strategy. All audit nodes must belong to the same Elasticsearch cluster. Otherwise, each audit cluster will contain only a subset of audit events. If you intend to send audit events to multiple clusters, define one output per Elasticsearch cluster.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: data_stream
      cluster: ["https://user1:password@auditNode1:9200", "https://user2:password@auditNode2:9200"]
  ...
```

Setting `audit.cluster` is optional, it accepts a non-empty list of audit cluster nodes URIs.

#### Data stream settings

Here are the default settings set for the audit data stream created by the ReadonlyREST plugin:

![Audit data stream](/files/aAeaNTBW2asbVdWZ2avB) ![Audit data stream template](/files/1rSw5sIVBmd0tqKeH0eL) ![Index lifecycle policy defaults](/files/dXNpJ46ftfDAFU7KlpDU)

Managing Elasticsearch data streams, such as the ReadonlyREST audit data stream, should be customized based on your specific use case. Aspects like:

* data retention policies (how long to keep and when to delete data),
* migrating old indices into the new data stream,
* handling transitions between different index lifecycle phases (e.g., hot, warm, cold, delete),

depend on your business requirements, data volume and characteristics, and how the data is analyzed and used.

Therefore, we encourage you to configure these settings yourself to best fit your needs. Elasticsearch provides flexible tools, like Index Lifecycle Management (ILM), that allow automating data management based on user-defined rules. Customizing your configuration helps optimize storage costs and search performance.

You can manage and update settings related to your audit data stream directly from Kibana's **Index Management** UI.

**Steps to Change Data Stream Settings using Kibana**

1. **Open Kibana and Navigate to Index Management**
   * In Kibana, go to **Management** > **Stack Management** > **Index Management**.
   * Select the **Data Streams** tab to see the list of available data streams.
2. **Select Your Audit Data Stream**
   * Find your audit data stream (e.g., `custom_audit_data_stream`) in the list.
   * Click on it to view details such as indices backing the data stream, mappings, and lifecycle policies.
3. **Edit Index Lifecycle Policy (ILM)**
   * If you want to update rollover criteria, retention period, or other lifecycle actions:
     * Navigate to **Index Lifecycle Policies** under **Stack Management**.
     * Select the ILM policy associated with your audit data stream.
     * Modify phases such as `hot`, `warm`, or `delete` to adjust settings like maximum size, max age, or deletion timing.
     * Save your changes — they will be applied automatically to the indices backing the data stream.
4. **Update Index Template**
   * To change index settings or mappings for new backing indices:
     * Go to **Index Templates** in Stack Management.
     * Locate the template associated with your audit data stream (usually matching the data stream name or pattern).
     * Edit the template's settings or mappings as needed.
     * Save the updated template; new indices created for the data stream will use these settings.
5. **Verify Changes**
   * After updating policies or templates, monitor your data stream to ensure rollover and retention behave as expected.
   * You can also query audit events via Kibana's Discover tab or using the Elasticsearch API.

**Important Notes**

* Changes to lifecycle policies and index templates affect **new indices** created after the update; existing indices are not modified retroactively.
* To apply mapping changes to existing indices, you may need to reindex data.
* Ensure you carefully test ILM and template changes in a staging environment before applying to production audit streams.

#### Rolling Migration from `index` to `data_stream`

To migrate ReadonlyREST audit logging from the `index` output type to `data_stream` in a **rolling update**, follow this safe, zero-downtime approach:

1. **Add `data_stream` as an Additional Output**

Temporarily configure both `index` and `data_stream` outputs so that audit events are sent to both destinations:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
      - type: index
      - type: data_stream # add data stream output type to your config
        data_stream: "custom_audit_data_stream"
```

> ✅ This ensures no audit logs are lost during the transition.

2. **Verify Data Stream Creation**

```
GET _data_stream/custom_audit_data_stream
```

Ensure the data stream is being created and audit events are flowing in.

3. **Monitor for Consistency**

Use Kibana or the `_search` API to confirm that events are present in both audit indices and `custom_audit_data_stream`.

4. **(Optional) Backfill Historical Data**

If you wish to migrate historical audit data from the old audit index, you can reindex it manually:

```json
POST _reindex
{
  "conflicts": "proceed",
  "source": {
    "index": "readonlyrest_audit-2025-06-07"
  },
  "dest": {
    "index": "custom_audit_data_stream",
    "op_type": "create"
  }
}
```

> ⚠️ Ensure both audit outputs have the same serializer for data consistency.

> ⚠️ Data streams are append-only — use `"op_type": "create"` to avoid overwrites.

> ⚠️ If the source index contains documents already present in the destination data stream, `"conflicts": "proceed"` will skip duplicates.

5. **Remove the `index` Output**

After confirming successful logging to the data stream from all nodes, update your config to remove the `index` output:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
      - type: data_stream
        data_stream: "custom_audit_data_stream"
```

6. **Final Verification**

Use Kibana dashboards, metrics, or direct queries to confirm that new audit events are flowing into the configured data stream.

### The 'log' output specific configurations

The `log` output writes audit events to Elasticsearch log at INFO level using a dedicated logger.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: log
  ...
```

#### Built-in rolling file appender

For a self-contained rolling file output — without editing `log4j2.properties` — use the `file_appender` sub-section:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: log
      name: rolling-audit
      file_appender:
        file_path: /var/log/elasticsearch/ror_audit.log   # absolute path; directory must be writable
        max_file_size: "100MB"                            # accepted units: B, KB, MB, GB, TB
        max_files: 10                                     # number of rotated files to keep
```

When `file_appender` is present, ROR creates and manages the rolling appender internally, bypassing the default Elasticsearch log routing for this output. The `logger_name` setting is still accepted and used as the appender name.

#### Custom logger name

If you want to route log output through a specific log4j2 logger:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: log
      logger_name: custom-logger-name
  ...
```

The default logger name is `readonlyrest_audit`.

#### Custom logging settings via log4j2

For advanced log configuration — custom patterns, external syslog appenders, etc. — configure the logger in `$ES_PATH_CONF/config/log4j2.properties`. The logger name must match the `logger_name` value in the output config (or the default `readonlyrest_audit`):

```
appender.readonlyrest_audit_rolling.type = RollingFile
appender.readonlyrest_audit_rolling.name = readonlyrest_audit_rolling
appender.readonlyrest_audit_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}readonlyrest_audit.log
appender.readonlyrest_audit_rolling.layout.type = PatternLayout
appender.readonlyrest_audit_rolling.layout.pattern = [%d{ISO8601}] %m%n
appender.readonlyrest_audit_rolling.filePattern = readonlyrest_audit-%i.log.gz
appender.readonlyrest_audit_rolling.policies.type = Policies
appender.readonlyrest_audit_rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.readonlyrest_audit_rolling.policies.size.size = 1GB
appender.readonlyrest_audit_rolling.strategy.type = DefaultRolloverStrategy
appender.readonlyrest_audit_rolling.strategy.max = 4

# Logger name must match the one configured in readonlyrest.yml (or the default "readonlyrest_audit")
logger.readonlyrest_audit.name = readonlyrest_audit
logger.readonlyrest_audit.appenderRef.readonlyrest_audit_rolling.ref = readonlyrest_audit_rolling
# set to false to use only desired appenders
logger.readonlyrest_audit.additivity = false
```

#### ACL serializer

The `log` output type supports a special `acl` serializer that reproduces the human-readable format written by the default ACL log. This is useful when you want to disable the built-in ACL log (`default_acl_log_enabled: false`) and replace it with a custom log sink that you can route per-block:

```yaml
readonlyrest:
  audit:
    enabled: true
    default_acl_log_enabled: false
    outputs:
    - type: log
      name: custom-acl-log
      logger_name: my.acl.logger
      serializer:
        type: acl
```

The `acl` serializer type is only valid for `log` outputs. Attempting to use it with `index` or `data_stream` outputs will produce a configuration error.

## Extending audit events

The audit events are JSON documents describing incoming requests and how the system has handled them. To create such events, we use a `serializer`, which is responsible for the event's serialization and filtering. The example [event](#audit) is in default format and was produced by the default serializer (`tech.beshu.ror.audit.instances.BlockVerbosityAwareAuditLogSerializer`).

You can:

* skip serializer configuration - in that case the default is `tech.beshu.ror.audit.instances.BlockVerbosityAwareAuditLogSerializer`

  ```yaml
  readonlyrest:
    audit:
      enabled: true
      outputs:
      - type: index
  ```
* use any of the predefined serializers ([see the list of predefined serializers](#predefined-serializers))

  ```yaml
  readonlyrest:
    audit:
      enabled: true
      outputs:
      - type: index
        serializer:
          type: "static"
          class_name: "tech.beshu.ror.audit.instances.QueryAuditLogSerializer" # or any other serializer class
  ```
* use dynamic, configurable serializer - define JSON fields in ReadonlyREST settings (no implementation required, [see how to do it](#using-configurable-serializer))
* use ECS ([Elastic Common Schema](https://www.elastic.co/docs/reference/ecs)) serializer (no implementation required, [learn more about it](#using-ecs-serializer))
* implement and use your own serializer ([see how to implement a custom serializer](#custom-audit-event-serializer))

### Predefined serializers:

* `tech.beshu.ror.audit.instances.BlockVerbosityAwareAuditLogSerializer`
  * Serializes all non-`Allowed` events.
  * Serializes `Allowed` events only when the matched block has `log_allowed_events: true` (the default).
  * Recommended for standard audit logging, where full request body capture is not required.
  * Fields included:

    ```
     match — whether the request matched a rule (boolean)  
     matched_block_names - list of names of the blocks, that were matched (both forbidden and allowed) (array of strings)
     id — audit event identifier (string)  
     final_state — final processing state (ALLOWED/FORBIDDEN/ERRORED/INDEX NOT EXIST) (string)  
     @timestamp — event timestamp (ISO-8601 string)  
     correlation_id — correlation identifier for tracing (string)  
     processingMillis — request processing duration in milliseconds (number)  
     error_type — type of error, if any (string)  
     error_message — error message, if any (string)  
     content_len — request body size in bytes (number)  
     content_len_kb — request body size in kilobytes (number)  
     type — request type (string)  
     origin — client (remote) address (string)  
     destination — server (local) address (string)  
     xff — X-Forwarded-For HTTP header value (string)  
     task_id — Elasticsearch task ID (number)  
     req_method — HTTP request method (string)  
     headers — HTTP header names (array of strings)  
     path — HTTP request path (string)  
     user — authenticated user (string) - deprecated field, please use `logged_user` or `presented_identity`
     logged_user — human-readable username (string)
     presented_identity — user identity that was presented with the request, e.g. basic auth username (string)
     impersonated_by — impersonating user, if applicable (string)  
     action — Elasticsearch action name (string)  
     indices — indices involved in the request (array of strings)  
     acl_history — access control evaluation history (string)  
     es_node_name — Elasticsearch node name (string)  
     es_cluster_name — Elasticsearch cluster name (string)  
    ```
* `tech.beshu.ror.audit.instances.QueryAuditLogSerializer`
  * Similar to the `BlockVerbosityAwareAuditLogSerializer` regarding `Allowed` event handling and included JSON fields.
  * Additionally, captures the full request body (`content` field)
  * Recommended for standard audit logging, where full request body capture is required.
* `tech.beshu.ror.audit.instances.FullAuditLogSerializer`
  * Serializes all events of all types, including all `Allowed` events, regardless of the block's `log_allowed_events` setting.
  * Included fields are the same as for `BlockVerbosityAwareAuditLogSerializer`
  * Use this serializer, when you need complete coverage of all events.
* `tech.beshu.ror.audit.instances.FullAuditLogWithQuerySerializer`
  * Serializes all events of all types, including all `Allowed` events, regardless of the block's `log_allowed_events` setting.
  * Included fields are the same as for `QueryAuditLogSerializer` (includes `content` field - full request body)
  * Use this serializer, when you need complete coverage of all events with full request body.

### Using configurable serializer:

Configuration should look like that:

```yaml
    readonlyrest:
      audit:
        enabled: true
        outputs:
        - type: index
          serializer:
            type: "configurable"
            allowed_events_serialization_mode: "based_on_block_settings" # serialize Allowed events only from blocks with log_allowed_events: true (default); use "always" to serialize all Allowed events
            fields: # list of fields in the resulting JSON; placeholders (like {ES_NODE_NAME}) will be replaced with their corresponding values
              node_details: "{ES_CLUSTER_NAME}/{ES_NODE_NAME}"
              http_request: "{HTTP_METHOD} {HTTP_PATH}"
              tid: "{TASK_ID}"
              bytes: "{CONTENT_LENGTH_IN_BYTES}"
```

The configuration above corresponds to serialized event looking like that:

```json
  {
    "node_details": "mainEsCluster/esNode01",
    "http_request": "GET /_cat",
    "tid": 0,
    "bytes": 123
  }
```

You can also define nested structure of fields, and use fixed text, number and boolean values:

```yaml
  fields:
    tid: "{TASK_ID}"
    es_details:
      node_name: "{ES_NODE_NAME}"
      cluster_name: "{ES_CLUSTER_NAME}"
    event_details:
      custom_system_id: 12345 # example of hardcoded number value, can also be a decimal number; in this example represents some hardcoded system id
      is_dev_environment: false # example of hardcoded boolean value; in this example represents flag marking the events from development environment
      http:
        request_description: "HTTP request: {HTTP_METHOD} {HTTP_PATH}"
        request_details:
          method: "{HTTP_METHOD}"
          path: "{HTTP_PATH}"
```

The configuration above corresponds to serialized event looking like that:

```json
{
  "tid": 0,
  "es_details": {
    "node_name": "esNode01",
    "cluster_name": "mainEsCluster"
  },
  "event_details": {
    "custom_system_id": 12345,
    "is_dev_environment": false,
    "http": {
      "request_description": "HTTP request: GET /_cat",
      "request_details": {
        "method": "GET",
        "path": "/_cat"
      }
    }
  }
}
```

Available placeholders:

```
  {IS_MATCHED} — whether the request matched a rule (boolean)
  {MATCHED_BLOCK_NAMES} — list of names of the blocks that were matched, both allowed and forbidden (array of strings)
  {ID} — audit event identifier (string)
  {FINAL_STATE} — final processing state (string)
  {ECS_EVENT_OUTCOME} - final processing state, mapped to ECS-compliant values: success/failure/unknown (string)
  {TIMESTAMP} — event timestamp (ISO-8601 string)
  {CORRELATION_ID} — correlation identifier for tracing (string)
  {PROCESSING_DURATION_MILLIS} — request processing duration in milliseconds (number)
  {PROCESSING_DURATION_NANOS} — request processing duration in nanoseconds (number)
  {ERROR_TYPE} — type of error, if any (string)
  {ERROR_MESSAGE} — error message, if any (string)
  {CONTENT_LENGTH_IN_BYTES} — request body size in bytes (number)
  {CONTENT_LENGTH_IN_KB} — request body size in kilobytes (number)
  {TYPE} — request type (string)
  {REMOTE_ADDRESS} — client (remote) address (string)
  {LOCAL_ADDRESS} — server (local) address (string)
  {X_FORWARDED_FOR_HTTP_HEADER} — `X-Forwarded-For` HTTP header value (string)
  {TASK_ID} — Elasticsearch task ID (number)
  {HTTP_METHOD} — HTTP request method (string)
  {HTTP_HEADER_NAMES} — HTTP header names (array of strings)
  {HTTP_PATH} — HTTP request path (string)
  {LOGGED_USER} — human-readable username (string)
  {PRESENTED_IDENTITY} — user identity that was presented with the request, e.g. basic auth username (string)
  {IMPERSONATED_BY_USER} — impersonating user, if applicable (string)
  {ACTION} — Elasticsearch action name (string)
  {INVOLVED_INDICES} — indices involved in the request (array of strings)
  {ACL_HISTORY} — access control evaluation history (string)
  {CONTENT} — request body content (string or object)
  {ES_NODE_NAME} — Elasticsearch node name (string)
  {ES_CLUSTER_NAME} — Elasticsearch cluster name (string)
```

### Using ECS serializer:

Configuration should look like that:

```yaml
    readonlyrest:
      audit:
        enabled: true
        outputs:
        - type: index
          serializer:
            type: "ecs"
            allowed_events_serialization_mode: "based_on_block_settings" # serialize Allowed events only from blocks with log_allowed_events: true (default); use "always" to serialize all Allowed events
            include_full_request_content: false # controls whether the full HTTP request body is included in the ECS audit log (http.request.body field), disabled by default
```

The configuration above corresponds to serialized event, compatible with ECS 1.6 schema, looking like that:

```json
{
  "@timestamp": "2017-06-30T09:41:58Z",
  "trace" : {
    "id" : "correlation_id_123"
  },
  "ecs" : {
    "version" : "1.6.0"
  },
  "source" : {
    "address" : "192.168.0.123"
  },
  "destination" : {
    "address" : "192.168.100.100"
  },
  "http" : {
    "request" : {
      "method" : "GET",
      "body" : {
        "bytes" : 123,
        "content" : "Full content of the request"
      }
    }
  },
  "event" : {
    "duration" : 5000000000,
    "reason" : "RRTestConfigRequest",
    "action" : "cluster:internal_ror/user_metadata/get",
    "id" : "trace_id_123",
    "outcome" : "failure"
  },
  "error" : {},
  "user" : {
    "effective" : {
      "name" : "impersonated_by_user"
    },
    "name" : "logged_user"
  },
  "url" : {
    "path" : "/path/to/resource"
  },
  "labels" : {
    "es_cluster_name" : "testEsCluster",
    "es_task_id" : 123,
    "es_node_name" : "testEsNode",
    "ror_acl_history" : "historyEntry1, historyEntry2",
    "ror_detailed_reason" : "default",
    "ror_involved_indices" : [],
    "ror_final_state" : "FORBIDDEN"
  }
}
```

The ECS schema is highly permissive and ambiguous. The ROR audit events can be mapped to ECS fields in multiple ways. If the provided ECS implementation does not suit your needs, you can define your own ECS-compliant serializer as `configurable` serializer.

The provided ECS implementation is equivalent to `configurable` serializer shown below. You can [use and adjust it as needed](#using-configurable-serializer) in the configuration.

```yaml
    readonlyrest:
      audit:
        enabled: true
        outputs:
          - type: index
            serializer:
              type: "configurable"
              allowed_events_serialization_mode: "based_on_block_settings"
              fields: 
                ecs:
                  version: "1.6.0"
                trace:
                  id: "{CORRELATION_ID}"
                url:
                  path: "{HTTP_PATH}"
                source:
                  address: "{REMOTE_ADDRESS}"
                destination:
                  address: "{LOCAL_ADDRESS}"
                http:
                  request:
                    method: "{HTTP_METHOD}"
                    body:
                      # Warning: Enabling logging of the full HTTP request body is not recommended when requests 
                      # may contain sensitive data. It can also significantly increase the size of audit log entries.
                      content: "{CONTENT}"
                      bytes: "{CONTENT_LENGTH_IN_BYTES}"
                user:
                  name: "{LOGGED_USER}"
                  effective:
                    name: "{IMPERSONATED_BY_USER}"
                event:
                  id: "{ID}"
                  duration: "{PROCESSING_DURATION_NANOS}"
                  action: "{ACTION}"
                  reason: "{TYPE}"
                  outcome: "{ECS_EVENT_OUTCOME}"
                error:
                  type: "{ERROR_TYPE}"
                  message: "{ERROR_MESSAGE}"
                labels:
                  x_forwarded_for: "{X_FORWARDED_FOR_HTTP_HEADER}"
                  es_cluster_name: "{ES_CLUSTER_NAME}"
                  es_node_name: "{ES_NODE_NAME}"
                  es_task_id: "{TASK_ID}"
                  ror_involved_indices: "{INVOLVED_INDICES}"
                  ror_acl_history: "{ACL_HISTORY}"
                  ror_final_state: "{FINAL_STATE}"
                  ror_matched_block_names: "{MATCHED_BLOCK_NAMES}"
```

### Custom audit event serializer

You can write your own custom audit events serializer class, add it to the ROR plugin class path and configure it through the YAML settings.

We provided 2 project examples with custom serializers (in Scala and Java). You can use them as an example to write yours in one of those languages.

#### Create custom audit event serializer in Scala

1. Checkout <https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin>

   `git clone git@github.com:sscarduzio/elasticsearch-readonlyrest-plugin.git`
2. Install SBT

   `https://www.scala-sbt.org/download.html`
3. Find and go to: `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-scala-serializer/`
4. Create own serializer:
   * from scratch (example can be found in class `ScalaCustomAuditLogSerializer`)
   * extending default one (example can be found in class `ScalaCustomAuditLogSerializer`)
5. Build serializer JAR:

   `sbt assembly`
6. Jar can be find in:

   `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-scala-serializer/target/scala-2.13/ror-custom-scala-serializer-1.0.0.jar`

#### Create custom audit event serializer in Java

1. Checkout <https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin>

   `git clone git@github.com:sscarduzio/elasticsearch-readonlyrest-plugin.git`
2. Install Maven

   `https://maven.apache.org/install.html`
3. Find and go to: `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-java-serializer/`
4. Create own serializer:
   * from scratch (example can be found in class `JavaCustomAuditLogSerializer`)
   * extending default one (example can be found in class `JavaCustomAuditLogSerializer`)
5. Build serializer JAR:

   `mvn package`
6. Jar can be find in:

   `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-java-serializer/target/ror-custom-java-serializer-1.0.0.jar`

#### Configuration

1. mv ror-custom-java-serializer-1.0.0.jar plugins/readonlyrest/
2. Your config/readonlyrest.yml should start like this

   ```yaml
    readonlyrest:
        audit:
          enabled: true
          outputs:
          - type: index
            serializer:
              type: "static"
              class_name: "JavaCustomAuditLogSerializer" # when your serializer class is not in default package, you should use full class name here (eg. "tech.beshu.ror.audit.instances.QueryAuditLogSerializer")
   ```
3. Start elasticsearch (with ROR installed) and grep for:

   ```
    [2023-03-26T16:28:40,471][INFO ][t.b.r.a.f.d.AuditingSettingsDecoder$] Using custom serializer: JavaCustomAuditLogSerializer
   ```

## Protecting the audit index

To prevent users from modifying or deleting audit data, add a `forbid` block to your `readonlyrest.yml` that blocks write and delete actions on the audit indices.

```yaml
- name: "Protect audit index"
  type: forbid
  indices: ["readonlyrest_audit-*"]
  actions:
    - "indices:data/write/*"      # index, update, delete, bulk, *_by_query, reindex-into
    - "indices:admin/delete"      # delete the index
    - "indices:admin/close"       # close (then could be re-opened writable)
    - "indices:admin/open"
    - "indices:admin/settings/*"  # change settings (e.g. flip read-only / replicas)
    - "indices:admin/mapping/*"   # mapping/put + mapping/auto_put (NOT mappings/get — that's read)
    - "indices:admin/aliases"     # re-point / drop the audit aliases
    - "indices:admin/rollover"
    - "indices:admin/resize"      # shrink / split / clone
    - "indices:admin/forcemerge"
    - "indices:admin/freeze"
```

**Placement:** ACL blocks are evaluated top-to-bottom and the first matching block wins.

* If no user or service should be able to write to the audit index via the Elasticsearch API, place this block at the **very beginning** of the ACL. Audit events are written internally by the ReadonlyREST plugin and bypass the ACL entirely, so this does not affect audit collection.
* If some identities (e.g. a dedicated audit reader service) require access that would conflict with this rule, place the `forbid` block after their `allow` blocks but before all other allow rules.

The wildcard pattern `readonlyrest_audit-*` matches the default index name template. If you configured a custom `index_template` prefix, adjust the pattern accordingly.


# External to local groups mapping

The `groups` ACL rule accepts a list of group IDs. This rule will match on a requests in which the resolved username belongs at least to one of the listed groups. The association between usernames and groups is explicitly declared in the users section of the ACL. This is a list of usernames, and today, full wildcard patterns are also supported.

In the `users` section, each entry requires:

* an authentication rule: (I.e. one of the `auth_key_*` rules for local credentials, or `ldap_authentication`, `external_authentication`, etc)
* a list of groups within the ones precendently inserted in the groups rules of the ACL blocks
* optionally, an authorization rule (`ldap_authorization`, `groups_provider_authorization`, etc.)

When the users section's `groups` rule and the authorization rule are used together, we obtain "group mapping". That is: we are effectively mapping remote groups to local groups. There are two types of mapping available: **common** and **detailed** group mappings.

*Note:* the rule `ldap_auth` is the composition of `ldap_authentication` and `ldap_authorization`. So it can be used as a shortcut for both.

## Example

```yaml
readonlyrest:

  access_control_rules:
  - name: "Viewer block"
    indices: ["logstash-viewers*"]
    groups_any_of: ["viewers"]

  - name: "DevOps block"
    indices: ["logstash-devops*"]
    groups_any_of: ["devops"]

  [...]

  users:
  # PLAIN LOCAL GROUPS EXAMPLE
  # Local user "joe" is associated to local group "editors"
  - username: "joe"
    groups: ["editors"]
    auth_key: joe:password
    
  # COMMON GROUP MAPPING EXAMPLE
  # Externally authenticated user + authorization via external groups provider + groups common mapping
  # Users belonging to "external_group1" OR "external_group2" are authorized as "viewers" AND "editors" in the ACL.
  - username: "*"
    groups: ["viewers", "editors"]
    external_authentication: "ext1"
    groups_provider_authorization:
      user_groups_provider: "ext2"
      groups_any_of: ["external_group1", "external_group2"]
  
  # DETAILED GROUP MAPPING EXAMPLE
  # LDAP authenticated user + authorization via LDAP + groups detailed mapping (any LDAP user is valid; groups from `ldap1` are mapped to local groups) 
  # Users belonging to LDAP role `ldap_role_ops`, or any other LDAP role that matched `ldap_*_devops` pattern, will be mapped to "devops" local group 
  # AND 
  # Users belonging to LDAP `ldap_role_dev` are mapped to "developers" local group
  - username: "*"
    groups: 
      - devops: ["ldap_role_ops", "ldap_*_devops"]
      - developers: ["ldap_role_dev"]
    ldap_auth:
      name: "ldap1"
      groups_any_of: ["ldap_*_devops", "ldap_role_ops", "ldap_role_dev"]


  # DETAILED GROUP MAPPING EXAMPLE (STRUCTURED GROUPS)
  # LDAP authenticated user + authorization via LDAP + groups detailed mapping (any LDAP user is valid; groups from `ldap1` are mapped to local groups) 
  # Users belonging to LDAP role `ldap_role_ops`, or any other LDAP role that matched `ldap_*_devops` pattern, will be mapped to "devops" local group 
  # AND 
  # Users belonging to LDAP `ldap_role_dev` are mapped to "developers" local group
  - username: "*"
    groups:
    - local_group:
        id: "devops"
        name: "DevOps Group"
      external_group_ids:  ["ldap_role_ops", "ldap_*_devops"]
    - local_group:
        id: "developers"
        name: "Developers Group"
      external_group_ids: ["ldap_role_dev"]
    ldap_auth:
      name: "ldap1"
      groups_any_of: ["ldap_*_devops", "ldap_role_ops", "ldap_role_dev"]

  external_authentication_service_configs:
  - name: "ext1"
    [...]

  user_groups_providers:
  - name: ext2
    [...]

  ldaps:
  - name: ldap1
    [...]
```

As we can see, there are two blocks in our ACL:

1. `Viewer block` allows all users, which belong to `viewers` group, to access indices matching pattern `logstash-viewers*`
2. `DevOps block` allows all users, which belong to `devops` group, to access indices matching pattern `logstash-devops*`

### Common mapping example

```yml
  - username: "*"
    groups: ["viewers", "editors"]
    external_authentication: "ext1"
    groups_provider_authorization:
      user_groups_provider: "ext2"
      groups_any_of: ["external_group1", "external_group2"]
```

`viewers`, `devops`, (unused in the ACL example), `editors` and `developers` are local groups. That is, they exist only at ROR's configuration level. But ROR can also integrate with external authorization systems like an LDAP or some REST service, where we can find similar concepts to ROR groups (eg. users in LDAP can have roles assigned).

And sometimes we'd like to fulfil a requirement such as:

> Users having usernames defined by a given pattern, and having a given set of roles, should have certain given ROR internal groups assigned.

You can think about it as a mapping external groups to local ones.

Let's go back to our example. In the second element of the `users` array, we declare that:

* any user can be taken into consideration by this user definition
* a user should be authenticated by an `external_authentication` rule which uses the `ext1` service
* a user should be authorized by a `groups_provider_authorization` rule which uses the `ext2` service and such user belongs to at least one of `external_group1`, `external_group2` external groups.
* if all the above conditions are true, we can assign `viewers`, `editors` groups to the user

We have just "mapped" the external groups `external_group1`, `external_group2` returned by service `ext2` to the local ROR groups `viewers`, `editors`.

### Detailed mapping example

```yml
  - username: "*"
    groups: 
      - devops: ["ldap_*_devops", "ldap_role_ops"]
      - developers: ["ldap_role_dev"]
    ldap_auth:
      name: "ldap1"
      groups_any_of: ["ldap_role_devops", "ldap_role_ops", "ldap_role_dev"]
```

The third element of `users` array (in the example above) is similar, but we use one rule which is authentication and authorization rule at the same time (it can authenticate a user and then authorize him). And that's how we defined the following mappings:

* `ldap_role_ops` LDAP role, and any other LDAP role matching `ldap_*_devops` pattern, are mapped to `devops` ROR's local group
* LDAP role `ldap_role_dev` is mapped to `developers` ROR's local group

The "detailed" mapping offers a bit more structured approach to group mapping, and although less intuitive at first sight, it's more powerful and concise.


# FIPS mode

## What is FIPS?

According to [Wikipedia](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standards)

> Federal Information Processing Standards (FIPS) are publicly announced standards developed by the National Institute of Standards and Technology for use in computer systems by non-military American government agencies and government contractors. FIPS standards are issued to establish requirements for various purposes such as ensuring computer security and interoperability and are intended for cases in which suitable industry standards do not already exist.

In short it is a thoroughly tested and verified set of standards which could be used to implement high level of security. In terms of software we are usually speaking specifically about FIPS 140-2.

ReadonlyREST uses OpenSource [BouncyCastle](https://www.bouncycastle.org) library to provide FIPS 140-2 compliant algorithms.

## Is ReadonlyREST fully compliant to FIPS 140-2?

At the moment, ReadonlyREST can be configured as FIPS compliant only from the "data in transit" standpoint. That is, the SSL encryption of the HTTP and transport interfaces. Other aspects remain to be covered:

* Making all cryptographic algorithms FIPS compliant.
* Enforcing more strict security policies across whole ROR plugin in FIPS mode.

## How to enable SSL FIPS compliance

1. Prepare keystore and truststore in BCFKS format which is FIPS compliant. Your existing JKS or PKCS12 keystore could be easily converted to BCFKS. Process is described [in this section](#how-to-convert-jkspkcs12-keystore-files-into-bcfks).

> :warning: BCFKS format is supported only when FIPS mode is enabled. It won't be recognised otherwise.

> :warning: When using FIPS mode using different password for specific keystore elements is not supported and `key_pass` configuration field is ignored.

1. Configure readonlyrest.yml to use new keystore and truststore. You will also need to add new configuration parameter `fips_mode`. Here's an example:

```
readonlyrest:
  fips_mode: SSL_ONLY
  ssl:
    enable: true
    keystore_file: "keystore.bcfks"
    keystore_pass: readonlyrest
    truststore_file: "truststore.bcfks"
    truststore_pass: readonlyrest

  ssl_internode:
    enable: true
    keystore_file: "keystore.bcfks"
    keystore_pass: readonlyrest
    truststore_file: "truststore.bcfks"
    truststore_pass: readonlyrest
```

1. In case you are using ES >= 7.10 you need to modify `$JAVA_HOME/conf/security/java.policy` file and add this section at the end of it. This is required because otherwise Elasticsearch will not be able grant to our plugin all these permissions at the JVM level.

```
grant {
  permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey";
  permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey";
  permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms";
  permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms";
  permission java.security.SecurityPermission "getProperty.keystore.type.compat";
  permission java.security.SecurityPermission "removeProvider.SunRsaSign";
  permission java.security.SecurityPermission "removeProvider.SunJSSE";
  permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read";
  permission java.io.FilePermission "${java.home}/lib/security/cacerts", "read";
  permission java.security.SecurityPermission "getProperty.jdk.tls.server.defaultDHEParameters";
  permission org.bouncycastle.crypto.CryptoServicesPermission "defaultRandomConfig";
};
```

## How to convert JKS/PKCS12 keystore files into BCFKS

1. Download the [jar with bc-fips](https://repo1.maven.org/maven2/org/bouncycastle/bc-fips/1.0.2.3/bc-fips-1.0.2.3.jar) library and place it preferably in the same directory where you store keystore files to convert.
2. Open your terminal and go to directory with the keystore to convert
3. Use keytool with following parameters to perform the conversion:

```
keytool \
-importkeystore \
-srckeystore SOURCE_KEYSTORE_FILENAME  \
-destkeystore DEST_KEYSTORE_FILENAME \
-srcstoretype SOURCE_KEYSTORE_TYPE \
-deststoretype DEST_KEYSTORE_TYPE \
-srcstorepass SOURCE_KEYSTORE_PASSWORD \
-deststorepass DEST_KEYSTORE_PASSWORD \
-providerpath ./bc-fips-1.0.2.1.jar \
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
```

where:

* SOURCE\_KEYSTORE\_FILENAME - filename of the keystore(or truststore) that you want to convert.
* DEST\_KEYSTORE\_FILENAME - name of the output file.
* SOURCE\_KEYSTORE\_TYPE - type of keystore to convert. Must be JKS or PKCS12.
* DEST\_KEYSTORE\_TYPE - type of output keystore. Must be BCFKS.
* SOURCE\_KEYSTORE\_PASSWORD - password protecting keystore to convert.
* DEST\_KEYSTORE\_PASSWORD - password protecting output file. If you saved the bc-fips jar in a different path, remember to run it using the appropriate path instead of `./bc-fips-1.0.2.1.jar`


# Elastic Fleet

[Elastic Fleet](https://www.elastic.co/guide/en/fleet/current/fleet-overview.html) manages Elastic Agents centrally through Kibana. When Fleet is set up, it creates two kinds of dynamic Elasticsearch credentials that ReadonlyREST needs to recognize and validate:

* **Service tokens** - used by Fleet Server to authenticate with Elasticsearch. These are created by Kibana during Fleet setup and belong to Elasticsearch's built-in `elastic/fleet-server` service account.
* **API keys** - issued to each enrolled Elastic Agent. Fleet Server creates and rotates these automatically; each agent uses its own key to ship data.

Because both credential types are generated at runtime (not known in advance), they cannot be matched with static `auth_key` or `auth_key_sha256` rules. Instead, ReadonlyREST's `token_authentication` rule with `type: service-token` or `type: api-key` delegates validation to Elasticsearch, which has the ground truth for both.

## ReadonlyREST settings

```yaml
readonlyrest:
  access_control_rules:

    # 1. Kibana user - used by Kibana itself and by Fleet initialisation scripts.
    #    No action or index restriction; Kibana needs unrestricted access during
    #    Fleet setup (e.g. bootstrapping the Fleet Server service account).
    - name: "KIBANA"
      type: allow
      auth_key: kibana:kibana

    # 2. Fleet Server - authenticates using an Elasticsearch service token.
    #    ReadonlyREST validates the token against Elasticsearch's service account API.
    #    No action restriction: Fleet Server needs to call
    #    cluster:admin/xpack/security/api_key/create to issue API keys to
    #    enrolling agents.
    - name: "Fleet server"
      type: allow
      token_authentication:
        type: "service-token"
        username: "fleet"
      indices:
        - ".fleet-servers"
        - ".fleet-agents"
        - ".fleet-actions"
        - ".fleet-policies"
        - ".fleet-policies-leader"
        - ".fleet-enrollment-api-keys"

    # 3. Elastic Agents - each agent authenticates with its own API key, issued
    #    and rotated by Fleet Server. ReadonlyREST validates the key against Elasticsearch
    #    and grants access to the observability data-stream indices.
    - name: "Agents"
      type: allow
      token_authentication:
        type: "api-key"
        username: "fleet"
      indices:
        - ".apm-agent-configuration"
        - "metrics-*"
        - "traces-*"
        - "logs-*"

    # 4. Forbid direct token management - only Kibana and Fleet Server (matched
    #    above) should create or revoke service tokens and API keys. This block
    #    denies these actions for everyone else.
    - name: "Forbid access to service accounts and API keys"
      type: forbid
      actions:
        - "cluster:admin/xpack/security/service_account/*"
        - "cluster:admin/xpack/security/api_key/*"

    # 5. Admin user - full Kibana access.
    - name: "Admins"
      type: allow
      auth_key: admin:admin
      kibana:
        access: admin
```

## How Fleet credentials flow through ReadonlyREST

1. **Kibana creates a service token** - during Fleet setup, Kibana calls `cluster:admin/xpack/security/service_account/*` to create the Fleet Server service token. This request is authenticated by the `KIBANA` block.
2. **Fleet Server creates API keys** - Fleet Server uses its service token to call `cluster:admin/xpack/security/api_key/create`, issuing an API key to each enrolling agent. This request is authenticated by the `Fleet server` block.
3. **Elastic Agents use their API keys** - each agent presents its API key on every request to ship data to Elasticsearch. These requests are authenticated by the `Agents` block.

## Why the `forbid` block is necessary

Only Kibana and Fleet Server should be able to create service tokens and API keys - no other user needs these actions. The `KIBANA` and `Fleet server` blocks already permit these calls for the accounts that legitimately need them. The `forbid` block sits below those blocks and denies any remaining request that targets service-account or API-key management actions, preventing other authenticated users from creating, revoking or listing credentials.

## Credential rotation

You do not need to put service tokens or API key values into `readonlyrest.yml`. ReadonlyREST never sees or stores them - it asks Elasticsearch to validate each token on the fly. This means:

* Fleet Server can rotate its service token without any ReadonlyREST config change.
* Agents can be enrolled, unenrolled, and re-keyed without touching ReadonlyREST.
* The only things that must stay in sync with your deployment are the **index patterns** in the `service-token` and `api-key` blocks.

## Setting up Fleet Server and Elastic Agent

Configuring Fleet Server and enrolling Elastic Agents is covered in the [official Elastic Fleet documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html). APM agent setup is documented in the [APM quick-start guide](https://www.elastic.co/guide/en/apm/guide/current/apm-quick-start.html).

## Running the example

A full working example with Elasticsearch, Kibana (both with ReadonlyREST), Fleet Server, an Elastic Agent (APM), a demo Node.js app, and a traffic simulator is available in the [readonlyrest-examples](https://github.com/beshu-tech/readonlyrest-examples/tree/master/examples/fleet) repository:

```bash
curl -sL https://raw.githubusercontent.com/beshu-tech/readonlyrest-examples/master/quickstart.sh | bash -s fleet
```

Once running, log into Kibana and navigate to **Management → Fleet** to see the enrolled agent and its policy, or to **Observability → APM** for traces from the demo application.


# FLS engine

Applicable in the context of the [`fields` rule](/develop/elasticsearch#fields)

FLS engine specifies how ROR handles field-level security internally. Previously FLS was based entirely on [Lucene](https://en.wikipedia.org/wiki/Apache_Lucene) - that's why ROR needed to be installed on all nodes to make the `fields` rule work properly. Now the `fields` rule is more flexible and part of FLS responsibilities is handled solely by ES. Increasing ES usage and reducing Lucene exploitation in FLS implementation makes the rule more efficient.

Unfortunately, a few FLS functionalities still have to be handled at the Lucene level, and cannot benefit of the new ES level implementation (see supported at ES level [requests](#ES-limitations) ) Lucene is still used by `fields` rule when ES is not able to handle a request properly (as kind of a fallback).

## Configuration

FLS engine can be configured with global, optional property `fls_engine` set under the `readonlyrest.global_settings` section.

There are two engines available:

* **es\_with\_lucene** (default)

**⚠️IMPORTANT** As Lucene is part of this engine, the ReadonlyREST plugin still needs to be installed in all the cluster nodes that contain data.

Default hybrid approach - the major part of FLS is handled by ES. Corner cases are passed to Lucene. This solution handles all requests properly being more performant than the old full Lucene-based approach.

* **es**

FLS is handled only by ES, without fallback to Lucene. When ES is not able to handle FLS properly, the `fields` rule is not matched. In the `es` engine, FLS is not available for some types of requests (requirements listed below). The major advantage of this approach is to not rely on Lucene, so **ROR doesn't need to be installed on all nodes**.

If a lack of full FLS support is unacceptable and all type of requests needs to be handled properly (rule matching, no rejection) it's advised to use a more reliable `es_with_lucene` engine.

## ES limitations

Supported by `es` FLS engine requests are:

* all Get/MGet API requests
* Search/MSearch/AsyncSearch API requests with the following restrictions:
  * not using script fields
  * the used query is one of
    * common terms
    * match bool
    * match
    * match phrase
    * match phrase prefix
    * exists
    * fuzzy
    * prefix
    * range
    * regexp
    * term
    * wildcard
    * terms set
    * bool
    * boosting
    * constant score
    * dis max
  * the defined query doesn't use wildcards in field names
  * defined compound queries using only listed above supported queries as inner queries
  * the Search request doesn't use scroll

If the request doesn't meet above requirements (e.g. it's using `query_string` or script fields), the `es` engine will reject it.

Example configuration (ROR using `es` FLS engine):

```yaml
readonlyrest:
 
 global_settings:
   fls_engine: "es"
 
 access_control_rules:

   - name: "user_using_fields"
     auth_key: user:pass
     fields: ["~someNotAllowedField"]
```

Property `fls_engine` can be omitted, then by default, ROR uses `es_with_lucene` FLS engine.


# Indices rule - Index not found scenario

Examples:

> Let's assume that our ES cluster has 2 indices: `index_a` and `index_b`. At the same time we have two users: `userA` and `userB`. We'd like to give `userA` access to index `index_a`, and `userB` to `index_b`. `userA` should not see or be even aware of `index_b` and vice versa. We'd like to give each of them a feeling that they are alone on the cluster.
>
> ROR `readonlyrest.yml` configuration may look like this:
>
> ```yaml
> readonlyrest:
>   enable: true
>   access_control_rules:
>
>      - name: "user A indices"
>        indices: ["index_a"]
>        auth_key: userA:secret
>
>      - name: "user B indices"
>        indices: ["indexB"]
>        auth_key: userB:secret
> ```
>
> We can test if `userA` is able to reach `index_a`:
>
> ```
> $ curl -v -u userA:secret "http://127.0.0.1:9200/index_a?pretty"
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 611
>    
>   {
>     "index_a" : {
>       "aliases" : { },
>       "mappings" : { ... }
>       "settings" : { ... }
>     }
>   }
> ```
>
> It looks like he is. So far, so good. Let's try to access nonexistent index (we know, that index with name `nonexistent` for sure doesn't exist on our cluster):
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/nonexistent?pretty"                                                                                             18:15:28
>   HTTP/1.1 404 Not Found
>   content-type: application/json; charset=UTF-8
>   content-length: 634
>
>   {
>     "error" : {
>       "root_cause" : [ ... ],
>       "type" : "index_not_found_exception",
>       "reason" : "no such index [nonexistent_ROR_ZA1FXDsR7M]",
>       "resource.type" : "index_or_alias",
>       "resource.id" : "nonexistent_ROR_ZA1FXDsR7M",
>       "index_uuid" : "_na_",
>       "index" : "nonexistent_ROR_ZA1FXDsR7M"
>     },
>     "status" : 404
>  }
> ```
>
> The response is pretty straight forward - the index doesn't exist. But, let's see what happens, when the same user, `userA`, will try to get `index_b`:
>
> ```
> $ curl -v -u userA:secret "http://127.0.0.1:9200/index_b?pretty"
>   HTTP/1.1 404 Not Found
>   content-type: application/json; charset=UTF-8
>   content-length: 610
>  
>   {
>     "error" : {
>       "root_cause" : [ ... ],
>       "type" : "index_not_found_exception",
>       "reason" : "no such index [index_b_ROR_QcskliAl8A]",
>       "resource.type" : "index_or_alias",
>       "resource.id" : "index_b_ROR_QcskliAl8A",
>       "index_uuid" : "_na_",
>       "index" : "index_b_ROR_QcskliAl8A"
>     },
>     "status" : 404
>   }
> ```
>
> As we can see `userA` is not able to get `index_b`. But the response is HTTP 404 Not Found - it means that the index doesn't exist.
>
> So, the response is the same as we get if the called index really doesn't exist. Thanks to the described behaviour, `userA` is not aware that on the cluster there are any other indices but the ones he was given access to.
>
> > note:
> >
> > Careful reader may notice that, in example above, `userA` was getting `index_b`, but the response says that there is no `index_b_ROR_QcskliAl8Aindex_b_ROR_QcskliAl8A` index. It's the trick ROR does to fool ES and be sure that asking index, which the user should not be allowed to see, won't be reached by him.
>
> But we should also consider the other case - using an index name with wildcard. So, `userA` will try to get all indices which names match `index*` pattern:
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/index*?pretty"                                                                                                  19:58:29
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 611
>   
>   {
>     "index_a" : {
>       "aliases" : { },
>       "mappings" : { ... },
>        "settings" : { ... }
>      }
>    }
> ```
>
> Response is exactly like we'd expect - only `index_a` was returned. But what if nothing matches our index name pattern?
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/index_userA*?pretty"                                                                                                20:05:10
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 4
>
>   { }
> ```
>
> Response is empty list. Now, let's see what happens when an index name pattern matches an index which is not authorized for a user who asks about it.
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/index_b*?pretty"                                                                                                20:14:34
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 4
>
>   { }
> ```
>
> As we see, response is the same as we have experienced when there was really no index matching the pattern. Also here a user has a feeling that only his indices are present on a cluster.


# Indices rule - ES Templates handling

<details>

<summary>A ROR configuration for all examples below (click to expand)</summary>

```yaml
  readonlyrest:

    access_control_rules:
      - name: "admin block"
        verbosity: error
        type: allow
        auth_key: admin:admin

      - name: "dev1 block"
        indices: ["idev1", "idev1_*"]
        auth_key: dev1:test

      - name: "dev2 block"
        indices: ["idev2", "idev2_*"]
        auth_key: dev2:test
```

</details>

## Index templates

An `indices` rule takes into consideration index patterns and aliases which are a part of a template definition. We should consider four types of template related requests:

### Create an index template

The request will be allowed when all of following conditions are met:

* a template with requested name does not exist (if it does, it's rather a template modification, than a creation),
* all index patterns of the new, requested template are allowed,
* all aliases of the new, requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

Let's try to add an index template. We can see, using `admin` account, that there are no templates defined yet.

```
$ curl -vk -u admin:admin "http://localhost:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "index_templates" : [ ]
  }
```

Now, let's use `dev1` user account to create an index template `temp1`:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["index*"],
     "template": {
       "aliases": { 
         "dev1_index": {},
         "dev2_index": {}
       }
     }
  }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Oh, something went wrong. It seems that, a user `dev1` is not allowed to add this template. Let's check ROR logs to figure out why:

FORBIDDEN by default req={ ID:193441275-173645661#8, TYP:PutComposableIndexTemplateAction$Request, CGR:N/A, USR:dev1 (attempted), BRS:true, KDX:null, ACT:indices:admin/index\_template/put, OA:127.0.0.1/32, XFF:null, DA:127.0.0.1/32, `IDX:index*,dev2_index,dev1_index`, MET:PUT, `PTH:/_index_template/test`, CNT:\<OMITTED, LENGTH=157.0 B> , HDR:Accept=*/*, Authorization=, Content-Length=157, Content-Type=application/json, Host=127.0.0.1:9200, User-Agent=curl/7.64.1, HIS:\[CONTAINER ADMIN-> RULES:\[auth\_key->false] RESOLVED:\[indices=index\*,dev2\_index,dev1\_index;template=ADD(test:index\*:dev2\_index,dev1\_index)]], `[dev1 block-> RULES:[auth_key->true, indices->false]` RESOLVED:\[user=dev1;indices=index\*,dev2\_index,dev1\_index;template=ADD(test:index\*:dev2\_index,dev1\_index)]], \[dev2 block-> RULES:\[auth\_key->false] RESOLVED:\[indices=index\*,dev2\_index,dev1\_index;template=ADD(test:index\*:dev2\_index,dev1\_index)]], }

We can see that our request was forbidden - credentials were OK, but `indices` rule was not matched in `dev1 block`. We can see also that ROR found 3 indices which are related to the request:

* `index*` - an index pattern from our request
* `dev1_index` - a first alias from out request
* `dev2_index` - a second alias from out request

When we take a look at indices configured in `indices` rule for our user, we can see that, he has an access only to `idev1` and `idev1_*` indices. Now, it's pretty much obvious why the request was blocked - the user has no access to index pattern and aliases used in the request. Let's try to fix that:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev1_test*"],
     "template": {
       "aliases": { 
         "idev1": {},
         "idev1_test": {}
       }
     }
  }'

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Hooray! The index template was added. This time ROR allowed us to do so. It's because `dev1` user has an access to index pattern `idev1_test*`, because it is contained in `idev1_*`. Used aliases are also allowed.

</details>

### Modify an index template

The request will be allowed when all of following conditions are met:

* a template with requested name does exist,
* all index patterns of the existing template are allowed,
* all aliases of the existing template are allowed,
* all index patterns of the requested template are allowed,
* all aliases of the requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

Let's assume the user `dev1` would like to modify the previously created template, because the index pattern is too detailed:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev*"],
     "template": {
       "aliases": {
         "idev1": {},
         "idev1_test": {}
       }
     }
   }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Ups! Something is wrong. Let's check the ROR forbidden log:

FORBIDDEN by default req={ ID:918326057-1726421783#75, TYP:PutComposableIndexTemplateAction$Request, CGR:N/A, USR:dev1 (attempted), BRS:true, KDX:null, ACT:indices:admin/index\_template/put, OA:127.0.0.1/32, XFF:null, DA:127.0.0.1/32, `IDX:idev*,idev1,idev1_test`, MET:PUT, PTH:/\_index\_template/test, CNT:\<OMITTED, LENGTH=151.0 B> , HDR:Accept=*/*, Authorization=, Content-Length=151, Content-Type=application/json, Host=127.0.0.1:9200, User-Agent=curl/7.64.1, HIS:\[CONTAINER ADMIN-> RULES:\[auth\_key->false] RESOLVED:\[indices=idev\*,idev1,idev1\_test;template=ADD(test:idev\*:idev1,idev1\_test)]], `[dev1 block-> RULES:[auth_key->true, indices->false]` RESOLVED:\[user=dev1;indices=idev\*,idev1,idev1\_test;template=ADD(test:idev\*:idev1,idev1\_test)]], \[dev2 block-> RULES:\[auth\_key->false] RESOLVED:\[indices=idev\*,idev1,idev1\_test;template=ADD(test:idev\*:idev1,idev1\_test)]], }

We can see that `indices` rule hasn't not been matched. Looking at the IDX section, we can figure out that the index pattern we requested `idev*`, cannot be allowed. `idev*` is too generic, because in the `indices` list we have `["idev1", "idev1_*"]`. Let's try to fix that:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev1_*"],
     "template": {
       "aliases": {
         "idev1": {},
         "idev1_test": {}
       }
     }
   }'

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Yeah, now it works. Let's check if the template is modified (we will use `admin` user to do so):

```
$ curl -vk -u admin:admin "http://127.0.0.1:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

All is good. We have only one template and the modifications was applied.

So far, so good. But we can wonder what happens if `dev2` will try to modify (or override) template `temp`? Let's check:

```
$ curl -vk -u dev2:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev2_*"],
     "template": {
       "aliases": {
         "idev2": {},
         "idev2_test": {}
       }
     }
   }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Yes! This is something what we wanted like to see. Even if the request was correct and the user `dev2` has an access to the requested index pattern and aliases, the request was forbidden. Obviously, there is already existed template `temp` which has the index pattern and aliases, which are not allowed for `dev2`. ROR deduces that `dev2` cannot be considered as someone how can modify/overwrite it.

Pretty awesome. Won't `dev2` also be able to remove it? We'll see in next section ...

</details>

### Delete an index template

The request will be allowed when template does not exist OR all of the following conditions are met:

* a template with requested name does exist,
* all index patterns of the existing template are allowed,
* all aliases of the existing template are allowed.

<details>

<summary>Example (click to expand)</summary>

In the last section we wondered, if ROR will be able to block removing the template `temp` by the user `dev2`. Let's recall, that we proved that the user is not able to modify this template, because ROR considers that he doesn't have permissions to change/remove it.

```
$ curl -vk -u dev2:test -XDELETE "http://127.0.0.1:9200/_index_template/test?pretty"

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Perfect! OK, but we also would like to know if `user1` will be able to remove his template. Let's check it:

```
$ curl -vk -u dev1:test -XDELETE "http://127.0.0.1:9200/_index_template/test?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Great! Everything works.

</details>

### Get index templates

At the moment ROR doesn't have `templates` rule (similar to `snapshots` or `repositories`), which allows to restrict which templates can be visible to the user (it is going to change in the future). But the `indices` rule is enough to filter templates based on index patterns in their definitions. An index template is considered to be visible for a user, when the user has access to AT LEAST ONE index pattern of the template's index pattern list. ROR is going to show the template but to hide the information about not allowed index patterns and not allowed aliases.

<details>

<summary>Example (click to expand)</summary>

In previous sections we proved that ROR gets along with index templates adding, modifying and removing pretty well. Now, we'd like check what index templates are supposed to be visible for users. Let's assume we have 4 index templates:

```
$ curl -vk -u admin:admin "http://127.0.0.1:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "index_templates" : [
      {
        "name" : "t1",
        "index_template" : {
          "index_patterns" : ["i*"],
          "template" : {
            "aliases" : {
              "idev2" : { },
              "idev3" : { },
              "idev1" : { }
            }
          },
          "composed_of" : [ ]
        }
      },
      {
        "name" : "t2",
        "index_template" : {
          "index_patterns" : ["idev1_*"],
          "template" : {
            "aliases" : {
              "admin_idev" : { },
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 1
        }
      },
      {
        "name" : "t3",
        "index_template" : {
          "index_patterns" : ["idev2_*"],
          "template" : {
            "aliases" : {
              "idev2" : { },
              "admin_idev" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 1
        }
      },
      {
        "name" : "t4",
        "index_template" : {
          "index_patterns" : ["idev1_*", "idev2_*"],
          "template" : {
            "aliases" : {
              "idev2" : { },
              "admin_idev" : { },
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 2
        }
      }
    ]
  }
```

`admin` has unrestricted access to all templates. Now, let's check which templates `dev` are supposed to see:

```
$ curl -vk -u dev1:test "http://127.0.0.1:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "index_templates" : [
      {
        "name" : "t1",
        "index_template" : {
          "index_patterns" : ["i*"],
          "template" : {
            "aliases" : {
              "idev1" : { }
            }
          },
          "composed_of" : [ ]
        }
      },
      {
        "name" : "t2",
        "index_template" : {
          "index_patterns" : ["idev1_*"],
          "template" : {
            "aliases" : {
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 1
        }
      },
      {
        "name" : "t4",
        "index_template" : {
          "index_patterns" : ["idev1_*"],
          "template" : {
            "aliases" : {
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 2
        }
      }
    ]
  }
```

Hmm, we can see many weird things here. Let's start with the simplest case: index template `t2` is allowed for the user, because the used index pattern is allowed by `indices` rule. But we can also see that user `dev1` is not aware of existence the `admin_idev` alias - it was filter out from the aliases list. The user has no access to the alias, so he should not be able to see it.

What about the index template `t3`? `dev1` is not allowed to see it because the index pattern `idev2_*` is not allowed for him. It was also pretty much obvious!

The next is `t4`. When `admin` had listed index templates, we saw that template `t4` has 2 index patterns. But `dev1` can see only one. This is great, because he has an access to a part of that template, so he definitely should be able to see it. ROR behaviour here is pretty neat - it allows the user to see a template with filtered, not allowed parts of it, but at the same time, the user doesn't have permissions to modify/remove the template (Don't believe me? Go ahead and check!)

And the last one to explain - `t1`. The index pattern of the template is `i*`. Obviously user `dev1` has no access to it, because his allowed indices are `idev1, idev1_*`. But if we imagine all possible values generated from pattern `i*` and all possible values generated from `idev1, idev1_*`, we can notice that the latter will be a subset of the first. It means that this template can be interesting for the user `dev1`, because it will ba applied to indices created by him. That's why ROR decides to show it.

</details>

## Component templates

Component templates doesn't have index patterns but could have aliases. So, in this case, we should also consider four types of template related requests:

### Create a component template

The request will be allowed when all of following conditions are met:

* a template with requested name does not exist (if it does, it's rather a template modification, than a creation),
* all aliases of the new, requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

Unlike index templates, component templates don't have index patterns. But they still have aliases. So, their behaviour according to an aliases usage is quite similar, but there are several differences which are worth mentioning.

Let's check if `dev1` user can create a component template:

```
$ curl -vk -u dev1:test "http://localhost:9200/_component_template/ctemp1?pretty" -XPUT -H "Content-Type: application/json" -d \
  '{
     "template": {
   	   "settings": {
   	     "index.number_of_replicas": 0
   	   },
   	   "aliases": { 
   	     "idev1": {},
   	     "idev2": {}
   	   }
     }
  }'
  
  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Oh, user `dev1` is not allowed to create this template. But wait! It looks like we have the same problem as had while creating index template. Alias `idev2` is not allowed. Let's try to do the same without this alias:

```
$ curl -vk -u dev1:test "http://localhost:9200/_component_template/ctemp1?pretty" -XPUT -H "Content-Type: application/json" -d \
  '{
     "template": {
   	   "settings": {
   	     "index.number_of_replicas": 0
   	   },
   	   "aliases": { 
   	     "idev1": {}
   	   }
     }
  }'
  
  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Ha! As expected. A user has to have access to all aliases during adding a component template which has aliases defined.

*Note* If a component template doesn't involve aliases, there is no restriction from ROR side to add one. It can be changed in future, when we add sth like `templates` rule.

</details>

### Modify a component template

The request will be allowed when all of following conditions are met:

* a template with requested name does exist,
* all aliases of the existing template are allowed,
* all aliases of the requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

In the previous example, user `dev1` created the component template `ctemp1` with one alias `idev1`. Let's check if user `dev2` will be able to modify it:

```
$ curl -vk -u dev2:test "http://localhost:9200/_component_template/ctemp1?pretty" -XPUT -H "Content-Type: application/json" -d \
  '{
     "template": {
   	   "settings": {
   	     "index.number_of_replicas": 0
   	   },
   	   "aliases": { 
   	     "idev2": {}
   	   }
     }
  }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

No. And this is a good behaviour, because `dev2` doesn't have an access to the alias `idev1` which the `ctemp1` has. ROR assumes, that he cannot modify the component template (please notice, that the same request will be allowed when a different, nonexistent component template name is used). I can assure you that `dev1` is able to modify the template (you can check if you want).

</details>

### Delete a component template

The request will be allowed when template does not exist OR all of the following conditions are met:

* a template with requested name does exist,
* all aliases of the existing template are allowed.

<details>

<summary>Example (click to expand)</summary>

If you read the previous example, you won't find anything interesting here. A component template can be removed only by someone whom ROR considers to have modification rights of the template. See that `dev2` is not able to remove `ctemp1`:

```
$ curl -vk -u dev2:test -XDELETE "http://localhost:9200/_component_template/ctemp1?pretty"

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

I told you. But please remember that only aliases are checked by ROR when it's trying to figure out modification rights of a component template. If a component template doesn't have any aliases, it can be modified or deleted by any user.

</details>

### Get component templates

At the moment there is no way to restrict which component templates can be visible to the user (it is going to change in the future - see a corresponding index template section). But ROR is going to hide the information about not allowed aliases of returned component templates.

<details>

<summary>Example (click to expand)</summary>

A careful reader can guess that ROR won't forbid showing component templates. But similar to indices templates, ROR will filter out aliases list depending on an aliases accessability of current user. Let's see an example:

```
$ curl -vk -u admin:admin "http://localhost:9200/_component_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "component_templates" : [
      {
        "name" : "ctemp2",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : {
              "idev2" : { }
            }
          }
        }
      },
      {
        "name" : "ctemp1",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : {
              "idev1" : { }
            }
          }
        }
      }
    ]
  }
```

We can see that we have two component templates. `ctemp1` has alias `idev1` and `ctemp2` alias `idev2`. Let check what templates `dev1` user will be able to see:

```
$ curl -vk -u dev1:test "http://localhost:9200/_component_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "component_templates" : [
      {
        "name" : "ctemp2",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : { }
          }
        }
      },
      {
        "name" : "ctemp1",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : {
              "idev1" : { }
            }
          }
        }
      }
    ]
  }
```

We can see that he is able to see all component templates, but `ctemp2` doesn't have `idev2` alias. User `dev1` has no access to the alias, so response returned by ROR doesn't contain the alias. Similar behaviour we will observe when `dev2` user will try to get all templates.

</details>

## Troubleshooting

To figure out why the template is not returned or/and cannot be altered, you should [enable a DEBUG log level](/develop/elasticsearch#acl-troubleshooting) and check your logs. ROR logs each step of template request handling in the `indices` rule, so detailed description should explain the given template is not allowed.


# For Kibana

User manual for ReadonlyREST Enterprise/PRO/Free

🧙 **Are you using Kibana version 7.8.x or older? Go to the** [**old platform manual page**](/develop/kibana/kibana-7.8.x-and-older)**.**

## Overview

ReadonlyREST plugin for Kibana is not open source, and it's offered as part of the [ReadonlyREST PRO](https://readonlyrest.com/pro) and [ReadonlyREST ENTERPRISE](https://readonlyrest.com/enterprise), and [ReadonlyREST Free](https://readonlyrest.com/free) packages. See product descriptions and a comparison chart on the official [ReadonlyREST website](https://readonlyrest.com)

ReadonlyREST plugins for Kibana **always require** the ReadonlyREST open-source plugin to be installed in the Elasticsearch nodes your Kibana instance(s) will connect to.

Installation of ReadonlyREST is not required on all Elasticsearch nodes. It's mandatory to be installed only on the nodes where you intend to secure the HTTP interface.

### After purchasing

If you haven't installed it yet, download the latest [universal build](https://docs.readonlyrest.com/universal-builds) from our [download page](https://readonlyrest.com/download/) and install it manually. Alternatively, see below if you want to install it directly via the command line without downloading it from the browser.

Once the universal build plugin for Kibana is installed, you can activate it using an **activation key**. You can get one of these in the [ReadonlyREST customer portal](https://readonlyrest.com/customer) if you are a subscriber, otherwise, use the same portal to get a trial activation key (for PRO or Enterprise) for 30 days evaluation.

### Version strings

All our plugins include in their file name a version string. For example, the file `readonlyrest-1.46.0_es8.6.0.zip` has a version string `1.46.0_es8.6.0`.

#### Reading version strings

Given the version string `1.46.0_es8.6.0`

* ReadonlyREST plugin code version `1.46.0`
* Works only with Elasticsearch/Kibana version `8.6.0`

The "es" stands for "Elastic stack" which used to mean the family of products made by Elastic which get released at the same time under the same version number. This was chosen **before** Elastic renamed their X-Pack commercial offer to Elastic Stack.

To be clear, there is no affiliation between ReadonlyREST and Elastic, or their commercial products.

#### Universal Kibana plugin version strings

Our Kibana plugin file naming follows very similar rules:

I.e. `readonlyrest_kbn_universal-1.46.0_es8.6.0.zip`

* ReadonlyREST PRO plugin version 1.46.0
* Works only with Kibana version 8.6.0

### When an update is out

You will receive another email notification that a new deliverable is available.

If the update contains a security fix, it is very important that you take action and **update the plugin immediately**.

## Installation and Operations

### Running with Docker

The simplest method to run Kibana with the ReadonlyREST plugin is to use one of our docker images which you can find on [Docker Hub](https://hub.docker.com/r/beshultd/kibana-readonlyrest). In the example below we will use [Docker Compose](https://docs.docker.com/compose/):

```yaml
# docker-compose.yml file content
services:

  kbn-ror:
    image: beshultd/kibana-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports: 
      - "5601:5601"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes
      - ELASTICSEARCH_HOSTS=https://es-ror:9200
      - ELASTICSEARCH_USERNAME=kibana
      - ELASTICSEARCH_PASSWORD=kibana
      - ELASTICSEARCH_SSL_VERIFICATIONMODE=none
      - readonlyrest_kbn__cookiePass=abcd1234abcd1234abcd1234abcd1234 # this is an equivalent of the `readonlyrest_kbn.cookiePass` setting defined in kibana.yml
    depends_on:
      - es-ror

  es-ror:
    image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports:
      - "9200:9200"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
      - KIBANA_USER_PASS=kibana
      - ADMIN_USER_PASS=admin
      - discovery.type=single-node

```

It can be run like this:

```bash
docker-compose up
```

It will run Kibana container with ReadonlyREST connected with the single ES node (with ReadonlyREST too). You can access Kibana by calling `http://localhost:5601` in the browser (use `admin:admin` credentials to log in).

#### Customizing ROR Kibana settings

All config options are described in the [configuration section](#configuration) below. In general, you will use the `kibana.yml` file to configure ROR Kibana settings. But in the case, of the ROR Docker image, you can pass any ROR settings as ENV - just remember to replace `.` (dot) with `__` (double underscore). E.g. to configure `readonlyrest_kbn.store_sessions_in_index: true` pass `readonlyrest_kbn__store_sessions_in_index=true` ENV.

### Installation

You can install this as a normal Kibana plugin using the `bin/kibana-plugin` utility. Let's see the two ways to use this utility with ReadonlyREST.

{% hint style="warning" %}
**Don't forget**

After Kibana 7.9.x, it's necessary to [patch](#patching-kibana) Kibana after you install, otherwise ReadonlyREST will NOT work.
{% endhint %}

#### Installing via URL

This installation method is more practical if your Kibana server is connected to the internet.

Please note that this will always download the latest version of Kibana plugin available for the current supported Elasticsearch version.

```bash
bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_universal&email=<your_email_address>" # ReadonlyREST Universal Kibana plugin
```

If you want to download the latest version of the plugin for a specific version of Kibana, then use the query parameter `esVersion` to specify your required Kibana version.

```bash
bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_universal&esVersion=7.6.1&email=<your_email_address>"
```

If you want to download an older version of the plugin for a specific version of Elasticsearch, then use the query parameter `pluginVersion` along with `esVersion`. Please note that you can only go so far back with plugin versions. [Let us know](https://readonlyrest.com/contact) if you can't download a specific one.

```bash
bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_universal&esVersion=8.6.0&pluginVersion=1.46.0&email=<your_email_address>"
```

It's possible to add an extra query parameter (`checksum=true`) to any download URL to obtain a `sha1` checksum of the corresponding deliverable. For example:

```bash
curl -vvv  "https://portal.readonlyrest.com/download/kbn?esVersion=8.6.0&pluginVersion=1.46.0&email=your@emailaddress.com&edition=kbn_universal&checksum=true" 
[...]
curl -vvv  "https://portal.readonlyrest.com/download/es?esVersion=8.6.0&pluginVersion=1.46.0&checksum=true" 
[...]
```

Now you are ready to [patch Kibana](#patching-kibana).

#### Installing from a zip file

```bash
bin/kibana-plugin install file:///home/user/downloads/readonlyrest_kbn-X.Y.Z_esW.Q.U.zip
```

Notice how we need to type in the format `file://` + absolute path (yes, with three slashes).

#### Patching Kibana

If you are using Kibana 7.9.x or newer, you need **an extra post-installation step**. This will slightly modify some core Kibana files.

**Before Kibana 8.15.0**

```bash
node/bin/node plugins/readonlyrestkbn/ror-tools.js patch
```

**For Kibana 8.15.0 and never**

**For Linux**

```bash
node/glibc-217/bin/node plugins/readonlyrestkbn/ror-tools.js patch
```

**For macOS**

```bash
node/default/bin/node plugins/readonlyrestkbn/ror-tools.js patch
```

**For Windows**

```shell
node\default\node plugins\readonlyrestkbn\ror-tools.js patch
```

**Patching Kibana acknowledgment in a silent mode**

To apply patches in Kibana using a script in non-interactive mode (bypassing prompts), you have two options:

* Using `--I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes` Script Argument:

```shell
node/bin/node plugins/readonlyrestkbn/ror-tools.js patch --I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes # This example applies to Kibana before 8.15.0. Be sure to use the correct Node.js path based on the Kibana version and your operating system.
```

* Using environment variable:

Define `I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes` env variable and run patching script as usual

#### Unpatching Kibana

If you are using Kibana 7.9.x or newer, you need **an extra pre-uninstallation step**. This will restore the core Kibana files to the original state.

**Before Kibana 8.15.0**

```bash
node/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch
```

**For Kibana 8.15.0 and never**

**For Linux**

```bash
node/glibc-217/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch
```

**For macOS**

```bash
node/default/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch
```

**For Windows**

```shell
node\default\node plugins\readonlyrestkbn\ror-tools.js unpatch
```

#### Configuring Kibana

For the activation key persistence after upgrading the license to PRO or Enterprise edition, From readonlyREST version 1.51.0 `readonlyrest_kbn.cookiePass` is a required `kibana.yml` config parameter. It needs to be configured also in case of a free license.

#### Uninstalling

{% hint style="info" %}
To uninstall, you should unpatch Kibana first, then uninstall the ReadonlyREST plugin. However, **the Kibana plugin system uninstallation process is highly unreliable**.

So we highly recommend throwing away the entire Kibana directory and starting from scratch. Ideally, use ephemeral docker containers.

Need inspiration? Try the [ROR Docker demo](https://github.com/sscarduzio/ror-docker-demo)!
{% endhint %}

To bring Kibana to its pre-patching original state, it's possible to unpatch.

**Before Kibana 8.15.0**

```bash
node/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

**For Kibana 8.15.0 and never**

**For Linux**

```bash
node/glibc-217/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

**For macOS**

```bash
node/default/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

**For Windows**

```shell
node\default\node plugins\readonlyrestkbn\ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

And the classic uninstall command...

```bash
bin/kibana-plugin remove readonlyrest_kbn
```

#### Upgrading Kibana

The ReadonlyREST plugin version must always match the currently installed Kibana version. As a result, if you want to upgrade Kibana with ROR plugin installed:

1. Before upgrading Kibana, unpatch and uninstall the ReadonlyREST plugin according to the instructions:
   * [Unpatch Kibana](#unpatching-kibana)
   * [Uninstall the plugin](#uninstalling)
2. Upgrade Kibana.
3. After upgrading Kibana, install the matching version of the ReadonlyREST plugin and patch according to the instructions:
   * [Install matching plugin version](#installation)
   * [Patch Kibana](#patching-kibana)

{% hint style="warning" %}
Upgrading Kibana without following the instructions above may cause corruption of the Kibana installation and inability to patch the upgraded version.
{% endhint %}

#### Upgrading ReadonlyREST plugin

To upgrade to a new version of ReadonlyREST plugin for Kibana, you should:

* [Unpatch Kibana](#unpatching-kibana)
* [Uninstall](#uninstalling) the old plugin
* [Install](#installation) the new one
* [Patch Kibana](#patching-kibana)
* Restart Kibana

#### Major version upgrades when using multi-tenancy

If you use multi-tenancy (Enterprise only), you will have one or more tenancy-specific Kibana indices beyond the main `.kibana` (e.g. `.kibana_tenant1`, `.kibana_tenant2`, etc.).

The first time you run Kibana after a major version upgrade (e.g. upgrading from Kibana 7.17.7 to Kibana 8.0.0), Kibana will run a [saved objects migration](https://www.elastic.co/guide/en/kibana/current/saved-object-migrations.html) on the default `.kibana` index, or whatever it finds configured as `kibana.index` in `kibana.yml`.

Now, because you may have multiple Kibana indices containing saved objects, you should apply the "saved object migration" to those indices as well.

ReadonlyREST Enterprise will automatically make sure a tenancy index is migrated to satisfy the current Kibana version **right before every time it's being used.**

For example, after a tenant logs in, before the Kibana session is started, or when a user changes tenancy with the tenancy switcher, the tenancy index gets created if absent, checked and migrated if necessary. These logs mean that migration started correctly:

```
  [savedobjects-service] Waiting until all Elasticsearch nodes are compatible with Kibana before starting saved objects migrations...
  [savedobjects-service] Starting saved objects migrations
  [savedobjects-service] [.kibana] INIT -> CREATE_NEW_TARGET. took: 27ms.
  [savedobjects-service] [.kibana_task_manager] INIT -> CREATE_NEW_TARGET. took: 29ms.
  [savedobjects-service] [.kibana_task_manager] CREATE_NEW_TARGET -> MARK_VERSION_INDEX_READY. took: 82ms.
  [savedobjects-service] [.kibana] CREATE_NEW_TARGET -> MARK_VERSION_INDEX_READY. took: 95ms.
  [savedobjects-service] [.kibana_task_manager] MARK_VERSION_INDEX_READY -> DONE. took: 23ms.
  [savedobjects-service] [.kibana_task_manager] Migration completed after 135ms
  [savedobjects-service] [.kibana] MARK_VERSION_INDEX_READY -> DONE. took: 20ms.
  [savedobjects-service] [.kibana] Migration completed after 143ms
```

Now Kibana will have migrated the tenancy index, like it did with the main `.kibana` index.

#### Using ROR with a reverse proxy

ROR - just like Kibana itself - is meant to be used either with a proxy or without one.

* If you decide to set the `server.basePath` property in `kibana.yml` and set `server.rewriteBasePath` into a `true`, ROR will be accessed directly and via a reverse proxy,
* If you decide to rewrite the base path manually by your reverse proxy and set the `server.rewriteBasePath` property in `kibana.yml` into a `false`, be sure to access ROR via a proxy, as it will not work properly when accessed directly.

## Configuration

ReadonlyREST for Kibana is almost entirely remote-controlled from the Elasticsearch configuration. Login credentials, hidden Kibana apps, etc. are all going to be configured from the Elasticearch side via the usual "rules". This means the configuration will be kept all in one place and if you used ReadonlyREST before, it will be also very familiar.

### ROR Settings in kibana.yml

* `readonlyrest_kbn.logLevel: <trace|debug|info|error|warn>`: for extra visibility set debug or (rarely) trace. Keep in mind `trace` could leak secrets into logs, so be careful.
* `readonlyrest_kbn.logPrettyPrintEnabled: true|false`: if you want to see pretty-printed or compact logs.
* [session configuration](#session-configuration)
* [UI customisation](#login-screen-tweaking)
* [custom middleware](#custom-middleware)

> In this document, every time you will encounter references to "readonlyrest.yml" or "elasticsearch.yml", we will be referring to the configuration files **in the Elasticsearch plugin** (our Kibana plugins do not need a "readonlyrest.yml").

In general, by design, we tend to concentrate all configuration within the main plugin (the Elasticsearch one) as much as possible.

### Kibana configuration

Activate authentication for the Kibana server: let the Kibana daemon connect to Elasticsearch using one of the following methods:

* a pair of credentials defined in `readonlyrest.yml` (see above, the ::KIBANA-SRV:: block).
* [a service account token](https://www.elastic.co/guide/en/elasticsearch/reference/current/service-accounts.html#service-accounts-tokens) generated for Kibana, defined in `readonlyrest.yml` (see above, the ::KIBANA-SRV-TOKEN:: block). Open up `conf/kibana.yml` and add the following:

```yaml
# Kibana server use the ::KIBANA-SRV:: basic auth credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"

# Kibana server use the ::KIBANA-SRV-TOKEN:: token value (without the bearer scheme)
# use the following setting instead of the 'elasticsearch.username' and the 'elasticsearch.password'
# elasticsearch.serviceAccountToken: AAEAAWVsYXN0aWMva2liYW5hL3Rva2VuXzE6MVhQUXRubWhRd3FxUmlzNmhFVVZQdw

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

And of course, also make sure `elasticsearch.url` points to the designated Elasticsearch instance (check also the http or https)

### Cluster-wide Settings VS readonlyrest.yml

([PRO](https://readonlyrest.com/pro))

Our Kibana plugins introduce a "ReadonlyREST" Kibana app. From here, you can edit the security settings of the whole Elasticsearch cluster, and they will take effect within 10 seconds in all Elasticsearch cluster nodes without the need to restart them.

When you change the security settings from the Kibana app, they will be saved in a special index called ".readonlyrest", so all the Elasticsearch nodes will pick them up. You can customize the name of the index by setting `readonlyrest.settings_index: .my_custom_readonlyrest` in the `elasticsearch.yml` file (remember to set the same value for all your ES nodes).

When an Elasticsearch node restarts, the order of settings evaluation is the following: 1. Attempt to find valid settings in readonlyrest.yml 2. If none is found, look inside elasticsearch.yml 3. Once successfully bootstrapped using file-based settings, attempt to read ".readonlyrest" index 4. If the index exists and contains valid settings, override file-based settings with the ones from the index. 5. Pressing "save" in the cluster-wide settings app, will **not overwrite the readonlyrest.yml** file.

Best practices:

* Build and update your production security settings from the Kibana app (will be saved in index)
* Protect the ".readonlyrest" Kibana index with an ACL rule

#### Loading settings: order of precedence

As you read, there are two possible places where the settings can be read from:

* `readonlyrest.yml` a file the user needs to create in the same directory where `elasticsearch.yml` is found.
* `.readonlyrest` index. Our Kibana plugins' GUI (PRO/Enterprise) is programmed to write this index.

When the ES plugin boots up, it follows some logic to evaluate where to read the YAML settings from. The following diagram shows how that works.

![config loading diagram](/files/7iuaj1fPpwOLSwymxLzO)

#### Malformed in-index settings

If for some reason the in-index settings get corrupted and ROR can't parse them, then neither settings from file or in-index settings can be loaded, so ES can't start. In this case, ES would print a message like:

```
Loading ReadonlyREST settings from index failed: Settings config content is malformed. Details: while scanning a quoted scalar
 in 'reader', line 9, column 17:
          auth_key: "admin:container
                    ^
```

To recover from this state, set `readonlyrest.force_load_from_file: true` in `elasticsearch.yml` on one node `es1`.

Example recovery settings:

elasticsearch.yml

```yaml
[...]
readonlyrest:
  force_load_from_file: true
```

readonlyrest.yml

```yaml
readonlyrest:

  access_control_rules:
  - name: "::ADMIN recover::"
    auth_key: admin:dev
    indices: ["*"]
```

Then remove the in-index settings index manually.

```bash
curl -X DELETE "admin:dev@es1:9200/.readonlyrest?pretty"
```

Now you can restore your settings to `readonlyrest.yml`, remove `readonlyrest.force_load_from_file: true` `from elasticsearch.yml` and restart the node.

### Example: multiuser ELK

This configuration will work in PRO and Enterprise editions. This is a typical example of a configuration snippet to add at the end of your `readonlyrest.yml` (the settings file of the Elasticsearch plugin), to support ReadonlyREST PRO.

```yaml
readonlyrest:

    access_control_rules:

    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["logstash-*"]

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

   #  use the following block instead of the `::KIBANA-SRV::` block if you use service account tokens (see https://www.elastic.co/guide/en/elasticsearch/reference/current/service-accounts.html)
   #
   #- name: "::KIBANA-SRV-TOKEN::"  
   #  token_authentication:
   #    token: "Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2VuXzE6MVhQUXRubWhRd3FxUmlzNmhFVVZQdw" # generated token for Kibana
   #    username: kibana

    - name: "::RO::"
      auth_key: ro:dev
      indices: ["logstash-*"]
      kibana:
        access: ro
        hide_apps: [ "Security", "Enterprise Search"]

    - name: "::RW::"
      auth_key: rw:dev
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: [ "Security", "Enterprise Search"]


    - name: "::ADMIN::"
      auth_key: admin:dev
      # KIBANA ADMIN ACCESS NEEDED TO EDIT SECURITY SETTINGS IN ROR KIBANA APP!
      kibana:
        access: admin

    - name: "::WEBSITE SEARCH BOX::"
      indices: ["public"]
      actions: ["indices:data/read/*"]
```

### Very important

#### ACL blocks ordering matters

> Blocks related to the authentication of the users should be at the top of the ACL

One of the most common mistakes is forgetting that the ACL blocks are evaluated in order from the first to the last.

So, some requests with credentials can be let through from one of the first blocks and come back to Kibana with no user identity metadata associated.

Take this example of a troublesome ACL:

```yaml
    # PROBLEMATIC SETTINGS (EXAMPLE) ⚠️

    access_control_rules:

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin
```

The user will be able to login because the login request will be allowed by the first ACL block. But the ACL will not have resolved any metadata about the user identity (credentials checking was ignored)!

This means the response to the Kibana login request will contain no user identity metadata (username, hidden apps, etc) and ReadonlyREST for Kibana won't be able to function correctly.

The solution to this is to reorder the ACL blocks, so the ones that authenticate Kibana users are on the top.

```yaml
    # SOLUTION: KIBANA USER AUTH RELATED BLOCKS GO FIRST! ✅👍

    access_control_rules:

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]
```

### SSL/TLS server

You can configure Kibana with the ReadonlyREST plugin to accept SSL connection the same way you would with vanilla Kibana configuration. For example, in `kibana.yml`:

```yaml
server.ssl.enabled: true
server.ssl.keystore.path: "/usr/share/kibana/config/certificates/kibana-server.p12"
server.ssl.keystore.password: ""
server.ssl.supportedProtocols: ["TLSv1.2", "TLSv1.3"]
```

#### Secure cookies

ReadonlyREST will set the "secure" flag to its Kibana session cookie ("ror-cookie") automatically when SSL is enabled in Kibana.\
\
This is because modern browsers like Chrome won't accept "secure"-flagged cookies if the website is not HTTPS.

However, a common situation is when SSL is configured in a reverse proxy (SSL termination): so the browser will interact with Kibana using HTTPS. But because ROR doesn't know it, it will still serve session cookies without the "secure" flag.\
\
In this case, you can force ReadonlyREST to create "secure"-flagged cookies by adding this line in `kibana.yml`:

```yaml
xpack.security.secureCookies: true 
```

### Load balancers

These features will work with all ReadonlyREST Editions

#### Enable health check endpoint

Normally a load balancer needs a health check URL to see if the instance is still running, you can whitelist this Kibana path so the load balancer avoids a redirection to `/login`.

Edit `kibana.yml`

```
readonlyrest_kbn.whitelistedPaths: [".*/api/status$"]
```

#### Session management with multiple Kibana instances

Each Kibana node stores user sessions in memory. This will cause problems when using multiple Kibana instances behind a load balancer (especially without sticky sessions), as there would be no synchronization between nodes' session cache. To avoid this, session synchronization via an Elasticsearch index should be enabled. Follow these steps:

1. Come up with a string of at least 32 characters length or more to be used as the shared cookie encryption key, called `cookiePass`.
2. Open up `conf/kibana.yml` and add:
   * `readonlyrest_kbn.cookiePass: "generatedStringIn1step"` (example: "12345678901234567890123456789012")
   * `readonlyrest_kbn.cookieName` (custom cookie name - this property is optional; if not specified default cookie name would be `rorCookie`)
   * `readonlyrest_kbn.store_sessions_in_index: true` (enable session storage in index)
   * `readonlyrest_kbn.sessions_index_name: "someCustomIndexName"` (index name - this property is optional; if not specified default index would be `.readonlyrest_kbn_sessions`)
   * `readonlyrest_kbn.sessions_refresh_after: 5000` (time in milliseconds, describes how often sessions should be fetched from ES and refreshed for each node - optional, by default 2 seconds)
   * `readonlyrest_kbn.sessions_probe_interval_seconds: 120` (default 60s) how often should the browser poll Kibana to check if their session is still valid. Raise this value if you connect to Kibana through slow networks (i.e. VPN), or have very slow-loading dashboards.
3. Add the above config in all Kibana nodes behind the load balancer, and restart them.

{% hint style="warning" %}
From ReadonlyREST version 1.51.0 `readonlyrest_kbn.cookiePass` is a required `kibana.yml` config parameter.
{% endhint %}

### Session Configuration

#### Session timeout

When a user logs in, ReadonlyREST writes an encrypted cookie in the browser. The session lifetime can be configured with the following key in `kibana.yml`:

```yaml
readonlyrest_kbn.session_timeout_minutes: 480 # defaults to 4320 (3 days)
```

This is a sliding inactivity window — each user action resets the clock.

#### Automatic Session cleanup

All expired Index or In-memory sessions, determined by an `expiresAt` date that falls prior to the current time and date, will be systematically cleaned. The parameters for this automated session cleanup procedure can be adjusted within the `kibana.yml` configuration file.

```yaml
readonlyrest_kbn.sessions_cleanup_interval: '1h' # Default to 1d 
```

**Automatic Session cleanup options**

You can defines interval as:

| Value | Description | Example |
| ----- | ----------- | ------- |
| s     | seconds     | "1s"    |
| m     | minutes     | "1m"    |
| h     | hours       | "1h"    |
| d     | days        | "1d"    |

#### Clearing session history

By default, all session data (search history, dev tool command history, etc.) is wiped from the browser whenever a new user logs in or a user changes tenancy. To override this behavior:

```yaml
readonlyrest_kbn.clearSessionOnEvents: ["never"]
```

Possible values: `"login"`, `"tenancyHop"`, `"never"`.

#### Cookie settings

ReadonlyREST sets the following security flags on every session cookie:

| Flag       | Default                   | Notes                                                                                                                                                             |
| ---------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HttpOnly` | `true` (always)           | Cannot be disabled.                                                                                                                                               |
| `Secure`   | `true` when TLS is active | Automatically enabled when Kibana is configured with SSL, or when `readonlyrest_kbn.cookies.secure: true` is set explicitly (required for NGINX SSL termination). |
| `SameSite` | `Lax`                     | Configurable to `Strict` (recommended for management interfaces) or `None`.                                                                                       |

The following cookie attributes are configurable in `kibana.yml`:

| Setting                             | Default      | Notes                                                                                                                                                   |
| ----------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `readonlyrest_kbn.cookieName`       | `rorCookie`  | Name of the session cookie.                                                                                                                             |
| `readonlyrest_kbn.cookiePass`       | *(required)* | Minimum 32-character secret used to encrypt the cookie. Required since ROR 1.51.0.                                                                      |
| `readonlyrest_kbn.cookies.secure`   | auto         | Set to `true` to force the `Secure` flag when SSL is terminated at a reverse proxy (i.e. Kibana runs over plain HTTP internally).                       |
| `readonlyrest_kbn.cookies.sameSite` | `Lax`        | Controls the `SameSite` attribute. Accepted values: `strict`, `lax`, `none`. Use `none` together with `secure: true` for cross-domain iframe embedding. |

Example `kibana.yml`:

```yaml
readonlyrest_kbn.cookieName: rorCookie
readonlyrest_kbn.cookiePass: <minimum-32-character-secret>
readonlyrest_kbn.cookies.sameSite: strict   # lax | strict | none
readonlyrest_kbn.cookies.secure: true       # explicit override for reverse-proxy setups
```

The `HttpOnly` flag is always set and cannot be disabled. The `Secure` flag is set automatically when Kibana is configured with SSL.

***

### Terminate Kibana on ES high-watermark

When enabled, Kibana will exit if the connected Elasticsearch cluster reports a disk high‑watermark condition. This is useful to prevent Kibana from running in a degraded state when Elasticsearch is unable to allocate shards due to insufficient disk space.

```yaml
# kibana.yml
# If set to true, Kibana will exit when Elasticsearch reports a disk high-watermark condition.
readonlyrest_kbn.diskThresholdVerificationEnabled: false  # default: true
```

## Authentication

ReadonlyREST Kibana supports several methods for authenticating users at the Kibana layer. When a user logs in through one of these methods, Kibana establishes a session and forwards the verified identity to Elasticsearch. On the Elasticsearch side, ReadonlyREST must be configured to trust this forwarded identity using the appropriate rule:

| Kibana authentication method     | Required ROR ES rule                                                                                                                                                                                         |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Proxy Auth                       | [`proxy_auth`](/develop/elasticsearch#proxy_auth)                                                                                                                                                            |
| SAML / OIDC                      | [`ror_kbn_authentication`](/develop/elasticsearch#ror_kbn_authentication), [`ror_kbn_authorization`](/develop/elasticsearch#ror_kbn_authorization), or [`ror_kbn_auth`](/develop/elasticsearch#ror_kbn_auth) |
| Standard login form (Basic auth) | No Kibana-level auth config needed — handled directly in ROR ES via `auth_key`, `ldap_authentication`, etc.                                                                                                  |

This is why SAML and OIDC sections below each contain an "Elasticsearch side" configuration step: the two plugins must share a secret so the identity can flow securely between them.

### Proxy Auth

This feature will work in all ReadonlyREST editions.

ROR for Elasticsearch can delegate authentication to a reverse proxy which will enforce some kind of authentication, and pass the successfully authenticated user's name inside an `X-Forwarded-User` header.

> Today, it's possible to skip the regular ROR login form and use the "delegated authentication" technique in ROR for Kibana as well.

1. Configure ROR for ES to expect delegated authentication (see [`proxy_auth` rule](/develop/elasticsearch#proxy_auth)) in ROR for ES documentation.
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.proxy_auth_passthrough: true`

Now ROR for Kibana will **skip the login form entirely**, and will only require that all incoming requests must carry an `X-Forwarded-User` header containing the user's name. Based on this identity, ROR for Kibana will build an encrypted cookie and handle your session normally.

#### Custom Logout link

This feature will work in all ReadonlyREST editions.

Normally, when a user presses the logout button in ROR for Kibana, it deletes the encrypted cookie that represents the user's identity and the login form is shown.

However, when the authentication is delegated to a proxy, the logout button needs to become a link to some URL capable to unregister the session a user-initiated within the proxy.

For this, ROR for Kibana offers a way to customize the logout button's URL:

1. Find a link that will delete the reverse proxy's user session
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_logout_link: https://..../logout`

Now users who gained a session through delegated auth can also click on the logout button in ROR for Kibana and actually exit their session.

#### Custom Login link

This feature will work in all ReadonlyREST editions.

When you delegate authentication to an external service, you can tell ReadonlyREST to skip the classic login form entirely and redirect users to your proxy or identity provider's login screen.

To enable this:

1. Find your authentication proxy or identity provider login URL for the ROR app
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_login_link: "https://../login"`

The advantage of this approach is a streamlined user experience for users that login with an external IdP. The disadvantage is that you give up the possibility to log in as a local user in ROR, as the login form will be always skipped.

#### Caveat

Enabling proxy auth passthrough will relax the requirement to provide a password. Therefore, don't enable this option if you don't make sure Kibana can **only be accessed through the reverse proxy\***.

### JWT Token Forwarding as URL Query Parameter

This feature will work in all ReadonlyREST editions.

As an alternative to typing in credentials in the standard login form, it is possible to create an authenticated Kibana session by passing a JWT token as a query parameter in a URL.

#### Configuration

To enable this feature in ReadonlyREST, you need to:

* Have JWT authentication configured in ReadonlyREST (modifying `readonlyrest.yml` or the cluster-wide settings UI in the Kibana plugin). [See how](/develop/elasticsearch#json-web-token-jwt-auth).
* Specify the query parameter name in `kibana.yml` by adding the line `readonlyrest_kbn.jwt_query_param: "jwt"` as a string, in our case "jwt".

#### In Action

Once Kibana is restarted, you will be able to navigate to a link like this:

```
http://kibana:5601/login?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

The following will happen:

1. The Kibana plugin will forward the JWT token found in the query parameter into the `Authorization` header in a request to Elasticsearch.
2. Elasticsearch will cryptographically authenticate and resolve the user's identity from the JWT claims.
3. Kibana will write an encrypted cookie in your browser and use that from now on for the length of the authenticated session. From here onwards, the session management will be identical to the normal login form flow.
4. When the user presses logout, Kibana will delete the cookie and redirect you to the login form, or whatever link you configured as `readonlyrest_kbn.custom_logout_link`.

**Deep linking with JWT**

Because the identity is embedded in the link, and ReadonlyREST is able to authenticate the request on the fly, the JWT authentication can be used in conjunction with the `nextUrl` query parameter for sharing deep links inside Kibana apps.

**Anatomy of a JWT deep link**

```
http://kibana:5601/login?jwt=<the-token>&nextUrl=urlEncode(<kibana-path>)
```

In JavaScript one can compose a JWT deep link as follows:

```javascript
var absoluteKibanaPath = '/app/kibana#/visualize/edit/28dcde30-2258-11e8-82a3-af58d04b3c02?_g=()';

var url = 'http://kibana:5601/login?jwt=' + 
           jwtToken + 
           '&nextUrl=' + 
           encodeURI(absoluteKibanaPath);

console.log("Final JWT deep link: " + url)
```

The result may look something like this:

```
http://localhost:5601/login?nextUrl=%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2F28dcde30-2258-11e8-82a3-af58d04b3c02%3F_g%3D%28%29&jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

### Embedding Kibana Dashboard or Visualization with an iframe and JWT Authentication

([PRO](https://readonlyrest.com/pro))

You have the option to embed visualizations and dashboards inside iframes. For more information, refer to the [official Elastic documentation](https://www.elastic.co/guide/en/kibana/current/reporting-getting-started.html#embed-code).

To add JWT authentication, modify the iframe `src` attribute as follows:

Original iframe `src`:

```html
<iframe src="https://localhost:5601/s/default/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f?embed=true&_g=()&_a=()" height="600" width="800"></iframe>
```

Modified iframe `src` with JWT:

```html
<iframe src="https://localhost:5601/s/default/app/dashboards?jwt=<the-token>#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f?embed=true&_g=()&_a=()" height="600" width="800"></iframe>
```

Replace with your actual JWT token to enable authentication.

{% hint style="info" %}
For a cross-domain iframe, you need to set the cookie sameSite: none and secure: true. You can do this via the kibana.yml configuration file by setting `readonlyrest_kbn.cookies.secure: true` and `readonlyrest_kbn.cookies.sameSite: 'none'`.
{% endhint %}

### SAML

([Enterprise](https://readonlyrest.com/enterprise))

ReadonlyREST Enterprise supports service provider-initiated via SAML. This connector supports both SSO (single sign-on) and SLO (single log out). Here is how to configure it.

#### Configure ReadonlyREST ES bridge

In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugins with a shared secret, that is: an arbitrarily long string.

#### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    # ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/develop/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/develop/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/develop/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

**⚠️IMPORTANT** Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

#### Kibana side

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  saml_serv1:
    enabled: true
    type: saml
    issuer: ror
    buttonName: "Partner's SSO Login"
    entryPoint: 'https://my-saml-idp/saml2/http-post/sso' # <-- identity Provider's URL, to request to sign on
    kibanaExternalHost: 'my.public.hostname.com' # <-- public URL used by the Identity Provider to call back Kibana with the "assertion" message
    protocol: http # <-- is the Kibana server listening for "http" "https" connections? Default: http
    usernameParameter: 'nameID'
    groupsParameter: 'memberOf'
    logoutUrl: 'https://my-saml-idp/saml2/http-post/slo'
    cert: /etc/ror/integration/certs/dag.crt # <-- It can be also provided a string value 
    
    # OPTIONAL, advanced parameters
    # decryptionCert: /etc/ror/integration/certs/pub.crt
    # decryptionPvk: /etc/ror/integration/certs/decrypt_pvk.crt
    # issuer: saml_sso_idp
```

* `issuer`: issuer string to supply to identity provider during sign-on request. Defaults to 'ror'
* `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. This is known to help when authenticating against Active Directory (AD FS) servers.
* `decryptionPvk`: Service Provider Private Key. A private key will be used to attempt to decrypt any encrypted assertions that are received.
* `cert`: The downloadable certificate in IDP Metadata (file, absolute path) or single line string value

For advanced SAML options, see [passport-saml documentation](https://github.com/bergie/passport-saml).

#### Identity provider side

1. Enter the settings of your identity provider, and create a new app.
2. Configure it using the information found by connecting to `http://my.public.hostname.com/ror_kbn_saml_serv1/metadata.xml`

Example response:

```xml
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="onelogin_saml" ID="onelogin_saml">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn/notifylogout"/>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
    <AssertionConsumerService index="1" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn/assert"/>
  </SPSSODescriptor>
</EntityDescriptor>
```

1. Create some users and some groups in the identity provider app
2. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set Kibana in debug mode so ReadonlyREST will print the user profile).
3. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
4. If you want to use SAML for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting.

#### Usage with Active Directory Federation Services

To work properly with ADFS, ensure that you add the following to the configuration:

```yaml
readonlyrest_kbn:
   auth:
      saml_adfs:
              authnContext: "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/windows" # Name identifier format to request auth context.`
              identifierFormat: null # Name identifier format to request from identity provider.`
              [...]
```

#### Additional Parameters

When configuring SAML authentication in ReadonlyREST Enterprise, you can provide additional parameters to customize the behavior of the SAML service provider integration. These parameters allow for fine-tuning the SAML integration to work with various identity providers and specific configurations

You can find a list of all supported parameters in the [Passport-SAML Configuration Parameters documentation](https://github.com/node-saml/passport-saml/tree/3.x?tab=readme-ov-file#config-parameter-details)

```yaml
readonlyrest_kbn:
   auth:
      saml_serv1:
              audience: "https://sp.example.com/metadata"`
              [...]
```

### OpenID Connect (OIDC)

([Enterprise](https://readonlyrest.com/enterprise))

This feature will work in ReadonlyREST Enterprise.

ReadonlyREST Enterprise supports OpenID Connect for both authentication and authorization.

Here is how to configure it.

#### Configure ReadonlyREST ES bridge

This part is identical as seen in SAML connectors. In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugins with a shared secret, that is: an arbitrarily long string.

#### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    # ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

**⚠️IMPORTANT** the Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

If you have configured OIDC with the `groupsParameter` ( *See below* ), you can also restrict ACL to specific groups:

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    # ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1 for group 1"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group1"]

    - name: "ReadonlyREST Enterprise instance #1 for group 2"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group2"]

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

You may also use any custom claim from the OIDC `userinfo` token in ACL rules by using `{{jwt:assertion.<path_to_your_claim>}}` syntax. See the [Dynamic variables from JWT claims section](/develop/elasticsearch#usage-examples) for more information. ( **TIP** : Do not forget the `assertion` prefix in front of you jsonpath. )

#### Kibana side

We will assume the OpenID identity provider responds to port 8080 of localhost. In our example, we used Keycloak, an open-source implementation of OpenID Connect identity provide.

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  oidc_kc: 
    buttonName: "KeyCloak OpenID"
    type: "oidc"
    issuer: 'http://localhost:8080/auth/realms/ror'
    authorizationURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/auth'
    tokenURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/token'
    userInfoURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/userinfo'
    clientID: 'ror_oidc'
    clientSecret: '9f1d39c8-a211-460a-84b6-0a4a1499c455'
    scope: 'openid profile roles role_list email'
    usernameParameter: 'preferred_username'
    groupsParameter: 'groups'
    kibanaExternalHost: 'localhost:5601'
    logoutUrl: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/logout'
    jwksURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/certs'
```

#### Identity provider side

1. Enter the settings interface of your identity provider, and create a new OpenID app.
2. The redirect URL should be configured as `http://localhost:5601/*` assuming Kibana is listening on localhost and on the default port.
3. Create some users and some groups in the identity provider if not present.
4. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set `readonlyrest_kbn.logLevel: debug` in kibana.yml, so you will see the user profile how it's received from the identity provider right in the logs).
5. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
6. If you want to use OpenID for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting. ( **TIP**: the `groupsParameter` must be present in the `userinfo` token of your OIDC provider.)
7. If Kibana is accessed through a reverse proxy, kibanaExternalHost should be configured with the external hostname. if omitted, the default value is equal to `server.host:server.port` defined in kibana.yml. ( This parameter can be used also when Kibana is bound to 0.0.0.0, for example, if using docker.)

#### Client Authentication Methods

You can configure how the ReadonlyREST Kibana plugin sends `client_id` and `client_secret` to the identity provider using the option

```yaml
readonlyrest_kbn:
   auth:
      oidc_kc:
         tokenEndpointAuthMethod: 'client_secret_post'  #Available options: client_secret_basic (default) or client_secret_post
              [...]
```

There are two available methods for authentication:

1. **client\_secret\_basic** (default): The `client_id` and `client_secret` are sent using the Authorization header, as specified in [RFC 6749, Section 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1). Before sending, the `client_id` and `client_secret` are encoded.
2. **client\_secret\_post**: The `client_id` and `client_secret` are included in the request body, following the guidelines in [RFC 6749, Section 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1). In this method, the `client_id` and `client_secret` are not encoded before being sent. Choose this method when the OpenID Connect provider, such as [lemonLDAP::NG](https://lemonldap-ng.org/), cannot decode the encoded values.

The following description explains the available options for the setting:

#### User Info Source Methods

You can configure where the ReadonlyREST Kibana plugin obtains the OIDC user profile information using the `userInfoSource` option in the `readonlyrest_kbn.auth.oidc_kc` block. There are three available methods:

1. **user\_info\_endpoint** (default):\
   When set to `user_info_endpoint`, the plugin makes an additional call to the URL specified under `userInfoURL` to retrieve the most up-to-date user profile information from the OIDC provider.
2. **access\_token**:\
   When set to `access_token`, the plugin extracts the user profile information directly from the access token.
3. **id\_token**:\
   When set to `id_token`, the plugin extracts the user profile information directly from the ID token.

For example, you can configure it as follows:

```yaml
readonlyrest_kbn:
   auth:
      oidc_kc:
         userInfoSource: 'access_token'  # Available options: 'user_info_endpoint' (default), 'access_token', 'id_token'
```

#### Additional Parameters

When configuring OpenID Connect (OIDC) in ReadonlyREST Enterprise, you can provide additional parameters to customize the behavior of the OIDC client and issuer. These parameters allow for fine-tuning the OIDC integration to work with various providers and specific configurations. These additional parameters allow you to solve complex authentication scenarios, work with non-standard OIDC providers, and fine-tune the security and performance characteristics of your OIDC integration.

**Issuer Additional Parameters**

You can find a list of all supported parameters:

For Kibana 7.12.0 and above: [documentation](https://github.com/panva/openid-client/tree/v5.7.1/docs#new-issuermetadata)

For Kibana below 7.12.0: [documentation](https://github.com/panva/openid-client/tree/v4.9.1/docs#new-issuermetadata)

```yaml
readonlyrest_kbn.auth:
   oidc_kc:
      [...]
      issuerAdditionalParameters:
         metadata:
            token_endpoint: 'https://custom-token-endpoint'
            jwks_uri: 'https://custom-jwks-uri'
```

#### Clock skew tolerance

You can configure the clock tolerance (in seconds) to account for potential time discrepancies between the Kibana server and the OpenID Connect provider. This setting helps prevent authentication failures due to minor time differences.

```yaml
readonlyrest_kbn.auth:
   oidc_kc:
      [...]
      clockToleranceSeconds: 5  # Default is 0 seconds
```

**Client Additional Parameters**

You can find a list of all supported parameters:

For Kibana 7.12.0 and above: [documentation](https://github.com/panva/openid-client/tree/v5.x/docs#new-clientmetadata-jwks-options)

For Kibana below 7.12.0: [documentation](https://github.com/panva/openid-client/tree/v4.x/docs#new-clientmetadata-jwks-options)

```yaml
readonlyrest_kbn.auth:
   oidc_kc:
      [...]
      clientAdditionalParameters:
        metadata:
          response_types: ['code']
          redirect_uris: ['https://my-app/callback']
        jwks:
           keys:
           - kty: 'RSA'
             use: 'sig'
             alg: 'RS256'
             kid: 'key1'
             n: 'PLACEHOLDER_TO_CHANGE_INTO_REAL_CERTIFICATE'
             e: 'AQAB'
        options:
          additionalAuthorizedParties: 'my-app'
```

### Impersonation

According to [Wikipedia](https://en.wikipedia.org/wiki/Impersonator):

> An impersonator is someone who imitates or copies the behavior or actions of another.

So, an impersonation can be understood as imitating behaviors or actions. In the context of ReadonlyREST: one user could imitate an action of another user. Why would we want it? Let's suppose the first user is an admin, who has just configured access for a new user. They would like to know if the rule(s) are configured correctly. And here comes the impersonation feature. The admin can impersonate the given user in Kibana and see what the user would see if they logged in themselves.

ROR plugins support impersonation and provide UI for configuring a cluster before using it. Visit the [impersonation details page](/develop/kibana/impersonation) to know more.

## Multi-tenancy

### Multi-tenancy Kibana

([Enterprise](https://readonlyrest.com/enterprise))

ReadonlyREST Enterprise is capable of going beyond multi-user. Users or groups can be isolated into tenancies, so their dashboards and configurations won't mix. Behind each tenancy, there is a kibana index.

#### What is a kibana index?

In the vanilla Kibana, all the configuration objects are stored under an Elasticsearch index called `.kibana`, but with ReadonlyREST Enterprise installed, you can dynamically route Kibana into reading and writing to other indices entirely, for example `.kibana_tenancy1`. So when "tenancy1" is selected from the UI, Kibana hard reloads and all settings, dashboards, and visualizations are (potentially) different.

A user can be associated to multiple tenancies, and if so, will be presented with a tenancy switcher in the UI. ![image](https://github.com/beshu-tech/readonlyrest-docs/assets/1327189/b07d27d3-310c-4754-a5c5-21b0fe3f3d45)

Using this tool, they can hop between tenancies. Keep in mind that the ACL evaluation is slightly different when multi tenancy is activated: if a tenancy is selected, only blocks without `kibana.index` rule, or with the `kibana.index` [rule](https://docs.readonlyrest.com/elasticsearch#kibana) matching to the current teancy name will be evaluated.

In ReadonlyREST Enterprise, multi-tenancy is activated by default. But if you want it to behave as in PRO/Free editions, you can disable it by writing into `kibana.yml`:

```yml
readonlyrest_kbn.multiTenancyEnabled: false
```

### Configuring Multi-tenancy

([Enterprise](https://readonlyrest.com/enterprise))

You can configure an ACL in multi tenancy mode by adding a few ACL blocks containing the `kibana.index` [rule](https://docs.readonlyrest.com/elasticsearch#kibana). See examples and further explanation under our [multi-tenancy guide](/develop/examples/multitenancy_guide).

### Extending the Kibana API with the x-ror-tenancy-id header

([Enterprise](https://readonlyrest.com/enterprise))

To target a specific tenant when making a [Kibana API](https://www.elastic.co/guide/en/kibana/current/api.html) request, include the custom HTTP header `x-ror-tenancy-id`. The value of this header should match one of the [groups rules](/develop/elasticsearch#groups-rules) id defined in your ACL configuration. The first group defined in the ACL for a specific user is used as the default tenancy id.

example usage:

```bash
curl -X GET "http://localhost:5601/api/saved_objects/_find?type=dashboard" \
  -H "kbn-xsrf: true" \
  -H "x-ror-tenancy-id: marketing-team"
```

#### No authentication rule defined

The “problem with the configuration of authentication” error message is presented in ReadonlyREST Free/PRO/Enterprise when the login request is checked by the ACL and gets accepted by an ACL block with no authentication rule in it.

An example of this would be:

```yaml
readonlyrest:
   access_control_rules:
   - name: "LDAP Auth"
     ldap_authentication: ...
   
   - name: "Allow requests from localhost"
     hosts: ["127.0.0.1"]
```

Imagine you run Elasticsearch and Kibana on the same host:

* the Kibana user login request comes to Elasticsearch
* Credentials are wrong, and the first block does not match
* The second block is then evaluated, and the request is allowed because of its origin IP

As you can see, Elasticsearch has no user-related information (metadata) to return to Kibana, and the error “problem with the configuration of authentication ” is shown.

In general, we highly discourage implementing access control using origin IPs alone, users should set up SSL, Basic HTTP auth in their agents in any case, even on localhost. The `hosts` rule would then be an extra protection.

If this is not possible for very important reasons, then we would prevent any Kibana-originated request to match that rule by using the negated form of the [headers rule](/develop/elasticsearch#headers). I.e.

readonlyrest.yml

```yaml
- name: "Allow requests from localhost"
  hosts: ["127.0.0.1"]
  headers: [ "~x-from-kibana:true" ]
```

kibana.yml (append)

```yaml
elasticsearch.customHeaders:  {"x-from-kibana":"true"}
```

### Tenancy index templating

([Enterprise](https://readonlyrest.com/enterprise))

This feature will work only with ReadonlyREST Enterprise

When a tenant logs in for the first time, ReadonlyREST Enterprise will create the kibana index associated to the tenancy as per ACL. For example, it will create and initialize the ".kibana\_user1" index, where the tenant "user1" will store all the ["saved objects"](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html), that is: visualizations, dashboards, spaces, settings, data views, etc.

The problem is that user1, and any other new users would login for the first time in to a completely blank Kibana. And this is particularly challenging if the tenant is supposed to be read-only (i.e. kibana.access: "ro") because they won't even have privileges to create their own index-pattern, let alone any dashboards.

To fix this, ReadonlyREST Enterprise offers the possibility for administrators to create and curate a template kibana index from which all the Kibana objects will be copied over to the newly initialised tenancy. The objects in the templating index will be copied every time the user logs in (or changes tenancy with the tenancy selector), and **if the objects were already present, they will be overwritten**.

The object overwrite is desirable because administrators would like to improve and enrich the content of the template tenancy over time, and these enhancements need to be propagated to the tenants.

If the tenants were not read-only, and created other objects of their own (e.g. another space, another dashboard), these won't be deleted.

#### Reset tenancy to template

If you add `readonlyrest_kbn.resetKibanaIndexToTemplate: true` to `kibana.yml` your tenants will get their index deleted and reinitialized to the content in the kibana template index specified in `readonlyrest_kbn.kibanaIndexTemplate` every time they log in, or change tenancy using the tenancy selector.

The reset tenancy to template only works if a valid kibana index template is specified.

#### How to use tenancy templating

An administrator will need to create the template tenancy, populate it with the default Kibana objects (index-patterns, dashboards) and configure ReadonlyREST Enterprise to take the index template it in use. Let's see this step by step:

**Create the template tenancy**

Let's start to add to our access control list (found in $ES\_PATH\_CONF/config/readonlyrest.yml, or ReadonlyREST App in Kibana) a local user "administrator" that will belong to two tenancies: the default one (stored in .kibana index), and the template one (stored in .kibana\_template index).

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

  users:
  - username: administrator
    auth_key: administrator:dev
    groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu
```

NB: If you know what you are doing, you can add a tenancy with kibana\_index: ".kibana\_template" adding a LDAP/SAML group to your administrative user.

#### Configure the template tenancy

Now login as administrator in Kibana, hop into the "Template" tenancy, and start configuring the default saved objects for your future tenants: add all the data views, create or import all the dashboards you want.

#### Configure the template tenancy index in ReadonlyREST Enterprise

Open kibana.yml and add the following line:

```yaml
readonlyrest_kbn.kibanaIndexTemplate: ".kibana_template"
```

Now, ReadonlyREST Enterprise will look for the ".kibana\_template" index, and try to copy over all its documents every time a new kibana index is initialised to support a new tenancy.

#### Try it out

Restart Kibana with the new setting. Add a new tenancy to the ACL:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

  # Newly added tenant!
  - name: user1
    auth_key: user1:passwd
    kibana:
      access: rw
      index: ".kibana_user1"

  users:
  - username: administrator
    auth_key: administrator:dev
    groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu

```

Now try to login as user1, and ReadonlyREST Enterprise should initialize the index ".kibana\_user1" with all the index patterns and dashboards contained in the template tenancy.

### Tenant index configuration

You can configure the `number_of_shards` and `number_of_replicas` for the tenant index via the `kibana.yml` file, allowing you to override the default index settings. This can be particularly useful in a single-node environment.

```yaml
readonlyrest_kbn.tenantIndex.number_of_shards: 1
readonlyrest_kbn.tenantIndex.number_of_replicas: 0
```

{% hint style="warning" %}
These settings will overwrite the index template settings.
{% endhint %}

## UI Customization

### Hiding Kibana Apps

([PRO](https://readonlyrest.com/pro))

Previously we needed to keep track and document all Kibana app IDs, and you had to look them up all the time. Now we made it simpler by letting you type the apps and submenu titles exactly as you see them in the UI.

For example, this is how you hide the whole Enterprise Search submenu.

![kibana\_hide\_apps: \["Enterprise Search"\]](/files/-MXq6rKbbyqZPQtJVADZ)

And this is how you hide only one app from the Enterprise Search menu:

![kibana\_hide\_apps: \["Enterprise Search|Workplace Search"\]](/files/-MXq7Z0f12aRqYchy1pX)

More generally, either of these two ways will work:

```yaml
kibana:
  hide_apps: [ "<submenu-title>" ]
```

```yaml
kibana:
  hide_apps: [ "<submenu-title|app-title>" ]
```

For example, the following is a valid rule:

```yaml
kibana:
  hide_apps: [ "Security", "Management|Stack Management", "Enterprise Search" ]
```

There is also a way to use regular expression as a `kibana.hide_apps` value

for example, you can hide all submenus except for the specific app

```yaml
kibana:
  hide_apps: [ "/^Analytics\\|(?!(Maps)$).*$/"]
```

In this case, all analytics apps will be hidden except `Maps`

**⚠️IMPORTANT** Pipe operator needs to be escaped correctly when it's declared in the regular expression `\\|`. The regular expression must be declared between double quote `"/<regular-expression/"`

You can also hide all submenus except specified values

```yaml
kibana:
  hide_apps: ["/^(?!(Analytics|Management).*$).*$/"]
```

In this case, everything except of `Analytics` and `Management`, will submenus will be hidden

**⚠️IMPORTANT** In this case `|` is treated as logical `or` operator, that's why it shouldn't be escaped

To check all regular expressions available options, check the [regular expressions syntax cheatsheet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet)

#### Hiding Kibana management apps

There is an option to hide specific management apps. You can declare hide\_apps value like:

```yaml
hide_apps: [ "<submenu-title|app-title|management-submenu-title|management-app-title>" ]

```

* To hide a single management application, you can use:

```yaml
kibana:
  hide_apps: [ "Management|Stack Management|Kibana|Tags" ]
```

In this case, only the Stack Management Tags application will be hidden

* To hide all management Kibana section applications, you can use:

```yaml
kibana:
  hide_apps: [ "/^Management\\|Stack Management\\|(?!(Kibana)|$).*$/" ]
```

In this case, all Stack Management Kibana sections will be hidden

* To hide all management Kibana section applications except selected, you can use

```yaml
kibana:
  hide_apps: ["/^Management\\|Stack Management\\|Kibana\\|(?!(Data Views|Tags)$).*$/"]
```

In this case, all Stack Management Kibana section apps except Data Views and Tags will be hidden

* To hide all management applications except selected, you can use

```yaml
kibana:
  hide_apps: ["/^Management\\|Stack Management\\|(?!(Kibana)|$).*$/", "/^Management\\|Stack Management\\|Kibana\\|(?!(Data Views|Tags)$).*$/"]
```

In this case, all Stack Management apps except Data Views and Tags will be hidden

### Hiding ReadonlyREST menu elements

This feature will work in ReadonlyREST PRO and Enterprise.

To hide the `Manage Kibana` button for the specific user you need to provide `ROR Manage Kibana` value into a `kibana.hide_apps`

```yaml
kibana:
  hide_apps: [ "ROR Manage Kibana" ]
```

To hide the `Edit security settings` button for the specific user you need to provide `ROR Security Settings` or `readonlyrest_kbn` value into a `kibana.hide_apps`

```yaml
kibana:
  hide_apps: [ "ROR Security Settings" ]
```

![Hiding ReadonlyREST menu elements](/files/cSDBfyn0vmXlYEAxJN8v)

### Login screen tweaking

([PRO](https://readonlyrest.com/pro))

These features will work with ReadonlyREST PRO and Enterprise.

It is possible to customize the look of the login screen.

#### Two column layout

By default, the login form appears in a single-column view. ![one column](blob:https://imgur.com/f7514ca2-7f8f-4f96-aecd-09e7ea636b62)

But once the title and subtitle are configured, it will switch to two columns to make room for the new text.

```yaml
readonlyrest_kbn.login_title: "Some Title"
readonlyrest_kbn.login_subtitle: "Longer text <b>any HTML is supported<b/> including ifrmaes"
```

![two columns](https://i.imgur.com/Sqf1GIL.png)

#### Add your company logo

It's recommended to use a transparent PNG, negative logo. Ideally a white foreground, and transparent background.

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.login_custom_logo: 'https://.../logo.png'
```

To incorporate your personalized logo into the login page, place your image file within the `<YOUR_ROOT_DIRECTORY>/kibana/plugins/readonlyrestkbn/public/assets directory`. Then, proceed by appending the following code snippet to `kibana.yml`:

```yaml
readonlyrest_kbn.login_custom_logo: '/pkp/legacy/web/assets/<YOUR_LOGO>'
```

Your personalized logo can be in any format [supported by web browsers](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types). The maximum file size varies depending on the browser you're using. We recommend keeping them smaller, with a maximum size of 500KB, to maintain optimal page load speed.

#### Add custom CSS/JS

**Inject via HTML code**

You have the opportunity to inject HTML code right before the closing head tag (`</head>`).

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.login_html_head_inject: '<style> * { color:red; }</style>'
```

**Inject via JS file**

There is an option to inject JavaScript file before the login screen is rendered.

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.login_html_head_inject: '<ABSOLUTE_PATH_TO_CUSTOM_JS_FILE>'
```

**Inject via CSS file**

There is an option to inject CSS file before the login screen is rendered.

```yaml
readonlyrest_kbn.login_custom_css_inject_file: '<ABSOLUTE_PATH_TO_CUSTOM_CSS_FILE>'
```

### Kibana UI tweaking

([Enterprise](https://readonlyrest.com/enterprise))

This feature will work with ReadonlyREST Enterprise

It's possible to inject custom CSS and Javascript to achieve a customized user experience for your users/tenants.

#### Inject custom CSS in Kibana

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.kibana_custom_css_inject: '.global-nav, kbnGlobalNav { background-color: green }'
```

Alternatively, it's possible to load the CSS from a file in the filesystem:

```yaml
readonlyrest_kbn.kibana_custom_css_inject_file: '/tmp/custom.css'
```

**⚠️IMPORTANT** If you use relative paths, you end up pointing to kibana home, i.e. `readonlyrest_kbn.kibana_custom_css_inject_file: 'config/custom.css'` will refer to `$KBN_HOME/config/custom.css` which is the same directory where `kibana.yml` can normally be found.

#### Inject custom JS in Kibana

```yaml
readonlyrest_kbn.kibana_custom_js_inject: '$(".global-nav__logo").hide(); alert("hello!")'
```

Alternatively, it's possible to load the JS from a file in the filesystem:

```yaml
readonlyrest_kbn.kibana_custom_js_inject_file: '/tmp/custom.js'
```

**⚠️IMPORTANT** If you use relative paths, you end up pointing to kibana home, i.e. `readonlyrest_kbn.kibana_custom_js_inject: 'config/custom.js'` will refer to `$KBN_HOME/config/custom.js` which is the same directory where `kibana.yml` can normally be found.

### Custom middleware

([Enterprise](https://readonlyrest.com/enterprise))

Sometimes, Enterprise users might need more flexibility and customize the plugin behavior to adjust the product to the business needs. There are two options to declare the custom middleware:

* JS file: `readonlyrest_kbn.custom_middleware_inject_file: '/path/to/your/file.js'` // You can also use a relative path here. It's relative to the kibana root folder
* Inline: `readonlyrest_kbn.custom_middleware_inject: 'function test(req, res, next) {logger.debug("custom middleware called"); next()}'`

Visit the [Custom middleware](/develop/examples/custom-middleware) to know more.

## Audit dashboard

This feature will work in all ReadonlyREST editions.

The Elasticsearch plugin audit feature is widely described in [📖docs for the Elasticsearch plugin](/develop/elasticsearch#audit). The Kibana plugin has a predefined dashboard representing collected audit data.

### Loading visualization

In the *Audit* tab of the ReadonlyREST Kibana app, there is a button that automatically creates a dashboard with some audit log-specific visualizations.

![audit log tab](/files/iKPgTVLGkfQxtXpUVZGu)

Click the *Load* button to load the dashboard and visualizations. An *Override* checkbox allows reloading the default dashboard and visualizations. It will override any previously loaded audit log dashboard.

![loading visualization](/files/VE6AjQNHqXYPAyV8vhjk)

In detail, this feature creates three Kibana "saved objects":

* an index pattern for `readonlyrest_audit-*`
* a dashboard called `ReadonlyREST Audit Log`
* some visualizations

### Dashboard

The audit log dashboard, by default, has only a few basic visualizations. They cover security, access logs, and performance metrics.


# Impersonation (Enterprise)

([Enterprise](https://readonlyrest.com/enterprise))

After describing what [the impersonation is](/develop/kibana#impersonation), it's high time to see how ROR supports it and who and when could be interested in using this feature. Let's start with the latter.

## Use cases

The impersonation feature is intended for ROR administrators, rather than users. We can point out the two most obvious use cases when the admin could take advantage of the feature:

#### Debugging users' problems:

Let's imagine that some user has a problem with their ROR configuration (eg. the user doesn't have access to some feature that was blocked at ROR's level by you, the admin). And they are not able to clearly describe what the issue is (sounds familiar?). As an administrator, it would be extremely beneficial if you could see what the user sees. Thanks to the impersonation feature, an admin is allowed to impersonate the user and experience exactly what the user experiences.

#### Configuring a new user:

When an admin configures a new user in ROR settings, they face two problems:

1. `Will the updated configuration break the production cluster?`
2. `How do I know that the new user is correctly configured? Did I configure all their permissions correctly??`

Both of the problems can be solved using the ROR's impersonation. Thanks to the fact that the impersonation feature always uses its own Test Settings, that is completely independent from the main production settings, the admin can alter it without worries that their actions will break something and users won't be able to do their job.

Admin can add the new user configuration without worrying and then test it by impersonating the user. They can check if the user can log in without problems and if the user has access only to the Kibana features the admin wanted to grant. When the admin is sure that everything is configured correctly, they can promote the settings (test) to production.

## Impersonation configuration

Before an admin will be able to impersonate a user, they have to configure ROR properly. The configuration consists of several parts:

1. creating ROR's Test Settings,
2. defining mocks of the external services (like [LDAP](/develop/elasticsearch#ldap-connector), [External Basic Auth](/develop/elasticsearch#external-basic-auth) or [Custom groups provider](/develop/elasticsearch#custom-groups-providers)),
3. impersonating a chosen user.

#### Creating ROR's Test Settings

When you call Elasticsearch directly or through ROR Kibana, ROR ACL is defined by Settings (we can assume they are Main Settings). The Test Settings define another ACL, that is taken into consideration by ROR ES only when a proper impersonation header is passed. The header is managed by ROR internally. The Test Settings are active only for a strictly defined amount of time (by default it's *30 minutes*, but the admin can change it before applying Test Settings). After the time has expired, they are automatically invalidated (for security reasons). Obviously, the admin is allowed to invalidate the configured Test Settings in any time. There is no way to have more than one Test Settings configured at time.

ROR Kibana plugin provides a dedicated Test Settings UI. See our [Test Settings management guide](/develop/examples/impersonation/test-settings-ui) for more information.

But copying Main Settings as Test Settings is not enough. We also have to instruct ROR which users can be considered as impersonators (the ones, who are allowed to impersonate other users):

1. In the `access_control_rules` section in ROR Settings, there must be a rule that authenticates the impersonator user.
2. The impersonator user must be defined in the `impersonation` section in ROR Settings
3. The impersonator's credentials in `impersonation` section must match the credentials, that the impersonator uses to authenticate in Kibana.

```yaml
readonlyrest:
  access_control_rules:
    - name: "Authenticate admin1"
      auth_key: admin1:pass
    - name: "Authenticate admin2"
      ldap_authentication: "ldap1"

  impersonation:
    - impersonator: admin1      // Who can impersonate? (user name or pattern)
      users: ["*"]              // Who can be impersonated? (user names or patterns)
      auth_key: admin1:pass     // Authentication rule required to impersonate (any authentication rule can be used here)
    - impersonator: admin2
      users: ["dev2"]
      ldap_authentication: "ldap1"
```

In the example above, we see that we have two impersonators: `admin1` and `admin2`. The first one can impersonate any user (`*`) and they are able to authenticate using basic auth (`admin1:pass`). The second impersonator can impersonate only `dev2` user. They will be authenticated using `ldap1` connector.

When an impersonator passes wrong credentials ROR will tell Kibana that impersonation is not allowed.

#### Defining mocks of the external services (optional)

ROR has many sophisticated authentication & authorization methods. Some of them are based on external systems like LDAP. The problem with such systems, in regard to to the impersonation feature, is that those systems either don't support it by default or don't support it at all and even if they do - the configuration is complex.

That's why we decided to solve it totally differently - using mocks. [Wikipedia](https://en.wiktionary.org/wiki/mock) defines `mock` as `an imitation, usually of lesser quality.` And in the case of external authentication systems we are going provide an imitation of it that will tell ACL which users should be successfully authenticated by it. When we consider an authorization service, a mock of it will return the ACL users with their roles in the service. And this is enough for ROR to support impersonation.

How does ROR use the mocks? Let's suppose we have an `ldap_auth` rule. When ROR processes the rule, it:

* asks the given LDAP service if the username can be authenticated with a given password, and if they can ...
* asks LDAP to list what groups the user belongs to

In the impersonation case, it looks pretty much the same. The difference being that ROR won't call any LDAP server - the mock will provide the required information instead (no password required). During impersonating, when ROR processes an LDAP rule, it:

* asks the mock if the username exists, and if it does ...
* asks the mock to tell what groups the user belongs to

**⚠️ IMPORTANT:** If one or more of the external services are not mocked, ROR might inform Kibana that the impersonation is not supported. It's better to always define all mocks, to avoid the "Impersonation not supported" Elasticsearch response.

ROR Kibana plugin helps administrators to visually create and edit service mocks with a dedicated graphical UI. Follow our [service mock configuration guide](/develop/examples/impersonation/external-services-mocks-ui) for more.

#### Impersonating a chosen user

Now that we have configured Test Settings and External Services Mocks, we can try to impersonate a user. In Elasticsearch ROR Settings, user can be:

* provided statically (defined in the settings),
* provided dynamically:
  * from external, dependant systems (like LDAP) - we mock them
  * from upstream systems (eg. through headers) - they are not known upfront

It means that we pick the users defined in Settings or Mocks, but also we can enter the username and try to impersonate such user.

Follow the instructions on how to [impersonate a user using the ROR Kibana plugin UI](/develop/examples/impersonation/impersonate-user-ui).

## Logs & audit

In Elasticsearch logs, in `USR` field, if an admin user finds something like this: `admin1 as (user1)` - it means that `admin1` was authenticated and they are the impersonator who is impersonating `user1`.

All logs of impersonated user in Kibana will have this format `[<log level>][plugins][ReadonlyREST][<filename>][impersonating <impersonated user username>]`

When auditing is enabled, the audit document is going to contain an `impersonated_by` field.

## Impersonation limitations

Impersonation mode has some limitations. Please check if they have an impact on your use cases:

* Not all features available in the ROR configuration are testable with impersonation mode. Some rules used in ROR ACL do not support impersonation. For example, auth rule with hashed credentials (e.g. `auth_key_sha512`) can be used in impersonation mode only when credentials follow the format `USER_NAME: HASH(PASSWORD)`; A fully hashed username and password don't allow fetching a username. The auth rule in such a format won't match during impersonation. In the [rules description](/develop/elasticsearch#rules) section you can find information about each rules impersonation support.
* Test Settings are stored in the memory of the node that handled the saving request sent by ROR Kibana plugin. Impersonation support will be limited to this node. We are going to improve it in the future, but for now your Kibana should only communicate with one Elasticsearch node.
* Sometimes it is impossible to fetch usernames defined in the Test Settings. If a `users` rule contains a username pattern with a wildcard, to impersonate a user matching the pattern, you need to enter the username manually.

  ```yaml
  readonlyrest:

    access_control_rules:
      - name: "LDAP group g1"
        type: allow
        groups_any_of: ["g1"]
      
    users:
      - username: "admin*"  // To impersonate a user with a username matching 'admin*' you need to enter the username manually, like 'admin123'
        groups:
          - g1: group1
        ldap_auth:
          name: "ldap1"
          groups_any_of: ["group1"]
        
    ldaps:
      - name: ldap1
        [..]
        
    impersonation:
      [...]
  ```

## Glossary

* **Impersonator** - someone who imitates or copies the behavior or actions of another,
* **Impersonation** - imitating behaviors or actions of a given user,
* **Main Settings** - the ROR's settings that apply to ACL that handles requests during regular sessions (not the impersonation ones),
* **Test Settings** - the ROR's settings that apply to ACL that handles impersonating requests (the ones during impersonation session),
* **External Service Mock** - an imitation of an external service (the supported ones: LDAP, an external authentication service, an external authorization service).


# Kibana 7.8.x and older

User manual for ReadonlyREST Enterprise/PRO/Free plugins

## Kibana Plugin overview

ReadonlyREST plugin for Kibana is not open source, and it's offered as part of the [ReadonlyREST PRO](https://readonlyrest.com/pro) and [ReadonlyREST ENTERPRISE](https://readonlyrest.com/enterprise), and [ReadonlyREST Free](https://readonlyrest.com/free) packages. See product descriptions and a comparison chart in the official [ReadonlyREST website](https://readonlyrest.com)

ReadonlyREST plugins for Kibana **always require** ReadonlyREST Free to be installed in the Elasticsearch nodes your Kibana instance(s) will connect to.

It's not mandatory to install ReadonlyREST Free in all Elasticsearch nodes, but only in the ones in where you need the HTTP interface to be secured.

### After purchasing

You will receive a link to the plugin zip file in an email. Download your zip.

You will be able to download it also in the future as long as your subscription is active.

### Version strings

All our plugins include in their file name a version string. For example the file `readonlyrest-1.16.26_es6.4.0.zip` has a version string `1.16.26_es6.4.0`.

#### Reading version strings

Given the version string `1.16.26_es6.4.0`

* ReadonlyREST plugin code version `1.16.26`
* Works only with Elasticsearch/Kibana version `6.4.0`

The "es" stands for "Elastic stack" which used to mean the family of products made by Elastic which get released at the same time under the same version number. This was chosen **before** Elastic renamed their X-Pack commercial offer to Elastic Stack.

To be clear, there is no affiliation between ReadonlyREST and Elastic, or their commercial products.

#### Trial builds version strings

Trial builds are valid for 30 days after they were built, and they will stop working soon after the time is elapsed. Trial builds have a special version string which includes a build-time timestamp.

I.e. `readonlyrest_kbn_pro-1.16.26-20180911_es6.0.0.zip`

* ReadonlyREST PRO plugin version 1.16.26
* Build date 11th September 2018, expiring on the 11th of October 2018.
* Works only with Kibana version 6.0.0

### When an update is out

You will receive another email notification that a new deliverable is available.

If the update contains a security fix, it is very important that you take action and **update the plugin immediately**.

## Installation

You can install this as a normal Kibana plugin using the `bin/kibana-plugin` utility.

### Install via URL

This installation method is more practical if your Kibana server is connected to the internet.

According to what edition of ReadonlyREST you want to install, from your Kibana installation, launch one of the commands:

Please note that this will always download the latest version of Kibana plugin available for the current supported Elasticsearch version.

```bash
# ReadonlyREST Free edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_free&email=<your_email_address>"

# ReadonlyREST PRO (30 days trial) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&email=<your_email_address>"

# ReadonlyREST Enterprise (30 days trial) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&email=<your_email_address>"
```

If you want to download the latest version of plugin for a specific version of Elasticsearch, then use query parameter esVersion to specify your required Elasticsearch version.

```bash
# ReadonlyREST Free edition for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_free&esVersion=7.6.1&email=<your_email_address>"

# ReadonlyREST PRO (30 days trial) edition for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&esVersion=7.6.1&email=<your_email_address>"

# ReadonlyREST Enterprise (30 days trial) edition for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&esVersion=7.6.1&email=<your_email_address>"
```

If you want to download an older version of plugin for a specific version of Elasticsearch, then use query parameter pluginVersion along with esVersion.

```bash
# ReadonlyREST Free edition - version 1.22.0 for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_free&esVersion=7.6.1&pluginVersion=1.22.0&email=<your_email_address>"

# ReadonlyREST PRO (30 days trial) edition - version 1.22.0 for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&esVersion=7.6.1&pluginVersion=1.22.0&email=<your_email_address>"

# ReadonlyREST Enterprise (30 days trial) edition - version 1.22.0 for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&esVersion=7.6.1&pluginVersion=1.22.0&email=<your_email_address>"
```

If you are a PRO or Enterprise subscriber, the link will include an extra parameter "token" which can only be used in association with the provided email address.

You can append required plugin version and Elasticsearch version query parameters to download specific version as described above.

**NB: This URL is personal, and should be handled as a secret.**

```bash
# ReadonlyREST PRO (Official) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&email=<your_email_address>&token=<your_secret_token>"

# ReadonlyREST Enterprise (30 days trial) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&email=<your_email_address>&token=<your_secret_token>"
```

You can obtain official links with personal secret tokens using our self service [download form](https://readonlyrest.com/download/), once your email address has been recognized as active subscriber.

### Install from zip file

```bash
$ bin/kibana-plugin install file:///home/user/downloads/readonlyrest_kbn-X.Y.Z_esW.Q.U.zip
```

Notice how we need to type in the format `file://` + absolute path (yes, with three slashes).

### Uninstall

```bash
$ bin/kibana-plugin remove readonlyrest_kbn
```

### Upgrade

Just uninstall the old version and install the new version.

```bash
$ bin/kibana-plugin remove readonlyrest_kbn
```

Install the new version of ReadonlyREST into Kibana.

```bash
$ bin/kibana-plugin install file:///home/user/downloads/readonlyrest_kbn-*.zip

# Only for older versions (until Kibana early 6.x)
$ touch optimize/bundles/readonlyrest_kbn.style.css
```

Restart Kibana.

### Using ROR with a reverse proxy

ROR - just like Kibana itself - is meant to be used either with a proxy or without one, but not both simultaneously. If you decide to set the `server.basePath` property in `kibana.yml` be sure to access ROR via a proxy, as it will not work properly when accessed directly.

## Configuration

ReadonlyREST for Kibana is completely remote-controlled from the Elasticsearch configuration. Login credentials, hidden Kibana apps, etc. are all going to be configured from the Elasticearch side via the usual "rules". This means the configuration will be kept all in one place and if you used ReadonlyREST before , it will be also very familiar.

> In this document, every time you will encounter references to "readonlyrest.yml" or "elasticsearch.yml", we will be referring to the configuration files **in the Elasticsearch plugin** (our Kibana plugins do not need a "readonlyrest.yml").

In general, by design, we tend to concentrate all configuration within the main plugin (the Elasticsearch one) as much as possible.

### Clusterwide Settings vs readonlyrest.yml

([PRO](https://readonlyrest.com/pro))

Our Kibana plugins introduce a "ReadonlyREST" Kibana app. From here, you can edit the security settings of the whole Elasticsearch cluster, and they will take effect within 10 seconds in all Elasticsearch cluster nodes without the need to restart them.

When you change the security settings from the Kibana app, they will be saved in a special index called ".readonlyrest", so all the Elasticsearch nodes will pick them up. You can customize a name of the index by setting `readonlyrest.settings_index: .my_custom_readonlyrest` in `elasticsearch.yml` file (remember to set the same value for all your ES nodes).

When an Elasticsearch node restarts, the order of settings evaluation is the following: 1. Attempt to find valid settings in readonlyrest.yml 2. If none is found, look inside elasticsearch.yml 3. Once successfully bootstrapped using file-based settings, attempt to read ".readonlyrest" index 4. If the index exists and contains valid settings, override file based settings with the ones from the index. 5. Pressing "save" in the cluster wide settings app, will **not overwrite the readonlyrest.yml** file.

Best practices:

* Build and update your production security settings from the Kibana app (will be saved in index)
* Protect the ".readonlyrest" Kibana index with an ACL rule

#### Loading settings: order of precedence

As you read, there are two possible places where the settings can be read from:

* `readonlyrest.yml` a file the user needs to create in the same directory where `elasticsearch.yml` is found.
* `.readonlyrest` index. Our Kibana plugins' GUI (PRO/Enterprise) is programmed to write this index.

When the ES plugin boots up, it follows some logic to evaluate where to read the YAML settings from. The following diagram shows how that works.

![config loading diagram](/files/7iuaj1fPpwOLSwymxLzO)

#### Malformed in-index settings

If for some reason the in-index settings get corrupted and ROR can't parse them, then neither settings from file or in-index settings can be loaded, so ES can't start. In this case ES would print message like:

```
Loading ReadonlyREST settings from index failed: Settings config content is malformed. Details: while scanning a quoted scalar
 in 'reader', line 9, column 17:
          auth_key: "admin:container
                    ^
```

To recover from this state, set `readonlyrest.force_load_from_file: true` in `elasticsearch.yaml` on one node `es1`.

Example recovery settings:

elasticsearch.yaml

```yaml
[...]
readonlyrest:
  force_load_from_file: true
```

readonlyrest.yaml

```yaml
readonlyrest:

  access_control_rules:
  - name: "::ADMIN recover::"
    auth_key: admin:dev
    indices: ["*"]
```

Then remove in-index settings index manually.

```bash
curl -X DELETE "admin:dev@es1:9200/.readonlyrest?pretty"
```

Now you can restore your settings to `readonlyrest.yml`, remove `readonlyrest.force_load_from_file: true` `from elasticsearch.yaml` and restart node.

### Example: multiuser ELK

Make sure X-Pack is uninstalled or disabled from `elasticsearch.yml` (on the Elasticsearch side) and `kibana.yml` (on the Kibana side): This is how you disable X-pack modules:

```yaml
# For X-Pack users: you may only leave monitoring on. 
# Don't add this if X-Pack is not installed at all, or Kibana won't start.
xpack.monitoring.enabled: true
xpack.security.enabled: false
xpack.watcher.enabled: false
xpack.telemetry.enabled: false
```

This is a typical example of a configuration snippet to add at the end of your `readonlyrest.yml` (the settings file of the Elasticsearch plugin), to support ReadonlyREST PRO.

```yaml
readonlyrest:

    access_control_rules:

    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["logstash-*"]

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    - name: "::RO::"
      auth_key: ro:dev
      indices: ["logstash-*"]
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:stack_management"]

    - name: "::RW::"
      auth_key: rw:dev
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:stack_management"]


    - name: "::ADMIN::"
      auth_key: admin:dev
      # KIBANA ADMIN ACCESS NEEDED TO EDIT SECURITY SETTINGS IN ROR KIBANA APP!
      kibana:
        access: admin

    - name: "::WEBSITE SEARCH BOX::"
      indices: ["public"]
      actions: ["indices:data/read/*"]
```

### Very important

Whatever your configuration ends up being, remember:

* The admin user has `kibana.access: admin`
* Remember to use `kibana.hide_apps: ["readonlyrest_kbn"]` to hide the ReadonlyREST icon from who is not meant to use it (makes for a better UX).

#### Rules ordering matters

> Blocks related to the authentication of the users should be at the top of the ACL

One of the most common mistakes is forgetting that the ACL blocks are evaluated in order from the first to the last.

So, some request with credentials can be let through from one of the first blocks and come back to Kibana with no user identity metadata associated.

Take this example of troublesome ACL:

```yaml
    # PROBLEMATIC SETTINGS (EXAMPLE) ⚠️

    access_control_rules:

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin
```

The user will be able to login because the login request will be allowed by the first ACL block. But the ACL will not have resolved any metadata about the user identity (credentials checking was ignored)!

This means the response to the Kibana login request will contain no user identity metadata (username, hidden apps, etc) and ReadonlyREST for Kibana won't be able to function correctly.

The solution to this is to reorder the ACL blocks, so the ones that authenticate Kibana users are on the top.

```yaml
    # SOLUTION: KIBANA USER AUTH RELATED BLOCKS GO FIRST! ✅👍

    access_control_rules:

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]
```

#### Session cookie expiration

When a user logs in, ReadonlyREST will write an encrypted cookie in the browser. This cookie has an time to live that can be tweaked with the following configuration key in `kibana.yml`.

```
readonlyrest_kbn.session_timeout_minutes: 600 # defaults to 4320 (3 days)
```

#### Clearing Session History

By default, all the session data like search history, dev tool commands history, etc, will be wiped out from the browser whenever a new user is logged in, or a user changes tenancy. To override this behaviour, use this setting:

```
readonlyrest_kbn.clearSessionOnEvents: ["never"]
```

Possible values: `"login", "tenancyHop", "never"`.

#### Kibana App strings

Examples of valid arguments for the `kibana.hide_apps: [...]` rule (readonlyrest.yml)

| hide-app key                     | App name         | App url                                                                                                                                |
| -------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| kibana:discover                  | Discover         | <http://kibana-url:5601/app/kibana#/discover>                                                                                          |
| kibana:visualize                 | Visualize        | <http://kibana-url:5601/app/kibana#/visualize>                                                                                         |
| kibana:dashboard                 | Dashboard        | <http://kibana-url:5601/app/kibana#/dashboards>                                                                                        |
| timelion                         | Timelion         | <http://kibana-url:5601/app/timelion>                                                                                                  |
| canvas                           | Canvas           | <http://kibana-url:5601/app/canvas>                                                                                                    |
| maps                             | Maps             | <http://kibana-url:5601/app/maps>                                                                                                      |
| code                             | Code (Beta)      | <http://kibana-url:5601/app/code>                                                                                                      |
| ~~readonlyrest\_kbn~~ (obsolete) | ~~ReadonlyREST~~ | ~~\~\~\[~~<http://kibana-url:5601/app/readonlyrest_kbn~~](http://kibana-url:5601/app/readonlyrest_kbn)~~~~>                            |
| ml                               | Machine Learning | <http://kibana-url:5601/app/ml>                                                                                                        |
| infra:home                       | Infrastructure   | [http://kibana-url:5601/app/infra#/infrastructure/inventory?\_g=(](http://kibana-url:5601/app/infra#/infrastructure/inventory?_g=%28)) |
| infra:logs                       | Logs             | [http://kibana-url:5601/app/infra#/logs?\_g=(](http://kibana-url:5601/app/infra#/logs?_g=%28))                                         |
| apm                              | APM              | <http://kibana-url:5601/app/apm>                                                                                                       |
| uptime                           | Uptime           | <http://kibana-url:5601/app/uptime#/>                                                                                                  |
| siem                             | SIEM             | <http://kibana-url:5601/app/siem>                                                                                                      |
| graph                            | Graph            | <http://kibana-url:5601/app/graph>                                                                                                     |
| kibana:dev\_tools                | Dev Tools        | <http://kibana-url:5601/app/kibana#/dev_tools>                                                                                         |
| monitoring                       | Stack Monitoring | <http://kibana-url:5601/app/monitoring>                                                                                                |
| kibana:stack\_management         | Stack Management | <http://kibana-url:5601/app/kibana#/management>                                                                                        |

### Kibana configuration

Activate authentication for the Kibana server: let the Kibana daemon connect to Elasticsearch using a pair of credentials we just defined in `readonlyrest.yml` (see above, the ::KIBANA-SRV:: block).

Open up `conf/kibana.yml` and add the following:

```yaml
# This is kibana.yml, but copy the exact same in elasticsearch.yml if you have to use some X-pack features.
xpack.graph.enabled: false
xpack.ml.enabled: false
xpack.monitoring.enabled: true
xpack.watcher.enabled: false

# Kibana server use ::KIBANA-SRV:: credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"
```

And of course also make sure `elasticsearch.url` points to the designated Elasticsearch instance (check also the http or https)

### Proxy Auth

ROR for Elasticsearch can delegate authentication to a reverse proxy which will enforce some kind of authentication, and pass the successfully authenticated user's name inside a `X-Forwarded-User` header.

> Today, it's possible to skip the regular ROR login form and use the "delegated authentication" technique in ROR for Kibana as well.

1. Configure ROR for ES to expect delegated authentication (see [`proxy_auth` rule](/develop/elasticsearch#proxy_auth)) in ROR for ES documentation.
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.proxy_auth_passthrough: true`

Now ROR for Kibana will **skip the login form entirely**, and will only require that all incoming requests must carry a `X-Forwarded-User` header containing the user's name. Based on this identity, ROR for Kibana will build an encrypted cookie and handle your session normally.

#### Custom Logout link

Normally, when a user presses the logout button in ROR for Kibana, it deletes the encrypted cookie that represents the users identity and the login form is shown.

However, when the authentication is delegated to a proxy, the logout button needs to become a link to some URL capable to unregister the session a user initiated within the proxy.

For this, ROR for Kibana offers a way to customize the logout button's URL:

1. Find a link that will delete the reverse proxy's user session
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_logout_link: https://..../logout`

Now users that gained a session through delegated auth, can also click on the logout button in ROR for kibana and actually exit their session.

#### Custom Login link

When you delegate authentication to an external service, you can tell ReadonlyREST to skip the classic login form entirely and redirect users to your proxy or identity provider's login screen.

To enable this:

1. Find your authentication proxy or identity provider login URL for the ROR app
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_login_link: "https://../login"`

The advantage of this approach is a streamlined user experience for users that login with an external IdP. The disadvantage is that you give up the possibility to login as a local user in ROR, as the login form will be always skipped.

#### Caveat

Enabling proxy auth passthrough will relax the requirement to provide a password. Therefore, don't enable this option if you don't make sure Kibana can **only be accessed through the reverse proxy\***.

### JWT Token Forwarding as URL Query Parameter

Alternatively to typing in credentials in the standard login form, it is possible to create an authenticated Kibana session by passing a JWT token as a query parameter in a URL.

#### Configuration

To enable this feature in ReadonlyREST, you need to:

* Have JWT authentication configured in ReadonlyREST (modifying `readonlyrest.yml` or the cluster wide settings UI in the Kibana plugin). [See how](/develop/elasticsearch#json-web-token-jwt-auth).
* Specify the query parameter name in `kibana.yml` by adding the line `readonlyrest_kbn.jwt_query_param: "jwt"` as a string, in our case "jwt".

#### In Action

Once Kibana is restarted, you will be able to navigate to a link like this:

```
http://kibana:5601/login?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

The following will happen:

1. The Kibana plugin will forward the JWT token found in the query parameter into the `Authorization` header in a request to Elasticsearch.
2. Elasticsearch will cryptographically authenticate and resolve the user's identity from the JWT claims.
3. Kibana will write an encrypted cookie in your browser and use that from now on for the length of the authenticated session. From here onwards, the session management will be identical to the normal login form flow.
4. When the user presses logout, Kibana will delete the cookie and redirect you to the login form, or whatever link you configured as `readonlyrest_kbn.custom_logout_link`.

**Deep linking with JWT**

Because the identity is embedded in the link, and ReadonlyREST is able to authenticate the call on the fly, the JWT authentication can be used in conjunction with `nextUrl` query parameter for sharing deep links inside Kibana apps, or embedding visualizations and dashboards inside I-Frames.

**Anatomy of a JWT deep link**

```
http://kibana:5601/login?jwt=<the-token>&nextUrl=urlEncode(<kibana-path>)
```

In Javascript one can compose a JWT deep link as follows:

```javascript
var absoluteKibanaPath = '/app/kibana#/visualize/edit/28dcde30-2258-11e8-82a3-af58d04b3c02?_g=()';

var url = 'http://kibana:5601/login?jwt=' + 
           jwtToken + 
           '&nextUrl=' + 
           encodeURI(absoluteKibanaPath);

console.log("Final JWT deep link: " + url)
```

The result may look something like this:

```
http://localhost:5601/login?nextUrl=%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2F28dcde30-2258-11e8-82a3-af58d04b3c02%3F_g%3D%28%29&jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

## Audit log

The audit log feature is widely described in [📖docs for Elasticsearch plugin](/develop/elasticsearch#audit). Kibana plugin has predefined dashboard representing collected audit data.

### Loading visualization

In the *Audit* tab of the ReadonlyREST Kibana app, there is a button that automatically creates a dashboard with some audit log specific visualizations.

![audit log tab](/files/iKPgTVLGkfQxtXpUVZGu)

Click the *Load* button to load the dashboard and visualizations. An *Override* checkbox allows to reload the default dashboard and visualizations. It will override any previously loaded audit log dashboard.

![loading visualization](/files/CmKSIxVPSiJVKjS2LAZ0)

In detail, this feature creates three Kibana "saved objects":

* an index pattern for `readonlyrest_audit-*`
* a dashboard called `ReadonlyREST Audit Log`
* some visualizations

### Dashboard

The audit log dashboard, by default, has only a few basic visualizations. They cover security, access logs, and performance metrics.

## SAML

ReadonlyREST Enterprise supports service provider initiated via SAML. This connector supports both SSO (single sign on) and SLO (single log out). Here is how to configure it.

### Configure ReadonlyREST ES bridge

In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugin with a shared secret, that is: an arbitrarily long string.

### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/develop/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/develop/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/develop/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

**⚠️IMPORTANT** the Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

### Kibana side

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  saml_serv1:
    enabled: true
    type: saml
    issuer: ror
    buttonName: "Partner's SSO Login"
    entryPoint: 'https://my-saml-idp/saml2/http-post/sso' # <-- identity Provider's URL, to request to sign on
    kibanaExternalHost: 'my.public.hostname.com' # <-- public URL used by the Identity Provider to call back Kibana with the "assertion" message
    protocol: http # <-- is the Kibana server listening for "http" "https" connections? Default: http
    usernameParameter: 'nameID'
    groupsParameter: 'memberOf'
    logoutUrl: 'https://my-saml-idp/saml2/http-post/slo'

    # OPTIONAL, advanced parameters
    # decryptionCert: /etc/ror/integration/certs/pub.crt
    # cert: /etc/ror/integration/certs/dag.crt
    # decryptionPvk: /etc/ror/integration/certs/decrypt_pvk.crt
    # issuer: saml_sso_idp
```

* `issuer`: issuer string to supply to identity provider during sign on request. Defaults to 'ror'
* `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. This is known to help when authenticating against Active Directory (AD FS) servers.
* `decryptionPvk`: Service Provider Private Key. Private key that will be used to attempt to decrypt any encrypted assertions that are received.
* cert: The downloadable certificate in IDP Metadata (file, absolute path)

For advanced SAML options, see [passport-saml documentation](https://github.com/bergie/passport-saml).

### Identity provider side

1. Enter the settings of your identity provider, create a new app.
2. Configure it using the information found by connecting to `http://my.public.hostname.com/ror_kbn_sso_saml_serv1/metadata.xml`

Example response:

```markup
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="onelogin_saml" ID="onelogin_saml">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn_sso/notifylogout"/>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
    <AssertionConsumerService index="1" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn_sso/assert"/>
  </SPSSODescriptor>
</EntityDescriptor>
```

1. Create some users and some groups in the identity provider app
2. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set kibana in debug mode so ReadonlyREST will print the user profile).
3. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
4. If you want to use SAML for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting.

## OpenID Connect (OIDC)

ReadonlyREST Enterprise support OpenID Connect for authentication and authorization.

> soon we will create a specific guide only for OpenID, like the ones we have for SAML

Here is how to configure it.

### Configure ReadonlyREST ES bridge

This part is identical as seen in SAML connectors. In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugin with a shared secret, that is: an arbitrarily long string.

### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/develop/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/develop/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/develop/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

**⚠️IMPORTANT** the Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

If you have configured OIDC with the `groupsParameter` ( *See below* ), you can also restrict ACL to specific groups:

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1 for group 1"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group1"]

    - name: "ReadonlyREST Enterprise instance #1 for group 2"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group2"]

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

You may also use any custom claim from the OIDC `userinfo` token in ACL rules by using `{{jwt:assertion.<path_to_your_claim>}}` syntax. See the [Dynamic variables from JWT claims section](/develop/elasticsearch#usage-examples) for more information. ( **TIP** : Do not forget the `assertion` prefix in front of you jsonpath. )

### Kibana side

We will assume the OpenID identity provider responds to port 8080 of localhost. In our example, we used Keycloak, an open source implementation of OpenID Connect identity provide.

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  oidc_kc: 
            buttonName: "KeyCloak OpenID"
            type: "oidc"
            issuer: 'http://localhost:8080/auth/realms/ror'
            authorizationURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/auth'
            tokenURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/token'
            userInfoURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/userinfo'
            clientID: 'ror_oidc'
            clientSecret: '9f1d39c8-a211-460a-84b6-0a4a1499c455'
            scope: 'openid profile roles role_list email'
            usernameParameter: 'preferred_username'
            groupsParameter: 'groups'
            kibanaExternalHost: 'localhost:8080'
            logoutUrl: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/logout'
```

### Identity provider side

1. Enter the settings interface of your identity provider, and create a new OpenID app .
2. The redirect URL should be configured as `http://localhost:5601/*` assuming kibana is listening on localhost and on the default port.
3. Create some users and some groups in the identity provider if not present.
4. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set readonlyrest\_kbn.logLevel: debug\` in kibana.yml, so you will see the user profile how it's received from the identity provider right in the logs).
5. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
6. If you want to use OpenID for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting. ( **TIP**: the `groupsParameter` must be present in the `userinfo` token of your OIDC provider.)
7. If kibana is accessed through a reverse proxy, kibanaExternalHost should be configured with the external hostname. if omitted, the default value is equals to `server.host:server.port` defined in kibana.yml. ( This parameter can be used also when kibana is bound to 0.0.0.0, for example, if using docker.)

## Load balancers

### Enable health check endpoint

Normally a load balancer needs a health check URL to see if the instance is still running, you can whitelist this Kibana path so the load balancer avoids a redirection to `/login`.

Edit `kibana.yml`

```
readonlyrest_kbn.whitelistedPaths: [".*/api/status$"]
```

### Session management with multiple Kibana instances

Each Kibana node stores user sessions in-memory. This will cause problems when using multiple Kibana instances behind a load balancer (especially without sticky sessions), as there would be no synchronization between nodes' sessions cache. To avoid this, session synchronization via an Elasticsearch index should be enabled. Follow these steps:

1. Come up with a string of at least 32 characters length or more to be used as the shared cookie encryption key, called `cookiePass`.
2. Open up `conf/kibana.yml` and add:
   * `readonlyrest_kbn.cookiePass: "generatedStringIn1step"` (example: "12345678901234567890123456789012")
   * `readonlyrest_kbn.cookieName` (custom cookie name - this property is optional, if not specified default cookie name would be `rorCookie`)
   * `readonlyrest_kbn.store_sessions_in_index: true` (enable session storage in index)
   * `readonlyrest_kbn.sessions_index_name: "someCustomIndexName"` (index name - this property is optional, if not specified default index would be `.readonlyrest_kbn_sessions`)
   * `readonlyrest_kbn.sessions_refresh_after: 1000` (time in milliseconds, describes how often sessions should be fetched from ES and refreshed for each node - optional, by default 2 seconds)
   * `readonlyrest_kbn.sessions_probe_interval_seconds: 15` (default 10s) how often should the browser poll Kibana to check if their session is still valid. Raise this value if you connect to Kibana through slow networks (i.e. VPN), or have very slow loading dashboards.
3. Add the above config in all Kibana nodes behind the load balancer, and restart them.

## Login screen tweaking

([PRO](https://readonlyrest.com/pro))

It is possible to customize the look of the login screen.

### Two column layout

By default,the login form appears in a single column view. ![one column](blob:https://imgur.com/f7514ca2-7f8f-4f96-aecd-09e7ea636b62)

But once title and subtitle are configured, it will switch to two columns for making room to the new text.

```
readonlyrest_kbn.login_title: "Some Title"
readonlyrest_kbn.login_subtitle: "Longer text <b>any HTML is supported<b/> including ifrmaes"
```

![two columns](https://i.imgur.com/Sqf1GIL.png)

### Add your company logo

It's recommended to use a transparent PNG, negative logo. Ideally a white foreground, and transparent background.

Open `config/kibana.yml` and append the following:

```
readonlyrest_kbn.login_custom_logo: 'https://.../logo.png'
```

### Add custom CSS/JS

You have the opportunity to inject HTML code right before the closing head tag (`</head>`).

Open `config/kibana.yml` and append the following:

```
readonlyrest_kbn.login_html_head_inject: '<style> * { color:red; }</style>'
```

## Kibana UI tweaking

([Enterprise](https://readonlyrest.com/enterprise))

With ReadonlyREST Enterprise, it's possible to inject custom CSS and Javascript to achieve a customized user experience for your users/tenants.

### Inject custom CSS in Kibana

Open `config/kibana.yml` and append the following:

```
readonlyrest_kbn.kibana_custom_css_inject: '.global-nav, kbnGlobalNav { background-color: green }'
```

Alternatively, it's possible to load the CSS from a file in the filesystem:

```
readonlyrest_kbn.kibana_custom_css_inject_file: '/tmp/custom.css'
```

### Inject custom JS in Kibana

```
readonlyrest_kbn.kibana_custom_js_inject: '$(".global-nav__logo").hide(); alert("hello!")'
```

### Map groups to aliases

You can provide a function, mapping group IDs to aliases of your choosing. To do so, add the following line to `config/kibana.yml`:

```
readonlyrest_kbn.groupsMapping: '(group) => group.toLowerCase()'
```

**⚠️IMPORTANT** The mapping function has to return a string. Otherwise, an error will be printed in kibana logs and the original group ID will be used as fallback. Also, if the mapping function is not specified, the original group ID value will be used.

## Tenancy index templating

([Enterprise](https://readonlyrest.com/enterprise))

When a tenants logs in for the first time, ReadonlyREST Enterprise will create the ".kibana" index associated to the tenancy. For example, it will create and initialize the ".kibana\_user1" index, where "user1" will store all the visualizations, dashboards, settings and index-patterns.

The issue is that "user1"'s user experience will be really raw as they will see a completely blank Kibana tenancy. Not even a default index pattern will be present. And this is particularly challenging if the tenant is supposed to be read-only (i.e. kibana\_access: "ro") because they won't even have privileges to create their own index-pattern, let alone any dashboards.

To fix this, ReadonlyREST Enterprise offers the possibility for administrators to create a template kibana index from which all the Kibana objects will be copied over to the newly initialized tenancy.

### How to use tenancy templating

An administrator will need to create the template tenancy, populate it with the default Kibana objects (index-patterns, dashboards) and configure ReadonlyREST Enterprise to take the index template it in use. Let's see this step by step:

#### Create the template tenancy

Let's start to add to our access control list (found in $ES\_PATH\_CONF/config/readonlyrest.yml, or ReadonlyREST App in Kibana) a local user "administrator" that will belong to two tenancies: the default one (stored in .kibana index), and the template one (stored in .kibana\_template index).

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

 users:
 - username: administrator
   auth_key: administrator:dev
   groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu
```

NB: If you know what you are doing, you can add a tenancy with kibana\_index: ".kibana\_template" adding a LDAP/SAML group to your administrative user.

### Configure the template tenancy

Now login as administrator in Kibana, hop into the "Template" tenancy, and start configuring the default UX for your future tenants. Add all the index patterns, create or import all the dashboards you want.

### Configure the template tenancy index in ReadonlyREST Enterprise

Open kibana.yml and add the following line:

```
readonlyrest_kbn.kibanaIndexTemplate: ".kibana_template"
```

Now, ReadonlyREST Enterprise will look for the ".kibana\_template" index, and try to copy over all its documents every time a new kibana index is initialized to support a new tenancy.

### Try it out

Restart Kibana with the new setting. Add a new tenancy to the ACL:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

  # Newly added tenant!
  - name: user1
    auth_key: user1:passwd
    kibana:
      access: rw
      index: ".kibana_user1"

 users:
 - username: administrator
   auth_key: administrator:dev
   groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu
`
```

Now try to login as user1, and ReadonlyREST Enterprise should initialize the index ".kibana\_user1" with all the index patterns and dashboards contained in the template tenancy.


# ReadonlyREST API

An authenticated API for changing the security settings without rebooting the ES cluster.

([Enterprise](https://readonlyrest.com/enterprise))

As an Enterprise user, you can benefit from automating the security configuration changes without the need to reboot the ES cluster.

Every request is **validated against ROR syntax first**, and rejected if syntactically or semantically incorrect. This adds to the safety of each change operation.

[Link to the API documentation](https://portal.readonlyrest.com/docs/swagger/master)


# ReadonlyREST DISA STIG Compliance

DISA STIG compliance analysis for deployments using the ReadonlyREST plugin as the authentication and authorization enforcement point.

This document answers DISA STIG (Web Server Security Requirements Guide) compliance questions for deployments where the ReadonlyREST Kibana plugin serves as the authentication and authorization enforcement point.

***

## 1. Session Management

### V-206351 — Server-side session management

**Status: Fully satisfied**

ReadonlyREST stores all session state on the server side. The client-side cookie contains only an encrypted session identifier — no session data is stored in the cookie itself.

Two storage backends are available:

* **In-memory** (default): suitable for single Kibana instance deployments.
* **Elasticsearch index** (recommended for HA): sessions are persisted in a dedicated index shared across all Kibana nodes.

See [Session management with multiple Kibana instances](/develop/kibana#session-management-with-multiple-kibana-instances) for configuration details.

***

### V-206396 — Invalidate session identifiers upon logout or session termination

**Status: Fully satisfied**

On logout, ReadonlyREST deletes the session record from the server-side store and clears the client cookie in the same operation. Once a session is deleted, any subsequent request presenting the old cookie is rejected and redirected to the login page.

For multi-tab browser scenarios, a background probe running in each tab detects the logout event and immediately redirects all open tabs to login.

***

### V-206397 — Cookie security settings (HttpOnly, Secure flags; SameSite)

**Status: Fully satisfied**

ReadonlyREST sets appropriate security flags on every session cookie. See [Cookie settings](/develop/kibana#cookie-settings) for defaults, behavior, and configurable attributes.

***

### V-206398 — Accept only system-generated session identifiers

**Status: Fully satisfied**

ReadonlyREST rejects any session identifier it did not create through two independent checks:

1. The cookie is encrypted with HAPI Iron (AES-256 + HMAC-SHA256) using a secret key known only to the ReadonlyREST deployment. Any tampered or externally crafted cookie fails decryption.
2. Even a structurally valid identifier must exist as an active record in the server-side session store. Identifiers not present in the store are rejected.

***

### V-206399 — Session ID generation using FIPS 140-2 approved RNG

**Status: Conditionally satisfied — depends on the Node.js runtime**

ReadonlyREST generates session IDs using the Node.js cryptographic random number generator (`crypto.randomBytes()`), which uses the operating system CSPRNG. When the Kibana process is started with a FIPS-validated Node.js build or with the `--enable-fips` flag, this call automatically uses the FIPS-validated OpenSSL DRBG, satisfying the requirement.

ReadonlyREST inherits the cryptographic posture of the Kibana runtime — enabling FIPS mode is an infrastructure and deployment concern, not a ReadonlyREST configuration option.

***

### V-206400 — Non-reproducible session identifiers

**Status: Fully satisfied**

Session IDs are UUID v4 values generated from 122 bits of independent random entropy per call. The same RNG call never produces the same output.

***

### V-206401 — Session ID length ≥ 128 bits

**Status: Fully satisfied**

UUID v4 is a 128-bit value. ReadonlyREST uses it as the session key.

***

### V-206402 — Session ID character set (A–Z, a–z, 0–9 minimum)

**Status: Fully satisfied**

UUID v4 is represented using hexadecimal characters (`0–9`, `a–f`), which satisfies the minimum alphanumeric requirement.

***

### V-206403 — Session ID entropy ≥ 50% of ID length

**Status: Fully satisfied**

UUID v4 carries 122 bits of random entropy in a 128-bit value — 95% entropy density, well above the 50% threshold.

***

### V-206414 — Absolute session timeout ≤ 8 hours

**Status: Partially satisfied — requires configuration; note architectural limitation**

ReadonlyREST enforces a configurable session timeout. For STIG compliance this must be set to 480 minutes (8 hours) or less. See [Session timeout](/develop/kibana#session-timeout) for configuration details.

**Limitation:** ReadonlyREST's timeout is a sliding inactivity window — each user action resets the clock. There is no hard absolute cap on total session lifetime from the moment of login. A continuously active user will not be forcibly logged out after 8 hours. ReadonlyREST creates and manages its own session independently of the IdP after the initial authentication, so there is no external control point that can enforce an absolute lifetime on an active ReadonlyREST session. Strict absolute session lifetime enforcement is a known limitation of the current ReadonlyREST implementation.

***

### V-206415 — Inactive/idle session timeout

**Status: Fully satisfied — requires configuration**

ReadonlyREST terminates idle sessions and cleans them up automatically. See [Session timeout](/develop/kibana#session-timeout) for configuration details.

***

## 2. Session IP Binding

### V-264360 — Restrict management sessions to consistent inbound source IP

### V-264361 — Restrict user sessions to consistent inbound source IP

**Status: Not implemented**

ReadonlyREST does not bind sessions to an originating IP address. A session token is valid regardless of the IP from which it is presented.

IP session binding is a known limitation of the current ReadonlyREST implementation.

***

## 3. Authorization & Access Control

### V-206355 — Enforce approved authorizations for logical access (RBAC)

**Status: Fully satisfied**

ReadonlyREST is the RBAC enforcement point for Kibana. Access control is driven by the [ReadonlyREST ACL](/develop/elasticsearch#readonlyrest-acl) — configured in the Elasticsearch plugin's `readonlyrest.yml` — which maps authenticated identities (users, SAML attributes, OIDC claims, group memberships) to permissions. The following are enforced per session:

* **Kibana tenants / spaces:** each user or group is confined to a specific Kibana space, isolating dashboards and saved objects.
* **Elasticsearch index access:** users can only query the indices permitted by their ACL block.
* **API path restrictions:** specific Kibana API endpoints can be allowed or denied per user or group.
* **Access level:** read-only, read-write, or admin access within Kibana is configurable per ACL block.

***

### V-206394 — Prohibit anonymous user access / prevent unauthorized changes

**Status: Fully satisfied**

Every request passing through ReadonlyREST requires a valid authenticated session. Unauthenticated requests are redirected to the login page before reaching Kibana. The only paths that bypass authentication are health-check endpoints, which expose no user data and allow no modifications. See [Enable health check endpoint](/develop/kibana#enable-health-check-endpoint) for configuring whitelisted paths.

***

### V-264342 — Individual authentication before shared account access

**Status: Fully satisfied at the plugin level**

ReadonlyREST requires each session to be established through an individual authentication event — credentials, a SAML assertion, or an OIDC token exchange. Every session is tied to a specific authenticated identity and carries its own server-side record.

Whether multiple people share the same IdP credentials is outside ReadonlyREST's scope — that is an identity provider concern.

***

## 4. Authentication

### V-222523 — Multi-Factor Authentication for privileged accounts *(CAT I)*

**Status: Conditionally satisfied — depends on IdP configuration**

ReadonlyREST does not implement MFA natively. Authentication is fully delegated to external identity providers via SAML or OIDC. When the IdP enforces MFA, that requirement is satisfied before ReadonlyREST issues a session — ReadonlyREST neither bypasses nor weakens IdP-side MFA policies.

Deployments not using SAML or OIDC have no MFA enforcement point at the ReadonlyREST Kibana plugin layer. MFA for such deployments requires migrating to SAML or OIDC with an IdP that enforces MFA.

***

### V-222543 — Plaintext credential transmission *(CAT I)*

**Status: Conditionally satisfied — requires TLS configuration**

The exposure of credentials in transit depends on the authentication method in use:

* **SAML / OIDC deployments:** ReadonlyREST does not handle raw credentials directly — authentication relies on SAML assertions or OIDC token exchanges. Only session tokens are transmitted between the browser and Kibana, and these are exposed if TLS is not configured.
* **Basic auth deployments (`auth_key`):** Username and password are transmitted from the browser on every request. Without TLS, credentials are exposed in plaintext on every authenticated request.

TLS is mandatory for STIG compliance regardless of the authentication method. See [SSL/TLS server](/develop/kibana#ssltls-server) for configuration details.

***

## 5. Transport Security

### V-222596 — TLS protocol version enforcement *(CAT I)*

**Status: Conditionally satisfied — requires configuration**

When TLS is enabled, ReadonlyREST enforces a minimum protocol baseline: TLSv1.0, SSLv2, and SSLv3 are always disabled. TLSv1.1, TLSv1.2, and TLSv1.3 are permitted by default.

DISA STIG requires a minimum of TLS 1.2. See [SSL/TLS server](/develop/kibana#ssltls-server) for how to restrict the allowed protocols. ReadonlyREST respects this setting and applies the configured protocol restrictions across all TLS connections.

***

### V-222571 — Cryptographic algorithms *(CAT I)*

**Status: Fully satisfied**

ReadonlyREST uses only modern, approved cryptographic algorithms internally:

| Usage                               | Algorithm                                   |
| ----------------------------------- | ------------------------------------------- |
| Session cookie encryption           | AES-256 (HAPI Iron — AES-CBC + HMAC-SHA256) |
| Session and tenancy data encryption | AES (CryptoJS)                              |
| License token verification          | ES512 (ECDSA with SHA-512)                  |

No deprecated algorithms are present in the ReadonlyREST codebase. MD5, SHA-1, DES, and RC4 are not used.

***

## 6. Audit & Logging

### V-222452 — Failed login attempt logging *(CAT II)*

**Status: Conditionally satisfied — requires audit configuration**

The ReadonlyREST Elasticsearch plugin provides formal structured audit logging for all access control decisions, including rejected authentication attempts. When enabled, FORBIDDEN events are written to a timestamped Elasticsearch index (default: `readonlyrest_audit-YYYY-MM-DD`, configurable via `index_template`). The output format is structured by default and can be customized via [serializers](/develop/elasticsearch/audit#predefined-serializers), including support for [ECS (Elastic Common Schema)](/develop/elasticsearch/audit#using-ecs-serializer). See [Audit configuration](/develop/elasticsearch/audit#configuration) for how to enable audit logging.

The Kibana plugin additionally logs rejected attempts at `INFO` level in the Kibana application log (e.g. "Could not login in: …"), visible in standard production deployments.

***

### V-222463 — Privileged user action logging *(CAT II)*

**Status: Conditionally satisfied — requires audit configuration**

Every Kibana user action — including actions performed by privileged users — translates into one or more Elasticsearch REST API calls. When audit logging is enabled in the ReadonlyREST Elasticsearch plugin, every such request is audited with no exceptions based on privilege level. See [Audit configuration](/develop/elasticsearch/audit#configuration) for how to enable it. For additional field coverage (request path, ACL history), see [Predefined serializers](/develop/elasticsearch/audit#predefined-serializers).

***

### V-222507 — Audit log integrity *(CAT II)*

**Status: Partially satisfied — access control covered, tamper-detection outside scope**

ReadonlyREST writes audit events to a timestamped Elasticsearch index or data stream (default name: `readonlyrest_audit-YYYY-MM-DD`, configurable). The ACL can be used to prevent unauthorized modification or deletion of audit data. See [Protecting the audit index](/develop/elasticsearch/audit#protecting-the-audit-index) for the required ACL rule.

Tamper detection (log signing, hash chaining) and audit log backup are outside ReadonlyREST scope and must be addressed at the Elasticsearch/infrastructure layer.

***

## 7. HTTP Security Headers

### V-222602 — Content-Security-Policy *(CAT II)*

**Status: Partially satisfied — architectural limitation in Kibana**

`Content-Security-Policy` is managed by Kibana, not ReadonlyREST. Kibana 7.x and 8.x do not set a CSP header by default — configure it explicitly in `kibana.yml`.

Kibana's frontend architecture hardcodes `'unsafe-inline'` in `style-src` regardless of configuration — this cannot be removed without breaking the UI. Additionally, Kibana 7.x and 8.x hardcode `'unsafe-eval'` in `script-src`; this was removed in Kibana 9.x. The configuration below restricts what operators can control:

Kibana 7.9 – 7.13 (only `csp.rules` is available):

```yaml
csp.rules:
  - "script-src 'self'"
  - "style-src 'self'"
  - "object-src 'none'"
```

Kibana 7.14+ and 8.x (per-directive settings):

```yaml
csp.script_src: ["'self'"]
csp.style_src: ["'self'"]
csp.object_src: ["'none'"]   # 8.x only; not available in 7.x
```

The resulting effective policy will always contain `'unsafe-inline'` in `style-src` (all versions) and `'unsafe-eval'` in `script-src` (Kibana 7.x and 8.x). These weaken the CSP and should be documented as accepted risks in the system's security assessment.

***

### X-Frame-Options — Clickjacking prevention *(CAT II)*

**Status: Outside ReadonlyREST scope — requires Kibana configuration**

`X-Frame-Options` is managed by Kibana, not ReadonlyREST. Configure it explicitly in `kibana.yml`:

```yaml
server.customResponseHeaders:
  X-Frame-Options: "DENY"
```

***

### Strict-Transport-Security (HSTS) *(CAT II)*

**Status: Outside ReadonlyREST scope — requires Kibana configuration**

`Strict-Transport-Security` is managed by Kibana, not ReadonlyREST. Configure it explicitly in `kibana.yml` (requires TLS to be enabled):

```yaml
server.customResponseHeaders:
  Strict-Transport-Security: "max-age=31536000; includeSubDomains"
```

***

## Summary

| STIG Control                                                                            | Requirement                              | CAT | Status                                                                                  |
| --------------------------------------------------------------------------------------- | ---------------------------------------- | --- | --------------------------------------------------------------------------------------- |
| [V-206351](#v-206351-server-side-session-management)                                    | Server-side session state                | II  | ✅ Fully satisfied                                                                       |
| [V-206396](#v-206396-invalidate-session-identifiers-upon-logout-or-session-termination) | Invalidate on logout                     | II  | ✅ Fully satisfied                                                                       |
| [V-206397](#v-206397-cookie-security-settings-httponly-secure-flags-samesite)           | HttpOnly, Secure, SameSite               | II  | ✅ Fully satisfied                                                                       |
| [V-206398](#v-206398-accept-only-system-generated-session-identifiers)                  | Only system-generated SIDs               | II  | ✅ Fully satisfied                                                                       |
| [V-206399](#v-206399-session-id-generation-using-fips-140-2-approved-rng-high)          | FIPS 140-2 RNG                           | I   | ⚠️ Depends on Node.js FIPS mode                                                         |
| [V-206400](#v-206400-non-reproducible-session-identifiers)                              | Non-reproducible SIDs                    | II  | ✅ Fully satisfied                                                                       |
| [V-206401](#v-206401-session-id-length-128-bits)                                        | SID ≥ 128 bits                           | II  | ✅ Fully satisfied                                                                       |
| [V-206402](#v-206402-session-id-character-set-az-az-09-minimum)                         | SID charset A–Z, a–z, 0–9                | II  | ✅ Fully satisfied                                                                       |
| [V-206403](#v-206403-session-id-entropy-50-of-id-length)                                | Entropy ≥ 50% of SID length              | II  | ✅ Fully satisfied                                                                       |
| [V-206414](#v-206414-absolute-session-timeout-8-hours)                                  | Absolute timeout ≤ 8 hours               | II  | ⚠️ Sliding timeout only; requires configuration                                         |
| [V-206415](#v-206415-inactiveidle-session-timeout)                                      | Idle/inactivity timeout                  | II  | ✅ Satisfied — requires configuration                                                    |
| [V-264360](#v-264360-restrict-management-sessions-to-consistent-inbound-source-ip)      | IP binding — management sessions         | II  | 🔴 Not implemented                                                                      |
| [V-264361](#v-264361-restrict-user-sessions-to-consistent-inbound-source-ip)            | IP binding — user sessions               | II  | 🔴 Not implemented                                                                      |
| [V-206355](#v-206355-enforce-approved-authorizations-for-logical-access-rbac)           | RBAC logical access control              | II  | ✅ Fully satisfied                                                                       |
| [V-206394](#v-206394-prohibit-anonymous-user-access--prevent-unauthorized-changes)      | No anonymous access                      | II  | ✅ Fully satisfied                                                                       |
| [V-264342](#v-264342-individual-authentication-before-shared-account-access)            | Individual auth before shared access     | II  | ✅ Satisfied at plugin level                                                             |
| [V-222523](#v-222523-multi-factor-authentication-for-privileged-accounts-cat-i)         | MFA for privileged accounts              | I   | ⚠️ Depends on IdP — not available without SAML/OIDC                                     |
| [V-222543](#v-222543-plaintext-credential-transmission-cat-i)                           | Plaintext credential transmission        | I   | ⚠️ Requires TLS configuration                                                           |
| [V-222596](#v-222596-tls-protocol-version-enforcement-cat-i)                            | TLS protocol version (min. 1.2)          | I   | ⚠️ Conditionally satisfied — requires configuration                                     |
| [V-222571](#v-222571-cryptographic-algorithms-cat-i)                                    | Cryptographic algorithms (no deprecated) | I   | ✅ Fully satisfied                                                                       |
| [V-222452](#v-222452-failed-login-attempt-logging-cat-ii)                               | Failed login attempt logging             | II  | ⚠️ Conditionally satisfied — requires audit configuration                               |
| [V-222463](#v-222463-privileged-user-action-logging-cat-ii)                             | Privileged user action logging           | II  | ⚠️ Conditionally satisfied — requires audit configuration                               |
| [V-222507](#v-222507-audit-log-integrity-cat-ii)                                        | Audit log integrity                      | II  | ⚠️ Partially satisfied — access control via ACL; tamper-detection outside scope         |
| [V-222602](#v-222602-content-security-policy-cat-ii)                                    | Content-Security-Policy                  | II  | ⚠️ Partially satisfied — style-src 'unsafe-inline' is a Kibana architectural limitation |

**Legend:**

* ✅ — satisfied (by default or via documented configuration)
* ⚠️ — requires configuration; control is not met if skipped
* 🔴 — not implemented


# For ECK

ReadonlyREST plugins officially support installation on Elasticsearch and Kibana working in Kubernetes cluster and managed by the [ECK operator](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html).

You can choose one of the following installation methods:

## Installation methods

### Using our Docker images from Docker Hub

The easiest and fastest method to start with a ROR-powered ELK stack on ECK is to use our official images. We will show you how to do it in the following sections:

#### Elasticsearch node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Elasticsearch cluster specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-elasticsearch.html) we should do it as below:

```yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # check https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest
  image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest 
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
    podTemplate:
        spec:
          containers:
            - name: elasticsearch
              # we have to run our image as root (id: 0) - after the required patching step Elasticsearch will be run using "elasticsearch" user (id: 1000)
              securityContext:
                runAsNonRoot: false
                runAsUser: 0
                runAsGroup: 0
              env:
                # we have to explicitly agree to patch the ES binaries (the patching step will be done only once)
                - name: I_UNDERSTAND_AND_ACCEPT_ES_PATCHING
                  value: "yes"
                # these two passwords are used by "elastic-internal" and "elastic-internal-probe" users - these users are used by ECK
                - name: INTERNAL_USR_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal
                - name: INTERNAL_PROBE_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal-probe
                # Kibana service account to handle internal Kibana requests 
                - name: KIBANA_SERVICE_ACCOUNT_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-kibana-user
                      key: token
              # the initial readonlyrest.yml file loaded by ROR plugin during ES startup
              volumeMounts:
                - name: config-ror
                  mountPath: /usr/share/elasticsearch/config/readonlyrest.yml
                  subPath: readonlyrest.yml
          volumes:
            - name: config-ror
              configMap:
                name: config-readonlyrest.yml
```

**ReadonlyREST initial settings**

The initial settings can be defined as ConfigMap like this:

```yaml
apiVersion: v1
data:
   readonlyrest.yml: |
     readonlyrest:
       access_control_rules:

       - name: "ELASTIC-INTERNAL"
         verbosity: error
         auth_key: "elastic-internal:${INTERNAL_USR_PASS}"
     
       - name: "ELASTIC INTERNAL PROBE"
         verbosity: error
         auth_key: "elastic-internal-probe:${INTERNAL_PROBE_PASS}"
       
       - name: "Kibana service account"
         verbosity: error
         token_authentication:
           token: "Bearer ${KIBANA_SERVICE_ACCOUNT_TOKEN}" 
           username: service_account

       - name: "Admin access"
         type: allow
         auth_key: "admin:admin"

kind: ConfigMap
metadata:
  name: config-readonlyrest.yml
```

Notice that if you use ROR Enterprise, you can take advantage of the [Cluster-wide Settings](https://docs.readonlyrest.com/develop/pages/-MN37wx0LoBtagigbU5L#cluster-wide-settings-vs-readonlyrest.yml) functionality and reload configuration on all your nodes without restarting K8s' PODs.

#### Kibana node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Kibana instance specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-kibana.html) we should do it as follows:

```yaml
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # check https://hub.docker.com/r/beshultd/kibana-readonlyrest
  image: beshultd/kibana-readonlyrest:8.14.3-ror-latest 
  count: 1
  elasticsearchRef:
    name: quickstart
  config:
    # define ROR Kibana settings 
    # readonlyrest_kbn.store_sessions_in_index: true # we have to set it to true when we define more than one node
    readonlyrest_kbn.cookiePass: "12345678901234567890123456789012345678901234567890"
  podTemplate:
    spec:
      # we have to run our image as root (id: 0) - after the required patching step Kibana will be run using "kibana" user (id: 1000)
      securityContext:
        runAsNonRoot: false
        runAsUser: 0
        runAsGroup: 0
      containers:
        - name: kibana
          env:
            # we have to explicitly agree to patch the KBN binaries (the patching step will be done only once)
            - name: I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING
              value: "yes"
            # we have to provide a ROR license if we want to use ROR Pro or Enterprise (if the license is not provided, then ROR Free is used)
            - name: ROR_ACTIVATION_KEY
              value: "<YOUR_ACTIVATION_KEY/>"
```

And these are all differences we need to make to run [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html) with ReadonlyREST instead of X-Pack security.

### Using Docker images built by you and stored in your registry

As you probably noticed, our docker images have to be run with root privileges. It's due to legal reasons and you, as a user, have to confirm that you agree to do the patching steps. If running a pod with root privileges is something you cannot accept, you can create your own image with the patching step done at the image creation level (not at runtime as our image does) and save it your your own registry.

<details>

<summary>Expand to see details</summary>

#### Elasticsearch with ROR custom image

The minimal Elasticsearch with ROR image definition looks like this:

```
# 'Dockerfile' file content
ARG ES_VERSION
FROM docker.elastic.co/elasticsearch/elasticsearch:${ES_VERSION}

ARG ES_VERSION
ARG ROR_VERSION

USER elasticsearch
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch "https://portal.readonlyrest.com/download/es?esVersion=$ES_VERSION&pluginVersion=$ROR_VERSION&email=[YOUR-EMAIL-ADDRESS]"
USER root
RUN /usr/share/elasticsearch/jdk/bin/java -jar /usr/share/elasticsearch/plugins/readonlyrest/ror-tools.jar patch --I_UNDERSTAND_AND_ACCEPT_ES_PATCHING yes
USER 1000:0
```

And then you can build it as follows:

```bash
docker build --build-arg ES_VERSION=8.14.3 --build-arg ROR_VERSION=1.59.0 -t elasticsearch-with-ror  .
```

And place the `elasticsearch-with-ror` image in your registry.

#### Elasticsearch node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Elasticsearch cluster specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-elasticsearch.html) we should do it as below:

```yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # this is the image from your registry
  image: elasticsearch-with-ror
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
    podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                # these two passwords are used by "elastic-internal" and "elastic-internal-probe" users - these users are used by ECK
                - name: INTERNAL_USR_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal
                - name: INTERNAL_PROBE_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal-probe
                # Kibana service account to handle internal Kibana requests 
                - name: KIBANA_SERVICE_ACCOUNT_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-kibana-user
                      key: token
              # the initial readonlyrest.yml file loaded by ROR plugin during ES startup
              volumeMounts:
                - name: config-ror
                  mountPath: /usr/share/elasticsearch/config/readonlyrest.yml
                  subPath: readonlyrest.yml
          volumes:
            - name: config-ror
              configMap:
                name: config-readonlyrest.yml
```

Check [the section from the previous paragraph](#readonlyrest-initial-settings) to see how to define `config-ror`.

#### Kibana with ROR custom image

The minimal Kibana with ROR image definition looks like this:

```
# 'Dockerfile' file content
ARG KBN_VERSION

FROM docker.elastic.co/kibana/kibana:${KBN_VERSION}

ARG KBN_VERSION
ARG ROR_VERSION

RUN /usr/share/kibana/bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?esVersion=$KBN_VERSION&pluginVersion=$ROR_VERSION&edition=kbn_universal&email=[YOUR-EMAIL-ADDRESS]"
USER root
RUN /usr/share/kibana/node/bin/node plugins/readonlyrestkbn/ror-tools.js patch --I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes && \
    chown -R kibana:kibana /usr/share/kibana/config
USER 1000:0
```

And then you can build it as follows:

```bash
docker build --build-arg KBN_VERSION=8.14.3 --build-arg ROR_VERSION=1.59.0 -t kibana-with-ror  .
```

And place the `kibana-with-ror` image in your registry.

#### Kibana node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Kibana instance specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-kibana.html) we should do it as follows:

```yaml
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # this is the image from your registry
  image: kibana-with-ror
  count: 1
  elasticsearchRef:
    name: quickstart
  config:
    # define ROR Kibana settings 
    # readonlyrest_kbn.store_sessions_in_index: true # we have to set it to true when we define more than one node
    readonlyrest_kbn.cookiePass: "12345678901234567890123456789012345678901234567890"
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            # we have to provide a ROR license if we want to use ROR Pro or Enterprise (if the license is not provided, then ROR Free is used)
            - name: ROR_ACTIVATION_KEY
              value: "<YOUR_ACTIVATION_KEY/>"
```

And these are all differences we need to make to run [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html) with ReadonlyREST instead of X-Pack security.

</details>

### Using an Init Container

{% hint style="warning" %}
This is not a recommended method.
{% endhint %}

It's possible to install ROR using an [Init Container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). Unfortunately, this method is not as easy to use in the case of ROR. This is due to the required ReadonlyREST patching step (for both plugins), which allows ROR to work with Elasticsearch/Kibana. During this step, the ROR patcher tool modifies some of the Elasticsearch and Kibana binaries. Different versions of Elasticsearch/Kibana require different binaries to be modified. In order to do this in a Kubernetes-based environment, we need to mount appropriate locations from the Elasticsearch/Kibana POD to the init container that will install ROR. The volumes should have write permissions because of the changes made by the ROR patcher. You can still use this method, but as you can see it's not that easy.

<details>

<summary>Expand to see details</summary>

If you are still interested in this one, please take a look at the examples in our repository:

* [Elasticsearch with ROR installed using the Init Container method](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/v1.58.0_es8.14.3/docker-envs/eck/kind-cluster/ror/es.yml)
* [Kibana with ROR installed using the Init Container method](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/v1.58.0_es8.14.3/docker-envs/eck/kind-cluster/ror/kbn.yml)

</details>

## Handling asynchronous ROR startup

ReadonlyREST starts asynchronously during Elasticsearch initialization. In some cases, there may be a brief moment when the pod reports as ready, but ROR is still starting up. During this window, requests may receive error responses (403/401/503, depending on your settings).

If you need to ensure that the pod is only marked as ready after ROR has fully started, you can use the sidecar pattern described below.

{% hint style="info" %}
**Note:** This is an optional pattern and not required for most deployments. Elastic [does not recommend](https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/readiness-probe#k8s_elasticsearch_versions_8_2_0_and_later) overriding the default Elasticsearch readiness probe. The sidecar pattern allows you to achieve ROR readiness checking without modifying the main Elasticsearch probe.
{% endhint %}

### Sidecar pattern for ROR readiness

The following example extends the basic Elasticsearch configuration by adding a sidecar container (`ror-ready-gate`) that includes its own readiness probe. This probe checks if ROR is ready to handle requests by testing the cluster health endpoint with proper authentication:

```yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 8.14.3
  image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
    podTemplate:
        spec:
          containers:
            - name: elasticsearch
              securityContext:
                runAsNonRoot: false
                runAsUser: 0
                runAsGroup: 0
              env:
                - name: I_UNDERSTAND_AND_ACCEPT_ES_PATCHING
                  value: "yes"
                - name: INTERNAL_USR_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal
                - name: INTERNAL_PROBE_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal-probe
                - name: KIBANA_SERVICE_ACCOUNT_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-kibana-user
                      key: token
                # Optional: little speed up ROR initialization
                - name: ES_JAVA_OPTS
                  value: "-Dcom.readonlyrest.settings.loading.delay=0s -Dcom.readonlyrest.settings.loading.attempts.count=1"
              volumeMounts:
                - name: config-ror
                  mountPath: /usr/share/elasticsearch/config/readonlyrest.yml
                  subPath: readonlyrest.yml

            # Sidecar container that waits for ROR to be ready
            - name: ror-ready-gate
              image: curlimages/curl:8.6.0
              command: ["sh", "-c", "sleep infinity"]
              securityContext:
                runAsNonRoot: true
                runAsUser: 1000
                runAsGroup: 1000
                allowPrivilegeEscalation: false
              readinessProbe:
                exec:
                  command:
                    - sh
                    - -c
                    - |
                      curl -sf -k --max-time 2 \
                        -u "elastic-internal-probe:$(cat /mnt/probe-user/elastic-internal-probe | tr -d '\n')" \
                        https://127.0.0.1:9200/_cluster/health > /dev/null
                periodSeconds: 5
                timeoutSeconds: 5
                failureThreshold: 60
              volumeMounts:
                - name: probe-user
                  mountPath: /mnt/probe-user
                  readOnly: true

          volumes:
            - name: config-ror
              configMap:
                name: config-readonlyrest.yml
            - name: probe-user
              secret:
                secretName: quickstart-es-internal-users
```

### How it works

1. The `ror-ready-gate` sidecar container runs alongside Elasticsearch
2. Its readiness probe makes authenticated requests to the cluster health endpoint
3. The pod is only marked ready when both containers pass their readiness checks
4. This ensures ROR is fully initialized before the pod receives traffic

The readiness probe will retry for up to 5 minutes (60 failures × 5 second period) before marking the pod as failed.

## Notes

* [ReadonlyREST SSL](https://docs.readonlyrest.com/elasticsearch#encryption) can be used but it's simpler to leave `xpack.security.enabled: true` and use X-Pack SSL instead
* To figure out how to obtain the ROR License see [our guide](/develop/universal-builds#how-to-activate-proenterprise-features-a-universal-build).

## Example

You can check the ROR-powered ECK Quickstart example by running our [one-liner script](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/tree/master/docker-envs/eck). It supports MacOS and Linux.


# Universal Builds

Starting from ReadonlyREST 1.44.0, our Kibana plugins are released in a unified format.

## What is a universal build?

A universal build is a single Kibana plugin deliverable that is compatible with a given Kibana version.

![](/files/CdS6lnOnJtCbZAi6Miwg)

If not activated, a universal build will behave exactly like the old "Free" edition.

Some activation keys can be used to unlock PRO and Enterprise features. If a trial activation key is used, these features availability will be limited in time.

## How to install a universal build?

The same way it worked before, just download it the same way you used to, from the [Downloads page](https://readonlyrest.com/download) and install it as usual.

## How to activate PRO/Enterprise features a universal build?

Check out our new [customer portal](https://readonlyrest.com/customer). If your email is associated to a customer contract with a valid ongoing subscription, you will be able to obtain an activation key. Otherwise, you will be able to obtain a trial activation key.

There are a few ways to pass the activation key to Kibana. Regardless of which one you use, the result will be that Kibana will save the activation key to an encrypted Elasticsearch index, so other Kibana instances will pick up the new activation key.

### Via Environmental variable

This method is useful for Docker deployments. Just set the `ROR_ACTIVATION_KEY` environment variable to the activation key you obtained from the customer portal.

```bash
$ export ROR_ACTIVATION_KEY=<your activation key>
$ bin/kibana 
```

### Via our plugin license API

Once Kibana is up and running, you can send a HTTP request to Kibana.

```
POST http://<kibana-host-with-ror>:5601/api/ror/license?overwrite=true
{
  "token": "your activation key" 
}
```

### Via ROR\_ACTIVATION\_KEY.txt file

The universal build is a zip archive. You can add a small text file with your activation key to this archive before installing it, so it will be picked up automatically at the first boot.

* The text file should be called `ROR_ACTIVATION_KEY.txt`
* It should contain your secret activation key string (no spaces, no new lines)
* It should be added to the **root directory** of the plugin zip archive

Now install the plugin file, start Kibana, and the activation key should be loaded.

### Interactive activation key management

This method is useful for manual deployments.

1. Start Kibana in the default "Free" edition mode
2. Login in Kibana as an `admin` or `unrestricted` kibana access
3. Toggle the ROR Menu on the top right
4. Click on the "Free" text tag

![](/files/NW7JZBes31ywnFbqcEIo)

1. Enter the activation key you obtained from the customer portal in the license management UI

![](/files/vLMf7ygi9mgiOix0Xilv)

### Change Activation key retrieval mode via kibana.yml

In the kibana.yml configuration file, you have the option to specify the mode for retrieving activation keys. Setting this parameter effectively means that all other activation key retrieval methods will be disregarded.

#### Default behaviour

If there is no `kibana.yml` license config provided or `activationKeyRetrievalMode: "all"` defined:

```yaml
readonlyrest_kbn:
    license:
        activationKeyRetrievalMode: "all" # "file" | "env" | "all" | "none"
```

Then the order of activation key validation is:

1. Retrieve from index
2. ENV variable
3. File
4. Default Activation key (which means free license) If AK is found in any of the aforementioned locations, the verification process will be halted.

#### From environment variable retrieval option

you can add to `kibana.yml`:

```yaml
readonlyrest_kbn:
    license:
        activationKeyRetrievalMode: "env" # "file" | "env" | "all" | "none"
```

#### From a file retrieval option

you can add to `kibana.yml`:

```yaml
readonlyrest_kbn:
    license:
        activationKeyRetrievalMode: "file" # "file" | "env" | "all" | "none"
        activationKeyFilePath: /tmp/activation.key
```


# Examples


# Multi-tenancy Elastic Stack (Enterprise)

([Enterprise](https://readonlyrest.com/enterprise))

This document will guide you through setting up your Elasticsearch and Kibana stack with ReadonlyREST such that:

* There will be two tenancies: one for Sales and one for Ops department.
* In each tenancy, 3 users will be able to login into Kibana using their own set of credentials
* Each tenancy will contain **its own, independant** Kibana dashboards, visualizations and index patterns.
* Each user within a tenancy may be restricted to visualizing distinct subsets of the whole data contained in Elasticsearch (i.e. only certain indices).

### Users and capabilities

For this tutorials, we want to have three users per tenancy, each of them has a distinct access level to a shared Kibana tenancy (set of dashboards and settings).

#### Sales Department

|                                              | "sales\_admin" | "sales\_rw\_usr" | "sales\_ro\_usr" |
| -------------------------------------------- | -------------- | ---------------- | ---------------- |
| Can create,edit,delete Sales' dashboards     | ✅              | ✅                |                  |
| Can change Kibana settings for Sales         | ✅              | ✅                |                  |
| Only sees "sales\_logstash\*" data from 2018 |                |                  | ✅                |
| Can see "add","delete","edit" buttons        | ✅              | ✅                |                  |
| "dev-tools" Kibana App is hidden             | ✅              | ✅                |                  |
| "readonlyrest" Kibana App is hidden          | ✅              |                  |                  |

#### Ops Department

|                                        | "ops\_admin" | "ops\_rw\_usr" | "ops\_ro\_usr" |
| -------------------------------------- | ------------ | -------------- | -------------- |
| Can create,edit,delete Ops dashboards  | ✅            | ✅              |                |
| Can change Kibana settings for Ops     | ✅            | ✅              |                |
| Only sees ops\_logstash data from 2018 |              |                | ✅              |
| Can see "add","delete","edit" buttons  | ✅            | ✅              |                |
| "dev-tools" Kibana App is hidden       | ✅            | ✅              |                |
| "readonlyrest" Kibana App is hidden    | ✅            |                |                |

> NB: ReadonlyREST for Elastisearch and ReadonlyREST Enterprise for Kibana have an great amount of features like groups, connector for external systems like LDAP, etc. Don't forget to visit the full documentation and the forum to know more about it.
>
> NB: The capabilities gained by admin users when they access the "readonlyrest" Kibana App **are global**, that is, they can add/remove tenancies, users, groups, etc.

## Before you start

For the scope of this guide, we will assume:

* You will have a functioning installation of Elasticsearch and Kibana
* You have [installed the ROR plugin for Elasticsearch](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#installing)
* You have [installed the ROR Enterprise plugin for Kibana](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#installation)

If you don't have the ROR [Enterprise](https://readonlyrest.com/enterprise) for Kibana plugin, get yourself a two weeks free trial build!

## Setup: the Elasticsearch side

Right beside your `elasticsearch.yml`, create a file called `readonlyrest.yml` and write the following settings into it.

```yaml
readonlyrest:

    access_control_rules:

    #########################################################
    # These credentials shall be used by the logstash daemon.
    #########################################################  
    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["*logstash-*"]


    #####################################################################################
    # These credentials have no limitations, and shall be used only by the Kibana deamon.
    #####################################################################################
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana
      verbosity: error

    ##############################
    # SALES: Actual human users...
    ##############################
    - name: "::RO_SALES::"
      auth_key: sales_ro_usr:dev1
      indices: ["logstash-2018*"]
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "kibana:dev_tools"]
        index: ".kibana_sales"

    - name: "::RW_SALES::"
      auth_key: sales_rw_usr:dev2
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]
        index: ".kibana_sales"

    - name: "::ADMIN_SALES::"
      auth_key: sales_admin_usr:dev3
      indices: ["logstash-*"]
      kibana:
        access: admin
        index: ".kibana_sales"

    ###########################
    # OPS Actual human users...
    ###########################
    - name: "::RO_OPS::"
      auth_key: ops_ro_usr:dev4
      indices: ["logstash-2018*"]
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "kibana:dev_tools"]
        index: ".kibana_ops"

    - name: "::RW_OPS::"
      auth_key: ops_rw_usr:dev5
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]
        index: ".kibana_ops"

    - name: "::ADMIN_OPS::"
      auth_key: ops_admin_usr:dev6
      indices: ["logstash-*"]
      kibana:
        access: admin
        index: ".kibana_ops"
```

## Setup: the Kibana side

With ROR, we try as much as possible to keep all the settings withing the Elasticsearch domain. Therefore, you'll notice how few settings are needed on the Kibana side, apart from actually installing the plugin.

Open up `config/kibana.yml` and add/edit the following settings:

```yaml
# Kibana server use ::KIBANA-SRV:: credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

## Running

Fire up Elasticsearch

```bash
$ bin/elasticsearch
```

And then Kibana

```bash
$ bin/kibana
```

## Logging in

Now you are ready to point your browser to the Kibana server IP (defaulting on port 5601) and you should see a login prompt. You can login as any user i.e. "sales\_rw\_usr", or "ops\_admin" and the password is always "dev".

MJust remember to login with a RW user first, so Kibana can create its own default settings.


# Multi-user Elastic Stack (PRO)

([PRO](https://readonlyrest.com/pro))

This document will guide you through setting up your Elasticsearch and Kibana stack with ReadonlyREST such that:

* 3 users will be able to login into Kibana using their own set of credentials
* All users will see the same Kibana dashboards, but may be seeing different subsets of the whole data contained in Elasticsearch.

### Users and capabilities

For this tutorials, we want to have three users, each of them has a distinct access level to a shared Kibana tenancy (set of dashboards and settings).

|                                         | "admin" | "rw\_usr" | "ro\_usr" |
| --------------------------------------- | ------- | --------- | --------- |
| Can create, edit, delete dashboards     | ✅       | ✅         |           |
| Can change Kibana settings              | ✅       | ✅         |           |
| Only sees logstash data from 2019       |         |           | ✅         |
| Can see "add", "delete", "edit" buttons | ✅       | ✅         |           |
| "dev-tools" Kibana App is hidden        | ✅       | ✅         |           |
| "readonlyrest" Kibana App is hidden     | ✅       |           |           |

NB: ReadonlyREST for Elastisearch and ReadonlyREST PRO for Kibana have an great amount of features like groups, connector for external systems like LDAP, etc. Don't forget to visit the full documentation and the forum to know more about it. NB: This guide works with ROR Enterprise as well.

## Before you start

For the scope of this guide, we will assume:

* You will have a functioning installation of Elasticsearch and Kibana
* You have [installed the ROR plugin for Elasticsearch](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#installing)
* You have [installed the ROR PRO/Enterprise plugin for Kibana](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#installation)

If you don't have the ROR [PRO](https://readonlyrest.com/pro) (or [Enterprise](https://readonlyrest.com/enterprise)) plugin for Kibana, get yourself a two weeks free trial build

## Setup: the Elasticsearch side

On the same directory with your `elasticsearch.yml` (default: `config/`, create a file called `readonlyrest.yml` and write the following settings into it.

```yaml
readonlyrest:

    access_control_rules:

    #########################################################
    # These credentials shall be used by the logstash daemon.
    #########################################################  
    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["*logstash-*"]


    #####################################################################################
    # These credentials have no limitations, and shall be used only by the Kibana deamon.
    #####################################################################################
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    #######################
    # Actual human users...
    #######################
    - name: "::RO::"
      auth_key: ro_usr:dev
      indices: ["logstash-2019*"] # <--- can see only data from 2019
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]

    - name: "::RW::"
      auth_key: rw_usr:dev
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]

    - name: "::ADMIN::"
      auth_key: admin_usr:dev
      indices: ["logstash-*"]
      kibana:
        access: admin
```

## Setup: the Kibana side

With ROR, we try as much as possible to keep all the settings withing the Elasticsearch domain. Therefore, you'll notice how few settings are needed on the Kibana side, apart from actually installing the plugin.

Open up `config/kibana.yml` and add/edit the following settings:

```yaml
# Kibana server use ::KIBANA-SRV:: credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

## Running

Fire up Elasticsearch

```bash
$ bin/elasticsearch
```

And then Kibana

```bash
$ bin/kibana
```

## Logging in

Now you are ready to point your browser to the Kibana server IP (defaulting on port 5601) and you should see a login prompt. You can login as any user i.e. "rw\_usr", or "admin" and the password is always "dev".

Just remember to login with a RW user first, so Kibana can create its own default settings.


# SAML SSO (Enterprise)

External connectors integration

([Enterprise](https://readonlyrest.com/enterprise))

With ReadonlyREST Enterprise, you can integrate with SAML 2.0 Single Sign-on identity providers for both authentication and authorization.

Follow the guides to know more.


# Keycloak

SAML SSO Integration with Keycloak as an identity provider.

This document will guide you through the task of setting up an excellent, open-source identity provider ([KeyCloak](https://www.keycloak.org)) to work as an external authenticator and authorizer system for your ELK stack. The scenario is the usual:

* A centralized, large Elasticsearch cluster
* A Kibana installation
* We want one, centralized multi-tenant Elasticsearch + Kibana

But with some more enterprise requirements:

* Users need to be able to change their passwords independently
* Users need to verify their emails
* Group managers need to be able to add, remove, block (only) their users.
* [Multi-factor authentication (MFA)](https://www.keycloak.org/docs/latest/server_admin/#one-time-password-otp-policies) is a requirement.

## What is Keycloak

Keycloak is an advanced authentication server that lets user administer their credentials and speaks many authentication protocols, Including SAML2.0 SSO.

### Setup KeyCloak

This tutorial was created using KeyCloak 8.0.1.

1. Download the standalone version of Keycloak from their official website
2. Run Keycloak: run `bin/standalone.sh` or equivalent for your platform.
3. Navigate to <http://localhost:8080> and configure the admin user's credentials **don't forget to fill the email address!**
4. Login as admin
5. Follow the explanation below, or (if your KC version is the same or close enough to this) use the import function to load this [configuration file](https://github.com/beshu-tech/readonlyrest-docs/tree/d77c4981b29a843fc82f89c4272fdddaab390d89/keycloak_601_ror_SAML.json)

If you imported the JSON file, you should have a "ror" realm, and a SAML client called "ror" (keep this ID or change the "issuer" setting in kibana.yml) in the "master" realm. Please now select "ror" realm, navigate to "clients", click "ror" client and double check everything matches with your use case, as this guide assumes both Kibana, Elasticsearch and Keycloak are running on "localhost".

### Configure Keycloak to work with ROR

First, we want to create a new dedicated "ror" realm, so we don't interfere with any other use of this Keycloak installation.

![keycloak\_screenshot](/files/kakO1oxYBDFIwdq2Ee7P)

Then, let's create a SAML client for this realm:

![keycloak\_screenshot](/files/14xGYtwIGdV4w0nnKmDb)

Then, configure the SAML client according to your Kibana URL, in this example, Kibana responds to "<https://localhost:5601/k>"

![keycloak\_screenshot](/files/ZgltdSpmsLei7lnlrU0F)

Now that the client is saved, let's observe the "configure" tab, here we will extract the two logout and login endpoints that we will use for configuring our SAML connector in "kibana.yml".

![keycloak\_screenshot](/files/xAXyfRfEGHkIoxe7OevW)

### Install ReadonlyREST Enterprise for Kibana

Please refer to our [documentation](/develop/kibana) on how to obtain and install ReadonlyREST Enterprise for Kibana. Also, remember that it relies on the Elasticsearch plugin to be configured as well.

### Setup the SAML connector

Provided that you have ReadonlyREST Enterprise installed and configured, you can add the following configuration:

**kibana.yml**

```yaml
# More on how to enable SSL on the official documentation of Kibana
server.ssl.enabled: true
server.ssl.key: /home/xx/selfsigned_ssl_localhost/localhost.key
server.ssl.certificate: /home/xx/selfsigned_ssl_localhost/localhost.crt

server.basePath: /k  # <-- optional, remember to change it in KC
elasticsearch:
  hosts: ["https://localhost:9200"] # <-- our Elasticsearch responds to https
  ssl.verificationMode: none
  username: kibana
  password: kibana

readonlyrest_kbn:
  logLevel: debug
  auth:
    # this secret string has to be longer than 256 chars, use environmental variables to fill it in maybe.
    signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"

    saml_kc:  # <--- Our SAML connector name, used in the path configured in KC
      buttonName: "KeyCloak SAML SSO"
      enabled: true
      type: "saml"
      issuer: "ror"  # <-- called exactly like the SAML client in KC
      entryPoint: "http://localhost:8080/auth/realms/ror/protocol/saml" # <-- from KC configuration tab!
      kibanaExternalHost: 'localhost:5601' 
      protocol: "https"  # <--- our Kibana responds to HTTPS
      usernameParameter: "nameID"
      groupsParameter: "Role"
      logoutUrl: "http://localhost:8080/auth/realms/ror/protocol/saml" # <-- from KC configuration tab!
      cert: /etc/ror/integration/certs/dag.crt # from KC realm keys tab <-- It can be also provided a string value 
```

You can find a public PEM-encoded X.509 signing certificate as a string value by selecting the "keys" tab in your newly created realm. After clicking on a cert button, you can copy the value into `kibana.yml` SAML config `cert` parameter.

![keycloak\_screenshot](/files/A1RhA6nHKp7VRkeIGrCL)

Don't forget setting up SAML requires some changes to security settings in `readonlyrest.yml` (on the Elasticsearch side). Security settings can also be changed via the ReadonlyREST Kibana app.

### Setup Elasticsearch with ReadonlyREST

Our Elasticsearch needs to be available on HTTPS (more detailed info in our [documentation](/develop/elasticsearch#encryption)). Configure SSL according to this guide.

Then write in **readonlyrest.yml**

```yaml
readonlyrest:

    audit:
      enabled: true
      outputs:
      - type: index

    access_control_rules:
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana
      verbosity: error

    - name: "ReadonlyREST Enterprise instance #1"
      kibana:
        access: ro
        index: ".kibana_sso"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["*"]

    ror_kbn:
    - name: kbn1
      # It has to be the same string as we declared in kibana.yml.
      signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/develop/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/develop/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/develop/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)


# Microsoft Azure AD

Integration with the managed cloud service Microsoft Azure Active Directory.

[Azure Active Directory (Azure AD)](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-whatis) is Microsoft’s cloud-based identity and access management ([IAM](https://en.wikipedia.org/wiki/Identity_management)) service. And it can be used as a SAML Single Sign-On (SSO) \[identity provider (IdP)]\(<https://en.wikipedia.org/wiki/Identity_provider_(SAML)>) for a pool of exiting users to sign in and access resources in external service providers like [ReadonlyREST Enterprise](https://readonlyrest.com/enterprise).

With Azure AD, you can graphically manage users, groups, credentials and permissions. ReadonlyREST Enterprise for Kibana will collaborate with Azure AD to authenticate, grant permissions and access to tenancies for users that are entirely managed within Azure AD.

In this guide, we are going to see how to configure Elasticsearch and Kibana with ReadonlyREST Enterprise to make use of Azure AD via the SAML protocol. The result will be a multi-user, optionally multi-tenant Kibana instance powered by [ReadonlyREST](https://readonlyrest.com).

## Install Elasticsearch and Kibana

Make sure you have a functioning installation of Kibana, backed by an instance of Elasticsearch. You can find [the installation guide](https://www.elastic.co/guide/en/kibana/current/install.html) in Elastic's website.

## Set up the ReadonlyREST plugins

In order to use ReadonlyREST Enterprise for Kibana, make sure you have installed ReadonlyREST Free for Elasticsearch first. Head to our [setup guide](/develop/elasticsearch#installing-the-plugin) to find instructions.

Once ReadonlyREST Free plugin is installed, configure an ACL for accepting SAML sessions from ReadonlyREST Enterprise for Kibana. This is also [explained in our guide](/develop/elasticsearch#ror_kbn_auth). Remember to choose a very long secret phrase (256+ characters)

Now head to the Kibana directory, and install a trial (or full) version of ReadonlyREST Enterprise, which can be freely downloaded from [our download page](https://readonlyrest.com/download). For [installation instructions](/develop/kibana#installation), see our Kibana plugin guide.

Azure AD only speaks with "https" websites, so make sure your Kibana web server is configured to serve pages in https. See a guide from Elastic on how to enable SSL

### Conventions and assumptions in this guide

This tutorial assumes that Kibana runs in <https://localhost:5601>, which is clearly only valid if you are trying this authentication system in your local computer.

And it also assumes you used something like [mkcert](https://blog.filippo.io/mkcert-valid-https-certificates-for-localhost/) to let your browser trust SSL certificates for localhost URLs.

In the real world, when you are configuring Kibana in production, make sure: 1. You have a valid SSL certificate for the Kibana server 2. You replace all the references to `localhost:5601` with the publicly reachable host name of your actual Kibana server.

## ReadonlyREST Configuration

Now, **on the Elasticsearch side**, you should have the `$ES_HOME/config/readonlyrest.yml` file configured to accept SAML sessions from kibana using the `ror_kbn_auth` rule. I.e.

```
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise sessions"
      ror_kbn_auth:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

**On the Kibana side**, we will now configure our Kibana plugin to speak with Azure AD. Open your `$KBN_HOME/config/kibana.yml`, it should look something like:

```yaml
elasticsearch.hosts: ["http://localhost:9200"] # <-- consider enabling "https" using the SSL feature in ReadonlyREST Free!
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"
# elasticsearch.ssl.verificationMode: none  # <-- uncomment if your Elasticsearch uses "https" with self signed certificates

server.ssl.enabled: true # <-- It's mandatory for Azure AD that we enable SSL in our Kibana server!
server.ssl.certificate: '/etc/kibana/ssl_cert/localhost.pem'
server.ssl.key: '/etc/kibana/ssl_cert/localhost-key.pem'

readonlyrest_kbn:
  cookiePass: '12312313123213123213123abcdefghijklm'
  logLevel: debug
  clearSessionOnEvents: ["login"]

  auth:
    signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!

    saml_azure:
      buttonName: 'Azure AD SAML SSO'
      enabled: true
      type: saml
      issuer: 'ror'
      protocol: 'https'
      cert: '/etc/kibana/config/cert.pem' # <-- will download later from Azure enterprise app dashboard
      entryPoint: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2'
      kibanaExternalHost: 'localhost:5601'
      usernameParameter: 'http://schemas.microsoft.com/identity/claims/displayname'
      groupsParameter: 'http://schemas.microsoft.com/ws/2008/06/identity/claims/groups'
      logoutUrl: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2'
```

### Notes about ReadonlyREST Kibana settings

The `issuer` parameter is important and should be ideantical to what you wrote in the field *(1) Basic SAML Configuration - Identifier (Entity ID)* in the Azure AD settings.

The `entryPoint` value should be copied from the field `(4) Set up ReadonlyREST Enterprise > Login URL` in Azure AD settings.

The `kibanaExternalHost` only accepts the browser facing hostname (or IP address) and optionally the port of our Kibana server. Do not put any "https\://" prefix here.

The `cert` is an **absolute** path to the **base64** version of the certificate ReadonlyREST Enterprise will use to verify the signature of the SAML assertion coming from Azure AD. This file can be downloaded from : `(3) SAML Signing Certificate > Certificate (Base64)`

The `groupsParameter` and `usernameParameter` values represent the JSON fields names from the SAML assertion object coming from Azure AD. They represent the field names we take the username and groups information from.

An example of SAML assertion object coming from Azure AD after successful authentication looks like so:

```javascript
{
  "http://schemas.microsoft.com/claims/authnmethodsreferences": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  "http://schemas.microsoft.com/identity/claims/displayname": "Simone Scarduzio",
  "http://schemas.microsoft.com/identity/claims/identityprovider": "https://sts.windows.net/88af1572-1347-45b6-8f65-xxxxxxxxx/",
  "http://schemas.microsoft.com/identity/claims/objectidentifier": "486abf50-a61f-40e9-8a37-3ff6a6eeda26",
  "http://schemas.microsoft.com/identity/claims/tenantid": "88af1572-1347-45b6-8f65-xxxxxxxxxxxx",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups": [
    "00f22de3-0d59-4867-8e1a-xxxxxxxxxxxx",
    "dbe4ff5a-deba-419f-a653-xxxxxxxxxxxx",
    "3c19b288-263c-4dd1-9947-xxxxxxxxxxxx"
  ],
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/wids": "62e90394-69f5-4237-9190-xxxxxxxxxxxx",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": "Simone",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Simone@ror-enterprise-test.com",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": "Scarduzio",
  "issuer": "https://sts.windows.net/88af1572-1347-45b6-8f65-xxxxxxxxxxxx/",
  "nameID": "Simone@ror-enterprise-test.com",
  "nameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
  "sessionIndex": "_97f290ee-2ff6-445f-a0c6-xxxxxxxxxxxx",
  "user": "Simone Scarduzio"
}
```

## Azure AD configuration

1. Login in your Microsoft Azure dashboard, and head to Enterprise Applications.

![Azure Dashboard](/files/PnmbZJb6zcuRoilv76pt)

1. Click on "Non-gallery application".

![Azure Enterprise apps](/files/hnpZNRs9vfkcdhsTN3GB)

1. Create a new app called "Readonlyrest Enterprise".

![Azure App Name](/files/v8nxWPbuS8oiQ6QDQ2Ex)

1. Click "Single Sign On" to configure the app for SAML.

![Azure ror app](/files/cfRj3xa47XTdUqbuxPpf)

1. Insert URLs and data about our Kibana server as shown in the picture. And press SAVE.

![Azure ror basic saml settings](/files/xNcbD7dSkOR3nM4SliR1)

1. Download the base64 encoded "pem" file, and place it under the **absolute path** `/etc/kibana/config/cert.pem`.

![Azure ror cert](/files/-MIsCJDhsbMkbvkdJpw9)

7 Make sure this app has at least a test user assigned, and press SAVE. Otherwise the single sign-on will fail.

![Azure ror app users](/files/OWLzqsrAN4hDYIGg5kn5)

## Testing if this all works.

1. Now point your browser to your Kibana installation (in the example <https://localhost:5601>).

   You should now see a new blue button that says "Azure AD SAML SSO".

![ROR login](/files/QNrLaAo2rSRXkCkQ0uhS)

1. Press it, and you should see the Azure AD login page. Place your credentials here, or pick an already authenticated identity to enter Kibana.

![Azure Login](/files/E3jByDcRwJJQif0FjGb7)

1. You will now be redirected to Kibana, logged in as your Azure AD identity.

![Azure Login](/files/tAClXpF8YlVtNJcwlxHv)

1. You can now logout from the "ReadonlyREST SAML SSO" Azure AD Enterprise app by pressing the exit button right beside the username in the bottom right corner.

![Azure Login](/files/3xqvGQMk3lZ9kCfIaiLg)

## Authorization using Azure AD groups

Users in Azure AD can belong to groups. The list of group associated to a user is useful information for ReadonlyREST Enterprise for identifying sets of users that we want to authorize to:

* see certain indices
* perform certain actions over certain indices
* belong to a tenancy
* have read or read/write permission to a tenancy
* have administrative rights over ReadonlyREST cluster-wide security settings
* many more things, or even a combination of all these.

### Example: ReadonlyREST Admins group

Suppose we would like to authorise the group "ReadonlyREST Admins" to access the administrative dashboard that can oversee all the indices, and we want to grant them access to an "admin" tenancy that contains dashboards based on the real time [ReadonlyREST audit logs](/develop/elasticsearch#audit) indices.

#### Creating and assigning the group in Azure Ad

Let's go to Azure and make sure the "ReadonlyREST Admins" group is created, and one or more users - including ours - belongs to it.

![Azure Create Groups](/files/impgJ4yhsjBaS91EGetW)

#### Finding the new group's Azure object ID

From ReadonlyREST settings, we will refer to the newly created group using its associated object ID provided by Azure platform. To discover it, navigate the Azure AD dashboard to:

`Dashboard > Enterprise applications - All applications > ReadonlyREST Enterprise - Users and groups > [your user] - Groups`

![Azure Show Groups](/files/d5wxVZCcuEnlqPTIH61n)

The object ID of the new group is "3f8ebed8-f742-42a6-94ba-2d57550fc3cf", let's take note of this. We will use it in our ACL.

#### Using ReadonlyREST ACL to authorize the group

Let's head back to Elasticsearch, and open `readonlyrest.yml`. Let's now add the authorization.

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    - name: "Azure AD - ReadonlyREST Admins group"
      indices: ["readonlyrest-audit*"]
      kibana:
        access: "admin"
        index: ".kibana_admin_tenancy"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["3f8ebed8-f742-42a6-94ba-2d57550fc3cf"]

    - name: "Azure AD - Anyone else"
      indices: ["readonlyrest-audit*"]
      kibana:
        access: "rw"
        index: ".kibana_generic_tenancy"
        hide_apps: ["readonlyrest_kbn"]
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["*"]

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

Now we have two ACL blocks dedicated to Azure AD: one will match for users that belong to "ReadonlyREST Admins" (a.k.a object ID `3f8ebed8-f742-42a6-94ba-2d57550fc3cf`), the other will match for Azure AD users that do not belong to the group.

The key here is the use of the `roles` option in the `ror_kbn_auth` rule, as an extra constraint so that the ACL block is only matched when the user has the "3f8ebed8-f742-42a6-94ba-2d57550fc3cf" string in the list of their groups.


# Microsoft ADFS

Integration manual for ReadonlyREST Enterprise with the on-premises Active Directory Federated Services  Single Sign-on from Microsoft.

How to Connect ROR Enterprise with SAML and ADFS

ReadonlyREST (ROR) Enterprise allows for complex authentication and authorization configurations with Kibana and Elasticsearch. When Elasticsearch is combined with Kibana, a data visualization dashboard, the combination provides a powerful way to ingest logs and analyze data.

To access that data, many enterprises manage users in a central directory. This directory could be an Active Directory (AD) instance, in the case of a Windows-centric environment, or a cloud directory provider, such as Google Cloud Identity, in a cloud-based environment. Instead of integrating these services directly into a product, an abstraction layer such as SAML can provide authentication and authorization and tie into different back ends as necessary.

ReadonlyREST provides a free Elasticsearch plugin that provides advanced authentication options. When it is combined with the ReadonlyREST Enterprise plugin for Kibana, integrating SAML authentication into the authentication process becomes easy.

This article will walk through the process of setting up an entire environment in order to demonstrate how the ReadonlyREST free and Enterprise plugins integrate with Active Directory Federation Services (AD FS) to provide SAML authentication.

In this tutorial, you will learn how to:

* Provision Azure Virtual Machines to host Active Directory, Elasticsearch, and Kibana
* Install and configure Active Directory (AD) Services
* Provision sample AD users
* Install and configure Active Directory Certificate Services (AD CS)
* Install and configure Active Directory Federation Services (AD FS)
* Install and configure ElasticSearch and the ReadonlyREST Free Plugin
* Install and configure Kibana and the ReadonlyREST Enterprise Plugin

## Provisioning Azure Virtual Machines to Host Active Directory, Elasticsearch, and Kibana

Any Windows Server 2016 Virtual Machines (VM) can be used for this process; however, in this demonstration, the Microsoft Azure environment will be used to provision and host the VMs.

You can name your VMs whatever you would like. This article will refer to the names listed below for consistency.

* Virtual Machine 1: lc-win2019-02
  * **Roles**: Active Directory, AD Certificate Services, AD Federation Services, DNS
  * **Memory**: 4GB
* Virtual Machine 2: lc-win2019-03
  * **Roles**: Elasticsearch, Kibana
  * **Memory**: 8GB

These Azure VMs will be Pay-As-You-Go and Spot Instances for affordability. The example shown below is for the Elasticsearch and Kibana VM which will be duplicated for the Active Directory VM but will have 4GBs of memory instead of 8GB.

*Please note that Azure Spot Instances cannot be resized after creation.*

### Provisioning Virtual Machines

1. Log into the **Azure portal** using a **Pay-As-You-Go** subscription.
2. Create a **new virtual machine.**
3. If you do not already have a **resource group** created to serve as a home for the VMs, select **Create new** and create the **resource group.**
4. Name your virtual machine appropriately, and choose the details for your instance, as shown in the example below.
5. You can use the default hard drive sizes and **Standard HDD** disks for this environment.
6. ![](/files/-ML8MzNOB1E7Vi3n1UoG)
7. The default **Networking** options will also work here.
8. ![](/files/-ML8MzNPdlZVtGRJxd-3)
9. As will the default **Management** options.
10. ![](/files/-ML8MzNQ1TUWyAsPDp8O)
11. No additional **Advanced** options are necessary.
12. ![](/files/-ML8MzNRIT1g0V4TIFIa)
13. If you would like to tag your VMs for later categorization and tracking, you can do so here.
14. ![](/files/-ML8MzNSNAeWMIUa1Dk9)
15. Finally, create the VM.

After this VM has been created, create one more to host the Active Directory and related services. In the end, you should have two VMs as outlined above.

## Installing and Configuring Active Directory (AD) Services

After the two VMs have been provisioned, the next step is to set up directory services on the first VM, lc-win2019-02.

### Installing Active Directory Services

1. Once you are logged into lc-win2019-02, choose **Add Roles and Features** on the **Server Manager** screen.
2. Select **Role-based or feature-based installation**.
3. ![](/files/-ML8MzNVKrk8lirgAHH5)
4. Select the correct server from the server pool.
5. ![](/files/-ML8MzNWCj_ASCeITRxL)
6. Select **Active Directory Domain Services,** and add the additional features as prompted.
7. No additional features are necessary since the default options work.
8. ![](/files/-ML8MzNZWns_fS1c39gQ)
9. Click **Next** on the **Active Directory Domain Services** informational screen.
10. ![](/files/-ML8MzN_SV3pRjAhSCGx)
11. Finally, select **Restart the destination server automatically if required**. Click **Yes** when prompted, and then click **Install.**
12. Once installation has finished, click on **Close.**
13. ![](/files/-ML8MzNck-qvrqoDsVpV)

### Configuring Active Directory Services

If DNS has not been installed already, the role installation screen may pop up in the middle of the Active Directory installation. Installation instructions for the DNS role are shown after the Configuring Active Directory Services section below.

1. Click on **Promote this server to a domain controller,** which will allow you to see the **Deployment Configuration** screen.
2. ![](/files/-ML8MzNd3zkDsvP8B5An)
3. Name the domain. In this case, use ad.lc-test.local.
4. This name was arbitrarily chosen. Using a subdomain such as “ad” instead of your actual domain (i.e., \[lc-test.local]\([http://lc-test.local\\](http://lc-test.local/\)\)%20by%20itself/) is recommended.
5. ![](/files/-ML8MzNe8sRO0MYrwmWT)
6. Select **Windows Server 2016** as the **functional level**. For the **domain controller capabilities**, choose **Domain Name System (DNS) server**. Set a Directory Services Restore Mode (DSRM) **password.**
7. ![](/files/-ML8MzNfj1fBOJDPt84I)
8. The following **DNS Options** warning message can be disregarded:
9. ![](/files/-ML8MzNgGbQv2a1cxNv0)
10. Set the **NetBIOS domain name**, which is usually the short name prior to the host name (e.g., AD), and click **Next.**
11. ![](/files/-ML8MzNh9m0aok5EF9UN)
12. Use the default paths, and click on **Next.**
13. ![](/files/-ML8MzNiXFQXdGNFtXw3)
14. On the **Review Options** screen, click **Next** if everything looks correct.
15. ![](/files/-ML8MzNjtvS_-O7HQFEi)
16. On the **Prerequisites Check** screen, click **Install.**
17. You’ll see a number of warnings related to the fact that this is a test environment. They can be safely ignored.
18. ![](/files/-ML8MzNkiyFagMd-nVW2)
19. When you click **Close**, you will have a successful configuration.
20. ![](/files/-ML8MzNlvRibpGRsFk6O)
21. Click **Close** on the restart prompt.
22. ![](/files/-ML8MzNmPqox1-ETLmZX)

### Configuring the Domain Name Services (DNS) Role

1. Click on the **DNS Services** role, add the additional features as requested, and click **Next.**
2. If you are using DHCP for the server (this is not recommended for a production service), then you will see the validation warning shown below. It can be disregarded. Click on **Continue** and **Install.**
3. ![](/files/-ML8MzNpIt9aaWDLkhcC)
4. Click **Next** on the **Features** screen, since no additional features are necessary.
5. ![](/files/-ML8MzNqV4O3XudgCOya)
6. Click **Next** on the informational **DNS Server** screen.
7. ![](/files/-ML8MzNrS-yQm8CSUAXy)
8. Click **Install** on the **Confirmation** screen.
9. ![](/files/-ML8MzNsi8MMQQIZsiX0)
10. Select **Restart the destination server automatically if required,** and click **Install.** Finally, click **Close,** and you will have a successful installation.
11. ![](/files/-ML8MzNtCClbf1Hu2AEE)

### Joining Computers to the Domain

Next we need to join the second server—the one hosting Elasticsearch and Kibana—to the domain.

1. Open an RDP connection to the second server. Then, open **Notepad** as an Administrator, and open the file C:\Windows\System32\drivers\etc\hosts.
2. ![](/files/-ML8MzNuQrSeVzy5_oKp)
3. Add the IP address and hostnames for the domain controller.
4. These will reflect the IP addresses and hostnames you chose for your configuration:
   * **FQDN**: 10.0.0.5 - ad.lc-test.local
   * **NetBIOS**: 10.0.0.5 - ad
5. ![](/files/-ML8MzNvtEC_0Ni6V2R7)
6. Additionally, you will need to change your network adapter DNS to point to your domain server; in this case, it is 10.0.0.5.
7. If the system restarted, open an RDP connection, and then open the **System** screen under the **Control Pane** and select **Advanced System** settings.
8. ![](/files/-ML8MzNynDTLChI9xe6d)
9. Click on **Change** to add this server to the domain.
10. ![](/files/-ML8MzNzUmMW3DpR76X-)
11. Enter the domain (e.g., ad.lc-test.local).
12. ![](/files/-ML8MzO-yRihwCDdc5Hq)
13. Click on **OK,** and enter the credentials of the account that has privileges enabling it to add the domain.
14. ![](/files/-ML8MzO06Ik_tvTG3k5z)
15. Restart the server after joining it to the domain.

## Provisioning Sample AD Users

For testing purposes, it can be useful to provision additional users within the Active Directory. The following PowerShell script, which should be run on the domain controller, will make this easy. Note that we are setting the mail attribute which will be used for the SAML username.

Import-Module -Name 'ActiveDirectory'

$Domain = 'ad.lc-test.local'\
$OU = 'CN=Users,DC=ad,DC=lc-test,DC=local'

$Users = @{\
"TestUser1" = "testPass1"\
"TestUser2" = "testPass2"\
"TestUser3" = "testPass3"\
"TestUser4" = "testPass4"\
"TestUser5" = "testPass5"\
}

$Users.GetEnumerator() | ForEach-Object {\
$Name = $\_.Key\
$Password = $\_.Value

$Params = @{\
"Name" = $Name\
"Path" = $OU\
"AccountPassword" = (ConvertTo-SecureString -AsPlainText $Password -Force)\
"Enabled" = $True\
"DisplayName" = $Name\
"PasswordNeverExpires" = $True\
"CannotChangePassword" = $True\
"EmailAddres" = "$Name@$Domain"\
}

New-ADUser @Params\
}

## Installing Active Directory Certificate Services (AD CS)

1. Click on **Active Directory Certificate Services,** and add the additional features as prompted.
2. Since no additional features are necessary, allow defaults, and click on **Next.**
3. ![](/files/-ML8MzO3O_wdTqoS6MFC)
4. On the **Active Directory Certificate Services** informational screen, click **Next** to continue.
5. ![](/files/-ML8MzO4285d61x48hfS)
6. Select the **Certification Authority** role services, and click **Next.**
7. ![](/files/-ML8MzO5NPcH7wlyD2KG)
8. Select **Restart the destination server automatically if required,** and click on **Install.**
9. ![](/files/-ML8MzO6NgpCqbSIQOI5)
10. Finally, click on **Close** when the installation has been completed.
11. ![](/files/-ML8MzO7e8ChFU-Zt0oS)

### Configuring Active Directory Certificate Services

1. Click on **Configure Active Directory Certificate Services.**
2. ![](/files/-ML8MzO85sRb0Gq9gJlO)
3. Select the default administrative user for AD CS credentials.
4. ![](/files/-ML8MzO92lXXp7_YFoFF)
5. Under **Role Services**, check **Certification Authority,** and click Next.
6. ![](/files/-ML8MzOAGJHHzqqGvs5N)
7. Select **Enterprise CA,** and click on **Next.**
8. ![](/files/-ML8MzOBHDMGy4iFbvH2)
9. Click on **Root CA**, then click on **Next.**
10. ![](/files/-ML8MzOCgjXw_6BGmDuv)
11. Click on **Create a new private key,** and then click on **Next.**
12. ![](/files/-ML8MzODhRZ5kTQ8xsXE)
13. Select **RSA#Microsoft Software Key Storage Provider**, a default key length of **2048**, and a hash algorithm of **SHA256.** Click **Next.**
14. ![](/files/-ML8MzOEETSwPQ3aAlo3)
15. Use the defaults given for the CA Name, and click on **Next.**
16. ![](/files/-ML8MzOFYH3WT-eTnw0M)
17. Select a validity period of 5 years, and click on **Next.**
18. ![](/files/-ML8MzOG3W9RIcugKEOH)
19. Leave the default database locations in place, and click on **Next.**
20. ![](/files/-ML8MzOHGOnsvXCNKWto)
21. Click on **Configure** and **Close** when the configuration process has been completed.
22. ![](/files/-ML8MzOI6N-uAhM9lY8l)

## Installing and Configuring Active Directory Federation Services (AD FS)

Previously, it was recommended that AD FS should not be installed on the same server as the DC because IIS was installed as part of that process. As of 2012, this recommendation has changed, since AD FS does not use IIS anymore. Now, mounting AD FS and DC on the same server is advised for domains under 1000 users.

### Provisioning SSL Certificate Templates

1. Open the **Certification Authority** MMC snapin, right-click on **Certificate Templates,** and click on **Manage.**
2. ![](/files/-ML8MzOJQdF5uKiW0nUU)
3. Select **Duplicate Template** on the **Web Server** template.
4. ![](/files/-ML8MzOKvawdmixryLJ9)
5. Enter “SSL Certificates” in the box labeled **Template display name** on the **General** tab.
6. ![](/files/-ML8MzOLtvOKeXHtczYu)
7. On the **Security** tab, click on **Enroll** and **Allow for Authenticated Users,** and, finally, **Apply** the configuration.
8. ![](/files/-ML8MzOMmuJ-ckF3EZM3)
9. Right-click on **Certificate Templates → New → Certificate Template to Issue.**
10. ![](/files/-ML8MzONQ5mHS5bIlSol)
11. Select **SSL Certificates** from the **Certificate Template** list, and click **OK.**

### Provisioning the SSL Certificate

1. Open the Certificates MMC snapin for the Local Computer. Navigate to **Personal → Certificates,** and right-click to open **All Tasks → Request New Certificate.**
2. ![](/files/-ML8MzOQPEcN9hqpAbEE)
3. Click **Next** on the **Before you Begin** screen.
4. ![](/files/-ML8MzORi7gxLPl_y5D4)
5. Click **Next** on the **Select Certificate Enrollment Policy** screen.
6. ![](/files/-ML8MzOSNw4tGSPF8F62)
7. Select **SSL Certificates,** and click on **More information is required to enroll for this certificate.** Select **Click here to configure settings** to configure the certificate.
8. ![](/files/-ML8MzOTEC-LwnXStC-u)
9. Add the following details on the **Certificate Properties Subject** screen, then click **OK**:
   * **Subject Name**
     * **Common Name**: CN=lc-win2019-02.ad.lc-test.local
   * **Alternative Name**
     * **DNS**: lc-win2019-02.ad.lc-test.local
     * **DNS**: enterpriseregistration.ad.lc-test.local
10. ![](/files/-ML8MzOU2dAt-W7hdsUB)
11. Click on **Enroll** to request the certificate, then click **Finish**.
12. ![](/files/-ML8MzOV7I5XI1rfJzKC)

### Setting up Active Directory Federation Services

1. Select the **Active Directory Federation Services** role, and click **Next.**
2. ![](/files/-ML8MzOWROq8YQprgDlN)
3. Click **Next** on **Select Features**, since no additional features are needed.
4. ![](/files/-ML8MzOXqGg6okTJj30n)
5. Click **Next** on the **Active Directory Federation Services** informational screen.
6. ![](/files/-ML8MzOYdDucXXywHna2)
7. Check **Restart the destination server automatically if required,** and click **Install** and **Close** when the installation has completed.
8. ![](/files/-ML8MzOZAvzrbbjT4zos)

### Creating a Group Managed Service Account and Adding a KDS Key

It is best to use a **gMSA** (group Managed Service Account) instead of a traditional **sMSA** (standalone Managed Service Account). The primary difference between the two is that, in a gMSA, the Windows operating system manages the password for the account instead of relying on the administrator to do it.

Before we can select the gMSA, however, we need to add a **KDS Root Key**. To avoid non-blocking warnings later in the process, this key should be added with an effective date of 10 hours prior to the current date and time.

Open a **PowerShell** session as an Administrator, and run the following command to add the KDS root key:

Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))

If you do not take this step, you will see the following error when attempting to add the gMSA account:

![](/files/-ML8MzO_MlaaniFUHDH-)

To create a gMSA account to use with the AD FS service, use the PowerShell script provided below. If you get an “access denied” error when running Install-ADServiceAccount, you may need to restart the server first.

$Name = 'sa\_adfs'

$Params = @{\
"Name" = $Name\
"DNSHostName" = 'lc-win2019-02.ad.lc-test.local'\
"PrincipalsAllowedToRetrieveManagedPassword" = 'lc-win2019-02$'\
"ServicePrincipalNames" = 'http/lc-win2019-02.ad.lc-test.local'\
}

$ServiceAccount = New-ADServiceAccount @Params

Install-ADServiceAccount -Identity $Name

Add-ADComputerServiceAccount -Identity 'lc-win2019-02' -ServiceAccount $ServiceAccount

### Configuring Federation Services

1. Click on the link that says **Configure the federation service on this server**.
2. ![](/files/-ML8MzOaKdqLRmUJOaBR)
3. Select **Create the first federation server in a federation server farm,** and click **Next.**
4. ![](/files/-ML8MzObOnElJPC73Ncc)
5. On the **Connect to Active Directory Domain Services** screen, leave the default user selected, then click on **Next**.
6. ![](/files/-ML8MzOc9E-zRXlrin9P)
7. Select the previously created SSL Certificate, and enter “LC Test” for the **Federation Service Display Name.** Click **Next.**
8. ![](/files/-ML8MzOdknq8iXw81vEv)
9. On the **Specify Service Account** screen, click on **Select** to use an existing account and locate the sa\_adfs service account that was previously created. Click **Next.**
10. ![](/files/-ML8MzOeuvErqRL3tyBS)
11. Select **Create a database on this server using Windows Internal Database,** and click **Next**.
12. ![](/files/-ML8MzOfklxg8hFTSDfh)
13. Click on **Next** under **Review Options.**
14. ![](/files/-ML8MzOgzbmHNZXb-NoY)
15. Verify the **Pre-requisite Checks,** and click on **Configure.**
16. ![](/files/-ML8MzOhvUdZZKDUyAeA)
17. Click on **Close,** and restart the server.
18. The warnings shown below can be disregarded for this test instance:
19. ![](/files/-ML8MzOiZF29HwRjPbDE)
20. Once the server has restarted, open an Administrative PowerShell session, and run the following command to enable the **IdP Signon Page:**
21. Set-ADFSProperties -EnableIdPInitiatedSignonPage $True
22. Verify that AD FS metadata is being returned by navigating to the following URL:
23. [https://{FQDN](https://{fqdn) of AD FS Server}/adfs/fs/federationserverservice.asmx
24. ![](/files/-ML8MzOjmGIbhcz2aqdU)

### Setting Up ReadonlyREST Relying Trust

1. Open the **AD FS** MMC snapin, right-click on the **Relying Party Trusts** folder, and select **Add Relying Party Trust.**
2. ![](/files/-ML8MzOkjaWC1vT7GT0s)
3. Select **Claims aware,** and click **Start.**
4. Choose **Enter data about the relying party manually,** and click **Next.**
5. Enter a **Display Name** (in this case, “ror”), and click **Next.**
6. It’s not necessary to specify a token encryption certificate, so click **Next** to continue.
7. Select the option **Enable support for the SAML 2.0 SSL service URL,** and enter:
8. [https://{IP Address of Kibana Server}:5601/ror\_kbn\_sso\_saml\_adfs/assert](https://10.0.0.6:5601/ror_kbn_sso_saml_adfs/assert)
9. The saml\_adfs will change depending on the name chosen in the configuration of the kibana.yml file.
10. Enter the **Relying party trust identifiers**, in this case, “ror.” This will match the **Issuer** in the Kibana configuration. Click **Next** when you are done with this step.
11. On the **Access Control Policy** screen, select **Permit everyone,** and click **Next.**
12. Click **Next** to finish adding the trust.
13. Verify that **Configure claims insurance policy for this application** is selected, and click on **Close.**

### Configuring Claims

Though we have not yet configured claims for Kibana, the metadata for the SAML configuration in Kibana would look similar to the following, if you were able to view it:

![](/files/-ML8MzOl0_q7lubzsVMp)

The important section to note concerns the claims issuance policy. We need to return a **NameID** format in the form of an **emailAddress** by entering the following code\*\*:\*\*

\<NameIDFormat>\
urn:oasis:names:flag\_tc:SAML:1.1:nameid-format:emailAddress\
\</NameIDFormat>

Therefore, we need two rules: one to pull back the LDAP attribute from the Active Directory, and another to transform that data into the correct format.

1. Click on **Add Rule** on the **Edit Claim Issuance Policy** **for ror** screen.
2. ![](/files/-ML8MzOmnxaD7-NfnoId)
3. For the first rule, choose **Send LDAP Attributes as Claims,** and click **Next.**

![](/files/-ML8MzOno0NVs_KkvENM)

1. Choose the AD Attribute to return—in this case, the email address—and click **Finish** on the **Configure Rule** screen of the **Add Transform Claim Rule Wizard.**
   * **Claim rule name**: LDAP Email
   * **Active Store**: Active Directory
   * **LDAP Attribute**: E-Mail-Addresses
   * **Outgoing Claim Type**: E-Mail Address

![](/files/-ML8MzOohijZwoWxx-ir)

1. Click on **Add Rule,** then choose the **Transform an Incoming Claim** claim rule template, and click on **Next.**

![](/files/-ML8MzOpk9GS5vPvTx5R)

1. Enter the transformation details as listed below, and, on the **Edit Rule** screen, click on **OK.**
   * **Claim rule name**: Email Transform
   * **Incoming claim type**: E-Mail Address
   * **Outgoing claim type**: Name ID
   * **Outgoing name ID format**: Email
   * **Pass through all claim values**: Selected

![](/files/-ML8MzOq-QCmrPIRCJUa)

1. Click **OK** to save the rules.
2. Please note that the order of the rules on the **Edit Claim Issuance Policy** screen is important.
3. ![](/files/-ML8MzOrds5Dhn242dik)

### Updating Relying Party Trusts

1. Navigate to the **Relying Party Trusts** folder, right-click on the **ror trust,** and select **Properties.**

![](/files/-ML8MzOsBRiLm34RVaXv)

1. Click on the **Endpoints** tab, select the **SAML Assertion Consumer Endpoints,** and click on **Edit.**

![](/files/-ML8MzOtth3z2H3WnuSv)

1. Click on **Set the trusted URL as default,** and change the Index to 1 from 0. Click **OK.**

![](/files/-ML8MzOunpe0lnJ0MrZS)

1. On the **Endpoints** screen, click on **Add SAML,** and enter the **SAML Logout** details as follows:
   * **Endpoint Type**: SAML Logout
   * **Binding**: POST
   * **Trusted URL**: [https://{IP](https://{ip) Address of Kibana Server}:5601/ror\_kbn\_sso\_saml\_adfs/notifylogout

![](/files/-ML8MzOvty24bhIaFPei)

1. Click on **OK** to save the modified properties. ![](/files/-ML8MzOwHfU6p4YMtmyM)

## Installing and Configuring Elasticsearch and the ReadonlyREST Free Plugin

### Installing Elasticsearch

Elasticsearch will be installed on the lc-win2019-03 server provisioned with 8GB of RAM in Azure.

1. Locate a recent download of [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/windows.html), and install the [MSI](https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2.msi) package.
2. At the time this article was written, the most recent version available was 7.6.2; however, you may want to check for more updated versions as they become available.
3. Launch the downloaded installer and click **Next** on the **Locations** screen, leaving the defaults in place.
4. ![](/files/-ML8MzOx6atG3wczS5iB)
5. Use the defaults on the **Service** screen, and click **Next.**
6. ![](/files/-ML8MzOy3QSS9vmQyK7k)
7. Use the defaults on the **Configuration** screen, and click **Next.**
8. ![](/files/-ML8MzOzQcjivZbrDBv4)
9. No additional plugins are necessary; therefore, click **Next.**
10. ![](/files/-ML8MzP-jE7eMASi6IeV)
11. Leave the **X-Pack** licenses set to **Basic,** and click on **Install.**
12. ![](/files/-ML8MzP0vNwmazSxkAL7)
13. Click on **Exit.**
14. ![](/files/-ML8MzP1JHl0VfWenrm9)

### Installing the Elasticsearch Plugin

1. Navigate to the [ReadonlyREST Plugin download page](https://readonlyrest.com/download/) to enter your details. You will receive the download link in your email. Make sure to choose the **Free Elasticsearch Plugin** that matches your Elasticstack version.
2. <img src="/files/-ML8MzP2jwzJI0T_r-0j" alt="" data-size="original">
3. Download the plugin, open an Administrative command prompt, and navigate to the Elasticsearch program directory. Run the plugin installation by entering the following:
4. cd "C:\Program Files\Elastic\ElasticSearch\7.6.2\bin"

   elasticsearch-plugin.bat install file:///C:/Users/lc-admin.AD/Downloads/readonlyrest-1.19.4\_es7.6.2.zip
5. <img src="/files/-ML8MzP3fIuCoc15Wf-n" alt="" data-size="original">
6. Navigate to the C:\ProgramData\Elastic\ElasticSearch\config directory, and create the file readonlyrest.yml.
7. <img src="/files/-ML8MzP4LkzP8JKcqeER" alt="" data-size="original">
8. Open the readonlyrest.yml file in Notepad to run this very basic configuration that configures the following two different access control rules: 1. **“**::KIBANA-SRV::**”**—this rule allows the Kibana server to authenticate to Elasticsearch using digest authentication with the username “kibana” and password “kibana.” 2. “ADFS Users”—this rule uses the ror\_kbn\_auth method which allows SAML authenticates to succeed.
9. Create a random 256-character signature\_key. This key will be shared between Kibana and Elasticsearch.
10. Please note that the kbn1 identifier must match in the ror\_kbn\_authentication and ror\_kbn sections; however, any names can be used for them.

    ```yaml
    readonlyrest:  
     access_control_rules:

     - name: "::KIBANA-SRV::"  
       auth_key: kibana:kibana

     - name: "ADFS Users"  
       ror_kbn_authentication:  
         name: "kbn1"

     ror_kbn:  
     - name: kbn1  
       signature_key: "VEGj@YLLhsAigspnNi2Xsopsqja_nrKUqU__eQW9VQ2!9p!RoeHwc-G.y-MVJtYYcDFCH.e3W2BKcZsoynJaHyjjXyh7kDHjsYKPkczvai-xCzP@Ez3QW23ZBFuReA7kPAqnc6pQ3VeNeFf3sWNoKeJAt_d9J7aFwEvCP2Gb-kQcA8YR*wNWHQuo-jwmmo2Qqpu_Fq3aKFCbNFWUbK@BVwmmKezxn3h687mAkuyhV4.hnfrjVjF-Rphjqmy4.tB8"
    ```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/develop/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/develop/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/develop/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

11. Restart **Elasticsearch** **Windows Service**. This can be done in the **Services** MMC snapin.

## Installing and Configuring Kibana and the ReadonlyREST Enterprise Plugin

### Installing Kibana

1. Locate a recent download of [Kibana](https://artifacts.elastic.co/downloads/kibana/kibana-7.6.2-windows-x86_64.zip), and download the zip package. At the time this article was written, the most recent version was 7.6.2. You may want to check for more updated links as they become available.
2. Extract the Kibana installation. Note that this is a rather large file. If you have trouble with the default Windows zip extractor, you may want to try a tool such as 7-Zip.
3. ![](/files/-ML8MzP5N8GiZa5LnT8O)
4. Move the extracted folder to C:\kibana. This may require you to rename the folder.
5. ![](/files/-ML8MzP6jKvwwrOaL0Qj)
6. Open an administrative command prompt, and navigate to the **Kibana** directory to run the kibana.bat batch file and start **Kibana.**
7. ![](/files/-ML8MzP7285XyaXkgz76)
8. Once Kibana has started, navigate to <http://localhost:5601> to verify that Kibana is functional.
9. ![](/files/-ML8MzP8siJAvZaHsxMR)

### Creating a Self-Signed Certificate for Kibana

It is necessary to make Kibana operate under SSL for AD FS to perform SAML authentication.

1. The easiest way to generate a self-signed certificate using the required format is to use **OpenSSL**. A Windows version of this tool available for download is located [here](https://slproweb.com/products/Win32OpenSSL.html).
2. <img src="/files/-ML8MzP9BVWUmnqhyW7e" alt="" data-size="original">
3. If **Microsoft Visual C++ 2017 Redistributables (64-bit)** is not already installed, click **Yes** to download the installation and run the installer first.
4. Accept the license agreement, and click on **Install.**
5. Back on the OpenSSL installation, click on **I accept the agreement**, then click on **Next.**
6. <img src="/files/-ML8MzPEKUnAMtcYJyrY" alt="" data-size="original">
7. Click **Next** on the **Destination Location** screen.
8. <img src="/files/-ML8MzPFH9eO15zetn3U" alt="" data-size="original">
9. Click **Next** on the **Select Start Menu Folder** screen.
10. <img src="/files/-ML8MzPG6anBhdd6HGkW" alt="" data-size="original">
11. Select **The** **Windows system directory** on the **Additional Tasks** screen, and click **Next.**
12. <img src="/files/-ML8MzPH5a1Zqw4yzJYw" alt="" data-size="original">
13. Click on **Install.**
14. <img src="/files/-ML8MzPI4JtF9KUOH-Pp" alt="" data-size="original">
15. Click on **Finish.**
16. <img src="/files/-ML8MzPJg_JPzswdklXM" alt="" data-size="original">
17. Open an administrative command prompt, and run the following command to create the certificates in the specific X509 PEM format that Kibana requires:
18. "C:\Program Files\OpenSSL-Win64\bin\openssl.exe"

    req -x509 -sha256 -nodes -days 730 -newkey rsa:2048 -keyout localhost-key.pem -out localhost.pem -subj "/C=US/ST=IL/L=Bloomington/O=lc-test/CN=10.0.0.6"
19. Change the subj to one that is more indicative of your installation. Make sure the CN={IP Address} matches the accessible IP of your Elasticsearch/Kibana server.
20. <img src="/files/-ML8MzPK94doTw91VUul" alt="" data-size="original">
21. Locate the newly created pem certificates and copy them to C:\kibana\ssl\_cert.
22. The ssl\_cert directory will need to be created first. For our purposes here, it has been arbitrarily named.
23. <img src="/files/-ML8MzPLOMkk6r0gGfpT" alt="" data-size="original">
24. Restart Kibana by entering **Ctrl-C** in the running command prompt window and then re-running kibana.bat.

### Installing the ReadonlyREST Enterprise Plugin

1. Navigate to the [ReadonlyREST Plugin download page](https://readonlyrest.com/download/) to enter your details. You will get the download link in your email. Making sure to choose the **Enterprise Kibana Plugin** and match it with your Elasticstack version.
2. The email that you receive will contain installation instructions. The link will be time-limited, as shown below.
3. Navigate to C:\kibana\config, and locate the kibana.yml configuration file.
4. Open the kibana.yml file in Notepad and update it with the following details:

```yaml
    elasticsearch.username: kibana  # This field matches the first part (pre-colon) of the auth\_key in the readonlyrest.yml Elasticsearch configuration file.
    elasticsearch.password: kibana # This field matches the second part (post-colon) of the auth\_key in the readonlyrest.yml Elasticsearch configuration file.
    elasticsearch.ssl.verificationMode: true # Set the value to “true” to ignore SSL errors. This is useful when working in a test environment.
    
    server.host: 10.0.0.6 # We need to use a routable address, which, in this case, is the 10.0.0.6 IP of this server.
    server.ssl.enabled: true # This is used to turn on SSL and respond to https.
    server.ssl.certificate: '/etc/kibana/ssl_cert/localhost.pem' # This is the location of the public key certificate.
    server.ssl.key: '/etc/kibana/ssl_cert/localhost-key.pem' # This is the location of the private key for the certificate.
    readonlyrest_kbn:
      logLevel: debug # The value is set to “debug” to enable troubleshooting in the console.
      clearSessionOnEvents: [ login ] # This clears the session on a successful login event.
      auth:
        signature_key: "VEGj@YLLhsAigspnNi2Xsopsqja_nrKUqU__eQW9VQ2!9p!RoeHwc-G.y-MVJtYYcDFCH.e3W2BKcZsoynJaHyjjXyh7kDHjsYKPkczvai-xCzP@Ez3QW23ZBFuReA7kPAqnc6pQ3VeNeFf3sWNoKeJAt_d9J7aFwEvCP2Gb-kQcA8YR*wNWHQuo-jwmmo2Qqpu_Fq3aKFCbNFWUbK@BVwmmKezxn3h687mAkuyhV4.hnfrjVjF-Rphjqmy4.tB8" # This must match the 256-character value in the signature\_key attribute of the readonlyrest.yml Elasticsearch configuration file.
        saml_adfs:
          buttonName: "ADFS SAML SSO" # This is the name of the login button on the login screen of Kibana.
          enabled: true # This enables the SAML SSO configuration.
          type: "saml" #  For AD FS, this must be “saml.”
          issue: "ror" #  This is the unique identifier that was defined in the AD FS Relying Party Trust configuration, in this case, “ror”.
          protocol: "https" # AD FS requires https.
          entryPoint: "https://{AD_FS Server}/adfs/ls" # This is the entry point for AD FS
          logoutUrl: "https://{AD_FS Server}/adfs/ls?wa=wsignout1.0" # This is the logout call to AD FS
          kibanaExternalHost: "10.0.0.6:5601" # This is the address and port without the protocol preceding (i.e., https).
          usernameParameter: "nameID" # This configuration is only doing authentication, and it must match the nameID parameter.
          # disableRequestedAuthnContext: false # This is optional configuration which can fix known `SAML provider returned Responder error: NoAuthnContext` https://github.com/node-saml/passport-saml/issues/226. Allowed value is true/false
          # authnContext: "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/windows" # Name identifier format to request auth context. Allowed value is a string array of strings. Default: `urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport`
          # identifierFormat: null # Name identifier format to request from identity provider. Allowed value is a string. Default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`
```

### Opening the Firewall Port

To allow the AD FS server to talk to Kibana, we need to open the 5601 port on the Kibana server since \[localhost]\([http://localhost\\](http://localhost\)) is not routable.

1. Open the **Windows Firewall with Advanced Security** screen, and add a new rule under **Inbound Rules.** Choose **Port.**
2. Add the specific local port of **5601,** and click **Next.**
3. Select **Allow the connection,** and click **Next.**
4. Choose all profiles (the default), and click **Next.**
5. Name the rule “Kibana,” and click **Finish.**

## Demonstration

1. Navigate to your Kibana URL ([https://10.0.0.6:5601\\](https://10.0.0.6/:5601\)/) using Chrome or Firefox. Do not use IE or the SSO button may not show up.
2. ![](/files/-ML8MzPMeUJYTLJLnFm_)
3. Click on ADFS, the button configured in the kibana.yml file, and log in with one of the created AD users. Use the defined mail attribute on the AD account (an email address).
4. ![](/files/-ML8MzPNHujPRxOJQ4MR)
5. With a successful login, the Kibana screen will appear, and you will see your SAML authenticated user in the lower right corner.
6. ![](/files/-ML8MzPOOQFRKWOhRIh5)

## Conclusion

ReadonlyREST combined with Elasticsearch and Kibana opens a world of advanced authentication and authorization options to you. Though only a basic configuration was outlined here, many more useful configuration options are available. You can find out more information about these advanced configurations in the ReadonlyREST documentation and in the ROR forums.


# Duo Security MFA

This tutorial is a step by step guide for the integration between [DUO](https://duo.com) multi factor authentication provider and [ReadonlyREST Enterprise](https://readonlyrest.com/enterprise).

The multi factor authentication (MFA) provided by DUO is an additional authorization step for the user after they have inserted the correct credentials. This extra step is mediated by the DUO platform and it is either an SMS, a push notification to their app, or a one time password obtained via their app or google authenticator.

For this tutorial you are going to need:

* A valid installation of ReadonlyREST Enterprise (trial, or official) Kibana plugin. If you haven't got one, [get your own trial build here](https://readonlyrest.com/enterprise).
* A valid trial or paid account in DUO website (see [pricing](https://duo.com/pricing), you are going to need the "Remote Access & Single Sign-On (SSO)" feature.

## Duo Access Gateway (DAG) server configuration

The access gateway is a piece of software released by DUO that takes care of integrating on premises service providers like ReadonlyREST SAML with arbitrary identity providers (like LDAP) and the multi factor authentication features offered by DUO platform.

### Installing Duo Gateway

Follow the instructions for Duo Gateway (<https://duo.com/docs/dag-linux>). The gateway is a Docker container and it will need the ports 80 and 443 to be available, so you will probably need a dedicated VM or Bare Metal so that Duo Gateway can properly bind to these ports.

### Configuring Authentication Source

Once the Duo Gateway is installed, open a browser and point to its web interface to configure it. When you configure the authentication sources, be sure to set the correct username attribute. Keep in mind this value because it will be mapped directly to whatever has been configured in the actual Duo.com dashboard, under `Gateway > Applications` (which we are just about to configure).

![Source](/files/013FZBvEAqVQ1bc9Z35o)

### Configure Application in Duo Admin Dashboard

In the Duo Administration dashboard, go to Applications. Click `Protect an Application`, then search for `Generic` and click `Protect this Application` button.

This will create a generic SAML provider. Set these fields:

* Service provider name: A name that refers to your ReadonlyREST Enterprise installation
* EntityID: Set an existing entity name or use the same as Service Provider Name
* Assertion Consumer Service: The SAML Url assertion found in metadata.xml, the url format is `<kibanaExternalHost>/ror_kbn_sso/assert`.

![SP](/files/fdTYB0KxgzoN5k024ow1)

In SAML Response, set NameID to be the same variable name as configured previously in the authentication source. Leave the default values for the remaining settings. Click `Save Application` and scroll up and download the configuration file.

## ReadonlyRest Configuration

To configure SAML, both Kibana and Elasticsearch ROR configuration needs to be edited to enable SAML in Duo Gateway.

### Configuring the Elasticsearch plugin

Open your `readonlyrest.yml` file or login as a local administrator in your ReadonlyREST Enterprise, and add this extra configuration required for SAML authentication.

```
readonlyrest:
  access_control_rules:

#    [... all your regular ACL blocks ...]

    - name: "ReadonlyREST Enterprise Kibana instance #1"
      ror_kbn_authentication:
        name: "kbn1"

# OPTIONAL FOR SECONDARY KIBANA ###
#
#    - name: "ReadonlyREST Enterprise Kibana instance #2"
#      ror_kbn_authentication:
#        name: "kbn2"

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1_(256+chars)" # <- use environmental variables for better security!

# OPTIONAL FOR SECONDARY KIBANA ###
#    - name: kbn2
#      signature_key: "shared_secret_kibana2(256+chars)" # <- use environmental variables for better security!
```

This authentication and authorization connector represents the secure channel (based on JWT tokens) of signed messages necessary for our Enterprise Kibana plugin to securely pass back to ES the username and groups information coming SAML identity provider.

### Configuring the Kibana plugin

Edit $KIBANA\_HOME/conf/kibana.yml configuration and append:

```
readonlyrest_kbn.auth:
  signature_key: “a very long key (more than 256 characters) goes here …..” # the same signing key added above in ES config
  saml:
    enabled: true
    entryPoint: 'https://duo-gateway.xyz/dag/saml2/idp/SSOService.php?spentityid=demo'
    kibanaExternalHost: 'ror-deployment.xyz' # <-- public URL used by the Identity Provider to call back Kibana with the "assertion" message
    usernameParameter: 'nameID'
    groupsParameter: 'memberOf'
    logoutUrl: 'https://duo-gateway.xyz/dag/saml2/idp/SingleLogoutService.php?ReturnTo=https://duo-gateway.xyz/dag/module.php/duosecurity/logout.php'
    decryptionCert: certs/dag.crt
    cert: certs/dag.crt
```

The following fields are mapped to the Duo Gateway Application Metadata:

* entryPoint: LoginUrl for the SAML Generic Application
* usernameParameter: Default SAML Generic Application value is `nameID`
* logoutUrl: a URL that points to the value found in the screen `Metadata > Logout URL`
* decryptionCert: The downloadable certificate in Metadata (absolute path)
* cert: The downloadable certificate in Metadata (absolute path)
* signature\_key: Signing key string for JWT, must match the same key value in elasticsearch ROR config
* kibanaExternalHost: The Kibana (with ReadonlyREST Enterprise) instance public hostname
* protocol: protocol schema (http or https) of the external Kibana host
* issuer: distinctive name of the identity provider (optional)
* decryptionPvk: service provider private key (string value) (optional)

For more advanced configurations and information, please refer to [passport-saml documentation](https://github.com/bergie/passport-saml)

### Elasticsearch index in Kibana ROR Dashboard

Make sure to update signature\_key in ROR Dashboard with the value. Otherwise you will get JWT errors while login with SAML.

## Login with SAML 2FA enabled

Go to ReadonlyREST Login page (<http://ror-deployment.xyz/login>) and click the SAML SSO button. This will redirect to Duo Security Gateway and ask for a two factor code to proceed. Note that the first time it will provision a two factor seed mapped to the user account.

Once Duo authenticates, it will redirect you to the private Kibana session powered by ReadonlyREST Enterprise.

## Logout from SAML from ReadonlyREST Enterprise logout button

Click the Logout button from ROR Dashboard. This will redirect you to Duo Gateway logout completion page. Follow the instructions and close the window.


# OpenID Connect (OIDC) (Enterprise)

External connectors integration

([Enterprise](https://readonlyrest.com/enterprise))

With ReadonlyREST Enterprise, you can integrate with OpenID Connect (OIDC) Single Sign-on identity providers for both authentication and authorization.

Follow the guides to know more.


# Keycloak

OpenID Connect (OIDC) SSO Integration with Keycloak as an identity provider.

This document will guide you through the task of setting up an excellent, open-source identity provider ([KeyCloak](https://www.keycloak.org)) to work as an external authenticator and authorizer system for your ELK stack. The scenario is the usual:

* A centralised, large Elasticsearch cluster
* A Kibana installation
* We want one, centralised multi tenant Elasticsearch + Kibana;

But with some more enterprise requirements:

* Users need to be able to change their passwords independently
* Users need to verify their emails
* Group managers need to be able to add, remove, block (only) their users.
* [Multi factor authentication (MFA)](https://www.keycloak.org/docs/latest/server_admin/#one-time-password-otp-policies) is a requirement.

## What is Keycloak

Keycloak is an advanced authentication server that lets user administer their credentials, and speaks many authentication protocols, Including OpenID Connect (OIDC) SSO.

### Setup KeyCloak

This tutorial was created using KeyCloak 14.0.0.

1. Download the Keycloak from their [official website](https://www.keycloak.org/archive/downloads-14.0.0.html). This guide will use [keycloak docker image](https://hub.docker.com/r/jboss/keycloak/)
2. Run Keycloak: run docker run -e KEYCLOAK\_USER= -e KEYCLOAK\_PASSWORD= jboss/keycloak where USERNAME and PASSWORD are credentials for your admin account
3. log in as admin
4. Follow the explanation below, or (if your KC version is the same or close enough to this) use the import function to load this [configuration file](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/examples/keycloak_ror_OIDC.json)

If you imported the JSON file, you should have a "ror" realm, and an OpenID Connect (OIDC) client called "ror\_oidc" (keep this ID or change the "clientID" setting in kibana.yml). Please now select "ror" realm, navigate to "clients", click "ror\_oidc" client and double-check everything matches with your use case, as this guide assumes both Kibana, Elasticsearch, and Keycloak are running on "localhost".

### Configure Keycloak to work with ROR

First, we want to create a new dedicated "ror" realm, so we don't interfere with any other use of this Keycloak installation.

![keycloak\_screenshot](/files/kakO1oxYBDFIwdq2Ee7P)

Then, let's create an OpenId Connect client for this realm:

![keycloak\_screenshot](/files/86tEow5DUM9hqQa9yXdI)

Then, configure the OpenID Connect (OIDC) client

![keycloak\_screenshot](/files/1QZ7nFjGKGQHR6y0Dg0I)

**kibana.yml** (without ssl enabled)

```yaml
# More on how to enable SSL on the official documentation of Kibana
server.ssl.enabled: false

elasticsearch:
  hosts: ["https://localhost:9200"] # <-- our Elasticsearch responds to https
  ssl.verificationMode: none
  username: kibana
  password: kibana

readonlyrest_kbn:
  logLevel: debug
  auth:
    # this secret string has to be longer than 256 chars, use environmental variables to fill it in maybe.
    signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"
    oidc_kc:
      buttonName: "KeyCloak OpenID"
      type: "oidc"
      protocol: "http"
      issuer: 'http://localhost:8080/auth/realms/ror' <-- Get it from OpenID Endpoint Configuration
      authorizationURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/auth' <-- Value from OpenID Endpoint Configuration
      tokenURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/token' <-- Value from OpenID Endpoint Configuration
      userInfoURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/userinfo' <-- Value from OpenID Endpoint Configuration
      clientID: 'ror_oidc' <-- Declared in a realm Client Scopes
      clientSecret: '35d0c1db-a2b7-42d9-9a43-bea88c6535e6'  <-- Declared in a realm ror_oidc (our created client) Credentials tab
      scope: 'openid profile roles role_list email' <-- Declared in a realm Client Scopes
      usernameParameter: 'preferred_username'
      groupsParameter: 'groups'
      kibanaExternalHost: 'localhost:5601'
      logoutUrl: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/logout' <-- Value from OpenID Endpoint Configuration
      jwksURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/certs' <-- Value from OpenID Endpoint Configuration
      # tokenEndpointAuthMethod: 'client_secret_post' <-- Optional value, the way the auth information will be send to the OIDC provider. Possible values 'client_secret_post' | ''client_secret_basic'
      # proxyURL: 'https://localhost:6200' <-- Optional value. Your proxy server URL
```

To verify all OpenID Endpoint Configuration-based, you can open OpenID Endpoint Configuration page in the kibana realm

![keycloak\_screenshot](/files/phZjRLG8V8vqVQEBsxJ8)

To provide clientSecret value, you need to open ror\_oidc client (or your custom client name)

![keycloak\_screenshot](/files/weS9dES02i1zoyrCNSum)

### Setup Elasticsearch with ReadonlyREST

Our elasticsearch can be run with or without SSL. To make it available on HTTPS (more detailed info in our [documentation](/develop/elasticsearch#encryption)).

Then write in **readonlyrest.yml**

```yaml
readonlyrest:

    audit:
      enabled: true
      outputs: 
      - type: index

    access_control_rules:
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana
      verbosity: error

    - name: "ReadonlyREST Enterprise instance #1"
      kibana:
        access: ro
        index: ".kibana_sso"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      # It has to be the same string as we declared in kibana.yml.
      signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/develop/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/develop/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/develop/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)


# Impersonation (Enterprise)

Impersonation

([Enterprise](https://readonlyrest.com/enterprise))

According to [Wikipedia](https://en.wikipedia.org/wiki/Impersonator):

> An impersonator is someone who imitates or copies the behavior or actions of another.

So, an impersonation can be understood as imitating behaviors or actions. In the context of ReadonlyREST: one user could imitate an action of another user. Why would we want it? Let's suppose the first user is an admin, who has just configured access for a new user. They would like to know if the rule(s) are configured correctly. And here comes the impersonation feature. The admin can impersonate a given user in Kibana and see what the user would see if they logged in themselves.

ROR plugins support impersonation and provide UI for configuring the cluster before using it. Visit the [impersonation details page](/develop/kibana/impersonation) to know more.


# Creating Test Settings

Creating Test Settings

For impersonation to work, some valid Test Settings should be created and saved. It's important that the main ROR setting will be unaffected, so you, as an admin/user don't need to worry that you will break something. Here is how to write test settings:

1. Open the ROR menu
2. Click the Edit security settings button

   ![Test settings ror menu](/files/jwhdKl4fgZe88qgih96s)
3. Go into the Test settings tab
4. You can set the "time to live" (TTL), which is a time interval after which the test settings will be automatically deactivated and impersonation session will also abruptly exit
5. You can load current settings as test settings
6. You can deactivate settings manually
7. You can save test settings as settings

   ![test settings tab](/files/B7wOOrFRlS8iPKxlV7cY)

Read more about [configuring impersonation in the ROR settings](/develop/kibana/impersonation#creating-rors-test-settings).


# Defining external services mock configurations

Defining external services mock configurations

External services mock is used to simulate the response of existing authentication or authorization service like LDAP. You don't need to create a user account for configuration testing. You will only need to define users (and their associated groups) that would normally be returned by the external services, listed in test settings.

![Auth mock](/files/vlK6oyvi4JrC8d4o3OmE)

After clicking add/edit user buttons (1), you will see a dialog with an option to add (2) or remove (3) user from an external service mock

![Add/edit external mock service](/files/SA20Q82OMYr9QgNic7eL)


# Impersonating users

Impersonating users

1. Open the ROR menu
2. Click the Edit security settings button

   ![Impersonate ror menu](/files/jwhdKl4fgZe88qgih96s)
3. Go into the Impersonate tab
4. You can free type impersonate. This button is available only in the situation when in some cases, the system is not able to receive all usernames. In this case, to impersonate, you need to type impersonating username manually.
5. You can add/edit user in a specific external auth mock service
6. You can impersonate a user and imitate his behavior and actions

   ![Impersonate tab](/files/ip0mbdR57wKoHUs9HDUw)
7. When an impersonation session is started correctly, the "impersonating" will be visible in the ROR menu as shown in the picture.
8. Click the Finish impersonation button to stop impersonation and go back into Impersonate tab

   ![Impersonate user](/files/EqK4dXsMcFTPclaGkl6N)


# ROR cluster with Elastic Cloud integration

ROR-based cluster with remote X-Pack Security cluster on Elastic Cloud integration

ReadonlyREST plugin cannot be installed on Elastic Cloud. But we can still take advantage of ROR's features with a little, smart trick - [the remote cluster](https://www.elastic.co/guide/en/elasticsearch/reference/current/remote-clusters.html) Elasticsearch feature. A self-managed ROR-based cluster can access an Elastic Cloud cluster when the latter is configured as a remote cluster in the first one.

## Solution Architecture

![Solution architecture](/files/n4d01Awosc2xUSKJWW29)

The two clusters will communicate at a transport level. The communication will be secured by two-way SSL. Because both clusters have to be configured to trust each other, the initial configuration procedure requires attention. But we prepared a demo that provides an interactive guide to properly set up the clusters automatically. Moreover, details of the setup are described below. Let's start!

## Configuration

Depending on what you need now, you may be interested in either:

* [Quick Start using our docker-based Playground](/develop/examples/elastic-cloud-cluster-integration/playgroud)
* [Detailed explanation on how to set up the solution](/develop/examples/elastic-cloud-cluster-integration/details)

## Testing

You can test the setup using Kibana sample web logs. Let's see how to do it in a few steps:

1. Open your browser and go to your Elastic Cloud deployment Kibana and add "Sample web logs".
2. In a new browser tab, open your local ROR cluster Kibana (`http://localhost:15601/`) and log in as admin (`admin:admin`).
3. Pick `Stack Management` in the Kibana menu, go to `Data views`, and click `Create data view` to create the data view to explore the sample logs from the Elastic cloud cluster.

   ![Creating data view 1](/files/B9j5SJU2vPU6XBkJyPtf)
4. Fill out the form to create a data view:

   a) pick `Name` (it doesn't matter what you enter here) b) enter index pattern `escloud:kibana*` c) one index should be matched: `escloud:kibana_sample_logs` d) click `Save data view to Kibana`

   ![Creating data view 2](/files/3GKiHSMo26dJWmYHbHSL)
5. Pick `Discover` in the Kibana menu. You should see the data. It's great, but currently, you are logged as `admin` who has access to all indices. Let's try with a different user. Click `Log out`.

   ![Discover - admin](/files/riYbFZhqNGmFkMqYKoSJ)
6. Let's log in as `user1` (`user1:test`). This user has RO access and should be able to see `escloud:kibana_sample*` indices (check `readonlyrest.yml` or ROR's settings editor while being logged as `admin`). Go to `Discover` in the Kibana menu and check if you see all the logs from the Elastic Cloud cluster.

   ![Discover - user1](/files/riYbFZhqNGmFkMqYKoSJ)
7. As you saw, the cross-cluster search and Kibana integration works well :) This is the basic setup and the simple use case. Now, you can play with it and try to do something more complicated.


# Docker-based playground

Docker-based playground

This document is a step-by-step guide on how to bootstrap a playground with a local ROR cluster in docker (one Elasticsearch node and one Kibana node) and connecting it to a real Elastic Cloud deployment using the "Trusted deployment" feature in Elastic Cloud.

This guide requires minimal knowledge because most of the process is automated. This interactive script will help you to do it quickly. As a result of the script, you will have a working local ROR cluster connected to the remote Elastic Cloud cluster.

### Before you start

1. Linux or Mac OS machine (Windows is untested)
2. Account in <https://cloud.elastic.co/> and valid deployment (a free trial is OK)
3. [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) and [Git](https://git-scm.com/) installed

### Running interactive script

1. Clone `ror-sandbox` repository:

   ```bash
   git clone git@github.com:beshu-tech/ror-sandbox.git
   cd ror-sandbox/ror-cluster-elastic-cloud-demo/
   ```
2. Run the interactive script:

   ```bash
   ./run.sh
   ```

   ![Intro](/files/3WbDjccfqbU3tIyrfBFF)
3. After hitting enter, you will be asked to download the [CA file](https://en.wikipedia.org/wiki/Certificate_authority) with trusted Elastic Cloud deployment certificates:

   ![Elastic Cloud CA Cert](/files/IvQjFoPJP3qFAQLtPRNt)
4. Let's assume the CA file was downloaded and saved in `/tmp` folder. Let's enter the location of the file and hit enter:

   ![Elastic Cloud CA Cert location](/files/AXKKGpb1lcFIF0iG35E1)

   The interactive script will use the CA file and generate certificates of the local cluster and its CA too. Let's hit enter to continue ...

   ![ROR cluster certs generation](/files/cEnxYeD7n0GCce65eycM)

   As we can see CA file `ca.crt` of the ROR cluster was created in `/tmp/ror-sandbox/ror-cluster-elastic-cloud-demi/certs/ca` folder.
5. Now, the ROR cluster CA file will be used to add a trusted deployment in Elastic Cloud:

   ![Adding trust deployment instructions](/files/RVSGrtyiDc3O4Byf8xlJ)
6. The next step is to configure the Elastic Cloud remote cluster settings. Our script will ask you to provide "Proxy address" and "Server Name". Both can be found in the Elastic Cloud console.

   ![Remote cluster settings](/files/0lmTCcvYRz43kACkB9gY)
7. This is all we need to do in the Elastic Cloud console. Now, we can pick Elasticsearch, Kibana and ROR versions:

   ![Picking versions](/files/YnG0zJLTXtxUd9h8t9eT)
8. Now, the script will create the docker-compose environment with one node of Elasticsearch with ROR installed and connected to the remote Elastic Cloud cluster. Moreover, one node of Kibana with ROR too will be visible `http://localhost:15601`. It's time to test it now :)

   ![Summary](/files/Z4TlMlJrk2LIiaj5oqY2)


# Configuration details

Detailed configuration

This is a detailed description of how to configure two Elasticsearch clusters:

1. One in Elastic Cloud (managed Elasticsearch from Elastic) containing the bulk of the data
2. One self-hosted with ReadonlyREST (for enterprise-level access control and authentication)

The objective is to get the two connected using the transport protocol over SSL, so that we can attach a Kibana (with ROR Enterprise installed) to the cluster #2, and from there query the data in cluster #1 using the [Cross Cluster Search (CCS)](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-cross-cluster-search.html) feature.

## Two-way SSL configuration

The local, self-managed ROR cluster connects with the remote Elastic Cloud cluster using the Elasticsearch transport interface. The transport uses two-way SSL to authorize nodes of clusters.

To do that, we need to

1. Generate CA certificates of nodes of the local cluster (using the CA certificates of the Elastic cloud cluster)
2. Use them to add a trusted environment in the Elastic Cloud console
3. Configure the internode SSL and remote cluster settings in `elasticsearch.yml`

The CA certificates of the Elastic Cloud cluster nodes can be downloaded from the security settings of the Elastic Cloud deployment (see [screenshots](/develop/examples/elastic-cloud-cluster-integration/playgroud#running-interactive-script)).

### Generating ROR cluster CA and nodes' certificates

To generate CA certificates in the self-hosted cluster, we will use the `elasticsearch-certutil` which can be found in the `bin` folder in your Elasticsearch location (eg. `/usr/share/elasticsearch/bin/`).

Our working directory structure will look like that:

```bash
/tmp/certs# tree
.
|-- input
`-- output

2 directories, 0 files
```

Let's move the downloaded Elastic Cloud CA certificates file to `/tmp/certs/input` as `elastic-cloud-ca.cer`:

```bash
/tmp/certs# tree
.
|-- input
|   `-- elastic-cloud-ca.cer
`-- output

2 directories, 1 file
```

Now, let's create the `instances.yml` file in the `/tmp/certs/input` directory where we will define all nodes and their properties (see [Elastic instruction for details](https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html#certutil-silent)) eg.

```yaml
instances:
  - name: "ror-es01" #{node name}
    cn:
      - "ror-es01.node.ror-cluster.ror-test" #{node name}.node.{cluster name}.{scope} (the scope will be useful during configuration of the trusted environments in Elastic Cloud deployment security settings)
    dns:
      - "localhost"
    ip:
      - "127.0.0.1"
```

Great, we have all the ingredients to generate the CA certificates of the nodes in our local ROR cluster:

```bash
mkdir -p /tmp/certs/output/ca
bin/elasticsearch-certutil ca --out /tmp/certs/output/ca/ca.p12 --pass mycapassword 
```

Details about the usage of the `elasticsearch-certutil` tool you will find in [Elastic documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html). We have the CA certificate in `p12` format. We need to convert it to `X509`. It can be done using `openssl`:

```bash
openssl pkcs12 -in /tmp/certs/output/ca/ca.p12 -out /tmp/certs/output/ca/ca.crt -nokeys --password pass:mypassword 
```

Let's use our CA and generate certificates for the ROR cluster nodes:

```bash
bin/elasticsearch-certutil cert --silent --in /tmp/certs/input/instances.yml --out /tmp/certs/output/ror-cluster.zip --ca /tmp/certs/output/ca/ca.p12 --ca-pass mypassword --pass mypassword
unzip /tmp/certs/output/ror-cluster.zip -d /tmp/certs/output/ror-cluster
```

The last thing, we need to do, is to import Elastic Cloud CA to the ROR node's keystore:

```bash
jdk/bin/keytool -importcert -noprompt -file /tmp/certs/input/elastic-cloud-ca.cer -alias 'elastic-cloud' -keystore /tmp/certs/output/ror-cluster/ror-es01/ror-es01.p12 -storepass mypassword
```

This is it. The structure of the `certs` folder should look like this:

```bash
/usr/share/elasticsearch# tree /tmp/certs
/tmp/certs
|-- input
|   |-- elastic-cloud-ca.cer
|   `-- instances.yml
`-- output
    |-- ca
    |   |-- ca.crt
    |   `-- ca.p12
    |-- ror-cluster
    |   `-- ror-es01
    |       `-- ror-es01.p12
    `-- ror-cluster.zip

5 directories, 6 files
```

### Adding a new trusted environment in the Elastic Cloud deployment

In Elastic Cloud deployment security settings, there is a Remote Connections section, where you can add a new trusted environment (see [screenshots](/develop/examples/elastic-cloud-cluster-integration/playgroud#running-interactive-script)). The new trusted environment will be the self-managed cluster. To complete the process we need to:

1. upload the ROR cluster CA (`/tmp/certs/output/ca/ca.crt`)
2. select trusted cluster by:
   * ticking `Trust clusters whose Common Name follows the Elastic pattern`
   * entering `Scope ID` (in out example, it was `ror-test`)

* marking that we trust "All deployments" (or specific if you wish)

3. give a name of the environment (pick anything you want)
4. click `Create trust`

And that's it! Now ROR cluster should trust the Elastic Cloud cluster and vice versa.

## The minimal configuration of Elasticsearch & ReadonlyREST settings

`elasticsearch.yml` should look like this:

```yaml
cluster.name: ror-cluster # the same value used in `instances.yml`
node.name: ror-es01  # the same value used in `instances.yml`
network.host: 0.0.0.0

transport.type: ror_ssl_internode
readonlyrest: # we will put in in `elasticsearch.yml` because each node should have different certificate
  ssl_internode: 
    enable: true # we have to enable internode SSL because it's required to communicate with Elastic Cloud remote cluster
    keystore_file: "ror-cluster/ror-es01/ror-es01.p12"
    keystore_pass: "mypassword"
    truststore_file: "ror-cluster/ror-es01/ror-es01.p12"
    truststore_pass: "mypassword"
    key_pass: "mypassword"
    certificate_verification: true # it means that certificates will be validated
    client_authentication: true # ES with ROR acting as a client is going to authenticate itself

cluster.remote.escloud.mode: proxy # `escloud` is a remote cluster name - so to access `index1` on this remote cluster from the local cluster, we should refer it like that: `escloud:index1` (see `readonlyrest.yml` below) 
cluster.remote.escloud.proxy_address: '${ES_CLOUD_PROXY_ADDRESS}' # taken from Elastic Cloud deployment security settings, "Remote cluster parameters" section
cluster.remote.escloud.server_name: '${ES_CLOUD_SERVER_NAME}' # taken from Elastic Cloud deployment security settings, "Remote cluster parameters" section
```

and the `readonlyrest.yml` like this:

```yaml
readonlyrest:

  access_control_rules:

    - name: "KIBANA" # for Kibana 
      type: allow
      auth_key: kibana:kibana

    - name: "ADMIN" # admin user - can change ROR settings
      type: allow
      kibana:
        access: admin
      auth_key: admin:admin
      
    - name: "User 1" # user1 can read remote Elastic Cloud cluster (escloud) indices matching pattern kibana_sample*
      type: allow
      kibana:
        access: ro
      auth_key: "user1:test"
      indices: ["escloud:kibana_sample*"]
```

Kibana configuration doesn't contain anything special.

<details>

<summary>Expand it if you really need to see how it looks like</summary>

\\

`kibana.yml`:

```yaml
server.name: kibana-ror
server.host: 0.0.0.0
elasticsearch.hosts: [ "${ES_REST_API_URL}" ]
monitoring.ui.container.elasticsearch.enabled: true

elasticsearch.username: kibana
elasticsearch.password: kibana

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

</details>


# Custom middleware (Enterprise)

Custom middleware

([Enterprise](https://readonlyrest.com/enterprise))

Sometimes, Enterprise users might need more flexibility and customize the plugin behavior to adjust the product to the business needs. There are two options to declare the custom middleware:

* JS file: `readonlyrest_kbn.custom_middleware_inject_file: '/path/to/your/file.js'` // You can also use a relative path here. It's relative to the Kibana root folder
* Inline: `readonlyrest_kbn.custom_middleware_inject: 'function test(req, res, next) {logger.debug("custom middleware called"); next()}'`


# Enriching the metadata

Enriching the metadata

The metadata is the user-specific data available after the Kibana user successfully logs in. Thanks to the custom middleware, you can enrich metadata and use them in the Kibana custom js file. For example to load a custom logo to the Kibana you can:

1. Declare `readonlyrest_kbn.custom_middleware_inject_file: 'path/to/custom_middleware_inject_file.js'` in the kibana.yml and declare `custom_middleware_inject_file.js`

```ts
async function customMiddleware(req, res, next) {
  const rorRequest = req.rorRequest;
  const userRequest = rorRequest && (await req.rorRequest.getUserRequestIdentity());
  const metadata = userRequest && userRequest.metadata;

  if (metadata && metadata.username === 'admin') {
    req.rorRequest.enrichIdentitySessionMetadata({
      newLogo:
        'PHN2ZyBpZD0ic3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0MDAiIGhlaWdodD0iMzYzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJkaXNwbGF5OiBibG9jazsiPgogICAgPGcgaWQ9InN2Z2ciPgogICAgICAgIDxwYXRoIGlkPSJwYXRoMCIKICAgICAgICAgICAgICBkPSJNMTIyLjgzNiAxMS42MTEgQyAxMTMuNTg2IDE0LjQwNCwxMDguMDAyIDkzLjQxNiwxMDkuOTgyIDE5My41MDAgQyAxMTEuNTE1IDI3MC45ODAsMTExLjI4OCAzMDAuNzQzLDEwOS4wODUgMzExLjI4MyBDIDEwNS4yNjkgMzI5LjU0NCwxMDAuNDI2IDMyNy4zNDAsOTYuMzczIDMwNS41MDAgQyA5NC44ODcgMjk3LjQ4OSw5NC42MzEgMjgxLjI3Nyw5NC4wNDMgMTU4LjAwMCBDIDkzLjM1NCAxMy40NTcsOTMuNDQzIDE2LjAwMCw4OS4wNjggMTYuMDAwIEMgNjcuMDkxIDE2LjAwMCwzMC42ODMgNDQuNjgwLDE5Ljc2MCA3MC41OTUgQyAxMS43NDggODkuNjA3LDkuMjk2IDEzMi42NTcsMTQuNzI1IDE1OS4wMDAgQyAzMC4xNjkgMjMzLjkzOCw1NC45MjIgMjg4LjYxNiw4Ny42MzYgMzIwLjA1OCBDIDEyMi4xNjAgMzUzLjIzOCwxNzAuOTYxIDM1Ny45MjAsMjMwLjAwMCAzMzMuNzE1IEMgMjQ3LjY5OSAzMjYuNDU5LDI0OC4yNjEgMzI1LjA5OCwyNDIuMjg0IDMwNC4wMDAgQyAyMjkuNzc2IDI1OS44NDYsMjE3LjE2OCAyMzkuMDE4LDE3Ni42MDQgMTk1LjUwMCBDIDE1My43NDYgMTcwLjk3OCwxNDkuMzkxIDE2NC4zMzQsMTQ1LjA4NiAxNDcuNDE5IEMgMTM3LjE3NyAxMTYuMzQ3LDE0MS4zMjcgOTIuMzg0LDE2My41MTcgNDEuMDAwIEMgMTc0LjkwNiAxNC42MjYsMTc0Ljg4OCAxNC40NjksMTYwLjM2OCAxMy4wMTUgQyAxNDguODIxIDExLjg2MCwxMjQuOTY4IDEwLjk2NywxMjIuODM2IDExLjYxMSBNMTk2LjA2MSAyMi4xNDEgQyAxOTUuNTE0IDIzLjQzOCwxOTMuMDU1IDI5LjkwMCwxOTAuNTk3IDM2LjUwMCBDIDE4OC4xNDAgNDMuMTAwLDE4My4yMTAgNTUuNTg2LDE3OS42NDQgNjQuMjQ3IEMgMTUzLjU3MiAxMjcuNTU5LDE1NC4wMjUgMTMxLjI3NCwxOTMuMDM2IDE3NC4wMDAgQyAyMjYuMjg4IDIxMC40MTksMjQ5Ljk2OSAyNTIuOTM2LDI1OC40ODEgMjkxLjUwMCBDIDI2MC44NTIgMzAyLjI0MywyNjIuNzkyIDMwOS4yMDksMjYzLjg3OCAzMTAuODc2IEMgMjY0Ljg4NiAzMTIuNDI1LDI3MC4wMzMgMzA5LjI5NCwyNzguNzI0IDMwMS44NDcgQyAyODEuMzUxIDI5OS41OTYsMjg2LjQyNSAyOTUuMjQ3LDI5MC4wMDAgMjkyLjE4MiBDIDMzMy40NTYgMjU0LjkzMCwzNzQuMTM0IDIwMS45NzMsMzgxLjkzMSAxNzIuNTAwIEMgMzkzLjczMiAxMjcuODkwLDMzNi4yMDMgNjguNTg2LDI0OS4wMDAgMzUuNDY3IEMgMjQ3LjA3NSAzNC43MzYsMjQzLjAyNSAzMy4xODMsMjQwLjAwMCAzMi4wMTUgQyAyMTMuNDEwIDIxLjc0OCwxOTcuNzE1IDE4LjIyMSwxOTYuMDYxIDIyLjE0MSAiCiAgICAgICAgICAgICAgc3Ryb2tlPSJub25lIiBmaWxsPSIjMDBiZmIyIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjwvcGF0aD4KICAgIDwvZz4KPC9zdmc+Cg=='
    });
  }

  return next();
}
```

In this example, thanks to the enrichIdentitySessionMetadata method we can pass new logo custom metadata when the logged-in user username is 'admin'. It mustn't be a static value, you can ask for external service for the logo:

```ts
if (metadata && metadata.username === 'admin') {
    const response = fetch(<EXTERNAL_SERVICE_URL>);
    req.rorRequest.enrichIdentitySessionMetadata({
      newLogo: await response.json().newLogo
    });
  }
```

Now, enriched metadata will be available in the custom kibana js script, where you can perform client-based operations like logo replacement.

**⚠️IMPORTANT** Custom middleware must return `next()` function, to not block the request

2. To replace the logo, we need to declare the custom Kibana JS file `readonlyrest_kbn.kibana_custom_js_inject_file: '/path/to/custom_kibana.js'`

```js
const logoHeader = document.querySelector('.euiHeaderLogo');

if (window.ROR_METADATA.newLogo) {
  Array.from(logoHeader.childNodes).forEach(node => {
    node.style.display = 'none';
  });

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        const customLogo = document.querySelector('#customLogo');

        const createCustomLogo = () => {
          const img = document.createElement('img');
          img.src = `data:image/svg+xml;base64,${window.ROR_METADATA.newLogo}`;
          img.style.width = '32px';
          img.style.height = '32px';
          img.id = 'customLogo';
          logoHeader.appendChild(img);
        };

        const hideAllLogoElements = () => {
          Array.from(logoHeader.childNodes).forEach(node => {
            node.style.display = 'none';
          });
        };

        const handleInit = () => {
          hideAllLogoElements();
          createCustomLogo();
        };

        if (customLogo) {
          const displayCustomLogo = () => {
            customLogo.style.display = 'block';
          };
          const hideCustomLogo = () => {
            customLogo.style.display = 'none';
          };
          if (node.role === 'progressbar') {
            hideCustomLogo();
          }

          if (node.role === 'img') {
            const hideDefaultLogo = () => {
              node.style.display = 'none';
            };

            hideDefaultLogo();
            displayCustomLogo();
          }
        }

        if (node.dataset.type === 'logoElastic' && !customLogo) {
          handleInit();
        }
      });
    });
  });

  observer.observe(logoHeader, { childList: true });
}
```

All session metadata will be available via `window.ROR_METADATA` property. To get your custom logo, just use `window.ROR_METADATA.newLogo` value. In the example above, after login in as a user with username `admin` you will see a custom logo. The whole example is a little complex but seems, Kibana logo is also a loading indicator, we need to detect the loading state, replace the logo with a spinner, and after the loading, back the custom logo again.


# Reject machine-to-machine traffic using custom metadata ACL rules

Reject machine-to-machine traffic using custom metadata ACL rules

We can also reject the specific request for example based on the custom metadata

1. Define ACL in your `readonlyrest.yml` file

```yaml
  - name: ADMIN_GRP
    groups_any_of: [ administrators ]
    kibana:
       access: admin
       index: '.kibana_@{acl:current_group}'
       metadata:
          rejectBasicAuth: true
```

2. Declare custom Kibana JS file `readonlyrest_kbn.kibana_custom_js_inject_file: '/path/to/custom_kibana.js'`. it's injected at the end of the HTML Body tag of the Kibana UI frontend code.

```js
async function customMiddleware(req, res, next) {
  const rorRequest = req.rorRequest;
  const userRequest = rorRequest && (await req.rorRequest.getUserRequestIdentity());
  const metadata = userRequest && userRequest.metadata;

  const authorizationHeaders = rorRequest && (await rorRequest.getIdentitySessionHeaders());

  const headerAuth = authorizationHeaders && authorizationHeaders.get('authorization');
  const isBasicAuth = headerAuth && headerAuth.includes('Basic');

  if (metadata && metadata.customMetadata && metadata.customMetadata.rejectBasicAuth && isBasicAuth) {
    return res.status(401).json({ message: 'Machine to machine communication is not allowed' });
  }

  return next();
}

```

You can pass any custom metadata and based on it accepts or reject the specific request

**⚠️IMPORTANT** Custom middleware must return `next()` function, to not block the request


# Reordering available tenancies

Reordering available tenancies

We can change the default tenancy, and the display ordering of the tenancies in the ROR menu by providing the `defaultGroup` query parameter in the HTTP request submitted by the login form, and change the order of `availableGroups` thanks to the `enrichIdentitySessionMetadata` method.

1. Declare `readonlyrest_kbn.custom_middleware_inject_file: 'path/to/custom_middleware_inject_file.js'` in the kibana.yml and declare `custom_middleware_inject_file.js`

```js
async function customMiddleware(req, res, next) {
    const rorRequest = req.rorRequest;
    const userRequest = rorRequest && (await req.rorRequest.getUserRequestIdentity());
    const metadata = userRequest && userRequest.metadata;
    const defaultGroup = 'infosec';
    const X_FORWARDED_USER = 'x-forwarded-user';
    
    if (rorRequest.getPath() === '/login' && rorRequest.getMethod() === 'post') {
        // For the login form
        if (rorRequest.getBody().username === 'admin') {
            rorRequest.setQuery('defaultGroup', defaultGroup);
        }

        // For the SAML/OIDC login
        const token = rorRequest.getBody().conn_svc_transient_jwt;
        if (token) {
            const parsedJWT = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());

            if (parsedJWT.user === 'admin') {
                rorRequest.setQuery('defaultGroup', defaultGroup);
            }
        }
    }

    // For the Proxy authorization
    if (!metadata && req.headers[X_FORWARDED_USER]) {
        if (req.headers[X_FORWARDED_USER] === 'admin') {
            rorRequest.setQuery('defaultGroup', defaultGroup);
        }
    }

    if (metadata && rorRequest.getPath() === '/pkp/api/info') {
        const availableGroups = metadata.availableGroups;
        if (availableGroups.some(availableGroup => availableGroup.id === defaultGroup)) {
            const reorderedGroups = [...availableGroups].sort((a, b) =>
                a.id === defaultGroup ? -1 : b.id === defaultGroup ? 1 : 0
            );

            rorRequest.enrichIdentitySessionMetadata({ availableGroups: reorderedGroups });
        }
    }

    return next();
}
```

In this example, before the login to the Kibana, when the username is equal 'admin', we add default tenant `rorRequest.setQuery('defaultGroup', defaultGroup);` which means, that it will be the first tenant opened after the login. During the active Kibana session, we will also change the order of tenants displayed in the ROR menu and our default tenant will be the first on the list.

**⚠️IMPORTANT** Custom middleware must return `next()` function, to not block the request


# Available rorRequest API

Available rorRequest API

You can access the rorRequest API via `req.rorRequest` in your custom middleware. The available options are:

| Property name                                                               | Return value type                                                                                                                                        | Example return value                                                           | Description                                                                |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| getCookies()                                                                | Record\<string, string>                                                                                                                                  | `{ 'session': 'abc123' }`                                                      | Get all cookies from the request                                           |
| getMethod()                                                                 | Method                                                                                                                                                   | `'GET'`                                                                        | Get the HTTP method of the request                                         |
| getPath()                                                                   | string                                                                                                                                                   | `'/api/v1/users'`                                                              | Get the path of the request                                                |
| getUrl()                                                                    | string                                                                                                                                                   | `'/api/v1/users?foo=bar'`                                                      | Get the full URL including query string                                    |
| getBody()                                                                   | Body                                                                                                                                                     | `{ username: 'john' }`                                                         | Get the request body                                                       |
| getParams()                                                                 | ParamsDictionary                                                                                                                                         | `{ id: '123' }`                                                                | Get route parameters                                                       |
| getQueries()                                                                | ParsedQs                                                                                                                                                 | `{ page: '1' }`                                                                | Get query string parameters                                                |
| setQuery(key: string, value: string)                                        | void                                                                                                                                                     | -                                                                              | Set a query parameter on the request                                       |
| getOriginAddress()                                                          | string or undefined                                                                                                                                      | `'192.168.1.1'`                                                                | Get the origin IP address of the request                                   |
| getHeaders()                                                                | Record\<string, string>                                                                                                                                  | `{ host: 'localhost', authorization: 'Basic ...' }`                            | Get all request headers                                                    |
| isCookiePresent(cookieName: string)                                         | boolean                                                                                                                                                  | `true`                                                                         | Check if a specific cookie is present in the request                       |
| getIdentitySessionHeaders()                                                 | Promise\<Map\<string, string>>                                                                                                                           | `Map(2) {'authorization' => 'Basic BWRtaW46ZGV2', 'cookie' => 'cookie value'}` | Get the headers used during authorization                                  |
| getWhitelistedHeaders()                                                     | Promise\<Map\<string, string>>                                                                                                                           | `Map(1) {'x-custom-header' => 'value'}`                                        | Get whitelisted headers from the session                                   |
| getSid()                                                                    | Promise\<SID or null>                                                                                                                                    | `'a5442490-45ee-4a60-a9a1-e62989db3ab1'`                                       | Get the session ID from the request                                        |
| isAuthenticated(input?: { sid?: SID })                                      | Promise\<boolean>                                                                                                                                        | `true`                                                                         | Check if the session is authenticated                                      |
| getUserRequestIdentity(input?: { sid?: SID; predefinedTenancyId?: string }) | Promise<[UserRequestIdentity](https://github.com/beshu-tech/readonlyrest-docs/tree/develop/examples/custom-middleware/user-request-identity.md) or null> | Check User request identity section                                            | Get the user request identity (returns `null` if not authenticated)        |
| ensuredIdentityAvailability()                                               | Promise<{ ok: true } or { ok: false; reason: string }>                                                                                                   | `{ ok: true }`                                                                 | Check if identity is available without throwing; returns reason on failure |
| enrichIdentitySessionMetadata(customMetadata: Record\<string, unknown>)     | void                                                                                                                                                     | -                                                                              | Enrich existing user session with additional custom metadata               |
| lastSessionActivityDate()                                                   | Promise\<Date or undefined>                                                                                                                              | `2023-03-23T19:50:37.932Z`                                                     | Date of the last session activity; used in the context of session timeout  |
| extractHiddenAppsNames()                                                    | Promise\<string\[]>                                                                                                                                      | `[ 'Enterprise Search, Overview', 'Observability' ]`                           | List of all hidden apps for the current user                               |
| getTenancyId(sid: SID or undefined)                                         | Promise\<string or undefined>                                                                                                                            | `'my-tenant'`                                                                  | Get the tenancy ID associated with the given session ID                    |

You also have access to the standard [Express.js](https://expressjs.com) [request](https://expressjs.com/en/api.html#req) and [response](https://expressjs.com/en/api.html#res) objects


# Secure Logstash

We have a Logstash agent installed somewhere and we want to ship the logs to our Elasticsearch cluster securely.

## Elasticsearch side

**Step 1: Bring Elasticsearch HTTP interface (port 9200) to HTTPS** When you get SSL certificates (i.e. from your IT department, or from LetsEncrypt), you should obtain a private key and a certificate chain. In order to use them with ReadonlyREST, we need to wrap them into a JKS (Java key store) file. For the sake of this example, or for your testing, we won't use real SSL certificates, we are going to create a self signed certificate.

Remember, we'll do with a self-signed certificate for example convenience, but if you deploy this to a server, use a real one!

```bash
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass readonlyrest -validity 360 -keysize 2048
```

Now copy the `keystore.jks` inside the plugin directory inside the Elasticsearch home.

```bash
cp keystore.jks /elasticsearch/config/
```

**IMPORTANT:** to enable ReadonlyREST's SSL stack, open `elasticsearch.yml` and append this one line:

```yaml
http.type: ssl_netty4
```

**Step 3** Now We need to create some credentials for logstash to login, let's say

* user = logstash
* password = logstash

**Step 4** Hash the credentials string `logstash:logstash` using SHA256. The simplest way is to paste the string in an [online tool](http://www.xorbin.com/tools/sha256-hash-calculator) You should have obtained "280ac6f756a64a80143447c980289e7e4c6918b92588c8095c7c3f049a13fbf9".

**Step 5** Let's add some configuration to our Elasticsearch: edit `conf/readonlyrest.yml` and append the following lines:

```yaml
readonlyrest:

  ssl:
    enable: true
    # keystore in the same dir with readonlyrest.yml
    keystore_file: "keystore.jks"
    keystore_pass: readonlyrest
    key_pass: readonlyrest

  global_settings:
    response_if_req_forbidden: Forbidden by ReadonlyREST ES plugin

  access_control_rules:

  - name: "::LOGSTASH::"
    auth_key_sha256: "280ac6f756a64a80143447c980289e7e4c6918b92588c8095c7c3f049a13fbf9" #logstash:logstash
    actions: ["cluster:monitor/main","indices:admin/types/exists","indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
    indices: ["logstash-*"]
```

## Logstash side

Edit the logstash configuration file and fix the output block as follows:

```ruby
 output {
   elasticsearch {
     ssl => true
     ssl_certificate_verification => false
     hosts => ["YOUR_ELASTICSEARCH_HOST:9200"]
     user => logstash
     password => logstash
   }
 }
```

The `ssl_certificate_verification` bit is necessary for accepting self-signed SSL certificates. You might also need to add cacert parameter to provide the path to your .cer or .pem file.


# Secure Metricbeat

Very similar to Logstash, here's a snippet of configuration for [Metricbeat](https://www.elastic.co/downloads/beats/metricbeat) logging agent configuration of the metricbeat - the elasticsearch section

## On the Metricbeat's side

```
output.elasticsearch:
  output.elasticsearch:
  username: metricbeat
  password: hereyourpasswordformetricbeat
  protocol: https
  hosts: ["xx.xx.xx.xx:9200"]
  worker: 1
  index: "log_metricbeat-%{+yyyy.MM}"
  template.enabled: false
  template.versions.2x.enabled: false
  ssl.enabled: true
  ssl.certificate_authorities: ["./certs/your-rootca_cert.pem"]
  ssl.certificate: "./certs/your_srv_cert.pem"
  ssl.key: "./certs/your_srv_key.pem"
```

Of course, if you do not use SSL, disable it.

## On the Elasticsearch side

```yaml
readonlyrest:
  ssl:
    enable: true
    # keystore in the same dir with elasticsearch.yml
    keystore_file: "keystore.jks"
    keystore_pass: readonlyrest
    key_pass: readonlyrest

  access_control_rules:
  - name: "metricbeat can write and create its own indices"
    auth_key_sha1: fd2e44724a234234454324253094080986e8fda
    actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
    indices: ["metricbeat-*", "log_metricbeat*"]
```


# Elastic Fleet

[Elastic Fleet](https://www.elastic.co/guide/en/fleet/current/fleet-overview.html) manages Elastic Agents centrally through Kibana. When Fleet is set up, it creates two kinds of dynamic Elasticsearch credentials that ReadonlyREST needs to recognize and validate:

* **Service tokens** - used by Fleet Server to authenticate with Elasticsearch. These are created by Kibana during Fleet setup and belong to Elasticsearch's built-in `elastic/fleet-server` service account.
* **API keys** - issued to each enrolled Elastic Agent. Fleet Server creates and rotates these automatically; each agent uses its own key to ship data.

Because both credential types are generated at runtime (not known in advance), they cannot be matched with static `auth_key` or `auth_key_sha256` rules. Instead, ReadonlyREST's `token_authentication` rule with `type: service-token` or `type: api-key` delegates validation to Elasticsearch, which has the ground truth for both.

## ReadonlyREST settings

```yaml
readonlyrest:
  access_control_rules:

    # 1. Kibana user - used by Kibana itself and by Fleet initialisation scripts.
    #    No action or index restriction; Kibana needs unrestricted access during
    #    Fleet setup (e.g. bootstrapping the Fleet Server service account).
    - name: "KIBANA"
      type: allow
      auth_key: kibana:kibana

    # 2. Fleet Server - authenticates using an Elasticsearch service token.
    #    ReadonlyREST validates the token against Elasticsearch's service account API.
    #    No action restriction: Fleet Server needs to call
    #    cluster:admin/xpack/security/api_key/create to issue API keys to
    #    enrolling agents.
    - name: "Fleet server"
      type: allow
      token_authentication:
        type: "service-token"
        username: "fleet"
      indices:
        - ".fleet-servers"
        - ".fleet-agents"
        - ".fleet-actions"
        - ".fleet-policies"
        - ".fleet-policies-leader"
        - ".fleet-enrollment-api-keys"

    # 3. Elastic Agents - each agent authenticates with its own API key, issued
    #    and rotated by Fleet Server. ReadonlyREST validates the key against Elasticsearch
    #    and grants access to the observability data-stream indices.
    - name: "Agents"
      type: allow
      token_authentication:
        type: "api-key"
        username: "fleet"
      indices:
        - ".apm-agent-configuration"
        - "metrics-*"
        - "traces-*"
        - "logs-*"

    # 4. Forbid direct token management - only Kibana and Fleet Server (matched
    #    above) should create or revoke service tokens and API keys. This block
    #    denies these actions for everyone else.
    - name: "Forbid access to service accounts and API keys"
      type: forbid
      actions:
        - "cluster:admin/xpack/security/service_account/*"
        - "cluster:admin/xpack/security/api_key/*"

    # 5. Admin user - full Kibana access.
    - name: "Admins"
      type: allow
      auth_key: admin:admin
      kibana:
        access: admin
```

## How Fleet credentials flow through ReadonlyREST

1. **Kibana creates a service token** - during Fleet setup, Kibana calls `cluster:admin/xpack/security/service_account/*` to create the Fleet Server service token. This request is authenticated by the `KIBANA` block.
2. **Fleet Server creates API keys** - Fleet Server uses its service token to call `cluster:admin/xpack/security/api_key/create`, issuing an API key to each enrolling agent. This request is authenticated by the `Fleet server` block.
3. **Elastic Agents use their API keys** - each agent presents its API key on every request to ship data to Elasticsearch. These requests are authenticated by the `Agents` block.

## Why the `forbid` block is necessary

Only Kibana and Fleet Server should be able to create service tokens and API keys - no other user needs these actions. The `KIBANA` and `Fleet server` blocks already permit these calls for the accounts that legitimately need them. The `forbid` block sits below those blocks and denies any remaining request that targets service-account or API-key management actions, preventing other authenticated users from creating, revoking or listing credentials.

## Credential rotation

You do not need to put service tokens or API key values into `readonlyrest.yml`. ReadonlyREST never sees or stores them - it asks Elasticsearch to validate each token on the fly. This means:

* Fleet Server can rotate its service token without any ReadonlyREST config change.
* Agents can be enrolled, unenrolled, and re-keyed without touching ReadonlyREST.
* The only things that must stay in sync with your deployment are the **index patterns** in the `service-token` and `api-key` blocks.

## Setting up Fleet Server and Elastic Agent

Configuring Fleet Server and enrolling Elastic Agents is covered in the [official Elastic Fleet documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html). APM agent setup is documented in the [APM quick-start guide](https://www.elastic.co/guide/en/apm/guide/current/apm-quick-start.html).

## Running the example

A full working example with Elasticsearch, Kibana (both with ReadonlyREST), Fleet Server, an Elastic Agent (APM), a demo Node.js app, and a traffic simulator is available in the [readonlyrest-examples](https://github.com/beshu-tech/readonlyrest-examples/tree/master/examples/fleet) repository:

```bash
curl -sL https://raw.githubusercontent.com/beshu-tech/readonlyrest-examples/master/quickstart.sh | bash -s fleet
```

Once running, log into Kibana and navigate to **Management → Fleet** to see the enrolled agent and its policy, or to **Observability → APM** for traces from the demo application.


# Contribution License Agreement

Thank you for your interest in ReadonlyREST documentation (“Product”), managed by Beshu Limited, a company duly established under the laws of United Kingdom, with registration number No. 10888034, and registered address at Office 32 13-21 Crawford Street, WH1 1PG, the owner the product (“We” or “Us”). We appreciate all the Contributions, made to our Product.

The purpose of this Contribution License Agreement (“CLA”, or “Agreement”) is to clarify the intellectual property rights granted with the Contribution to the Product from any person or entity. This CLA serves as a protection for a Contributor, as well as the protection of Us, our Product and its users.

This Agreement does not change your right to use your Contribution for the other purposes.

By submitting the present Contribution to Us, you acknowledge that you have read this Contribution License Agreement (a copy of which you can download) and that you will abide and comply to the requirements of the Agreement.

## 1. Definitions

“You” means an individual, who is a copyright owner of the Contribution, or a legal entity, which is authorized by a copyright owner to make a Contribution to the Product. “Contribution” means any original work of authorship, including any modifications or additions to the existing work, in which You own or assert ownership of the Copyright, that is intentionally Submitted by You to Us for inclusion in the Product. “Submit” means any form of electronic, verbal, or written communication sent to Us, including but not limited to electronic mailing lists, source code control systems, and issue tracking systems that are managed by Us, for the purpose of discussing and improving of our Product, but excluding communications that are conspicuously marked or otherwise designated in writing by You as “Not a Contribution”. “Product” means OSS ReadonlyREST Plugin for Elasticsearch (specified on the following web-site: <http://readonlyrest.com/download.html>), which is managed by BeShu Tech, which owns the Product.

## 2. Grant of Copyright License

By signing this Agreement, being a subject to the terms and conditions of it, You hereby grant to Us a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable copyright license with the right to sublicense such rights through multiple number of sublicensees, to reproduce, prepare derivative works, modify, publicly display, publicly perform and distribute Your Contributions as a part of the Product.

## 3. Grant of Patent License

By signing this Agreement, You hereby grant to Us a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable patent license with the right to sublicense these rights to multiple number of sublicensees, to make, have made, use, offer to sell, sell, import or otherwise transfer the Product, where such license applies only to those claims licensable by You that are necessarily infringed by your Contribution alone or by combination of your Contribution with the Product to which such Contribution was Submitted.

## 4. Our rights

We are not obliged to use Your Contribution as a part of the Product and We reserve the right to decide whether the Contribution is appropriate and can be included to the Product. If We include the Contribution to the Product We may license the Contribution under any licensing terms, including without limitation:

(a) open source licenses like the GPLv3 license; and

(b) binary, proprietary, or commercial licenses.

Except for the licenses granted herein, You reserve all right, title, and interest in and to the Contribution. including copyleft, permissive, commercial, or proprietary licenses.

## 5. Moral Rights

To the extent permitted by law, the You hereby irrevocably and unconditionally waive any and all moral rights conferred by Chapter IV of the UK Copyright Designs and Patents Act 1988 or any rights of a similar nature under laws now or in the future in force in any jurisdiction in and to any and all Contributions to Our Product, submitted by You and agree not to assert such moral rights against Us or any of our licensee, either direct or indirect.

## 6. Your Representations

By signing this Agreement, You represent and confirm that:

* You have a legal authority to enter into this Agreement and You are legally entitled to grant the above license;
* The Contribution is Your original creation and You own a copyright and patent claims covering the Contribution which are required to grant the rights under the sections 2 and 3 of this Agreement; Should You wish to Submit materials that are not Your original creation, You may Submit them separately to the Product if You (a) retain all copyright and license information that was in the materials as you received them, (b) in the description accompanying your Submission, include the phrase "Submission containing materials of a third party:" followed by the names of the third party and any licenses or other restrictions of which You are aware;
* The rights You grant under the Sections 2 and 3 of this Agreement does not violate any grant of rights, which You have made to the third parties;
* If You are an employee, You have received permission to make such Contribution on behalf of the employer;
* If You are less, then eighteen years old, please have Your parents or guardian sign this Agreement.

In addition, You agree to notify Us of any fact or circumstances of which you become aware that would make these representations inaccurate in any respect.

## 7. Disclaimer

EXCEPT FOR THE EXPRESS WARRANTIES IN THE SECTION 6, THE CONTRIBUTION IS PROVIDED ON “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF THE TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.

## 8. Other Provisions of the Agreement

This Agreement shall be governed and construed in accordance with the laws of the United Kingdom.

Unless you explicitly state otherwise, any Contribution shall be under the terms and conditions of this Agreement, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Us regarding such Contribution.

This Agreement sets out the entire agreement between You and Us and overrides all other agreements or understandings.

The relationship of the parties under this Agreement is that of independent contractors, and neither party will have the rights to act as the agent of the other party.

If You or We assign the rights or obligations received through this Agreement to a third party, as a conditions of the assignment, that third party must agree in writing to abide by all the rights and obligations in the Agreement.

If any provisions of this Agreement is found to be invalid or unenforceable, such provisions shall be severed from the Agreement and the remainder of this Agreement shall be interpreted so as to best reflects the original intent of the parties.


# Commercial Licenses

ReadonlyREST PRO and ReadonlyREST Enterprise are commercial subscriptions. They both include the license to use a Kibana plugin that can operate exclusively in synergy with our ReadonlyREST Elasticsearch plugin.

The Kibana plugin included in the Enterprise offer has more functionality than the one included in the PRO subscription. See [readonlyrest.com](https://readonlyrest.com) for the detailed differences.

The ReadonlyREST Elasticsearh plugin is released as open source under the GPLv3 license. However, It is possible to request a quote for obtaining a commercial license that enables you to integrate ReadonlyREST for Elasticsearch and/or for Kibana inside your commercial product.

## What is Priority Support?

Priority support is an email based, private support service with the creators of ReadonlyREST, and it covers two (2) incidents per quarter. Email <support@readonlyrest.com>.

We guarantee a max response time of 2 working days (usually less).

Please remember that the scope of priority support is limited to the resolution of ReadonlyREST issues, not Elasticsearch or Kibana issues, not your application or infrastructure issues.

Any further engagement beyond the above terms requires to either:

* go through the [public forum](https://forum.readonlyrest.com) (outside of SLA terms)
* be purchased as [consultancy days](mailto:finance@readonlyrest.com?subject=ReadonlyREST%20consultancy%20required) ($700 USD / day)
* the subscriber to buy a secondary Enterprise subscription for that year, so to double their quarterly priority support slots.

### Am I eligible of Priority support?

Non paying users, must rely only on community support alone. ReadonlyREST PRO comes with 30 days "onboarding" dedicated support (on the whole ReadonlyREST stack). ReadonlyREST Enterprise comes with 30 days "onboarding" dedicated support (on both plugins) AND **one year of priority support** via email or forum private messages.

In case you have a specific agreement with Beshu Limited (the company behind ReadonlyREST) for a commercial license that allows you to redistribute ReadonlyREST commercially, the priority SLA support does not cover your commercial customers directly. We will accept support requests from you and your staff only, and within the limits stated in the end user license agreement.

### Join the support forum

For enabling priority support:

* Register immediately to the support [forum](https://forum.readonlyrest.com).
* Register using the exact email (or same distinctive domain) used in the license registration
* Ask to be added to the PRO or Enterprise group.

When opening the account, make sure you are using the same domain as the original license email or explain your connection to the licensed company.

After that, when you actually need support, **open a support topic**. Don't forget to:

* Search for similar issues first! Often someone else already reported your issue.
* Start the topic title with `[URGENT]`, `[HIGH]` or `[NORMAL]` severity followed by a description of the issue.
* State whether you are a PRO or Enterprise customer
* State clearly the problem: the input, the desired output and the erroneous output
* Collect the logs and the configuration to reproduce the bug before opening the support topic.

### How severe is my issue?

* **URGENT**: Production is down, your business has stopped, we need to drop everything now and help you.
* **HIGH**: Production is wounded, but still functioning. You aren't sure if it's fatal, we will send help as quickly as possible.
* **NORMAL**: Production seems fine, but you have questions (this is usually the default).

## Is there a trial version?

We publish also a "Free" simplified version of our Kibana plugin, but we also offer a 30 day trial of the full PRO and Enterprise editions. After 30 days, you will need to either uninstall the plugin, or purchase a license (the software will automatically stop working otherwise).

## Can I get a discount?

You can a discount buying multiple licenses, or signing a contract for 2 or more years in advance (with advance payment).

## Licensing

Every organization running ReadonlyREST PRO or Enterprise must have a license. There's no limit to the number of cluster nodes for each cluster. Any license you buy allows you to use our software only **within the scope of your organization**. Please read your end user license agreement (EULA) for any clarification.

## When a subscription lapses

Legally, you must have an active subscription to keep ReadonlyREST PRO or Enterprise running. After a one week grace period, the software will refuse to work and you will not be eligible of priority support.\
Moreover, you won't have access to any more **security updates** or new features.

## Can I upgrade to Enterprise?

Sure, just ask for a discount coupon before deleting your previous subscription. So you'll only be charged the difference when [purchasing an Enterprise license.](https://readonlyrest.com/contact-us) **Please don't forget to mention that you are an existing PRO subscriber.**

## Can I distribute it to my customers?

The short answer is YES, but only as long as you have one valid, active [Embedded](https://readonlyrest.com/embedded/) subscription ongoing. You need to make sure your subscription remains active for as long as your product/solution containing ReadonlyREST is being offered for sale.

The reason you cannot distribute ReadonlyREST as a Free user or a PRO subscriber is that the ElasticSearch plugin is released under the GPLv3 license; and the only legal way you could bundle it into a commercial product/solution is by also releaseing all your software under a GPL compatible license.

We recognise this is rarely possible, that's why we agree to release the ElasticSearch plugin and the Kibana plugin under a commercial license that permits you to redistribute them.

For more legal information, please contact us [filing an inquiry for ReadonlyREST embed](https://readonlyrest.com/contact-us/).

## Can you transfer a license?

Licenses are **not** transferrable to another company. We will transfer the license from a user-specific email to a group email address (e.g. <john_smith@acme.com> -> <tech@acme.com>) but only for **the same domain**. It is strongly recommended that you buy the license using a group email address so the license is not attached to any one employee's email address.

## Obligations as a subscriber

Your purchase gets you access to downloading the PRO and/or Enterprise software. The license agreement requires you to keep this access private. If we find your access credentials are ever publicized:

1. We'll send you a warning email with details. You need to remove the content and change the password.
2. If your access is publicized a second time, we reserve the right to permanently remove access (but won't unless it's really egregious - sloppy contractors happen).

## Can I get a refund?

Yes, up to two weeks after purchase. Let us know the reason and maybe we can help but either way it's not a problem. Email [finance@readonlyrest.com](mailto:finance@readonlyrest.com?subject=ReadonlyREST%20refund%20required).

## What about payment methods?

For new subscriptions or renewals we offer a simplified method where you just receive an invoice, and you pay via credit card or wire transfer, or the full procurement process (quote, purchase order, invoice) at your discretion. For any questions: email [finance@readonlyrest.com](mailto:finance@readonlyrest.com?subject=Payments).


# Changelog

### (2026-07-12) What's new in **ROR 1.70.3**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-54399">CVE-2026-54399</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-54428">CVE-2026-54428</a></summary>

This release addresses two high-severity (CVSS 7.5) denial-of-service vulnerabilities in Apache HttpComponents Core, a dependency used by Elasticsearch. CVE-2026-54399 affects the HTTP/1.1 message parser — a remote attacker can send messages with an excessive number of headers or header length, causing memory exhaustion. CVE-2026-54428 affects the HTTP/2 HPACK decoder — a remote attacker can send oversized compressed header blocks, also leading to memory exhaustion before the header size limit is applied. Both vulnerabilities are fixed by updating the affected dependency.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed CSV report generation failing for users with <code>kibana.access</code>: <code>ro</code> or <code>ro_strict</code></summary>

Users with read-only (`ro`) or strict read-only (`ro_strict`) Kibana access roles were unable to generate CSV reports from saved searches or visualizations. This fix ensures that CSV report generation works correctly for these restricted roles, allowing read-only users to export data without requiring write permissions.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed the Kibana usage counter, which is now stored per tenancy index instead of being shared across tenancies</summary>

Previously, the Kibana usage counter was stored in a shared index, causing usage statistics to be mixed across different tenancies. This fix ensures that each tenancy maintains its own separate usage counter, providing accurate per-tenancy usage tracking and preventing data leakage between tenants.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a node rejecting all requests until restarted when ROR settings could not be read at startup. ROR now keeps retrying until they are available</summary>

When ROR settings (stored in the cluster's system index) were temporarily unavailable at node startup — for example, during cluster initialization or network delays — the node would reject all requests indefinitely until manually restarted. ROR now implements a retry mechanism that continuously attempts to read the settings until they become available, eliminating the need for a manual restart and improving cluster resilience during startup scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) ROR no longer falls back to the local <code>readonlyrest.yml</code> when the in-index settings exist but cannot be read, which could start a node with different rules than the rest of the cluster</summary>

If the in-index ROR settings existed but were temporarily unreadable (e.g., due to a transient error), ROR would silently fall back to the local `readonlyrest.yml` file. This could cause a node to start with a completely different set of security rules than the rest of the cluster, creating a dangerous security gap. ROR now refuses to start with the local file when in-index settings are present but unreadable, ensuring consistent security policy enforcement across all cluster nodes.

</details>

### (2026-06-21) What's new in **ROR 1.70.2**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-12143">CVE-2026-12143</a>, <a href="https://security.snyk.io/vuln/SNYK-JS-DOMPURIFY-17344526">CVE-2026-49458</a>, <a href="https://github.com/advisories/GHSA-76mc-f452-cxcm">GHSA-76mc-f452-cxcm</a>, <a href="https://github.com/advisories/GHSA-gvmj-g25r-r7wr">GHSA-gvmj-g25r-r7wr</a></summary>

🚨Security Fix (KBN) — This release addresses multiple security vulnerabilities in Kibana's bundled dependencies. CVE-2026-12143 is a CRLF injection in the `form-data` library (up to v4.0.5) that could allow header injection via crafted field names. GHSA-76mc-f452-cxcm and GHSA-gvmj-g25r-r7wr are DOMPurify vulnerabilities (up to v3.4.7) that could lead to XSS via hook-based mutation of allowed tags/attributes and template expression bypass inside `<template>` elements respectively. All dependencies have been updated to patched versions.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) 9.4.3, 9.3.7, 9.3.6, 8.19.18, 8.19.17 support</summary>

🚀New (KBN) — Added support for Kibana versions 9.4.3, 9.3.7, 9.3.6, 8.19.18, and 8.19.17. Users running these Kibana versions can now install and use the ReadonlyREST plugin.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.4.3, 9.3.7, 9.3.6, 8.19.18, 8.19.17 support</summary>

🚀New (ES) — Added support for Elasticsearch versions 9.4.3, 9.3.7, 9.3.6, 8.19.18, and 8.19.17. Users running these Elasticsearch versions can now install and use the ReadonlyREST plugin.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/ror-ent-1-70-1-9-4-2-lens-visualization-from-library-in-ro-mode-broken/2995">Visualizations not rendering for <code>kibana.access</code>: <code>ro</code>/<code>ro_strict</code> users on KBN 9.x</a></summary>

🐞Fix (KBN) — Resolved an issue where Lens visualizations from the library would fail to render for users with `kibana.access: ro` or `ro_strict` permissions on Kibana 9.x. This fix restores proper read-only visualization rendering for restricted users.

</details>

### (2026-06-12) What's new in **ROR 1.70.1**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42587">CVE-2026-42587</a></summary>

This release addresses a Netty vulnerability (CVE-2026-42587) where the HttpContentDecompressor's maxAllocation limit was silently ignored for Brotli, Zstd, and Snappy compression encodings, allowing an attacker to trigger unbounded memory allocation and denial of service via a crafted compressed payload. The fix updates the bundled Netty dependency to a patched version that properly enforces the decompression buffer limit for all supported content encodings.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/1-69-1-es9-4-2-unable-to-create-new-tenancy/2989">Fixed tenancy creation after in-place Kibana 8.x→9.x upgrade; stale tenancy indices are repaired automatically (reindex + atomic alias swap)</a></summary>

When upgrading Kibana in-place from 8.x to 9.x, existing tenancy indices could become stale and block the creation of new tenants. This fix automatically detects and repairs such stale indices by performing a reindex operation followed by an atomic alias swap, ensuring a seamless upgrade path without manual intervention.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed Grok Debugger and Painless Lab in DevTools forbidden error for <code>admin</code>, <code>RW</code>, and <code>RO</code>, <code>RO-strict</code> <code>kibana.access</code> levels</summary>

Users with admin, RW, RO, or RO-strict kibana.access levels were incorrectly receiving forbidden errors when trying to use the Grok Debugger and Painless Lab tools in DevTools. This fix ensures these built-in Kibana debugging tools are properly authorized for all standard access levels.

</details>

### (2026-06-03) What's new in **ROR 1.70.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) Fixed <code>kibana.allowed_api_paths</code> to only check Kibana and ReadonlyREST API calls, and to only be usable when <code>api_only</code> user access is configured</summary>

Fixed `kibana.allowed_api_paths` to only check Kibana and ReadonlyREST API calls, and to only be usable when `api_only` user access is configured. Previously, this setting could be misapplied to non-API requests, potentially allowing unintended access. The fix ensures it is scoped strictly to API-only user configurations.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-62718">CVE-2025-62718</a>, <a href="https://nvd.nist.gov/vuln/detail/cve-2026-41673">CVE-2026-41673</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-41907">CVE-2026-41907</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42264">CVE-2026-42264</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-6321">CVE-2026-6321</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-6322">CVE-2026-6322</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-8159">CVE-2026-8159</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-45149">CVE-2026-45149</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-8723">CVE-2026-8723</a>, <a href="https://security.snyk.io/vuln/SNYK-CHAINGUARDLATEST-WAZUHDASHBOARDFIPS-16807878">CVE-2026-46625</a></summary>

Updated Kibana plugin dependencies to patch 10 CVEs across libraries including Axios (proxy bypass, prototype pollution), xmldom (stack overflow DoS), uuid (buffer overflow), fast-uri (path normalization bypass), multiparty (regex DoS), brace-expansion (memory exhaustion), and qs (TypeError on null values). These fixes address vulnerabilities ranging from denial-of-service to credential leakage and request smuggling.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42582">CVE-2026-42582</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42583">CVE-2026-42583</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42587">CVE-2026-42587</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42584">CVE-2026-42584</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42580">CVE-2026-42580</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42585">CVE-2026-42585</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42581">CVE-2026-42581</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-41417">CVE-2026-41417</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-34479">CVE-2026-34479</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-34480">CVE-2026-34480</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-40490">CVE-2026-40490</a></summary>

Updated Elasticsearch plugin dependencies to patch 11 CVEs across Netty (multiple DoS, request smuggling, and CRLF injection flaws), Apache Log4j (malformed XML output), and AsyncHttpClient (credential leakage on redirect). These fixes address high-severity vulnerabilities including denial-of-service via crafted packets, HTTP request smuggling, and sensitive credential exposure during cross-domain redirects.

</details>

<details>

<summary><strong>🚀New</strong> (ECK) 3.4.1 support</summary>

Added support for Elastic Cloud on Kubernetes (ECK) operator version 3.4.1, ensuring compatibility with the latest ECK release for managing Elasticsearch and Kibana deployments on Kubernetes.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Cleaned newer builds of ancient libraries to avoid false positive CVE scanner reports</summary>

Removed outdated bundled libraries from the Kibana plugin build to eliminate false positive CVE scanner alerts. This cleanup ensures security scanning tools no longer flag ancient dependencies that were present in the build artifacts but not actually used at runtime.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://forum.readonlyrest.com/t/log-files-rotations/2930/2">Added support for rolling-file logging</a></summary>

Added rolling-file logging support for the ROR Kibana plugin, addressing community requests for log file rotation. This prevents log files from growing unboundedly and makes log management easier for production deployments.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) ROR initialisation now retries automatically when Elasticsearch is not yet fully ready at Kibana startup</summary>

ROR initialization now automatically retries when Elasticsearch is not yet fully available during Kibana startup. This eliminates manual restarts in containerized or orchestrated environments where Kibana may start before Elasticsearch is ready to accept connections.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/issues-with-letsencrypt-certs-from-dehydrated-curl-error-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate/2889">External SSL now supports EC private keys produced by dehydrated and similar ACME clients</a></summary>

External SSL configuration now supports EC (Elliptic Curve) private keys generated by dehydrated and similar ACME clients. This resolves compatibility issues where Let's Encrypt certificates obtained via these tools caused SSL handshake failures in ROR's external SSL layer.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Slashed ACL evaluation overhead for wildcard index patterns: 49x more throughput, 98% lower p99 latency, and 50% less CPU compared to the previous ROR version</summary>

Drastically optimized ACL evaluation for wildcard index patterns, delivering up to 49x more throughput, 98% lower p99 latency, and 50% less CPU usage compared to the previous ROR version. This is a significant performance improvement for clusters with complex index pattern rules.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) ROR bootstrap settings in <code>elasticsearch.yml</code> are now configured via proper nested YAML blocks under <code>readonlyrest.*</code> keys</summary>

ROR bootstrap settings in `elasticsearch.yml` can now be configured using proper nested YAML blocks under `readonlyrest.*` keys, providing a cleaner and more intuitive configuration structure compared to the previous flat key format.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Improved consistency of groups rule settings - the <code>users</code> section can only be present in the config when there is at least one groups rule that uses it</summary>

Improved configuration validation for groups rules: the `users` section is now only allowed in the configuration when at least one groups rule actually references it. This prevents orphaned user definitions and makes configuration errors easier to catch at startup.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/cannot-download-generated-report-for-kibana-8-19-7/2927/38">Fixed a problem with downloading reports when multitenancy is disabled for Kibana > 8.13.x</a></summary>

Fixed a problem where downloading generated reports failed with a 404 error in Kibana versions above 8.13.x when multitenancy was disabled. The issue occurred when the `kibana.index` setting was omitted from `kibana.yml` and is now resolved.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a bug with the <code>x-ror-tenancy-id</code> header not being respected in direct Kibana requests</summary>

Fixed a bug where the `x-ror-tenancy-id` header was not being properly respected when making direct requests to Kibana. This ensures that multi-tenant routing via the custom header works correctly in all request scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a problem with the OIDC proxy where the Issuer cert endpoint wasn't passed through a proxy</summary>

Fixed an issue in the OIDC proxy where the Issuer certificate endpoint was not being passed through the configured proxy. This caused OIDC authentication failures in environments where all outbound traffic must go through a corporate proxy.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a problem with correctly setting <code>nextUrl</code> when redirecting from OIDC with an external proxy</summary>

Fixed a problem where the `nextUrl` redirect parameter was not correctly set during OIDC authentication flows when an external proxy was involved. This ensures users are redirected to the correct page after successful OIDC login in proxied environments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a problem with the relative path for <code>readonlyrest_kbn.login_custom_logo</code></summary>

Resolved an issue where the `readonlyrest_kbn.login_custom_logo` setting did not correctly handle relative paths. Custom login page logos configured with relative paths now display properly.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/authorization-via-proxy-auth-does-not-work-correctly/2969/9">Fixed a problem with logging in to Kibana when proxy auth is enabled and the <code>x-forwarded-user</code> header is set</a></summary>

Fixed a login issue in Kibana when proxy authentication is enabled and the `x-forwarded-user` header is present. The proxy auth flow now correctly processes the forwarded user identity, resolving authentication failures reported by users in proxy-based deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a problem with the ROR KBN plugin loading when different plugin versions and non-sticky sessions are used</summary>

Resolved a plugin loading issue that occurred when different ROR plugin versions were deployed across Kibana instances behind a load balancer without sticky sessions. The fix ensures consistent plugin behavior regardless of which Kibana node handles the request.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed the user metadata response returning the same Kibana index for all of a user's groups when the index uses <code>@{acl:current_group}</code></summary>

Fixed a bug where the user metadata response returned the same Kibana index for all of a user's groups when the index pattern used the `@{acl:current_group}` variable. Each group now correctly resolves to its own Kibana index as intended.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed the <code>expand_wildcards</code> parameter being ignored during index resolution: ROR now correctly filters indices and aliases by their open/closed state when resolving wildcard patterns, preventing closed indices from leaking into rewritten requests</summary>

Fixed a critical issue where the `expand_wildcards` parameter was ignored during index resolution. ROR now correctly respects the open/closed state of indices when resolving wildcard patterns, preventing closed indices from being inadvertently included in rewritten requests and causing unexpected behavior.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a missing Kibana access policy in the metadata response when a matched ACL block has no <code>kibana</code> section configured; the default unrestricted access is now always returned</summary>

Fixed a bug where the metadata response was missing the Kibana access policy when a matched ACL block had no `kibana` section configured. The default unrestricted access policy is now always returned, ensuring consistent Kibana behavior even when the ACL block doesn't explicitly define Kibana rules.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/global-checkpoint-sync-blocked/2870">The ES action <code>indices:admin/seq_no/global_checkpoint_sync</code> is now treated as an internal action and bypasses ACL evaluation. This action is dispatched by Elasticsearch internally after write operations and should never require explicit user permissions</a></summary>

The `indices:admin/seq_no/global_checkpoint_sync` action is now treated as an internal Elasticsearch action and bypasses ACL evaluation. This action is dispatched internally after write operations and should never require explicit user permissions. Previously, strict ACL rules could block this action, causing write operation failures.

</details>

### (2026-04-10) What's new in **ROR 1.69.1**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) Fixed vulnerability <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-2950">CVE-2026-2950</a></summary>

Fixed a prototype pollution vulnerability (CVE-2026-2950) in the Lodash library used by Kibana. The issue allowed attackers to bypass a previous fix (CVE-2025-13465) by using array-wrapped path segments in `_.unset` and `_.omit` functions, potentially deleting properties from built-in prototypes. The vulnerability is patched by upgrading Lodash to version 4.18.0.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) 9.4.2, 9.4.1, 9.4.0, 9.3.5, 9.3.4, 9.3.3, 9.2.8, 8.19.16, 8.19.15, 8.19.14 support</summary>

Added compatibility with the latest Kibana versions, including 9.4.x, 9.3.x, 9.2.8, and multiple 8.19.x releases. Users running these Kibana versions can now install and use ReadonlyREST without compatibility issues.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.4.2, 9.4.1, 9.4.0, 9.3.5, 9.3.4, 9.3.3, 9.2.8, 8.19.16, 8.19.15, 8.19.14 support</summary>

Added compatibility with the latest Elasticsearch versions, covering 9.4.x, 9.3.x, 9.2.8, and multiple 8.19.x releases. Users on these Elasticsearch versions can now deploy ReadonlyREST for access control.

</details>

<details>

<summary><strong>🚀New</strong> (ECK) 3.4.0 support</summary>

Added support for Elastic Cloud on Kubernetes (ECK) version 3.4.0, enabling ReadonlyREST deployment in Kubernetes environments running this ECK version.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed <code>jsonwebtoken-ancient</code> being stripped from Kibana builds earlier than 7.11.0</summary>

Fixed an issue where the `jsonwebtoken-ancient` dependency was incorrectly removed from Kibana builds for versions earlier than 7.11.0, which could cause JWT authentication failures on older Kibana deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Filtered out Fleet-based apps from search results when Management is hidden in Kibana 8.x and 9.x</summary>

Fixed a search visibility issue where Fleet-based applications (e.g., Integrations, Fleet) would still appear in Kibana search results even when the Management section was hidden by security rules. These apps are now properly filtered out.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed <code>/pkp/session-probe</code> requests being blocked by browsers that enforce async-only calls</summary>

Fixed a compatibility issue where browsers enforcing async-only fetch calls would block the `/pkp/session-probe` requests used for session health checks. This ensures seamless session validation across all modern browsers.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a problem with redirecting to the login form after a 401 error following a session probe check</summary>

Fixed a redirect loop issue where users would not be properly redirected to the login form after receiving a 401 error during a session probe check. Users are now correctly prompted to re-authenticate.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a missing Kibana access policy in the metadata response when the matched ACL block has no <code>kibana</code> section configured</summary>

Fixed an issue where the Elasticsearch metadata response was missing the Kibana access policy when the matched ACL rule block did not explicitly define a `kibana` section. The policy is now properly included in the response metadata.

</details>

### (2026-04-02) What's new in **ROR 1.69.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-24001">CVE-2026-24001</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-69873">CVE-2025-69873</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-2391">CVE-2026-2391</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-25639">CVE-2026-25639</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-27904">CVE-2026-27904</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-3449">CVE-2026-3449</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-15599">CVE-2025-15599</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-33750">CVE-2026-33750</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-4867">CVE-2026-4867</a>, <a href="https://www.tenable.com/cve/CVE-2026-34601">CVE-2026-34601</a>, <a href="https://nvd.nist.gov/vuln/detail/cve-2022-31129">CVE-2022-31129</a></summary>

This release patches 11 CVEs in Kibana's bundled JavaScript dependencies, addressing denial-of-service (ReDoS/infinite loop), XSS, and crash vulnerabilities in libraries such as jsdiff, ajv, qs, axios, minimatch, @tootallnate/once, DOMPurify, brace-expansion, path-to-regexp, xmldom, and moment. All CVEs are fixed by upgrading the affected dependencies to their patched versions.

</details>

<details>

<summary><strong>🚀New</strong> (KBN/ES) <a href="https://docs.readonlyrest.com/elasticsearch/fleet">Added Fleet support via native API key and service account token authentication (ES 7.14+)</a></summary>

ReadonlyREST now supports Elastic Fleet by validating the two dynamic credential types Fleet creates: service tokens (for Fleet Server) and API keys (for Elastic Agents). The `token_authentication` rule delegates validation to Elasticsearch, so no token values need to be stored in the ROR configuration, and credential rotation requires no config changes.

</details>

<details>

<summary><strong>🚀New</strong> (KBN/ES) The ReadonlyREST Audit Dashboard available in the Kibana plugin now supports audit events written to data streams</summary>

The ReadonlyREST Audit Dashboard can now visualize audit events stored in data streams, in addition to the previously supported regular indices. This ensures compatibility with modern Elasticsearch deployments that use data streams for time-series audit data.

</details>

<details>

<summary><strong>🚀New</strong> (KBN/ES) The ReadonlyREST Audit Dashboard provided by the Kibana plugin can now be used with the ECS (Elastic Common Schema) audit index</summary>

The Audit Dashboard now supports the Elastic Common Schema (ECS) format for audit indices, allowing organizations that standardize on ECS to use the dashboard without requiring a custom audit log serializer.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) <a href="https://forum.readonlyrest.com/t/multi-tenancy-and-link-sharing/1978/3">Added support for opening different tenancies in separate tabs</a></summary>

Users can now open multiple Kibana tenancies in separate browser tabs simultaneously, making it easier to work across different tenants without repeatedly switching contexts.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) <a href="https://forum.readonlyrest.com/t/multi-tenancy-and-link-sharing/1978/3">Added support for sharing links to Kibana visualizations for the selected tenancy</a></summary>

Visualization links now respect the active tenancy context, enabling users to share direct links to Kibana dashboards and visualizations that automatically open in the correct tenancy for the recipient.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Added support for rolling upgrades when upgrading the ROR Elasticsearch plugin and ROR Kibana plugin in a cluster</summary>

Rolling upgrades are now supported for both the ROR Elasticsearch and Kibana plugins, allowing cluster administrators to upgrade nodes one at a time without taking the entire cluster offline.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Removed the need for manual username input in the impersonation mechanism</summary>

The impersonation feature no longer requires administrators to manually type the target username, streamlining the workflow and reducing the chance of typos when testing user permissions.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Fixed an error in Kibana caused by empty data streams in Kibana 8.18.0+</summary>

Resolved an error that occurred in Kibana 8.18.0+ when empty data streams were present, ensuring the Kibana UI remains stable and functional regardless of data stream state.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Added a fallback for an empty <code>indices</code> field in the Audit Dashboard</summary>

The Audit Dashboard now gracefully handles audit events where the `indices` field is empty, preventing visualization errors and ensuring the "Who uses what indices?" view remains functional.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/develop/examples/custom-middleware">Updated custom metadata examples to use the new method. <code>getIdentitySession</code> and <code>getAuthorizationHeaders</code> are now deprecated in favor of <code>getUserRequestIdentity</code>, <code>getIdentitySessionHeaders</code>, and <code>getWhitelistedHeaders</code></a></summary>

The custom middleware API has been updated with new, more clearly named methods. `getIdentitySession` and `getAuthorizationHeaders` are deprecated; users should migrate to `getUserRequestIdentity`, `getIdentitySessionHeaders`, and `getWhitelistedHeaders` for accessing request identity and header information.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch#token_authentication"><code>token_authentication</code> rule extended with <code>api_key</code> and <code>service_token</code> types</a></summary>

The `token_authentication` ACL rule now supports `api_key` and `service_token` as token types, enabling fine-grained access control for Elastic Fleet and other service-to-service authentication scenarios.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/distinguish-between-wrong-credentials-and-missing-permissions/2914">Audit log entries and ACL history now include a human-readable reason when a request is denied, making access-control troubleshooting significantly easier</a></summary>

Denied requests now include a clear, human-readable reason in both audit log entries and ACL history, making it much easier to distinguish between authentication failures (wrong credentials) and authorization failures (missing permissions) during troubleshooting.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Added the new <code>matched_block_names</code> field to audit entries created by audit log serializers other than ECS and custom serializers. The <code>reason</code> field is now deprecated.</summary>

A new `matched_block_names` field has been added to audit entries for non-ECS and non-custom serializers, listing which ACL blocks matched the request. The `reason` field is now deprecated in favor of the more descriptive human-readable reason and `matched_block_names` fields.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Users defined with LDAP, external, and <code>ror_kbn</code> authentication are no longer treated as local users by the impersonation mechanism</summary>

The impersonation mechanism now correctly distinguishes between local users and users authenticated via LDAP, external providers, or `ror_kbn`. This prevents impersonation from incorrectly applying local-user-only logic to externally managed users.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) The ROR Kibana plugin can no longer be used when the <code>prompt_for_basic_auth: true</code> setting is configured</summary>

When `prompt_for_basic_auth: true` is set in the Elasticsearch plugin configuration, the ROR Kibana plugin will now refuse to operate, preventing an incompatible and insecure configuration where Kibana's session management conflicts with the browser's basic auth prompt.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a memory leak related to direct calls via the Kibana API</summary>

Fixed a memory leak that occurred when making direct API calls to Kibana, improving long-term stability and preventing gradual memory exhaustion in production environments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) No longer shows the "Data Set Quality" and "Index management" applications to users with RO or RO_strict access</summary>

The "Data Set Quality" and "Index Management" Kibana applications are now properly hidden from users with read-only (RO) or read-only strict (RO\_strict) access, preventing confusion and ensuring the access control model is consistently enforced.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed JWT token authorization when using embedded Kibana</summary>

Resolved an issue where JWT token authorization failed when Kibana was embedded within another application, ensuring seamless SSO integration in embedded Kibana scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed the styling of the page-not-found screen for Kibana 9.x</summary>

The page-not-found (404) screen now renders with correct styling in Kibana 9.x, eliminating visual glitches and maintaining a polished user experience.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Correctly displays the "Who uses what indices?" Audit Dashboard visualization when indices are not specified in the audit events</summary>

The "Who uses what indices?" visualization in the Audit Dashboard now renders correctly even when audit events lack index information, preventing blank or broken visualizations.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/sending-logs-to-another-cluster/2925">Improved stability when sending audit logs to another cluster, so temporary remote cluster outages no longer affect the main cluster</a></summary>

When audit logs are forwarded to a remote Elasticsearch cluster, temporary outages of that remote cluster no longer impact the stability or performance of the main cluster. The audit log shipping is now resilient to connection interruptions.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed Search Profiler being inactive in Kibana 8.18.0+</summary>

The Search Profiler tool in Kibana 8.18.0+ was not functioning correctly with ROR; this has been fixed, restoring the ability to profile and analyze search query performance.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <code>beshultd/elasticsearch-readonlyrest</code> images for ES 7.16.x, 7.17.0–7.17.6, and 8.0.x–8.4.x now ship with a patched JDK, replacing bundled JDK 17.0.0–17.0.4 / JDK 18, which crashes on cgroup v2 hosts due to JDK-8287073</summary>

Docker images for the affected Elasticsearch versions now include a patched JDK, resolving crashes on cgroup v2 hosts (common in modern Linux distributions and container runtimes) caused by the JDK-8287073 bug in JDK 17.0.0–17.0.4 and JDK 18.

</details>

### (2026-01-07) What's new in **ROR 1.68.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-51999">CVE-2024-51999</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-65945">CVE-2025-65945</a></summary>

This release addresses two Kibana-related security vulnerabilities. CVE-2024-51999 was a rejected CVE issued in error and has been removed. CVE-2025-65945 fixes an improper signature verification flaw in the auth0/node-jws library that could allow attackers to bypass HMAC signature verification when using user-provided data in the secret lookup process.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-67735">CVE-2025-67735</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-66453">CVE-2025-66453</a></summary>

This release patches two Elasticsearch-related security vulnerabilities. CVE-2025-67735 addresses a CRLF injection vulnerability in the Netty framework that could lead to HTTP request smuggling attacks. CVE-2025-66453 fixes a denial-of-service vulnerability in the Rhino JavaScript engine where crafted floating-point numbers could cause excessive CPU consumption.

</details>

<details>

<summary><strong>⚠️Warning</strong> (ES) Audit outputs now use the round-robin strategy for custom audit clusters. <a href="https://docs.readonlyrest.com/elasticsearch/audit#custom-audit-cluster">Audit nodes must belong to the same Elasticsearch cluster; otherwise, audit events may be incomplete</a> for configuration guidelines.</summary>

The audit system now uses round-robin distribution for custom audit clusters. Administrators must ensure all audit nodes belong to the same Elasticsearch cluster to prevent incomplete audit events. This change improves load distribution but requires proper cluster configuration.

</details>

&#x20;       **🚀New** (KBN) 9.3.2, 9.3.1, 9.3.0, 9.2.7, 9.2.6, 9.2.5, 9.2.4, 9.1.10, 8.19.13, 8.19.12, 8.19.11, 8.19.10 support

&#x20;       **🚀New** (ES) 9.3.2, 9.3.1, 9.3.0, 9.2.7, 9.2.6, 9.2.5, 9.2.4, 9.1.10, 8.19.13, 8.19.12, 8.19.11, 8.19.10 support

<details>

<summary><strong>🚀New</strong> (KBN) Added "Remember last picked tenant" feature for external identity providers</summary>

This feature enhances user experience by remembering the last selected tenant when using external identity providers. Users no longer need to reselect their preferred tenant on each login, streamlining the authentication process for multi-tenant environments.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Introduced support for the Kibana Data Set Quality beta application</summary>

ROR now supports the Kibana Data Set Quality beta application, allowing administrators to manage and monitor data quality metrics within their secured Kibana environment. This integration ensures compatibility with Elastic's latest data management tools.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Restyled ROR menu featuring searchable tenancy selector</summary>

The ROR menu interface has been redesigned with a modern look and includes a searchable tenancy selector. This improvement makes it easier for users to find and switch between tenants in environments with large numbers of tenants.

</details>

<details>

<summary><strong>🚀New</strong> (ES) Added new rules: <a href="https://docs.readonlyrest.com/elasticsearch#jwt_authentication"><code>jwt_authentication</code></a> and <a href="https://docs.readonlyrest.com/elasticsearch#jwt_authorization"><code>jwt_authorization</code></a>, as alternatives to the existing <code>jwt_auth</code> rule</summary>

Two new JWT rules provide more granular control over authentication and authorization processes. The `jwt_authentication` rule handles user identity verification, while `jwt_authorization` manages permission assignments, offering greater flexibility compared to the combined `jwt_auth` rule.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#using-ecs-serializer">New audit log serializer compliant with Elastic Common Schema (ECS)</a></summary>

A new ECS-compliant audit log serializer ensures audit events follow Elastic's standardized format. This improves compatibility with Elastic Stack tools and makes audit data easier to analyze using ECS-aware applications and dashboards.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#configuration">The audit can be enabled or disabled on the block level</a></summary>

Audit logging can now be controlled at the individual rule block level, providing finer-grained control over what gets logged. Administrators can enable or disable auditing for specific access control blocks while maintaining global audit settings.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Disabled caching in the Login CSRF protection mechanism.</summary>

Caching has been disabled in the Login CSRF protection to enhance security. This prevents potential CSRF token reuse and ensures each authentication request uses fresh, unique tokens for improved protection against cross-site request forgery attacks.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Made the tenant indicator always visible and improved its dropdown behavior</summary>

The tenant indicator is now always visible in the UI, providing constant awareness of the current tenant context. The dropdown behavior has been improved for better usability and smoother tenant switching experience.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Added stack traces to ReadonlyREST KBN plugin error logs for easier debugging</summary>

Error logs now include full stack traces, making it easier for administrators to diagnose and troubleshoot issues. This enhancement significantly improves debugging capabilities by providing detailed error context and call paths.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/ldap-connection-timeout-leads-to-authentication-error/2899">Added LDAP connection health checking to prevent stale connection authentication failures</a></summary>

Improved LDAP connection health checking prevents authentication failures caused by stale connections in the pool. This fix addresses issues where daily login attempts would fail after periods of inactivity, particularly in environments with network proxies like Kubernetes.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#using-configurable-serializer">Enable nested field definitions in the configurable audit log serializer for more flexible audit logging</a></summary>

The configurable audit log serializer now supports nested field definitions, allowing more complex and structured audit data. This provides greater flexibility in customizing audit event formats to match specific organizational requirements.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#predefined-serializers">The predefined audit log serializers</a> now include a new <code>logged_user</code> field, which contains a human-readable username</summary>

Predefined audit log serializers now include a `logged_user` field displaying human-readable usernames. This enhancement makes audit logs more readable and easier to interpret by showing actual user identities instead of technical identifiers.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved an issue causing the Kibana Search Sessions app to fail on Kibana 8.x</summary>

Fixed a compatibility issue that prevented the Kibana Search Sessions application from functioning properly on Kibana 8.x versions. This ensures full compatibility with Elastic's search session management features.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/errors-after-upgrade-kibana-7-17-29-to-8-19-7/2887">Fixed cluster resolution issues that caused Kibana errors and unexpected logouts in versions 8.19.x and above</a></summary>

Resolved cluster resolution problems that were causing Kibana errors and unexpected user logouts after upgrading to Elasticsearch 8.19.x and later versions. This fix addresses compatibility issues introduced in recent Elasticsearch releases.

</details>

### (2025-11-29) What's new in **ROR 1.67.3**

<details>

<summary><strong>🚀New</strong> (KBN) 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, 8.19.8 support</summary>

ReadonlyREST now officially supports Kibana versions 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, and 8.19.8. This ensures compatibility with the latest Kibana security patches and features, allowing administrators to secure their Kibana instances with the most recent releases.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, 8.19.8 support</summary>

This release adds official support for Elasticsearch versions 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, and 8.19.8. Users can now deploy ReadonlyREST with these Elasticsearch versions to benefit from the latest security updates and performance improvements while maintaining full access control functionality.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Resolved index resolution compatibility issue with Elasticsearch 9.1.7</summary>

Fixed a compatibility issue where ReadonlyREST had problems resolving index patterns and aliases correctly when running with Elasticsearch 9.1.7. This fix ensures proper index resolution and access control enforcement for users upgrading to or already using Elasticsearch 9.1.7.

</details>

### (2025-11-13) What's new in **ROR 1.67.2**

<details>

<summary><strong>🚀New</strong> (KBN) 9.2.1, 9.1.7, 8.19.7 support</summary>

ReadonlyREST now officially supports Kibana versions 9.2.1, 9.1.7, and 8.19.7. This ensures compatibility with the latest Kibana security patches and features, allowing users to upgrade their Kibana deployments while maintaining ReadonlyREST security functionality.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.2.1, 9.1.7, 8.19.7 support</summary>

ReadonlyREST now officially supports Elasticsearch versions 9.2.1, 9.1.7, and 8.19.7. This update provides compatibility with the latest Elasticsearch security updates and performance improvements, ensuring seamless integration of ReadonlyREST security features with these Elasticsearch releases.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed SAML/OIDC provider support behind a reverse proxy when <code>server.rewriteBasePath: false</code> is set in kibana.yml</summary>

This fix resolves an issue where SAML and OpenID Connect authentication providers would fail when Kibana is deployed behind a reverse proxy with `server.rewriteBasePath: false` configuration. The problem occurred because ReadonlyREST was incorrectly handling URL rewriting in this specific deployment scenario, preventing successful authentication through reverse proxy setups.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Delegated handling of certain internal exceptions to Elasticsearch, preserving native error responses</summary>

This fix improves error handling by allowing Elasticsearch to process certain internal exceptions natively instead of ReadonlyREST intercepting them. This ensures that error responses maintain their original Elasticsearch format and behavior, providing better compatibility with client applications that expect specific error response structures from Elasticsearch.

</details>

### (2025-11-03) What's new in **ROR 1.67.1**

<details>

<summary><strong>🚀New</strong> (KBN) 9.2.0, 9.1.6, 8.19.6 support</summary>

Ensures compatibility and full security functionality with the latest Kibana releases, allowing safe upgrades to versions 9.2.0, 9.1.6, and 8.19.6.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.2.0, 9.1.6, 8.19.6 support</summary>

Provides official support for Elasticsearch versions 9.2.0, 9.1.6, and 8.19.6, ensuring the plugin's security features work correctly across the latest stack releases.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Allow using the <code>actions</code> rule with the <code>kibana</code> rule in the same block when <code>kibana.access: unrestricted</code></summary>

Removes a previous restriction, granting administrators greater configuration flexibility to combine fine-grained action controls with Kibana access rules in unrestricted blocks.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed JWT handling for wrong license edition</summary>

Resolves an authentication failure where JWT validation incorrectly failed based on license type, ensuring reliable JWT authentication regardless of the Elastic license edition.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Suppressed “Forbidden” toast in Discover/Dashboard on Kibana 8.x–9.x</summary>

Eliminates confusing and unnecessary 'Forbidden' pop-up notifications in Kibana's Discover and Dashboard apps when access is correctly denied by ROR rules, improving the user experience.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/unable-to-download-reports-from-kibana/2859/2">Resolved report download failure on Kibana 9.1.x</a></summary>

Fixes a critical bug that blocked users from downloading reports (PDF, PNG, CSV) from Kibana 9.1.x dashboards and visualizations, restoring essential reporting functionality.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed timeout when saving Security settings</summary>

Addresses a configuration issue where attempts to save Security settings in Kibana would hang and eventually timeout, preventing administrators from applying critical security changes.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Restored visibility of reports when multiple data streams exist for a reporting index</summary>

Corrects an issue where generated reports became invisible in the Kibana UI if the reporting index was backed by multiple data streams, ensuring all reports are accessible.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed invisible reports for non-tenancy users on Kibana 9.1.x</summary>

Resolves a bug specific to Kibana 9.1.x where users not utilizing multi-tenancy features could not see their generated reports, effectively breaking the reporting interface for them.

</details>

### (2025-10-14) What's new in **ROR 1.67.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-58754">CVE-2025-58754</a></summary>

This fix addresses CVE-2025-58754, a Denial of Service vulnerability in the Axios HTTP client library (used by the Kibana plugin). When processing `data:` URIs on Node.js, Axios ignored `maxContentLength` and `maxBodyLength` limits, allowing an attacker to trigger unlimited memory allocation and crash the process. The Axios dependency has been updated to a patched version.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-58057">CVE-2025-58057</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-58056">CVE-2025-58056</a></summary>

Two Netty vulnerabilities have been patched in the Elasticsearch plugin. CVE-2025-58057 is a DoS flaw where the BrotliDecoder and other decompression decoders could allocate unlimited byte buffers, causing Out-of-Memory errors. CVE-2025-58056 is an HTTP request smuggling vulnerability caused by Netty incorrectly accepting standalone newline characters (LF) as chunk-size line terminators instead of requiring CRLF per HTTP/1.1 spec. The Netty dependency has been updated to a fixed version.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#using-configurable-serializer">Added support for defining a custom audit serializer directly in ROR settings (no code required)</a></summary>

Previously, customizing the format of audit log events required writing a Scala or Java serializer, compiling it into a JAR, and adding it to the plugin classpath. Now you can define a custom audit serializer directly in the ROR configuration using YAML — no coding or compilation needed. This makes it much easier to tailor audit event fields to your specific monitoring and compliance requirements.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#predefined-serializers">Introduced new predefined audit serializers: <code>ReportingAllEventsAuditLogSerializer</code>, <code>ReportingAllEventsWithQueryAuditLogSerializer</code></a></summary>

Two new built-in audit serializers have been added. `ReportingAllEventsAuditLogSerializer` logs all audit events regardless of verbosity settings, while `ReportingAllEventsWithQueryAuditLogSerializer` does the same but also captures the full request body. These complement the existing serializers and give administrators more granular control over audit logging without needing custom code.

</details>

<details>

<summary><strong>🚀New</strong> (ES) Added new rules: <a href="https://docs.readonlyrest.com/elasticsearch#ror_kbn_authentication"><code>ror_kbn_authentication</code></a> and <a href="https://docs.readonlyrest.com/elasticsearch#ror_kbn_authorization"><code>ror_kbn_authorization</code></a>, as alternatives to the existing <code>ror_kbn_auth</code> rule</summary>

The existing `ror_kbn_auth` rule combined both authentication and authorization into a single rule. The new `ror_kbn_authentication` and `ror_kbn_authorization` rules allow you to split these concerns into separate ACL blocks, giving you more flexibility to define different authentication methods and authorization logic independently in your security configuration.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/kibana#clock-skew-tolerance">Added OIDC <code>clock-skew-tolerance</code> configuration option in <code>kibana.yml</code></a></summary>

A new `clock-skew-tolerance` configuration option has been added for OIDC authentication in the Kibana plugin. This allows administrators to configure how much time drift (clock skew) is tolerated between the Kibana server and the OIDC identity provider when validating token timestamps, helping to avoid authentication failures in environments with slight clock discrepancies.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/kibana#terminate-kibana-on-es-high-watermark">Added option to disable Kibana termination on watermark errors in <code>kibana.yml</code></a></summary>

Previously, when Elasticsearch disk watermark thresholds were exceeded, the ROR Kibana plugin would terminate Kibana to prevent data loss. A new configuration option has been added to `kibana.yml` that allows administrators to disable this automatic termination behavior, giving them more control over how their cluster handles high watermark scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Logout did not invalidate the app session when the <code>ror_kbn_auth</code> rule was used with local group definitions</summary>

When using the `ror_kbn_auth` rule with locally defined groups, the logout action was not properly invalidating the Kibana application session. This meant that after logging out, the session could potentially remain active. This has been fixed so that logout correctly terminates the session regardless of how groups are defined.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/kibana-data-view-filter-not-working-with-keyword/2843">Restored keyword field value suggestions in Discover/Data View filters</a></summary>

After upgrading ROR to versions 1.60+, the Discover and Data View filter dropdowns in Kibana stopped showing value suggestions for keyword fields. This regression has been fixed, restoring the expected autocomplete behavior when filtering by keyword fields in Kibana's data exploration tools.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Integration-based options were visible in search results even when the app was marked as hidden</summary>

When certain Kibana apps were configured as hidden, their integration-based options (such as dashboards or visualizations) could still appear in Kibana's global search results. This fix ensures that when an app is marked as hidden, its associated integration options are also properly excluded from search results.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Index Management appeared in app search results even when the app was declared as hidden</summary>

The Index Management app in Kibana was still appearing in global search results even when administrators had explicitly marked it as hidden in the ROR security configuration. This has been corrected so that hidden apps are fully excluded from search results.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved an issue with CSRF token override when multiple browser tabs were open</summary>

When users had multiple Kibana browser tabs open simultaneously, CSRF token management could cause one tab's token to override another's, leading to unexpected request failures. This issue has been resolved to ensure proper CSRF token isolation across multiple tabs.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed OIDC compatibility for Kibana 7.10.2 and earlier</summary>

OIDC authentication was broken on older Kibana versions (7.10.2 and earlier) due to compatibility issues with the ROR Kibana plugin. This fix restores proper OIDC support for users running these legacy Kibana versions.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Restored backward compatibility for custom audit log serializer implementations extending the <code>DefaultAuditLogSerializer</code> class. Custom serializers compiled against ROR 1.65 or 1.66 that use <code>DefaultAuditLogSerializer</code> must be recompiled to work correctly</summary>

Custom audit log serializers that were compiled against ROR 1.65 or 1.66 and extended the `DefaultAuditLogSerializer` class stopped working due to internal API changes. Backward compatibility has been restored, though custom serializers compiled against those versions must be recompiled to work correctly with this release.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a defect that broke the "Snapshot and Restore" functionality in Kibana</summary>

A defect in the Elasticsearch plugin was preventing the Snapshot and Restore functionality in Kibana from working correctly. This has been fixed, restoring the ability to create, manage, and restore snapshots through the Kibana UI when ROR security is active.

</details>

### (2025-09-03) What's new in **ROR 1.66.1**

<details>

<summary><strong>🚀New</strong> (KBN) 9.1.5, 9.1.4, 9.0.8, 9.0.7 8.19.5, 8.19.4, 8.18.7 support</summary>

ReadonlyREST now supports Kibana versions 9.1.5, 9.1.4, 9.0.8, 9.0.7, 8.19.5, 8.19.4, and 8.18.7. This ensures compatibility with the latest Kibana releases and allows users to upgrade their Kibana instances while maintaining ROR security features.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.1.5, 9.1.4, 9.0.8, 9.0.7, 8.19.5, 8.19.4, 8.18.8, 8.18.7 support</summary>

ReadonlyREST now supports Elasticsearch versions 9.1.5, 9.1.4, 9.0.8, 9.0.7, 8.19.5, 8.19.4, 8.18.8, and 8.18.7. This update provides compatibility with the latest Elasticsearch releases across multiple version branches, ensuring users can securely run ROR with current Elasticsearch deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/ror-1-65-1-java-17/2841">Patching issue in Elasticsearch 9.x, 8.19.x, and 8.18.x that caused startup failures on Java 17</a></summary>

Fixed a compatibility issue that prevented Elasticsearch clusters from starting when using Java 17 with ROR. The patch resolves startup failures affecting Elasticsearch versions 9.x, 8.19.x, and 8.18.x, ensuring smooth operation with modern Java runtime environments.

</details>

### (2025-08-28) What's new in **ROR 1.66.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-7339">CVE-2025-7339</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-7783">CVE-2025-7783</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-54419">CVE-2025-54419</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-9288">CVE-2025-9288</a></summary>

🚨Security Fix (KBN) — Patched multiple CVEs affecting Kibana's Node.js dependencies: CVE-2025-7339 (response header manipulation via `on-headers`), CVE-2025-7783 (HTTP Parameter Pollution via `form-data`), CVE-2025-54419 (SAML assertion bypass in Node-SAML), and CVE-2025-9288 (input validation flaw in `sha.js`). These fixes address vulnerabilities ranging from data manipulation to authentication bypass, ensuring your Kibana instances remain secure.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/hidden-functions-are-available-through-the-search/2840/2">Prevented visibility of hidden functions through Kibana UI search</a></summary>

🚨Security Fix (KBN) — Fixed an issue where hidden functions (features restricted by ReadonlyREST rules) could still be discovered and accessed via the Kibana UI search bar. This patch ensures that restricted functionality remains fully hidden from users, closing a potential information disclosure and privilege escalation vector.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) Removed internal failure details from error responses to prevent unintended information disclosure</summary>

🚨Security Fix (ES) — Internal error messages previously exposed stack traces and implementation details in certain failure scenarios. This information is now stripped from error responses, preventing potential leakage of sensitive system internals that could aid an attacker.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) 9.1.3, 9.1.2, 9.0.6, 8.19.3, 8.18.6 support</summary>

🚀New (KBN) — Added compatibility with Kibana versions 9.1.3, 9.1.2, 9.0.6, 8.19.3, and 8.18.6. Users on these versions can now install and run ReadonlyREST Kibana plugin without compatibility issues.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.1.3, 9.1.2, 9.0.6, 8.19.3, 8.18.6 support</summary>

🚀New (ES) — Added compatibility with Elasticsearch versions 9.1.3, 9.1.2, 9.0.6, 8.19.3, and 8.18.6. The Elasticsearch plugin now fully supports these releases.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Refined user metadata selection logic during login to prioritize matched blocks associated with a defined Kibana index</summary>

🧐Enhancement (ES) — Improved the login flow so that when multiple ACL blocks match a user, the system now prioritizes the block that is associated with a defined Kibana index. This results in more predictable and correct user metadata assignment, especially in multi-block configurations.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Patching: improved handling of the consent flag when provided via environment variables for more reliable configuration</summary>

🧐Enhancement (ES) — Enhanced the patching mechanism to more reliably process the consent flag when it is supplied through environment variables. This reduces configuration errors and ensures smoother automated deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved issue with index deletion in <strong>Index Management</strong> via Kibana UI</summary>

🐞Fix (KBN) — Fixed a bug where users with appropriate permissions were unable to delete indices through the Kibana Index Management interface. Index deletion now works correctly when authorized by ReadonlyREST rules.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Corrected document display in <strong>Discover</strong> when indices are defined in the user ACL block</summary>

🐞Fix (KBN) — Resolved an issue where documents were not displayed correctly in the Kibana Discover section when indices were explicitly defined in the user's ACL block. Document browsing now works as expected in restricted index configurations.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an error preventing <strong>Spaces</strong> from being deleted in Kibana <strong>9.1.0</strong></summary>

🐞Fix (KBN) — Addressed a specific error that prevented users from deleting Kibana Spaces in version 9.1.0 when ReadonlyREST was active. Space management now functions correctly on this version.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Corrected handling of <code>readonlyrest_kbn.whitelistedPaths</code> in <code>kibana.yml</code> when <code>xpack.security.enabled: true</code></summary>

🐞Fix (KBN) — Fixed a configuration handling issue where the `readonlyrest_kbn.whitelistedPaths` setting in `kibana.yml` was not properly respected when `xpack.security.enabled` was set to `true`. Whitelisted paths now work reliably regardless of the xpack security setting.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved startup issues for Kibana versions <strong>7.9.0 → 7.10.2</strong></summary>

🐞Fix (KBN) — Fixed a compatibility regression that caused ReadonlyREST to fail during Kibana startup on versions 7.9.0 through 7.10.2. Users on these older Kibana releases can now run the plugin without startup errors.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed report generation when <code>xpack.security.enabled: true</code> and <code>xpack.encryptedSavedObjects.encryptionKey</code> is set in Kibana <strong>8.19.x</strong> and <strong>9.1.x</strong></summary>

🐞Fix (KBN) — Resolved an issue where report generation (e.g., PDF/CSV exports) would fail in Kibana 8.19.x and 9.1.x when both `xpack.security.enabled` and `xpack.encryptedSavedObjects.encryptionKey` were configured. Reports now generate successfully in these environments.

</details>

### (2025-07-15) What's new in **ROR 1.65.1**

<details>

<summary><strong>🚀New</strong> (KBN) 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, 8.17.9 support</summary>

ReadonlyREST now supports the latest Kibana versions including 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, and 8.17.9. This ensures compatibility with recent Kibana releases and their security patches, allowing users to upgrade their Kibana instances while maintaining ReadonlyREST security features.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, 8.17.9 support</summary>

The plugin now supports Elasticsearch versions 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, and 8.17.9. This update provides compatibility with the latest Elasticsearch releases, including security updates and performance improvements from Elastic.

</details>

<details>

<summary><strong>🚀New</strong> (ECK) 3.1.0 support</summary>

ReadonlyREST now supports Elastic Cloud on Kubernetes (ECK) version 3.1.0. This enables users running Elasticsearch on Kubernetes through ECK to leverage ReadonlyREST's security features in their containerized environments with the latest ECK release.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Docker images now start correctly when <code>I_UNDERSTAND_AND_ACCEPT_ES_PATCHING</code> is set.</summary>

Fixed an issue where Elasticsearch Docker images with ReadonlyREST would fail to start when the environment variable `I_UNDERSTAND_AND_ACCEPT_ES_PATCHING` was set. This variable is commonly used in Elasticsearch Docker deployments to acknowledge patching terms, and the fix ensures smooth container startup.

</details>

### (2025-07-10) What's new in **ROR 1.65.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-5889">CVE-2025-5889</a></summary>

A vulnerability in the `brace-expansion` library (up to versions 1.1.11, 2.0.1, 3.0.0, 4.0.0) could lead to inefficient regular expression complexity and potential denial of service. This fix addresses the issue by updating the affected dependency to a patched version.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/cve-2024-29857">CVE-2024-29857</a> (when FIPS SSL is used)</summary>

A high-severity vulnerability (CVSS 7.5) in the Bouncy Castle cryptographic library (before 1.78) could cause excessive CPU consumption and denial of service when importing an EC certificate with specially crafted F2m parameters. This fix updates the Bouncy Castle dependency and is relevant when FIPS-compliant SSL is configured.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Added support for configuring <a href="https://www.elastic.co/docs/troubleshoot/kibana/using-kibana-server-logs">JSON log format</a> in <code>kibana.yml</code>.</summary>

Administrators can now enable structured JSON logging for Kibana, making it easier to parse, index, and analyze logs with centralized log management tools like Elasticsearch and Logstash.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#configuration">Added support for a new output type: <code>data_stream</code> in audit logging</a>.</summary>

Audit events can now be stored in Elasticsearch data streams instead of regular indices. Data streams offer better lifecycle management, automatic rollover, and simplified retention policies via ILM. If the specified data stream doesn't exist, ReadonlyREST creates it automatically along with the necessary component templates and index template.

</details>

<details>

<summary><strong>🚀New</strong> (ES) Included Elasticsearch node name and cluster name in the audit reports.</summary>

Audit log entries now contain the originating Elasticsearch node name and cluster name, providing better traceability and context when auditing requests across multi-node or multi-cluster deployments.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Logged detailed messages when the CSRF token has expired.</summary>

Improved logging now provides clearer, more descriptive messages when a CSRF token expires, helping administrators diagnose and troubleshoot authentication-related issues more efficiently.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/kibana#user-info-source-methods">Added <code>id_token</code> as a valid option for <code>userInfoSource</code></a>.</summary>

Administrators can now configure the `userInfoSource` setting to use the `id_token` directly as the source of user information, providing more flexibility in OIDC-based authentication workflows.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Improved handling of JVM properties related to ROR settings.</summary>

The way ReadonlyREST processes and applies JVM property-based configuration settings has been refined, resulting in more reliable behavior and better error handling when custom JVM options are used.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed OIDC logout redirection issue by switching <code>redirect_uri</code> to <code>id_token_hint</code> and using <code>post_logout_redirect_uri</code>.</summary>

The OIDC logout flow has been corrected to use the standard `id_token_hint` parameter instead of `redirect_uri`, along with proper `post_logout_redirect_uri` handling, ensuring users are correctly redirected after logout.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) The ReadonlyREST Kibana plugin now accepts custom appender names defined in <code>kibana.yml</code>.</summary>

Previously, the plugin would reject custom logging appender names configured in Kibana's logging configuration. This fix ensures compatibility with custom appender setups.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) When "Remember Group After Logout" is enabled, groups without access are correctly ignored during login.</summary>

Fixed a bug where previously remembered groups that no longer had access permissions could still be applied during re-authentication. Now only groups with valid access are considered.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed issue where the Kibana index template was not applied for Kibana versions ≥ 8.8.0.</summary>

A compatibility issue with Kibana 8.8.0 and newer prevented the ROR index template from being properly applied. This fix restores correct template application for these versions.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a bug with <code>readonlyrest_kbn.resetKibanaIndexToTemplate: true</code> for Kibana 7.x.</summary>

The index reset functionality, which restores the Kibana index to match the expected template, was not working correctly on Kibana 7.x. This fix ensures the setting behaves as intended.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an issue where a custom session index name was not respected after Kibana restart.</summary>

When a custom session index name was configured, Kibana would revert to the default session index after a restart. This fix ensures the custom session index name is persisted and used correctly across restarts.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed an issue preventing snapshots from being restored when no indices were specified.</summary>

Restoring a snapshot without explicitly specifying indices (i.e., restoring all indices) was failing under certain conditions. This fix ensures that snapshot restore operations work correctly even when no index list is provided.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) File ownership and permissions are now preserved during <code>ror-tools</code> patch and unpatch operations.</summary>

Previously, running `ror-tools` to patch or unpatch Elasticsearch could alter file ownership and permissions. This fix ensures that the original file attributes are maintained throughout the patching process.

</details>

### (2025-05-17) What's new in **ROR 1.64.2**

<details>

<summary><strong>🚀New</strong> (KBN) 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, 7.17.29 support</summary>

ReadonlyREST now supports the latest Kibana versions including 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, and 7.17.29. This ensures compatibility with recent Kibana releases and their security updates.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, 7.17.29 support</summary>

ReadonlyREST now supports the latest Elasticsearch versions including 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, and 7.17.29. This provides compatibility with recent Elasticsearch releases and their security patches.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/ror-1-64-0-for-es9-0-1-windows-setup/2778">Fixed an issue with Elasticsearch patching process on Windows operating systems</a></summary>

Resolved a Windows-specific error that occurred during the Elasticsearch patching process with ReadonlyREST. The issue was successfully reproduced by the support team and fixed to ensure smooth installation on Windows environments.

</details>

### (2025-05-13) What's new in **ROR 1.64.1**

<details>

<summary><strong>🐞Fix</strong> (ES) Correct patching verification in ROR Docker image entrypoint</summary>

This fix addresses an issue in the Docker image entrypoint script where patching verification was not functioning correctly. The entrypoint script, which handles the application of security patches and configuration updates, now properly validates that patches are applied successfully before proceeding with container startup, ensuring reliable deployment of ROR-secured Elasticsearch instances.

</details>

### (2025-05-11) What's new in **ROR 1.64.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-53382">CVE-2024-53382</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-27789">CVE-2025-27789</a>, <a href="https://www.cve.org/CVERecord?id=CVE-2025-29774">CVE-2025-29774</a></summary>

This release addresses three security vulnerabilities in Kibana dependencies: CVE-2024-53382 is a DOM clobbering XSS vulnerability in PrismJS syntax highlighter (versions ≤1.29.0), CVE-2025-27789 is a performance/DoS issue in Babel's regex polyfill with quadratic complexity, and CVE-2025-29774 (details not fully available). These fixes prevent potential cross-site scripting attacks and denial of service scenarios.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-3894">CVE-2023-3894</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-25193">CVE-2025-25193</a></summary>

This release patches two Elasticsearch-related vulnerabilities: CVE-2023-3894 is a Denial of Service vulnerability in jackson-dataformats-text library (versions <2.15.0) that could cause stack overflow when parsing malicious TOML data, and CVE-2025-25193 is a Windows-specific DoS vulnerability in Netty (versions ≤4.1.118.Final) where large environment files could crash the application. These fixes enhance system stability and security.

</details>

<details>

<summary><strong>⚠️Warning</strong> (ES) Acknowledgement needs to be accepted before the Elasticsearch patching process. For scripts, you can <a href="https://docs.readonlyrest.com/elasticsearch#id-3.-patch-elasticsearch">set the flag</a> to automate the process.</summary>

When patching Elasticsearch for ReadonlyREST installation, users must now explicitly acknowledge the patching process. For automated deployments, administrators can set a configuration flag to bypass the manual acknowledgement, enabling script-based automation of the patching workflow.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Added an endpoint to retrieve all user tenancies via the ReadonlyREST API. See the <a href="https://portal.readonlyrest.com/docs/swagger/master#/User&#x27;s%20tenants/get_api_ror_user_tenants">ReadonlyREST API Documentation</a> for usage details.</summary>

A new API endpoint has been added to retrieve all tenancies associated with a user. This enables programmatic access to multi-tenancy information, allowing administrators and applications to query and manage user tenancy assignments through the ReadonlyREST API interface.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Introduced support for passing <code>x-ror-tenancy-id</code> in direct Kibana requests. See the <a href="https://portal.readonlyrest.com/docs/swagger/master#/Example%20ReadonlyREST%20headers%20usage%20with%20Kibana%20API/get_api__">ReadonlyREST API Documentation</a> for details.</summary>

Direct Kibana API requests can now include the `x-ror-tenancy-id` header to specify the target tenancy context. This allows applications and scripts to make requests within specific tenancy contexts without relying on session-based tenancy selection, improving automation and integration capabilities.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Introduced support for passing <code>x-ror-impersonating</code> in direct Kibana requests. See the <a href="https://portal.readonlyrest.com/docs/swagger/master#/Example%20ReadonlyREST%20headers%20usage%20with%20Kibana%20API/get_api__">ReadonlyREST API Documentation</a> for details.</summary>

The new `x-ror-impersonating` header enables administrators to make Kibana API requests on behalf of other users. This feature supports administrative workflows where privileged users need to perform actions or troubleshoot issues within another user's security context while maintaining audit trails.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Retains the currently selected group information after user logout. This setting is user-configurable and disabled by default.</summary>

Kibana now optionally preserves the user's selected group/tenancy information across logout/login cycles. This user-preference setting (disabled by default) improves user experience by maintaining context between sessions, reducing the need to reselect groups upon each login.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Displays <a href="https://docs.readonlyrest.com/elasticsearch#unauthorized-response-configuration">detailed "reason" messages from the ROR Elasticsearch</a> response in the login form instead of a generic "Wrong credentials" message.</summary>

Login failures now show specific error messages from Elasticsearch's ReadonlyREST plugin rather than generic "Wrong credentials" messages. This provides users with actionable feedback about authentication issues, such as account lockouts, expired credentials, or specific authorization failures.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Added support for passing additional <a href="https://docs.readonlyrest.com/kibana#additional-parameters">SAML</a> and <a href="https://docs.readonlyrest.com/kibana#additional-parameters">OIDC</a> config parameters via <code>kibana.yml</code>.</summary>

Extended configuration options for SAML and OIDC authentication providers can now be specified directly in kibana.yml. This allows administrators to customize authentication flows with provider-specific parameters without modifying plugin code, enhancing integration flexibility with enterprise identity systems.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Adjusted ReadonlyREST plugin UI styles for compatibility with Kibana 9.x.</summary>

The ReadonlyREST plugin interface has been updated with CSS and styling adjustments to ensure proper display and functionality within Kibana 9.x environments. This maintains visual consistency and usability as Kibana evolves its user interface framework.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Username duplication check in the "users" section of ROR ES settings can <a href="https://docs.readonlyrest.com/elasticsearch#users_section_duplicate_usernames_detection">be optionally disabled</a>.</summary>

Administrators can now optionally disable the duplicate username validation in Elasticsearch settings. This provides flexibility for complex deployment scenarios where username duplication might be intentional or managed through external systems, while maintaining the default validation for security.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Added support for <a href="https://docs.readonlyrest.com/elasticsearch#global-settings"><code>readonlyrest.global_settings</code></a> in Elasticsearch ROR settings.</summary>

Elasticsearch configuration now supports `readonlyrest.global_settings` for centralized management of plugin-wide parameters. This enables consistent configuration across clusters and simplifies administration by separating global settings from rule-specific configurations.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved an unhandled error when <code>logging.root.level</code> is set to <code>all</code> in <code>kibana.yml</code>.</summary>

Fixed a crash that occurred when Kibana's logging.root.level was configured as "all" in kibana.yml. The plugin now properly handles this logging configuration, preventing startup failures and ensuring compatibility with verbose logging settings for debugging purposes.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an issue with retrieving username and group information in AFDS OIDC.</summary>

Corrected a bug where Azure AD Federated Services (AFDS) OIDC authentication failed to properly extract username and group information from identity tokens. This fix ensures proper user identification and group-based authorization for Azure AD-integrated deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an issue with passing <code>x-ror-correlation-id</code> to the ReadonlyREST API request.</summary>

Resolved a problem where the `x-ror-correlation-id` header was not being properly passed through to ReadonlyREST API requests. This fix ensures correlation IDs are correctly transmitted for request tracing, debugging, and audit logging across the authentication and authorization pipeline.

</details>

### (2025-03-12) What's new in **ROR 1.63.0**

&#x20;       **🚨Security Fix** (KBN) [CVE-2025-26791](https://www.cve.org/CVERecord?id=CVE-2025-26791), [CWE-772](https://cwe.mitre.org/data/definitions/772.html)

&#x20;       **🚨Security Fix** (ES) [CVE-2024-57699](https://nvd.nist.gov/vuln/detail/CVE-2024-53990) [CVE-2025-25193](https://nvd.nist.gov/vuln/detail/CVE-2025-25193) [CVE-2025-24970](https://nvd.nist.gov/vuln/detail/CVE-2025-24970)

&#x20;       **🚀New** (KBN) 9.0.1, 9.0.0, 9.0.0-rc1, 9.0.0-beta1, 8.18.1, 8.18.0, 8.17.6, 8.17.5, 8.17.4, 8.16.6 support

&#x20;       **🚀New** (ES) 9.0.1, 9.0.0, 9.0.0-rc1, 9.0.0-beta1, 8.18.1, 8.18.0, 8.17.6, 8.17.5, 8.17.4, 8.16.6 support

&#x20;       **🚀New** (ES) [Added `groups_not_any_of` and `groups_not_all_of` rules](https://forum.readonlyrest.com/t/support-kbn-ent-managing-forbidden-messages/2623)

&#x20;       **🚀New** (ES) [New unified and simplified syntax for groups rules](https://docs.readonlyrest.com/elasticsearch#groups-rules)

&#x20;       **🧐Enhancement** (KBN) For Kibana >= 8.14.0: Added backward compatibility to hide the Dashboard app by declaring Analytics|Dashboard and Analytics|Dashboards in the `kibana.hide_apps` rule

&#x20;       **🧐Enhancement** (KBN) Added information about skipping patching confirmation prompt to the patching helper

&#x20;       **🧐Enhancement** (KBN) \[When Kibana is opened in multiple browser tabs, logging into Kibana in one tab automatically logs in all browser tabs]

&#x20;       **🐞Fix** (KBN) Don't terminate Kibana when disk reaches low watermark

&#x20;       **🐞Fix** (KBN) For Kibana >= 8.15.0: Added support for reporting data stream multitenancy

&#x20;       **🐞Fix** (KBN) Silenced "Error fetching fields for index pattern" toast messages due to forbidden response in Kibana Dashboard and Discover page

&#x20;       **🐞Fix** (KBN) For Kibana >= 8.17.0: Fixed Elasticsearch navigation header being visible when `kibana.hide_apps: [ "Elasticsearch" ]`

&#x20;       **🐞Fix** (KBN) [For Kibana >= 8.5.0: Fixed Dev tools play buttons not being visible for RO users](https://forum.readonlyrest.com/t/ldap-multitenancy-with-no-group-name-to-index-name-relation/2742/8)

&#x20;       **🐞Fix** (KBN) Fixed an issue with hiding the dashboard app when using regular expressions in the kibana\_hide\_apps field

&#x20;       **🐞Fix** (ES) Fixed various issues with restoring snapshot API

&#x20;       **🐞Fix** (ES) Fixed data streams, index, and component templates being forbidden for RW users in stack management

### (2025-01-24) What's new in **ROR 1.62.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2024-53990](https://nvd.nist.gov/vuln/detail/CVE-2024-53990)

&#x20;       **🚨Security Fix** (KBN) [CVE-2024-21538](https://www.cve.org/CVERecord?id=CVE-2024-21538), [CVE-2024-47764](https://www.cve.org/CVERecord?id=CVE-2024-47764), [CVE-2024-52798](https://www.cve.org/CVERecord?id=CVE-2024-52798)

&#x20;       **⚠️Warning** (KBN) Updated [`readonlyrest_kbn: license: activationKeyRefreshInterval`](https://forum.readonlyrest.com/t/restricting-access-to-some-spaces/2633/4) - the maximum refresh interval is now set to 1 day.

&#x20;       **🚀New** (ES|KBN) Introduced support for [Elastic APM (Application Performance Monitoring)](https://www.elastic.co/observability/application-performance-monitoring).

&#x20;       **🚀New** (KBN) 8.17.3, 8.17.2, 8.17.1, 8.16.5, 8.16.4, 8.16.3, 7.17.28 support

&#x20;       **🚀New** (ES) 8.17.3, 8.17.2, 8.17.1, 8.16.5, 8.16.4, 8.16.3, 7.17.28 support

&#x20;       **🚀New** (KBN) Added [Kibana images with the preinstalled ReadonlyREST plugin for the arm64 platform](https://hub.docker.com/r/beshultd/kibana-readonlyrest) on Docker Hub.

&#x20;       **🚀New** (ES) Added [Elasticsearch images with the preinstalled ReadonlyREST plugin for the arm64 platform](https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest) on Docker Hub.

&#x20;       **🧐Enhancement** (ES) [Introduced validation to prevent multiple username entries in the users section.](https://forum.readonlyrest.com/t/ror-1-57-3-es-8-13-2-double-usernames-allowed/2621/2)

&#x20;       **🐞Fix** (KBN) [Resolved an issue with exit patching-based commands.](https://forum.readonlyrest.com/t/restricting-access-to-some-spaces/2633/6)

&#x20;       **🐞Fix** (KBN) Addressed a bug in Kibana 8.16.0 and later versions to hide the permissions tab in a space.

&#x20;       **🐞Fix** (KBN) Fixed a compatibility issue where OIDC and SAML didn't work in Kibana versions earlier than 7.11.0.

&#x20;       **🐞Fix** (KBN) Ensured user settings are overridden only for the default space.

&#x20;       **🐞Fix** (ES) Relaxed restrictions on snapshot restoration during index checks.

&#x20;       **🐞Fix** (ES) Resolved issue with Stack Monitoring access when `xpack.security.enabled: true` is configured.

### (2024-11-20) What's new in **ROR 1.61.1**

&#x20;       **🚨Security Fix** (ES) [Data leak through the ESQL API](https://forum.readonlyrest.com/t/eql-requests-returns-data-even-though-they-aren-t-allowed/2679) (for ES >= 8.11.0)

&#x20;       **🚨Security Fix** (KBN) [CVE-2024-21538](https://www.cve.org/CVERecord?id=CVE-2024-21538), [CVE-2024-47764](https://www.cve.org/CVERecord?id=CVE-2024-47764)

&#x20;       **🚨Security Fix** (ES) [CVE-2024-47535](https://nvd.nist.gov/vuln/detail/CVE-2024-47535)

&#x20;       **🚀New** (KBN) 8.17.0, 8.16.2, 8.16.1, 8.16.0, 8.15.5, 7.17.27, 7.17.26 support

&#x20;       **🚀New** (ES) 8.17.0, 8.16.2, 8.16.1, 8.15.5, 7.17.27, 7.17.26 support

&#x20;       **🚀New** (ES) ESQL support

&#x20;       **🐞Fix** (KBN) Elasticsearch red status shouldn't kill the Kibana process on initialization

### (2024-11-12) What's new in **ROR 1.61.0**

&#x20;       **🚨Security Fix** (KBN) [CVE-2024-47764](https://www.cve.org/CVERecord?id=CVE-2024-47764)

&#x20;       **⚠️Warning** (KBN) Acknowledgement needs to be accepted before a Kibana patching process. For scripts, you can [set a flag](https://docs.readonlyrest.com/kibana#patching-kibana) to automate a process (edited)

&#x20;       **🚀New** (KBN) 8.15.4 support

&#x20;       **🚀New** (ES) 8.16.0, 8.15.4 support

&#x20;       **🚀New** (ES) There is an option to define [a custom response for users in ACL block with the 'forbid' policy](https://docs.readonlyrest.com/elasticsearch#unauthorized-response-configuration)

&#x20;       **🧐Enhancement** (KBN) Set-Cookie is not returned with KBN API response

&#x20;       **🧐Enhancement** (KBN) Reduce the amount of ReadonlyREST session updates

&#x20;       **🧐Enhancement** (KBN) Kibana plugin won't start until the connection with Elasticsearch is established

&#x20;       **🧐Enhancement** (KBN) API and activation key tabs in the Security settings are visible only for the admin or unrestricted access users

&#x20;       **🧐Enhancement** (KBN) detecting issues related to high disk watermark warning

&#x20;       **🧐Enhancement** (KBN) License expiration info only for admin and unrestricted access users

&#x20;       **🧐Enhancement** (ES) index exclusion (dash) syntax support

&#x20;       **🐞Fix** (KBN) Don't stop Kibana when correlationId is not available in the session

&#x20;       **🐞Fix** (KBN) Provide additional [SAML configuration options](https://docs.readonlyrest.com/kibana#usage-with-active-directory-federation-services) to handle Active Directory Federation Services (ADFS) properly

&#x20;       **🐞Fix** (KBN) login page customization should be a PRO feature instead of an Enterprise

&#x20;       **🐞Fix** (KBN) Logging to file doesn't work for Kibana 8.x

&#x20;       **🐞Fix** (ES) Snapshot Status API - forbidden response while checking the status of all snapshots of the given repository

&#x20;       **🐞Fix** (ES) Snapshot API - misc issues for ES 6.x

### (2024-09-15) What's new in **ROR 1.60.0**

&#x20;       **🚀New** (KBN) 8.15.3, 8.15.2, 7.17.25 support

&#x20;       **🚀New** (ES) 8.15.3, 8.15.2, 7.17.25 support

&#x20;       **🚀New** (KBN|ES) [ECK support documentation](https://docs.readonlyrest.com/eck)

&#x20;       **🚀New** (ES) configurable ROR YAML settings max size

&#x20;       **⚠️Warning** (ES) The prompt for basic authorization is disabled by default. To keep the previous behavior, set `readonlyrest.prompt_for_basic_auth` to `true` in the ROR configuration

&#x20;       **🧐Enhancement** (KBN) There is an option to define [client authentication methods](https://docs.readonlyrest.com/kibana#client-authentication-methods) in the `kibana.yml` via `readonlyrest_kbn.auth.<YOUR_OIDC_CONFIG>.tokenEndpointAuthMethod`, 'client\_secret\_post' or ''client\_secret\_basic'

&#x20;       **🧐Enhancement** (KBN) Stop Kibana when enabled features are not available

&#x20;       **🐞Fix** (KBN) HTTP 400 (bad request) issue when there is a Nginx proxy server between es and Kibana

&#x20;       **🐞Fix** (KBN) Fix for the problem with correctly hiding Management features `ROR Manage Kibana` defined in the readonlyrest.yml `kibana_hide_apps` property

&#x20;       **🐞Fix** (ES) ROR KBN docker image: passing ROR settings as ENVs fixes

&#x20;       **🐞Fix** (ES) [Data stream backing indices access issue with the indices rule](https://forum.readonlyrest.com/t/requested-index-doesnt-exist/2573)

&#x20;       **🐞Fix** (ES) [Fix for the problem with remote access to data stream aliases](https://forum.readonlyrest.com/t/requested-index-doesnt-exist/2573)

### (2024-08-01) What's new in **ROR 1.59.0**

&#x20;       **🚀New** (ES) 8.15.1, 8.15.0, 7.17.24, 7.17.23, 6.7.x support

&#x20;       **🚀New** (KBN) 8.15.1, 8.15.0, 7.17.24, 7.17.23 support

&#x20;       **🧐Enhancement** (KBN) Replace a broken Alert and Connectors applications with the link to our [new tool](https://anaphora.it) for Reports and alerting for Kibana > 8.6.0 (edited)

&#x20;       **🐞Fix** (KBN) Handling reporting URL for report generation

&#x20;       **🐞Fix** (KBN) Embedding with inline JWT is a feature available only in ReadonlyREST PRO and Enterprise

&#x20;       **🐞Fix** (ES) [Patcher `UnsupportedOperationException` issue on Windows](https://forum.readonlyrest.com/t/ror-1-58-0-for-es8-14-3-windows-setup/2577)

&#x20;       **🐞Fix** (ES) for the problem with `_async_search` on ES 8.14.x

### (2024-06-30) What's new in **ROR 1.58.0**

&#x20;       **🚨Security Fix** (KBN) [CVE-2022-39353](https://www.cve.org/CVERecord?id=CVE-2022-39353), [CVE-2020-7753](https://www.cve.org/CVERecord?id=CVE-2020-7753), [CVE-2022-37616](https://www.cve.org/CVERecord?id=CVE-2022-37616), [CVE-2024-29041](https://www.cve.org/CVERecord?id=CVE-2024-29041), [CVE-2022-0691](https://www.cve.org/CVERecord?id=CVE-2022-0691), [CVE-2021-3801](https://www.cve.org/CVERecord?id=CVE-2021-3801), [CVE-2022-25883](https://www.cve.org/CVERecord?id=CVE-2022-25883), [CVE-2022-0512](https://www.cve.org/CVERecord?id=CVE-2022-0512), [CVE-2022-0686](https://www.cve.org/CVERecord?id=CVE-2022-0686), [CVE-2022-0639](https://www.cve.org/CVERecord?id=CVE-2022-0639), [CVE-2022-25881](https://www.cve.org/CVERecord?id=CVE-2022-25881), [CVE-2023-0842](https://www.cve.org/CVERecord?id=CVE-2023-0842), [CVE-2017-16137](https://www.cve.org/CVERecord?id=CVE-2017-16137), [CVE-2022-33987](https://www.cve.org/CVERecord?id=CVE-2022-33987), [CVE-2022-23647](https://www.cve.org/CVERecord?id=CVE-2022-23647), [CVE-2022-36083](https://www.cve.org/CVERecord?id=CVE-2022-36083), [CVE-2024-28176](https://www.cve.org/CVERecord?id=CVE-2024-28176)

&#x20;       **🚀New** (KBN) [Kibana images with preinstalled ReadonlyREST plugin in Docker Hub](https://hub.docker.com/r/beshultd/kibana-readonlyrest)

&#x20;       **🚀New** (KBN) 8.14.3, 8.14.2 support

&#x20;       **🚀New** (ES) 8.14.3, 8.14.2 support

&#x20;       **🚀New** (ES) ["structured groups" feature](https://github.com/beshu-tech/readonlyrest-docs/blob/develop/details/structured-groups.md) (authorization rules group names and group IDs can be defined separately)

&#x20;       **🧐Enhancement** (KBN) New `readonlyrest_kbn.cookies.secure` and `readonlyrest_kbn.cookies.sameSite` cookie settings via kibana.yml

&#x20;       **🧐Enhancement** (ES) improved error logging on the creation of LDAP connectors

&#x20;       **🧐Enhancement** (ES) Patcher - invalid state after patching detection improvements

&#x20;       **🐞Fix** (KBN) Impersonation and session probe logout issue

&#x20;       **🐞Fix** (KBN) [Problem with the number of replicas and index template, where the number of replicas was always set to 1. Now, the default value will be the same, as in the case of the Kibana index](https://forum.readonlyrest.com/t/0-replicas-for-single-node-clusters/2530)

&#x20;       **🐞Fix** (KBN) Fix problem with multi-tenancy features when xpack.security.enabled: true

### (2024-05-18) What's new in **ROR 1.57.3**

&#x20;       **🚨Security Fix** (ES) [CVE-2024-34447](https://nvd.nist.gov/vuln/detail/CVE-2024-34447)

&#x20;       **🚀New** (KBN) 8.14.1, 8.14.0, 7.17.22 support

&#x20;       **🚀New** (ES) 8.14.1, 8.14.0, 7.17.22 support

&#x20;       **🐞Fix** (KBN) The CSRF cookie name issue that caused the "Wrong credentials" error during login

&#x20;       **🐞Fix** (KBN) Automatic migration issue for Kibana >= 8.8.0 that caused the "mapping set to strict, dynamic introduction of... error

### (2024-05-05) What's new in **ROR 1.57.2**

&#x20;       **🚀New** (KBN) 8.13.4, 8.13.3, 7.17.21 support

&#x20;       **🚀New** (ES) 8.13.4, 8.13.3, 7.17.21 support

&#x20;       **🐞Fix** (KBN) Kibana <= 7.2.1 doesn't run

&#x20;       **🐞Fix** (KBN) Provides a way to migrate an existing session index to the new session

&#x20;       **🐞Fix** (ES) [Patching issue for Elasticsearch installed from packages](https://forum.readonlyrest.com/t/bootstrap-error-es/2574)

&#x20;       **🐞Fix** (ES) Patching issue for Elasticsearch OSS versions

### (2024-04-29) What's new in **ROR 1.57.1**

&#x20;       **🐞Fix** (ES) configuration parsing regression: one group definition can be a string

### (2024-04-28) What's new in **ROR 1.57.0**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-29025">CVE-2024-29025</a></summary>

This vulnerability affects the Netty `HttpPostRequestDecoder`, which could be exploited to accumulate unlimited data from chunked POST requests containing many small fields, potentially leading to a denial of service. The fix addresses this by properly limiting the accumulated data in the decoder.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch#configuration-notes">LDAP Connector</a> feature: groups server-side filtering</summary>

The LDAP connector now supports filtering groups directly on the LDAP server side, reducing the amount of data transferred and improving performance when dealing with large directories.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch#configuration-notes">LDAP Connector</a> feature: skip user search option when user attribute is <code>cn</code></summary>

A new configuration option allows skipping the user search phase when the user attribute is set to `cn` (Common Name), streamlining authentication for LDAP setups where the bind DN directly matches the user's CN.

</details>

<details>

<summary><strong>⚠️Warning</strong> (KBN|ES) Internal API incompatibilities (to take advantage of rolling update capabilities, upgrade ROR ES first)</summary>

Due to internal API changes, it is recommended to upgrade the ROR Elasticsearch plugin first before upgrading the Kibana plugin. This order allows you to leverage rolling update capabilities and minimize downtime during the upgrade process.

</details>

<details>

<summary><strong>⚠️Warning</strong> (ES) Support for ES &#x3C; 6.8.0 was dropped</summary>

This release no longer supports Elasticsearch versions older than 6.8.0. Users running older versions should plan an upgrade to a supported Elasticsearch version before updating ROR.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) User settings available for all access type users</summary>

User settings in the Kibana plugin are now accessible to all user access types, not just administrators, giving end users more control over their experience.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Add option to change the Default Route and Time zone in User settings</summary>

Users can now configure their default landing page (route) and time zone directly from the user settings interface in Kibana, improving personalization.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Provide correlation ID to Kibana logs</summary>

A correlation ID is now included in Kibana logs, making it easier to trace requests across the system and troubleshoot issues by correlating log entries.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Rich, context-based debug logging in the LDAP connector and LDAP-related rules</summary>

Debug logging has been significantly improved for the LDAP connector and related authentication/authorization rules, providing more detailed and contextual information to help administrators diagnose LDAP integration issues.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Additional <a href="https://docs.readonlyrest.com/elasticsearch#configuring-an-acl-with-filter-fields-rules-when-using-kibana">validations</a>: <code>kibana</code> rule should not be used with some other rules in the same block</summary>

New configuration validations have been added to warn when the `kibana` rule is combined with incompatible rules (such as `fields` or `filter`) in the same ACL block, helping prevent misconfigurations that could lead to unexpected behavior.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Sometimes reports are not generated correctly for Kibana &#x3C; 8.0.0 and the "Max attempt reached" error appears</summary>

A bug causing report generation failures in Kibana versions prior to 8.0.0 has been fixed. The issue previously resulted in a "Max attempt reached" error, preventing successful report creation.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Adjust interactive API swagger dark mode colors</summary>

The color scheme for the interactive Swagger API documentation in dark mode has been adjusted to improve readability and visual consistency.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) CSRF problem when multiple ECK Kibana instances</summary>

A Cross-Site Request Forgery (CSRF) issue that occurred when running multiple Kibana instances managed by ECK (Elastic Cloud on Kubernetes) has been resolved, ensuring secure communication between instances.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Plugin doesn't run for a version Kibana &#x3C; 7.11.0 when the OIDC proxy is enabled</summary>

A compatibility issue has been fixed where the ROR Kibana plugin would fail to start on Kibana versions older than 7.11.0 when an OIDC proxy was enabled.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Session probe should log out the user when empty metadata was returned from ES ROR</summary>

The session probe now properly logs out the user when the Elasticsearch ROR plugin returns empty metadata, preventing stale or invalid sessions from persisting.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Misc issues when <code>xpack.security.enabled: true</code> is set</summary>

Various miscellaneous issues that occurred when X-Pack security was enabled alongside ReadonlyREST have been resolved, improving interoperability between the two security layers.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Patched files permission issue</summary>

A file permission issue affecting patched Elasticsearch files has been fixed, ensuring that the patching process correctly sets the appropriate permissions for all modified files.

</details>

### (2024-03-15) What's new in **ROR 1.56.0**

&#x20;       **🚀New** (KBN) Provide a way to switch light/dark mode per user

&#x20;       **🚀New** (KBN) 8.13.2, 8.13.1, 8.13.0, 7.17.20, 7.17.19 support

&#x20;       **🚀New** (ES) 8.13.2, 8.13.1, 8.13.0, 7.17.20, 7.17.19 support

&#x20;       **⚠️Warning** (ES) [for ES > 6.5 patching is required since this version of ROR](https://docs.readonlyrest.com/elasticsearch#id-5.-patch-elasticsearch)

&#x20;       **🧐Enhancement** (KBN) The activation key will be revalidated in the interval

&#x20;       **🧐Enhancement** (KBN) Provide a way to define Activation key [retrieval mode](https://docs.readonlyrest.com/v/develop/universal-builds#change-activation-key-retrieval-mode-via-kibana.yml)

&#x20;       **🐞Fix** (KBN) Sometimes reports are not generated correctly for Kibana >= 8.0.0 and "Max attempt reached" error appears

&#x20;       **🐞Fix** (KBN) The OIDC scope configuration property was not applied and the default configuration was used instead.

&#x20;       **🐞Fix** (KBN) The OIDC proxy parameter was not handled properly in case of HTTPs connection over HTTP proxy server

&#x20;       **🐞Fix** (KBN) Missing information when Kibana is not patched

&#x20;       **🐞Fix** (ES) [Repositories and Snapshots handling by ES coordinating nodes](https://forum.readonlyrest.com/t/snapshot-status-cannot-modify-incoming-request/2471)

&#x20;       **🐞Fix** (ES) [Internode SSL `certificate_verification: true` was causing problems with nodes discovery](https://forum.readonlyrest.com/t/upgrade-elasticsearch-8-2-to-8-x-leads-to-ssl-problems/2480)

&#x20;       **🐞Fix** (ES) Missing `x-elastic-product` header in the response when `fields` and `filter` rules were used

&#x20;       **🐞Fix** (ES) Proper `forbid` policy handling during processing ROR login request

&#x20;       **🐞Fix** (ES) `application/nd-json` media type handling (in case of ES `7.x` versions)

### (2024-01-29) What's new in **ROR 1.55.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-51074](https://nvd.nist.gov/vuln/detail/CVE-2023-51074)

&#x20;       **🚀New** (KBN) 8.12.2 ,8.12.1, 7.17.18, 7.17.17 support

&#x20;       **🚀New** (ES) 8.12.2, 8.12.1, 7.17.18 support

&#x20;       **🚀New** (ES) [Elasticsearch images with preinstalled ReadonlyREST plugin in Docker Hub](https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest)

&#x20;       **🧐Enhancement** (KBN) Optional `readonlyrest_kbn.auth.oidc_kc.proxyURL` kibana.yml configuration for the OIDC connection which allows declaring your proxy URL

&#x20;       **🧐Enhancement** (KBN) Upon successful activation and edition changes all sessions are cleared and users are logged out

&#x20;       **🐞Fix** (KBN) Saved objects are not visible for the users on Kibana >= 8.8.0

&#x20;       **🐞Fix** (ES) [LDAP nested group IDs are properly escaped](https://forum.readonlyrest.com/t/support-kbn-ent-ldap-and-parentheses/2466)

&#x20;       **🐞Fix** (ES) Logout when a user with restricted `kibana.access` tried to see a restoration status of snapshots in Kibana

### (2023-12-17) What's new in **ROR 1.54.0**

&#x20;       **🚨Security Fix** (ES) [Scroll API: protected data could leak when the `fields` rule was used with `fls_engine` set to `es` or `es_with_lucene`](https://forum.readonlyrest.com/t/field-rule-not-working-when-exceeding-a-certain-no-of-docs/2415/7)

&#x20;       **🚀New** (KBN) 8.12.0, 8.11.4 support

&#x20;       **🚀New** (ES) 8.12.0, 8.11.4, 7.17.17 support

&#x20;       **🧐Enhancement** (KBN) Provide automatic [cleaning of stale sessions](https://docs.readonlyrest.com/kibana#automatic-session-cleanup)

&#x20;       **🧐Enhancement** (KBN) Provide automatic cleaning of stale CSRF cookies

&#x20;       **🐞Fix** (KBN) Adjust the ROR API POST license endpoint body to the contract to respect the `license` body parameter instead of a `token`

&#x20;       **🐞Fix** (KBN) \`CorelationId\`\` is changed on every session refresh

&#x20;       **🐞Fix** (ES) ["missing authorization info" problem in some situations when `xpack.security.enabled` was configured to be `true`](https://forum.readonlyrest.com/t/diana-eck/2298/75)

### (2023-11-20) What's new in **ROR 1.53.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-4586](https://nvd.nist.gov/vuln/detail/CVE-2023-4586), [CVE-2023-5072](https://nvd.nist.gov/vuln/detail/CVE-2023-5072)

&#x20;       **🚀New** (KBN) 8.11.3, 8.11.2, 8.11.1, 8.11.0, 7.17.16 support

&#x20;       **🚀New** (ES) 8.11.3, 8.11.2, 8.11.1, 8.11.0, 7.17.16 support

&#x20;       **🧐Enhancement** (KBN) Provide Activate license endpoint to the ReadonlyREST API

&#x20;       **🧐Enhancement** (ES) [when the `kibana` rule and the `indices` rule are defined in the same block](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#index), there is no need to explicitly allow kibana-related indices

&#x20;       **🐞Fix** (KBN) problem with reports generation when `kibana.index` in kibana.yml is used

&#x20;       **🐞Fix** (KBN) crash loop during license service initialization

&#x20;       **🐞Fix** (KBN) problem with logging in in KBN 7.17.13 (and above) and 8.10.4 (and above) when deployed using ECK

&#x20;       **🐞Fix** (KBN) [problem with multi-tenancy and ECK](https://forum.readonlyrest.com/t/multi-tanancy-issue/2427)

&#x20;       **🐞Fix** (KBN) problem with forbidden `/_create/config` response on Login to the Kibana

&#x20;       **🐞Fix** (ES) [patching fix, when a non-default ES path is used (e.g. on K8s)](https://forum.readonlyrest.com/t/getting-java-lang-illegalargumentexception-when-initializing-ror-in-es-8-10-4/2441)

### (2023-10-09) What's new in **ROR 1.52.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-4586](https://access.redhat.com/security/cve/cve-2023-4586)

&#x20;       **🚀New** (KBN) 8.10.4, 8.10.3, 7.17.15, 7.17.14 support

&#x20;       **🚀New** (ES) 8.10.4, 8.10.3, 7.17.15, 7.17.14 support

&#x20;       **🚀New** (ES) [New `token_authentication` rule](https://docs.readonlyrest.com/elasticsearch#token_authentication)

&#x20;       **🧐Enhancement** (KBN) Permanently hide Kibana|ES features that are impossible to support

&#x20;       **🧐Enhancement** (KBN) [License expiration reminder](https://forum.readonlyrest.com/t/license-expiration-reminder/2417)

&#x20;       **🧐Enhancement** (KBN) Make `kibana.index` setting from kibana.yml an invalid property for an Enterprise user

&#x20;       **🐞Fix** (KBN) Issue with not adding `elasticsearch.customHeaders` setting from kibana.yml to ROR requests

&#x20;       **🐞Fix** (KBN) Logout after opening Stack management Upgrading assistant

&#x20;       **🐞Fix** (KBN) Problem with logging in of two users in two tabs when two Kibana instances are used

&#x20;       **🐞Fix** (KBN) Problem with logging in when multi-tenancy is enabled and the `indices` rule is defined in the ROR settings

### (2023-09-25) What's new in **ROR 1.51.1**

&#x20;       **🚨Security Fix** (ES) [`fields` rule didn't work well in the case of ES 7.10.0 and later and more than 10 documents in the response](https://forum.readonlyrest.com/t/field-rule-not-working-when-exceeding-a-certain-no-of-docs/2415)

&#x20;       **🐞Fix** (KBN) issue with Observability Overview-based applications hiding

&#x20;       **🐞Fix** (KBN) Correct `kibana.index` handling for KBN >= 7.9.0 when multi-tenancy is disabled or unavailable

&#x20;       **🐞Fix** (KBN) Unrestricted Kibana Access on the tenancy switch when a selected tenant is not available anymore

&#x20;       **🐞Fix** (KBN) Unhandled error during login when `multiTenancyEnabled: false`

&#x20;       **🐞Fix** (ES) LDAP connectivity improvements

### (2023-09-10) What's new in **ROR 1.51.0**

&#x20;       **🚨Security Fix** (KBN) the issue with [api\_only](https://docs.readonlyrest.com/elasticsearch#kibana-related-rules) access level user and accessing via Kibana UI

&#x20;       **🚀New** (KBN) 8.10.2, 8.10.1, 8.9.2, 7.17.13 support

&#x20;       **🚀New** (ES) 8.10.2, 8.10.1, 8.10.0, 8.9.2, 7.17.13 support

&#x20;       **🚀New** (ES) [Dynamic variables transformation support](https://docs.readonlyrest.com/elasticsearch#variables-functions)

&#x20;       **🧐Enhancement** (KBN) Expose interactive Swagger as a new Security settings tab

&#x20;       **🧐Enhancement** (KBN) Provide detailed information about the invalid activation key

&#x20;       **🧐Enhancement** (ES) additional `hide_apps` validation in the `kibana` rule

&#x20;       **🐞Fix** (KBN) the issue with the persistence of an activation key provided via UI when `readonlyrest_kbn.cookiePass` was not provided. The [readonlyrest\_kbn.cookiePass](https://docs.readonlyrest.com/kibana#configuring-kibana) is required `kibana.yml` property

&#x20;       **🐞Fix** (KBN) issues for Kibana versions between 7.9.0 and 7.10.2, related to the activation key, Spaces, and readonlyREST menu crash

&#x20;       **🐞Fix** (KBN) The issue with a logout from Kibana when the link to the Kibana is open from a third-party application like `Gmail`

&#x20;       **🐞Fix** (ES) [getting data streams when not full names of backing indices are declared in the `indices` rule](https://forum.readonlyrest.com/t/forbidden-for-creating-component-templates/2372/7)

&#x20;       **🐞Fix** (ES) stack-management screen fix in case of `xpack.security.enabled: true`

### (2023-07-25) What's new in **ROR 1.50.0**

&#x20;       **🚀New** (KBN/ES) ECK support

&#x20;       **🚀New** (KBN) 8.9.1, 8.9.0, 7.17.12 support

&#x20;       **🚀New** (ES) 8.9.1, 8.9.0, 7.17.12 support

&#x20;       **🚀New** (KBN) Introduce the new ReadonlyREST API

&#x20;       **🧐Enhancement** (KBN) Remove application item info from URL on the tenant switch to avoid a 404 not found message

&#x20;       **🧐Enhancement** (KBN) Provide Reordering available tenancies for proxy auth authentication

&#x20;       **🧐Enhancement** (KBN) Provide information about granted/rejected log-in users to debug logs

### (2023-06-27) What's new in **ROR 1.49.1**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-2976](https://nvd.nist.gov/vuln/detail/CVE-2023-2976)

&#x20;       **🚨Security Fix** (ES) [CVE-2023-34462](https://github.com/advisories/GHSA-6mjq-h674-j845)

&#x20;       **🚀New** (KBN) 8.8.2, 8.8.1, 8.8.0, 7.17.11 support

&#x20;       **🚀New** (ES) 8.8.2, 7.17.11 support

&#x20;       **🚀New** (ES) [LDAP nested groups support](https://docs.readonlyrest.com/elasticsearch#ldap-connector)

&#x20;       **🧐Enhancement** (KBN) [Allow setting default tenancy via `/login?defaultGroup` query param. To be used with "Custom Middleware" feature for reordering available tenancies in the ROR menu](https://docs.readonlyrest.com/examples/custom-middleware/reordering-available-tenancies)

&#x20;       **🐞Fix** (ES) [Fix for ES warnings in logs about custom action names (ROR internal actions)](https://forum.readonlyrest.com/t/invalid-action-name-cluster-ror-audit-event-put/2186)

&#x20;       **🐞Fix** (ES) [kibana access `rw` and `admin` should allow to manage component templates](https://forum.readonlyrest.com/t/forbidden-for-creating-component-templates/2372)

### (2023-05-28) What's new in **ROR 1.49.0**

&#x20;       **🚀New** (ES) 8.8.1 support

&#x20;       **🧐Enhancement** (KBN) Handle `elasticsearch.serviceAccountSupport` configuration property

&#x20;       **🧐Enhancement** (KBN) Provide a way to Hidden apps Stack management items hiding

&#x20;       **🧐Enhancement** (KBN) Provide an automated migration of tenancy indices on major Kibana version upgrade

&#x20;       **🧐Enhancement** (ES) external group ID patterns support in the external to local groups mapping

&#x20;       **🐞Fix** (KBN) the issue with the replica number being set to 0 on tenant index creation

&#x20;       **🐞Fix** (KBN) users won't log out from Kibana on the 500 status error

&#x20;       **🐞Fix** (KBN) the issue with Kibana keystore not being read by the Kibana plugin

&#x20;       **🐞Fix** (KBN < 7.9.0) logging issue when two Kibanas are handled by one browser at the same time

&#x20;       **🐞Fix** (ES) resolving ENVs to YAML number in ROR settings

### (2023-04-15) What's new in **ROR 1.48.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-45688](https://nvd.nist.gov/vuln/detail/CVE-2022-45688)

&#x20;       **🚀New** (KBN) 8.7.1, 7.17.10 support

&#x20;       **🚀New** (ES) 8.8.0, 8.7.1, 7.17.10 support

&#x20;       **🚀New** (KBN/ES) [Introducing "Custom Middleware" functionality](https://docs.readonlyrest.com/kibana#custom-middleware)

&#x20;       **🚀New** (KBN/ES) [`allowed_api_paths` support in the `kibana` ACL rule](https://docs.readonlyrest.com/elasticsearch#kibana-related-rules)

&#x20;       **🚀New** (KBN) Add CSRF protection in the login form

&#x20;       **🚀New** (KBN) Restore deprecated "kibana.index" support for Kibana > 8.x

&#x20;       **🚀New** (ES) [all Kibana-related rules are gathered in one, new `kibana` ACL rule](https://docs.readonlyrest.com/elasticsearch#kibana-related-rules)

&#x20;       **🚀New** (ES) [audit supports a new output type: `log`](https://docs.readonlyrest.com/elasticsearch/audit)

&#x20;       **🧐Enhancement** (KBN) Provide a way to disable multi-tenancy in ROR Enterprise

&#x20;       **🧐Enhancement** (KBN) Realign index templates behaviour to the old platform

&#x20;       **🧐Enhancement** (KBN) Error logs when SAML obtains an unusable username from the assertion

&#x20;       **🧐Enhancement** (KBN) Test configuration warnings improvement

&#x20;       **🧐Enhancement** (ES) [Added support to override default response code for not started ROR](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/issues/794)

&#x20;       **🐞Fix** (KBN) Security card not hidden by default

&#x20;       **🐞Fix** (KBN) Hidden apps regex with two "or" operators don't hide all kibana apps

&#x20;       **🐞Fix** (KBN) Fix Alerting Rules resulting in logout issue

&#x20;       **🐞Fix** (KBN) Fix audit dashboard

&#x20;       **🐞Fix** (KBN) Stop handling 500 error from `api/lens/existing_fields`

&#x20;       **🐞Fix** (KBN) Fix lens app

&#x20;       **🐞Fix** (KBN < 7.9.x) using a custom kibana index in cooperation with ROR Free

### (2023-02-13) What's new in **ROR 1.47.0**

&#x20;       **🚨Security Fix** (ES) "/" endpoint was not protected for ES 8.x

&#x20;       **🚨Security Fix** (ES) "/\_cat" endpoint was not protected for all ES versions

&#x20;       **🚀New** (KBN) 8.7.0, 8.6.2 support

&#x20;       **🚀New** (ES) 8.7.0, 8.6.2 support

&#x20;       **🚀New** (ES) [the `data_streams` rule](https://docs.readonlyrest.com/v/develop/elasticsearch#data_streams)

&#x20;       **🧐Enhancement** (KBN) optimisation in hidden apps feature

&#x20;       **🐞Fix** (KBN) Opening index management mappings tab forces logout

&#x20;       **🐞Fix** (KBN) Fix dark mode in the ROR menu

&#x20;       **🐞Fix** (KBN) YAML editor updates and fixes

&#x20;       **🐞Fix** (ES) Data streams support in the `indices` rule

&#x20;       **🐞Fix** (ES) NPE when `_search` with aggregations (script) and the `fields` rule were used together

### (2023-01-02) What's new in **ROR 1.46.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-1471](https://nvd.nist.gov/vuln/detail/CVE-2022-1471), [CVE-2022-41915](https://nvd.nist.gov/vuln/detail/CVE-2022-41915), [CVE-2022-36944](https://nvd.nist.gov/vuln/detail/CVE-2022-36944) in [audit Scala 2.13 jar](https://mvnrepository.com/artifact/tech.beshu.ror/audit)

&#x20;       **🚀New** (KBN) 8.6.1, 8.6.0, 7.17.9 support

&#x20;       **🚀New** (ES) 8.6.1, 8.6.0, 7.17.9 support

&#x20;       **🧐Enhancement** (KBN) Activation key management UI

&#x20;       **🧐Enhancement** (KBN) Less verbose logging in info mode

&#x20;       **🧐Enhancement** (KBN) "Stack management" kibana compatibility

&#x20;       **🐞Fix** (KBN) Test settings pop up won't show

&#x20;       **🐞Fix** (KBN) hide apps behaviour when "Management" is hidden

&#x20;       **🐞Fix** (KBN) Data view with a ":" symbol forces logout from a kibana

&#x20;       **🐞Fix** (KBN) Session probe causes constant refresh when no `kibana_access` defined

&#x20;       **🐞Fix** (ES) large report generation using data from a remote cluster with enabled x-pack security

### (2022-12-05) What's new in **ROR 1.45.1**

&#x20;       **🚀New** (KBN) 8.5.3, 7.17.8 support

&#x20;       **🚀New** (ES) 8.5.3, 7.17.8 support

&#x20;       **🐞Fix** (KBN) ROR KBN patching script

### (2022-11-29) What's new in **ROR 1.45.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-42003](https://nvd.nist.gov/vuln/detail/CVE-2022-42003), [CVE-2022-45146](https://nvd.nist.gov/vuln/detail/CVE-2022-45146)

&#x20;       **🚀New** (KBN) Activation Key API: read AK from ROR\_ACTIVATION\_KEY.txt

&#x20;       **🚀New** (KBN) Activation Key API: submit AK via POST /pkp/license (Basic auth)

&#x20;       **🚀New** (KBN) Inject CSS/JS files in login page

&#x20;       **🚀New** (KBN) Add user metadata to \<body> for extra UI customization

&#x20;       **🚀New** (ES) Added groups\_and mode to [groups\_provider\_authorization](https://docs.readonlyrest.com/elasticsearch#groups_provider_authorization) rule

&#x20;       **🧐Enhancement** (ES) all authorization rules support wildcards in group IDs

&#x20;       **🧐Enhancement** (ES) connections in the LDAP pool should not be closed unnecessarily

&#x20;       **🧐Enhancement** (KBN) Deterministic reporting index detection

&#x20;       **🧐Enhancement** (KBN) Move free type impersonation to the local users area

&#x20;       **🧐Enhancement** (KBN) don't logout when initial JWT token expires

&#x20;       **🐞Fix** (KBN) Direct Kibana API requests not aware of kibana\_index

&#x20;       **🐞Fix** (KBN) RO and RO\_strict kibana accesses

&#x20;       **🐞Fix** (ES) [when `fls_engine: es` is configured and `fields` rule is used, aggregations should be available only for allowed fields](https://forum.readonlyrest.com/t/field-level-security-and-aggregations/2133)

&#x20;       **🐞Fix** (ES) [Data streams creation issue fix](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/issues/829)

&#x20;       **🐞Fix** (ES) Unknown structure of index settings issue fix

&#x20;       **🐞Fix** (ES) resolving index names with wildcards should take into consideration the current index state and request indices options

### (2022-10-09) What's new in **ROR 1.44.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-25857](https://nvd.nist.gov/vuln/detail/CVE-2022-25857)

&#x20;       **🚀New** (KBN) 8.5.2, 8.5.1, 8.5.0, 7.17.7 support

&#x20;       **🚀New** (ES) 8.5.2, 8.5.1, 8.5.0, 7.17.7 support

&#x20;       **🚀New** (KBN) **plugin packages are now** [**universal**](https://docs.readonlyrest.com/universal-builds)

&#x20;       **🚀New** (KBN) **Manage your activation keys through the** [**customer portal**](https://readonlyrest.com/customer)

&#x20;       **🚀New** (ES) Added support for certificates in PEM format

&#x20;       **🧐Enhancement** (KBN) SAML groups list duplication made header size exceed limits

&#x20;       **🧐Enhancement** (KBN) kibana\_access: admin has now privileges to manage a Kibana cluster

&#x20;       **🧐Enhancement** (ES) added distributed and persistent Test Settings & Auth Mocks configuration for the Impersonation Feature

&#x20;       **🧐Enhancement** (ES) handling high load when LDAP rules are used

&#x20;       **🧐Enhancement** (ES) `client_authentication` settings in internode SSL configuration

&#x20;       **🧐Enhancement** (ES) `acl:available_groups` dynamic variable can be used in a single value context

&#x20;       **🐞Fix** (ES) SNI handling (internode SSL)

### (2022-08-22) What's new in **ROR 1.43.0**

&#x20;       **🚀New** (KBN) 8.4.3, 8.4.2, 8.4.1, 8.4.0, 7.17.6 support

&#x20;       **🚀New** (ES) 8.4.3, 8.4.2, 8.4.1, 8.4.0, 7.17.6 support

&#x20;       **🚀New** (KBN) `kibana_custom_js_inject_file` feature

&#x20;       **🐞Fix** (ES) [`ror-tools` fix for Windows OS (patching ES 3.x issue)](https://forum.readonlyrest.com/t/ror-plugin-for-es-8-x-patch-error/2115)

&#x20;       **🐞Fix** (ES) resolving indices in the remote x-pack cluster

&#x20;       **🐞Fix** (KBN|PRO) ROR menu title wraps when version text is too short (cosmetic)

&#x20;       **🐞Fix** (KBN) infinite loading when kibana\_access not defined for user

&#x20;       **🐞Fix** (KBN) transient error with randomly choosing off range bind port on localhost

&#x20;       **🐞Fix** (KBN) 404 on login when `xpack.spaces.enabled: false`

### (2022-07-25) What's new in **ROR 1.42.0**

&#x20;       **🚀New** (KBN|ES) 8.3.3, 8.3.2, 8.3.1, 8.3.0, 7.15.5 support

&#x20;       **🧐Enhancement** (KBN) Search box in tenancy switcher (when #tenancies > 5)

&#x20;       **🧐Enhancement** (ES) added configuration warnings in the Impersonation Feature

&#x20;       **🐞Fix** (KBN) Logout didn't delete the SAML session on the IdP

&#x20;       **🐞Fix** (KBN) 5xx errors from Elasticsearch break Kibana users' session unrecoverably

&#x20;       **🐞Fix** (ES) ROR node cooperation with X-pack nodes

### (2022-06-21) What's new in **ROR 1.41.0**

&#x20;       **🚀New** (ES) Added `groups_and` mode to [`ror_kbn_auth`](https://docs.readonlyrest.com/elasticsearch#ror_kbn_auth) and [`jwt_auth`](https://docs.readonlyrest.com/elasticsearch#jwt_auth) rules

&#x20;       **🧐Enhancement** (KBN) Prevent native credentials dialogue to appear in Kibana when ES responds 401

&#x20;       **🧐Enhancement** (KBN) Logging in after logout shows the same page you last visited

&#x20;       **🧐Enhancement** (KBN) x-ror-correlation-id header lets you audit a whole Kibana session

&#x20;       **🐞Fix** (ES|KBN) tenancy selector didn't work well with `jwt_auth` and `ror_kbn_auth` rules

&#x20;       **🐞Fix** (KBN) Support for special characters in tenancy names

&#x20;       **🐞Fix** (KBN) OIDC logout flow redirecting to bad request error

&#x20;       **🐞Fix** (KBN) OIDC connector not working in Kibana < 7.12.0

### (2022-05-24) What's new in **ROR 1.40.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-25647](https://nvd.nist.gov/vuln/detail/CVE-2022-25647) & [CVE-2022-24823](https://nvd.nist.gov/vuln/detail/CVE-2022-24823) & [CVE-2020-13956](https://nvd.nist.gov/vuln/detail/CVE-2020-13956) & [CVE-2020-36518](https://nvd.nist.gov/vuln/detail/CVE-2020-36518) & [CVE-2020-13956](https://nvd.nist.gov/vuln/detail/CVE-2020-13956) & [CVE-2020-36518](https://nvd.nist.gov/vuln/detail/CVE-2020-36518)

&#x20;       **🚨Security Fix** (KBN) "Security" app not entirely hidden in 8.2.x

&#x20;       **🚀New** (ES) New Support for 8.2.3, 8.2.2, 8.2.1, 7.17.4

&#x20;       **🚀New** (KBN) New Support for 8.2.2 8.2.1, 7.17.4

&#x20;       **🚀New** (ES & KBN) [The Impersonation feature](https://docs.readonlyrest.com/kibana#impersonation)

&#x20;       **🚀New** (ES) [FIPS compliant SSL mode](https://docs.readonlyrest.com/elasticsearch/fips)

&#x20;       **🧐Enhancement** (KBN) SAML cert is now required

&#x20;       **🧐Enhancement** (KBN) moved OIDC to better library

&#x20;       **🧐Enhancement** (KBN) OIDC jwksURL is now required

&#x20;       **🐞Fix** (ES) `indices: ["1"]` interpreted as integer and fails to parse

&#x20;       **🐞Fix** (KBN) /login?jwt=xxx authorization now works again

&#x20;       **🐞Fix** (KBN) OIDC/SAML assertion claims were not forwarded to ES

&#x20;       **🐞Fix** (KBN) include whitelisted headers while logging

&#x20;       **🐞Fix** (KBN) basepath handling fixes (too many redirects)

&#x20;       **🐞Fix** (KBN) Make ROR default space the actual default one

&#x20;       **🐞Fix** (KBN) OIDC connection error

### (2022-03-19) What's new in **ROR 1.39.0**

&#x20;       **🚨Security Fix** (KBN) XSS sanitize path requested

&#x20;       **🚨Security Fix** (ES) [CVE-2020-36518](https://nvd.nist.gov/vuln/detail/CVE-2020-36518) & [CVE-2022-21653](https://nvd.nist.gov/vuln/detail/CVE-2022-21653)

&#x20;       **🚀New** (KBN) New Support for 8.2.0 8.1.3, 8.1.2, 8.1.1, 8.1.0, 8.0.0, 8.0.1, 7.17.3, 7.17.2

&#x20;       **🚀New** (ES) New Support for 8.2.0, 8.1.3, 8.1.2, 8.1.1, 8.1.0, 8.0.0, 8.0.1 ([required additional patching step](https://docs.readonlyrest.com/elasticsearch#3.-patch-es))

&#x20;       **🚀New** (ES) New Support for 7.17.3, 7.17.2

&#x20;       **🚀New** (ES) [New `groups_and` ACL rule](https://docs.readonlyrest.com/elasticsearch#groups_and)

&#x20;       **🧐Enhancement** (KBN) Stop inlining whitelisted headers into Authorization header

&#x20;       **🧐Enhancement** (KBN) Log additional errors and info related to HA

&#x20;       **🧐Enhancement** (KBN) Misc internal dependencies upgrades

&#x20;       **🐞Fix** (KBN) Mandatory elasticsearch credentials in kibana.yml

&#x20;       **🐞Fix** (KBN) [Reporting page redirect on refresh when kibana\_hide\_apps: \["Stack Management"\]](https://forum.readonlyrest.com/t/when-hiding-stack-management-a-redirect-appears-with-report/2088)

&#x20;       **🐞Fix** (KBN) whitelistedPaths: log errors when 404 occurs

&#x20;       **🐞Fix** (KBN) [Issue uploading large payload](https://forum.readonlyrest.com/t/issue-uploading-large-payload/2091)

&#x20;       **🐞Fix** (KBN) `elasticsearch.requestHeadersWhitelist` should be case insensitive

&#x20;       **🐞Fix** (ES) [Issue with handling data streams by `indices` rule](https://forum.readonlyrest.com/t/ror-1-37-0-indices-rule-and-alias-within-kibana/2078)

&#x20;       **🐞Fix** (ES) X-Pack SSL nodes cooperation with ROR SSL nodes

&#x20;       **🐞Fix** (ES) \_msearch issue when filter rules was used in matched block

### (2022-01-17) What's new in **ROR 1.38.0**

&#x20;       **🚀New** (ES) New Support for 7.17.0, 7.17.1

&#x20;       **🚀New** (KBN) New Support for 7.17.0

&#x20;       **🚀New** (ES) [Configuration for custom audit cluster](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.38.x/elasticsearch.md#custom-audit-cluster)

&#x20;       **🧐Enhancement** (ES) Separate "audit" section for all audit settings

&#x20;       **🐞Fix** (KBN) Editor rendering issue with kibana basePath enabled

### (2021-12-14) What's new in **ROR 1.37.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2021-43797](https://nvd.nist.gov/vuln/detail/CVE-2021-43797)

&#x20;       **🚀New** (ES) New Support for 7.16.3, 7.16.2, 6.8.23, 6.8.22

&#x20;       **🚀New** (KBN) New Support for 7.16.3, 7.16.2, 7.16.1, 7.16.10, 6.8.23, 6.8.22, 6.8.21

&#x20;       **🧐Enhancement** (ES) fields rule handling in the context of x-Pack SQL requests

&#x20;       **🐞Fix** (ES) filter rule handling in the context of x-Pack SQL requests

&#x20;       **🐞Fix** (KBN) POST / bulk cause an 400 error in devtools console

&#x20;       **🐞Fix** (KBN) More robust Kibana patcher + better logs messages

### (2021-11-21) What's new in **ROR 1.36.0**

&#x20;       **🚀New** (ES) New Support for 7.16.1, 7.16.0, 6.8.21

&#x20;       **🚀New** (KBN) Support Kibana 7.15.2

&#x20;       **🚀New** (ES) [Added support for setting up cluster containing ES with ROR (with disabled XPack security) and ES with XPack security enabled](https://forum.readonlyrest.com/t/ssl-internode-with-elk-cluster/1916)

&#x20;       **🧐Enhancement** (KBN) kibana\_hide\_apps: \[ror|kibana] to remove kibana mgmt button

&#x20;       **🐞Fix** (ES) [/\_snapshot/\_status should return only running snapshots](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/issues/756)

&#x20;       **🐞Fix** (ES) [Adding policy to index template bug](https://forum.readonlyrest.com/t/forbidden-by-readonlyrest-es-plugin-with-add-policy-to-index-template-action-in-kibana/1969)

&#x20;       **🐞Fix** (KBN) Index management tabs result in "forbidden" error

&#x20;       **🐞Fix** (KBN) [corrupted patch file for Kibana 7.9.x](https://forum.readonlyrest.com/t/ror-1-35-1-kibana-7-9-3-unable-to-patch/2018)

&#x20;       **🐞Fix** (KBN) [YAML editor not working in air-gapped environments](https://forum.readonlyrest.com/t/readonlyrest-security-settings-editor-loading/2014/5)

&#x20;       **🐞Fix** (KBN) [Devtools not working](https://forum.readonlyrest.com/t/kibana-devtools-error-does-not-support-having-a-body/2027)

&#x20;       **🐞Fix** (KBN) [Monitoring not working in multi-tenancy](https://forum.readonlyrest.com/t/kibana-alerting-not-working-with-readonlyrest/1986)

&#x20;       **🐞Fix** (KBN) Regression in Kibana < 6.8.x front end crash

&#x20;       **🐞Fix** (KBN) Kibana < 7.8.x prevent navigation to hidden apps from home links

&#x20;       **🐞Fix** (KBN) Kibana < 7.8.x implicitly hide kibana:dashboard when kibana:dashboards is hidden (and viceversa)

&#x20;       **🐞Fix** (KBN) Kibana < 7.8.x broken `clearSessionOnEvents: [tenancyHop]`

### (2021-10-17) What's new in **ROR 1.35.1**

&#x20;       **🚨Security Fix** (ES) [CVE-2021-21409](https://nvd.nist.gov/vuln/detail/CVE-2021-21409) & [CVE-2021-27568](https://nvd.nist.gov/vuln/detail/CVE-2021-27568)

&#x20;       **🚀New** (KBN) Support Kibana 7.15.1

&#x20;       **🚀New** (ES) New Support for 7.15.2

&#x20;       **🧐Enhancement** (KBN) Support "server.ssl.supportedProtocols" settings

&#x20;       **🧐Enhancement** (KBN) Support "server.ssl.cipherSuites"

&#x20;       **🧐Enhancement** (KBN) Always honor SSL cipher order

&#x20;       **🐞Fix** (KBN) Don'thide "Add/Remove field as column" in Discover app for RO users

&#x20;       **🐞Fix** (KBN) More alerting fixes (only for main tenancy)

### (2021-10-12) What's new in **ROR 1.35.0**

&#x20;       **🚀New** (KBN) Support Kibana 7.15.0, 7.14.2

&#x20;       **🚀New** (ES) New Support for 7.15.1, 6.8.19, 6.8.20

&#x20;       **🧐Enhancement** (ES) [local->external groups detailed mapping for groups rule](https://github.com/beshu-tech/readonlyrest-docs/blob/master/details/groups-rule-mapping.md)

&#x20;       **🧐Enhancement** (ES) when ROR is starting any request is going to end up with HTTP 403 response, instead of HTTP 503

&#x20;       **🧐Enhancement** (KBN) "server.basePath" kibana option implementation

&#x20;       **🧐Enhancement** (KBN) Support full regex in kibana\_hidden\_apps rule

&#x20;       **🧐Enhancement** (unspecified) Crash if Kibana is not patched

&#x20;       **🧐Enhancement** (KBN) Honour kibana setting "logging.dest"

&#x20;       **🧐Enhancement** (KBN) Confirm before overwriting audit log dashboard

&#x20;       **🐞Fix** (ES) verbosity: error fix in case of ROR KBN login request

&#x20;       **🐞Fix** (KBN) Make alerting work on primary tenancy

&#x20;       **🐞Fix** (KBN) OIDC fix sameSite / secure cookie options

&#x20;       **🐞Fix** (KBN) Login form is stretched when long error

&#x20;       **🐞Fix** (KBN) Login form is stretched when long error

&#x20;       **🐞Fix** (KBN-PRO) [Don't send x-ror-currentgroup in PRO](https://forum.readonlyrest.com/t/upgrading-6-7-w-1-18-to-7-14-w-1-33-ldap-from-ms-active-directory-no-longer-understands-multiple-ad-group-memberships/1973/6)

&#x20;       **🐞Fix** (KBN) Resolve browser console errors on a popover close

### (2021-09-24) What's new in **ROR 1.34.0**

&#x20;       **🚀New** (ES) New Support for 7.15.0, 7.14.2

&#x20;       **🚀New** (KBN) VS Code style YAML editor

&#x20;       **🚀New** (KBN) Skip rendering hidden app groups entirely

&#x20;       **🚀New** (KBN) Redesigned ROR Menu

&#x20;       **🚀New** (KBN) Dark theme awareness

&#x20;       **🐞Fix** (KBN) Broken Kibana Spaces

&#x20;       **🐞Fix** (KBN) Support Kibana's undocumented "server.ssl.\*" settings

&#x20;       **🐞Fix** (KBN) cookiePass config parsing broke load balancing

### (2021-08-14) What's new in **ROR 1.33.1**

&#x20;       **🚀New** (ES) New Support for 7.14.1

&#x20;       **🐞Fix** (KBN) Error in patching for 7.14.0

&#x20;       **🐞Fix** (KBN) clearSessionOnEvents now works as expected

&#x20;       **🐞Fix** (KBN) login form font loads correctly

### (2021-08-09) What's new in **ROR 1.33.0**

&#x20;       **🚨Security Fix** (KBN) xml-crypto dependency update

&#x20;       **🚀New** (KBN) New Support for 7.14.0, 6.8.18

&#x20;       **🧐Enhancement** (KBN) Parse credentials in /api/\* requests, no need for valid cookie. Supersedes whitelistedPaths

&#x20;       **🐞Fix** (KBN) Caching issues switching tenancies with dark/light theme

&#x20;       **🐞Fix** (KBN) Newly created Space shows in all tenancies when using default kibana index

&#x20;       **🐞Fix** (KBN < 7.9.x) nextUrl works again with SAML and OIDC

### (2021-07-25) What's new in **ROR 1.32.0**

&#x20;       **🚨Security Fix** (ES) [Apache Commons Codec vulnerability](https://forum.readonlyrest.com/t/security-vulnerability-for-common-codec-1-10/1906)

&#x20;       **🚨Security Fix** (KBN) upgraded dependencies due to security fixes

&#x20;       **🚨Security Fix** (KBN) disable x-powered-by to avoid fingerprinting

&#x20;       **🚀New** (ES) Support for ES 7.14.0 & 6.8.18

&#x20;       **🚀New** (KBN) Support for Kibana 7.13.x series

&#x20;       **🧐Enhancement** (KBN) honor configurations coming from ENV and CLI options

&#x20;       **🧐Enhancement** (KBN) when metadata has no username, login must be denied

&#x20;       **🧐Enhancement** (KBN) audit tab ported to new platform

&#x20;       **🧐Enhancement** (ES) improved ES resources cleaning when ROR returns FORBIDDEN response

&#x20;       **🧐Enhancement** (KBN < 7.9.x) auto clean-up dangling SAML/OIDC cookies

&#x20;       **🐞Fix** (ES) [incomplete response for request GET \*/\_alias](https://forum.readonlyrest.com/t/ror-return-incomplete-response-for-request-get-alias/1872)

&#x20;       **🐞Fix** (ES) not allowed aliases should not present in a response for a Get Index API request

&#x20;       **🐞Fix** (KBN) fix dev-tools and import saved object not working

&#x20;       **🐞Fix** (KBN) honor `requestHeadersWhitelist` in user metadata request (login)

&#x20;       **🐞Fix** (KBN < 7.9.x) do not crash on invalid metadata

### (2021-06-29) What's new in **ROR 1.31.0**

&#x20;       **🚨Security Fix** (KBN) prevent direct navigation to hidden apps

&#x20;       **🚀New** (ES) 7.13.4, 7.13.3, 7.13.2, 6.8.17 support

&#x20;       **🚀New** (KBN) new minimal Kibana Management menu when "Management" app is hidden

&#x20;       **🧐Enhancement** (KBN) logout active Kibana session if key metadata/permissions change in ACL

&#x20;       **🧐Enhancement** (KBN) better port number validation

&#x20;       **🧐Enhancement** (ES) improved cluster indices handling

&#x20;       **🐞Fix** (ES) [Kibana access rule regression fix](https://forum.readonlyrest.com/t/es7-11-2-1-30-0-enterprise-two-contexts-rw-ro-issue/1855)

&#x20;       **🐞Fix** (ES) search template API handling with `filter` and `fields` rule

&#x20;       **🐞Fix** (ES) multi-tenancy issue when groups\_provider\_authorization is used

&#x20;       **🐞Fix** (ES) `x_forwarded_for` rule: wrong handling of / request

&#x20;       **🐞Fix** (ES) Issue with handling ResizeRequest which made it unable to upgrade Kibana to version 7.12.0+

&#x20;       **🐞Fix** (KBN) some Kibana requests arrive to ES without credentials

&#x20;       **🐞Fix** (KBN) inconsistent read after write in session storage lead to issues with round robin load balancing

&#x20;       **🐞Fix** (KBN) bad multipart POST handling leads to saved object import errors

### (2021-05-26) What's new in **ROR 1.30.1**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-27568">CVE-2021-27568</a></summary>

This release addresses CVE-2021-27568, a vulnerability in the json-smart library (used by Elasticsearch) where an uncaught exception (e.g., NumberFormatException) could cause crashes or potentially expose sensitive information. ROR users are advised to update to mitigate this risk.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 7.13.0, 7.13.1 support</summary>

ReadonlyREST now supports Elasticsearch versions 7.13.0 and 7.13.1, ensuring compatibility with the latest features and improvements in those releases.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Regression in multi-tenancy handling</summary>

A regression introduced in a previous release that affected multi-tenancy behavior has been resolved. Multi-tenant configurations should now work as expected without unintended access restrictions or errors.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Proper handling of _snapshot/_status endpoint</summary>

Fixed an issue where the `_snapshot/_status` endpoint was not being handled correctly by the security plugin. This ensures that snapshot status requests are properly authorized and processed.

</details>

### (2021-05-16) What's new in **ROR 1.30.0**

&#x20;       **🚀New** (KBN) 7.12.x compatibility

&#x20;       **🚀New** (ES) [LDAP connector circuit breaker](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.30.x/elasticsearch.md#circuit-breaker)

&#x20;       **🧐Enhancement** (ES) [Username with wildcard support in users section](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.30.x/elasticsearch.md#groups) and [groups mapping](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.30.x/elasticsearch.md#group-mapping)

&#x20;       **🧐Enhancement** (KBN < 7.9.x) OIDC errors visibility

&#x20;       **🧐Enhancement** (KBN < 7.9.x) Smarter session probe algorithm

&#x20;       **🐞Fix** (KBN >= 7.9.x) [Load CertificateAuthorities as an array if not specified as an array](https://forum.readonlyrest.com/t/kibana-crash-at-startup-with-the-new-7-10-2-version/1840)

&#x20;       **🐞Fix** (KBN < 7.9.x) Don't hide visualizations list search box in RO mode

### (2021-04-09) What's new in **ROR 1.29.0**

<details>

<summary><strong>🚨Security Fix</strong> (ES) Security Fix (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21409">CVE-2021-21409</a></summary>

This release addresses CVE-2021-21409, a Netty vulnerability (versions before 4.1.61.Final) that allows HTTP request smuggling via improper validation of the content-length header when a single Http2HeaderFrame with endStream set to true is used. The ROR plugin has been updated to use a patched version of Netty, eliminating the risk.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) support 7.9.0, 7.9.1, 7.10.0, 7.10.1, 7.10.2, 7.11.0, 7.11.1, 7.11.2 (<a href="https://beta.readonlyrest.com/">with ROR new platform</a>)</summary>

ReadonlyREST now supports Kibana versions 7.9.0 through 7.11.2, powered by the new ROR platform. This expands compatibility for teams running older Kibana instances who still need enterprise-grade security for their Elasticsearch stack.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 7.12.1 support</summary>

ReadonlyREST now supports Elasticsearch 7.12.1, ensuring users on this version can benefit from the plugin's fine-grained access control, field- and document-level security, and YAML-based rule configuration.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) logout if the credentials/metadata of the current user change in the ACL</summary>

Kibana sessions are now automatically terminated when the authenticated user's credentials or metadata are modified in the ACL configuration. This prevents stale sessions from persisting after access rights have been updated, improving security and ensuring that policy changes take effect immediately.

</details>

### (2021-04-01) What's new in **ROR 1.28.2**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21295">CVE-2021-21295</a></summary>

This release addresses CVE-2021-21295, an HTTP request smuggling vulnerability in Netty's `netty-codec-http2` module (versions before 4.1.60.Final). The flaw occurs when an HTTP/2 request containing a `Content-Length` header is converted to HTTP/1.1 objects and proxied to a remote peer, potentially allowing an attacker to smuggle requests within the body. The underlying Netty dependency has been updated to the patched version to mitigate this risk.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) prevent SAML/OIDC initiated Kibana sessions from expiring after <code>session_timeout_minutes</code> despite continued interaction</summary>

Fixed a bug where Kibana sessions authenticated via SAML or OIDC would expire after the configured `session_timeout_minutes` even when the user was actively interacting with the application. The session timeout now properly resets on user activity, ensuring that active users are not unexpectedly logged out.

</details>

### (2021-03-24) What's new in **ROR 1.28.1**

<details>

<summary><strong>🐞Fix</strong> (ES) Getting index templates issue when no <code>indices</code> rule was used in matched block</summary>

Fixed an issue where retrieving index templates would fail when a matched block in the ROR configuration did not include an `indices` rule. This ensures that index template operations work correctly even in configurations where access control is defined without explicit index-level restrictions.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/cannot-put-index-template-template-1/1681/25">NPE on getting template aliases</a></summary>

Resolved a NullPointerException that occurred when fetching template aliases in Elasticsearch. This fix addresses the issue reported by users who were unable to perform `PUT _index_template/template_1` operations due to the NPE, restoring proper handling of index template aliases in secured clusters.

</details>

### (2021-03-14) What's new in **ROR 1.28.0**

<details>

<summary><strong>🚀New</strong> (ES) 7.12.0, 7.11.2 support</summary>

ReadonlyREST now supports Elasticsearch versions 7.12.0 and 7.11.2, ensuring compatibility with the latest Elasticsearch releases in the 7.x line.

</details>

<details>

<summary><strong>🚀New</strong> (ES) full <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.9/index-templates.html">Index and Component Templates API</a> support</summary>

ReadonlyREST now fully supports Elasticsearch's composable index templates and component templates API (introduced in ES 7.8). This allows administrators to define fine-grained access control rules for both index templates (which configure indices/data streams upon creation) and component templates (reusable building blocks for mappings, settings, and aliases), ensuring security policies extend to template management operations.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/ldap-based-user-authentication/1667">Username case sensitivity settings</a></summary>

Added a configurable flag to handle username case sensitivity in authentication backends like LDAP. When enabled, this setting allows case-insensitive username matching — useful when the LDAP server treats usernames case-insensitively but the ROR configuration requires an exact case match. Administrators can now apply string transformations (e.g., toLower, toUpper) to usernames during authentication.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/kibana-plugin-software-licensing-and-expiration/1808/5">Kibana logout event storing fix</a></summary>

Fixed an issue where Kibana logout events were not being properly stored or handled, which could interfere with session tracking and audit logging. This ensures that logout actions are correctly recorded and processed.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/reindex-index-not-found-exception/1708/20">Fixed remote reindex operation with "type" parameter</a></summary>

Resolved an issue where remote reindex operations failed with an "index not found" exception when the request included a "type" parameter. This fix ensures that cross-cluster reindexing works correctly even when legacy type parameters are present in the request.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Prevent cookie expiration deadlock in browsers when using SAML/OIDC</summary>

Fixed a browser-side deadlock scenario where cookie expiration could cause authentication loops when using SAML or OIDC single sign-on. This ensures a smoother login experience and prevents users from getting stuck in an infinite redirect cycle.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) When credentials change in the ACL, make it possible to login again</summary>

Fixed an issue where users could not log in again after their credentials were updated in the ACL configuration. Previously, cached or stale authentication states could prevent re-authentication with the new credentials.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Kibana management app ID changed from "kibana:management" to "kibana:stack_management"</summary>

Updated the Kibana management application identifier from the legacy "kibana:management" to the current "kibana:stack\_management" to align with changes in newer Kibana versions. This ensures that access control rules targeting the management section work correctly.

</details>

### (2021-02-27) What's new in **ROR 1.27.1**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21290">CVE-2021-21290</a></summary>

This release addresses CVE-2021-21290, a security vulnerability in Netty (versions before 4.1.59.Final) affecting Unix-like systems. The issue involves insecure temporary file creation when multipart decoders store uploads to disk — files created via `File.createTempFile` in shared temporary directories have default permissions (`-rw-r--r--`), making them readable by other local users and potentially leading to local information disclosure. ROR has updated its Netty dependency to the patched version to mitigate this risk.

</details>

&#x20;       **🚀New** (ES) 7.11.1 support

### (2021-02-16) What's new in **ROR 1.27.0**

<details>

<summary><strong>🚀New</strong> (ES) 7.11.0, 7.10.2, 6.8.14 support</summary>

Added support for Elasticsearch versions 7.11.0, 7.10.2, and 6.8.14, ensuring compatibility with the latest patch releases across these major version lines.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) X-Forwarded-For copied from incoming request (or filled with source IP) before forwarding to ES</summary>

Kibana now properly propagates the X-Forwarded-For header from the incoming request to Elasticsearch. If the header is absent, it is populated with the source IP address, improving audit trail accuracy and enabling proper client IP tracking in multi-tier proxy setups.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Kibana logout event generates a special audit log entry in ROR audit logs index</summary>

When a user logs out of Kibana, ROR now generates a dedicated audit log entry in the ROR audit logs index. This provides better visibility into user session lifecycles and helps with security auditing and compliance requirements.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) ROR panel shows "reports" button if kibana:management app is hidden</summary>

The ROR panel in Kibana now displays a "Reports" button even when the kibana:management application is hidden. This ensures users can still access reporting features regardless of their Kibana management visibility settings.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#fields">blocks containing filter and/or fields won't match internal kibana requests, so kibana_* rules won't have to be placed in such blocks</a></summary>

Fixed an issue where ACL blocks containing `filter` and/or `fields` rules could incorrectly match internal Kibana requests. With this fix, internal Kibana requests bypass such blocks, meaning `kibana_*` rules no longer need to be placed inside blocks that also define field-level or document-level security rules. This simplifies ACL configuration and prevents unintended access restrictions on Kibana's internal operations.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) SQL API - better handling of invalid query</summary>

Improved error handling for the Elasticsearch SQL API when an invalid query is submitted. Instead of potentially returning unclear or inconsistent responses, ROR now handles malformed SQL queries more gracefully, providing better feedback and stability.

</details>

### (2021-01-11) What's new in **ROR 1.26.1**

&#x20;       **🐞Fix** (ES) wrong behaviour of `kibana_access` rule for ROR actions when ADMIN value is set

### (2021-01-02) What's new in **ROR 1.26.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35490) & [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35491) (removed Jackson dependency from ROR core)

&#x20;       **🚀New** (ES) [New response\_fields rule](https://forum.readonlyrest.com/t/ror-1-18-9-enterprise-es-7-2-0-enable-cluster-health-without-authentication/1567)

&#x20;       **🚀New** (ES) [Support for LDAP server discovery using \_ldaps.\_tcp SRV record](https://forum.readonlyrest.com/t/does-ror-support-dc-locator/1211)

&#x20;       **🚀New** (ES) [New configuration option allowing to ignore LDAP connectivity problems](https://forum.readonlyrest.com/t/ror-cannot-start-if-ldap-is-not-available/1748)

&#x20;       **🧐Enhancement** (ES) Full support for ILM API

&#x20;       **🧐Enhancement** (KBN) Enforce read-after-write consistency between kibana nodes

&#x20;       **🧐Enhancement** (KBN ENT) OIDC custom claims incorporated in "assertion" claim

&#x20;       **🧐Enhancement** (KBN ENT) OIDC support for configurable kibanaExternalHost (good for Docker)

&#x20;       **🧐Enhancement** (KBN ENT) ROR adds "ror-user\_" class to "body" tag for easy per-user CSS/JS

&#x20;       **🧐Enhancement** (KBN ENT/PRO) ROR adds "ror-group\_" class to "body" tag for easy per-group CSS/JS

&#x20;       **🐞Fix** (ES) [ROR authentication endpoint action](https://forum.readonlyrest.com/t/es-7-4-2-ror-1-18-9-rradmin-refreshsettings-by-block-default/1388)

&#x20;       **🐞Fix** (ES) "username" in audit entry when request is rejected ### What's new in 1.25.2

&#x20;       **🐞Fix** (ES) [removed verbose logging](https://forum.readonlyrest.com/t/elastic-message-cannot-extract-fields-for-query-after-readonlyrest-installation/1749) ### What's new in 1.25.1

&#x20;       **🚨Security Fix** (ES) [CVE-2020-25649](https://nvd.nist.gov/vuln/detail/CVE-2020-25649)

&#x20;       **🚀New** (ES) 7.10.1 support ### What's new in 1.25.0

&#x20;       **🚨Security Fix** (ES) [Common Vulnerabilities and Exposures (CVE)](https://forum.readonlyrest.com/t/update-of-jackson-databind-2-9-6-jar/176)

&#x20;       **🚀New** (ES) 7.10.0 support

&#x20;       **🚀New** (ES) [auth\_key\_pbkdf2 rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#auth_key_pbkdf2)

&#x20;       **🚀New** (ES) [Introduced configuration property defining FLS engine used by fields rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#fields)

&#x20;       **🧐Enhancement** (ES) Fields rule performance improvement

&#x20;       **🧐Enhancement** (ES) Resolved index API support

&#x20;       **🐞Fix** (ES) ["username" in audit entry when user is authenticated via proxy\_auth](https://forum.readonlyrest.com/t/ror-audit-not-logging-user-id)

&#x20;       **🐞Fix** (ES) index resolve action should be treated as readonly action

&#x20;       **🐞Fix** (ES) /\_snapshot and /\_snapshot/\_all should behave the same ### What's new in 1.24.0

&#x20;       **🚨Security Fix** (ES) search template handling fix

&#x20;       **🚀New** (ES) 7.9.3 & 6.8.13 support

&#x20;       **🧐Enhancement** (ES) full support for ES Snapshots and Restore APIs

&#x20;       **🐞Fix** (KBN) fix crash in error handling

&#x20;       **🐞Fix** (ES) don't remove ES response warning headers

&#x20;       **🐞Fix** (ES) issue when entropy of /dev/random could have been exhausted when using JwtToken rule ### What's new in 1.23.1

&#x20;       **🚀New** (ES) 7.9.2 support

&#x20;       **🐞Fix** (KBN) fix code 500 error on login in Kibana ### What's new in 1.23.0

&#x20;       **🚀New** (ES) introduced must\_involve\_indices option for indices rule

&#x20;       **🧐Enhancement** (ES) negation support in headers rules

&#x20;       **🧐Enhancement** (ES) [x-pack rollup API handling](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)

&#x20;       **🐞Fix** (KBN) deep links query parameters are now handled

&#x20;       **🐞Fix** (KBN) make sure default kibana index is always discovered (fixes reporting in 6.x)

&#x20;       **🐞Fix** (ES) [settings file permission issue with JDK 1.8.0 25.262-b10](https://forum.readonlyrest.com/t/readonlyrest-for-elastic-wont-start-1-18-8-es6-8-1/1652)

&#x20;       **🐞Fix** (ES) /\_cluster/allocation/explain request should not be forbidden if matched block doesn't have indices rules

&#x20;       **🐞Fix** (ES) remote address extracting issue

&#x20;       **🐞Fix** (ES) [fixed TYP audit field for some request types](https://forum.readonlyrest.com/t/match-wrong-index-in-forbid-block/1653/2) ### What's new in 1.22.1

&#x20;       **🐞Fix** (ES) missing handling of aliases API for ES 7.9.0 ### What's new in 1.22.0

&#x20;       **🚀New** (ES) 7.9.0 support

&#x20;       **🧐Enhancement** (ES) aliases API handling

&#x20;       **🧐Enhancement** (ES) dynamic variables support in fields rule

&#x20;       **🐞Fix** (ES) [adding aliases issue](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)

&#x20;       **🐞Fix** (ES) potential memory leak for ES 7.7.x and above

&#x20;       **🐞Fix** (ES) cross cluster search issue fix for X-Pack \_async\_search action

&#x20;       **🐞Fix** (ES) XFF entry in audit issue

&#x20;       **🐞Fix** (KBN) SAML certificate loading

&#x20;       **🐞Fix** (KBN) SAML loading groups from assertion

&#x20;       **🐞Fix** (KBN) fix reporting in pre-7.7.0 ### What's new in 1.21.0

&#x20;       **🧐Enhancement** (ES) [cluster API support improvements](https://forum.readonlyrest.com/t/settings-problems/1616)

&#x20;       **🐞Fix** (ES) X-Pack \_async\_search support

&#x20;       **🐞Fix** (ES) \_rollover request handling

&#x20;       **🐞Fix** (ES) [handling numeric ssl configuration properties](https://forum.readonlyrest.com/t/numeric-passphrases-invalid-ssl-config/1512)

&#x20;       **🐞Fix** (KBN) multitenancy+reporting regression fix (for 7.6.x and earlier)

&#x20;       **🐞Fix** (KBN) "x-" headers should be forwarded in /login route when proxy passthrough is enabled

&#x20;       **🐞Fix** (unspecified) [(KBN) Logout now redirects to login screen when using proxy](https://forum.readonlyrest.com/t/kibana-ror-1-19-5-issue/1576/24)

&#x20;       **🐞Fix** (KBN) SAML metadata.xml endpoint not responding

&#x20;       **🐞Fix** (KBN) NAT/reverse proxy support for SAML

&#x20;       **🐞Fix** (KBN) SAML login redirect error

&#x20;       **🐞Fix** (ES) \_readonlyrest/metadata/current\_user should be always allowed by filter/fields rule ### What's new in 1.20.0

&#x20;       **🚀New** (unspecified) 7.7.1, 7.8.0 support

&#x20;       **🧐Enhancement** (KBN) tidy up audit page

&#x20;       **🧐Enhancement** (KBN FREE) clearly inform when features are not available

&#x20;       **🧐Enhancement** (KBN) ship license report of libraries

&#x20;       **🧐Enhancement** (ES) filter rule performance improvement

&#x20;       **🐞Fix** (KBN) proxy\_auth: avoid logout-login loop

&#x20;       **🐞Fix** (KBN) 404 error on font CSS file

&#x20;       **🐞Fix** (ES) [wildcard in filter query issue](https://forum.readonlyrest.com/t/wildcard-in-dls-filter-gives-error/1551)

&#x20;       **🐞Fix** (ES) [forbidden /\_snapshot issue](https://forum.readonlyrest.com/t/get-snapshot-permission-issue/1594)

&#x20;       **🐞Fix** (ES) /\_mget handling by indices rule when no index from a list is found

&#x20;       **🐞Fix** (ES) available groups order in metadata response should match the order in which groups appear in ACL

&#x20;       **🐞Fix** (ES) .readonlyrest and audit index - removed usage of explicit index type

&#x20;       **🐞Fix** (ES) [tasks leak bug](https://forum.readonlyrest.com/t/lots-of-active-tasks-in-cat-tasks/1593) ### What's new in 1.19.5

&#x20;       **🚀New** (unspecified) 7.7.0, 7.6.2, 6.8.9, 6.8.8 support

&#x20;       **🧐Enhancement** (ES/KBN) kibana\_access can be explicitly set to unrestricted

&#x20;       **🧐Enhancement** (ES) [LDAP connection pool improvement](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)

&#x20;       **🐞Fix** (ES) [better LDAP request timeout handling](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)

&#x20;       **🐞Fix** (ES) remote indices searching bug

&#x20;       **🐞Fix** (ES) cross cluster search support for \_field\_caps request

&#x20;       **🚨Security Fix** (ES) create and delete templates handling

&#x20;       **🐞Fix** (KBN) Regression in proxy\_auth\_passthrough

&#x20;       **🧐Enhancement** (KBN) whitelistedPaths now accepts basic auth credentials

&#x20;       **🧐Enhancement** (KBN) Dump logout button, [new ROR Panel](https://forum.readonlyrest.com/t/new-logout-button-design-new-ror-panel/1476)

&#x20;       **🧐Enhancement** (KBN) removed ROR from Kibana sidebar. Admins have a link in new panel.

&#x20;       **🧐Enhancement** (KBN) avoid show login form redirecting from SAML IdP

&#x20;       **🚀New** (KBN) [OpenID Connect (OIDC) authentication connector](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#openid-connect-oidc)

&#x20;       **🚀New** (KBN) [login\_title, login\_subtitle enable 2 column login page](https://forum.readonlyrest.com/t/ror-enterprise-show-support-contact-on-login-page/1508/2)

&#x20;       **🚨Security Fix** (KBN) server-side navigation prevention to hidden apps ### What's new in 1.19.4

&#x20;       **🐞Fix** (ES) Interpolating config with environment variables in SSL section

&#x20;       **🐞Fix** (KBN Ent 6.x) Fixed default space creation in

&#x20;       **🐞Fix** (KBN 6.x) Fixed error toast notification not showing

&#x20;       **🐞Fix** (KBN Ent) Fixed missing Axios dependency

&#x20;       **🐞Fix** (KBN Ent) Fixed SAML connector

&#x20;       **🐞Fix** (KBN) Toast notification overlap with logout bar

&#x20;       **🧐Enhancement** (KBN) Restyled logout bar

&#x20;       **🧐Enhancement** (KBN) Configurable periodic session checker ### What's new in 1.19.3

&#x20;       **🚀New** (ES/KBN) 7.6.1 compatibility

&#x20;       **🚀New** (ES) customizable name of settings index

&#x20;       **🧐Enhancement** (KBN) configurable ROR cookie name

&#x20;       **🧐Enhancement** (ES/KBN) handling of encoded ROR headers in Authorization header values

&#x20;       **🧐Enhancement** (KBN) user feedback on why login failed

&#x20;       **🐞Fix** (ES) support for multiple header values

&#x20;       **🐞Fix** (ES) releasing LDAP connection pool on reloading ROR settings

&#x20;       **🐞Fix** (KBN) multitenancy issue with 7.6.0+

&#x20;       **🐞Fix** (KBN) creation of default space for new tenant

&#x20;       **🐞Fix** (KBN 6.x) in RO mode, don't hide add/remove over fields in discovery

&#x20;       **🐞Fix** (KBN 6.x) index template & in-index session manager issues ### What's new in 1.19.2

&#x20;       **🚀New** (KBN) 7.6.0 support

&#x20;       **🧐Enhancement** (KBN) less verbose info logging

&#x20;       **🧐Enhancement** (KBN) start up time semantic check for settings

&#x20;       **🐞Fix** (KBN Free) missing logout button

&#x20;       **🐞Fix** (KBN) error message creating internal proxy

&#x20;       **🐞Fix** (KBN 6.x) add field to filter button invisible in RO mode ### What's new in 1.19.1

&#x20;       **\<unknown>** (KBN) [Launched ReadonlyREST Free for Kibana!](https://forum.readonlyrest.com/t/provide-kibana-login-page-for-ror-oss-version/1441/2?u=sscarduzio)

&#x20;       **🚀New** (ES) 7.6.0 support, Kibana support coming soon

&#x20;       **🚀New** (KBN) Audit log dashboard

&#x20;       **🚀New** (KBN) Template index can now be declared per tenant instead of globally

&#x20;       **🚀New** (ES) custom trust store file and password options in ROR settings

&#x20;       **🧐Enhancement** (ES) When "prompt\_for\_basic\_auth" is enabled, ROR is going to return 401 instead of 404 when the index is not found or a user is not allowed to see the index

&#x20;       **🧐Enhancement** (ES) literal ipv6 with zone Id is acceptable network address

&#x20;       **🧐Enhancement** (ES) LDAP client cache improvements

&#x20;       **🐞Fix** (ES) /\_all/\_settings API issue

&#x20;       **🐞Fix** (ES) Index stats API & Index shard stores API issue

&#x20;       **🐞Fix** (ES) readonlyrest.force\_load\_from\_file setting decoding issue

&#x20;       **🐞Fix** (KBN) allowing user to be logged in in two tabs at the same time

&#x20;       **🐞Fix** (KBN) logging with JWT parameter issue

&#x20;       **🐞Fix** (KBN) parsing of sessions fetched from ES index

&#x20;       **🐞Fix** (KBN) logout issue ### What's new in 1.19.0

&#x20;       **🚀New** (KBN) Configurable option to delete docs from tenant index when not present in template

&#x20;       **🧐Enhancement** (ES) Less verbose logging of blocks history

&#x20;       **🧐Enhancement** (ES) Enriched logs and audit with attempted username

&#x20;       **🧐Enhancement** (ES) Better settings validation - only one authentication rule can be used in given block

&#x20;       **🧐Enhancement** (ES/KBN) Plugin versions printing in logs on launch

&#x20;       **🧐Enhancement** (ES) When user doesn't have access to given index, ROR pretends that the index doesn't exist and return 404 instead of 403

&#x20;       **🐞Fix** (ES) Searching for nonexistent/forbidden index with wildcard mirrors default ES behaviour instead of returning 403

&#x20;       **🐞Fix** (KBN) Switching groups bug ### What's new in 1.18.10

&#x20;       **🚀New** (ES/KBN) Support v6.8.6, v7.5.0, v7.5.1

&#x20;       **🚀New** (KBN) Group IDs can now be mapped to aliases

&#x20;       **🚀New** (ES) New, more robust and simple method of creating custom audit log serializers

&#x20;       **🚀New** (ES) Example projects with custom audit log serializers

&#x20;       **\<unknown>** (KBN) Prevent index migration after kibana startup

&#x20;       **🧐Enhancement** (KBN) If default space doesn't exist in kibana index then copy from default one

&#x20;       **🧐Enhancement** (KBN) Crypto improvements - store init vector with encrypted data as base64 encoded json.

&#x20;       **🧐Enhancement** (ES) Better settings validation - prevent duplicated keys in readonlyrest.yml ### What's new in 1.18.9

&#x20;       **🚀New** (ES/KBN) Support v7.4.1, v7.4.2

&#x20;       **🚀New** (KBN) Kibana sessions stored in ES index

&#x20;       **\<unknown>** (ES) issue with in-index settings auto-reloading

&#x20;       **\<unknown>** (ES) \_cat/indices empty response when matched block doesn't contain 'indices' rule ### What's new in 1.18.8

&#x20;       **🚀New** (ES/KBN) Support v7.4.0

&#x20;       **🚀New** (ES) Elasticsearch SQL Support

&#x20;       **🚀New** (ES) Internode ssl support for es5x, es60x, es61x and es62x

&#x20;       **🚀New** (ES) new runtime variable @{acl:current\_group}

&#x20;       **🚀New** (ES) namespace for user variable and support for both versions: @{user} and @{acl:user}

&#x20;       **🚀New** (ES) support for multiple values in uri\_re rule

&#x20;       **🧐Enhancement** (ES) more reliable in-index settings loading of ES with ROR startup

&#x20;       **🧐Enhancement** (ES) less verbose logs in JWT rules

&#x20;       **🧐Enhancement** (ES) Better response from ROR API when plugin is disabled

&#x20;       **🧐Enhancement** (ES) Splitting verification ssl property to client\_authentication and certificate\_verification

&#x20;       **🐞Fix** (ES) issue with backward compatibility of proxy\_auth settings

&#x20;       **🐞Fix** (ES) /\_render/template request NPE

&#x20;       **🐞Fix** (ES) \_cat/indices API bug fixes

&#x20;       **🐞Fix** (ES) \_cat/templates API return empty list instead of FORBIDDEN when no indices are found

&#x20;       **🐞Fix** (ES) updated regex for kibana access rule to support 7.3 ES

&#x20;       **🐞Fix** (ES) proper resolving of non-string ENV variables in readonlyrest.yml

&#x20;       **🐞Fix** (ES) lang-mustache search template handling ### What's new in 1.18.7

&#x20;       **🚀New** (ES) Field level security (FLS) supports nested JSON fields

&#x20;       **🐞Fix** (ES) Authorization headers appeared in clear in logs

&#x20;       **🧐Enhancement** (KBN) Don't logout users when they are not allowed to search a index-pattern

&#x20;       **🧐Enhancement** (ES) Headers obfuscation is now case insensitive ### What's new in 1.18.6

&#x20;       **🚀New** (ES/KBN) Support v7.3.1, v7.3.2

&#x20;       **🚀New** (ES) Configurable header names whose value should be obfuscated in logs

&#x20;       **🚀New** (KBN) Dynamic variables from user identity available in custom\_logout\_link

&#x20;       **🧐Enhancement** (ES) Richer logs for JWT errors

&#x20;       **🧐Enhancement** (ENT) nextUrl works also with SAML now

&#x20;       **🧐Enhancement** (ENT) SAML assertion object available in ACL dynamic variables

&#x20;       **🧐Enhancement** (KBN) Validate LDAP server(s) before accepting new YAML settings

&#x20;       **🧐Enhancement** (KBN) Ensure a read-only UX for 'ro' users in older Kibana

&#x20;       **🐞Fix** (ES) Fix memory leak from dependency (snakeYAML) ### What's new in 1.18.5

&#x20;       **🐞Fix** (ES) indices rule can now properly handle also the templates API

&#x20;       **🧐Enhancement** (ES) Array dynamic variables are serialized as CSV wrapped in double quotes

&#x20;       **🧐Enhancement** (ES) Cleaner debug logs (no stacktraces on forbidden requests)

&#x20;       **🧐Enhancement** (ES) LDAP debug logs fire also when cache is hit

&#x20;       **🚀New** (ES/KBN) Support v7.2.1, v7.3.0

&#x20;       **🐞Fix** (PRO) PRO plugin crashing for some Kibana versions

&#x20;       **🐞Fix** (ENT) SAML library wrote a too large cookie sometimes

&#x20;       **🐞Fix** (ENT) SAML logout not working

&#x20;       **🐞Fix** (ENT) JWT fix exception "cannot set requestHeadersWhitelist"

&#x20;       **🐞Fix** (PRO/ENT) Hide more UI elements for RO users

&#x20;       **🐞Fix** (PRO/ENT) Sometimes not all the available groups appear in tenancy selector

&#x20;       **🐞Fix** (PRO/ENT) Feature "nextUrl" broke

&#x20;       **🐞Fix** (PRO/ENT) prevent user kick-out when APM is not configured and you are not an admin

&#x20;       **🚀New** (PRO/ENT) Kibana request path/method now sent to ES (good for policing dev-tools) ### What's new in 1.18.4

&#x20;       **🚀New** (ES) User impersonation API

&#x20;       **🚀New** (ES) Support latest 6.x and 5.x versions

&#x20;       **🐞Fix** (ES) filter/fields rules leak

&#x20;       **🐞Fix** (KBN/ENT) allow more action for kibana\_access, prevent sudden logout

&#x20;       **🐞Fix** (KBN/ENT) temporarily roll back "support for unlimited tenancies" ### What's new in 1.18.3

&#x20;       **🚀New** (unspecified) Support added for ES/Kibana 6.8.1

&#x20;       **🧐Enhancement** (ES) Crash ES on invalid settings instead of stalling forever

&#x20;       **🧐Enhancement** (ES) Better logging on JWT, JSON-paths, LDAP, YAML errors

&#x20;       **🧐Enhancement** (ES) Block level settings validation to user with precious hints

&#x20;       **🧐Enhancement** (ES) If force\_load\_from\_file: true, don't poll index settings

&#x20;       **🧐Enhancement** (ES) Order now counts declaring LDAP Failover HA servers

&#x20;       **🐞Fix** (ES) "EsIndexJsonContentProvider" had a null pointer exception

&#x20;       **🐞Fix** (ES) "es.set.netty.runtime.available.processors" exception

&#x20;       **🧐Enhancement** (KBN) Collapsible logout button

&#x20;       **🧐Enhancement** (KBN) ROR App now uses a HA http client

&#x20;       **🧐Enhancement** (KBN) Automatic logout for inactivity

&#x20;       **🧐Enhancement** (KBN) Support unlimited amount of tenancies

&#x20;       **🐞Fix** (KBN/ENT) concurrent multitenancy bug

&#x20;       **🐞Fix** (KBN) Avoid sporadic errors on Save/Load buttons ### What's new in 1.18.2

&#x20;       **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.2.0

&#x20;       **🐞Fix** (ES) restore indices ("IDX") in audit logging

&#x20;       **🧐Enhancement** (ES) New algorithm of setting evaluation order

&#x20;       **🚀New** (ES) JWT claims as dynamic variables. I.e. "@{jwt:claim.json.path}"

&#x20;       **🚀New** (ES) "explode" dynamic variables. I.e. indices: \["@explode{x-indices}"]

&#x20;       **🐞Fix** (PRO/Enterprise) preserve comments and formatting in YAML editor

&#x20;       **🐞Fix** (PRO/Enterprise) Print error message when session is expired

&#x20;       **🐞Fix** (PRO/Enterprise) Redirect to original link after login

&#x20;       **🐞Fix** (PRO/Enterprise) Broken CSV reporting

&#x20;       **🧐Enhancement** (PRO/Enterprise) Prevent navigating away from YAML editor w/ unsaved changes

&#x20;       **🐞Fix** (Enterprise) Exception when SAML connectors were all disabled

&#x20;       **🐞Fix** (Enterprise) Concurrent tenants could mix up each other kibana index

&#x20;       **🐞Fix** (Enterprise) Cannot inject custom JS if no custom CSS was also declared

&#x20;       **🐞Fix** (Enterprise) Injected JS had no effect on ROR logout button

&#x20;       **🐞Fix** (Enterprise) On narrow screens, the YAML editor showed buttons twice ### What's new in 1.18.1

&#x20;       **🐞Fix** (Elasticsearch) Reindex requests failed for a regression in indices extraction

&#x20;       **🐞Fix** (Elasticsearch) Groups rule erratically failed

&#x20;       **🐞Fix** (Elasticsearch) JWT claims can now contain special characters

&#x20;       **🧐Enhancement** (Elasticsearch) Better ACL History logging

&#x20;       **🧐Enhancement** (Elasticsearch) QueryLogSerializer and old custom log serializers work again

&#x20;       **🐞Fix** (PRO/Enterprise) ReadonlyREST icon in Kibana was white on white

&#x20;       **🐞Fix** (Enterprise) SAML connectors could not be disabled

&#x20;       **🐞Fix** (Enterprise) SAML connector "buttonName" didn't work ### What's new in 1.18.0

&#x20;       **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.0.1

&#x20;       **🧐Enhancement** (Elasticsearch) empty array values in settings are invalid

&#x20;       **🐞Fix** (Elasticsearch) arbitrary x-cluster search referencing local cluster

&#x20;       **🐞Fix** (Elasticsearch) ArrayOutOfBoundException on snapshot operations

&#x20;       **🧐Enhancement** (PRO/Enterprise) History cleaning can now be disabled ("clearSessionOnEvents") ### What's new in 1.17.7

&#x20;       **🚀New** (unspecified) Support for Elasticsearch 7.0.0 (Kibana is coming soon)

&#x20;       **🧐Enhancement** (Elasticsearch) rewritten LDAP connector

&#x20;       **🧐Enhancement** (Elasticsearch) new core written in Scala is now GA

&#x20;       **🐞Fix** (Enterprise) devtools requests now honor the currently selected tenancy

&#x20;       **🐞Fix** (Enterprise/PRO) Fix "connectorsService" error in installation ### What's new in 1.17.5

&#x20;       **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.1

&#x20;       **🧐Enhancement** (Enterprise >= Kibana 6.6.0) Multiple SAML identity provider

&#x20;       **🐞Fix** (Enterprise/PRO) Don't pass auth headers back to the browser

&#x20;       **🐞Fix** (Enterprise/PRO) Missing null check caused error in reporting (CSV)

&#x20;       **🐞Fix** (Enterprise) Don't reject requests if SAML groups are not configured

&#x20;       **🐞Fix** (unspecified) filter/fields rules not working in msearch (in 6.7.x)

&#x20;       **🧐Enhancement** (unspecified) Print whole LDAP search query in debug log ### What's new in 1.17.4

&#x20;       **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.0

&#x20;       **🧐Enhancement** (PRO/Enterprise) JWT query param is the preferred credentials provider

&#x20;       **🧐Enhancement** (PRO/Enterprise) admin users can use indices management

&#x20;       **🧐Enhancement** (PRO/Enterprise) ro users can dismiss telemetry form

&#x20;       **🐞Fix** (unspecified) Audit logging in 5.1.x now works again

&#x20;       **🐞Fix** (unspecified) unpredictable behaviour of "filter" and "fields" when using external auth

&#x20;       **🐞Fix** (unspecified) LDAP ConcurrentModificationException

&#x20;       **🐞Fix** (unspecified) Audit logging in 5.1.x now works again

&#x20;       **🐞Fix** (PRO/Enterprise) JWT deep-link works again ### What's new in 1.17.3 1.17.2 went unreleased, all changes have been merged in 1.17.3 directly

&#x20;       **🐞Fix** (Enterprise) Tenancy selector showing if user belonged to one group

&#x20;       **🐞Fix** (PRO/Enterprise) RW buttons not hiding for RO users in React Kibana apps

&#x20;       **🐞Fix** (Enterprise) Tenancy templating now works much more reliably

&#x20;       **🐞Fix** (Enterprise) Missing tenancy selector icon after switching tenancy

&#x20;       **🐞Fix** (PRO/Enterprise) barring static files requests caused sudden logout

&#x20;       **🐞Fix** (unspecified) Numerous fixes to better support Kibana 6.6.x

&#x20;       **🐞Fix** (unspecified) Critical fixes in new Scala core

&#x20;       **🐞Fix** (unspecified) Exception in reindex requests caused tenancy templating to fail

&#x20;       **🧐Enhancement** (unspecified) Bypass cross-cluster search logic if single cluster ### What's new in 1.17.1

&#x20;       **🐞Fix** (PRO/Enterprise) SAML now works well in 6.6.x

&#x20;       **🐞Fix** (PRO/Enterprise) "undefined" authentication error before login

&#x20;       **🐞Fix** (Enterprise) Default space creation failures for new tenants

&#x20;       **🐞Fix** (Enterprise) Icons/titles CSS misalignment in sidebar (Firefox)

&#x20;       **🧐Enhancement** (Enterprise) UX: Larger tenancy selector

&#x20;       **🐞Fix** (Enterprise) Privilege escalation when changing tenancies under monitoring

&#x20;       **🐞Fix** (Elasticsearch) compatibility fixes to support new Kibana features

&#x20;       **🧐Enhancement** (Elasticsearch) New core and LDAP connector written in Scala is finished, now under QA. ### What's new in 1.17.0

&#x20;       **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.6.0, 6.6.1

&#x20;       **🚀New** (unspecified) Internode SSL (ES 6.3.x onwards)

&#x20;       **🧐Enhancement** (PRO/Enterprise) UI appearence

&#x20;       **🧐Enhancement** (unspecified) Made HTTP Connection configurable (PR #410)

&#x20;       **🐞Fix** (unspecified) slow boot due to SecureRandom waiting for sufficient entropy

&#x20;       **🐞Fix** (unspecified) Enable kibana\_access:ro to create short urls in es6.3+ (PR #408) ### What's new in 1.16.34

&#x20;       **🧐Enhancement** (unspecified) X-Forwarded-For header in printed es logs ("XFF")

&#x20;       **🧐Enhancement** (unspecified) kibana\_index: ".kibana\_@{user}" when user is "John Doe" becomes .kibana\_john\_doe

&#x20;       **🐞Fix** (Enteprise) parse SAML groups from assertion as array of strings

&#x20;       **🐞Fix** (Enteprise) SAMLRequest in location header was URLEncoded twice, broke on some IdP

&#x20;       **🐞Fix** (PRO/Enteprise) "cookiePass" works again, no more need for sticky cookies in load balancers!

&#x20;       **🐞Fix** (PRO/Enteprise) fix redirect loop with JWT deep linking when JWT token expires

&#x20;       **🧐Enhancement** (PRO/Enteprise) fix audit demo page CSS

&#x20;       **🧐Enhancement** (Enteprise) SAML more configuration parameters available

&#x20;       **🚀New** (PRO/Enteprise) set ROR to debug mode (readonlyrest\_kbn.logLevel: "debug") ### What's new in 1.16.33

&#x20;       **🐞Fix** (PRO/Enteprise) compatibility problems with older Kibana versions

&#x20;       **🐞Fix** (PRO/Enteprise) compatibility problems with OSS Kibana version ### What's new in 1.16.32

&#x20;       **🚀New** (unspecified) "kibanaIndexTemplate": default dashboards and spaces for new tenants

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.4

&#x20;       **🧐Enhancement** (unspecified) Upgraded LDAP library

&#x20;       **🧐Enhancement** (Enterprise) Now tenants save their CSV exports in their own reporting index

&#x20;       **🐞Fix** (PRO/Enteprise) Support passwords that start and/or end with spaces

&#x20;       **🐞Fix** (PRO/Enterprise) Now reporting works again ### What's new in 1.16.31

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.2, 6.5.3

&#x20;       **\<unknown>** (unspecified) : Laid out the foundation for LDAP HA support ### What's new in 1.16.29

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.3

&#x20;       **🚀New** (PRO/Enterprise) configurable server side session duration

&#x20;       **🚀New** (unspecified) \[LDAP] High Availability: Round Robin or Failover ### What's new in 1.16.28

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.2

&#x20;       **🐞Fix** (Enterprise) Multi tenancy: sometimes changing tenancy would not change kibana index

&#x20;       **🐞Fix** (Enterprise/PRO) Avoid echoing Base64 encoded credentials in login form error message

&#x20;       **🧐Enhancement** (Enterprise/PRO) Remove latest search/visualization/dashboard history on logout

&#x20;       **🧐Enhancement** (Enterprise/PRO) Clear transient authentication cookies on login error to avoid authentication deadlocks

&#x20;       **🐞Fix** (unspecified) : External JWT verification may throw ArrayOutOfBoundException

&#x20;       **\<unknown>** (unspecified) : Laid out the foundation for internode SSL transport (port 9300) ### What's new in 1.16.27

&#x20;       **🚀New** (unspecified) \[JWT] external validator: it's now possible to avoid storing the private key in settings

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.1

&#x20;       **🧐Enhancement** (unspecified) Rewritten big part of ES plugin [documentation](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md)

&#x20;       **🧐Enhancement** (unspecified) SAML Single log out flow

&#x20;       **🐞Fix** (Enterprise/PRO) [cookiePass](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#common-cookie-encryption-secret) works again, but only for Kibana 5.x. Newer Kibana needs sticky sessions in LB.

&#x20;       **🧐Enhancement** (Enterprise/PRO) much faster logout ### What's new in 1.16.26

&#x20;       **🐞Fix** (PRO/Enterprise) bugs during plugin packaging and installation process ### What's new in 1.16.25

&#x20;       **🚀New** (unspecified) Users rule: easily restrict external authentication to a list of users

&#x20;       **🧐Enhancement** (unspecified) Support for ES 5.6.11

&#x20;       **🐞Fix** (Enterprise/PRO) Error 404 when logging in with older versions of Kibana ### What's new in 1.16.24

&#x20;       **🚀New** (Enterprise) SAML Authentication

&#x20;       **🚀New** (unspecified) Support for Elasticsearch and Kibana 6.4.0

&#x20;       **🚀New** (unspecified) Headers rule now split in headers\_or and headers\_and

&#x20;       **🧐Enhancement** (unspecified) Headers rule now allows wildcards

&#x20;       **🚀New** (Enterprise) Multi-tenancy now works also with JSON groups provider

&#x20;       **🐞Fix** (unspecified) Multi-tenancy (Enterprise) incoherent initial kibana\_index and current group ### What's new in 1.16.23

&#x20;       **🧐Enhancement** (unspecified) Support for Elastic Stack 6.3.1 and 5.6.10

&#x20;       **🚀New** (Enterprise) Custom CSS injection for Kibana

&#x20;       **🚀New** (Enterprise) Custom Javascript injection for Kibana

&#x20;       **🚀New** (PRO/Enterprise) access paths without need to login (i.e. /api/status)

&#x20;       **🐞Fix** (PRO/Enterprise) Navigating to X-Pack APM caused hidden Kibana apps to reappear ### What's new in 1.16.22

&#x20;       **🚀New** (unspecified) map LDAP groups to local groups (a.k.a. role mapping)

&#x20;       **🐞Fix** (Elasticsearch) wildcard aliases resolution not working in "indices" rule.

&#x20;       **🧐Enhancement** (unspecified) it is now possible now to use JDK 9 and 10

&#x20;       **🐞Fix** (PRO/Enterprise) wait forever for login request (i.e. slow LDAP servers)

&#x20;       **🐞Fix** (PRO/Enterprise) add spinner and block UI if login request is being sent

&#x20;       **🐞Fix** (PRO/Enterprise) if user is logged out because of LDAP cache expiring + slow authentication, redirect to login.

&#x20;       **🐞Fix** (PRO/Enterprise) let RO users delete/edit search filters ### What's new in 1.16.21

&#x20;       **🚀New** (unspecified) Introducing support for Elasticsearch and Kibana v6.3.0

&#x20;       **🐞Fix** (Enterprise) multi tenancy - switching tenancy does not always switch kibana index ### What's new in 1.16.20 ## ReadonlyREST PRO/Enterprise for Kibana

&#x20;       **🧐Enhancement** (unspecified) : when login, forward "elasticsearch.requestHeadersWhitelist" headers. (useful for "headers" rule and "proxy\_auth" to work well.) ## ReadonlyREST for Elasticsearch

&#x20;       **🚀New** (unspecified) : DLS (with dynamic variables suppoort) Thanks [DataSweet](http://www.datasweet.fr/)!

&#x20;       **🚀New** (unspecified) : Field level security

&#x20;       **🚀New** (unspecified) : Snapshot, Repositories, Headers

&#x20;       **🧐Enhancement** (unspecified) : custom audit serializers: the request content is available

&#x20;       **🐞Fix** (unspecified) readonlyrest.yml path discovery

&#x20;       **🐞Fix** (unspecified) LDAP available groups discovery (tenancy switcher) corner cases

&#x20;       **🐞Fix** (unspecified) : auth\_key\_sha1, auth\_key\_sha256 hashes in settings should be case insensitive

&#x20;       **🐞Fix** (unspecified) : LDAP authentication didn't work with local group

### (2021-01-02) What's new in **ROR 1.26.0**

* **🚨Security Fix** (ES) [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35490) & [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35491) (removed Jackson dependency from ROR core)
* **🚀New** (ES) [New response\_fields rule](https://forum.readonlyrest.com/t/ror-1-18-9-enterprise-es-7-2-0-enable-cluster-health-without-authentication/1567)
* **🚀New** (ES) [Support for LDAP server discovery using \_ldaps.\_tcp SRV record](https://forum.readonlyrest.com/t/does-ror-support-dc-locator/1211)
* **🚀New** (ES) [New configuration option allowing to ignore LDAP connectivity problems](https://forum.readonlyrest.com/t/ror-cannot-start-if-ldap-is-not-available/1748)
* **🧐Enhancement** (ES) Full support for ILM API
* **🧐Enhancement** (KBN) Enforce read-after-write consistency between kibana nodes
* **🧐Enhancement** (KBN ENT) OIDC custom claims incorporated in "assertion" claim
* **🧐Enhancement** (KBN ENT) OIDC support for configurable kibanaExternalHost (good for Docker)
* **🧐Enhancement** (KBN ENT) ROR adds "ror-user\_" class to "body" tag for easy per-user CSS/JS
* **🧐Enhancement** (KBN ENT/PRO) ROR adds "ror-group\_" class to "body" tag for easy per-group CSS/JS
* **🐞Fix** (ES) [ROR authentication endpoint action](https://forum.readonlyrest.com/t/es-7-4-2-ror-1-18-9-rradmin-refreshsettings-by-block-default/1388)
* **🐞Fix** (ES) "username" in audit entry when request is rejected ### What's new in 1.25.2
* **🐞Fix** (ES) [removed verbose logging](https://forum.readonlyrest.com/t/elastic-message-cannot-extract-fields-for-query-after-readonlyrest-installation/1749) ### What's new in 1.25.1
* **🚨Security Fix** (ES) [CVE-2020-25649](https://nvd.nist.gov/vuln/detail/CVE-2020-25649)
* **🚀New** (ES) 7.10.1 support ### What's new in 1.25.0
* **🚨Security Fix** (ES) [Common Vulnerabilities and Exposures (CVE)](https://forum.readonlyrest.com/t/update-of-jackson-databind-2-9-6-jar/176)
* **🚀New** (ES) 7.10.0 support
* **🚀New** (ES) [auth\_key\_pbkdf2 rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#auth_key_pbkdf2)
* **🚀New** (ES) [Introduced configuration property defining FLS engine used by fields rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#fields)
* **🧐Enhancement** (ES) Fields rule performance improvement
* **🧐Enhancement** (ES) Resolved index API support
* **🐞Fix** (ES) ["username" in audit entry when user is authenticated via proxy\_auth](https://forum.readonlyrest.com/t/ror-audit-not-logging-user-id)
* **🐞Fix** (ES) index resolve action should be treated as readonly action
* **🐞Fix** (ES) /\_snapshot and /\_snapshot/\_all should behave the same ### What's new in 1.24.0
* **🚨Security Fix** (ES) search template handling fix
* **🚀New** (ES) 7.9.3 & 6.8.13 support
* **🧐Enhancement** (ES) full support for ES Snapshots and Restore APIs
* **🐞Fix** (KBN) fix crash in error handling
* **🐞Fix** (ES) don't remove ES response warning headers
* **🐞Fix** (ES) issue when entropy of /dev/random could have been exhausted when using JwtToken rule ### What's new in 1.23.1
* **🚀New** (ES) 7.9.2 support
* **🐞Fix** (KBN) fix code 500 error on login in Kibana ### What's new in 1.23.0
* **🚀New** (ES) introduced must\_involve\_indices option for indices rule
* **🧐Enhancement** (ES) negation support in headers rules
* **🧐Enhancement** (ES) [x-pack rollup API handling](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)
* **🐞Fix** (KBN) deep links query parameters are now handled
* **🐞Fix** (KBN) make sure default kibana index is always discovered (fixes reporting in 6.x)
* **🐞Fix** (ES) [settings file permission issue with JDK 1.8.0 25.262-b10](https://forum.readonlyrest.com/t/readonlyrest-for-elastic-wont-start-1-18-8-es6-8-1/1652)
* **🐞Fix** (ES) /\_cluster/allocation/explain request should not be forbidden if matched block doesn't have indices rules
* **🐞Fix** (ES) remote address extracting issue
* **🐞Fix** (ES) [fixed TYP audit field for some request types](https://forum.readonlyrest.com/t/match-wrong-index-in-forbid-block/1653/2) ### What's new in 1.22.1
* **🐞Fix** (ES) missing handling of aliases API for ES 7.9.0 ### What's new in 1.22.0
* **🚀New** (ES) 7.9.0 support
* **🧐Enhancement** (ES) aliases API handling
* **🧐Enhancement** (ES) dynamic variables support in fields rule
* **🐞Fix** (ES) [adding aliases issue](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)
* **🐞Fix** (ES) potential memory leak for ES 7.7.x and above
* **🐞Fix** (ES) cross cluster search issue fix for X-Pack \_async\_search action
* **🐞Fix** (ES) XFF entry in audit issue
* **🐞Fix** (KBN) SAML certificate loading
* **🐞Fix** (KBN) SAML loading groups from assertion
* **🐞Fix** (KBN) fix reporting in pre-7.7.0 ### What's new in 1.21.0
* **🧐Enhancement** (ES) [cluster API support improvements](https://forum.readonlyrest.com/t/settings-problems/1616)
* **🐞Fix** (ES) X-Pack \_async\_search support
* **🐞Fix** (ES) \_rollover request handling
* **🐞Fix** (ES) [handling numeric ssl configuration properties](https://forum.readonlyrest.com/t/numeric-passphrases-invalid-ssl-config/1512)
* **🐞Fix** (KBN) multitenancy+reporting regression fix (for 7.6.x and earlier)
* **🐞Fix** (KBN) "x-" headers should be forwarded in /login route when proxy passthrough is enabled
* **🐞Fix** (unspecified) [(KBN) Logout now redirects to login screen when using proxy](https://forum.readonlyrest.com/t/kibana-ror-1-19-5-issue/1576/24)
* **🐞Fix** (KBN) SAML metadata.xml endpoint not responding
* **🐞Fix** (KBN) NAT/reverse proxy support for SAML
* **🐞Fix** (KBN) SAML login redirect error
* **🐞Fix** (ES) \_readonlyrest/metadata/current\_user should be always allowed by filter/fields rule ### What's new in 1.20.0
* **🚀New** (unspecified) 7.7.1, 7.8.0 support
* **🧐Enhancement** (KBN) tidy up audit page
* **🧐Enhancement** (KBN FREE) clearly inform when features are not available
* **🧐Enhancement** (KBN) ship license report of libraries
* **🧐Enhancement** (ES) filter rule performance improvement
* **🐞Fix** (KBN) proxy\_auth: avoid logout-login loop
* **🐞Fix** (KBN) 404 error on font CSS file
* **🐞Fix** (ES) [wildcard in filter query issue](https://forum.readonlyrest.com/t/wildcard-in-dls-filter-gives-error/1551)
* **🐞Fix** (ES) [forbidden /\_snapshot issue](https://forum.readonlyrest.com/t/get-snapshot-permission-issue/1594)
* **🐞Fix** (ES) /\_mget handling by indices rule when no index from a list is found
* **🐞Fix** (ES) available groups order in metadata response should match the order in which groups appear in ACL
* **🐞Fix** (ES) .readonlyrest and audit index - removed usage of explicit index type
* **🐞Fix** (ES) [tasks leak bug](https://forum.readonlyrest.com/t/lots-of-active-tasks-in-cat-tasks/1593) ### What's new in 1.19.5
* **🚀New** (unspecified) 7.7.0, 7.6.2, 6.8.9, 6.8.8 support
* **🧐Enhancement** (ES/KBN) kibana\_access can be explicitly set to unrestricted
* **🧐Enhancement** (ES) [LDAP connection pool improvement](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)
* **🐞Fix** (ES) [better LDAP request timeout handling](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)
* **🐞Fix** (ES) remote indices searching bug
* **🐞Fix** (ES) cross cluster search support for \_field\_caps request
* **🚨Security Fix** (ES) create and delete templates handling
* **🐞Fix** (KBN) Regression in proxy\_auth\_passthrough
* **🧐Enhancement** (KBN) whitelistedPaths now accepts basic auth credentials
* **🧐Enhancement** (KBN) Dump logout button, [new ROR Panel](https://forum.readonlyrest.com/t/new-logout-button-design-new-ror-panel/1476)
* **🧐Enhancement** (KBN) removed ROR from Kibana sidebar. Admins have a link in new panel.
* **🧐Enhancement** (KBN) avoid show login form redirecting from SAML IdP
* **🚀New** (KBN) [OpenID Connect (OIDC) authentication connector](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#openid-connect-oidc)
* **🚀New** (KBN) [login\_title, login\_subtitle enable 2 column login page](https://forum.readonlyrest.com/t/ror-enterprise-show-support-contact-on-login-page/1508/2)
* **🚨Security Fix** (KBN) server-side navigation prevention to hidden apps ### What's new in 1.19.4
* **🐞Fix** (ES) Interpolating config with environment variables in SSL section
* **🐞Fix** (KBN Ent 6.x) Fixed default space creation in
* **🐞Fix** (KBN 6.x) Fixed error toast notification not showing
* **🐞Fix** (KBN Ent) Fixed missing Axios dependency
* **🐞Fix** (KBN Ent) Fixed SAML connector
* **🐞Fix** (KBN) Toast notification overlap with logout bar
* **🧐Enhancement** (KBN) Restyled logout bar
* **🧐Enhancement** (KBN) Configurable periodic session checker ### What's new in 1.19.3
* **🚀New** (ES/KBN) 7.6.1 compatibility
* **🚀New** (ES) customizable name of settings index
* **🧐Enhancement** (KBN) configurable ROR cookie name
* **🧐Enhancement** (ES/KBN) handling of encoded ROR headers in Authorization header values
* **🧐Enhancement** (KBN) user feedback on why login failed
* **🐞Fix** (ES) support for multiple header values
* **🐞Fix** (ES) releasing LDAP connection pool on reloading ROR settings
* **🐞Fix** (KBN) multitenancy issue with 7.6.0+
* **🐞Fix** (KBN) creation of default space for new tenant
* **🐞Fix** (KBN 6.x) in RO mode, don't hide add/remove over fields in discovery
* **🐞Fix** (KBN 6.x) index template & in-index session manager issues ### What's new in 1.19.2
* **🚀New** (KBN) 7.6.0 support
* **🧐Enhancement** (KBN) less verbose info logging
* **🧐Enhancement** (KBN) start up time semantic check for settings
* **🐞Fix** (KBN Free) missing logout button
* **🐞Fix** (KBN) error message creating internal proxy
* **🐞Fix** (KBN 6.x) add field to filter button invisible in RO mode ### What's new in 1.19.1
* **\<unknown>** (KBN) [Launched ReadonlyREST Free for Kibana!](https://forum.readonlyrest.com/t/provide-kibana-login-page-for-ror-oss-version/1441/2?u=sscarduzio)
* **🚀New** (ES) 7.6.0 support, Kibana support coming soon
* **🚀New** (KBN) Audit log dashboard
* **🚀New** (KBN) Template index can now be declared per tenant instead of globally
* **🚀New** (ES) custom trust store file and password options in ROR settings
* **🧐Enhancement** (ES) When "prompt\_for\_basic\_auth" is enabled, ROR is going to return 401 instead of 404 when the index is not found or a user is not allowed to see the index
* **🧐Enhancement** (ES) literal ipv6 with zone Id is acceptable network address
* **🧐Enhancement** (ES) LDAP client cache improvements
* **🐞Fix** (ES) /\_all/\_settings API issue
* **🐞Fix** (ES) Index stats API & Index shard stores API issue
* **🐞Fix** (ES) readonlyrest.force\_load\_from\_file setting decoding issue
* **🐞Fix** (KBN) allowing user to be logged in in two tabs at the same time
* **🐞Fix** (KBN) logging with JWT parameter issue
* **🐞Fix** (KBN) parsing of sessions fetched from ES index
* **🐞Fix** (KBN) logout issue ### What's new in 1.19.0
* **🚀New** (KBN) Configurable option to delete docs from tenant index when not present in template
* **🧐Enhancement** (ES) Less verbose logging of blocks history
* **🧐Enhancement** (ES) Enriched logs and audit with attempted username
* **🧐Enhancement** (ES) Better settings validation - only one authentication rule can be used in given block
* **🧐Enhancement** (ES/KBN) Plugin versions printing in logs on launch
* **🧐Enhancement** (ES) When user doesn't have access to given index, ROR pretends that the index doesn't exist and return 404 instead of 403
* **🐞Fix** (ES) Searching for nonexistent/forbidden index with wildcard mirrors default ES behaviour instead of returning 403
* **🐞Fix** (KBN) Switching groups bug ### What's new in 1.18.10
* **🚀New** (ES/KBN) Support v6.8.6, v7.5.0, v7.5.1
* **🚀New** (KBN) Group IDs can now be mapped to aliases
* **🚀New** (ES) New, more robust and simple method of creating custom audit log serializers
* **🚀New** (ES) Example projects with custom audit log serializers
* **\<unknown>** (KBN) Prevent index migration after kibana startup
* **🧐Enhancement** (KBN) If default space doesn't exist in kibana index then copy from default one
* **🧐Enhancement** (KBN) Crypto improvements - store init vector with encrypted data as base64 encoded json.
* **🧐Enhancement** (ES) Better settings validation - prevent duplicated keys in readonlyrest.yml ### What's new in 1.18.9
* **🚀New** (ES/KBN) Support v7.4.1, v7.4.2
* **🚀New** (KBN) Kibana sessions stored in ES index
* **\<unknown>** (ES) issue with in-index settings auto-reloading
* **\<unknown>** (ES) \_cat/indices empty response when matched block doesn't contain 'indices' rule ### What's new in 1.18.8
* **🚀New** (ES/KBN) Support v7.4.0
* **🚀New** (ES) Elasticsearch SQL Support
* **🚀New** (ES) Internode ssl support for es5x, es60x, es61x and es62x
* **🚀New** (ES) new runtime variable @{acl:current\_group}
* **🚀New** (ES) namespace for user variable and support for both versions: @{user} and @{acl:user}
* **🚀New** (ES) support for multiple values in uri\_re rule
* **🧐Enhancement** (ES) more reliable in-index settings loading of ES with ROR startup
* **🧐Enhancement** (ES) less verbose logs in JWT rules
* **🧐Enhancement** (ES) Better response from ROR API when plugin is disabled
* **🧐Enhancement** (ES) Splitting verification ssl property to client\_authentication and certificate\_verification
* **🐞Fix** (ES) issue with backward compatibility of proxy\_auth settings
* **🐞Fix** (ES) /\_render/template request NPE
* **🐞Fix** (ES) \_cat/indices API bug fixes
* **🐞Fix** (ES) \_cat/templates API return empty list instead of FORBIDDEN when no indices are found
* **🐞Fix** (ES) updated regex for kibana access rule to support 7.3 ES
* **🐞Fix** (ES) proper resolving of non-string ENV variables in readonlyrest.yml
* **🐞Fix** (ES) lang-mustache search template handling ### What's new in 1.18.7
* **🚀New** (ES) Field level security (FLS) supports nested JSON fields
* **🐞Fix** (ES) Authorization headers appeared in clear in logs
* **🧐Enhancement** (KBN) Don't logout users when they are not allowed to search a index-pattern
* **🧐Enhancement** (ES) Headers obfuscation is now case insensitive ### What's new in 1.18.6
* **🚀New** (ES/KBN) Support v7.3.1, v7.3.2
* **🚀New** (ES) Configurable header names whose value should be obfuscated in logs
* **🚀New** (KBN) Dynamic variables from user identity available in custom\_logout\_link
* **🧐Enhancement** (ES) Richer logs for JWT errors
* **🧐Enhancement** (ENT) nextUrl works also with SAML now
* **🧐Enhancement** (ENT) SAML assertion object available in ACL dynamic variables
* **🧐Enhancement** (KBN) Validate LDAP server(s) before accepting new YAML settings
* **🧐Enhancement** (KBN) Ensure a read-only UX for 'ro' users in older Kibana
* **🐞Fix** (ES) Fix memory leak from dependency (snakeYAML) ### What's new in 1.18.5
* **🐞Fix** (ES) indices rule can now properly handle also the templates API
* **🧐Enhancement** (ES) Array dynamic variables are serialized as CSV wrapped in double quotes
* **🧐Enhancement** (ES) Cleaner debug logs (no stacktraces on forbidden requests)
* **🧐Enhancement** (ES) LDAP debug logs fire also when cache is hit
* **🚀New** (ES/KBN) Support v7.2.1, v7.3.0
* **🐞Fix** (PRO) PRO plugin crashing for some Kibana versions
* **🐞Fix** (ENT) SAML library wrote a too large cookie sometimes
* **🐞Fix** (ENT) SAML logout not working
* **🐞Fix** (ENT) JWT fix exception "cannot set requestHeadersWhitelist"
* **🐞Fix** (PRO/ENT) Hide more UI elements for RO users
* **🐞Fix** (PRO/ENT) Sometimes not all the available groups appear in tenancy selector
* **🐞Fix** (PRO/ENT) Feature "nextUrl" broke
* **🐞Fix** (PRO/ENT) prevent user kick-out when APM is not configured and you are not an admin
* **🚀New** (PRO/ENT) Kibana request path/method now sent to ES (good for policing dev-tools) ### What's new in 1.18.4
* **🚀New** (ES) User impersonation API
* **🚀New** (ES) Support latest 6.x and 5.x versions
* **🐞Fix** (ES) filter/fields rules leak
* **🐞Fix** (KBN/ENT) allow more action for kibana\_access, prevent sudden logout
* **🐞Fix** (KBN/ENT) temporarily roll back "support for unlimited tenancies" ### What's new in 1.18.3
* **🚀New** (unspecified) Support added for ES/Kibana 6.8.1
* **🧐Enhancement** (ES) Crash ES on invalid settings instead of stalling forever
* **🧐Enhancement** (ES) Better logging on JWT, JSON-paths, LDAP, YAML errors
* **🧐Enhancement** (ES) Block level settings validation to user with precious hints
* **🧐Enhancement** (ES) If force\_load\_from\_file: true, don't poll index settings
* **🧐Enhancement** (ES) Order now counts declaring LDAP Failover HA servers
* **🐞Fix** (ES) "EsIndexJsonContentProvider" had a null pointer exception
* **🐞Fix** (ES) "es.set.netty.runtime.available.processors" exception
* **🧐Enhancement** (KBN) Collapsible logout button
* **🧐Enhancement** (KBN) ROR App now uses a HA http client
* **🧐Enhancement** (KBN) Automatic logout for inactivity
* **🧐Enhancement** (KBN) Support unlimited amount of tenancies
* **🐞Fix** (KBN/ENT) concurrent multitenancy bug
* **🐞Fix** (KBN) Avoid sporadic errors on Save/Load buttons ### What's new in 1.18.2
* **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.2.0
* **🐞Fix** (ES) restore indices ("IDX") in audit logging
* **🧐Enhancement** (ES) New algorithm of setting evaluation order
* **🚀New** (ES) JWT claims as dynamic variables. I.e. "@{jwt:claim.json.path}"
* **🚀New** (ES) "explode" dynamic variables. I.e. indices: \["@explode{x-indices}"]
* **🐞Fix** (PRO/Enterprise) preserve comments and formatting in YAML editor
* **🐞Fix** (PRO/Enterprise) Print error message when session is expired
* **🐞Fix** (PRO/Enterprise) Redirect to original link after login
* **🐞Fix** (PRO/Enterprise) Broken CSV reporting
* **🧐Enhancement** (PRO/Enterprise) Prevent navigating away from YAML editor w/ unsaved changes
* **🐞Fix** (Enterprise) Exception when SAML connectors were all disabled
* **🐞Fix** (Enterprise) Concurrent tenants could mix up each other kibana index
* **🐞Fix** (Enterprise) Cannot inject custom JS if no custom CSS was also declared
* **🐞Fix** (Enterprise) Injected JS had no effect on ROR logout button
* **🐞Fix** (Enterprise) On narrow screens, the YAML editor showed buttons twice ### What's new in 1.18.1
* **🐞Fix** (Elasticsearch) Reindex requests failed for a regression in indices extraction
* **🐞Fix** (Elasticsearch) Groups rule erratically failed
* **🐞Fix** (Elasticsearch) JWT claims can now contain special characters
* **🧐Enhancement** (Elasticsearch) Better ACL History logging
* **🧐Enhancement** (Elasticsearch) QueryLogSerializer and old custom log serializers work again
* **🐞Fix** (PRO/Enterprise) ReadonlyREST icon in Kibana was white on white
* **🐞Fix** (Enterprise) SAML connectors could not be disabled
* **🐞Fix** (Enterprise) SAML connector "buttonName" didn't work ### What's new in 1.18.0
* **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.0.1
* **🧐Enhancement** (Elasticsearch) empty array values in settings are invalid
* **🐞Fix** (Elasticsearch) arbitrary x-cluster search referencing local cluster
* **🐞Fix** (Elasticsearch) ArrayOutOfBoundException on snapshot operations
* **🧐Enhancement** (PRO/Enterprise) History cleaning can now be disabled ("clearSessionOnEvents") ### What's new in 1.17.7
* **🚀New** (unspecified) Support for Elasticsearch 7.0.0 (Kibana is coming soon)
* **🧐Enhancement** (Elasticsearch) rewritten LDAP connector
* **🧐Enhancement** (Elasticsearch) new core written in Scala is now GA
* **🐞Fix** (Enterprise) devtools requests now honor the currently selected tenancy
* **🐞Fix** (Enterprise/PRO) Fix "connectorsService" error in installation ### What's new in 1.17.5
* **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.1
* **🧐Enhancement** (Enterprise >= Kibana 6.6.0) Multiple SAML identity provider
* **🐞Fix** (Enterprise/PRO) Don't pass auth headers back to the browser
* **🐞Fix** (Enterprise/PRO) Missing null check caused error in reporting (CSV)
* **🐞Fix** (Enterprise) Don't reject requests if SAML groups are not configured
* **🐞Fix** (unspecified) filter/fields rules not working in msearch (in 6.7.x)
* **🧐Enhancement** (unspecified) Print whole LDAP search query in debug log ### What's new in 1.17.4
* **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.0
* **🧐Enhancement** (PRO/Enterprise) JWT query param is the preferred credentials provider
* **🧐Enhancement** (PRO/Enterprise) admin users can use indices management
* **🧐Enhancement** (PRO/Enterprise) ro users can dismiss telemetry form
* **🐞Fix** (unspecified) Audit logging in 5.1.x now works again
* **🐞Fix** (unspecified) unpredictable behaviour of "filter" and "fields" when using external auth
* **🐞Fix** (unspecified) LDAP ConcurrentModificationException
* **🐞Fix** (unspecified) Audit logging in 5.1.x now works again
* **🐞Fix** (PRO/Enterprise) JWT deep-link works again ### What's new in 1.17.3 1.17.2 went unreleased, all changes have been merged in 1.17.3 directly
* **🐞Fix** (Enterprise) Tenancy selector showing if user belonged to one group
* **🐞Fix** (PRO/Enterprise) RW buttons not hiding for RO users in React Kibana apps
* **🐞Fix** (Enterprise) Tenancy templating now works much more reliably
* **🐞Fix** (Enterprise) Missing tenancy selector icon after switching tenancy
* **🐞Fix** (PRO/Enterprise) barring static files requests caused sudden logout
* **🐞Fix** (unspecified) Numerous fixes to better support Kibana 6.6.x
* **🐞Fix** (unspecified) Critical fixes in new Scala core
* **🐞Fix** (unspecified) Exception in reindex requests caused tenancy templating to fail
* **🧐Enhancement** (unspecified) Bypass cross-cluster search logic if single cluster ### What's new in 1.17.1
* **🐞Fix** (PRO/Enterprise) SAML now works well in 6.6.x
* **🐞Fix** (PRO/Enterprise) "undefined" authentication error before login
* **🐞Fix** (Enterprise) Default space creation failures for new tenants
* **🐞Fix** (Enterprise) Icons/titles CSS misalignment in sidebar (Firefox)
* **🧐Enhancement** (Enterprise) UX: Larger tenancy selector
* **🐞Fix** (Enterprise) Privilege escalation when changing tenancies under monitoring
* **🐞Fix** (Elasticsearch) compatibility fixes to support new Kibana features
* **🧐Enhancement** (Elasticsearch) New core and LDAP connector written in Scala is finished, now under QA. ### What's new in 1.17.0
* **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.6.0, 6.6.1
* **🚀New** (unspecified) Internode SSL (ES 6.3.x onwards)
* **🧐Enhancement** (PRO/Enterprise) UI appearence
* **🧐Enhancement** (unspecified) Made HTTP Connection configurable (PR #410)
* **🐞Fix** (unspecified) slow boot due to SecureRandom waiting for sufficient entropy
* **🐞Fix** (unspecified) Enable kibana\_access:ro to create short urls in es6.3+ (PR #408) ### What's new in 1.16.34
* **🧐Enhancement** (unspecified) X-Forwarded-For header in printed es logs ("XFF")
* **🧐Enhancement** (unspecified) kibana\_index: ".kibana\_@{user}" when user is "John Doe" becomes .kibana\_john\_doe
* **🐞Fix** (Enteprise) parse SAML groups from assertion as array of strings
* **🐞Fix** (Enteprise) SAMLRequest in location header was URLEncoded twice, broke on some IdP
* **🐞Fix** (PRO/Enteprise) "cookiePass" works again, no more need for sticky cookies in load balancers!
* **🐞Fix** (PRO/Enteprise) fix redirect loop with JWT deep linking when JWT token expires
* **🧐Enhancement** (PRO/Enteprise) fix audit demo page CSS
* **🧐Enhancement** (Enteprise) SAML more configuration parameters available
* **🚀New** (PRO/Enteprise) set ROR to debug mode (readonlyrest\_kbn.logLevel: "debug") ### What's new in 1.16.33
* **🐞Fix** (PRO/Enteprise) compatibility problems with older Kibana versions
* **🐞Fix** (PRO/Enteprise) compatibility problems with OSS Kibana version ### What's new in 1.16.32
* **🚀New** (unspecified) "kibanaIndexTemplate": default dashboards and spaces for new tenants
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.4
* **🧐Enhancement** (unspecified) Upgraded LDAP library
* **🧐Enhancement** (Enterprise) Now tenants save their CSV exports in their own reporting index
* **🐞Fix** (PRO/Enteprise) Support passwords that start and/or end with spaces
* **🐞Fix** (PRO/Enterprise) Now reporting works again ### What's new in 1.16.31
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.2, 6.5.3
* **\<unknown>** (unspecified) : Laid out the foundation for LDAP HA support ### What's new in 1.16.29
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.3
* **🚀New** (PRO/Enterprise) configurable server side session duration
* **🚀New** (unspecified) \[LDAP] High Availability: Round Robin or Failover ### What's new in 1.16.28
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.2
* **🐞Fix** (Enterprise) Multi tenancy: sometimes changing tenancy would not change kibana index
* **🐞Fix** (Enterprise/PRO) Avoid echoing Base64 encoded credentials in login form error message
* **🧐Enhancement** (Enterprise/PRO) Remove latest search/visualization/dashboard history on logout
* **🧐Enhancement** (Enterprise/PRO) Clear transient authentication cookies on login error to avoid authentication deadlocks
* **🐞Fix** (unspecified) : External JWT verification may throw ArrayOutOfBoundException
* **\<unknown>** (unspecified) : Laid out the foundation for internode SSL transport (port 9300) ### What's new in 1.16.27
* **🚀New** (unspecified) \[JWT] external validator: it's now possible to avoid storing the private key in settings
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.1
* **🧐Enhancement** (unspecified) Rewritten big part of ES plugin [documentation](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md)
* **🧐Enhancement** (unspecified) SAML Single log out flow
* **🐞Fix** (Enterprise/PRO) [cookiePass](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#common-cookie-encryption-secret) works again, but only for Kibana 5.x. Newer Kibana needs sticky sessions in LB.
* **🧐Enhancement** (Enterprise/PRO) much faster logout ### What's new in 1.16.26
* **🐞Fix** (PRO/Enterprise) bugs during plugin packaging and installation process ### What's new in 1.16.25
* **🚀New** (unspecified) Users rule: easily restrict external authentication to a list of users
* **🧐Enhancement** (unspecified) Support for ES 5.6.11
* **🐞Fix** (Enterprise/PRO) Error 404 when logging in with older versions of Kibana ### What's new in 1.16.24
* **🚀New** (Enterprise) SAML Authentication
* **🚀New** (unspecified) Support for Elasticsearch and Kibana 6.4.0
* **🚀New** (unspecified) Headers rule now split in headers\_or and headers\_and
* **🧐Enhancement** (unspecified) Headers rule now allows wildcards
* **🚀New** (Enterprise) Multi-tenancy now works also with JSON groups provider
* **🐞Fix** (unspecified) Multi-tenancy (Enterprise) incoherent initial kibana\_index and current group ### What's new in 1.16.23
* **🧐Enhancement** (unspecified) Support for Elastic Stack 6.3.1 and 5.6.10
* **🚀New** (Enterprise) Custom CSS injection for Kibana
* **🚀New** (Enterprise) Custom Javascript injection for Kibana
* **🚀New** (PRO/Enterprise) access paths without need to login (i.e. /api/status)
* **🐞Fix** (PRO/Enterprise) Navigating to X-Pack APM caused hidden Kibana apps to reappear ### What's new in 1.16.22
* **🚀New** (unspecified) map LDAP groups to local groups (a.k.a. role mapping)
* **🐞Fix** (Elasticsearch) wildcard aliases resolution not working in "indices" rule.
* **🧐Enhancement** (unspecified) it is now possible now to use JDK 9 and 10
* **🐞Fix** (PRO/Enterprise) wait forever for login request (i.e. slow LDAP servers)
* **🐞Fix** (PRO/Enterprise) add spinner and block UI if login request is being sent
* **🐞Fix** (PRO/Enterprise) if user is logged out because of LDAP cache expiring + slow authentication, redirect to login.
* **🐞Fix** (PRO/Enterprise) let RO users delete/edit search filters ### What's new in 1.16.21
* **🚀New** (unspecified) Introducing support for Elasticsearch and Kibana v6.3.0
* **🐞Fix** (Enterprise) multi tenancy - switching tenancy does not always switch kibana index ### What's new in 1.16.20 ## ReadonlyREST PRO/Enterprise for Kibana
* **🧐Enhancement** (unspecified) : when login, forward "elasticsearch.requestHeadersWhitelist" headers. (useful for "headers" rule and "proxy\_auth" to work well.) ## ReadonlyREST for Elasticsearch
* **🚀New** (unspecified) : DLS (with dynamic variables suppoort) Thanks [DataSweet](http://www.datasweet.fr/)!
* **🚀New** (unspecified) : Field level security
* **🚀New** (unspecified) : Snapshot, Repositories, Headers
* **🧐Enhancement** (unspecified) : custom audit serializers: the request content is available
* **🐞Fix** (unspecified) readonlyrest.yml path discovery
* **🐞Fix** (unspecified) LDAP available groups discovery (tenancy switcher) corner cases
* **🐞Fix** (unspecified) : auth\_key\_sha1, auth\_key\_sha256 hashes in settings should be case insensitive
* **🐞Fix** (unspecified) : LDAP authentication didn't work with local group


# README

* [📖 Docs for Elasticsearch plugin](/elasticsearch)
* [📖 Docs for Kibana plugin](/kibana)

The documentation of an open source product should also be open source! Found a problem? Edit the file directly from GitHub!

## Getting started

* [🚀 Kibana Multi-User with ROR PRO](/examples/multiuser_guide)
* [🚀 Kibana Multi-Tenancy with ROR Enterprise](/examples/multitenancy_guide)
* [🚀 ECK with ROR](/eck)

[⬅️ Elasticsearch plugin project](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin) (Github)


# For Elasticsearch

## Overview

ReadonlyREST is a light-weight Elasticsearch plugin that adds encryption, authentication, authorization and access control capabilities to Elasticsearch embedded REST API. The core of this plugin is an ACL engine that checks each incoming request through a sequence of **rules** a bit like a firewall. There are a dozen rules that can be grouped in sequences of blocks and form a powerful representation of a logic chain.

The Elasticsearch plugin known as `ReadonlyREST Free` is released under the GPLv3 license, or alternatively a commercial license (see [ReadonlyREST Embedded](https://readonlyrest.com/embedded)) and lays the technological foundations for the companion Kibana plugin which is released in two versions: [ReadonlyREST PRO](https://readonlyrest.com/pro) and [ReadonlyREST Enterprise](https://readonlyrest.com/enterprise).

Unlike the Elasticsearch plugin, the Kibana plugins are commercial only. But rely on the Elasticsearch plugin in order to work.

For a description of the Kibana plugins, skip to the [dedicated documentation page](/kibana) instead.

### ReadonlyREST Free plugin for Elasticsearch

In this document, we are going to describe how to operate the Elasticsearch plugin in all its features. Once installed, this plugin will greatly extend the Elasticsearch HTTP API (port 9200), adding numerous extra capabilities:

* **Encryption**: transform the Elasticsearch API from HTTP to HTTPS
* **Authentication**: require credentials
* **Authorization**: declare groups of users, permissions and partial access to indices.
* **Access control**: complex logic can be modeled using an ACL (access control list) written in YAML.
* **Audit events**: a trace of the access requests can be logged to a file or index (or both).

#### Flow of a Search Request

The following diagram models an instance of Elasticsearch with the ReadonlyREST plugin installed and configured with SSL encryption and an ACL with at least one "allow" type ACL block.

![readonlyrest request processing diagram](/files/q9EtWL5AV3TdcwCf2dHv)

1. The User Agent (i.e. cURL, Kibana) sends a search request to Elasticsearch using port 9200 and the HTTPS URL schema.
2. The HTTPS filter in the ReadonlyREST plugin unwraps the SSL layer and hands over the request to the Elasticsearch HTTP stack
3. The HTTP stack in Elasticsearch parses the HTTP request
4. The HTTP handler in Elasticsearch extracts the indices, action, request type, and creates a `SearchRequest` (internal Elasticsearch format).
5. The SearchRequest goes through the ACL (access control list), external systems like LDAP can be asynchronously queried, and an exit result is eventually produced.
6. The exit result is used by the audit event serializer, to write a record to index and/or Elasticsearch log file
7. If no ACL block was matched, or if a `type: forbid` block was matched, ReadonlyREST does not forward the search request to the search engine and creates an "unauthorized" HTTP response.
8. In case the ACL matches a `type: allow` block, the request is forwarded to the search engine
9. The Elasticsearch code creates a search response containing the results of the query
10. The search response is converted to an HTTP response by the Elasticsearch code
11. The HTTP response flows back to ReadonlyREST's HTTPS filter and to the User agent

## Installation and Operations

### Running with Docker

The simplest method to run Elasticsearch with the ReadonlyREST plugin is to use one of our docker images which you can find on [Docker Hub](https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest):

```bash
docker run -u root -p 9200:9200 -e "I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes" -e "KIBANA_USER_PASS=kibana" -e "ADMIN_USER_PASS=admin" -e "discovery.type=single-node" beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
```

OR with [Docker Compose](https://docs.docker.com/compose/):

```yaml
# docker-compose.yml file content
services:

  es-ror:
    image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports:
      - "9200:9200"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
      - KIBANA_USER_PASS=kibana
      - ADMIN_USER_PASS=admin
      - discovery.type=single-node
```

(To run the docker-compose.yml call `docker compose up`)

Any of these methods, runs Elasticsearch container with ReadonlyREST with [init settings](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/develop/docker-image/init-readonlyrest.yml).

When the service is started you can test it using curl or Postman:

```
curl -v -u admin:admin https://localhost:9200
```

#### Customizing ROR settings

You can create locally customized `readonlyrest.yml` file and mount it as a [docker volume](https://docs.docker.com/storage/volumes/). Assuming that your ROR settings file is located in `/tmp/my-readonlyrest.yml` you can use it like that:

```bash
docker run -u root -p 9200:9200 -e "discovery.type=single-node" -e "I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes" -v /tmp/my-readonlyrest.yml:/etc/share/elasticsearch/config/readonlyrest.yml beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
```

OR

```yaml
# docker-compose.yml file content
services:

  es-ror:
    image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports:
      - "9200:9200"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
      - KIBANA_USER_PASS=kibana
      - ADMIN_USER_PASS=admin
      - discovery.type=single-node
    volumes:
      - ./my-readonlyrest.yml:/etc/share/elasticsearch/config/readonlyrest.yml # we assume that the `my-readonlyrest.yml` file is in the same folder as `docker-compose.yml` file is
```

####

### Installing the plugin

To install the ReadonlyREST plugin for Elasticsearch:

#### 1. Obtain the build

From the [official download page](https://readonlyrest.com/download). Select your Elasticsearch version and send yourself a link to the compatible ReadonlyREST zip file.

#### 2. Install the build

```bash
bin/elasticsearch-plugin install file:///tmp/readonlyrest-X.Y.Z_esW.Q.U.zip
```

Notice how we need to type in the format `file://` + absolute path (yes, with three slashes).

```
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
```

When prompted about additional permissions, answer **y**.

#### 3. Patch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra post-installation step**. Depending on the [Elasticsearch version](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/master/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches), this command might tweak the main Elasticsearch installation files and/or copy some jars to `plugins/readonlyrest` directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar patch --I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
```

**⚠️IMPORTANT**: The command above runs in silent mode with the `--I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes` flag. Without this flag, the patcher runs in interactive mode and will prompt you to confirm that you understand and accept the implications of ES patching.

**⚠️IMPORTANT**: for Elasticsearch 8.3.x or newer, the patching operation requires `root` user privileges.

You can verify if Elasticsearch was correctly patched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

Please note that the tool assumes that you run it from the root of your ES installation directory or the default installation directory is `/usr/share/elasticsearch`. But if you want or need, you can instruct it where your Elasticsearch is installed by executing one of the tool's command with the `--es-path` parameter:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar patch --es-path /my/custom/path/to/es/folder
```

or

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify --es-path /my/custom/path/to/es/folder
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 4. Create settings file

Create and edit the `readonlyrest.yml` settings file in the **same directory where `elasticsearch.yml` is found**:

```bash
vim $ES_PATH_CONF/conf/readonlyrest.yml
```

Now write some basic settings, just to get started. In this example, we are going to tell ReadonlyREST to require HTTP Basic Authentication for all the HTTP requests, and return `401 Unauthorized` otherwise.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Require HTTP Basic Auth"
      type: allow
      auth_key: user:password
```

#### 5. Start Elasticsearch

```bash
bin/elasticsearch
```

or:

```bash
service start elasticsearch
```

Depending on your environment.

Now you should be able to see the logs and ReadonlyREST-related lines like the one below:

```
[2018-09-18T13:56:25,275][INFO ][o.e.p.PluginsService     ] [c3RKGFJ] loaded plugin [readonlyrest]
```

#### 7. Test everything is working

The following command should succeed, and the response should show a status code 200.

```bash
curl -vvv -u user:password "http://localhost:9200/_cat/indices"
```

The following command should not succeed, and the response should show a status code 401

```bash
curl -vvv "http://localhost:9200/_cat/indices"
```

### Upgrading the plugin

To upgrade ReadonlyREST for Elasticsearch:

#### 1. Stop Elasticsearch.

Either kill the process manually, or use:

```bash
service stop elasticsearch
```

depending on your environment.

#### 2. Unpatch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra pre-uninstallation step**. This will remove all previously copied jars from ROR's installation directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar unpatch
```

You can verify if Elasticsearch was correctly unpatched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 3. Uninstall ReadonlyREST

```bash
bin/elasticsearch-plugin remove readonlyrest
```

#### 4. Install the new version of ReadonlyREST into Elasticsearch.

```bash
bin/elasticsearch-plugin install file://<download_dir>/readonlyrest-<ROR_VERSION>_es<ES_VERSION>.zip
```

e.g.

```bash
bin/elasticsearch-plugin install file:///tmp/readonlyrest-1.56.0_es8.12.2.zip
```

#### 5. Patch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra post-installation step**. Depending on the [Elasticsearch version](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/master/ror-tools-core/src/main/scala/tech/beshu/ror/tools/core/patches), this command might tweak the main Elasticsearch installation files and/or copy some jars to the `plugins/readonlyrest` directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar patch --I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
```

**⚠️IMPORTANT**: The command above runs in silent mode with the `--I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes` flag. Without this flag, the patcher runs in interactive mode and will prompt you to confirm that you understand and accept the implications of ES patching.

**⚠️IMPORTANT**: For Elasticsearch 8.3.x or newer, the patching operation requires `root` user privileges.

You can verify if Elasticsearch was correctly patched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 6. Restart Elasticsearch

```bash
bin/elasticsearch
```

or:

```bash
service start elasticsearch
```

Depending on your environment.

Now you should be able to see the logs and ReadonlyREST-related lines like the one below:

```
[2024-03-14T20:21:49,589][INFO ][t.b.r.b.RorInstance      ] [ROR_SINGLE_1] ReadonlyREST was loaded ...
```

### Removing the plugin

#### 1. Stop Elasticsearch.

Either kill the process manually, or use:

```bash
service stop elasticsearch
```

depending on your environment.

#### 2. Unpatch Elasticsearch

If you are using Elasticsearch 6.7.0 or newer, you need **an extra pre-uninstallation step**. This will remove all previously copied jars from ROR's installation directory.

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar unpatch
```

You can verify if Elasticsearch was correctly unpatched using the command `verify`:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar verify
```

**NB:** In case of any problems with the `ror-tools`, please call:

```bash
jdk/bin/java -jar plugins/readonlyrest/ror-tools.jar --help
```

#### 3. Uninstall ReadonlyREST from Elasticsearch:

```bash
bin/elasticsearch-plugin remove readonlyrest
```

#### 4. Start Elasticsearch.

```bash
bin/elasticsearch
```

or:

```bash
service start elasticsearch
```

Depending on your environment.

### Upgrading Elasticsearch

The ReadonlyREST plugin version must always match the currently installed Elasticsearch version. As a result, if you want to upgrade Elasticsearch:

1. Before upgrading Elasticsearch, unpatch and uninstall the ReadonlyREST plugin according to the instructions:
   * [Unpatch Elasticsearch and uninstall the plugin](#removing-the-plugin)
2. Upgrade Elasticsearch.
3. After upgrading Elasticsearch, install the matching version of the ReadonlyREST plugin and patch according to the instructions:
   * [Install matching plugin version and patch Elasticsearch](#installing-the-plugin)

{% hint style="warning" %}
Upgrading Elasticsearch without following the instructions above may cause corruption of the ES installation and inability to patch the upgraded version.
{% endhint %}

### Deploying ReadonlyREST in a stable production cluster

Unless some advanced features are being used (see below), this Elasticsearch plugin operates like a lightweight, stateless filter glued in front of Elasticsearch HTTP API. Therefore it's sufficient to install the plugin **only in the nodes that expose the HTTP interface** (port 9200).

Installing ReadonlyREST in a dedicated node has numerous advantages:

* No need to restart all nodes, only the one you have installed the plugin into.
* No need to restart all nodes to update the security settings
* No need to restart all nodes when a security update is out
* Less complexity on the actual cluster nodes.

For example, if we want to move to HTTPS all the traffic coming from Logstash into a 9-node Elasticsearch cluster which has been running stable in production for a while, it's not necessary to install the ReadonlyREST plugin in all the nodes.

Creating a dedicated, lightweight ES node where to install ReadonlyREST:

1. (Optional) [disable the HTTP interface](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-http.html#_disable_http) from all the existing nodes
2. Create a new, lightweight, dedicated node without shards, nor master eligibility.
3. Configure ReadonlyREST with SSL [encryption](#encryption) in the new node
4. Configure Logstash to connect to the new node directly in HTTPS.

#### An exception

**⚠️IMPORTANT** By default when the `fields` [rule](#fields) is used, it's required to install the ReadonlyREST plugin in all the data nodes.

## Elasticsearch Configuration

ReadonlyREST uses two distinct kinds of settings:

* **ACL settings** — the access control rules defined in `readonlyrest.yml` (or stored in an Elasticsearch index). Every node in the cluster must share the same ACL settings. Subscribers of the [PRO](https://readonlyrest.com/pro) or [Enterprise](https://readonlyrest.com/enterprise) Kibana plugin can also reload ACL settings at runtime through the GUI (see [Cluster-wide Settings VS readonlyrest.yml](/kibana#cluster-wide-settings-vs-readonlyrestyml)) or via the [ReadonlyREST API](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/readonlyrest-api.md).
* **Node settings** — settings placed in `elasticsearch.yml` that are specific to each Elasticsearch node. They control how ROR behaves during startup, where to look for ACL settings, and how SSL is configured. These settings are read once at node startup and are not shared between nodes.

The sections below describe the node settings that go into `elasticsearch.yml`.

> **Note:** All ROR node settings described below can also be provided as JVM system properties (e.g. `-Dreadonlyrest.settings.index_name=.my-ror-index`), using the same dot-separated key that appears in the YAML.

### Encryption

SSL/TLS encryption protects data in transit between clients and Elasticsearch. ReadonlyREST supports two independent encryption layers:

1. **External REST API** — client ↔ Elasticsearch traffic (port 9200)
2. **Internode transport** — node ↔ node traffic (port 9300)

#### Choosing between ReadonlyREST SSL and XPack Security SSL

There are two ways to configure SSL in an Elasticsearch cluster running ReadonlyREST:

* **ReadonlyREST SSL** — SSL provided by the ReadonlyREST plugin itself (described in the subsections below).
* **XPack Security SSL** — SSL provided by Elasticsearch's built-in `xpack.security` module.

The choice depends on whether `xpack.security.enabled` is set to `true` or `false` in `elasticsearch.yml`:

| `xpack.security.enabled` | SSL to use         |
| ------------------------ | ------------------ |
| `false`                  | ReadonlyREST SSL   |
| `true`                   | XPack Security SSL |

**Why does this matter?** During its patching step, ReadonlyREST deactivates XPack Security's authentication and authorization features — these are replaced by ROR's ACL engine. However, **XPack SSL is not deactivated**. This means that when `xpack.security.enabled: true`, XPack SSL is still fully active and must be configured through Elasticsearch's standard mechanism, not through ROR.

> **Recommendation:** Because `xpack.security` enables features used by Elasticsearch and Kibana (e.g. API keys, token service, certain Kibana integrations), it should not be disabled without a clear reason. If there is no specific requirement to disable it, prefer leaving `xpack.security.enabled: true` and use XPack Security SSL.

#### XPack Security SSL (when `xpack.security.enabled: true`)

When `xpack.security.enabled` is `true`, configure SSL by following the official Elasticsearch documentation:

* [Set up basic security (internode TLS)](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html)
* [Set up basic security plus HTTPS (REST API TLS)](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup-https.html)

ROR's ACL will handle authentication and authorization, while XPack manages the SSL layer transparently.

#### ReadonlyREST SSL (when `xpack.security.enabled: false`)

The following subsections describe how to configure SSL using ReadonlyREST's own SSL implementation. This applies only when `xpack.security.enabled` is set to `false` in `elasticsearch.yml`.

> **Configuration placement:** All SSL settings — including `http.type`, `transport.type`, `readonlyrest.ssl.*`, and `readonlyrest.ssl_internode.*` — must be placed in `elasticsearch.yml`.

**External REST API**

Encrypts traffic between clients and Elasticsearch on port 9200. Add the following to your `elasticsearch.yml`.

**Keystore option (JKS or PKCS#12):**

```yaml
http.type: ssl_netty4

readonlyrest.ssl.keystore_file: "keystore.jks"        # also accepts .p12 (PKCS#12)
readonlyrest.ssl.keystore_pass: "<keystore-password>"
readonlyrest.ssl.key_pass: "<key-password>"
readonlyrest.ssl.key_alias: "my-server-cert"          # optional; if omitted, ROR uses the first alias found in the keystore

# Optional: restrict accepted TLS versions and cipher suites
readonlyrest.ssl.allowed_protocols: [TLSv1.2, TLSv1.3]
readonlyrest.ssl.allowed_ciphers: [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]

# Optional: mutual TLS — require clients to present a certificate
readonlyrest.ssl.client_authentication: false          # default

# Optional: custom trust anchor for client certificates (defaults to JVM truststore)
readonlyrest.ssl.truststore_file: "truststore.jks"
readonlyrest.ssl.truststore_pass: "<truststore-password>"
```

**PEM option (preferred):**

```yaml
http.type: ssl_netty4

readonlyrest.ssl.server_certificate_key_file: "private_key.pem"
readonlyrest.ssl.server_certificate_file: "fullchain.pem"

# Optional: custom trust anchor for client certificates
readonlyrest.ssl.client_trusted_certificate_file: "trusted_certs.pem"
```

All certificate files must be placed in the same directory as `elasticsearch.yml`.

**Verify SSL is working**

After restarting Elasticsearch, confirm SSL is active by querying the cluster health endpoint:

```bash
# Full validation with a custom CA (self-signed or internal CA)
curl --cacert /path/to/ca-chain.pem \
     https://your-domain:9200/_cluster/health \
     -u admin:your_password
```

If your certificate was issued by a public CA (Let's Encrypt or any commercial CA), the system trust store is sufficient and `--cacert` can be omitted:

```bash
curl https://your-domain:9200/_cluster/health -u admin:your_password
```

Expected result: HTTP 200 with a JSON body containing `"status":"green"` or `"status":"yellow"`.

**Internode communication — transport module**

Encrypts traffic between nodes in the Elasticsearch cluster on port 9300. This configuration must be added to all nodes in the cluster.

**`elasticsearch.yml`:**

```yaml
transport.type: ror_ssl_internode

readonlyrest.ssl_internode.keystore_file: "keystore.jks"   # also accepts .p12 (PKCS#12)
readonlyrest.ssl_internode.keystore_pass: "<keystore-password>"
readonlyrest.ssl_internode.key_pass: "<key-password>"
readonlyrest.ssl_internode.key_alias: "my-node-cert"        # optional; if omitted, ROR uses the first alias found in the keystore
```

The keystore file must be placed in the same directory as `elasticsearch.yml`.

**Internode communication with XPack nodes**

It is possible to set up internode SSL between ROR nodes (with `xpack.security.enabled: false`) and XPack nodes. This requires ES 6.7.0 or newer.

Generate a certificate for the ROR node following the [Elasticsearch certificate generation guide](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html#generate-certificates).

The generated `elastic-certificates.p12` can then be used in the ROR node:

```yaml
readonlyrest.ssl_internode.enable: true
readonlyrest.ssl_internode.keystore_file: "elastic-certificates.p12"
readonlyrest.ssl_internode.keystore_pass: "<keystore-password>"
readonlyrest.ssl_internode.key_pass: "<key-password>"
readonlyrest.ssl_internode.truststore_file: "elastic-certificates.p12"
readonlyrest.ssl_internode.truststore_pass: "<truststore-password>"
readonlyrest.ssl_internode.client_authentication: true    # default: false
readonlyrest.ssl_internode.certificate_verification: true
readonlyrest.ssl_internode.hostname_verification: false   # default: false
```

**Certificate verification**

By default, certificate verification is disabled for internode SSL. This means any certificate is accepted without validation — useful in local or test environments. In production, it is advised to enable this option.

```yaml
readonlyrest.ssl_internode.certificate_verification: true
```

This option applies to internode SSL only.

**Hostname verification**

By default, hostname verification is disabled. This means the hostname or IP address is not checked against the names in the certificate.

> **Production:** Enable hostname verification alongside certificate verification for full transport security.

```yaml
readonlyrest.ssl_internode.hostname_verification: true
```

**Client authentication**

By default, the server does not request a client certificate. When enabled, Elasticsearch verifies the client's identity via mutual TLS.

For external REST API:

```yaml
readonlyrest.ssl.client_authentication: true
```

For internode communication:

```yaml
readonlyrest.ssl_internode.client_authentication: true
```

**Allowed protocols and ciphers**

Optionally, restrict the accepted TLS versions and cipher suites. Connections from clients not supporting the listed values will be dropped.

For external REST API:

```yaml
readonlyrest.ssl.allowed_protocols: [TLSv1.2, TLSv1.3]
readonlyrest.ssl.allowed_ciphers: [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
```

For internode communication:

```yaml
readonlyrest.ssl_internode.allowed_protocols: [TLSv1.2, TLSv1.3]
readonlyrest.ssl_internode.allowed_ciphers: [TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256]
```

ReadonlyREST logs available ciphers and protocols supported by the current JVM at startup:

```
[2018-01-03T10:09:38,683][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Available ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA
[2018-01-03T10:09:38,684][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Restricting to ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
[2018-01-03T10:09:38,684][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Available SSL protocols: TLSv1,TLSv1.1,TLSv1.2
[2018-01-03T10:09:38,685][INFO ][t.b.r.e.SSLTransportNetty4] ROR SSL: Restricting to SSL protocols: TLSv1.2
```

**Custom truststore**

Replaces the default JVM truststore. The truststore file must be placed in the same directory as `elasticsearch.yml`.

For external REST API:

```yaml
readonlyrest.ssl.truststore_file: "truststore.jks"
readonlyrest.ssl.truststore_pass: "<truststore-password>"
```

For internode communication:

```yaml
readonlyrest.ssl_internode.truststore_file: "truststore.jks"
readonlyrest.ssl_internode.truststore_pass: "<truststore-password>"
```

When not specified, ReadonlyREST uses the default JVM truststore.

**Using Let's Encrypt**

Shows how to use Let's Encrypt certificates with ReadonlyREST. The same approach applies to certificates from other providers.

You can use PEM files directly without creating a keystore — see the PEM option in the [External REST API](#external-rest-api) section above. In that case, only step 1 below is needed.

**1. Obtain certificates**

```bash
certbot certonly --standalone -d DOMAIN.TLD -d DOMAIN_2.TLD --email EMAIL@EMAIL.TLD
```

Change to the certificate directory (typically `/etc/letsencrypt/live/DOMAIN.TLD`). The files you need are `fullchain.pem` and `privkey.pem`.

**2. Create a PKCS#12 keystore**

```bash
openssl pkcs12 -export -in fullchain.pem -inkey privkey.pem -out keystore.p12 -name ror
```

You will be prompted to set a password for the `.p12` file. Remember it — you will need it in the next step.

**3. Convert to JKS (optional)**

Skip this step if you use `keystore.p12` directly — ReadonlyREST supports both PKCS#12 and JKS formats.

```bash
keytool -importkeystore \
  -srckeystore keystore.p12 \
  -srcstoretype PKCS12 \
  -srcstorepass STORE_PASS \       # password set in step 2
  -destkeystore keystore.jks \
  -deststorepass PASSWORD_STORE \  # protects keystore.jks → readonlyrest.ssl.keystore_pass
  -destkeypass PASSWORD_KEYPASS \  # protects the private key entry → readonlyrest.ssl.key_pass
  -alias ror
```

> `PASSWORD_STORE` and `PASSWORD_KEYPASS` can be the same value — most deployments use a single password for simplicity. `STORE_PASS` must match the password set in step 2.

The resulting keystore maps to your ROR configuration as follows:

```yaml
readonlyrest.ssl.keystore_file: "keystore.jks"
readonlyrest.ssl.keystore_pass: "PASSWORD_STORE"   # -deststorepass from step 3
readonlyrest.ssl.key_pass: "PASSWORD_KEYPASS"      # -destkeypass from step 3
```

If you get `java.io.IOException: failed to decrypt safe contents entry: javax.crypto.BadPaddingException: Given final block not properly padded`, the `STORE_PASS` value does not match the password set in step 2.

(Credits for the original JKS tutorial to [Maximilian Boehm](https://maximilian-boehm.com))

**FIPS mode**

If you need FIPS 140-2 compliant SSL, ReadonlyREST supports it via the BouncyCastle library and BCFKS keystore format. See [FIPS mode](/elasticsearch/fips) for setup instructions.

### ACL settings source configuration

By default, ROR looks for ACL settings in a `readonlyrest.yml` file located next to `elasticsearch.yml`, and also watches a dedicated Elasticsearch index for settings updates. The following options let you customize this behavior.

#### Settings file and index

```yaml
readonlyrest:
  settings:
    index_name: .my-ror-index              # default: .readonlyrest
    file_path: /custom/readonlyrest.yml    # default: <ES config dir>/readonlyrest.yml
    max_size: 10 MB                        # default: 3 MB — maximum allowed size of ACL settings loaded from the index
```

#### Index loading strategy

When loading from index (the default), ROR polls the index periodically and retries on failure during startup. Both the poll interval and the startup retry behavior can be tuned:

```yaml
readonlyrest:
  load_from_index:
    poll_interval: 5s                      # how often to check the index for ACL settings changes (default: 5s, set to 0s to disable polling)
    initial_loading_retry_strategy:
      initial_delay: 5s                    # delay before the first attempt to load from the index at startup (default: 5s)
      attempts_interval: 5s               # interval between retry attempts if the index is not yet available (default: 5s)
      attempts_count: 5                   # maximum number of retry attempts before falling back to file (default: 5)
```

Setting `poll_interval` to `0s` disables periodic polling — ROR will load ACL settings from the index once at startup and will not check for changes until the node is restarted.

#### Force loading from file

When set to `true`, ROR will only load ACL settings from the file and will never attempt to read from the Elasticsearch index. This is typically used during recovery when in-index settings have become corrupted — see [Malformed in-index settings](/kibana#malformed-in-index-settings).

```yaml
readonlyrest:
  force_load_from_file: true
```

Default: `false`.

### Request handling during ES startup

Each incoming request to the Elasticsearch node passes to the installed plugin. During Elasticsearch node startup, the plugin rejects incoming requests until it is fully initialized. The plugin rejects such requests with `403` forbidden responses by default.

To change this behavior, add the following to `elasticsearch.yml`:

```yaml
readonlyrest:
  not_started_response_code: 503
  failed_to_start_response_code: 503
```

`not_started_response_code` — HTTP code returned while the plugin has not yet finished starting. Allowed values: `403` (default), `503`.

`failed_to_start_response_code` — HTTP code returned when the plugin failed to start (e.g. due to a malformed ACL). Allowed values: `403` (default), `503`.

## ReadonlyREST ACL

### ACL basics

The core of this plugin is an ACL (access control list). A logic structure very similar to the one found in firewalls. The ACL is part of the plugin configuration, and it's written in YAML.

* The ACL is composed of an *ordered* sequence of named **blocks**
* Each block contains some **rules**, and a policy (forbid or allow)
* HTTP requests run through the blocks, starting from the first,
* The *first* block that satisfies *all the rules* decides if to forbid or allow the request (according to its policy).
* If none of the blocks is matched, the request is rejected

**⚠️IMPORTANT**: The ACL blocks are **evaluated sequentially**, therefore **the ordering of the ACL blocks is crucial**. The order of the rules inside an ACL block instead, is irrelevant.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Block 1 - only Logstash indices are accessible"
      type: allow # <-- default policy type is "allow", so this line could be omitted
      indices: ["logstash-*"] # <-- This is a rule

    - name: "Block 2 - Blocking everything from a network"
      type: forbid
      hosts: ["10.0.0.0/24"] # <-- this is a rule
```

*An Example of the Access Control List (ACL) made of 2 blocks.*

The YAML snippet above, like all of this plugin's settings should be saved inside the `readonlyrest.yml` file. Create this file **on the same path where `elasticsearch.yml` is found**.

**TIP**: If you are a subscriber of the [PRO](https://readonlyrest.com/pro) or [Enterprise](https://readonlyrest.com/enterprise) Kibana plugin, you can edit and refresh the settings through a GUI. For more on this, see the [documentation for the ReadonlyREST plugin for Kibana](/kibana).

### Blocks of rules

Every block **must** have at least the `name` field, and optionally a `type` field valued either "allow" or "forbid". If you omit the `type`, your block will be treated as `type: allow` by default.

Keep in mind that ReadonlyREST ACL is a white list, so by default all request are blocked, unless you specify a block of rules that allows all or some requests.

* `name` will appear in logs, so keep it short and distinctive.
* `type` can be either `allow` or `forbid`. Can be omitted, default is `allow`.

```yaml
    - name: "Block 1 - Allowing anything from localhost"
      type: allow
      # In real life now you should increase the specificity by adding rules here (otherwise this block will allow all requests!)
```

*Example: the simplest example of an allow block.*

#### Unauthorized response configuration

When the request does not match any of the ACL blocks or the request matches the block with the `forbid` policy, the plugin rejects such requests with the `403` response code and `forbidden` content. You can change the content of the response as follows:

```yaml
readonlyrest:
  
  global_settings:
    response_if_req_forbidden: Forbidden by ReadonlyREST ES plugin # custom response for all forbidden requests

  access_control_rules:

    - name: "Block 1"
      type: # extended format for `type` property
        policy: allow
      indices: ["logstash-*"]

    - name: "Block 2"
      type: # extended format for `type` property
        policy: forbid
        # response returned when a request matches 'Block 2' (setting on the block level takes precedence over the global setting)
        response_message: "You are unauthorized to access this resource"
      indices: ["templates-*"]
```

See also [response\_if\_req\_forbidden](#response_if_req_forbidden) section.

### Rules

ReadonlyREST access control rules can be divided into the following categories:

* Authentication & Authorization rules
* Elasticsearch level rules
* Kibana-related rules
* HTTP level rules
* Network level rules

Please refrain from using HTTP level rules to protect certain indices or limit what people can do to an index. The level of control at this level is really coarse, especially because Elasticsearch REST API does not always respect RESTful principles. This makes of HTTP a bad abstraction level to write ACLs in Elasticsearch all together.

The only **clean and exhaustive** way to implement access control is to reason about requests **AFTER ElasticSearch has parsed** them. Only then, the list of affected **indices** and the **action** will be known for sure. See **Elasticsearch level** rules.

#### Authentication & Authorization rules

This section contains description of rules that can be used to authenticate and/or authorize users. Most of the following rules use HTTP Basic Auth, so the credentials are passed with the `Authorization` header and they can be easily decoded when the request is intercepted by a malicious third party. Please note that this authentication method is secure only if SSL is enabled.

**`auth_key`**

`auth_key: sales:p455wd`

It's an authentication rule that accepts [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication). Configure this value *in clear text*. Clients will need to provide the header e.g. `Authorization: Basic c2FsZXM6cDQ1NXdk` where "c2FsZXM6cDQ1NXdk" is Base64 for "sales:p455wd".

**⚠️IMPORTANT**: this rule is handy just for tests, replace it with another rule that hashes credentials, like: `auth_key_sha512`, or `auth_key_unix`.

[Impersonation](/kibana/impersonation) is supported by this rule without an extra configuration.

**`auth_key_sha512`**

`auth_key_sha512: 280ac6f...94bf9`

The authentication rule that accepts [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication). The value is a string like `username:password` *hashed in* [*SHA512*](https://md5calc.com/hash/sha512). Clients will need to provide the usual Authorization header.

There are also available other rules with less secure SHA algorithms `auth_key_sha256` and `auth_key_sha1`.

The rules support also alternative syntax, where only password is hashed, eg:

`auth_key_sha512: "admin:280ac6f...94bf9"`

In the example below `admin` is the username and `280ac6f...94bf9` is the hashed secret.

[Impersonation](/kibana/impersonation) is supported by these rules by default.

**`auth_key_pbkdf2`**

`auth_key_pbkdf2: "KhIxF5EEYkH5GPX51zTRIR4cHqhpRVALSmTaWE18mZEL2KqCkRMeMU4GR848mGq4SDtNvsybtJ/sZBuX6oFaSg=="` # logstash:logstash

`auth_key_pbkdf2: "logstash:JltDNAoXNtc7MIBs2FYlW0o1f815ucj+bel3drdAk2yOufg2PNfQ51qr0EQ6RSkojw/DzrDLFDeXONumzwKjOA=="` # logstash:logstash

The authentication rule that accepts [HTTP Basic Auth](https://en.wikipedia.org/wiki/Basic_access_authentication). The value is hashed in the same way as it's done in `auth_key_sha512` rule, but it uses [*PBKDF2*](https://en.wikipedia.org/wiki/PBKDF2) key derivation function. At the moment there is no way to configure it, so during the hash generation, the user has to take into consideration the following PBKDF2 input parameters values:

| Input parameter       | Value                      | Comment                                                                     |
| --------------------- | -------------------------- | --------------------------------------------------------------------------- |
| Pseudorandom function | HmacSHA512                 |                                                                             |
| Salt                  | use hashed value as a salt | eg. hashed value = `logstash:logstash`, use `logstash:logstash` as the salt |
| Iterations count      | 10000                      |                                                                             |
| Derived key length    | 512                        | bits                                                                        |

The hash can be calculated using [this calculator](https://8gwifi.org/pbkdf.jsp) (notice that the salt has to base Base64 encoded).

[Impersonation](/kibana/impersonation) is supported by this rule without an extra configuration.

**`auth_key_unix`**

`auth_key_unix: test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0 # Hashed for "test:test"`

**⚠️IMPORTANT** this hashing algorithm is **very CPU intensive**, so we implemented a caching mechanism around it. However, this will not protect Elasticsearch from a DoS attack with a high number of requests with random credentials.

This is authentication rule that is based on `/etc/shadow` file syntax.

If you configured sha512 encryption with 65535 rounds on your system the hash in /etc/shadow for the account `test:test` will be `test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0`

```yaml
readonlyrest:
  access_control_rules:
    - name: Accept requests from users in group team1 on index1
      groups_any_of: ["team1"]
      indices: ["index1"]

    users:
    - username: test
      auth_key_unix: test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0 #test:test
      groups: ["team1"]
```

You can generate the hash with **mkpasswd** Linux command, you need whois package `apt-get install whois` (or equivalent)

`mkpasswd -m sha-512 -R 65534`

Also you can generate the hash with a python script (works on Linux):

```python
#!/usr/bin/python
import crypt
import random
import sys
import string

def sha512_crypt(password, salt=None, rounds=None):
    if salt is None:
        rand = random.SystemRandom()
        salt = ''.join([rand.choice(string.ascii_letters + string.digits)
                        for _ in range(8)])

    prefix = '$6$'
    if rounds is not None:
        rounds = max(1000, min(999999999, rounds or 5000))
        prefix += 'rounds={0}$'.format(rounds)
    return crypt.crypt(password, prefix + salt)


if __name__ == '__main__':
    if len(sys.argv) > 1:
        print sha512_crypt(sys.argv[1], rounds=65635)
    else:
        print "Argument is missing, <password>"
```

**Finally you have to put your username at the beginning of the hash with ":" separator** `test:$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0`

For example, `test` is the username and `$6$rounds=65535$d07dnv4N$QeErsDT9Mz.ZoEPXW3dwQGL7tzwRz.eOrTBepIwfGEwdUAYSy/NirGoOaNyPx8lqiR6DYRSsDzVvVbhP4Y9wf0` is the hash for `test` (the password is identical to the username in this example).

[Impersonation](/kibana/impersonation) is supported by this rule without an extra configuration.

**`token_authentication`**

An authentication rule that accepts a token sent in the HTTP header (`Authorization` by default).

There are two modes of operation: **static token** and **Elasticsearch-native token** (service token or API key).

**Static token**

```yaml
token_authentication:
   type: "static"
   token: "Bearer abc123XYZ"      # required, expected HTTP header content containing the token
   username: "john"               # required, the username assigned after successful authentication
   header: x-custom-authorization # optional, defaults to 'Authorization'
```

The rule matches when the value of the configured header equals the `token` field exactly. For example, for `Authorization: Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2Vu`, the `token` value is `Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2Vu`.

**Elasticsearch service token or API key (Fleet support)**

ROR integrates with Elasticsearch's [service token](https://www.elastic.co/guide/en/elasticsearch/reference/current/service-accounts.html) and [API key](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html) APIs to support Elastic Fleet. When `type` is set to `service-token` or `api-key`, ROR delegates token validation to Elasticsearch rather than comparing against a static value.

```yaml
token_authentication:
   type: "service-token"          # or "api-key"
   username: fleet                # required, the username assigned after successful authentication
   header: x-custom-authorization # optional, defaults to 'Authorization'
```

* `service-token` — validates against Elasticsearch service accounts (used by Fleet Server).
* `api-key` — validates against Elasticsearch API keys (used by Fleet-enrolled agents).

For a complete Fleet setup — including the required `forbid` block for token/API-key management actions and the full set of Fleet index patterns — see the [Elastic Fleet with ReadonlyREST](/elasticsearch/fleet) guide.

For a complete walkthrough including credential flow, the `forbid` block rationale, and a runnable example, see the [Elastic Fleet guide](/examples/fleet).

[Impersonation](/kibana/impersonation) is supported by this rule without an extra configuration.

**`proxy_auth: "*"`**

`proxy_auth: "*"`

Delegated authentication. Trust that a reverse proxy has taken care of authenticating the request and has written the resolved user name into the `X-Forwarded-User` header. The value "\*" in the example, will let this rule match any username value contained in the `X-Forwarded-User` header.

If you are using this technique for authentication using our **Kibana** plugins, don't forget to add this snippet to `conf/kibana.yml`:

`readonlyrest_kbn.proxy_auth_passthrough: true`

So that Kibana will forward the necessary headers to Elasticsearch.

[Impersonation](/kibana/impersonation) is supported by this rule without an extra configuration.

**Groups rules**

The ACL block will match, when the user belongs to groups matching the specified conditions.

The groups rules use the user definitions from [the `users` section](#users-and-groups). In that section, we define static users (and we assign groups to them) or we can authorize dynamic users (and we can map the external groups to the local groups).

* the first step of the groups subrules is authorizing the user
* after this step, we have an authorized user with information about the authorized groups to which the user belongs
* then we check whether the authorized user groups are permitted in context of the rule

**`groups_any_of`**

The ACL block will match when the user belongs to any of the specified groups (boolean OR logic).

Simplified syntax:

```yaml
  groups_any_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    any_of: ["group1", "group2"]
```

**`groups_all_of`**

This rule is very similar to the above defined `groups_any_of` rule, but this time ALL the groups listed in the array are required (boolean AND logic), as opposed to at least one (boolean OR logic) of the `any_of` rule.

Simplified syntax:

```yaml
  groups_all_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    all_of: ["group1", "group2"]
```

**`groups_not_any_of`**

The ACL block will match when the user belongs to NONE of the specified groups.

Simplified syntax:

```yaml
  groups_not_any_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    not_any_of: ["group1", "group2"]
```

Looking at the examples above:

* ACL block will MATCH for user that belongs to `group0`
* ACL block will NOT MATCH for user that belongs only to `group1`
* ACL block will NOT MATCH for user that belongs only to `group2`
* ACL block will NOT MATCH for user that belongs to both `group1` and `group2`
* ACL block will NOT MATCH for user that belongs to `group0`, `group1` and `group2`

**`groups_not_all_of`**

The ACL block will match when the user does not belong to all the specified groups.

Simplified syntax:

```yaml
  groups_not_all_of: ["group1", "group2"]
```

Extended syntax:

```yaml
  groups:
    not_all_of: ["group1", "group2"]
```

Looking at the example above:

* ACL block will MATCH for user that belongs to `group0`
* ACL block will MATCH for user that belongs only to `group1`
* ACL block will MATCH for user that belongs only to `group2`
* ACL block will NOT MATCH for user that belongs to both `group1` and `group2`
* ACL block will NOT MATCH for user that belongs to `group0`, `group1` and `group2`

**groups\_combined**

Logic conditions can be combined inside a single ACL block. It applies only to combining one positive logic (`all_of`/`any_of`) with one negative logic (`not_all_of`/`not_any_of`) The ACL block will match, when both conditions are met.

```yaml
  groups:
    any_of: ["group1", "group2", "group3"]
    not_all_of: ["group1", "group2"]
```

Looking at the example above:

* ACL block will NOT MATCH for user that belongs only to `group0` (because the `any_of` logic is not satisfied)
* ACL block will MATCH for user that belongs only to `group1` (the `any_of` logic is satisfied, the `not_all_of` too, because the user is not member of `group2`)
* ACL block will MATCH for user that belongs to `group1` and `group3` for the same reason
* ACL block will NOT MATCH for user that belongs to `group1` and `group2` (the `any_of` logic is satisfied, but `not_all_of` is not)

**User management**

In the `users` section, each entry tells us that:

* A given user with a username matching one of patterns in the `username` array ...
* belongs to the local groups listed in the `groups` array (example 1 & 2 below) OR belongs to local groups that are result of ["detailed group mapping"](/elasticsearch/groups-rule-mapping) between local group ID and external groups (example 3 below).
* when they can be authenticated and (if authorization rule is present) authorized by the present rule(s).

In general it looks like this:

```yaml
  ...
  - name: "ACL block with groups rule"
    indices: [x, y]
    groups_any_of: ["local_group1"] # this group ID is defined in the "users" section

  users:
  - username: ["pattern1", "pattern2", ...]
    groups: ["local_group1", "local_group2", ...]
    <any authentication rule except groups rules>: ...

  - username: ["pattern1", "pattern2", ...]
    groups: ["local_group1", "local_group2", ...]
    <any authentication rule except groups rules>: ...
    <optionally_any_authorization_rule>: ...

  - username: ["pattern1", "pattern2", ...]
    groups:
      - local_group1: ["external_group1", "external_group2"]
      - local_group2: ["external_group2"]
    <authentication_with_authorization_rule>: ... # `ldap_auth` or `jwt_auth` or `ror_kbn_auth`
```

For details see [User management](#users-and-groups).

[Impersonation](/kibana/impersonation) support depends on authentication and authorization rules used in `users` section.

For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

**`ldap_authentication`**

simple version: `ldap_authentication: ldap1`

extended version:

```yaml
ldap_authentication:
  name: ldap1
  cache_ttl: 10 sec
```

It handles LDAP authentication only using the configured LDAP connector (here `ldap1`). Check the [LDAP connector section](#ldap-connector) to see how to configure the connector.

**`ldap_authorization`**

```yaml
ldap_authorization:
  name: "ldap1"
  groups_any_of: ["group3"]
  cache_ttl: 10 sec
```

* It handles LDAP authorization only using the configured LDAP connector (here `ldap1`).
* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md#checking-groups-logic)
* It matches when previously authenticated user has groups in LDAP and when he belongs to at least one of the configured `groups_any_of` (OR logic). Alternatively, `all_of`/`not_any_of`/`not_all_of`/combined logic can be used to require users to meet certain conditions concerning group membership, as described [here](#groups_combined)
* **⚠️IMPORTANT** the negative groups logic (`not_any_of`/`not_all_of`) cannot be used, when `server_side_groups_filtering` is enabled for LDAP. In that case please use the combined logic, for example with `any_of` positive logic.
* Check the [LDAP connector section](#ldap-connector) to see how to configure the connector.

**`ldap_auth`**

Shorthand rule that combines `ldap_authentication` and `ldap_authorization` rules together. It handles both authentication and authorization using the configured LDAP connector (here `ldap1`).

```yaml
ldap_auth:
  name: "ldap1"
  groups_any_of: ["group1", "group2"]
```

The same functionality can be achieved using the two rules described below:

```yaml
ldap_authentication: ldap1
ldap_authorization:
  name: "ldap1"
  groups_any_of: ["group1", "group2"] # match when user belongs to at least one group
```

In both `ldap_auth`and `ldap_authorization`, the `groups` clause can be replaced by `group_and` to require the valid LDAP user must belong to all the listed groups:

```yaml
ldap_auth:
  name: "ldap1"
  groups_all_of: ["group1", "group2"] # match when user belongs to ALL listed groups
```

Or equivalently:

```yaml
ldap_authentication: ldap1
ldap_authorization:
  name: "ldap1"
  groups_all_of: ["group1", "group2"] # match when user belongs to ALL listed groups
```

See the dedicated [LDAP section](#ldap-connector)

[Impersonation](/kibana/impersonation) support by LDAP rules requires to add [an extra configuration](/kibana/impersonation#defining-mocks-of-the-external-services-optional).

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

**`jwt_authentication`**

See below, the dedicated [JSON Web Tokens section](#json-web-token-jwt-auth). It's an authentication rule.

[Impersonation](/kibana/impersonation) is not currently supported by this rule.

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token
    kibana:
      access: ro
    jwt_authentication:
      name: "jwt_provider_1"

  jwt:
  - name: jwt_provider_1
    signature_key: "your_signature_min_256_chars"
    user_claim: email
```

**`jwt_authorization`**

See below, the dedicated [JSON Web Tokens section](#json-web-token-jwt-auth). It's an authorization rule.

[Impersonation](/kibana/impersonation) is not currently supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token with a writer group
    kibana:
      access: rw
    jwt_authorization:
      name: "jwt_provider_1"
      groups_any_of: ["writer"]

  - name: Valid JWT token with a viewer and writer groups
    kibana:
      access: rw
    jwt_authorization:
      name: "jwt_provider_1"
      groups_all_of: ["writer", "viewer"]

  jwt:
  - name: jwt_provider_1
    signature_key: "your_signature_min_256_chars"
    group_ids_claim: resource_access.client_app.group_ids
```

**`jwt_auth`**

See below, the dedicated [JSON Web Tokens section](#json-web-token-jwt-auth). It's an authentication and authorization rule at the same time.

[Impersonation](/kibana/impersonation) is not currently supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token with a viewer and writer groups
    kibana:
      access: rw
    jwt_auth:
      name: "jwt_provider_1"
      groups_all_of: ["writer", "viewer"]

  jwt:
  - name: jwt_provider_1
    signature_key: "your_signature_min_256_chars"
    user_claim: email
    group_ids_claim: resource_access.client_app.group_ids
```

**`external_authentication`**

Used to delegate authentication to another server that supports HTTP Basic Auth. See below, the dedicated [External BASIC Auth section](#external-basic-auth)

[Impersonation](/kibana/impersonation) support by this rule requires to add [an extra configuration](/kibana/impersonation#defining-mocks-of-the-external-services-optional).

For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

**`groups_provider_authorization`**

Used to delegate groups resolution for a user to a JSON microservice. See below, the dedicated [Groups Provider Authorization section](#custom-groups-providers)

[Impersonation](/kibana/impersonation) support by this rule requires to add [an extra configuration](/kibana/impersonation#defining-mocks-of-the-external-services-optional).

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

**`ror_kbn_authentication`**

([Enterprise](https://readonlyrest.com/enterprise))

For [Enterprise](https://readonlyrest.com/enterprise) customers only, required for SAML authentication. From ROR's perspective it authenticates users.

```yaml
readonlyrest:
  access_control_rules:
    - name: "ReadonlyREST Enterprise instance"
      ror_kbn_authentication:
        name: "kbn1"

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1" # <- use environmental variables for better security!
```

It handles authentication only using the configured ROR KBN connector (here `kbn1`). Continue reading about this in the kibana plugin documentation, in the dedicated [SAML section](/kibana#saml)

[Impersonation](/kibana/impersonation) is currently not supported by this rule.

**`ror_kbn_authorization`**

([Enterprise](https://readonlyrest.com/enterprise))

For [Enterprise](https://readonlyrest.com/enterprise) customers only. From ROR's perspective it authorizes users.

```yaml
readonlyrest:
  access_control_rules:

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authorization:
        name: "kbn1"
        groups_any_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to at least one of the configured groups

    - name: "ReadonlyREST Enterprise instance #1 - two groups required"
      ror_kbn_authorization:
        name: "kbn1"
        groups_all_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to all configured groups

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1" # <- use environmental variables for better security!

    - name: kbn2
      signature_key: "shared_secret_kibana2" # <- use environmental variables for better security!
```

It handles authorization only using the configured ROR KBN connector (here `kbn1` and `kbn2`). Continue reading about this in the kibana plugin documentation, in the dedicated [SAML section](/kibana#saml)

[Impersonation](/kibana/impersonation) is currently not supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

**`ror_kbn_auth`**

([Enterprise](https://readonlyrest.com/enterprise))

For [Enterprise](https://readonlyrest.com/enterprise) customers only, required for SAML authentication. From ROR's perspective it authenticates and authorize users.

```yaml
readonlyrest:
  access_control_rules:

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to at least one of the configured groups

    - name: "ReadonlyREST Enterprise instance #1 - two groups required"
      ror_kbn_auth:
        name: "kbn1"
        groups_all_of: ["SAML_GRP_1", "SAML_GRP_2"] # <- use this field when a user should belong to all configured groups

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1" # <- use environmental variables for better security!

    - name: kbn2
      signature_key: "shared_secret_kibana2" # <- use environmental variables for better security!
```

This authentication and authorization connector represents the secure channel (based on JWT tokens) of signed messages necessary for our Enterprise Kibana plugin to securely pass back to ES the username and groups information coming from browser-driven authentication protocols like SAML

Continue reading about this in the kibana plugin documentation, in the dedicated [SAML section](/kibana#saml)

[Impersonation](/kibana/impersonation) is currently not supported by this rule.

* Groups logic syntax can be uses as part of this rule, as described in the [Checking groups logic section](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md#checking-groups-logic)
* For more information on the ROR's authorization rules, see [Authorization rules details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/authorization-rules-details.md)

**`users`**

`users: ["root", "*@mydomain.com"]`

It's NOT an authentication rule, but it can be used to limit access to of specific users whose username is contained or matches the patterns in the array. This rule is independent from the authentication method chosen, so it will work well in conjunction LDAP, JWT, proxy\_auth, and all others. The rule won't be matched if there won't be an authenticated user by some other authentication rule (it means that to make sense, it should always be used in conjunction with some authentication rule).

For example:

```yaml
readonlyrest:
  access_control_rules:
    - name: "JWT auth for viewer group (role), limited to certain usernames"
      kibana:
        access: ro
      users: ["root", "*@mydomain.com"]
      jwt_auth:
        name: "jwt_provider_1"
        groups_any_of: ["viewer"]
```

#### Kibana-related rules

**`kibana`**

The `kibana` rule underpins all ROR Kibana-related settings that may be needed to provide great user experience.

```yaml
kibana:
  access: ro # required
  index: ".kibana_custom_index" # optional
  template_index: ".kibana_template" # optional
  hide_apps: [ "Security", "Enterprise Search"] # optional
  metadata: 
    dept: "@{jwt:tech.beshu.department}"
    alert_message:  "Dear @{acl:current_group} users, you are viewing dashboards for indices @{acl:available_groups}_logstash-*"
```

When `access: api_only` is used, `allowed_api_paths` can additionally be specified:

```yaml
kibana:
  access: api_only # required for allowed_api_paths
  allowed_api_paths: # optional, only valid with access: api_only
    - "^/api/spaces/.*$"
    - http_method: POST
      http_path: "^/api/saved_objects/.*$"
```

The rule consists of several sub-rules:

**`access`**

Enables the minimum set of Elasticsearch `actions` necessary for browsers to sustain a Kibana session, and rejects any other unrelated actions.

This "macro" rule allows the minimum set of actions necessary for a browser to use Kibana. It allows a set of actions towards the designated kibana index (see [`kibana.index`](#index)), plus a stricter subset of read-only actions towards other indices, which are considered "data indices".

The idea is that with one single sub-rule we allow the bare minimum set of index+action combinations necessary to support a Kibana browsing session.

Possible access levels:

* `ro_strict`: the browser has a read-only view on Kibana dashboards and settings and all other indices.
* `ro`: some write requests can go through to the `kibana_index` index so that the UI state in "Discover" can be saved and new short urls can be created.
* `rw`: some more requests will be allowed towards the `kibana_index` index only, so Kibana dashboards and settings can be modified.
* `admin`: like `rw`, but has additional permissions to save security settings in the ReadonlyREST PRO/Enterprise app
* `api_only`: only [Kibana REST API](https://www.elastic.co/guide/en/kibana/current/api.html) actions are allowed, login via browser is always denied.
* `unrestricted`: no action is restricted.

**NB:** The `admin` access level does not mean the user will be allowed to access all indices/actions. It's just like "rw" with settings changes privileges. If you truly require unrestricted access for your Kibana user, including ReadonlyREST PRO/Enterprise app, set `kibana.access: unrestricted`. You can use this rule with the `users` rule to restrict access to selected admins.

This sub-rule is often used with the `indices` rule, to limit the data a user is able to see represented on the dashboards. In that case do not forget to allow the custom kibana index in the `indices` rule!

**`index`**

([Enterprise](https://readonlyrest.com/enterprise))

**Default value is `.kibana`**

Specify to what index we expect Kibana to attempt to read/write its settings (use this together with `kibana.index` setting in the `kibana.yml` file)

This value directly affects how `kibana.access` works because at all the access levels (yes, even admin), `kibana.access` sub-rule will **NOT** match any *write* request in indices that are not the designated kibana index.

If used in conjunction with ReadonlyREST Enterprise, this rule enables **multi tenancy**, because in ReadonlyREST, a tenancy is identified with a set of Kibana configurations, which are by design collected inside a kibana index (default: `.kibana`).

It supports [dynamic variables](#dynamic-variables).

**⚠️IMPORTANT** When you use the `kibana` rule together with the `indices` rule in the same block, you don't have to explicitly allow the Kibana-related indices in the list of allowed indices of the `indices` rule. ROR will do it for you automatically.

Example:

```yaml
- name: "::RW_USER::"
  auth_key: rw_user:pwd
  kibana:
    access: rw
  indices: ["r*"] # .kibana, .kibana_8.10.4, .kibana_task_manager, etc are allowed here, because there is the `kibana` rule present in the same block
```

**`template_index`**

([Enterprise](https://readonlyrest.com/enterprise))

Used to pre-populate tenancies with default kibana objects, like dashboards and visualizations. Thus providing a starting point for new tenants that will avoid the bad user experience of logging for the first time and finding a completely empty Kibana.

It supports [dynamic variables](#dynamic-variables).

**`hide_apps`**

([PRO](https://readonlyrest.com/pro))

Specify which Kibana apps and menu items should be hidden. This feature will work in ReadonlyREST PRO and Enterprise.

For more information on the ROR's Kibana Hide Apps feature, see [Hiding Kibana Apps](/kibana#hiding-kibana-apps).

**`allowed_api_paths`**

**Only valid when `access: api_only`.**

Used to define which parts of [Kibana REST API](https://www.elastic.co/guide/en/kibana/current/api.html) can be used. The sub-rule requires to define a list of [regular expressions](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) which describes the API paths. Additionally, when you would like to restrict only specific HTTP methods of the API, you can use the extended format of the sub-rule:

```yaml
kibana:
  access: api_only
  allowed_api_paths: # optional
    - http_method: POST
      http_path: "^/api/saved_objects/.*$"
```

**`metadata`**

([Enterprise](https://readonlyrest.com/enterprise))

User to define the Custom ROR Kibana Metadata which can be used in [Custom middleware](/kibana#custom-middleware). The `kibana.metadata` in ReadonlyREST settings is an unstructured YAML object.

It supports [dynamic variables](#dynamic-variables).

Sample usage:

```yaml
kibana:
  [...]
  metadata:
     alert_message:  "Dear @{acl:current_group} users, you are viewing dashboards for indices @{acl:available_groups}_logstash-*"
```

`alert_message` Metadata can be used on the Kibana side to display information to the user on login to the Kibana.

Declare custom Kibana JS file `readonlyrest_kbn.kibana_custom_js_inject_file: '/path/to/custom_kibana.js'`. it's injected at the end of the HTML Body tag of the Kibana UI frontend code.

```js
const alertMessage = window.ROR_METADATA.customMetadata && window.ROR_METADATA.customMetadata.alert_message;

if (alertMessage) {
  alert(alertMessage);
}
```

#### Elasticsearch level rules

**`indices`**

`indices: ["sales", "logstash-*"]`

Matches if the request involves a set of indices (or aliases, or data streams) whose name is "sales", or starts with the string "logstash-", or a combination of both.

If a request involves a wildcard (i.e. "logstash-\*", "\*"), this is first expanded to the list of available indices, and then treated normally as follows:

* Requests that do not involve any indices (cluster admin, etc) result in a "match".
* Requests that involve only allowed indices result in a "match".
* Requests that involve a mix of allowed and not-allowed indices, are rewritten to only involve allowed indices, and result in a "match".
* Requests that involve only not-allowed indices result in a "no match". And the ACL evaluation moves on to the next block.

The rejection message and HTTP status code returned to the requester are chosen carefully with the main intent to simulate not-allowed indices do not exist at all.

The rule has also an extended version:

```yaml
indices:
  patterns: ["sales", "logstash-*"]`
  must_involve_indices: false
```

The definition above has the same meaning as the shortest version shown at the beginning of this section. By default the rule will be matched when a request doesn't involve indices (eg. /\_cat/nodes request). But we can change the behaviour by configuring `must_involve_indices: true` - in this case the request above will be rejected by the rule.

**In detail, with examples**

In ReadonlyREST we roughly classify requests as:

* "read": the request will not change the data or the configuration of the cluster
* "write": when allowed, the request changes the internal state of the cluster or the data.

If a **read request** involves some indices they have permissions for and some indices that they do NOT have permission for, the request is **rewritten** to involve only the subset of indices they have permission for. This is behaviour is very useful in Kibana: **different** users can see the **same** dashboards, but they are filled with a different, overlapping, or identical data set (according to the indices permissions).

When the subset of indices is empty, it means that user are not allowed to access requested indices. In multitenancy environment we should consider two options:

* requested indices don't exist
* requested indices exist but the current user is not authorized to access them

For both of these cases ROR is going to return HTTP 404 or HTTP 200 with an empty response. The same behaviour will be observed for ES with ROR disabled (for nonexistent index). If an index does exist, but a user is not authorized to access it, ROR is going to pretend that the index doesn't exist and a response will be the same like the index actually did not exist. See [detailed example](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/index-not-found-examples.md).

It's also worth mentioning, that when `global_settings.prompt_for_basic_auth` is set to `true` (that is disabled by default), ROR will return 401 instead of 404 HTTP status code. It is relevant for users who don't use ROR Kibana's plugin and would like to take advantage of default Kibana's behavior which shows the native browser basic auth dialog, when it receives HTTP 401 response (see [the example](#prompt_for_basic_auth)). If a **write request** wants to write to indices they don't have permission for, the write request is rejected.

**Requests related to templates**

Templates are also connected with indices, but rather indirectly. An index template has index patterns and could also have aliases. During an index template creation or modification, ROR checks if index patterns and aliases, defined in a request body, are allowed. When a user tries to remove or get template by name, ROR checks if the template can be considered as allowed for the user, and based on that information, it allows/forbids to remove or see it. See [details](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/indices-rule-templates.md).

**`actions`**

`actions: ["indices:data/read/*"]`

Match if the request action starts with "indices:data/read/".

In Elasticsearch, each request carries only one action. We extracted from Elasticsearch source code the full list of valid action strings as of all Elasticsearch versions. Please see the [dedicated section to find the actions list of your specific Elasticsearch version](https://github.com/beshu-tech/readonlyrest-docs/tree/master/actionstrings/README.md).

Example actions (see above for the full list):

```
 "cluster:admin/data_frame/delete"
 "cluster:admin/data_frame/preview"
 ...
 "cluster:monitor/data_frame/stats/get"
 "cluster:monitor/health"
 "cluster:monitor/main"
 "cluster:monitor/nodes/hot_threads"
 "cluster:monitor/nodes/info"
...
 "indices:admin/aliases"
 "indices:admin/aliases/get"
 "indices:admin/analyze"
...
 "indices:data/read/get"
 "indices:data/read/mget"
 "indices:data/read/msearch"
 "indices:data/read/msearch/template"
 ...
 "indices:data/write/bulk"
 "indices:data/write/bulk_shard_operations[s]"
 "indices:data/write/delete"
 "indices:data/write/delete/byquery"
 "indices:data/write/index"
 "indices:data/write/reindex"
 ...
 many more...
```

**`snapshots`**

`snapshots: ["snap_@{user}_*"]`

Restrict what snapshots names can be saved or restored

**`repositories`**

`repositories: ["repo_@{user}_*"]`

Restrict what repositories can snapshots be saved into

**`data_streams`**

`data_streams: ["ds_@{user}_*"]`

Restrict what data stream names can be created, deleted, or modified

**`filter`**

`filter: '{"query_string":{"query":"user:@{user}"}}'`

This rule enables **Document Level Security (DLS)**. That is: return only the documents that satisfy the boolean query provided as an argument.

This rule lets you filter the results of a read request using a boolean query. You can use *dynamic variables* i.e. `@{user}` (see dedicated paragraph) to inject a user name or some header values in the query, or even environmental variables.

**Example: per-user index segmentation**

In the index "test-dls", each user can only search documents whose field "user" matches their user name. I.e. A user with username "paul" requesting all documents in "test-dls" index, won't see returned a document containing a field `"user": "jeff"` .

```yaml
- name: "::PER-USER INDEX SEGMENTATION::"
  proxy_auth: "*"
  indices: ["test-dls"]
  filter: '{"bool": { "must": { "match": { "user": "@{user}" }}}}'
```

**Example 2: Prevent search of "classified" documents.**

In this example, we want to avoid that users belonging to group "press" can see any document that has a field "access\_level" with the value "CLASSIFIED". And this policy is applied to all indices (no indices rule is specified).

```yaml
- name: "::Press::"
  groups_any_of: ["press"]
  filter: '{"bool": { "must_not": { "match": { "access_level": "CLASSIFIED" }}}}'
```

**⚠️IMPORTANT** The `filter`and `fields` rules will only affect "read" requests, therefore "write" requests **will not match** because otherwise it would implicitly allow clients to "write" without the filtering restriction. For reference, this behaviour is identical to x-pack and search guard.

**⚠️IMPORTANT** Beginning with version 1.27.0 all ROR internal requests from kibana will not match blocks containing `filter` and/or `fields` rules. There requests are used to perform kibana login and dynamic config reload.

If you want to allow write requests (i.e. for Kibana sessions), just duplicate the ACL block, have the first one with `filter` and/or `fields` rule, and the second one without.

**`fields`**

This rule enables **Field Level Security (FLS)**. That is:

* for responses where fields with values are returned (e.g. Search/Get API) - filter and show only allowed fields
* make not allowed fields unsearchable - used in QueryDSL requests (e.g. Search/MSearch API) do not have impact on search result.

In other words: FLS protects from usage some not allowed fields for a certain user. From user's perspective it seems like such fields are nonexistent.

**Definition**

Field rule definition consists of two parts:

* A non empty list of fields (blacklisted or whitelisted) names. Supports wildcards and user runtime variables.
* The FLS engine definition (global setting, optional). See: [engine details](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/fls-engine.md).

**⚠️IMPORTANT** With default FLS engine it's required to install ReadonlyREST plugin in all the data nodes. Different configurations allowing to avoid such requirement are described in [engine details](https://github.com/beshu-tech/readonlyrest-docs/tree/c53dbf8e6d8fa97f505b0513ac57d3738a2a9356/elasticsearch-details/fls-engine.md).

**Field names**

Fields can be defined using two access modes: blacklist and whitelist.

**Blacklist mode (recommended)**

Specifies which fields should not be allowed prefixed with `~` (other fields from mapping become allowed implicitly). Example:

`fields: ["~excluded_fields_prefix_*", "~excluded_field", "~another_excluded_field.nested_field"]`

Return documents but deprived of the fields that:

* start with `excluded_fields_prefix_`
* are equal to `excluded_field`
* are equal to `another_excluded_field.nested_field`

**Whitelist mode**

Specifies which fields should be allowed explicitly (other fields from mapping become not allowed implicitly). Example:

`fields: ["allowed_fields_prefix_*", "_*", "allowed_field.nested_field.text"]`

Return documents deprived of all the fields, except the ones that:

* start with `allowed_fields_prefix_`
* start with underscore
* are equal to `allowed_field.nested_field.text`

**NB:** You can only provide a full black list or white list. Grey lists (i.e. `["~a", "b"]`) are invalid settings and ROR will refuse to boot up if this condition is detected.

Example: hide prices from catalogue indices

```yaml
- name: "External users - hide prices"
  fields: ["~price"]
  indices: ["catalogue_*"]
```

**⚠️IMPORTANT** Any metadata fields e.g. `_id` or `_index` can not be used in `fields` rule.

**⚠️IMPORTANT** The `filter`and `fields` rules will only affect "read" requests, therefore "write" requests **will not match** because otherwise it would implicitly allow clients to "write" without the filtering restriction. For reference, this behaviour is identical to x-pack and search guard.

**⚠️IMPORTANT** Beginning with version 1.27.0 all ROR internal requests from kibana will not match blocks containing `filter` and/or `fields` rules. There requests are used to perform kibana login and dynamic config reload.

If you want to allow write requests (i.e. for Kibana sessions), just duplicate the ACL block, have the first one with `filter` and/or `fields` rule, and the second one without.

**Configuring an ACL with filter/fields rules when using Kibana**

A normal Kibana session interacts with Elasticsearch using a mix of actions which we can roughly group in two macro categories of "read" and "write" actions. However the `fields` and `filter` rules will **only match read requests**. They will also block ROR internal request used to log in to kibana and reload config. This means that a complete Kibana session cannot anymore be entirely matched by a single ACL block like it normally would.

For example, this ACL block would perfectly support a complete Kibana session. That is, 100% of the actions (browser HTTP requests) would be allowed by this ACL block.

```yaml
    - name: "::RW_USER::"
      auth_key: rw_user:pwd
      kibana:
        access: rw
      indices: ["r*"]
```

However, when we introduce a filter (or fields) rule, this block will be able to match only some of the actions (only the "read" ones).

```yaml
    - name: "::RW_USER::"
      auth_key: rw_user:pwd
      kibana:
        access: rw  # <-- won't work because of `filter` rule present in block (it mismatches RW requests)
      indices: ["r*"]
      filter: '{"query_string":{"query":"DestCountry:FR"}}'  # <-- will reject all write requests! :(
```

The solution is to duplicate the block. The first one will intercept (and filter!) the read requests. The second one will intercept the remaining actions. Both ACL blocks together will entirely support a whole Kibana session.

```yaml
    - name: "::RW_USER (filter read requests)::"
      auth_key: rw_user:pwd
      indices: ["r*"] # <-- KIBANA-RELATED INDICES WON"T BE FILTERED HERE!
      filter: '{"query_string":{"query":"DestCountry:FR"}}'

    - name: "::RW_USER (allow remaining requests)::"
      auth_key: rw_user:pwd
      kibana:
        access: rw
      indices: ["r*"] # <-- KIBANA-RELATED INDICES ARE IMPLICITLY ALLOWED! (because of the presence of the `kibana` rule in the same block)
```

**NB:** Look at how we **make sure that the requests to ".kibana" won't get filtered** by specifying an `indices` rule in the first block.

Here is another example, a bit more complex. Look at how we can duplicate the "PERSONAL\_GRP" ACL block so that the read requests to the "r\*" indices can be filtered, and all the other requests can be intercepted by the second rule (which is identical to the one we had before the duplication).

Before adding the `filter` rule:

```yaml
  - name: "::PERSONAL_GRP::"
    groups_any_of: ["Personal"]
    kibana:
      access: rw
      index: ".kibana_@{user}"
      hide_apps: ["readonlyrest_kbn", "timelion"]
    indices: ["r*"]
```

After adding the `filter` rule (using the block duplication strategy).

```yaml
    - name: "::PERSONAL_GRP (FILTERED SEARCH)::"
      groups_any_of: ["Personal"]
      indices: [ "r*" ]
      filter: '{"query_string":{"query":"DestCountry:FR"}}'

    - name: "::PERSONAL_GRP::"
      groups_any_of: ["Personal"]
      indices: ["r*"]
      kibana:
        access: rw
        index: ".kibana_@{user}"
        hide_apps: ["readonlyrest_kbn", "timelion"]
```

**`response_fields`**

This rule allows filtering Elasticsearch responses using a list of fields. It works in very similar way to `fields` rule. In contrast to `fields` rule, which filters out document fields, this rule filters out response fields. It **doesn't make use of Field Level Security (FLS)** and can be applied to every response returned by Elasticsearch.

It can be configured in two modes:

* *whitelist* allowing only the defined fields from the response object
* *blacklist* filtering out (removing) only the defined fields from the response object

**Blacklist mode**

Specifies which fields should be filtered out by adding the \~ prefix to the field name. Other fields in the response will be implicitly allowed. For example:

`response_fields: ["~excluded_fields_prefix_*", "~excluded_field", "~another_excluded_field.nested_field"]`

The above will return the usual response object, but deprived (if found) of the fields that:

* start with `excluded_fields_prefix_`
* are equal to `excluded_field`
* are equal to `another_excluded_field.nested_field`

**Wildcard across nested fields** It's possible to use the `*` character to intercept nested fields. Imagine having this document:

```json
{
   "_index":"kafka-both",
   "_type":"_doc",
   "_id":"460D9",
   "_score":8.649008,
   "_source":{
      "session_id":64124.0,
      "country":"something",
      "resp":{
         "credit_card_confidential": "378282246310005"
         "proc_time":0.02,
         "type":"spelling",
         "raw_text":{
            "proc_time":0.02,
            "system_entities":{
               "phone_number_confidential":[
                  {
                     "unit":"Number",
                     "string":"666",
                     "value":666
                  }
               ]
            }
         }
      }
   }
}
```

You can write this `fields` rule containing a pattern that uses the `*` right after the `~`:

```yml
fields: ["~*_confidential"]
```

Now the search response will omit the string field `credit_card_confidential`, and the whole object `resp.raw_text.phone_number_confidential`, or any other field whose name ends in "\_confidential", regardless of their type or if and how deeply it's nested.

**Whitelist mode**

In this mode rule is configured to filter out each field that isn't defined in the rule.

`response_fields: ["allowed_fields_prefix_*", "_*", "allowed_field.nested_field.text"]`

Return response deprived of all the fields, except the ones that:

* start with `allowed_fields_prefix_`
* start with underscore
* are equal to `allowed_field.nested_field.text`

**NB:** You can only provide a full black list or white list. Grey lists (i.e. `["~a", "b"]`) are invalid settings and ROR will refuse to boot up if this condition is detected.

*Example*: allow only `cluster_name` and `status` field in cluster health response:

Without any filtering response from `/_cluster/health` looks more or less like:

```json
{
    "cluster_name": "ROR_SINGLE",
    "status": "yellow",
    "timed_out": false,
    "number_of_nodes": 1,
    "number_of_data_nodes": 1,
    "active_primary_shards": 2,
    "active_shards": 2,
    "relocating_shards": 0,
    "initializing_shards": 0,
    "unassigned_shards": 2,
    "delayed_unassigned_shards": 0,
    "number_of_pending_tasks": 0,
    "number_of_in_flight_fetch": 0,
    "task_max_waiting_in_queue_millis": 0,
    "active_shards_percent_as_number": 50.0
}
```

but after configuring such rule:

```yaml
- name: "Filter cluster health response"
  uri_re: "^/_cluster/health"
  response_fields: ["cluster_name", "status"]
```

response from above will look like:

```json
{
    "cluster_name": "ROR_SINGLE",
    "status": "yellow"
}
```

**NB:** Any response field can be filtered using this rule.

#### HTTP Level rules

**`x_forwarded_for`**

`x_forwarded_for: ["192.168.1.0/24"]`

Behaves exactly like `hosts`, but gets the source IP address (a.k.a. origin address, `OA` in logs) inside the `X-Forwarded-For` header only (useful replacement to `hosts`rule when requests come through a load balancer like AWS ELB)

**Load balancers**

This is a nice tip if your Elasticsearch is behind a load balancer. If you want to match all the requests that come through the load balancer, use `x_forwarded_for: ["0.0.0.0/0"]`. This will match the requests with a valid IP address as a value of the `X-Forwarded-For` header.

**DNS lookup caching**

It's worth to note that resolutions of DNS are going to be cached by JVM. By default successfully resolved IPs will be cached forever (until Elasticsearch is restarted) for security reasons. However, this may not always be the desired behaviour, and it can be changed by adding the following JVM options either in the jvm.options file or declaring the ES\_JAVA\_OPTS environment variable: `sun.net.inetaddr.ttl=TTL_VALUE` (or/and `sun.net.inetaddr.negative.ttl=TTL_VALUE`). More details about the problem can be found [here](https://www.ibm.com/support/pages/understanding-tuning-and-testing-inetaddress-class-and-cache).

**`methods`**

`methods: [GET, DELETE]`

Match requests with HTTP methods specified in the list. N.B. Elasticsearch HTTP stack does not make any difference between HEAD and GET, so all the HEAD request will appear as GET.

**`headers_and` (or `headers`)**

`headers: ["h1:x*y","~h2:*xy"]`

Match if **all** the HTTP headers in the request match the defined patterns in headers rule. This is useful in conjunction with [proxy\_auth](#proxy_auth), to carry authorization information (i.e. headers: `x-usr-group: admins`).

The `~` sign is a pattern negation, so eg. `~h2:*xy` means: match if h2 header's value does not match the pattern \*xy, or `h2` is not present at all.

**`headers_or`**

`headers_or: ["x-myheader:val*","~header2:*xy"]`

Match if **at least one** the specified HTTP headers `key:value` pairs is matched.

**`uri_re`**

`uri_re: ["^/secret-index/.*", "^/some-index/.*"]`

**☠️HACKY (try to use indices/actions rule instead)**

Match if **at least one** specified regular expression matches requested URI.

**`maxBodyLength`**

`maxBodyLength: 0`

Match requests having a request body length less or equal to an integer. Use `0` to match only requests without body.

**NB**: Elasticsearch HTTP API breaks the specifications, nad GET requests **might** have a body length greater than zero.

**`api_keys`**

`api_keys: [123456, abcdefg]`

A list of api keys expected in the header `X-Api-Key`

**`session_max_idle`**

`session_max_idle: 1h`

**⚠️DEPRECATED** Browser session timeout (via cookie). Example values 1w (one week), 10s (10 seconds), 7d (7 days), etc. NB: not available for Elasticsearch 2.x.

#### Transport level rules

These are the most basic rules. It is possible to allow/forbid requests originating from a list of IP addresses, host names or IP networks (in slash notation).

**`hosts`**

`hosts: ["10.0.0.0/24"]` Match a request whose **origin** IP address (also called origin address, or `OA` in logs) matches one of the specified IP addresses or subnets.

**`accept_x-forwarded-for_header`**

`accept_x-forwarded-for_header: false`

**⚠️DEPRECATED (use `x_forwarded_for instead`)** A modifier for `hosts` rule: if the origin IP won't match, fallback to check the `X-Forwarded-For` header

**`hosts_local`**

`hosts_local: ["127.0.0.1", "127.0.0.2"]` Match a request whose **destination** IP address (called `DA` in logs) matches one of the specified IP addresses or subnets. This finds application when Elasticsearch HTTP API is bound to multiple IP addresses.

#### Ancillary block settings

**`verbosity`**

`verbosity: error`

Don't spam elasticsearch log file printing log lines for requests that match this block. Defaults to `info`.

### Users and Groups

Sometimes we want to make allow/forbid decisions according to the username associated to a HTTP request. The extraction of the user identity (username) can be done via HTTP Basic Auth (Authorization header) or delegated to a reverse proxy (see `proxy_auth` rule).

The validation of the said credentials can be carried on locally with hard coded credential hashes (see `auth_key_sha256` rule), via one or more LDAP server, or we can forward the Authorization header to an external web server and examine the HTTP status code (see `external_authentication`).

Optionally we can introduce the notion of groups (see them as bags of users). The aim of having groups is to write a very specific block once, and being able to allow multiple usernames that satisfy the block.

Groups can be declared and associated to users statically in the readonlyrest.yml file. Alternatively, groups for a given username can be retrieved from an LDAP server or from a LDAP server, or a custom JSON/XML service.

You can mix and match the techniques to satisfy your requirements. For example, you can configure ReadonlyREST to:

* Extract the username from X-Forwarded-User
* Resolve groups associated to said user through a JSON microservice

Another example:

* Extract the username from Authorization header (HTTP Basic Auth)
* Validate said username's password via LDAP server
* resolve groups associated to the user from groups defined in readonlyrest.yml

More examples are shown below together with a sample configuration.

#### Local users and groups

The `groups` rule accepts a list of group IDs. This rule will match if the resolved username (i.e. via `auth_key`) is associated with the given groups.

In this example, usernames `alice` and `claire` are statically associated with group IDs.\
The username `bob` is statically associated with [structered groups](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/structured-groups.md)(a special syntax for defining groups, which may be helpful in the case of the Enterprise Kibana plugin)

```yaml
 access_control_rules:

    - name: Accept requests from users in group team1 on index1
      type: allow  # Optional, defaults to "allow" will omit now on.
      groups_any_of: ["team1"]
      indices: ["index1"]

    - name: Accept requests from users in group team2 on index2
      groups_any_of: ["team2"]
      indices: ["index2"]

    - name: Accept requests from users in groups team1 OR team2 on index3
      groups_any_of: ["team1", "team2"]
      indices: ["index3"]

    - name: Accept requests from users in groups team4 OR team5 on index3
      groups_any_of: ["team4", "team5"]
      indices: ["index3"]

    - name: Accept requests from users in groups team1 AND team2 on index3
      groups_all_of: ["team1", "team2"]
      indices: ["index3"]

    users:

    - username: "alice"
      groups: ["team1"] # group id - a value that ROR operates in groups rules
      auth_key: alice:p455phrase

    - username: "bob"
      # structured group syntax, useful in case of tenancy selector in Kibana Enterprise plugin
      groups: 
      - id: "team2"     # group id
        name: "Team 2"  # group name - `Team 2` will be visible in the tenancy selector for the 'team2' group 
      - id: "team4"     # group id
        name: "Team 4"  # group name - `Team 4` will be visible in the tenancy selector for the 'team4' group 
      auth_key: bob:s3cr37

    - username: "claire"
      groups: ["team1", "team5"] # group ids
      auth_key_sha256: e0bba5fda92dbb0570fd2e729a3c8ed6b1d52b380581f32427a38e396ba28ec6 #claire:p455key
```

*Example: rules are associated to groups (instead of users) and users-group association is declared separately later under `users:`*

#### Group mapping

Sometimes we'd like to take advantage of groups (roles) existing in external systems (like LDAP). We can do that in `users` section too. It's possible to map external groups to local ones. For details see [External to local groups mapping ](/elasticsearch/groups-rule-mapping).

#### Username case sensitivity

ReadonlyREST can cooperate with services that operate in a case-insensitive way. For this case, ROR has a toggleable username case sensitivity option. For details, see the [username\_case\_sensitivity section](#username_case_sensitivity) in Global Settings.

#### Static variables

Anywhere in `readonlyrest.yml` you can use the expression `${env:MY_ENV_VAR}` to replace in place the environmental variables. This is very useful for injecting credentials like LDAP bind passwords, especially in Docker.

For example, here we declare an environment variable, and we write `${env:LDAP_PASSWORD}` in our settings:

```bash
$ export LDAP_PASSWORD=S3cr3tP4ss
$ cat readonlyrest.yml
```

```yaml
ldaps:
  - name: ldap1
    host: "ldap1.example.com"
    port: 389                                                     
    ssl_enabled: false                                            
    ssl_trust_all_certs: true                                     
    bind_dn: "cn=admin,dc=example,dc=com"                         
    bind_password: "${env:LDAP_PASSWORD}"
    users:
      search_user_base_DN: "ou=People,dc=example,dc=com"
```

And ReadonlyREST ES will load "S3cr3tP4ss" as `bind_password`.

#### Dynamic variables

One of the neatest features in ReadonlyREST is that you can use dynamic variables inside most values of the following rules: `data_streams`, `indices`, `users`, `fields`, `filter`, `repositories`, `hosts`, `hosts_local`, `snapshots`, `response_fields`, `uri_re`, `x_forwarded_for`, `hosts_local`, `hosts`, `kibana.index`, `kibana.template_index`, `kibana.metadata`, [groups rules](#groups-rules). The variables are related to different contexts:

* `acl` - the context of data collected in authentication and authorization rules of the current block:
  * `@{acl:user}` gets replaced with the username of the successfully authenticated user. Using this variable is allowed only in blocks where one of the rules is an authentication rule of course it must be a rule different from the one containing the given variable.
  * `@{acl:current_group}` is the group ID explicitly requested by the tenancy selector in ReadonlyREST Enterprise plugin when using multi-tenancy.
  * `@{acl:available_groups}` gets replaced with available group IDs found in the authorization rule (because by default dynamic variables are resolved to a string, the variable resolved value will contain groups surrounded with double quotes and joined with a comma)
* `header` - the context of ES HTTP request headers
  * `@{header:<header_name>}` gets replaced with the value of the HTTP header with name `<header_name>` included in the incoming request (useful when reverse proxies handle authentication)
* `jwt` - the context of JWT header value
  * `@{jwt:<json_path>}` get replaced with value (or values) found in the JWT claim under the given JSON path

**Dynamic variables exploding**

A value resolved from a dynamic variable is a string. Some rules, like `indices` one, have multivalue context (you can configure several indices names in it).

Let's assume we have a request with the header: `APPS: app1,app2,app3`. Doing something like this:

```yaml
indices: ["logstash_@{header:apps}"]
```

We should expect it to be resolved to:

```yaml
indices: ["logstash_app1,app2,app3"]
```

for this particular request. No, it wouldn't be helpful at all. But there is an `explode` function for dynamic variables. Doing:

```yaml
indices: ["logstash_@explode{header:apps}"]
```

we should get:

```yaml
indices: ["logstash_app1", "logstash_app2", "logstash_app3"]
```

which looks more useful!

So, as we've seen, the `explode` attribute of a dynamic variable rule can be used to split a string with comma-separated values into an array of strings. But it can only be used in a rule with multi value context.

**Usage examples**

**Indices from user name**

You can let users authenticate externally, i.e. via LDAP, and use their user name string inside the `indices` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can see only their logstash indices" # i.e. alice can see alice_logstash-20170922
      ldap_authentication:
        name: "myLDAP"
      indices: ["@{acl:user}_logstash-*"] 

    # LDAP connector settings omitted, see LDAP section below..
```

**Indices from available groups**

You can let users authorize externally, i.e. via LDAP, and use their group strings inside the `indices` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can see only logstash indices for their departments" # i.e. alice belongs to 'dev' and 'ops' department groups, so she can see dev_logstash-20170922, ops_logstash-20170922
      ldap_auth:
        name: "myLDAP"
        groups_any_of: ["dev", "ops", "qa"]
      indices: ["@explode{acl:available_groups}_logstash-*"] # i.e when available_groups=[dev, ops] we will get indices: ["dev_logstash-*", "ops_logstash-*"] 

    # LDAP connector settings omitted, see LDAP section below..
```

**Filter from available groups**

You can let users authorize externally, i.e. via LDAP, and use their group strings inside the `filter` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can only see documents related to their departments" # i.e. alice belongs to 'dev' and 'ops' department groups, so she can see documents where "department" field is equal 'dev' or 'ops' 
      ldap_auth:
        name: "myLDAP"
        groups_any_of: ["dev", "ops", "qa"]
      filter: '{ "terms": { "department": [@{acl:available_groups}] }}' # i.e. from available_groups=[dev, ops] we will get filter: '{ "terms": { "department": ["dev","ops"] }}'
      indices: ["logstash-*"]

    # LDAP connector settings omitted, see LDAP section below..
```

**Uri regex matching user's current group**

You can let users authorize externally, i.e. via LDAP, and use their group inside the `uri_re` rule.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Users can access uri with value containing user's current group, i.e. user with group 'g1' can access: '/path/g1/some_thing'"
      ldap_authorization:
        name: "ldap1"
        groups_any_of: ["g1", "g2", "g3"]
      uri_re: ["^/path/@{acl:current_group}/.*"]

    # LDAP connector settings omitted, see LDAP section below..
```

**Kibana index from headers**

Imagine that we delegate authentication to a reverse proxy, so we know that only authenticated users will ever reach Elasticsearch. We can tell the reverse proxy (i.e. Nginx) to inject a header called `x-nginx-user` containing the username.

```yaml
readonlyrest:
    access_control_rules:

    - name: "Identify a personal kibana index where each user is supposed to save their dashboards"
      kibana:
        access: rw
        index: ".kibana_@{header:x-nginx-user}"
```

**Dynamic variables from JWT claims**

The JWT token is an authentication string passed generally as a header or a query parameter to the web browser. If you squint, you can see it's a concatenation of three base64 encoded strings. If you base64 decode the middle string, you can see the "claims object". That is the object containing the current user's metadata.

Here is an example of JWT claims object.

```javascript
{
  "user": "jdoe",
  "display_name": "John Doe",
  "department": "infosec",
  "allowedIndices": ["x", "y"]
}
```

Here follow some examples of how to use JWT claims as dynamic variables in ReadonlyREST ACL blocks, notice the "jwt:" prefix:

```yaml
# Using JWT claims as dynamic variables
indices: [ "idx_@{jwt:department}", "idx_other" ]
# claims = { "user": "u1", "department": "infosec"}
# -> indices: ["idx_infosec", "idx_other"]

# Using nested values in JWT using JSONPATH as dynamic variables
indices: [ "idx_@{jwt:jsonpath.to.department}", "idx_other"]
# claims = { "jsonpath": {"to": { "department": "infosec" }}}
# -> indices: ["idx_infosec", "idx_other"]

# Referencing array-typed values from JWT claims will expand in a list of strings
indices: [ "idx_@explode{jwt:allowedIndices}", "idx_other"]
# claims = {"username": "u1", "allowedIndices":  ["x", "y"] }
# -> indices: ["idx_x", "idx_y", "idx_other"]

# Explode operator will generate an array of strings from a comma-separated string
indices: ["logstash_@explode{x-indices_csv_string}*", "idx_other"]
# HTTP Headers: [{ "x-indices_csv_string": "a,b"}]
# -> indices: ["logstash_a*", "logstash_b*", "idx_other"]
```

#### Variables functions

A value resolved from a variable may not be valid in some contexts. Sometimes, the value from the variable needs some preprocessing before usage. For example, a HTTP header value containing the uppercase characters is a wrong candidate for the index name because it has to be a lowercase string. We introduced variable functions to overcome these limitations. They allow modification of the variable values during the variable resolution (both, [static](#static-variables) and [dynamic](#dynamic-variables) variables are supported).

With their help, you can use the HTTP header `X-Forwarded-User: James` containing uppercase characters in the `indices` rule:

```yaml
indices: [ 'index_@{header:x-forwarded-user}#{to_lowercase}' ]
# @{header:x-forwarded-user} is replaced by HTTP header value 'James', and then the given function chain (function `to_lowercase` converting all characters to lowercase) is applied to the header value
```

which resolves to:

```yaml
indices: [ 'index_james' ]
```

**Syntax**

In general, functions syntax is as follows:

`function_name("arg1","arg2")`

* `function_name` - a function that you want to apply
* `(...)` - function call parentheses (they may be omitted when the function has no args)
* `arg1`, `arg2` - arguments passed to function. They should be surrounded by `"`. If your argument contains a special character (`"` or `}`), you can escape it with a `\`, e.g. (`function_a("\}")`)

You can chain functions with the `.` operator (functions are applied in order from left to right):

`function_a("arg1").function_b.function_c("arg1")`

To apply functions to the variable, you need to use the `#` operator and enter your code in `{ }` braces:

```
@{--variable-definition--}#{--functions-chain--}
```

```yaml
# Using JWT claims as dynamic variables with variable function
indices: [ "idx_@{jwt:department}#{to_lowercase}", "idx_other" ]
# claims = { "user": "u1", "department": "Infosec"}
# -> indices: ["idx_infosec", "idx_other"]
```

**Supported functions**

Currently, we support functions like this:

* `replace_all(regex,replacement)` - Replaces each substring of the variable string that matches the given regular expression with the given replacement.

  Params:

  * `regex` - the [regular expression](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) to which the variable string is to be matched
  * `replacement` - the string to be substituted for each match

  Usage:

  ```yaml
  indices: [ 'index_@{header:app}#{replace_all("team","group")}' ]
  ```
* `replace_first(regex,replacement)` - Replaces the first substring of the variable string that matches the given regular expression with the given replacement.

  Params:

  * `regex` - the [regular expression](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html) to which the variable string is to be matched
  * `replacement` - the string to be substituted for each match

  Usage example:

  ```yaml
  indices: [ 'index_@{header:app}#{replace_first("^team","group")}' ]
  ```
* `to_lowercase` - Converts all characters in the variable string to lower case

  Usage example:

  ```yaml
  indices: [ 'index_@{header:app}#{to_lowercase}' ]
  ```
* `to_uppercase` - Converts all characters in the variable string to upper case Usage example:

  ```yaml
  groups_any_of: [ 'x1', '@{header:group}#{to_uppercase}' ]
  ```

**💡 Didn't find the function you are looking for?**

We can easily extend the function list. If you need any new function/mechanism that cannot be obtained using the supported functions, let us know about it in our [forum](https://forum.readonlyrest.com/). We will consider adding the proper implementation.

**Variable function aliases**

Sometimes, the function chain may be very complex or occur multiple times in ACL. In this case, you can use a `function aliases` to simplify configuration management. The function alias allows you to export your function chain outside the ACL. Then you can substitute your function via alias `func(alias_name)`.

Let's assume that we have the following configuration, and we want to introduce some function aliases:

```yaml
readonlyrest:
   access_control_rules:
      - name: Alice
        indices: ['index_@{header:group}#{to_lowercase.replace_all("\\d","x")}']
        auth_key: alice:p455phrase

      - name: Bob
        indices: ['index_@{header:group}#{to_lowercase.replace_all("\\d","x").replace_first("^team","")}']
        auth_key: bob:s3cr37
```

You can define function aliases in the `readonlyrest.variables_function_aliases` section and substitute functions code with `func(alias)`:

```yaml
readonlyrest:
   variables_function_aliases:
      - custom_replace: 'to_lowercase.replace_all("\\d","x")' # convert to lower case and replace digits with x
      - skip_team_prefix: 'replace_first("^team","")'

   access_control_rules:
      - name: Alice
        indices: ['index_@{header:group}#{func(custom_replace)}']
        auth_key: alice:p455phrase

      - name: Bob
        indices: ['index_@{header:group}#{func(custom_replace).func(skip_team_prefix)}']
        auth_key: bob:s3cr37
```

#### LDAP connector

The authentication and authorization rules for LDAP (`ldap_auth`, `ldap_authentication`, `ldap_authorization`) defined in the rules section, always need to contain a reference by name to one LDAP connector. One or more LDAP connectors need to be defined in the section "ldaps" of the ACL.

**Configuration notes**

If you would like to experiment with LDAP and need a development server, you can stand up an OpenLDAP server configuring it using our schema file, which can be found in [our tests](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/develop/core/src/test/resources/test_example.ldif)).

**Technical configuration**

There are also plenty of technical settings which can be useful:

* an LDAP server address:
  * single host:
    * `host` (String, required) - LDAP server address
    * `port` (Integer, optional, default: `389`) - LDAP server port
    * `ssl_enabled` (Boolean, optional, default: `true`) - enables or disables SSL for LDAP connection
  * several hosts:
    * `hosts` (List, required) - list of LDAP server addresses. The address should look like this `ldap://[HOST]:[PORT]` or/and `ldaps://[HOST]:[PORT]`
    * `ha` (enum: \[`FAILOVER`, `ROUND_ROBIN`], optional, default: `FAILOVER`) - provides high availability strategy for LDAP
  * auto-discovery:
    * `server_discovery` (Boolean|YAML object, optional, default: `false`) - for details see [LDAP server discovery section](#ldap-server-discovery)
* `connection_pool_size` (Integer, optional, default: `30`) - indicates how many connections LDAP connector should create to LDAP server
* `connection_timeout` (Duration, optional, default: `10 sec`) - instructs connector how long it should wait for the connection to LDAP server
* `request_timeout` (Duration, optional, default: `10 sec`) - instructs connector how long it should wait for receiving a whole response from LDAP server
* `connection_health_check_interval` (Duration, optional, default: `120 sec`) - defines how often the LDAP connection pool should perform health checks on idle connections. Health checks proactively detect and replace stale connections before they cause authentication failures.
* `connection_max_age` (Duration, optional, default: `10 min`) - defines the maximum age of a connection in the pool. Connections older than this value are automatically replaced with fresh ones, preventing stale connection issues. This works in conjunction with `connection_health_check_interval` to maintain a healthy connection pool.
* `ssl_trust_all_certs` (Boolean, optional, default: `false`) - if it is set to `true`, untrusted certificates will be accepted
* `ignore_ldap_connectivity_problems` (Boolean, optional, default: `false`) - when it is set to `true`, it allows ROR to function even when LDAP server is unreachable. Rules using unreachable LDAP servers won't match. By default, ROR starts only after it's able to connect to each server
* `cache_ttl` (Duration, optional, default: `0 sec`) - tells how long LDAP connector should cache queries results (for default see [caching section](#caching))
* `circuit_breaker` (YAML object, optional, default: `max_retries: 10`, `reset_duration: 10 sec`) - for details see [circuit breaker section](#circuit-breaker)

**Query configuration**

Usually, we would like to configure three main things for defining the way LDAP users and groups are queried:

1. a way to **authenticate client** (LDAP binding; used by all LDAP rules):
   * `bind_dn` (string, optional, default: \[not present]) - a username used to connect to the LDAP service. We can skip this setting when our LDAP service allows for anonymous binding
   * `bind_password` (string, optional, default: \[not present]) - a password used to connect to the LDAP service. We can skip this setting when our LDAP service allows for anonymous binding
2. a way to **search users**. In ROR it can be done using the following YAML keys (under the `users` section) (used by all LDAP rules):
   * `search_user_base_DN` (string, required) - should refer to the base Distinguished Name of the users to be authenticated
   * `user_id_attribute` (string, optional, default: `uid`) - should refer to a unique ID for the user within the base DN
   * `skip_user_search` (boolean, optional, default: `false`) - when you set `user_id_attribute: "cn"` you may want to skip the user search. This optimizes the authentication, which is done in two steps (searching for a user DN and authenticating the user with a given DN). If you configure it to be `true`, the user's DN will be `cn={user_login},{search_user_base_DN}`.
3. a way to **search user groups** (NOT used by [`ldap_authentication`](#ldap_authentication) rule). You can configure all properties under the `groups` section in LDAP connector configuration.

   In ROR, depending on LDAP schema, a relation between users and groups can be defined in:

   1. Group entry - it has an attribute that refers to User entries (`mode: search_groups_in_group_entries`this is the default):
      * `search_groups_base_DN` (required) - should refer to the base Distinguished ID of the groups to which these users may belong
      * `group_id_attribute` (string, optional, default: `cn`) - is the LDAP group object attribute that contains the IDs of the ROR groups
      * `group_name_attribute` (string, optional, default: group\_id\_attribute) - is the LDAP group object attribute that contains the [name](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/structured-groups.md) of the ROR groups
      * `unique_member_attribute` (string, optional, default: `uniqueMember`) - is the LDAP group object attribute that contains the IDs of the ROR groups
      * `group_search_filter` (string, optional, default: `(cn=*)`) - is the LDAP search filter (or filters) to limit the user groups returned by LDAP. By default, this filter will be joined (with `&`) with `unique_member_attribute=user_dn` filter resulting in this LDAP search filter: `(&YOUR_GROUP_SEARCH_FILTER(unique_member_attribute=user_dn))`.
      * `group_attribute_is_dn` (boolean, optional, default: `true`) -
        * when `true` the search filter will look like that: `(&YOUR_GROUP_SEARCH_FILTER(unique_member_attribute={USER_DN}))`
        * then `false` the search filer will look like that: `(&YOUR_GROUP_SEARCH_FILTER(unique_member_attribute={USER_ID_ATTRIBUTE_VALUE}))`
      * `server_side_groups_filtering` (boolean, optional, default: `false`) - by default ROR's LDAP connector asks for all groups of the given user. The group filtering is done on ROR's side. It allows ROR to cache them efficiently. But in some cases (e.g. when the user has hundreds of groups), it's better to filter them on the LDAP server side. If this setting is `true`, LDAP will only be queried for a certain subset of the user groups (defined by the `groups_any_of`/`groups_all_of` subrule of the `ldap_authorization`/`ldap_auth` rule). Note, however, that ONLY the returned subset of the user's groups is cached. See [groups caching details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/caching.md#group-caching) for a deep explanation.
      * `nested_groups_depth` (positive int, optional, no default) - it defines how deep ROR should ask LDAP to extract the nested LDAP groups. See the [nested groups support section](#nested-ldap-groups-support) for details.
   2. User entry - it has an attribute that refers to Group entries (`mode: search_groups_in_user_entries` has to be set to use this strategy):
      * `search_groups_base_DN` (string, required) - should refer to the base Distinguished ID of the groups to which these users may belong
      * `group_id_attribute` (string, optional, default: `cn`) - is the LDAP group object attribute that contains the IDs of the ROR groups
      * `groups_from_user_attribute` (string, optional, default: `memberOf`) - is the LDAP user object attribute that contains the names of the ROR groups
      * `group_search_filter` (string, optional, default: `(objectClass=*)`) is the LDAP search filter (or filters) to limit the user groups returned by LDAP
      * `nested_groups_depth` (positive int, optional, no default) - it defines how deep ROR should ask LDAP to extract the nested LDAP groups. When this setting is configured, ROR needs to know what group's attribute holds the parent group ID - it can be set using `unique_member_attribute` (the default is the `uniqueMember` value). See the [nested groups support section](#nested-ldap-groups-support) for details.

Examples:

```
group_search_filter: "(objectClass=group)"
group_search_filter: "(objectClass=group)(cn=application*)"
group_search_filter: "(cn=*)" # basically no group filtering
```

**Caching**

Too many calls made by ROR to our LDAP service can sometimes be problematic (eg. when one LDAP connector is used in many rules). The problem can be simply solved by using caching functionality. Caching can be configured per LDAP connector or per LDAP rule (see [`ldap_auth`](#ldap_auth), [`ldap_authentication`](#ldap_authentication), [`ldap_authorization`](#ldap_authorization) rules). By default cache is disabled. We can enable it by setting `cache_ttl` > `0 sec`. In the cache will be stored only results of successful requests - info about authentication results and/or returned LDAP groups for the given credentials. When LDAP connector level cache is used any rule that uses the connector can take advantage of cached results. When we configure `cache_ttl` at the LDAP rule level, the results of LDAP calls made by the rule will be stored in the cache. Other LDAP rules won't have access to this cache. See [caching details](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/caching.md) for deep explanation.

**Circuit Breaker**

The LDAP connector is equipped by default with a circuit breaker functionality. The circuit breaker can disable the connector from sending new requests to the server when it doesn't respond properly. After receiving a configurable number of failed responses in a row, the circuit breaker feature disables sending any new requests by terminating them immediately with an exception. After a configurable amount of time, the circuit breaker feature allows one request to pass again. If it succeeds, the connector goes back to normal operation. If not, a test request is sent again after a configurable amount of time. A general description of the concept could be found on [wiki](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) and more about specific implementation could be found in [library documentation](https://monix.io/docs/current/catnap/circuit-breaker.html).

The circuit breaker feature can be customized to adapt to specific needs using the following configuration parameters:

* `max_retries` is the number of failed responses in a row that will trigger the circuit breaker.
* `reset_duration` defines how long the circuit breaker feature will block the incoming requests before starting to send one test request. to the LDAP server.

**LDAP Server discovery**

The LDAP connector can get all LDAP hostnames from the DNS server rather than from the configuration file. By default `_ldap._tcp` SRV records are used for that, but any other SRV record can be configured.

The simplest configuration example of an LDAP connector instance using server discovery is:

```yaml
    - name: ldap
      server_discovery: true
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

This configuration is using the system DNS to fetch all the `_ldap._tcp` SRV records which are expected to contain the hostname and port of all the LDAP servers we should connect to. Each SRV record also has priority and weight assigned to it which determine the order in which they should be contacted. Records with a lower priority value will be used before those with a higher priority value. The weight will be used if there are multiple service records with the same priority, and it controls how likely each record is to be chosen. A record with a weight of 2 is twice as likely to be chosen as a record with the same priority and a weight of 1.

The server discovery mechanism can be optionally configured further, by adding a few more configuration parameters, all of which are optional:

* `record_name` - DNS SRV record name. By default it's `_ldap._tcp`, but could be `_ldap._tcp.domainname` or any custom value.
* `dns_url` - Address of non-default DNS server in form `dns://IP[:PORT]`. By default, the system DNS is used.
* `ttl` - DNS cache timeout. Specifies how long values from DNS will be kept in the cache. Default is 1h.
* `use_ssl` - Use `true` when SSL should be used for LDAP connections. Default is `false` which means that SSL won't be used.

Example:

```yaml
    - name: ldap
      server_discovery:
        record_name: "_ldap._tcp.example.com"
        dns_url: "dns://192.168.1.100"
        ttl: "3 hours"
        use_ssl: true
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

**Nested LDAP groups support**

Let's imagine we have the following groups in LDAP:

* `employees`
* `it`
* `developers`
* `sales`
* `managers`

And there is `John Dev` who is assigned to `developers` groups and `Alize Man` whos group is `managers`.

Moreover, we know that:

* to `it` group belongs all users who are `developers`
* to `sales` group belongs all users who are `managers`
* to `employees` groups belongs all users who are from `it` or `sales`

The groups configuration like the above, in ROR, we call nested LDAP groups.

By default, when ROR searches for eg. `John Dev`'s groups, it is going to get the `developers` group only. But taking into consideration the nested groups configuration, we know that `John Dev` belongs to the following groups: `developers`, `it` & `employees`. Sometimes it'd be nice to have them all available in ROR's LDAP authorization rule.

ROR extracts nested groups by making additional search queries to LDAP. It asks LDAP: *tell me which groups `developers` belongs to?*. LDAP should return: `it`. Then ROR asks again: *so, tell me which groups `it` belongs to?*. LDAP answers: `employees`. And again, ROR asks: *Tell me which groups `empoyees` belongs to?*. LDAP should say: *no groups found*. And this is the point where ROR stops. ROR did additional 3 queries to LDAP to establish that `John Dev` belongs additionally to `it` and `employees` group.

As you probably noticed, enabling nested groups extraction can be costly. ROR obviously tries to do its best to reduce the cost eg. by caching (if cache is enabled) or extracting each unique group always only once during the groups call handling. To reduce the cost more, you can define the depth of the extraction by providing `nested_groups_depth` (the presence of the setting enables the feature, so you have to configure it to enable the nested groups extraction).

Let's say we configured it like that: `nested_groups_depth: 1`. In the example above ROR asks only once: *tell me which groups `developers` belongs to?*. After the LDAP's response: `it` there won't be any more queries. That's because of the depth equaled 1.

**ROR with LDAP - examples**

In this example, users' credentials are validated via LDAP. The groups associated with each validated user, are resolved using the same LDAP server.

**Simpler: authentication and authorization in one rule**

```yaml
readonlyrest:

    access_control_rules:

    - name: Accept requests from users in group team1 on index1
      type: allow                                           # Optional, defaults to "allow", will omit from now on.
      ldap_auth:
        name: "ldap1"                                       # ldap name from below 'ldaps' section
        groups_any_of: ["g1", "g2"]                                # group within 'ou=Groups,dc=example,dc=com'
      indices: ["index1"]

    - name: Accept requests from users in group team2 on index2
      ldap_auth:
        name: "ldap2"
        groups_any_of: ["g3"]
        cache_ttl_in_sec: 60
      indices: ["index2"]

    ldaps:

    - name: ldap1
      host: "ldap1.example.com"
      port: 389
      ssl_enabled: false
      ssl_trust_all_certs: true
      ignore_ldap_connectivity_problems: true
      bind_dn: "cn=admin,dc=example,dc=com"
      bind_password: "password"
      users:
        search_user_base_DN: "ou=People,dc=example,dc=com"
        user_id_attribute: "uid"
      groups:
        mode: 'search_groups_in_group_entries'                # available options: 'search_groups_in_group_entries' (default), 'search_groups_in_user_entries' 
        search_groups_base_DN: "ou=Groups,dc=example,dc=com"
        unique_member_attribute: "uniqueMember"                   
        group_search_filter: "(objectClass=group)(cn=application*)"
        group_id_attribute: "cn"
      connection_pool_size: 20
      connection_timeout: 1s
      request_timeout: 2s
      connection_health_check_interval: 30s
      connection_max_age: 5min
      cache_ttl: 60s                                            
      circuit_breaker:                                        
        max_retries: 2                                           
        reset_duration: 5s                                       

    # High availability LDAP settings (using "hosts", rather than "host")
    - name: ldap2
      hosts:
      - "ldaps://ssl-ldap2.foo.com:636"
      - "ldaps://ssl-ldap3.foo.com:636"
      ha: "ROUND_ROBIN"
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"

    # Server discovery variant
    - name: ldap3
      server_discovery: true
      users:
        search_user_base_DN: "ou=People,dc=example2,dc=com"
      groups:  
        search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

**Advanced: authentication and authorization in separate rules**

```yaml
readonlyrest:
  
  global_settings:
    response_if_req_forbidden: Forbidden by ReadonlyREST ES plugin

  access_control_rules:

  - name: Accept requests to index1 from users with valid LDAP credentials, belonging to LDAP group'team1'
    ldap_authentication: "ldap1"
    ldap_authorization:
      name: "ldap1"                                       # ldap name from 'ldaps' section
      groups_any_of: ["g1", "g2"]                         # group within 'ou=Groups,dc=example dc=com'
    indices: ["index1"]

  - name: Accept requests to index2 from users with valid LDAP credentials, belonging to LDAP group 'team2'
    ldap_authentication:
      name: "ldap2"
      cache_ttl: 60s
    ldap_authorization:
      name: "ldap2"
      groups_any_of: ["g3"]
      cache_ttl: 60s
    indices: ["index2"]

  ldaps:

  - name: ldap1
    host: "ldap1.example.com"
    port: 389
    ssl_enabled: false
    ssl_trust_all_certs: true
    ignore_ldap_connectivity_problems: true
    bind_dn: "cn=admin,dc=example,dc=com"
    bind_password: "password"
    users:
      search_user_base_DN: "ou=People,dc=example,dc=com"
      user_id_attribute: "uid"
    groups:
      search_groups_base_DN: "ou=Groups,dc=example,dc=com"
      unique_member_attribute: "uniqueMember"                   
    connection_pool_size: 20                                  
    connection_timeout: 1s                                   
    request_timeout: 2s
    connection_health_check_interval: 30s
    connection_max_age: 5min                                      
    cache_ttl: 60s                                            

  # High availability LDAP settings (using "hosts", rather than "host")
  - name: ldap2
    hosts:
    - "ldaps://ssl-ldap2.foo.com:636"
    - "ldaps://ssl-ldap3.foo.com:636"
    ha: "ROUND_ROBIN"
    users: 
      search_user_base_DN: "ou=People,dc=example2,dc=com"
    groups:
      search_groups_base_DN: "ou=Groups,dc=example2,dc=com"
```

#### External Basic Auth

ReadonlyREST will forward the received `Authorization` header to a website of choice and evaluate the returned HTTP status code to verify the provided credentials. This is useful if you already have a web server with all the credentials configured and the credentials are passed over the `Authorization` header.

```yaml
readonlyrest:
  access_control_rules:

  - name: "::Tweets::"
    methods: GET
    indices: ["twitter"]
    external_authentication: "ext1"

  - name: "::Facebook posts::"
    methods: GET
    indices: ["facebook"]
    external_authentication:
      service: "ext2"
      cache_ttl_in_sec: 60

  external_authentication_service_configs:

  - name: "ext1"
    authentication_endpoint: "http://external-website1:8080/auth1"
    success_status_code: 200
    cache_ttl_in_sec: 60
    http_connection_settings:
      validate: false # SSL certificate validation (default to true)
      connection_timeout_in_sec: 1           # default 2
      socket_timeout_in_sec: 2               # default 5
      connection_request_timeout_in_sec: 1   # default 5  
      connection_pool_size: 20               # default 30

  - name: "ext2"
    authentication_endpoint: "http://external-website2:8080/auth2"
    success_status_code: 204
    cache_ttl_in_sec: 60
```

To define an external authentication service the user should specify:

* `name` for service (then this name is used as id in `service` attribute of `external_authentication` rule)
* `authentication_endpoint` (GET request)
* `success_status_code` - authentication response success status code

Cache can be defined at the service level or/and at the rule level. In the example, both are shown, but you might opt for setting up either.

#### Custom groups providers

This external authorization connector makes it possible to resolve to what groups a users belong, using an external JSON or XML service.

```yaml
readonlyrest:
  access_control_rules:

  - name: "::Tweets::"
    methods: GET
    indices: ["twitter"]
    proxy_auth:
      proxy_auth_config: "proxy1"
      users: ["*"]
    groups_provider_authorization:
      user_groups_provider: "GroupsService"
      groups_any_of: ["group3"]

  - name: "::Facebook posts::"
    methods: GET
    indices: ["facebook"]
    proxy_auth:
      proxy_auth_config: "proxy1"
      users: ["*"]
    groups_provider_authorization:
      user_groups_provider: "GroupsService"
      groups_any_of: ["group1"]
      cache_ttl_in_sec: 60

  proxy_auth_configs:

  - name: "proxy1"
    user_id_header: "X-Auth-Token"                         

  user_groups_providers:

  - name: GroupsService
    groups_endpoint: "http://localhost:8080/groups"
    auth_token_name: "token"
    auth_token_passed_as: QUERY_PARAM                              # HEADER OR QUERY_PARAM
    response_groups_ids_json_path: "$..groups[?(@.id)].id"         # JSON-path style, see https://github.com/json-path/JsonPath
    response_groups_names_json_path: "$..groups[?(@.name)].name"   # optional, JSON-path style, see https://github.com/json-path/JsonPath
    cache_ttl_in_sec: 60
    http_connection_settings:
      connection_timeout_in_sec: 1                        
      socket_timeout_in_sec: 2                            
      connection_request_timeout_in_sec: 2                
      connection_pool_size: 20                            
```

In example above, a user is authenticated by reverse proxy and then external service is asked for groups for that user. If groups returned by the service contain any group declared in `groups` list, user is authorized and rule matches.

Also in this rule, the `groups` clause can be replaced by `group_and` to require the user must belong to all the listed groups:

```yaml
  groups_provider_authorization:
    user_groups_provider: "GroupsService"
    groups_all_of: ["group1", "group2"] # match when user belongs to ALL listed groups
```

To define user groups provider you should specify:

* `name` - (string, required) - identifier of the service which needs to be passed in the `groups_provider_authorization` rule (`user_groups_provider` attribute)
* `groups_endpoint` - (string, required) - service with groups endpoint
* `auth_token_name` - (string, required) - user identifier will be passed with this name
* `auth_token_passed_as` - (string, required, can be one of `HEADER` or `QUERY_PARAM`) - the way how user identifier is passed to the service
* `http_method` - (string, optional, can be one of `GET` (default), `POST`) - HTTP method used to send request
* `response_group_ids_json_path`,`response_groups_json_path` (string, required) - response can be unrestricted, but you have to specify [JSON Path](https://github.com/json-path/JsonPath) for group ID list
* `response_group_names_json_path` (string, optional, default: `response_group_ids_json_path`)- [JSON Path](https://github.com/json-path/JsonPath) for [groups name](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/structured-groups.md) list (both arrays, available at `response_group_ids_json_path` and `response_group_names_json_path`, have to have the same length and have the same order)

As usual, the cache behaviour can be defined at service level or/and at rule level.

#### JSON Web Token (JWT) Auth

The information about the username can be extracted from the "claims" inside a JSON Web Token. Here is an example.

```yaml
readonlyrest:
  access_control_rules:
  - name: Valid JWT token with a viewer group
    kibana:
      access: ro
    jwt_auth:
      name: "jwt_provider_1"
      groups_any_of: ["viewer"]

  - name: Valid JWT token with a writer group
    kibana:
      access: rw
    jwt_auth:
      name: "jwt_provider_1"
      groups_any_of: ["writer"]

  - name: Valid JWT token with a viewer and writer groups
    kibana:
      access: rw
    jwt_auth:
      name: "jwt_provider_1"
      groups_all_of: ["writer", "viewer"]

  jwt:
  - name: jwt_provider_1
    signature_algo: HMAC # can be NONE, RSA, HMAC (default), and EC
    signature_key: "your_signature_min_256_chars"
    user_claim: email
    group_ids_claim: resource_access.client_app.group_ids # JSON-path style, see https://github.com/json-path/JsonPath
    group_names_claim: resource_access.client_app.group_names # optional, JSON-path style, see https://github.com/json-path/JsonPath
    header_name: Authorization
```

You can verify groups assigned to the user with the groups logic (`groups_any_of`/`groups_all_of`/`groups_not_any_of`/`groups_not_all_of`/`groups_combined`) described in the [Groups logic](#user_belongs_to_groups) section.

To define JWT provider, you need to provide:

* `name` - (string, required) - identifier of the JWT provider, which needs to be passed in the `jwt_auth` rule
* `user_claim` - (string, optional) - indicates which field in the JSON will be interpreted as the username. To define the claim path, use [JSON-path](https://github.com/json-path/JsonPath) syntax.
* `group_ids_claim` (string, optional) - indicates which field in the JSON will be interpreted as the group ID. To define the claim path, use [JSON-path](https://github.com/json-path/JsonPath) syntax.
* `group_names_claim` (string, optional, defaults to `group_ids_claim`) - indicates which field in the JSON will be interpreted as the [group name](https://github.com/beshu-tech/readonlyrest-docs/tree/master/details/structured-groups.md). To define the claim path, use [JSON-path](https://github.com/json-path/JsonPath) syntax.
* `header_name` (string, optional, defaults to `Authorization`) - HTTP header name carrying the JWT Token, can be used if we expect the JWT Token in a custom header (i.e. [Google Cloud IAP signed headers](https://cloud.google.com/iap/docs/signed-headers-howto)).
* `signature_key` (string, required) - shared secret between the issuer of the JWT and ReadonlyREST. It is used to verify the cryptographical "paternity" of the message.
* `signature_algo` (string, optional, can be one of `NONE`, `RSA`, `HMAC` (default), and `EC`) - indicates the family of cryptographic algorithms used to validate the JWT.

**⚠️IMPORTANT**: As described above, both claim names (`user_claim` and `group_ids_claim`) are optional, but:

* `jwt_authentication` rule requires `user_claim` to be defined in the JWT provider
* `jwt_authorization` rule requires `group_ids_claim` to be defined in the JWT provider
* `jwt_auth` rule requires both those settings

**Accepted signature\_algo values**

The value of this configuration represents the cryptographic family of the JWT protocol. Use the below table to tell what value you should configure, given a JWT token sample. You can decode sample JWT token using an [online tool](https://jwt.io/).

| Algorithm declared in JWT token | `signature_algo` value |
| ------------------------------- | ---------------------- |
| NONE                            | **None**               |
| HS256                           | **HMAC**               |
| HS384                           | **HMAC**               |
| HS512                           | **HMAC**               |
| RS256                           | **RSA**                |
| RS384                           | **RSA**                |
| RS512                           | **RSA**                |
| PS256                           | **RSA**                |
| PS384                           | **RSA**                |
| PS512                           | **RSA**                |
| ES256                           | **EC**                 |
| ES384                           | **EC**                 |
| ES512                           | **EC**                 |

### Audit

ReadonlyREST can gather audit events that contain information regarding a request and its processing by the system, which can then be forwarded to predefined outputs. You can use the available information from the audit events to construct interesting visual representations, such as Kibana dashboards or any other visualization tool. For details see [Audit configuration](/elasticsearch/audit).

### Other settings

#### Disabling ReadonlyREST ACL

The ReadonlyREST ACL can be temporarily disabled without uninstalling the plugin by setting `readonlyrest.enable: false` in the configuration. The default value is `true`. When disabled, all requests will bypass the ACL rules.

Example:

```yaml
readonlyrest:
  enable: false
```

#### Global settings

The `readonlyrest.global_settings` section contains various settings that affect different parts of the ACL:

**`prompt_for_basic_auth`**

When set to `true`, ROR will return HTTP 401 instead of 403 when authentication fails. This prompts browsers to show a basic auth dialog. This is particularly useful when not using ReadonlyREST Kibana plugin and wanting to take advantage of Kibana's default behavior. Defaults to `false`. But we don't recommend to change this default behaviour.

Example:

```yaml
readonlyrest:
  global_settings:
    prompt_for_basic_auth: true
```

**`response_if_req_forbidden`**

Customize the response message returned when a request is forbidden by any ACL block. This can be overridden at the block level using the `type.response_message` setting (see section on [Unauthorized response configuration](#unauthorized-response-configuration)). Defaults to "Forbidden by ReadonlyREST ES plugin".

Example:

```yaml
readonlyrest:
  global_settings:
    response_if_req_forbidden: "You shall not pass!"
```

**`fls_engine`**

Specifies which Field Level Security engine to use for document filtering. Can be either "es\_with\_lucene" (default) or "es". This setting determines how ReadonlyREST handles field-level security with the [`fields` rule](#fields).

* **es\_with\_lucene** (default): Hybrid approach where most FLS operations are handled by Elasticsearch, with Lucene as a fallback for complex cases. Provides full functionality but requires ReadonlyREST to be installed on all nodes.
* **es**: FLS is handled only by Elasticsearch without Lucene fallback. This mode doesn't require ReadonlyREST on all nodes but has limitations for certain request types.

For detailed information about capabilities and limitations of each engine, see [FLS engine documentation](/elasticsearch/fls-engine).

Example:

```yaml
readonlyrest:
  global_settings:
    fls_engine: es
```

**`username_case_sensitivity`**

Controls username comparison behavior across all authentication rules. Can be either "case\_sensitive" (default) or "case\_insensitive". Useful when integrating with case-insensitive systems.

Example:

```yaml
readonlyrest:
  global_settings:
    username_case_sensitivity: case_insensitive
```

**`users_section_duplicate_usernames_detection`**

When enabled, ROR validates the `users` section during startup to ensure there are no duplicate usernames defined. This helps prevent configuration errors. Defaults to `true`. In some scenarios you may want to disable it.

Example:

```yaml
readonlyrest:
  global_settings:
    users_section_duplicate_usernames_detection: false
```

### ACL Troubleshooting

The main issues seen in support cases:

* Bad ordering or ACL blocks. Remember that the ACL is evaluated sequentially, block by block. And the first block whose rules all match is accepted.
* Users don't know how to read the `HIS` field in the logs, which instead is crucial because it contains a trace of the evaluation of rules and blocks.
* LDAP configuration: LDAP is tricky to configure in any system. Configure ES root logger to `DEBUG` editing `$ES_PATH_CONF/config/log4j2.properties` to see a trace of the LDAP messages.

#### Interpreting ACL logs

ReadonlyREST prints a log line for each incoming request (this can be selectively avoided on ACL block level using the `verbosity` rule).

**Allowed requests**

This is an example of a request that matched an ACL block (allowed) and has been let through to Elasticsearch.

> ALLOWED by { name: 'Admins', policy: ALLOW, rules: \[groups\_any\_of, kibana] } req={ ID:44d12d75-4340-4e3e-9507-5bb439db9d80-1159548962#7510, TYP:SearchRequest, CGR:\<N/A>, USR:admin, BRS:true, ACT:indices:data/read/search, OA:192.168.65.1/32, XFF:null, DA:172.19.0.2/32, IDX:*, MET:GET, PTH:/\_search, CNT:\<N/A>, HDR:Accept=*/*, User-Agent=curl/8.7.1, Host=localhost:19200, Authorization=, HIS:\[KIBANA: NOT\_MATCHED (AUTH\_FAIL (Username mismatch)) -> RULES:\[auth\_key->false]], \[Admins: MATCHED -> RULES:\[groups\_any\_of->true, kibana->true] RESOLVED:\[user=admin;group=Administrators;av\_groups=Administrators;indices=*;kibana\_idx=.kibana]], }

**Explanation**

The log line immediately states that this request has been allowed by an ACL block called "Admins". Immediately follows a summary of the requests' anatomy. The format is semi-structured, and it's intended for humans to read quickly, it's not JSON, or anything else.

Similar information gets logged in JSON format via [audit events](#audit) feature described ealier.

Here is a glossary:

* `ID`: ReadonlyREST-level request id
* `TYP`: String, the name of the Java class that internally represent the request type (very useful for debug)
* `CGR`: String, the request carries a "current group" header (used for multi-tenancy).
* `USR`: String, the user name ReadonlyREST was able to extract from Basic Auth, JWT, LDAP, or other methods as specified in the ACL.
* `BRS`: Boolean, an heuristic attempt to tell if the request comes from a browser.
* `ACT`: String, the elasticsearch level action associated with the request. For a list of actions, see our [actions rule docs](#actions).
* `OA`: IP Address, originating address (source address) of the TCP connection underlying the http session.
* `IDX`: Strings array: the list of indices affected by this request.
* `MET`: String, HTTP Method
* `CNT`: String, HTTP body content. Comes as a summary of its length, full body of the request is available in debug mode.
* `HDR`: String array, list of HTTP headers, headers' content is available in debug mode.
* `HIS`: Chronologically ordered history of the ACL blocks and their rules being evaluated. When a block is `NOT_MATCHED`, the denial cause appears in parentheses after the block name (e.g. `AUTH_FAIL(...)`, `GROUPS_AUTH_FAIL(...)`, `AUTHZ_FAIL`, `IDX_NOT_FOUND`). See [Denial causes in HIS](#denial-causes-in-his) for a full reference.

In the example, the block `Admins` is allowing the request because all the rules in this block evaluate to `true`.

**Forbidden requests**

This is an example of a request that gets forbidden by ReadonlyREST ACL.

```
FORBIDDEN by default req={ ID:af26efdb-9193-424d-8dc9-d2cda617842a-1466512324#7967, TYP:SearchRequest, CGR:<N/A>, USR:admin (attempted), BRS:true, ACT:indices:data/read/search, OA:192.168.65.1/32, XFF:null, DA:172.19.0.2/32, IDX:*, MET:GET, PTH:/_search, CNT:<N/A>, HDR:Accept=*/*, User-Agent=curl/8.7.1, Host=localhost:19200, Authorization=<OMITTED>, HIS:[KIBANA: NOT_MATCHED (AUTH_FAIL (Username mismatch)) -> RULES:[auth_key->false]], [Admins: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Invalid password); {user1,user2}:GROUPS_AUTH_FAIL (No user's groups allowed))) -> RULES:[groups_any_of->false]], [End users: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Invalid password); {user1,user2}:AUTH_FAIL (Username mismatch))) -> RULES:[groups_any_of->false]], [Business users: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Invalid password); user1:AUTH_FAIL (Username mismatch); user2:GROUPS_AUTH_FAIL (No user's groups allowed))) -> RULES:[groups_any_of->false]] }
```

The above rule gets forbidden "by default". This means that no ACL block has matched the request, so ReadonlyREST's default policy of rejection takes effect.

**Requests finished with INDEX NOT FOUND**

This is an example of such request:

```
INDEX NOT FOUND req={ ID:5cdbd3ec-2093-426d-85c9-b2d0be7361b5-746941746#8477, TYP:GetIndexRequest, CGR:<N/A>, USR:user1 (attempted), BRS:true, ACT:indices:admin/get, OA:192.168.65.1/32, XFF:null, DA:172.19.0.2/32, IDX:nonexistent, MET:GET, PTH:/nonexistent, CNT:<N/A>, HDR:Accept=*/*, User-Agent=curl/8.7.1, Host=localhost:19200, Authorization=<OMITTED>, HIS:[KIBANA: NOT_MATCHED (AUTH_FAIL (Username mismatch)) -> RULES:[auth_key->false]], [Admins: NOT_MATCHED (GROUPS_AUTH_FAIL (admin:AUTH_FAIL (Username mismatch); {user1,user2}:GROUPS_AUTH_FAIL (No user's groups allowed))) -> RULES:[groups_any_of->false]], [End users: NOT_MATCHED (IDX_NOT_FOUND) -> RULES:[groups_any_of->true, kibana->true, indices->false]], [Business users: NOT_MATCHED (IDX_NOT_FOUND) -> RULES:[groups_any_of->true, kibana->true, indices->false]] }
```

The state above is only possible for read-only ES requests (ES requests which don't change ES cluster state) for a block containing an `indices` rule. If all other rules within the block are matched, but only the `indices` rule is mismatched, the final state of the block is forbidden due to an index not found.

**Denial causes in `HIS`**

When a block is `NOT_MATCHED`, a denial cause appears in parentheses after the block name. These causes make it straightforward to distinguish between authentication failures (wrong credentials) and authorization failures (missing permissions) without additional debugging.

| Cause                         | Meaning                                                                                                                                            |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH_FAIL(details)`          | Authentication failed. The human-readable `details` string describes the specific reason (e.g. wrong username, bad password, missing credentials). |
| `GROUPS_AUTH_FAIL(details)`   | Groups-based authorization failed. The `details` string describes which user definitions were tried and why each one was rejected.                 |
| `AUTHZ_FAIL`                  | A non-authentication rule (e.g. `indices`, `actions`) caused the block to be rejected.                                                             |
| `IDX_NOT_FOUND`               | All auth rules matched but the requested index does not exist. Applies only to read-only requests.                                                 |
| `ALIAS_NOT_FOUND`             | All auth rules matched but the requested alias does not exist. Applies only to read-only requests.                                                 |
| `TPL_NOT_FOUND`               | All auth rules matched but the requested index template does not exist. Applies only to read-only requests.                                        |
| `IMPERSONATION_NOT_SUPPORTED` | The rule being evaluated does not support impersonation, which was attempted by the request.                                                       |
| `IMPERSONATION_NOT_ALLOWED`   | Impersonation was attempted but the impersonator is not allowed to impersonate the target user under this block.                                   |

**`blocks_history` in audit logs**

The same per-block information is also available in structured form in the `blocks_history` field of audit log entries. Each element in the array represents one evaluated ACL block:

```json
"blocks_history": [
  {
    "block_name": "KIBANA",
    "matched": false,
    "forbidden_cause": "AUTH_FAIL(Username mismatch)"
  },
  {
    "block_name": "Admins",
    "matched": true,
    "forbidden_cause": null
  }
]
```

Each entry has three fields:

* `block_name` — the name of the ACL block
* `matched` — `true` if the block permitted the request, `false` if it was rejected
* `forbidden_cause` — the denial reason in the same format as `HIS`, or `null` if the block matched

#### Enabling debug logs

You can configure Elasticsearch logging by editing the `$ES_PATH_CONF/log4j2.properties` file. See [the official Elasticsearch logging documentation](https://www.elastic.co/docs/deploy-manage/deploy/self-managed/configure-elasticsearch#logging) for details.

**Global debug mode**

To enable debug logging globally, set the root logger level to `debug`:

```
rootLogger.level = debug
```

**Only ReadonlyREST debug mode**

To enable debug logging only for ReadonlyREST, append the following to `log4j2.properties`:

```
logger.ror.name=tech.beshu.ror
logger.ror.level=debug
```

**Trick: log requests to different files**

Use the following `log4j2.properties` snippet to write ReadonlyREST ACL/request logs to a dedicated rolling file:

```
# ReadonlyREST ACL/request log -> separate rolling file

appender.readonlyrest_acl_rolling.type = RollingFile
appender.readonlyrest_acl_rolling.name = readonlyrest_acl_rolling
appender.readonlyrest_acl_rolling.fileName = ${sys:es.logs}_readonlyrest_acl.log
appender.readonlyrest_acl_rolling.filePattern = ${sys:es.logs}_readonlyrest_acl-%d{yyyy-MM-dd}.log.gz

appender.readonlyrest_acl_rolling.layout.type = PatternLayout
appender.readonlyrest_acl_rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%.-10000m%n

appender.readonlyrest_acl_rolling.policies.type = Policies
appender.readonlyrest_acl_rolling.policies.time.type = TimeBasedTriggeringPolicy
appender.readonlyrest_acl_rolling.policies.time.interval = 1
appender.readonlyrest_acl_rolling.policies.time.modulate = true

logger.readonlyrest_acl.name = tech.beshu.ror
logger.readonlyrest_acl.level = info
logger.readonlyrest_acl.appenderRef.readonlyrest_acl_rolling.ref = readonlyrest_acl_rolling
logger.readonlyrest_acl.additivity = false

# Optional: exclude noisy service users
logger.readonlyrest_acl.filter.regex.type = RegexFilter
logger.readonlyrest_acl.filter.regex.regex = .*USR:(kibana|beat|logstash),.*
logger.readonlyrest_acl.filter.regex.onMatch = DENY
logger.readonlyrest_acl.filter.regex.onMismatch = ACCEPT
```

This configuration keeps ReadonlyREST ACL/request entries out of the main Elasticsearch log and writes them to a separate daily-rotated file instead. ReadonlyREST logs one line per incoming request, and the tech.beshu.ror logger is the logger to target for this purpose.

## Licensing

### GPLv3 License

ReadonlyREST Free (Elasticsearch plugin) is released under the GPLv3 license. For what this kind of software concerns, this is identical to GPLv2, that is, you can treat ReadonlyREST as you would treat Linux code. The big difference from Linux is that here you can ask for a commercial license and stop thinking about legal implications.

Here is a practical summary of what dealing with GPLv3 means:

#### You CAN

* Distribute for free or commercially a version (partial or total) of this software (along with its license and attributions) as part of a product or solution that is also **released under GPL-compatible license**. Please notify us if you do so.
* Use a modified version **internally to your company** without making your changes available under the GPLv3 license.
* Distribute for free or commercially a modified version (partial or total) of this software, provided that the source is contributed back as pull request to

  the [original project](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin) or publicly made available under the GPLv3 or compatible license.

#### You CANNOT

* Sell or give away a modified version of the plugin (or parts of it, or any derived work) without publishing the modified source under GPLv3 compatible licenses.
* Modify the code for a paying client without immediately contributing your changes back to this project's GitHub as a pull request, or alternatively publicly release said fork under GPLv3 or compatible license.

#### GPLv3 license FAQ

**1. Q**: I sell a proprietary software solution that already includes many other OSS components (i.e. Elasticsearch). Can I bundle also ReadonlyREST into it?

> **A**: No, GPLv3 does not allow it. But hey, no problem, just go for the [Enterprise subscription](https://readonlyrest.com/enterprise).

**2. Q**: I have a SaaS and we want to use a version of ReadonlyREST for Elasticsearch (as is, or modified), do I need a commercial license?

> **A**: No, you don't. Go for it! However if you are using Kibana, consider the [Enterprise offer](https://readonlyrest.com/enterprise) which includes multi-tenancy.

**3. Q**: I'm a consultant and I will charge my customer for modifying this software and they will not sell it as a product or part of their product.

> **A**: This is fine with GPLv3.

### Dual-license

Please don't hesitate to [contact us](mailto:info@readonlyrest.com) for a re-licensed copy of this source. Your success is what makes this project worthwhile, don't let legal issues slow you down.

See [commercial license FAQ page](/commercial) for more information.


# Audit configuration

ReadonlyREST can collect audit events containing information about a request and how the system has handled it and send them to configured outputs. Here is an example of the data points contained in each audit event. We can leverage all this information to build interesting Kibana dashboards, or any other visualization.

```json
{
    "error_message": null,
    "headers": [
      "Accept",
      "Authorization",
      "content-length",
      "Host",
      "User-Agent"
    ],
    "acl_history": "[[::LOGSTASH::->[auth_key->false]], [kibana->[auth_key->false]], [::RO::->[auth_key->false]], [::RW::->[kibana->true, indices->true, auth_key->true]]]",
    "origin": "127.0.0.1",
    "final_state": "ALLOWED",
    "task_id": 1158,
    "type": "SearchRequest",
    "req_method": "GET",
    "path": "/readonlyrest_audit-2017-06-29/_search?pretty",
    "indices": [
      "readonlyrest_audit-2017-06-29"
    ],
    "@timestamp": "2017-06-30T09:41:58Z",
    "content_len_kb": 0,
    "error_type": null,
    "processingMillis": 0,
    "action": "indices:data/read/search",
    "matched_block": "::RW::",
    "id": "933409190-292622897#1158",
    "content_len": 0,
    "logged_user": "simone",
    "presented_identity": "simone"
  }
```

## Configuration

The audit collecting by default is disabled. To enable it, you need to add `audit.enabled: true` and optionally configure the `audit.outputs`. In the `outputs` array, you can define i.a. where the audit events should be sent. The currently supported output types are:

* `index` - similarly to Logstash it writes audit events in the documents stored in the ReadonlyREST audit index
* `data_stream` - similar to index type, but the audit events are stored in the ES data stream
* `log` - it allows you to collect audit events using the Elasticsearch logs and format them with the help of features that `log4j2` enables.

You can configure multiple outputs for audit events. When the audit is enabled, at least one output has to be defined. If you omit `outputs` definition, the default `index` output will be used.

Audit can also be controlled at the block level:

* if audit is globally enabled, it is applied to all `access_control_rules` blocks by default — audit events will be generated for events pertaining to all blocks
* the audit can be optionally disabled for individual blocks, as shown in the example below for the `Kibana` block
* if audit is globally disabled, then it is disabled for all blocks, regardless of individual block settings

**⚠️IMPORTANT**: When audit is disabled for a specific block, then there will be no audit events when that block is matched.

Here is an example of how to enable audit events collecting with all defaults:

```yaml
readonlyrest:

  audit:
    enabled: true 

  access_control_rules:

   - name: Kibana
     type: allow
     auth_key: kibana:kibana
     verbosity: error
     audit: # the `audit` section is optional, by default audit is enabled for each block
       enabled: false  

   - name: "::RO::"
     auth_key: simone:ro
     kibaba:
       access: ro
```

You can also use multiple audit outputs, e.g.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs: [ index, data_stream, log ]

    ...
```

When you want to have more control over the audit outputs, the extended `outputs` format is for you. For example, you can disable given output by adding `enabled: false` to the output config:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs: 
    - type: index
    - type: log
      enabled: false # by default is true
    ...
```

The other settings, specific to the type of audit outputs, are mentioned in the next sections.

### The 'index' output specific configurations

#### Custom audit indices name and time granularity

By default, the ReadonlyREST audit index name template is `readonlyrest_audit-YYYY-MM-DD`. You can customize the name template using the `index_template` settings.

Example: tell ROR to write on the monthly index.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs: 
    - type: index
      index_template: "'custom-prefix'-yyyy-MM"  # <--monthly pattern
  ...
```

**⚠️IMPORTANT**: Notice the single quotes inside the double-quoted expression. This is the same syntax used for [Java's SimpleDateFormat](https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html).

#### Custom audit cluster

It's possible to set up a custom audit cluster responsible for storing audit events. When a custom cluster is specified, items will be sent to defined cluster nodes instead of the local one.

**⚠️IMPORTANT**: Since ROR version 1.68.0, audit events have been sent to audit nodes using a round-robin strategy. All audit nodes must belong to the same Elasticsearch cluster. Otherwise, each audit cluster will contain only a subset of audit events. If you intend to send audit events to multiple clusters, define one output per Elasticsearch cluster.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs: 
    - type: index
      cluster: ["https://user1:password@auditNode1:9200", "https://user2:password@auditNode2:9200"]
  ...
```

Setting `audit.cluster` is optional, it accepts a non-empty list of audit cluster nodes URIs.

### The 'data\_stream' output specific configurations

#### Custom audit data stream name

To change the default data stream name `readonlyrest_audit`, add the following configuration to your `readonlyrest.yml` config:

```yaml

readonlyrest:
  audit:
    enabled: true
    outputs:
      - type: data_stream
        data_stream: "custom_audit_data_stream"
```

Here, `custom_audit_data_stream` is the Elasticsearch data stream where audit events will be stored.

If the specified data stream does not exist, it will be automatically created by the ReadonlyREST plugin. This creation process includes setting up the following components, each dedicated specifically to the configured data stream:

* A dedicated Index Lifecycle Policy `({{data-stream-name}}-lifecycle-policy)`.
* Necessary index settings and mappings (component templates: `{{data-stream-name}}-mappings` and `{{data-stream-name}}-settings`).
* A customized Index Template (`{{data-stream-name}}-template`).

#### Custom audit cluster

It's possible to set a custom audit cluster responsible for audit events storage. When a custom cluster is specified, items will be sent to defined cluster nodes instead of the local one.

**⚠️IMPORTANT**: Since ROR version 1.68.0, audit events have been sent to audit nodes using a round-robin strategy. All audit nodes must belong to the same Elasticsearch cluster. Otherwise, each audit cluster will contain only a subset of audit events. If you intend to send audit events to multiple clusters, define one output per Elasticsearch cluster.

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs: 
    - type: data_stream
      cluster: ["https://user1:password@auditNode1:9200", "https://user2:password@auditNode2:9200"]
  ...
```

Setting `audit.cluster` is optional, it accepts a non-empty list of audit cluster nodes URIs.

#### Data stream settings

Here are the default settings set for the audit data stream created by the ReadonlyREST plugin:

![Audit data stream](/files/Uk1QmBkQAwkcuC0RcKEH) ![Audit data stream template](/files/AoYYPl3NMERwJxAyKbCF) ![Index lifecycle policy defaults](/files/LKnsIkUVd990bLudYUW9)

Managing Elasticsearch data streams, such as the ReadonlyREST audit data stream, should be customized based on your specific use case. Aspects like:

* data retention policies (how long to keep and when to delete data),
* migrating old indices into the new data stream,
* handling transitions between different index lifecycle phases (e.g., hot, warm, cold, delete),

depend on your business requirements, data volume and characteristics, and how the data is analyzed and used.

Therefore, we encourage you to configure these settings yourself to best fit your needs. Elasticsearch provides flexible tools, like Index Lifecycle Management (ILM), that allow automating data management based on user-defined rules. Customizing your configuration helps optimize storage costs and search performance.

You can manage and update settings related to your audit data stream directly from Kibana's **Index Management** UI.

**Steps to Change Data Stream Settings using Kibana**

1. **Open Kibana and Navigate to Index Management**
   * In Kibana, go to **Management** > **Stack Management** > **Index Management**.
   * Select the **Data Streams** tab to see the list of available data streams.
2. **Select Your Audit Data Stream**
   * Find your audit data stream (e.g., `custom_audit_data_stream`) in the list.
   * Click on it to view details such as indices backing the data stream, mappings, and lifecycle policies.
3. **Edit Index Lifecycle Policy (ILM)**
   * If you want to update rollover criteria, retention period, or other lifecycle actions:
     * Navigate to **Index Lifecycle Policies** under **Stack Management**.
     * Select the ILM policy associated with your audit data stream.
     * Modify phases such as `hot`, `warm`, or `delete` to adjust settings like maximum size, max age, or deletion timing.
     * Save your changes — they will be applied automatically to the indices backing the data stream.
4. **Update Index Template**
   * To change index settings or mappings for new backing indices:
     * Go to **Index Templates** in Stack Management.
     * Locate the template associated with your audit data stream (usually matching the data stream name or pattern).
     * Edit the template’s settings or mappings as needed.
     * Save the updated template; new indices created for the data stream will use these settings.
5. **Verify Changes**
   * After updating policies or templates, monitor your data stream to ensure rollover and retention behave as expected.
   * You can also query audit events via Kibana’s Discover tab or using the Elasticsearch API.

**Important Notes**

* Changes to lifecycle policies and index templates affect **new indices** created after the update; existing indices are not modified retroactively.
* To apply mapping changes to existing indices, you may need to reindex data.
* Ensure you carefully test ILM and template changes in a staging environment before applying to production audit streams.

#### Rolling Migration from `index` to `data_stream`

To migrate ReadonlyREST audit logging from the `index` output type to `data_stream` in a **rolling update**, follow this safe, zero-downtime approach:

1. **Add `data_stream` as an Additional Output**

Temporarily configure both `index` and `data_stream` outputs so that audit events are sent to both destinations:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
      - type: index
      - type: data_stream # add data stream output type to your config
        data_stream: "custom_audit_data_stream" 
```

> ✅ This ensures no audit logs are lost during the transition.

2. **Verify Data Stream Creation**

```
GET _data_stream/custom_audit_data_stream
```

Ensure the data stream is being created and audit events are flowing in.

3. **Monitor for Consistency**

Use Kibana or the `_search` API to confirm that events are present in both audit indices and `custom_audit_data_stream`.

4. **(Optional) Backfill Historical Data**

If you wish to migrate historical audit data from the old audit index, you can reindex it manually:

```json
POST _reindex
{
  "conflicts": "proceed",
  "source": {
    "index": "readonlyrest_audit-2025-06-07"
  },
  "dest": {
    "index": "custom_audit_data_stream",
    "op_type": "create"
  }
}
```

> ⚠️ Ensure both audit outputs have the same serializer for data consistency.

> ⚠️ Data streams are append-only — use `"op_type": "create"` to avoid overwrites.

> ⚠️ If the source index contains documents already present in the destination data stream, `"conflicts": "proceed"` will skip duplicates.

5. **Remove the `index` Output**

After confirming successful logging to the data stream from all nodes, update your config to remove the `index` output:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
      - type: data_stream
        data_stream: "custom_audit_data_stream"
```

6. **Final Verification**

Use Kibana dashboards, metrics, or direct queries to confirm that new audit events are flowing into the configured data stream.

### The 'log' output specific configurations

The `log` output uses a dedicated logger to write the audit events to the Elasticsearch log at INFO level.

To make ReadonlyREST start adding the audit events to the Elasticsearch log, all you have to do is add "log" as one of the outputs, e.g:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:      # you can use also 'outputs: [ log ]'
    - type: log  
  ...
```

#### Custom logging settings

If you want to control the logging process of audit events, you can do it via the `$ES_PATH_CONF/config/log4j2.properties`. Here is an example config with the default logger name, with a separate log file, and configured rolling:

```
appender.readonlyrest_audit_rolling.type = RollingFile
appender.readonlyrest_audit_rolling.name = readonlyrest_audit_rolling
appender.readonlyrest_audit_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}readonlyrest_audit.log
appender.readonlyrest_audit_rolling.layout.type = PatternLayout
appender.readonlyrest_audit_rolling.layout.pattern = [%d{ISO8601}] %m%n
appender.readonlyrest_audit_rolling.filePattern = readonlyrest_audit-%i.log.gz
appender.readonlyrest_audit_rolling.policies.type = Policies
appender.readonlyrest_audit_rolling.policies.size.type = SizeBasedTriggeringPolicy
appender.readonlyrest_audit_rolling.policies.size.size = 1GB
appender.readonlyrest_audit_rolling.strategy.type = DefaultRolloverStrategy
appender.readonlyrest_audit_rolling.strategy.max = 4

# Logger name, required, must be the same as the one defined in `readonlyrest.yml` audit configuration.
# If a custom logger name is not defined there, then the default logger name is "readonlyrest_audit"
logger.readonlyrest_audit.name = readonlyrest_audit
logger.readonlyrest_audit.appenderRef.readonlyrest_audit_rolling.ref = readonlyrest_audit_rolling
# set to false to use only desired appenders
logger.readonlyrest_audit.additivity = false
```

All settings are up to you. The only required entry is the logger name `logger.{your-logger-name}.name = {your-logger-name}`. The default logger name is the `readonlyrest_audit`.

If you want to set a custom logger name for the `log` output, add the `logger_name` setting for the given output:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs: 
    - type: log
      logger_name: custom-logger-name
  ...
```

## Extending audit events

The audit events are JSON documents describing incoming requests and how the system has handled them. To create such events, we use a `serializer`, which is responsible for the event's serialization and filtering. The example [event](#audit) is in default format and was produced by the default serializer (`tech.beshu.ror.audit.instances.BlockVerbosityAwareAuditLogSerializer`).

You can:

* skip serializer configuration - in that case the default is `tech.beshu.ror.audit.instances.BlockVerbosityAwareAuditLogSerializer`

  ```yaml
  readonlyrest:
    audit:
      enabled: true
      outputs:
      - type: index
  ```
* use any of the predefined serializers ([see the list of predefined serializers](#predefined-serializers))

  ```yaml
  readonlyrest:
    audit:
      enabled: true
      outputs:
      - type: index
        serializer:
          type: "static"
          class_name: "tech.beshu.ror.audit.instances.QueryAuditLogSerializer" # or any other serializer class
  ```
* use dynamic, configurable serializer - define JSON fields in ReadonlyREST settings (no implementation required, [see how to do it](#using-configurable-serializer))
* use ECS ([Elastic Common Schema](https://www.elastic.co/docs/reference/ecs)) serializer (no implementation required, [learn more about it](#using-ecs-serializer))
* implement and use your own serializer ([see how to implement a custom serializer](#custom-audit-event-serializer))

### Predefined serializers:

* `tech.beshu.ror.audit.instances.BlockVerbosityAwareAuditLogSerializer`
  * Serializes all non-`Allowed` events.
  * Serializes `Allowed` events only when the corresponding rule specifies, that they should be logged at `INFO` verbosity level.
  * Recommended for standard audit logging, where full request body capture is not required.
  * Fields included:

    ```
     match — whether the request matched a rule (boolean)  
     matched_block_names - list of names of the blocks, that were matched (both forbidden and allowed) (array of strings)
     id — audit event identifier (string)  
     final_state — final processing state (ALLOWED/FORBIDDEN/ERRORED/INDEX NOT EXIST) (string)  
     @timestamp — event timestamp (ISO-8601 string)  
     correlation_id — correlation identifier for tracing (string)  
     processingMillis — request processing duration in milliseconds (number)  
     error_type — type of error, if any (string)  
     error_message — error message, if any (string)  
     content_len — request body size in bytes (number)  
     content_len_kb — request body size in kilobytes (number)  
     type — request type (string)  
     origin — client (remote) address (string)  
     destination — server (local) address (string)  
     xff — X-Forwarded-For HTTP header value (string)  
     task_id — Elasticsearch task ID (number)  
     req_method — HTTP request method (string)  
     headers — HTTP header names (array of strings)  
     path — HTTP request path (string)  
     user — authenticated user (string) - deprecated field, please use `logged_user` or `presented_identity`
     logged_user — human-readable username (string)
     presented_identity — user identity that was presented with the request, e.g. basic auth username (string)
     impersonated_by — impersonating user, if applicable (string)  
     action — Elasticsearch action name (string)  
     indices — indices involved in the request (array of strings)  
     acl_history — access control evaluation history (string)  
     es_node_name — Elasticsearch node name (string)  
     es_cluster_name — Elasticsearch cluster name (string)  
    ```
* `tech.beshu.ror.audit.instances.QueryAuditLogSerializer`
  * Similar to the `BlockVerbosityAwareAuditLogSerializer` regarding `Allowed` event handling and included JSON fields.
  * Additionally, captures the full request body (`content` field)
  * Recommended for standard audit logging, where full request body capture is required.
* `tech.beshu.ror.audit.instances.FullAuditLogSerializer`
  * Serializes all events of all types, including all `Allowed` events, regardless of the rule verbosity.
  * Included fields are the same as for `BlockVerbosityAwareAuditLogSerializer`
  * Use this serializer, when you need complete coverage of all events.
* `tech.beshu.ror.audit.instances.FullAuditLogWithQuerySerializer`
  * Serializes all events of all types, including all `Allowed` events, regardless of the rule verbosity.
  * Included fields are the same as for `QueryAuditLogSerializer` (includes `content` field - full request body)
  * Use this serializer, when you need complete coverage of all events with full request body.

### Using configurable serializer:

Configuration should look like that:

```yaml
    readonlyrest:
      audit:
        enabled: true
        outputs:
        - type: index
          serializer:
            type: "configurable"
            verbosity_level_serialization_mode: [INFO, ERROR] # define which Allowed events will be serialized based on the rule verbosity level
            fields: # list of fields in the resulting JSON; placeholders (like {ES_NODE_NAME}) will be replaced with their corresponding values
              node_details: "{ES_CLUSTER_NAME}/{ES_NODE_NAME}"
              http_request: "{HTTP_METHOD} {HTTP_PATH}"
              tid: "{TASK_ID}"
              bytes: "{CONTENT_LENGTH_IN_BYTES}"
```

The configuration above corresponds to serialized event looking like that:

```json
  {
    "node_details": "mainEsCluster/esNode01",
    "http_request": "GET /_cat",
    "tid": 0,
    "bytes": 123
  }
```

You can also define nested structure of fields, and use fixed text, number and boolean values:

```yaml
  fields:
    tid: "{TASK_ID}"
    es_details:
      node_name: "{ES_NODE_NAME}"
      cluster_name: "{ES_CLUSTER_NAME}"
    event_details:
      custom_system_id: 12345 # example of hardcoded number value, can also be a decimal number; in this example represents some hardcoded system id
      is_dev_environment: false # example of hardcoded boolean value; in this example represents flag marking the events from development environment
      http:
        request_description: "HTTP request: {HTTP_METHOD} {HTTP_PATH}"
        request_details:
          method: "{HTTP_METHOD}"
          path: "{HTTP_PATH}"
```

The configuration above corresponds to serialized event looking like that:

```json
{
  "tid": 0,
  "es_details": {
    "node_name": "esNode01",
    "cluster_name": "mainEsCluster"
  },
  "event_details": {
    "custom_system_id": 12345,
    "is_dev_environment": false,
    "http": {
      "request_description": "HTTP request: GET /_cat",
      "request_details": {
        "method": "GET",
        "path": "/_cat"
      }
    }
  }
}
```

Available placeholders:

```
  {IS_MATCHED} — whether the request matched a rule (boolean)
  {MATCHED_BLOCK_NAMES} — list of names of the blocks that were matched, both allowed and forbidden (array of strings)
  {ID} — audit event identifier (string)
  {FINAL_STATE} — final processing state (string)
  {ECS_EVENT_OUTCOME} - final processing state, mapped to ECS-compliant values: success/failure/unknown (string)
  {TIMESTAMP} — event timestamp (ISO-8601 string)
  {CORRELATION_ID} — correlation identifier for tracing (string)
  {PROCESSING_DURATION_MILLIS} — request processing duration in milliseconds (number)
  {PROCESSING_DURATION_NANOS} — request processing duration in nanoseconds (number)
  {ERROR_TYPE} — type of error, if any (string)
  {ERROR_MESSAGE} — error message, if any (string)
  {CONTENT_LENGTH_IN_BYTES} — request body size in bytes (number)
  {CONTENT_LENGTH_IN_KB} — request body size in kilobytes (number)
  {TYPE} — request type (string)
  {REMOTE_ADDRESS} — client (remote) address (string)
  {LOCAL_ADDRESS} — server (local) address (string)
  {X_FORWARDED_FOR_HTTP_HEADER} — `X-Forwarded-For` HTTP header value (string)
  {TASK_ID} — Elasticsearch task ID (number)
  {HTTP_METHOD} — HTTP request method (string)
  {HTTP_HEADER_NAMES} — HTTP header names (array of strings)
  {HTTP_PATH} — HTTP request path (string)
  {LOGGED_USER} — human-readable username (string)
  {PRESENTED_IDENTITY} — user identity that was presented with the request, e.g. basic auth username (string)
  {IMPERSONATED_BY_USER} — impersonating user, if applicable (string)
  {ACTION} — Elasticsearch action name (string)
  {INVOLVED_INDICES} — indices involved in the request (array of strings)
  {ACL_HISTORY} — access control evaluation history (string)
  {CONTENT} — request body content (string or object)
  {ES_NODE_NAME} — Elasticsearch node name (string)
  {ES_CLUSTER_NAME} — Elasticsearch cluster name (string)
```

### Using ECS serializer:

Configuration should look like that:

```yaml
    readonlyrest:
      audit:
        enabled: true
        outputs:
        - type: index
          serializer:
            type: "ecs"
            verbosity_level_serialization_mode: [INFO, ERROR] # define which Allowed events will be serialized based on the rule verbosity level
            include_full_request_content: false # controls whether the full HTTP request body is included in the ECS audit log (http.request.body field), disabled by default
```

The configuration above corresponds to serialized event, compatible with ECS 1.6 schema, looking like that:

```json
{
  "@timestamp": "2017-06-30T09:41:58Z",
  "trace" : {
    "id" : "correlation_id_123"
  },
  "ecs" : {
    "version" : "1.6.0"
  },
  "source" : {
    "address" : "192.168.0.123"
  },
  "destination" : {
    "address" : "192.168.100.100"
  },
  "http" : {
    "request" : {
      "method" : "GET",
      "body" : {
        "bytes" : 123,
        "content" : "Full content of the request"
      }
    }
  },
  "event" : {
    "duration" : 5000000000,
    "reason" : "RRTestConfigRequest",
    "action" : "cluster:internal_ror/user_metadata/get",
    "id" : "trace_id_123",
    "outcome" : "failure"
  },
  "error" : {},
  "user" : {
    "effective" : {
      "name" : "impersonated_by_user"
    },
    "name" : "logged_user"
  },
  "url" : {
    "path" : "/path/to/resource"
  },
  "labels" : {
    "es_cluster_name" : "testEsCluster",
    "es_task_id" : 123,
    "es_node_name" : "testEsNode",
    "ror_acl_history" : "historyEntry1, historyEntry2",
    "ror_detailed_reason" : "default",
    "ror_involved_indices" : [],
    "ror_final_state" : "FORBIDDEN"
  }
}
```

The ECS schema is highly permissive and ambiguous. The ROR audit events can be mapped to ECS fields in multiple ways. If the provided ECS implementation does not suit your needs, you can define your own ECS-compliant serializer as `configurable` serializer.

The provided ECS implementation is equivalent to `configurable` serializer shown below. You can [use and adjust it as needed](#using-configurable-serializer) in the configuration.

```yaml
    readonlyrest:
      audit:
        enabled: true
        outputs:
          - type: index
            serializer:
              type: "configurable"
              verbosity_level_serialization_mode: [INFO, ERROR] # define which Allowed events will be serialized based on the rule verbosity level
              fields: 
                ecs:
                  version: "1.6.0"
                trace:
                  id: "{CORRELATION_ID}"
                url:
                  path: "{HTTP_PATH}"
                source:
                  address: "{REMOTE_ADDRESS}"
                destination:
                  address: "{LOCAL_ADDRESS}"
                http:
                  request:
                    method: "{HTTP_METHOD}"
                    body:
                      # Warning: Enabling logging of the full HTTP request body is not recommended when requests 
                      # may contain sensitive data. It can also significantly increase the size of audit log entries.
                      content: "{CONTENT}"
                      bytes: "{CONTENT_LENGTH_IN_BYTES}"
                user:
                  name: "{LOGGED_USER}"
                  effective:
                    name: "{IMPERSONATED_BY_USER}"
                event:
                  id: "{ID}"
                  duration: "{PROCESSING_DURATION_NANOS}"
                  action: "{ACTION}"
                  reason: "{TYPE}"
                  outcome: "{ECS_EVENT_OUTCOME}"
                error:
                  type: "{ERROR_TYPE}"
                  message: "{ERROR_MESSAGE}"
                labels:
                  x_forwarded_for: "{X_FORWARDED_FOR_HTTP_HEADER}"
                  es_cluster_name: "{ES_CLUSTER_NAME}"
                  es_node_name: "{ES_NODE_NAME}"
                  es_task_id: "{TASK_ID}"
                  ror_involved_indices: "{INVOLVED_INDICES}"
                  ror_acl_history: "{ACL_HISTORY}"
                  ror_final_state: "{FINAL_STATE}"
                  ror_matched_block_names: "{MATCHED_BLOCK_NAMES}"
```

### Custom audit event serializer

You can write your own custom audit events serializer class, add it to the ROR plugin class path and configure it through the YAML settings.

We provided 2 project examples with custom serializers (in Scala and Java). You can use them as an example to write yours in one of those languages.

#### Create custom audit event serializer in Scala

1. Checkout <https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin>

   `git clone git@github.com:sscarduzio/elasticsearch-readonlyrest-plugin.git`
2. Install SBT

   `https://www.scala-sbt.org/download.html`
3. Find and go to: `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-scala-serializer/`
4. Create own serializer:
   * from scratch (example can be found in class `ScalaCustomAuditLogSerializer`)
   * extending default one (example can be found in class `ScalaCustomAuditLogSerializer`)
5. Build serializer JAR:

   `sbt assembly`
6. Jar can be find in:

   `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-scala-serializer/target/scala-2.13/ror-custom-scala-serializer-1.0.0.jar`

#### Create custom audit event serializer in Java

1. Checkout <https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin>

   `git clone git@github.com:sscarduzio/elasticsearch-readonlyrest-plugin.git`
2. Install Maven

   `https://maven.apache.org/install.html`
3. Find and go to: `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-java-serializer/`
4. Create own serializer:
   * from scratch (example can be found in class `JavaCustomAuditLogSerializer`)
   * extending default one (example can be found in class `JavaCustomAuditLogSerializer`)
5. Build serializer JAR:

   `mvn package`
6. Jar can be find in:

   `elasticsearch-readonlyrest-plugin/custom-audit-examples/ror-custom-java-serializer/target/ror-custom-java-serializer-1.0.0.jar`

#### Configuration

1. mv ror-custom-java-serializer-1.0.0.jar plugins/readonlyrest/
2. Your config/readonlyrest.yml should start like this

   ```yaml
    readonlyrest:
        audit:
          enabled: true
          outputs:
          - type: index
            serializer:
              type: "static"
              class_name: "JavaCustomAuditLogSerializer" # when your serializer class is not in default package, you should use full class name here (eg. "tech.beshu.ror.audit.instances.QueryAuditLogSerializer")
   ```
3. Start elasticsearch (with ROR installed) and grep for:

   ```
    [2023-03-26T16:28:40,471][INFO ][t.b.r.a.f.d.AuditingSettingsDecoder$] Using custom serializer: JavaCustomAuditLogSerializer
   ```

## Protecting the audit index

To prevent users from modifying or deleting audit data, add a `forbid` block to your `readonlyrest.yml` that blocks write and delete actions on the audit indices.

```yaml
- name: "Protect audit index"
  type: forbid
  indices: ["readonlyrest_audit-*"]
  actions:
    - "indices:data/write/*"      # index, update, delete, bulk, *_by_query, reindex-into
    - "indices:admin/delete"      # delete the index
    - "indices:admin/close"       # close (then could be re-opened writable)
    - "indices:admin/open"
    - "indices:admin/settings/*"  # change settings (e.g. flip read-only / replicas)
    - "indices:admin/mapping/*"   # mapping/put + mapping/auto_put (NOT mappings/get — that's read)
    - "indices:admin/aliases"     # re-point / drop the audit aliases
    - "indices:admin/rollover"
    - "indices:admin/resize"      # shrink / split / clone
    - "indices:admin/forcemerge"
    - "indices:admin/freeze"
```

**Placement:** ACL blocks are evaluated top-to-bottom and the first matching block wins.

* If no user or service should be able to write to the audit index via the Elasticsearch API, place this block at the **very beginning** of the ACL. Audit events are written internally by the ReadonlyREST plugin and bypass the ACL entirely, so this does not affect audit collection.
* If some identities (e.g. a dedicated audit reader service) require access that would conflict with this rule, place the `forbid` block after their `allow` blocks but before all other allow rules.

The wildcard pattern `readonlyrest_audit-*` matches the default index name template. If you configured a custom `index_template` prefix, adjust the pattern accordingly.


# External to local groups mapping

The `groups` ACL rule accepts a list of group IDs. This rule will match on a requests in which the resolved username belongs at least to one of the listed groups. The association between usernames and groups is explicitly declared in the users section of the ACL. This is a list of usernames, and today, full wildcard patterns are also supported.

In the `users` section, each entry requires:

* an authentication rule: (I.e. one of the `auth_key_*` rules for local credentials, or `ldap_authentication`, `external_authentication`, etc)
* a list of groups within the ones precendently inserted in the groups rules of the ACL blocks
* optionally, an authorization rule (`ldap_authorization`, `groups_provider_authorization`, etc.)

When the users section's `groups` rule and the authorization rule are used together, we obtain "group mapping". That is: we are effectively mapping remote groups to local groups. There are two types of mapping available: **common** and **detailed** group mappings.

*Note:* the rule `ldap_auth` is the composition of `ldap_authentication` and `ldap_authorization`. So it can be used as a shortcut for both.

## Example

```yaml
readonlyrest:

  access_control_rules:
  - name: "Viewer block"
    indices: ["logstash-viewers*"]
    groups_any_of: ["viewers"]

  - name: "DevOps block"
    indices: ["logstash-devops*"]
    groups_any_of: ["devops"]

  [...]

  users:
  # PLAIN LOCAL GROUPS EXAMPLE
  # Local user "joe" is associated to local group "editors"
  - username: "joe"
    groups: ["editors"]
    auth_key: joe:password
    
  # COMMON GROUP MAPPING EXAMPLE
  # Externally authenticated user + authorization via external groups provider + groups common mapping
  # Users belonging to "external_group1" OR "external_group2" are authorized as "viewers" AND "editors" in the ACL.
  - username: "*"
    groups: ["viewers", "editors"]
    external_authentication: "ext1"
    groups_provider_authorization:
      user_groups_provider: "ext2"
      groups_any_of: ["external_group1", "external_group2"]
  
  # DETAILED GROUP MAPPING EXAMPLE
  # LDAP authenticated user + authorization via LDAP + groups detailed mapping (any LDAP user is valid; groups from `ldap1` are mapped to local groups) 
  # Users belonging to LDAP role `ldap_role_ops`, or any other LDAP role that matched `ldap_*_devops` pattern, will be mapped to "devops" local group 
  # AND 
  # Users belonging to LDAP `ldap_role_dev` are mapped to "developers" local group
  - username: "*"
    groups: 
      - devops: ["ldap_role_ops", "ldap_*_devops"]
      - developers: ["ldap_role_dev"]
    ldap_auth:
      name: "ldap1"
      groups_any_of: ["ldap_*_devops", "ldap_role_ops", "ldap_role_dev"]


  # DETAILED GROUP MAPPING EXAMPLE (STRUCTURED GROUPS)
  # LDAP authenticated user + authorization via LDAP + groups detailed mapping (any LDAP user is valid; groups from `ldap1` are mapped to local groups) 
  # Users belonging to LDAP role `ldap_role_ops`, or any other LDAP role that matched `ldap_*_devops` pattern, will be mapped to "devops" local group 
  # AND 
  # Users belonging to LDAP `ldap_role_dev` are mapped to "developers" local group
  - username: "*"
    groups:
    - local_group:
        id: "devops"
        name: "DevOps Group"
      external_group_ids:  ["ldap_role_ops", "ldap_*_devops"]
    - local_group:
        id: "developers"
        name: "Developers Group"
      external_group_ids: ["ldap_role_dev"]
    ldap_auth:
      name: "ldap1"
      groups_any_of: ["ldap_*_devops", "ldap_role_ops", "ldap_role_dev"]

  external_authentication_service_configs:
  - name: "ext1"
    [...]

  user_groups_providers:
  - name: ext2
    [...]

  ldaps:
  - name: ldap1
    [...]
```

As we can see, there are two blocks in our ACL:

1. `Viewer block` allows all users, which belong to `viewers` group, to access indices matching pattern `logstash-viewers*`
2. `DevOps block` allows all users, which belong to `devops` group, to access indices matching pattern `logstash-devops*`

### Common mapping example

```yml
  - username: "*"
    groups: ["viewers", "editors"]
    external_authentication: "ext1"
    groups_provider_authorization:
      user_groups_provider: "ext2"
      groups_any_of: ["external_group1", "external_group2"]
```

`viewers`, `devops`, (unused in the ACL example), `editors` and `developers` are local groups. That is, they exist only at ROR's configuration level. But ROR can also integrate with external authorization systems like an LDAP or some REST service, where we can find similar concepts to ROR groups (eg. users in LDAP can have roles assigned).

And sometimes we'd like to fulfil a requirement such as:

> Users having usernames defined by a given pattern, and having a given set of roles, should have certain given ROR internal groups assigned.

You can think about it as a mapping external groups to local ones.

Let's go back to our example. In the second element of the `users` array, we declare that:

* any user can be taken into consideration by this user definition
* a user should be authenticated by an `external_authentication` rule which uses the `ext1` service
* a user should be authorized by a `groups_provider_authorization` rule which uses the `ext2` service and such user belongs to at least one of `external_group1`, `external_group2` external groups.
* if all the above conditions are true, we can assign `viewers`, `editors` groups to the user

We have just "mapped" the external groups `external_group1`, `external_group2` returned by service `ext2` to the local ROR groups `viewers`, `editors`.

### Detailed mapping example

```yml
  - username: "*"
    groups: 
      - devops: ["ldap_*_devops", "ldap_role_ops"]
      - developers: ["ldap_role_dev"]
    ldap_auth:
      name: "ldap1"
      groups_any_of: ["ldap_role_devops", "ldap_role_ops", "ldap_role_dev"]
```

The third element of `users` array (in the example above) is similar, but we use one rule which is authentication and authorization rule at the same time (it can authenticate a user and then authorize him). And that's how we defined the following mappings:

* `ldap_role_ops` LDAP role, and any other LDAP role matching `ldap_*_devops` pattern, are mapped to `devops` ROR's local group
* LDAP role `ldap_role_dev` is mapped to `developers` ROR's local group

The "detailed" mapping offers a bit more structured approach to group mapping, and although less intuitive at first sight, it's more powerful and concise.


# FIPS mode

## What is FIPS?

According to [Wikipedia](https://en.wikipedia.org/wiki/Federal_Information_Processing_Standards)

> Federal Information Processing Standards (FIPS) are publicly announced standards developed by the National Institute of Standards and Technology for use in computer systems by non-military American government agencies and government contractors. FIPS standards are issued to establish requirements for various purposes such as ensuring computer security and interoperability and are intended for cases in which suitable industry standards do not already exist.

In short it is a thoroughly tested and verified set of standards which could be used to implement high level of security. In terms of software we are usually speaking specifically about FIPS 140-2.

ReadonlyREST uses OpenSource [BouncyCastle](https://www.bouncycastle.org) library to provide FIPS 140-2 compliant algorithms.

## Is ReadonlyREST fully compliant to FIPS 140-2?

At the moment, ReadonlyREST can be configured as FIPS compliant only from the "data in transit" standpoint. That is, the SSL encryption of the HTTP and transport interfaces. Other aspects remain to be covered:

* Making all cryptographic algorithms FIPS compliant.
* Enforcing more strict security policies across whole ROR plugin in FIPS mode.

## How to enable SSL FIPS compliance

1. Prepare keystore and truststore in BCFKS format which is FIPS compliant. Your existing JKS or PKCS12 keystore could be easily converted to BCFKS. Process is described [in this section](#how-to-convert-jkspkcs12-keystore-files-into-bcfks).

> :warning: BCFKS format is supported only when FIPS mode is enabled. It won't be recognised otherwise.

> :warning: When using FIPS mode using different password for specific keystore elements is not supported and `key_pass` configuration field is ignored.

1. Configure readonlyrest.yml to use new keystore and truststore. You will also need to add new configuration parameter `fips_mode`. Here's an example:

```
readonlyrest:
  fips_mode: SSL_ONLY
  ssl:
    enable: true
    keystore_file: "keystore.bcfks"
    keystore_pass: readonlyrest
    truststore_file: "truststore.bcfks"
    truststore_pass: readonlyrest

  ssl_internode:
    enable: true
    keystore_file: "keystore.bcfks"
    keystore_pass: readonlyrest
    truststore_file: "truststore.bcfks"
    truststore_pass: readonlyrest
```

1. In case you are using ES >= 7.10 you need to modify `$JAVA_HOME/conf/security/java.policy` file and add this section at the end of it. This is required because otherwise Elasticsearch will not be able grant to our plugin all these permissions at the JVM level.

```
grant {
  permission org.bouncycastle.crypto.CryptoServicesPermission "exportSecretKey";
  permission org.bouncycastle.crypto.CryptoServicesPermission "exportPrivateKey";
  permission java.security.SecurityPermission "getProperty.jdk.tls.disabledAlgorithms";
  permission java.security.SecurityPermission "getProperty.jdk.certpath.disabledAlgorithms";
  permission java.security.SecurityPermission "getProperty.keystore.type.compat";
  permission java.security.SecurityPermission "removeProvider.SunRsaSign";
  permission java.security.SecurityPermission "removeProvider.SunJSSE";
  permission java.io.FilePermission "${java.home}/lib/security/jssecacerts", "read";
  permission java.io.FilePermission "${java.home}/lib/security/cacerts", "read";
  permission java.security.SecurityPermission "getProperty.jdk.tls.server.defaultDHEParameters";
  permission org.bouncycastle.crypto.CryptoServicesPermission "defaultRandomConfig";
};
```

## How to convert JKS/PKCS12 keystore files into BCFKS

1. Download the [jar with bc-fips](https://repo1.maven.org/maven2/org/bouncycastle/bc-fips/1.0.2.3/bc-fips-1.0.2.3.jar) library and place it preferably in the same directory where you store keystore files to convert.
2. Open your terminal and go to directory with the keystore to convert
3. Use keytool with following parameters to perform the conversion:

```
keytool \
-importkeystore \
-srckeystore SOURCE_KEYSTORE_FILENAME  \
-destkeystore DEST_KEYSTORE_FILENAME \
-srcstoretype SOURCE_KEYSTORE_TYPE \
-deststoretype DEST_KEYSTORE_TYPE \
-srcstorepass SOURCE_KEYSTORE_PASSWORD \
-deststorepass DEST_KEYSTORE_PASSWORD \
-providerpath ./bc-fips-1.0.2.1.jar \
-provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
```

where:

* SOURCE\_KEYSTORE\_FILENAME - filename of the keystore(or truststore) that you want to convert.
* DEST\_KEYSTORE\_FILENAME - name of the output file.
* SOURCE\_KEYSTORE\_TYPE - type of keystore to convert. Must be JKS or PKCS12.
* DEST\_KEYSTORE\_TYPE - type of output keystore. Must be BCFKS.
* SOURCE\_KEYSTORE\_PASSWORD - password protecting keystore to convert.
* DEST\_KEYSTORE\_PASSWORD - password protecting output file. If you saved the bc-fips jar in a different path, remember to run it using the appropriate path instead of `./bc-fips-1.0.2.1.jar`


# Elastic Fleet

[Elastic Fleet](https://www.elastic.co/guide/en/fleet/current/fleet-overview.html) manages Elastic Agents centrally through Kibana. When Fleet is set up, it creates two kinds of dynamic Elasticsearch credentials that ReadonlyREST needs to recognize and validate:

* **Service tokens** - used by Fleet Server to authenticate with Elasticsearch. These are created by Kibana during Fleet setup and belong to Elasticsearch's built-in `elastic/fleet-server` service account.
* **API keys** - issued to each enrolled Elastic Agent. Fleet Server creates and rotates these automatically; each agent uses its own key to ship data.

Because both credential types are generated at runtime (not known in advance), they cannot be matched with static `auth_key` or `auth_key_sha256` rules. Instead, ReadonlyREST's `token_authentication` rule with `type: service-token` or `type: api-key` delegates validation to Elasticsearch, which has the ground truth for both.

## ReadonlyREST settings

```yaml
readonlyrest:
  access_control_rules:

    # 1. Kibana user - used by Kibana itself and by Fleet initialisation scripts.
    #    No action or index restriction; Kibana needs unrestricted access during
    #    Fleet setup (e.g. bootstrapping the Fleet Server service account).
    - name: "KIBANA"
      type: allow
      auth_key: kibana:kibana

    # 2. Fleet Server - authenticates using an Elasticsearch service token.
    #    ReadonlyREST validates the token against Elasticsearch's service account API.
    #    No action restriction: Fleet Server needs to call
    #    cluster:admin/xpack/security/api_key/create to issue API keys to
    #    enrolling agents.
    - name: "Fleet server"
      type: allow
      token_authentication:
        type: "service-token"
        username: "fleet"
      indices:
        - ".fleet-servers"
        - ".fleet-agents"
        - ".fleet-actions"
        - ".fleet-policies"
        - ".fleet-policies-leader"
        - ".fleet-enrollment-api-keys"

    # 3. Elastic Agents - each agent authenticates with its own API key, issued
    #    and rotated by Fleet Server. ReadonlyREST validates the key against Elasticsearch
    #    and grants access to the observability data-stream indices.
    - name: "Agents"
      type: allow
      token_authentication:
        type: "api-key"
        username: "fleet"
      indices:
        - ".apm-agent-configuration"
        - "metrics-*"
        - "traces-*"
        - "logs-*"

    # 4. Forbid direct token management - only Kibana and Fleet Server (matched
    #    above) should create or revoke service tokens and API keys. This block
    #    denies these actions for everyone else.
    - name: "Forbid access to service accounts and API keys"
      type: forbid
      actions:
        - "cluster:admin/xpack/security/service_account/*"
        - "cluster:admin/xpack/security/api_key/*"

    # 5. Admin user - full Kibana access.
    - name: "Admins"
      type: allow
      auth_key: admin:admin
      kibana:
        access: admin
```

## How Fleet credentials flow through ReadonlyREST

1. **Kibana creates a service token** - during Fleet setup, Kibana calls `cluster:admin/xpack/security/service_account/*` to create the Fleet Server service token. This request is authenticated by the `KIBANA` block.
2. **Fleet Server creates API keys** - Fleet Server uses its service token to call `cluster:admin/xpack/security/api_key/create`, issuing an API key to each enrolling agent. This request is authenticated by the `Fleet server` block.
3. **Elastic Agents use their API keys** - each agent presents its API key on every request to ship data to Elasticsearch. These requests are authenticated by the `Agents` block.

## Why the `forbid` block is necessary

Only Kibana and Fleet Server should be able to create service tokens and API keys - no other user needs these actions. The `KIBANA` and `Fleet server` blocks already permit these calls for the accounts that legitimately need them. The `forbid` block sits below those blocks and denies any remaining request that targets service-account or API-key management actions, preventing other authenticated users from creating, revoking or listing credentials.

## Credential rotation

You do not need to put service tokens or API key values into `readonlyrest.yml`. ReadonlyREST never sees or stores them - it asks Elasticsearch to validate each token on the fly. This means:

* Fleet Server can rotate its service token without any ReadonlyREST config change.
* Agents can be enrolled, unenrolled, and re-keyed without touching ReadonlyREST.
* The only things that must stay in sync with your deployment are the **index patterns** in the `service-token` and `api-key` blocks.

## Setting up Fleet Server and Elastic Agent

Configuring Fleet Server and enrolling Elastic Agents is covered in the [official Elastic Fleet documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html). APM agent setup is documented in the [APM quick-start guide](https://www.elastic.co/guide/en/apm/guide/current/apm-quick-start.html).

## Running the example

A full working example with Elasticsearch, Kibana (both with ReadonlyREST), Fleet Server, an Elastic Agent (APM), a demo Node.js app, and a traffic simulator is available in the [readonlyrest-examples](https://github.com/beshu-tech/readonlyrest-examples/tree/master/examples/fleet) repository:

```bash
curl -sL https://raw.githubusercontent.com/beshu-tech/readonlyrest-examples/master/quickstart.sh | bash -s fleet
```

Once running, log into Kibana and navigate to **Management → Fleet** to see the enrolled agent and its policy, or to **Observability → APM** for traces from the demo application.


# FLS engine

Applicable in the context of the [`fields` rule](/elasticsearch#fields)

FLS engine specifies how ROR handles field-level security internally. Previously FLS was based entirely on [Lucene](https://en.wikipedia.org/wiki/Apache_Lucene) - that's why ROR needed to be installed on all nodes to make the `fields` rule work properly. Now the `fields` rule is more flexible and part of FLS responsibilities is handled solely by ES. Increasing ES usage and reducing Lucene exploitation in FLS implementation makes the rule more efficient.

Unfortunately, a few FLS functionalities still have to be handled at the Lucene level, and cannot benefit of the new ES level implementation (see supported at ES level [requests](#ES-limitations) ) Lucene is still used by `fields` rule when ES is not able to handle a request properly (as kind of a fallback).

## Configuration

FLS engine can be configured with global, optional property `fls_engine` set under the `readonlyrest.global_settings` section.

There are two engines available:

* **es\_with\_lucene** (default)

**⚠️IMPORTANT** As Lucene is part of this engine, the ReadonlyREST plugin still needs to be installed in all the cluster nodes that contain data.

Default hybrid approach - the major part of FLS is handled by ES. Corner cases are passed to Lucene. This solution handles all requests properly being more performant than the old full Lucene-based approach.

* **es**

FLS is handled only by ES, without fallback to Lucene. When ES is not able to handle FLS properly, the `fields` rule is not matched. In the `es` engine, FLS is not available for some types of requests (requirements listed below). The major advantage of this approach is to not rely on Lucene, so **ROR doesn't need to be installed on all nodes**.

If a lack of full FLS support is unacceptable and all type of requests needs to be handled properly (rule matching, no rejection) it's advised to use a more reliable `es_with_lucene` engine.

## ES limitations

Supported by `es` FLS engine requests are:

* all Get/MGet API requests
* Search/MSearch/AsyncSearch API requests with the following restrictions:
  * not using script fields
  * the used query is one of
    * common terms
    * match bool
    * match
    * match phrase
    * match phrase prefix
    * exists
    * fuzzy
    * prefix
    * range
    * regexp
    * term
    * wildcard
    * terms set
    * bool
    * boosting
    * constant score
    * dis max
  * the defined query doesn't use wildcards in field names
  * defined compound queries using only listed above supported queries as inner queries
  * the Search request doesn't use scroll

If the request doesn't meet above requirements (e.g. it's using `query_string` or script fields), the `es` engine will reject it.

Example configuration (ROR using `es` FLS engine):

```yaml
readonlyrest:
 
 global_settings:
   fls_engine: "es"
 
 access_control_rules:

   - name: "user_using_fields"
     auth_key: user:pass
     fields: ["~someNotAllowedField"]
```

Property `fls_engine` can be omitted, then by default, ROR uses `es_with_lucene` FLS engine.


# Indices rule - Index not found scenario

Examples:

> Let's assume that our ES cluster has 2 indices: `index_a` and `index_b`. At the same time we have two users: `userA` and `userB`. We'd like to give `userA` access to index `index_a`, and `userB` to `index_b`. `userA` should not see or be even aware of `index_b` and vice versa. We'd like to give each of them a feeling that they are alone on the cluster.
>
> ROR `readonlyrest.yml` configuration may look like this:
>
> ```yaml
> readonlyrest:
>   enable: true
>   access_control_rules:
>
>      - name: "user A indices"
>        indices: ["index_a"]
>        auth_key: userA:secret
>
>      - name: "user B indices"
>        indices: ["indexB"]
>        auth_key: userB:secret
> ```
>
> We can test if `userA` is able to reach `index_a`:
>
> ```
> $ curl -v -u userA:secret "http://127.0.0.1:9200/index_a?pretty"
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 611
>    
>   {
>     "index_a" : {
>       "aliases" : { },
>       "mappings" : { ... }
>       "settings" : { ... }
>     }
>   }
> ```
>
> It looks like he is. So far, so good. Let's try to access nonexistent index (we know, that index with name `nonexistent` for sure doesn't exist on our cluster):
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/nonexistent?pretty"                                                                                             18:15:28
>   HTTP/1.1 404 Not Found
>   content-type: application/json; charset=UTF-8
>   content-length: 634
>
>   {
>     "error" : {
>       "root_cause" : [ ... ],
>       "type" : "index_not_found_exception",
>       "reason" : "no such index [nonexistent_ROR_ZA1FXDsR7M]",
>       "resource.type" : "index_or_alias",
>       "resource.id" : "nonexistent_ROR_ZA1FXDsR7M",
>       "index_uuid" : "_na_",
>       "index" : "nonexistent_ROR_ZA1FXDsR7M"
>     },
>     "status" : 404
>  }
> ```
>
> The response is pretty straight forward - the index doesn't exist. But, let's see what happens, when the same user, `userA`, will try to get `index_b`:
>
> ```
> $ curl -v -u userA:secret "http://127.0.0.1:9200/index_b?pretty"
>   HTTP/1.1 404 Not Found
>   content-type: application/json; charset=UTF-8
>   content-length: 610
>  
>   {
>     "error" : {
>       "root_cause" : [ ... ],
>       "type" : "index_not_found_exception",
>       "reason" : "no such index [index_b_ROR_QcskliAl8A]",
>       "resource.type" : "index_or_alias",
>       "resource.id" : "index_b_ROR_QcskliAl8A",
>       "index_uuid" : "_na_",
>       "index" : "index_b_ROR_QcskliAl8A"
>     },
>     "status" : 404
>   }
> ```
>
> As we can see `userA` is not able to get `index_b`. But the response is HTTP 404 Not Found - it means that the index doesn't exist.
>
> So, the response is the same as we get if the called index really doesn't exist. Thanks to the described behaviour, `userA` is not aware that on the cluster there are any other indices but the ones he was given access to.
>
> > note:
> >
> > Careful reader may notice that, in example above, `userA` was getting `index_b`, but the response says that there is no `index_b_ROR_QcskliAl8Aindex_b_ROR_QcskliAl8A` index. It's the trick ROR does to fool ES and be sure that asking index, which the user should not be allowed to see, won't be reached by him.
>
> But we should also consider the other case - using an index name with wildcard. So, `userA` will try to get all indices which names match `index*` pattern:
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/index*?pretty"                                                                                                  19:58:29
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 611
>   
>   {
>     "index_a" : {
>       "aliases" : { },
>       "mappings" : { ... },
>        "settings" : { ... }
>      }
>    }
> ```
>
> Response is exactly like we'd expect - only `index_a` was returned. But what if nothing matches our index name pattern?
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/index_userA*?pretty"                                                                                                20:05:10
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 4
>
>   { }
> ```
>
> Response is empty list. Now, let's see what happens when an index name pattern matches an index which is not authorized for a user who asks about it.
>
> ```
> $ curl -i -u userA:secret "http://127.0.0.1:9200/index_b*?pretty"                                                                                                20:14:34
>   HTTP/1.1 200 OK
>   content-type: application/json; charset=UTF-8
>   content-length: 4
>
>   { }
> ```
>
> As we see, response is the same as we have experienced when there was really no index matching the pattern. Also here a user has a feeling that only his indices are present on a cluster.


# Indices rule - ES Templates handling

<details>

<summary>A ROR configuration for all examples below (click to expand)</summary>

```yaml
  readonlyrest:

    access_control_rules:
      - name: "admin block"
        verbosity: error
        type: allow
        auth_key: admin:admin

      - name: "dev1 block"
        indices: ["idev1", "idev1_*"]
        auth_key: dev1:test

      - name: "dev2 block"
        indices: ["idev2", "idev2_*"]
        auth_key: dev2:test
```

</details>

## Index templates

An `indices` rule takes into consideration index patterns and aliases which are a part of a template definition. We should consider four types of template related requests:

### Create an index template

The request will be allowed when all of following conditions are met:

* a template with requested name does not exist (if it does, it's rather a template modification, than a creation),
* all index patterns of the new, requested template are allowed,
* all aliases of the new, requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

Let's try to add an index template. We can see, using `admin` account, that there are no templates defined yet.

```
$ curl -vk -u admin:admin "http://localhost:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "index_templates" : [ ]
  }
```

Now, let's use `dev1` user account to create an index template `temp1`:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["index*"],
     "template": {
       "aliases": { 
         "dev1_index": {},
         "dev2_index": {}
       }
     }
  }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Oh, something went wrong. It seems that, a user `dev1` is not allowed to add this template. Let's check ROR logs to figure out why:

FORBIDDEN by default req={ ID:193441275-173645661#8, TYP:PutComposableIndexTemplateAction$Request, CGR:N/A, USR:dev1 (attempted), BRS:true, KDX:null, ACT:indices:admin/index\_template/put, OA:127.0.0.1/32, XFF:null, DA:127.0.0.1/32, `IDX:index*,dev2_index,dev1_index`, MET:PUT, `PTH:/_index_template/test`, CNT:\<OMITTED, LENGTH=157.0 B> , HDR:Accept=*/*, Authorization=, Content-Length=157, Content-Type=application/json, Host=127.0.0.1:9200, User-Agent=curl/7.64.1, HIS:\[CONTAINER ADMIN-> RULES:\[auth\_key->false] RESOLVED:\[indices=index\*,dev2\_index,dev1\_index;template=ADD(test:index\*:dev2\_index,dev1\_index)]], `[dev1 block-> RULES:[auth_key->true, indices->false]` RESOLVED:\[user=dev1;indices=index\*,dev2\_index,dev1\_index;template=ADD(test:index\*:dev2\_index,dev1\_index)]], \[dev2 block-> RULES:\[auth\_key->false] RESOLVED:\[indices=index\*,dev2\_index,dev1\_index;template=ADD(test:index\*:dev2\_index,dev1\_index)]], }

We can see that our request was forbidden - credentials were OK, but `indices` rule was not matched in `dev1 block`. We can see also that ROR found 3 indices which are related to the request:

* `index*` - an index pattern from our request
* `dev1_index` - a first alias from out request
* `dev2_index` - a second alias from out request

When we take a look at indices configured in `indices` rule for our user, we can see that, he has an access only to `idev1` and `idev1_*` indices. Now, it's pretty much obvious why the request was blocked - the user has no access to index pattern and aliases used in the request. Let's try to fix that:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev1_test*"],
     "template": {
       "aliases": { 
         "idev1": {},
         "idev1_test": {}
       }
     }
  }'

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Hooray! The index template was added. This time ROR allowed us to do so. It's because `dev1` user has an access to index pattern `idev1_test*`, because it is contained in `idev1_*`. Used aliases are also allowed.

</details>

### Modify an index template

The request will be allowed when all of following conditions are met:

* a template with requested name does exist,
* all index patterns of the existing template are allowed,
* all aliases of the existing template are allowed,
* all index patterns of the requested template are allowed,
* all aliases of the requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

Let's assume the user `dev1` would like to modify the previously created template, because the index pattern is too detailed:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev*"],
     "template": {
       "aliases": {
         "idev1": {},
         "idev1_test": {}
       }
     }
   }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Ups! Something is wrong. Let's check the ROR forbidden log:

FORBIDDEN by default req={ ID:918326057-1726421783#75, TYP:PutComposableIndexTemplateAction$Request, CGR:N/A, USR:dev1 (attempted), BRS:true, KDX:null, ACT:indices:admin/index\_template/put, OA:127.0.0.1/32, XFF:null, DA:127.0.0.1/32, `IDX:idev*,idev1,idev1_test`, MET:PUT, PTH:/\_index\_template/test, CNT:\<OMITTED, LENGTH=151.0 B> , HDR:Accept=*/*, Authorization=, Content-Length=151, Content-Type=application/json, Host=127.0.0.1:9200, User-Agent=curl/7.64.1, HIS:\[CONTAINER ADMIN-> RULES:\[auth\_key->false] RESOLVED:\[indices=idev\*,idev1,idev1\_test;template=ADD(test:idev\*:idev1,idev1\_test)]], `[dev1 block-> RULES:[auth_key->true, indices->false]` RESOLVED:\[user=dev1;indices=idev\*,idev1,idev1\_test;template=ADD(test:idev\*:idev1,idev1\_test)]], \[dev2 block-> RULES:\[auth\_key->false] RESOLVED:\[indices=idev\*,idev1,idev1\_test;template=ADD(test:idev\*:idev1,idev1\_test)]], }

We can see that `indices` rule hasn't not been matched. Looking at the IDX section, we can figure out that the index pattern we requested `idev*`, cannot be allowed. `idev*` is too generic, because in the `indices` list we have `["idev1", "idev1_*"]`. Let's try to fix that:

```
$ curl -vk -u dev1:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev1_*"],
     "template": {
       "aliases": {
         "idev1": {},
         "idev1_test": {}
       }
     }
   }'

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Yeah, now it works. Let's check if the template is modified (we will use `admin` user to do so):

```
$ curl -vk -u admin:admin "http://127.0.0.1:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

All is good. We have only one template and the modifications was applied.

So far, so good. But we can wonder what happens if `dev2` will try to modify (or override) template `temp`? Let's check:

```
$ curl -vk -u dev2:test -XPUT "http://127.0.0.1:9200/_index_template/test?pretty" -H "Content-Type: application/json" -d \
  '{
     "index_patterns":["idev2_*"],
     "template": {
       "aliases": {
         "idev2": {},
         "idev2_test": {}
       }
     }
   }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Yes! This is something what we wanted like to see. Even if the request was correct and the user `dev2` has an access to the requested index pattern and aliases, the request was forbidden. Obviously, there is already existed template `temp` which has the index pattern and aliases, which are not allowed for `dev2`. ROR deduces that `dev2` cannot be considered as someone how can modify/overwrite it.

Pretty awesome. Won't `dev2` also be able to remove it? We'll see in next section ...

</details>

### Delete an index template

The request will be allowed when template does not exist OR all of the following conditions are met:

* a template with requested name does exist,
* all index patterns of the existing template are allowed,
* all aliases of the existing template are allowed.

<details>

<summary>Example (click to expand)</summary>

In the last section we wondered, if ROR will be able to block removing the template `temp` by the user `dev2`. Let's recall, that we proved that the user is not able to modify this template, because ROR considers that he doesn't have permissions to change/remove it.

```
$ curl -vk -u dev2:test -XDELETE "http://127.0.0.1:9200/_index_template/test?pretty"

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Perfect! OK, but we also would like to know if `user1` will be able to remove his template. Let's check it:

```
$ curl -vk -u dev1:test -XDELETE "http://127.0.0.1:9200/_index_template/test?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Great! Everything works.

</details>

### Get index templates

At the moment ROR doesn't have `templates` rule (similar to `snapshots` or `repositories`), which allows to restrict which templates can be visible to the user (it is going to change in the future). But the `indices` rule is enough to filter templates based on index patterns in their definitions. An index template is considered to be visible for a user, when the user has access to AT LEAST ONE index pattern of the template's index pattern list. ROR is going to show the template but to hide the information about not allowed index patterns and not allowed aliases.

<details>

<summary>Example (click to expand)</summary>

In previous sections we proved that ROR gets along with index templates adding, modifying and removing pretty well. Now, we'd like check what index templates are supposed to be visible for users. Let's assume we have 4 index templates:

```
$ curl -vk -u admin:admin "http://127.0.0.1:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "index_templates" : [
      {
        "name" : "t1",
        "index_template" : {
          "index_patterns" : ["i*"],
          "template" : {
            "aliases" : {
              "idev2" : { },
              "idev3" : { },
              "idev1" : { }
            }
          },
          "composed_of" : [ ]
        }
      },
      {
        "name" : "t2",
        "index_template" : {
          "index_patterns" : ["idev1_*"],
          "template" : {
            "aliases" : {
              "admin_idev" : { },
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 1
        }
      },
      {
        "name" : "t3",
        "index_template" : {
          "index_patterns" : ["idev2_*"],
          "template" : {
            "aliases" : {
              "idev2" : { },
              "admin_idev" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 1
        }
      },
      {
        "name" : "t4",
        "index_template" : {
          "index_patterns" : ["idev1_*", "idev2_*"],
          "template" : {
            "aliases" : {
              "idev2" : { },
              "admin_idev" : { },
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 2
        }
      }
    ]
  }
```

`admin` has unrestricted access to all templates. Now, let's check which templates `dev` are supposed to see:

```
$ curl -vk -u dev1:test "http://127.0.0.1:9200/_index_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "index_templates" : [
      {
        "name" : "t1",
        "index_template" : {
          "index_patterns" : ["i*"],
          "template" : {
            "aliases" : {
              "idev1" : { }
            }
          },
          "composed_of" : [ ]
        }
      },
      {
        "name" : "t2",
        "index_template" : {
          "index_patterns" : ["idev1_*"],
          "template" : {
            "aliases" : {
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 1
        }
      },
      {
        "name" : "t4",
        "index_template" : {
          "index_patterns" : ["idev1_*"],
          "template" : {
            "aliases" : {
              "idev1" : { }
            }
          },
          "composed_of" : [ ],
          "priority" : 2
        }
      }
    ]
  }
```

Hmm, we can see many weird things here. Let's start with the simplest case: index template `t2` is allowed for the user, because the used index pattern is allowed by `indices` rule. But we can also see that user `dev1` is not aware of existence the `admin_idev` alias - it was filter out from the aliases list. The user has no access to the alias, so he should not be able to see it.

What about the index template `t3`? `dev1` is not allowed to see it because the index pattern `idev2_*` is not allowed for him. It was also pretty much obvious!

The next is `t4`. When `admin` had listed index templates, we saw that template `t4` has 2 index patterns. But `dev1` can see only one. This is great, because he has an access to a part of that template, so he definitely should be able to see it. ROR behaviour here is pretty neat - it allows the user to see a template with filtered, not allowed parts of it, but at the same time, the user doesn't have permissions to modify/remove the template (Don't believe me? Go ahead and check!)

And the last one to explain - `t1`. The index pattern of the template is `i*`. Obviously user `dev1` has no access to it, because his allowed indices are `idev1, idev1_*`. But if we imagine all possible values generated from pattern `i*` and all possible values generated from `idev1, idev1_*`, we can notice that the latter will be a subset of the first. It means that this template can be interesting for the user `dev1`, because it will ba applied to indices created by him. That's why ROR decides to show it.

</details>

## Component templates

Component templates doesn't have index patterns but could have aliases. So, in this case, we should also consider four types of template related requests:

### Create a component template

The request will be allowed when all of following conditions are met:

* a template with requested name does not exist (if it does, it's rather a template modification, than a creation),
* all aliases of the new, requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

Unlike index templates, component templates don't have index patterns. But they still have aliases. So, their behaviour according to an aliases usage is quite similar, but there are several differences which are worth mentioning.

Let's check if `dev1` user can create a component template:

```
$ curl -vk -u dev1:test "http://localhost:9200/_component_template/ctemp1?pretty" -XPUT -H "Content-Type: application/json" -d \
  '{
     "template": {
   	   "settings": {
   	     "index.number_of_replicas": 0
   	   },
   	   "aliases": { 
   	     "idev1": {},
   	     "idev2": {}
   	   }
     }
  }'
  
  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

Oh, user `dev1` is not allowed to create this template. But wait! It looks like we have the same problem as had while creating index template. Alias `idev2` is not allowed. Let's try to do the same without this alias:

```
$ curl -vk -u dev1:test "http://localhost:9200/_component_template/ctemp1?pretty" -XPUT -H "Content-Type: application/json" -d \
  '{
     "template": {
   	   "settings": {
   	     "index.number_of_replicas": 0
   	   },
   	   "aliases": { 
   	     "idev1": {}
   	   }
     }
  }'
  
  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "acknowledged" : true
  }
```

Ha! As expected. A user has to have access to all aliases during adding a component template which has aliases defined.

*Note* If a component template doesn't involve aliases, there is no restriction from ROR side to add one. It can be changed in future, when we add sth like `templates` rule.

</details>

### Modify a component template

The request will be allowed when all of following conditions are met:

* a template with requested name does exist,
* all aliases of the existing template are allowed,
* all aliases of the requested template are allowed.

<details>

<summary>Example (click to expand)</summary>

In the previous example, user `dev1` created the component template `ctemp1` with one alias `idev1`. Let's check if user `dev2` will be able to modify it:

```
$ curl -vk -u dev2:test "http://localhost:9200/_component_template/ctemp1?pretty" -XPUT -H "Content-Type: application/json" -d \
  '{
     "template": {
   	   "settings": {
   	     "index.number_of_replicas": 0
   	   },
   	   "aliases": { 
   	     "idev2": {}
   	   }
     }
  }'

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

No. And this is a good behaviour, because `dev2` doesn't have an access to the alias `idev1` which the `ctemp1` has. ROR assumes, that he cannot modify the component template (please notice, that the same request will be allowed when a different, nonexistent component template name is used). I can assure you that `dev1` is able to modify the template (you can check if you want).

</details>

### Delete a component template

The request will be allowed when template does not exist OR all of the following conditions are met:

* a template with requested name does exist,
* all aliases of the existing template are allowed.

<details>

<summary>Example (click to expand)</summary>

If you read the previous example, you won't find anything interesting here. A component template can be removed only by someone whom ROR considers to have modification rights of the template. See that `dev2` is not able to remove `ctemp1`:

```
$ curl -vk -u dev2:test -XDELETE "http://localhost:9200/_component_template/ctemp1?pretty"

  HTTP/1.1 403 Forbidden
  content-type: application/json; charset=UTF-8

  {
    "error" : {
      "root_cause" : [
        {
          "reason" : "forbidden",
          "due_to" : ["OPERATION_NOT_ALLOWED"]
        }
      ],
      "reason" : "forbidden",
      "due_to" : ["OPERATION_NOT_ALLOWED"],
      "status" : 403
    }
  }
```

I told you. But please remember that only aliases are checked by ROR when it's trying to figure out modification rights of a component template. If a component template doesn't have any aliases, it can be modified or deleted by any user.

</details>

### Get component templates

At the moment there is no way to restrict which component templates can be visible to the user (it is going to change in the future - see a corresponding index template section). But ROR is going to hide the information about not allowed aliases of returned component templates.

<details>

<summary>Example (click to expand)</summary>

A careful reader can guess that ROR won't forbid showing component templates. But similar to indices templates, ROR will filter out aliases list depending on an aliases accessability of current user. Let's see an example:

```
$ curl -vk -u admin:admin "http://localhost:9200/_component_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "component_templates" : [
      {
        "name" : "ctemp2",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : {
              "idev2" : { }
            }
          }
        }
      },
      {
        "name" : "ctemp1",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : {
              "idev1" : { }
            }
          }
        }
      }
    ]
  }
```

We can see that we have two component templates. `ctemp1` has alias `idev1` and `ctemp2` alias `idev2`. Let check what templates `dev1` user will be able to see:

```
$ curl -vk -u dev1:test "http://localhost:9200/_component_template?pretty"

  HTTP/1.1 200 OK
  content-type: application/json; charset=UTF-8

  {
    "component_templates" : [
      {
        "name" : "ctemp2",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : { }
          }
        }
      },
      {
        "name" : "ctemp1",
        "component_template" : {
          "template" : {
            "settings" : {
              "index" : {
                "number_of_replicas" : "0"
              }
            },
            "aliases" : {
              "idev1" : { }
            }
          }
        }
      }
    ]
  }
```

We can see that he is able to see all component templates, but `ctemp2` doesn't have `idev2` alias. User `dev1` has no access to the alias, so response returned by ROR doesn't contain the alias. Similar behaviour we will observe when `dev2` user will try to get all templates.

</details>

## Troubleshooting

To figure out why the template is not returned or/and cannot be altered, you should [enable a DEBUG log level](/elasticsearch#acl-troubleshooting) and check your logs. ROR logs each step of template request handling in the `indices` rule, so detailed description should explain the given template is not allowed.


# For Kibana

User manual for ReadonlyREST Enterprise/PRO/Free

🧙 **Are you using Kibana version 7.8.x or older? Go to the** [**old platform manual page**](/kibana/kibana-7.8.x-and-older)**.**

## Overview

ReadonlyREST plugin for Kibana is not open source, and it's offered as part of the [ReadonlyREST PRO](https://readonlyrest.com/pro) and [ReadonlyREST ENTERPRISE](https://readonlyrest.com/enterprise), and [ReadonlyREST Free](https://readonlyrest.com/free) packages. See product descriptions and a comparison chart on the official [ReadonlyREST website](https://readonlyrest.com)

ReadonlyREST plugins for Kibana **always require** the ReadonlyREST open-source plugin to be installed in the Elasticsearch nodes your Kibana instance(s) will connect to.

Installation of ReadonlyREST is not required on all Elasticsearch nodes. It's mandatory to be installed only on the nodes where you intend to secure the HTTP interface.

### After purchasing

If you haven't installed it yet, download the latest [universal build](https://docs.readonlyrest.com/universal-builds) from our [download page](https://readonlyrest.com/download/) and install it manually. Alternatively, see below if you want to install it directly via the command line without downloading it from the browser.

Once the universal build plugin for Kibana is installed, you can activate it using an **activation key**. You can get one of these in the [ReadonlyREST customer portal](https://readonlyrest.com/customer) if you are a subscriber, otherwise, use the same portal to get a trial activation key (for PRO or Enterprise) for 30 days evaluation.

### Version strings

All our plugins include in their file name a version string. For example, the file `readonlyrest-1.46.0_es8.6.0.zip` has a version string `1.46.0_es8.6.0`.

#### Reading version strings

Given the version string `1.46.0_es8.6.0`

* ReadonlyREST plugin code version `1.46.0`
* Works only with Elasticsearch/Kibana version `8.6.0`

The "es" stands for "Elastic stack" which used to mean the family of products made by Elastic which get released at the same time under the same version number. This was chosen **before** Elastic renamed their X-Pack commercial offer to Elastic Stack.

To be clear, there is no affiliation between ReadonlyREST and Elastic, or their commercial products.

#### Universal Kibana plugin version strings

Our Kibana plugin file naming follows very similar rules:

I.e. `readonlyrest_kbn_universal-1.46.0_es8.6.0.zip`

* ReadonlyREST PRO plugin version 1.46.0
* Works only with Kibana version 8.6.0

### When an update is out

You will receive another email notification that a new deliverable is available.

If the update contains a security fix, it is very important that you take action and **update the plugin immediately**.

## Installation and Operations

### Running with Docker

The simplest method to run Kibana with the ReadonlyREST plugin is to use one of our docker images which you can find on [Docker Hub](https://hub.docker.com/r/beshultd/kibana-readonlyrest). In the example below we will use [Docker Compose](https://docs.docker.com/compose/):

```yaml
# docker-compose.yml file content
services:

  kbn-ror:
    image: beshultd/kibana-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports: 
      - "5601:5601"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes
      - ELASTICSEARCH_HOSTS=https://es-ror:9200
      - ELASTICSEARCH_USERNAME=kibana
      - ELASTICSEARCH_PASSWORD=kibana
      - ELASTICSEARCH_SSL_VERIFICATIONMODE=none
      - readonlyrest_kbn__cookiePass=abcd1234abcd1234abcd1234abcd1234 # this is an equivalent of the `readonlyrest_kbn.cookiePass` setting defined in kibana.yml
    depends_on:
      - es-ror

  es-ror:
    image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
    user: "0:0"
    ports:
      - "9200:9200"
    environment:
      - I_UNDERSTAND_AND_ACCEPT_ES_PATCHING=yes
      - KIBANA_USER_PASS=kibana
      - ADMIN_USER_PASS=admin
      - discovery.type=single-node

```

It can be run like this:

```bash
docker-compose up
```

It will run Kibana container with ReadonlyREST connected with the single ES node (with ReadonlyREST too). You can access Kibana by calling `http://localhost:5601` in the browser (use `admin:admin` credentials to log in).

#### Customizing ROR Kibana settings

All config options are described in the [configuration section](#configuration) below. In general, you will use the `kibana.yml` file to configure ROR Kibana settings. But in the case, of the ROR Docker image, you can pass any ROR settings as ENV - just remember to replace `.` (dot) with `__` (double underscore). E.g. to configure `readonlyrest_kbn.store_sessions_in_index: true` pass `readonlyrest_kbn__store_sessions_in_index=true` ENV.

### Installation

You can install this as a normal Kibana plugin using the `bin/kibana-plugin` utility. Let's see the two ways to use this utility with ReadonlyREST.

{% hint style="warning" %}
**Don't forget**

After Kibana 7.9.x, it's necessary to [patch](#patching-kibana) Kibana after you install, otherwise ReadonlyREST will NOT work.
{% endhint %}

#### Installing via URL

This installation method is more practical if your Kibana server is connected to the internet.

Please note that this will always download the latest version of Kibana plugin available for the current supported Elasticsearch version.

```bash
bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_universal&email=<your_email_address>" # ReadonlyREST Universal Kibana plugin
```

If you want to download the latest version of the plugin for a specific version of Kibana, then use the query parameter `esVersion` to specify your required Kibana version.

```bash
bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_universal&esVersion=7.6.1&email=<your_email_address>"
```

If you want to download an older version of the plugin for a specific version of Elasticsearch, then use the query parameter `pluginVersion` along with `esVersion`. Please note that you can only go so far back with plugin versions. [Let us know](https://readonlyrest.com/contact) if you can't download a specific one.

```bash
bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_universal&esVersion=8.6.0&pluginVersion=1.46.0&email=<your_email_address>"
```

It's possible to add an extra query parameter (`checksum=true`) to any download URL to obtain a `sha1` checksum of the corresponding deliverable. For example:

```bash
curl -vvv  "https://portal.readonlyrest.com/download/kbn?esVersion=8.6.0&pluginVersion=1.46.0&email=your@emailaddress.com&edition=kbn_universal&checksum=true" 
[...]
curl -vvv  "https://portal.readonlyrest.com/download/es?esVersion=8.6.0&pluginVersion=1.46.0&checksum=true" 
[...]
```

Now you are ready to [patch Kibana](#patching-kibana).

#### Installing from a zip file

```bash
bin/kibana-plugin install file:///home/user/downloads/readonlyrest_kbn-X.Y.Z_esW.Q.U.zip
```

Notice how we need to type in the format `file://` + absolute path (yes, with three slashes).

#### Patching Kibana

If you are using Kibana 7.9.x or newer, you need **an extra post-installation step**. This will slightly modify some core Kibana files.

**Before Kibana 8.15.0**

```bash
node/bin/node plugins/readonlyrestkbn/ror-tools.js patch
```

**For Kibana 8.15.0 and never**

**For Linux**

```bash
node/glibc-217/bin/node plugins/readonlyrestkbn/ror-tools.js patch
```

**For macOS**

```bash
node/default/bin/node plugins/readonlyrestkbn/ror-tools.js patch
```

**For Windows**

```shell
node\default\node plugins\readonlyrestkbn\ror-tools.js patch
```

**Patching Kibana acknowledgment in a silent mode**

To apply patches in Kibana using a script in non-interactive mode (bypassing prompts), you have two options:

* Using `--I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes` Script Argument:

```shell
node/bin/node plugins/readonlyrestkbn/ror-tools.js patch --I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes # This example applies to Kibana before 8.15.0. Be sure to use the correct Node.js path based on the Kibana version and your operating system.
```

* Using environment variable:

Define `I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes` env variable and run patching script as usual

#### Unpatching Kibana

If you are using Kibana 7.9.x or newer, you need **an extra pre-uninstallation step**. This will restore the core Kibana files to the original state.

**Before Kibana 8.15.0**

```bash
node/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch
```

**For Kibana 8.15.0 and never**

**For Linux**

```bash
node/glibc-217/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch
```

**For macOS**

```bash
node/default/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch
```

**For Windows**

```shell
node\default\node plugins\readonlyrestkbn\ror-tools.js unpatch
```

#### Configuring Kibana

For the activation key persistence after upgrading the license to PRO or Enterprise edition, From readonlyREST version 1.51.0 `readonlyrest_kbn.cookiePass` is a required `kibana.yml` config parameter. It needs to be configured also in case of a free license.

#### Uninstalling

{% hint style="info" %}
To uninstall, you should unpatch Kibana first, then uninstall the ReadonlyREST plugin. However, **the Kibana plugin system uninstallation process is highly unreliable**.

So we highly recommend throwing away the entire Kibana directory and starting from scratch. Ideally, use ephemeral docker containers.

Need inspiration? Try the [ROR Docker demo](https://github.com/sscarduzio/ror-docker-demo)!
{% endhint %}

To bring Kibana to its pre-patching original state, it's possible to unpatch.

**Before Kibana 8.15.0**

```bash
node/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

**For Kibana 8.15.0 and never**

**For Linux**

```bash
node/glibc-217/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

**For macOS**

```bash
node/default/bin/node plugins/readonlyrestkbn/ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

**For Windows**

```shell
node\default\node plugins\readonlyrestkbn\ror-tools.js unpatch

bin/kibana-plugin remove readonlyrestkbn
```

And the classic uninstall command...

```bash
bin/kibana-plugin remove readonlyrest_kbn
```

#### Upgrading Kibana

The ReadonlyREST plugin version must always match the currently installed Kibana version. As a result, if you want to upgrade Kibana with ROR plugin installed:

1. Before upgrading Kibana, unpatch and uninstall the ReadonlyREST plugin according to the instructions:
   * [Unpatch Kibana](#unpatching-kibana)
   * [Uninstall the plugin](#uninstalling)
2. Upgrade Kibana.
3. After upgrading Kibana, install the matching version of the ReadonlyREST plugin and patch according to the instructions:
   * [Install matching plugin version](#installation)
   * [Patch Kibana](#patching-kibana)

{% hint style="warning" %}
Upgrading Kibana without following the instructions above may cause corruption of the Kibana installation and inability to patch the upgraded version.
{% endhint %}

#### Upgrading ReadonlyREST plugin

To upgrade to a new version of ReadonlyREST plugin for Kibana, you should:

* [Unpatch Kibana](#unpatching-kibana)
* [Uninstall](#uninstalling) the old plugin
* [Install](#installation) the new one
* [Patch Kibana](#patching-kibana)
* Restart Kibana

#### Major version upgrades when using multi-tenancy

If you use multi-tenancy (Enterprise only), you will have one or more tenancy-specific Kibana indices beyond the main `.kibana` (e.g. `.kibana_tenant1`, `.kibana_tenant2`, etc.).

The first time you run Kibana after a major version upgrade (e.g. upgrading from Kibana 7.17.7 to Kibana 8.0.0), Kibana will run a [saved objects migration](https://www.elastic.co/guide/en/kibana/current/saved-object-migrations.html) on the default `.kibana` index, or whatever it finds configured as `kibana.index` in `kibana.yml`.

Now, because you may have multiple Kibana indices containing saved objects, you should apply the "saved object migration" to those indices as well.

ReadonlyREST Enterprise will automatically make sure a tenancy index is migrated to satisfy the current Kibana version **right before every time it's being used.**

For example, after a tenant logs in, before the Kibana session is started, or when a user changes tenancy with the tenancy switcher, the tenancy index gets created if absent, checked and migrated if necessary. These logs mean that migration started correctly:

```
  [savedobjects-service] Waiting until all Elasticsearch nodes are compatible with Kibana before starting saved objects migrations...
  [savedobjects-service] Starting saved objects migrations
  [savedobjects-service] [.kibana] INIT -> CREATE_NEW_TARGET. took: 27ms.
  [savedobjects-service] [.kibana_task_manager] INIT -> CREATE_NEW_TARGET. took: 29ms.
  [savedobjects-service] [.kibana_task_manager] CREATE_NEW_TARGET -> MARK_VERSION_INDEX_READY. took: 82ms.
  [savedobjects-service] [.kibana] CREATE_NEW_TARGET -> MARK_VERSION_INDEX_READY. took: 95ms.
  [savedobjects-service] [.kibana_task_manager] MARK_VERSION_INDEX_READY -> DONE. took: 23ms.
  [savedobjects-service] [.kibana_task_manager] Migration completed after 135ms
  [savedobjects-service] [.kibana] MARK_VERSION_INDEX_READY -> DONE. took: 20ms.
  [savedobjects-service] [.kibana] Migration completed after 143ms
```

Now Kibana will have migrated the tenancy index, like it did with the main `.kibana` index.

#### Using ROR with a reverse proxy

ROR - just like Kibana itself - is meant to be used either with a proxy or without one.

* If you decide to set the `server.basePath` property in `kibana.yml` and set `server.rewriteBasePath` into a `true`, ROR will be accessed directly and via a reverse proxy,
* If you decide to rewrite the base path manually by your reverse proxy and set the `server.rewriteBasePath` property in `kibana.yml` into a `false`, be sure to access ROR via a proxy, as it will not work properly when accessed directly.

## Configuration

ReadonlyREST for Kibana is almost entirely remote-controlled from the Elasticsearch configuration. Login credentials, hidden Kibana apps, etc. are all going to be configured from the Elasticearch side via the usual "rules". This means the configuration will be kept all in one place and if you used ReadonlyREST before, it will be also very familiar.

### ROR Settings in kibana.yml

* `readonlyrest_kbn.logLevel: <trace|debug|info|error|warn>`: for extra visibility set debug or (rarely) trace. Keep in mind `trace` could leak secrets into logs, so be careful.
* `readonlyrest_kbn.logPrettyPrintEnabled: true|false`: if you want to see pretty-printed or compact logs.
* [session configuration](#session-configuration)
* [UI customisation](#login-screen-tweaking)
* [custom middleware](#custom-middleware)

> In this document, every time you will encounter references to "readonlyrest.yml" or "elasticsearch.yml", we will be referring to the configuration files **in the Elasticsearch plugin** (our Kibana plugins do not need a "readonlyrest.yml").

In general, by design, we tend to concentrate all configuration within the main plugin (the Elasticsearch one) as much as possible.

### Kibana configuration

Activate authentication for the Kibana server: let the Kibana daemon connect to Elasticsearch using one of the following methods:

* a pair of credentials defined in `readonlyrest.yml` (see above, the ::KIBANA-SRV:: block).
* [a service account token](https://www.elastic.co/guide/en/elasticsearch/reference/current/service-accounts.html#service-accounts-tokens) generated for Kibana, defined in `readonlyrest.yml` (see above, the ::KIBANA-SRV-TOKEN:: block). Open up `conf/kibana.yml` and add the following:

```yaml
# Kibana server use the ::KIBANA-SRV:: basic auth credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"

# Kibana server use the ::KIBANA-SRV-TOKEN:: token value (without the bearer scheme)
# use the following setting instead of the 'elasticsearch.username' and the 'elasticsearch.password'
# elasticsearch.serviceAccountToken: AAEAAWVsYXN0aWMva2liYW5hL3Rva2VuXzE6MVhQUXRubWhRd3FxUmlzNmhFVVZQdw

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

And of course, also make sure `elasticsearch.url` points to the designated Elasticsearch instance (check also the http or https)

### Cluster-wide Settings VS readonlyrest.yml

([PRO](https://readonlyrest.com/pro))

Our Kibana plugins introduce a "ReadonlyREST" Kibana app. From here, you can edit the security settings of the whole Elasticsearch cluster, and they will take effect within 10 seconds in all Elasticsearch cluster nodes without the need to restart them.

When you change the security settings from the Kibana app, they will be saved in a special index called ".readonlyrest", so all the Elasticsearch nodes will pick them up. You can customize the name of the index by setting `readonlyrest.settings_index: .my_custom_readonlyrest` in the `elasticsearch.yml` file (remember to set the same value for all your ES nodes).

When an Elasticsearch node restarts, the order of settings evaluation is the following: 1. Attempt to find valid settings in readonlyrest.yml 2. If none is found, look inside elasticsearch.yml 3. Once successfully bootstrapped using file-based settings, attempt to read ".readonlyrest" index 4. If the index exists and contains valid settings, override file-based settings with the ones from the index. 5. Pressing "save" in the cluster-wide settings app, will **not overwrite the readonlyrest.yml** file.

Best practices:

* Build and update your production security settings from the Kibana app (will be saved in index)
* Protect the ".readonlyrest" Kibana index with an ACL rule

#### Loading settings: order of precedence

As you read, there are two possible places where the settings can be read from:

* `readonlyrest.yml` a file the user needs to create in the same directory where `elasticsearch.yml` is found.
* `.readonlyrest` index. Our Kibana plugins' GUI (PRO/Enterprise) is programmed to write this index.

When the ES plugin boots up, it follows some logic to evaluate where to read the YAML settings from. The following diagram shows how that works.

![config loading diagram](/files/DZiTCDxxEXDAFtUje4ik)

#### Malformed in-index settings

If for some reason the in-index settings get corrupted and ROR can't parse them, then neither settings from file or in-index settings can be loaded, so ES can't start. In this case, ES would print a message like:

```
Loading ReadonlyREST settings from index failed: Settings config content is malformed. Details: while scanning a quoted scalar
 in 'reader', line 9, column 17:
          auth_key: "admin:container
                    ^
```

To recover from this state, set `readonlyrest.force_load_from_file: true` in `elasticsearch.yml` on one node `es1`.

Example recovery settings:

elasticsearch.yml

```yaml
[...]
readonlyrest:
  force_load_from_file: true
```

readonlyrest.yml

```yaml
readonlyrest:

  access_control_rules:
  - name: "::ADMIN recover::"
    auth_key: admin:dev
    indices: ["*"]
```

Then remove the in-index settings index manually.

```bash
curl -X DELETE "admin:dev@es1:9200/.readonlyrest?pretty"
```

Now you can restore your settings to `readonlyrest.yml`, remove `readonlyrest.force_load_from_file: true` `from elasticsearch.yml` and restart the node.

### Example: multiuser ELK

This configuration will work in PRO and Enterprise editions. This is a typical example of a configuration snippet to add at the end of your `readonlyrest.yml` (the settings file of the Elasticsearch plugin), to support ReadonlyREST PRO.

```yaml
readonlyrest:

    access_control_rules:

    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["logstash-*"]

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

   #  use the following block instead of the `::KIBANA-SRV::` block if you use service account tokens (see https://www.elastic.co/guide/en/elasticsearch/reference/current/service-accounts.html)
   #
   #- name: "::KIBANA-SRV-TOKEN::"  
   #  token_authentication:
   #    token: "Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2VuXzE6MVhQUXRubWhRd3FxUmlzNmhFVVZQdw" # generated token for Kibana
   #    username: kibana

    - name: "::RO::"
      auth_key: ro:dev
      indices: ["logstash-*"]
      kibana:
        access: ro
        hide_apps: [ "Security", "Enterprise Search"]

    - name: "::RW::"
      auth_key: rw:dev
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: [ "Security", "Enterprise Search"]


    - name: "::ADMIN::"
      auth_key: admin:dev
      # KIBANA ADMIN ACCESS NEEDED TO EDIT SECURITY SETTINGS IN ROR KIBANA APP!
      kibana:
        access: admin

    - name: "::WEBSITE SEARCH BOX::"
      indices: ["public"]
      actions: ["indices:data/read/*"]
```

### Very important

#### ACL blocks ordering matters

> Blocks related to the authentication of the users should be at the top of the ACL

One of the most common mistakes is forgetting that the ACL blocks are evaluated in order from the first to the last.

So, some requests with credentials can be let through from one of the first blocks and come back to Kibana with no user identity metadata associated.

Take this example of a troublesome ACL:

```yaml
    # PROBLEMATIC SETTINGS (EXAMPLE) ⚠️

    access_control_rules:

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin
```

The user will be able to login because the login request will be allowed by the first ACL block. But the ACL will not have resolved any metadata about the user identity (credentials checking was ignored)!

This means the response to the Kibana login request will contain no user identity metadata (username, hidden apps, etc) and ReadonlyREST for Kibana won't be able to function correctly.

The solution to this is to reorder the ACL blocks, so the ones that authenticate Kibana users are on the top.

```yaml
    # SOLUTION: KIBANA USER AUTH RELATED BLOCKS GO FIRST! ✅👍

    access_control_rules:

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]
```

### SSL/TLS server

You can configure Kibana with the ReadonlyREST plugin to accept SSL connection the same way you would with vanilla Kibana configuration. For example, in `kibana.yml`:

```yaml
server.ssl.enabled: true
server.ssl.keystore.path: "/usr/share/kibana/config/certificates/kibana-server.p12"
server.ssl.keystore.password: ""
server.ssl.supportedProtocols: ["TLSv1.2", "TLSv1.3"]
```

#### Secure cookies

ReadonlyREST will set the "secure" flag to its Kibana session cookie ("ror-cookie") automatically when SSL is enabled in Kibana.\
\
This is because modern browsers like Chrome won't accept "secure"-flagged cookies if the website is not HTTPS.

However, a common situation is when SSL is configured in a reverse proxy (SSL termination): so the browser will interact with Kibana using HTTPS. But because ROR doesn't know it, it will still serve session cookies without the "secure" flag.\
\
In this case, you can force ReadonlyREST to create "secure"-flagged cookies by adding this line in `kibana.yml`:

```yaml
xpack.security.secureCookies: true 
```

### Load balancers

These features will work with all ReadonlyREST Editions

#### Enable health check endpoint

Normally a load balancer needs a health check URL to see if the instance is still running, you can whitelist this Kibana path so the load balancer avoids a redirection to `/login`.

Edit `kibana.yml`

```
readonlyrest_kbn.whitelistedPaths: [".*/api/status$"]
```

#### Session management with multiple Kibana instances

Each Kibana node stores user sessions in memory. This will cause problems when using multiple Kibana instances behind a load balancer (especially without sticky sessions), as there would be no synchronization between nodes' session cache. To avoid this, session synchronization via an Elasticsearch index should be enabled. Follow these steps:

1. Come up with a string of at least 32 characters length or more to be used as the shared cookie encryption key, called `cookiePass`.
2. Open up `conf/kibana.yml` and add:
   * `readonlyrest_kbn.cookiePass: "generatedStringIn1step"` (example: "12345678901234567890123456789012")
   * `readonlyrest_kbn.cookieName` (custom cookie name - this property is optional; if not specified default cookie name would be `rorCookie`)
   * `readonlyrest_kbn.store_sessions_in_index: true` (enable session storage in index)
   * `readonlyrest_kbn.sessions_index_name: "someCustomIndexName"` (index name - this property is optional; if not specified default index would be `.readonlyrest_kbn_sessions`)
   * `readonlyrest_kbn.sessions_refresh_after: 5000` (time in milliseconds, describes how often sessions should be fetched from ES and refreshed for each node - optional, by default 2 seconds)
   * `readonlyrest_kbn.sessions_probe_interval_seconds: 120` (default 60s) how often should the browser poll Kibana to check if their session is still valid. Raise this value if you connect to Kibana through slow networks (i.e. VPN), or have very slow-loading dashboards.
3. Add the above config in all Kibana nodes behind the load balancer, and restart them.

{% hint style="warning" %}
From ReadonlyREST version 1.51.0 `readonlyrest_kbn.cookiePass` is a required `kibana.yml` config parameter.
{% endhint %}

### Session Configuration

#### Session timeout

When a user logs in, ReadonlyREST writes an encrypted cookie in the browser. The session lifetime can be configured with the following key in `kibana.yml`:

```yaml
readonlyrest_kbn.session_timeout_minutes: 480 # defaults to 4320 (3 days)
```

This is a sliding inactivity window — each user action resets the clock.

#### Automatic Session cleanup

All expired Index or In-memory sessions, determined by an `expiresAt` date that falls prior to the current time and date, will be systematically cleaned. The parameters for this automated session cleanup procedure can be adjusted within the `kibana.yml` configuration file.

```yaml
readonlyrest_kbn.sessions_cleanup_interval: '1h' # Default to 1d 
```

**Automatic Session cleanup options**

You can defines interval as:

| Value | Description | Example |
| ----- | ----------- | ------- |
| s     | seconds     | "1s"    |
| m     | minutes     | "1m"    |
| h     | hours       | "1h"    |
| d     | days        | "1d"    |

#### Clearing session history

By default, all session data (search history, dev tool command history, etc.) is wiped from the browser whenever a new user logs in or a user changes tenancy. To override this behavior:

```yaml
readonlyrest_kbn.clearSessionOnEvents: ["never"]
```

Possible values: `"login"`, `"tenancyHop"`, `"never"`.

#### Cookie settings

ReadonlyREST sets the following security flags on every session cookie:

| Flag       | Default                   | Notes                                                                                                                                                             |
| ---------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HttpOnly` | `true` (always)           | Cannot be disabled.                                                                                                                                               |
| `Secure`   | `true` when TLS is active | Automatically enabled when Kibana is configured with SSL, or when `readonlyrest_kbn.cookies.secure: true` is set explicitly (required for NGINX SSL termination). |
| `SameSite` | `Lax`                     | Configurable to `Strict` (recommended for management interfaces) or `None`.                                                                                       |

The following cookie attributes are configurable in `kibana.yml`:

| Setting                             | Default      | Notes                                                                                                                                                   |
| ----------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `readonlyrest_kbn.cookieName`       | `rorCookie`  | Name of the session cookie.                                                                                                                             |
| `readonlyrest_kbn.cookiePass`       | *(required)* | Minimum 32-character secret used to encrypt the cookie. Required since ROR 1.51.0.                                                                      |
| `readonlyrest_kbn.cookies.secure`   | auto         | Set to `true` to force the `Secure` flag when SSL is terminated at a reverse proxy (i.e. Kibana runs over plain HTTP internally).                       |
| `readonlyrest_kbn.cookies.sameSite` | `Lax`        | Controls the `SameSite` attribute. Accepted values: `strict`, `lax`, `none`. Use `none` together with `secure: true` for cross-domain iframe embedding. |

Example `kibana.yml`:

```yaml
readonlyrest_kbn.cookieName: rorCookie
readonlyrest_kbn.cookiePass: <minimum-32-character-secret>
readonlyrest_kbn.cookies.sameSite: strict   # lax | strict | none
readonlyrest_kbn.cookies.secure: true       # explicit override for reverse-proxy setups
```

The `HttpOnly` flag is always set and cannot be disabled. The `Secure` flag is set automatically when Kibana is configured with SSL.

***

### Terminate Kibana on ES high-watermark

When enabled, Kibana will exit if the connected Elasticsearch cluster reports a disk high‑watermark condition. This is useful to prevent Kibana from running in a degraded state when Elasticsearch is unable to allocate shards due to insufficient disk space.

```yaml
# kibana.yml
# If set to true, Kibana will exit when Elasticsearch reports a disk high-watermark condition.
readonlyrest_kbn.diskThresholdVerificationEnabled: false  # default: true
```

## Authentication

ReadonlyREST Kibana supports several methods for authenticating users at the Kibana layer. When a user logs in through one of these methods, Kibana establishes a session and forwards the verified identity to Elasticsearch. On the Elasticsearch side, ReadonlyREST must be configured to trust this forwarded identity using the appropriate rule:

| Kibana authentication method     | Required ROR ES rule                                                                                                                                                                 |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Proxy Auth                       | [`proxy_auth`](/elasticsearch#proxy_auth)                                                                                                                                            |
| SAML / OIDC                      | [`ror_kbn_authentication`](/elasticsearch#ror_kbn_authentication), [`ror_kbn_authorization`](/elasticsearch#ror_kbn_authorization), or [`ror_kbn_auth`](/elasticsearch#ror_kbn_auth) |
| Standard login form (Basic auth) | No Kibana-level auth config needed — handled directly in ROR ES via `auth_key`, `ldap_authentication`, etc.                                                                          |

This is why SAML and OIDC sections below each contain an "Elasticsearch side" configuration step: the two plugins must share a secret so the identity can flow securely between them.

### Proxy Auth

This feature will work in all ReadonlyREST editions.

ROR for Elasticsearch can delegate authentication to a reverse proxy which will enforce some kind of authentication, and pass the successfully authenticated user's name inside an `X-Forwarded-User` header.

> Today, it's possible to skip the regular ROR login form and use the "delegated authentication" technique in ROR for Kibana as well.

1. Configure ROR for ES to expect delegated authentication (see [`proxy_auth` rule](/elasticsearch#proxy_auth)) in ROR for ES documentation.
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.proxy_auth_passthrough: true`

Now ROR for Kibana will **skip the login form entirely**, and will only require that all incoming requests must carry an `X-Forwarded-User` header containing the user's name. Based on this identity, ROR for Kibana will build an encrypted cookie and handle your session normally.

#### Custom Logout link

This feature will work in all ReadonlyREST editions.

Normally, when a user presses the logout button in ROR for Kibana, it deletes the encrypted cookie that represents the user's identity and the login form is shown.

However, when the authentication is delegated to a proxy, the logout button needs to become a link to some URL capable to unregister the session a user-initiated within the proxy.

For this, ROR for Kibana offers a way to customize the logout button's URL:

1. Find a link that will delete the reverse proxy's user session
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_logout_link: https://..../logout`

Now users who gained a session through delegated auth can also click on the logout button in ROR for Kibana and actually exit their session.

#### Custom Login link

This feature will work in all ReadonlyREST editions.

When you delegate authentication to an external service, you can tell ReadonlyREST to skip the classic login form entirely and redirect users to your proxy or identity provider's login screen.

To enable this:

1. Find your authentication proxy or identity provider login URL for the ROR app
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_login_link: "https://../login"`

The advantage of this approach is a streamlined user experience for users that login with an external IdP. The disadvantage is that you give up the possibility to log in as a local user in ROR, as the login form will be always skipped.

#### Caveat

Enabling proxy auth passthrough will relax the requirement to provide a password. Therefore, don't enable this option if you don't make sure Kibana can **only be accessed through the reverse proxy\***.

### JWT Token Forwarding as URL Query Parameter

This feature will work in all ReadonlyREST editions.

As an alternative to typing in credentials in the standard login form, it is possible to create an authenticated Kibana session by passing a JWT token as a query parameter in a URL.

#### Configuration

To enable this feature in ReadonlyREST, you need to:

* Have JWT authentication configured in ReadonlyREST (modifying `readonlyrest.yml` or the cluster-wide settings UI in the Kibana plugin). [See how](/elasticsearch#json-web-token-jwt-auth).
* Specify the query parameter name in `kibana.yml` by adding the line `readonlyrest_kbn.jwt_query_param: "jwt"` as a string, in our case "jwt".

#### In Action

Once Kibana is restarted, you will be able to navigate to a link like this:

```
http://kibana:5601/login?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

The following will happen:

1. The Kibana plugin will forward the JWT token found in the query parameter into the `Authorization` header in a request to Elasticsearch.
2. Elasticsearch will cryptographically authenticate and resolve the user's identity from the JWT claims.
3. Kibana will write an encrypted cookie in your browser and use that from now on for the length of the authenticated session. From here onwards, the session management will be identical to the normal login form flow.
4. When the user presses logout, Kibana will delete the cookie and redirect you to the login form, or whatever link you configured as `readonlyrest_kbn.custom_logout_link`.

**Deep linking with JWT**

Because the identity is embedded in the link, and ReadonlyREST is able to authenticate the request on the fly, the JWT authentication can be used in conjunction with the `nextUrl` query parameter for sharing deep links inside Kibana apps.

**Anatomy of a JWT deep link**

```
http://kibana:5601/login?jwt=<the-token>&nextUrl=urlEncode(<kibana-path>)
```

In JavaScript one can compose a JWT deep link as follows:

```javascript
var absoluteKibanaPath = '/app/kibana#/visualize/edit/28dcde30-2258-11e8-82a3-af58d04b3c02?_g=()';

var url = 'http://kibana:5601/login?jwt=' + 
           jwtToken + 
           '&nextUrl=' + 
           encodeURI(absoluteKibanaPath);

console.log("Final JWT deep link: " + url)
```

The result may look something like this:

```
http://localhost:5601/login?nextUrl=%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2F28dcde30-2258-11e8-82a3-af58d04b3c02%3F_g%3D%28%29&jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

### Embedding Kibana Dashboard or Visualization with an iframe and JWT Authentication

([PRO](https://readonlyrest.com/pro))

You have the option to embed visualizations and dashboards inside iframes. For more information, refer to the [official Elastic documentation](https://www.elastic.co/guide/en/kibana/current/reporting-getting-started.html#embed-code).

To add JWT authentication, modify the iframe `src` attribute as follows:

Original iframe `src`:

```html
<iframe src="https://localhost:5601/s/default/app/dashboards#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f?embed=true&_g=()&_a=()" height="600" width="800"></iframe>
```

Modified iframe `src` with JWT:

```html
<iframe src="https://localhost:5601/s/default/app/dashboards?jwt=<the-token>#/view/722b74f0-b882-11e8-a6d9-e546fe2bba5f?embed=true&_g=()&_a=()" height="600" width="800"></iframe>
```

Replace with your actual JWT token to enable authentication.

{% hint style="info" %}
For a cross-domain iframe, you need to set the cookie sameSite: none and secure: true. You can do this via the kibana.yml configuration file by setting `readonlyrest_kbn.cookies.secure: true` and `readonlyrest_kbn.cookies.sameSite: 'none'`.
{% endhint %}

### SAML

([Enterprise](https://readonlyrest.com/enterprise))

ReadonlyREST Enterprise supports service provider-initiated via SAML. This connector supports both SSO (single sign-on) and SLO (single log out). Here is how to configure it.

#### Configure ReadonlyREST ES bridge

In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugins with a shared secret, that is: an arbitrarily long string.

#### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    # ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

**⚠️IMPORTANT** Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

#### Kibana side

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  saml_serv1:
    enabled: true
    type: saml
    issuer: ror
    buttonName: "Partner's SSO Login"
    entryPoint: 'https://my-saml-idp/saml2/http-post/sso' # <-- identity Provider's URL, to request to sign on
    kibanaExternalHost: 'my.public.hostname.com' # <-- public URL used by the Identity Provider to call back Kibana with the "assertion" message
    protocol: http # <-- is the Kibana server listening for "http" "https" connections? Default: http
    usernameParameter: 'nameID'
    groupsParameter: 'memberOf'
    logoutUrl: 'https://my-saml-idp/saml2/http-post/slo'
    cert: /etc/ror/integration/certs/dag.crt # <-- It can be also provided a string value 
    
    # OPTIONAL, advanced parameters
    # decryptionCert: /etc/ror/integration/certs/pub.crt
    # decryptionPvk: /etc/ror/integration/certs/decrypt_pvk.crt
    # issuer: saml_sso_idp
```

* `issuer`: issuer string to supply to identity provider during sign-on request. Defaults to 'ror'
* `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. This is known to help when authenticating against Active Directory (AD FS) servers.
* `decryptionPvk`: Service Provider Private Key. A private key will be used to attempt to decrypt any encrypted assertions that are received.
* `cert`: The downloadable certificate in IDP Metadata (file, absolute path) or single line string value

For advanced SAML options, see [passport-saml documentation](https://github.com/bergie/passport-saml).

#### Identity provider side

1. Enter the settings of your identity provider, and create a new app.
2. Configure it using the information found by connecting to `http://my.public.hostname.com/ror_kbn_saml_serv1/metadata.xml`

Example response:

```xml
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="onelogin_saml" ID="onelogin_saml">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn/notifylogout"/>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
    <AssertionConsumerService index="1" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn/assert"/>
  </SPSSODescriptor>
</EntityDescriptor>
```

1. Create some users and some groups in the identity provider app
2. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set Kibana in debug mode so ReadonlyREST will print the user profile).
3. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
4. If you want to use SAML for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting.

#### Usage with Active Directory Federation Services

To work properly with ADFS, ensure that you add the following to the configuration:

```yaml
readonlyrest_kbn:
   auth:
      saml_adfs:
              authnContext: "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/windows" # Name identifier format to request auth context.`
              identifierFormat: null # Name identifier format to request from identity provider.`
              [...]
```

#### Additional Parameters

When configuring SAML authentication in ReadonlyREST Enterprise, you can provide additional parameters to customize the behavior of the SAML service provider integration. These parameters allow for fine-tuning the SAML integration to work with various identity providers and specific configurations

You can find a list of all supported parameters in the [Passport-SAML Configuration Parameters documentation](https://github.com/node-saml/passport-saml/tree/3.x?tab=readme-ov-file#config-parameter-details)

```yaml
readonlyrest_kbn:
   auth:
      saml_serv1:
              audience: "https://sp.example.com/metadata"`
              [...]
```

### OpenID Connect (OIDC)

([Enterprise](https://readonlyrest.com/enterprise))

This feature will work in ReadonlyREST Enterprise.

ReadonlyREST Enterprise supports OpenID Connect for both authentication and authorization.

Here is how to configure it.

#### Configure ReadonlyREST ES bridge

This part is identical as seen in SAML connectors. In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugins with a shared secret, that is: an arbitrarily long string.

#### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    # ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

**⚠️IMPORTANT** the Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

If you have configured OIDC with the `groupsParameter` ( *See below* ), you can also restrict ACL to specific groups:

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    # ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1 for group 1"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group1"]

    - name: "ReadonlyREST Enterprise instance #1 for group 2"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group2"]

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

You may also use any custom claim from the OIDC `userinfo` token in ACL rules by using `{{jwt:assertion.<path_to_your_claim>}}` syntax. See the [Dynamic variables from JWT claims section](/elasticsearch#usage-examples) for more information. ( **TIP** : Do not forget the `assertion` prefix in front of you jsonpath. )

#### Kibana side

We will assume the OpenID identity provider responds to port 8080 of localhost. In our example, we used Keycloak, an open-source implementation of OpenID Connect identity provide.

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  oidc_kc: 
    buttonName: "KeyCloak OpenID"
    type: "oidc"
    issuer: 'http://localhost:8080/auth/realms/ror'
    authorizationURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/auth'
    tokenURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/token'
    userInfoURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/userinfo'
    clientID: 'ror_oidc'
    clientSecret: '9f1d39c8-a211-460a-84b6-0a4a1499c455'
    scope: 'openid profile roles role_list email'
    usernameParameter: 'preferred_username'
    groupsParameter: 'groups'
    kibanaExternalHost: 'localhost:5601'
    logoutUrl: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/logout'
    jwksURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/certs'
```

#### Identity provider side

1. Enter the settings interface of your identity provider, and create a new OpenID app.
2. The redirect URL should be configured as `http://localhost:5601/*` assuming Kibana is listening on localhost and on the default port.
3. Create some users and some groups in the identity provider if not present.
4. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set `readonlyrest_kbn.logLevel: debug` in kibana.yml, so you will see the user profile how it's received from the identity provider right in the logs).
5. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
6. If you want to use OpenID for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting. ( **TIP**: the `groupsParameter` must be present in the `userinfo` token of your OIDC provider.)
7. If Kibana is accessed through a reverse proxy, kibanaExternalHost should be configured with the external hostname. if omitted, the default value is equal to `server.host:server.port` defined in kibana.yml. ( This parameter can be used also when Kibana is bound to 0.0.0.0, for example, if using docker.)

#### Client Authentication Methods

You can configure how the ReadonlyREST Kibana plugin sends `client_id` and `client_secret` to the identity provider using the option

```yaml
readonlyrest_kbn:
   auth:
      oidc_kc:
         tokenEndpointAuthMethod: 'client_secret_post'  #Available options: client_secret_basic (default) or client_secret_post
              [...]
```

There are two available methods for authentication:

1. **client\_secret\_basic** (default): The `client_id` and `client_secret` are sent using the Authorization header, as specified in [RFC 6749, Section 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1). Before sending, the `client_id` and `client_secret` are encoded.
2. **client\_secret\_post**: The `client_id` and `client_secret` are included in the request body, following the guidelines in [RFC 6749, Section 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1). In this method, the `client_id` and `client_secret` are not encoded before being sent. Choose this method when the OpenID Connect provider, such as [lemonLDAP::NG](https://lemonldap-ng.org/), cannot decode the encoded values.

The following description explains the available options for the setting:

#### User Info Source Methods

You can configure where the ReadonlyREST Kibana plugin obtains the OIDC user profile information using the `userInfoSource` option in the `readonlyrest_kbn.auth.oidc_kc` block. There are three available methods:

1. **user\_info\_endpoint** (default):\
   When set to `user_info_endpoint`, the plugin makes an additional call to the URL specified under `userInfoURL` to retrieve the most up-to-date user profile information from the OIDC provider.
2. **access\_token**:\
   When set to `access_token`, the plugin extracts the user profile information directly from the access token.
3. **id\_token**:\
   When set to `id_token`, the plugin extracts the user profile information directly from the ID token.

For example, you can configure it as follows:

```yaml
readonlyrest_kbn:
   auth:
      oidc_kc:
         userInfoSource: 'access_token'  # Available options: 'user_info_endpoint' (default), 'access_token', 'id_token'
```

#### Additional Parameters

When configuring OpenID Connect (OIDC) in ReadonlyREST Enterprise, you can provide additional parameters to customize the behavior of the OIDC client and issuer. These parameters allow for fine-tuning the OIDC integration to work with various providers and specific configurations. These additional parameters allow you to solve complex authentication scenarios, work with non-standard OIDC providers, and fine-tune the security and performance characteristics of your OIDC integration.

**Issuer Additional Parameters**

You can find a list of all supported parameters:

For Kibana 7.12.0 and above: [documentation](https://github.com/panva/openid-client/tree/v5.7.1/docs#new-issuermetadata)

For Kibana below 7.12.0: [documentation](https://github.com/panva/openid-client/tree/v4.9.1/docs#new-issuermetadata)

```yaml
readonlyrest_kbn.auth:
   oidc_kc:
      [...]
      issuerAdditionalParameters:
         metadata:
            token_endpoint: 'https://custom-token-endpoint'
            jwks_uri: 'https://custom-jwks-uri'
```

#### Clock skew tolerance

You can configure the clock tolerance (in seconds) to account for potential time discrepancies between the Kibana server and the OpenID Connect provider. This setting helps prevent authentication failures due to minor time differences.

```yaml
readonlyrest_kbn.auth:
   oidc_kc:
      [...]
      clockToleranceSeconds: 5  # Default is 0 seconds
```

**Client Additional Parameters**

You can find a list of all supported parameters:

For Kibana 7.12.0 and above: [documentation](https://github.com/panva/openid-client/tree/v5.x/docs#new-clientmetadata-jwks-options)

For Kibana below 7.12.0: [documentation](https://github.com/panva/openid-client/tree/v4.x/docs#new-clientmetadata-jwks-options)

```yaml
readonlyrest_kbn.auth:
   oidc_kc:
      [...]
      clientAdditionalParameters:
        metadata:
          response_types: ['code']
          redirect_uris: ['https://my-app/callback']
        jwks:
           keys:
           - kty: 'RSA'
             use: 'sig'
             alg: 'RS256'
             kid: 'key1'
             n: 'PLACEHOLDER_TO_CHANGE_INTO_REAL_CERTIFICATE'
             e: 'AQAB'
        options:
          additionalAuthorizedParties: 'my-app'
```

### Impersonation

According to [Wikipedia](https://en.wikipedia.org/wiki/Impersonator):

> An impersonator is someone who imitates or copies the behavior or actions of another.

So, an impersonation can be understood as imitating behaviors or actions. In the context of ReadonlyREST: one user could imitate an action of another user. Why would we want it? Let's suppose the first user is an admin, who has just configured access for a new user. They would like to know if the rule(s) are configured correctly. And here comes the impersonation feature. The admin can impersonate the given user in Kibana and see what the user would see if they logged in themselves.

ROR plugins support impersonation and provide UI for configuring a cluster before using it. Visit the [impersonation details page](/kibana/impersonation) to know more.

## Multi-tenancy

### Multi-tenancy Kibana

([Enterprise](https://readonlyrest.com/enterprise))

ReadonlyREST Enterprise is capable of going beyond multi-user. Users or groups can be isolated into tenancies, so their dashboards and configurations won't mix. Behind each tenancy, there is a kibana index.

#### What is a kibana index?

In the vanilla Kibana, all the configuration objects are stored under an Elasticsearch index called `.kibana`, but with ReadonlyREST Enterprise installed, you can dynamically route Kibana into reading and writing to other indices entirely, for example `.kibana_tenancy1`. So when "tenancy1" is selected from the UI, Kibana hard reloads and all settings, dashboards, and visualizations are (potentially) different.

A user can be associated to multiple tenancies, and if so, will be presented with a tenancy switcher in the UI. ![image](https://github.com/beshu-tech/readonlyrest-docs/assets/1327189/b07d27d3-310c-4754-a5c5-21b0fe3f3d45)

Using this tool, they can hop between tenancies. Keep in mind that the ACL evaluation is slightly different when multi tenancy is activated: if a tenancy is selected, only blocks without `kibana.index` rule, or with the `kibana.index` [rule](https://docs.readonlyrest.com/elasticsearch#kibana) matching to the current teancy name will be evaluated.

In ReadonlyREST Enterprise, multi-tenancy is activated by default. But if you want it to behave as in PRO/Free editions, you can disable it by writing into `kibana.yml`:

```yml
readonlyrest_kbn.multiTenancyEnabled: false
```

### Configuring Multi-tenancy

([Enterprise](https://readonlyrest.com/enterprise))

You can configure an ACL in multi tenancy mode by adding a few ACL blocks containing the `kibana.index` [rule](https://docs.readonlyrest.com/elasticsearch#kibana). See examples and further explanation under our [multi-tenancy guide](/examples/multitenancy_guide).

### Extending the Kibana API with the x-ror-tenancy-id header

([Enterprise](https://readonlyrest.com/enterprise))

To target a specific tenant when making a [Kibana API](https://www.elastic.co/guide/en/kibana/current/api.html) request, include the custom HTTP header `x-ror-tenancy-id`. The value of this header should match one of the [groups rules](/elasticsearch#groups-rules) id defined in your ACL configuration. The first group defined in the ACL for a specific user is used as the default tenancy id.

example usage:

```bash
curl -X GET "http://localhost:5601/api/saved_objects/_find?type=dashboard" \
  -H "kbn-xsrf: true" \
  -H "x-ror-tenancy-id: marketing-team"
```

#### No authentication rule defined

The “problem with the configuration of authentication” error message is presented in ReadonlyREST Free/PRO/Enterprise when the login request is checked by the ACL and gets accepted by an ACL block with no authentication rule in it.

An example of this would be:

```yaml
readonlyrest:
   access_control_rules:
   - name: "LDAP Auth"
     ldap_authentication: ...
   
   - name: "Allow requests from localhost"
     hosts: ["127.0.0.1"]
```

Imagine you run Elasticsearch and Kibana on the same host:

* the Kibana user login request comes to Elasticsearch
* Credentials are wrong, and the first block does not match
* The second block is then evaluated, and the request is allowed because of its origin IP

As you can see, Elasticsearch has no user-related information (metadata) to return to Kibana, and the error “problem with the configuration of authentication ” is shown.

In general, we highly discourage implementing access control using origin IPs alone, users should set up SSL, Basic HTTP auth in their agents in any case, even on localhost. The `hosts` rule would then be an extra protection.

If this is not possible for very important reasons, then we would prevent any Kibana-originated request to match that rule by using the negated form of the [headers rule](/elasticsearch#headers). I.e.

readonlyrest.yml

```yaml
- name: "Allow requests from localhost"
  hosts: ["127.0.0.1"]
  headers: [ "~x-from-kibana:true" ]
```

kibana.yml (append)

```yaml
elasticsearch.customHeaders:  {"x-from-kibana":"true"}
```

### Tenancy index templating

([Enterprise](https://readonlyrest.com/enterprise))

This feature will work only with ReadonlyREST Enterprise

When a tenant logs in for the first time, ReadonlyREST Enterprise will create the kibana index associated to the tenancy as per ACL. For example, it will create and initialize the ".kibana\_user1" index, where the tenant "user1" will store all the ["saved objects"](https://www.elastic.co/guide/en/kibana/current/managing-saved-objects.html), that is: visualizations, dashboards, spaces, settings, data views, etc.

The problem is that user1, and any other new users would login for the first time in to a completely blank Kibana. And this is particularly challenging if the tenant is supposed to be read-only (i.e. kibana.access: "ro") because they won't even have privileges to create their own index-pattern, let alone any dashboards.

To fix this, ReadonlyREST Enterprise offers the possibility for administrators to create and curate a template kibana index from which all the Kibana objects will be copied over to the newly initialised tenancy. The objects in the templating index will be copied every time the user logs in (or changes tenancy with the tenancy selector), and **if the objects were already present, they will be overwritten**.

The object overwrite is desirable because administrators would like to improve and enrich the content of the template tenancy over time, and these enhancements need to be propagated to the tenants.

If the tenants were not read-only, and created other objects of their own (e.g. another space, another dashboard), these won't be deleted.

#### Reset tenancy to template

If you add `readonlyrest_kbn.resetKibanaIndexToTemplate: true` to `kibana.yml` your tenants will get their index deleted and reinitialized to the content in the kibana template index specified in `readonlyrest_kbn.kibanaIndexTemplate` every time they log in, or change tenancy using the tenancy selector.

The reset tenancy to template only works if a valid kibana index template is specified.

#### How to use tenancy templating

An administrator will need to create the template tenancy, populate it with the default Kibana objects (index-patterns, dashboards) and configure ReadonlyREST Enterprise to take the index template it in use. Let's see this step by step:

**Create the template tenancy**

Let's start to add to our access control list (found in $ES\_PATH\_CONF/config/readonlyrest.yml, or ReadonlyREST App in Kibana) a local user "administrator" that will belong to two tenancies: the default one (stored in .kibana index), and the template one (stored in .kibana\_template index).

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

  users:
  - username: administrator
    auth_key: administrator:dev
    groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu
```

NB: If you know what you are doing, you can add a tenancy with kibana\_index: ".kibana\_template" adding a LDAP/SAML group to your administrative user.

#### Configure the template tenancy

Now login as administrator in Kibana, hop into the "Template" tenancy, and start configuring the default saved objects for your future tenants: add all the data views, create or import all the dashboards you want.

#### Configure the template tenancy index in ReadonlyREST Enterprise

Open kibana.yml and add the following line:

```yaml
readonlyrest_kbn.kibanaIndexTemplate: ".kibana_template"
```

Now, ReadonlyREST Enterprise will look for the ".kibana\_template" index, and try to copy over all its documents every time a new kibana index is initialised to support a new tenancy.

#### Try it out

Restart Kibana with the new setting. Add a new tenancy to the ACL:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

  # Newly added tenant!
  - name: user1
    auth_key: user1:passwd
    kibana:
      access: rw
      index: ".kibana_user1"

  users:
  - username: administrator
    auth_key: administrator:dev
    groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu

```

Now try to login as user1, and ReadonlyREST Enterprise should initialize the index ".kibana\_user1" with all the index patterns and dashboards contained in the template tenancy.

### Tenant index configuration

You can configure the `number_of_shards` and `number_of_replicas` for the tenant index via the `kibana.yml` file, allowing you to override the default index settings. This can be particularly useful in a single-node environment.

```yaml
readonlyrest_kbn.tenantIndex.number_of_shards: 1
readonlyrest_kbn.tenantIndex.number_of_replicas: 0
```

{% hint style="warning" %}
These settings will overwrite the index template settings.
{% endhint %}

## UI Customization

### Hiding Kibana Apps

([PRO](https://readonlyrest.com/pro))

Previously we needed to keep track and document all Kibana app IDs, and you had to look them up all the time. Now we made it simpler by letting you type the apps and submenu titles exactly as you see them in the UI.

For example, this is how you hide the whole Enterprise Search submenu.

![kibana\_hide\_apps: \["Enterprise Search"\]](/files/-MXq6rKbbyqZPQtJVADZ)

And this is how you hide only one app from the Enterprise Search menu:

![kibana\_hide\_apps: \["Enterprise Search|Workplace Search"\]](/files/-MXq7Z0f12aRqYchy1pX)

More generally, either of these two ways will work:

```yaml
kibana:
  hide_apps: [ "<submenu-title>" ]
```

```yaml
kibana:
  hide_apps: [ "<submenu-title|app-title>" ]
```

For example, the following is a valid rule:

```yaml
kibana:
  hide_apps: [ "Security", "Management|Stack Management", "Enterprise Search" ]
```

There is also a way to use regular expression as a `kibana.hide_apps` value

for example, you can hide all submenus except for the specific app

```yaml
kibana:
  hide_apps: [ "/^Analytics\\|(?!(Maps)$).*$/"]
```

In this case, all analytics apps will be hidden except `Maps`

**⚠️IMPORTANT** Pipe operator needs to be escaped correctly when it's declared in the regular expression `\\|`. The regular expression must be declared between double quote `"/<regular-expression/"`

You can also hide all submenus except specified values

```yaml
kibana:
  hide_apps: ["/^(?!(Analytics|Management).*$).*$/"]
```

In this case, everything except of `Analytics` and `Management`, will submenus will be hidden

**⚠️IMPORTANT** In this case `|` is treated as logical `or` operator, that's why it shouldn't be escaped

To check all regular expressions available options, check the [regular expressions syntax cheatsheet](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet)

#### Hiding Kibana management apps

There is an option to hide specific management apps. You can declare hide\_apps value like:

```yaml
hide_apps: [ "<submenu-title|app-title|management-submenu-title|management-app-title>" ]

```

* To hide a single management application, you can use:

```yaml
kibana:
  hide_apps: [ "Management|Stack Management|Kibana|Tags" ]
```

In this case, only the Stack Management Tags application will be hidden

* To hide all management Kibana section applications, you can use:

```yaml
kibana:
  hide_apps: [ "/^Management\\|Stack Management\\|(?!(Kibana)|$).*$/" ]
```

In this case, all Stack Management Kibana sections will be hidden

* To hide all management Kibana section applications except selected, you can use

```yaml
kibana:
  hide_apps: ["/^Management\\|Stack Management\\|Kibana\\|(?!(Data Views|Tags)$).*$/"]
```

In this case, all Stack Management Kibana section apps except Data Views and Tags will be hidden

* To hide all management applications except selected, you can use

```yaml
kibana:
  hide_apps: ["/^Management\\|Stack Management\\|(?!(Kibana)|$).*$/", "/^Management\\|Stack Management\\|Kibana\\|(?!(Data Views|Tags)$).*$/"]
```

In this case, all Stack Management apps except Data Views and Tags will be hidden

### Hiding ReadonlyREST menu elements

This feature will work in ReadonlyREST PRO and Enterprise.

To hide the `Manage Kibana` button for the specific user you need to provide `ROR Manage Kibana` value into a `kibana.hide_apps`

```yaml
kibana:
  hide_apps: [ "ROR Manage Kibana" ]
```

To hide the `Edit security settings` button for the specific user you need to provide `ROR Security Settings` or `readonlyrest_kbn` value into a `kibana.hide_apps`

```yaml
kibana:
  hide_apps: [ "ROR Security Settings" ]
```

![Hiding ReadonlyREST menu elements](/files/s0YayI3qZ9bv64rdlEfI)

### Login screen tweaking

([PRO](https://readonlyrest.com/pro))

These features will work with ReadonlyREST PRO and Enterprise.

It is possible to customize the look of the login screen.

#### Two column layout

By default, the login form appears in a single-column view. ![one column](blob:https://imgur.com/f7514ca2-7f8f-4f96-aecd-09e7ea636b62)

But once the title and subtitle are configured, it will switch to two columns to make room for the new text.

```yaml
readonlyrest_kbn.login_title: "Some Title"
readonlyrest_kbn.login_subtitle: "Longer text <b>any HTML is supported<b/> including ifrmaes"
```

![two columns](https://i.imgur.com/Sqf1GIL.png)

#### Add your company logo

It's recommended to use a transparent PNG, negative logo. Ideally a white foreground, and transparent background.

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.login_custom_logo: 'https://.../logo.png'
```

To incorporate your personalized logo into the login page, place your image file within the `<YOUR_ROOT_DIRECTORY>/kibana/plugins/readonlyrestkbn/public/assets directory`. Then, proceed by appending the following code snippet to `kibana.yml`:

```yaml
readonlyrest_kbn.login_custom_logo: '/pkp/legacy/web/assets/<YOUR_LOGO>'
```

Your personalized logo can be in any format [supported by web browsers](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types). The maximum file size varies depending on the browser you're using. We recommend keeping them smaller, with a maximum size of 500KB, to maintain optimal page load speed.

#### Add custom CSS/JS

**Inject via HTML code**

You have the opportunity to inject HTML code right before the closing head tag (`</head>`).

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.login_html_head_inject: '<style> * { color:red; }</style>'
```

**Inject via JS file**

There is an option to inject JavaScript file before the login screen is rendered.

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.login_html_head_inject: '<ABSOLUTE_PATH_TO_CUSTOM_JS_FILE>'
```

**Inject via CSS file**

There is an option to inject CSS file before the login screen is rendered.

```yaml
readonlyrest_kbn.login_custom_css_inject_file: '<ABSOLUTE_PATH_TO_CUSTOM_CSS_FILE>'
```

### Kibana UI tweaking

([Enterprise](https://readonlyrest.com/enterprise))

This feature will work with ReadonlyREST Enterprise

It's possible to inject custom CSS and Javascript to achieve a customized user experience for your users/tenants.

#### Inject custom CSS in Kibana

Open `config/kibana.yml` and append the following:

```yaml
readonlyrest_kbn.kibana_custom_css_inject: '.global-nav, kbnGlobalNav { background-color: green }'
```

Alternatively, it's possible to load the CSS from a file in the filesystem:

```yaml
readonlyrest_kbn.kibana_custom_css_inject_file: '/tmp/custom.css'
```

**⚠️IMPORTANT** If you use relative paths, you end up pointing to kibana home, i.e. `readonlyrest_kbn.kibana_custom_css_inject_file: 'config/custom.css'` will refer to `$KBN_HOME/config/custom.css` which is the same directory where `kibana.yml` can normally be found.

#### Inject custom JS in Kibana

```yaml
readonlyrest_kbn.kibana_custom_js_inject: '$(".global-nav__logo").hide(); alert("hello!")'
```

Alternatively, it's possible to load the JS from a file in the filesystem:

```yaml
readonlyrest_kbn.kibana_custom_js_inject_file: '/tmp/custom.js'
```

**⚠️IMPORTANT** If you use relative paths, you end up pointing to kibana home, i.e. `readonlyrest_kbn.kibana_custom_js_inject: 'config/custom.js'` will refer to `$KBN_HOME/config/custom.js` which is the same directory where `kibana.yml` can normally be found.

### Custom middleware

([Enterprise](https://readonlyrest.com/enterprise))

Sometimes, Enterprise users might need more flexibility and customize the plugin behavior to adjust the product to the business needs. There are two options to declare the custom middleware:

* JS file: `readonlyrest_kbn.custom_middleware_inject_file: '/path/to/your/file.js'` // You can also use a relative path here. It's relative to the kibana root folder
* Inline: `readonlyrest_kbn.custom_middleware_inject: 'function test(req, res, next) {logger.debug("custom middleware called"); next()}'`

Visit the [Custom middleware](/examples/custom-middleware) to know more.

## Audit dashboard

This feature will work in all ReadonlyREST editions.

The Elasticsearch plugin audit feature is widely described in [📖docs for the Elasticsearch plugin](/elasticsearch#audit). The Kibana plugin has a predefined dashboard representing collected audit data.

### Loading visualization

In the *Audit* tab of the ReadonlyREST Kibana app, there is a button that automatically creates a dashboard with some audit log-specific visualizations.

![audit log tab](/files/x6SqW2sAMbA6SXr14wTN)

Click the *Load* button to load the dashboard and visualizations. An *Override* checkbox allows reloading the default dashboard and visualizations. It will override any previously loaded audit log dashboard.

![loading visualization](/files/vKSoJZbwzRFKQixNNTmj)

In detail, this feature creates three Kibana "saved objects":

* an index pattern for `readonlyrest_audit-*`
* a dashboard called `ReadonlyREST Audit Log`
* some visualizations

### Dashboard

The audit log dashboard, by default, has only a few basic visualizations. They cover security, access logs, and performance metrics.


# Impersonation (Enterprise)

([Enterprise](https://readonlyrest.com/enterprise))

After describing what [the impersonation is](/kibana#impersonation), it's high time to see how ROR supports it and who and when could be interested in using this feature. Let's start with the latter.

## Use cases

The impersonation feature is intended for ROR administrators, rather than users. We can point out the two most obvious use cases when the admin could take advantage of the feature:

#### Debugging users' problems:

Let's imagine that some user has a problem with their ROR configuration (eg. the user doesn't have access to some feature that was blocked at ROR's level by you, the admin). And they are not able to clearly describe what the issue is (sounds familiar?). As an administrator, it would be extremely beneficial if you could see what the user sees. Thanks to the impersonation feature, an admin is allowed to impersonate the user and experience exactly what the user experiences.

#### Configuring a new user:

When an admin configures a new user in ROR settings, they face two problems:

1. `Will the updated configuration break the production cluster?`
2. `How do I know that the new user is correctly configured? Did I configure all their permissions correctly??`

Both of the problems can be solved using the ROR's impersonation. Thanks to the fact that the impersonation feature always uses its own Test Settings, that is completely independent from the main production settings, the admin can alter it without worries that their actions will break something and users won't be able to do their job.

Admin can add the new user configuration without worrying and then test it by impersonating the user. They can check if the user can log in without problems and if the user has access only to the Kibana features the admin wanted to grant. When the admin is sure that everything is configured correctly, they can promote the settings (test) to production.

## Impersonation configuration

Before an admin will be able to impersonate a user, they have to configure ROR properly. The configuration consists of several parts:

1. creating ROR's Test Settings,
2. defining mocks of the external services (like [LDAP](/elasticsearch#ldap-connector), [External Basic Auth](/elasticsearch#external-basic-auth) or [Custom groups provider](/elasticsearch#custom-groups-providers)),
3. impersonating a chosen user.

#### Creating ROR's Test Settings

When you call Elasticsearch directly or through ROR Kibana, ROR ACL is defined by Settings (we can assume they are Main Settings). The Test Settings define another ACL, that is taken into consideration by ROR ES only when a proper impersonation header is passed. The header is managed by ROR internally. The Test Settings are active only for a strictly defined amount of time (by default it's *30 minutes*, but the admin can change it before applying Test Settings). After the time has expired, they are automatically invalidated (for security reasons). Obviously, the admin is allowed to invalidate the configured Test Settings in any time. There is no way to have more than one Test Settings configured at time.

ROR Kibana plugin provides a dedicated Test Settings UI. See our [Test Settings management guide](/examples/impersonation/test-settings-ui) for more information.

But copying Main Settings as Test Settings is not enough. We also have to instruct ROR which users can be considered as impersonators (the ones, who are allowed to impersonate other users):

1. In the `access_control_rules` section in ROR Settings, there must be a rule that authenticates the impersonator user.
2. The impersonator user must be defined in the `impersonation` section in ROR Settings
3. The impersonator's credentials in `impersonation` section must match the credentials, that the impersonator uses to authenticate in Kibana.

```yaml
readonlyrest:
  access_control_rules:
    - name: "Authenticate admin1"
      auth_key: admin1:pass
    - name: "Authenticate admin2"
      ldap_authentication: "ldap1"

  impersonation:
    - impersonator: admin1      // Who can impersonate? (user name or pattern)
      users: ["*"]              // Who can be impersonated? (user names or patterns)
      auth_key: admin1:pass     // Authentication rule required to impersonate (any authentication rule can be used here)
    - impersonator: admin2
      users: ["dev2"]
      ldap_authentication: "ldap1"
```

In the example above, we see that we have two impersonators: `admin1` and `admin2`. The first one can impersonate any user (`*`) and they are able to authenticate using basic auth (`admin1:pass`). The second impersonator can impersonate only `dev2` user. They will be authenticated using `ldap1` connector.

When an impersonator passes wrong credentials ROR will tell Kibana that impersonation is not allowed.

#### Defining mocks of the external services (optional)

ROR has many sophisticated authentication & authorization methods. Some of them are based on external systems like LDAP. The problem with such systems, in regard to to the impersonation feature, is that those systems either don't support it by default or don't support it at all and even if they do - the configuration is complex.

That's why we decided to solve it totally differently - using mocks. [Wikipedia](https://en.wiktionary.org/wiki/mock) defines `mock` as `an imitation, usually of lesser quality.` And in the case of external authentication systems we are going provide an imitation of it that will tell ACL which users should be successfully authenticated by it. When we consider an authorization service, a mock of it will return the ACL users with their roles in the service. And this is enough for ROR to support impersonation.

How does ROR use the mocks? Let's suppose we have an `ldap_auth` rule. When ROR processes the rule, it:

* asks the given LDAP service if the username can be authenticated with a given password, and if they can ...
* asks LDAP to list what groups the user belongs to

In the impersonation case, it looks pretty much the same. The difference being that ROR won't call any LDAP server - the mock will provide the required information instead (no password required). During impersonating, when ROR processes an LDAP rule, it:

* asks the mock if the username exists, and if it does ...
* asks the mock to tell what groups the user belongs to

**⚠️ IMPORTANT:** If one or more of the external services are not mocked, ROR might inform Kibana that the impersonation is not supported. It's better to always define all mocks, to avoid the "Impersonation not supported" Elasticsearch response.

ROR Kibana plugin helps administrators to visually create and edit service mocks with a dedicated graphical UI. Follow our [service mock configuration guide](/examples/impersonation/external-services-mocks-ui) for more.

#### Impersonating a chosen user

Now that we have configured Test Settings and External Services Mocks, we can try to impersonate a user. In Elasticsearch ROR Settings, user can be:

* provided statically (defined in the settings),
* provided dynamically:
  * from external, dependant systems (like LDAP) - we mock them
  * from upstream systems (eg. through headers) - they are not known upfront

It means that we pick the users defined in Settings or Mocks, but also we can enter the username and try to impersonate such user.

Follow the instructions on how to [impersonate a user using the ROR Kibana plugin UI](/examples/impersonation/impersonate-user-ui).

## Logs & audit

In Elasticsearch logs, in `USR` field, if an admin user finds something like this: `admin1 as (user1)` - it means that `admin1` was authenticated and they are the impersonator who is impersonating `user1`.

All logs of impersonated user in Kibana will have this format `[<log level>][plugins][ReadonlyREST][<filename>][impersonating <impersonated user username>]`

When auditing is enabled, the audit document is going to contain an `impersonated_by` field.

## Impersonation limitations

Impersonation mode has some limitations. Please check if they have an impact on your use cases:

* Not all features available in the ROR configuration are testable with impersonation mode. Some rules used in ROR ACL do not support impersonation. For example, auth rule with hashed credentials (e.g. `auth_key_sha512`) can be used in impersonation mode only when credentials follow the format `USER_NAME: HASH(PASSWORD)`; A fully hashed username and password don't allow fetching a username. The auth rule in such a format won't match during impersonation. In the [rules description](/elasticsearch#rules) section you can find information about each rules impersonation support.
* Test Settings are stored in the memory of the node that handled the saving request sent by ROR Kibana plugin. Impersonation support will be limited to this node. We are going to improve it in the future, but for now your Kibana should only communicate with one Elasticsearch node.
* Sometimes it is impossible to fetch usernames defined in the Test Settings. If a `users` rule contains a username pattern with a wildcard, to impersonate a user matching the pattern, you need to enter the username manually.

  ```yaml
  readonlyrest:

    access_control_rules:
      - name: "LDAP group g1"
        type: allow
        groups_any_of: ["g1"]
      
    users:
      - username: "admin*"  // To impersonate a user with a username matching 'admin*' you need to enter the username manually, like 'admin123'
        groups:
          - g1: group1
        ldap_auth:
          name: "ldap1"
          groups_any_of: ["group1"]
        
    ldaps:
      - name: ldap1
        [..]
        
    impersonation:
      [...]
  ```

## Glossary

* **Impersonator** - someone who imitates or copies the behavior or actions of another,
* **Impersonation** - imitating behaviors or actions of a given user,
* **Main Settings** - the ROR's settings that apply to ACL that handles requests during regular sessions (not the impersonation ones),
* **Test Settings** - the ROR's settings that apply to ACL that handles impersonating requests (the ones during impersonation session),
* **External Service Mock** - an imitation of an external service (the supported ones: LDAP, an external authentication service, an external authorization service).


# Kibana 7.8.x and older

User manual for ReadonlyREST Enterprise/PRO/Free plugins

## Kibana Plugin overview

ReadonlyREST plugin for Kibana is not open source, and it's offered as part of the [ReadonlyREST PRO](https://readonlyrest.com/pro) and [ReadonlyREST ENTERPRISE](https://readonlyrest.com/enterprise), and [ReadonlyREST Free](https://readonlyrest.com/free) packages. See product descriptions and a comparison chart in the official [ReadonlyREST website](https://readonlyrest.com)

ReadonlyREST plugins for Kibana **always require** ReadonlyREST Free to be installed in the Elasticsearch nodes your Kibana instance(s) will connect to.

It's not mandatory to install ReadonlyREST Free in all Elasticsearch nodes, but only in the ones in where you need the HTTP interface to be secured.

### After purchasing

You will receive a link to the plugin zip file in an email. Download your zip.

You will be able to download it also in the future as long as your subscription is active.

### Version strings

All our plugins include in their file name a version string. For example the file `readonlyrest-1.16.26_es6.4.0.zip` has a version string `1.16.26_es6.4.0`.

#### Reading version strings

Given the version string `1.16.26_es6.4.0`

* ReadonlyREST plugin code version `1.16.26`
* Works only with Elasticsearch/Kibana version `6.4.0`

The "es" stands for "Elastic stack" which used to mean the family of products made by Elastic which get released at the same time under the same version number. This was chosen **before** Elastic renamed their X-Pack commercial offer to Elastic Stack.

To be clear, there is no affiliation between ReadonlyREST and Elastic, or their commercial products.

#### Trial builds version strings

Trial builds are valid for 30 days after they were built, and they will stop working soon after the time is elapsed. Trial builds have a special version string which includes a build-time timestamp.

I.e. `readonlyrest_kbn_pro-1.16.26-20180911_es6.0.0.zip`

* ReadonlyREST PRO plugin version 1.16.26
* Build date 11th September 2018, expiring on the 11th of October 2018.
* Works only with Kibana version 6.0.0

### When an update is out

You will receive another email notification that a new deliverable is available.

If the update contains a security fix, it is very important that you take action and **update the plugin immediately**.

## Installation

You can install this as a normal Kibana plugin using the `bin/kibana-plugin` utility.

### Install via URL

This installation method is more practical if your Kibana server is connected to the internet.

According to what edition of ReadonlyREST you want to install, from your Kibana installation, launch one of the commands:

Please note that this will always download the latest version of Kibana plugin available for the current supported Elasticsearch version.

```bash
# ReadonlyREST Free edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_free&email=<your_email_address>"

# ReadonlyREST PRO (30 days trial) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&email=<your_email_address>"

# ReadonlyREST Enterprise (30 days trial) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&email=<your_email_address>"
```

If you want to download the latest version of plugin for a specific version of Elasticsearch, then use query parameter esVersion to specify your required Elasticsearch version.

```bash
# ReadonlyREST Free edition for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_free&esVersion=7.6.1&email=<your_email_address>"

# ReadonlyREST PRO (30 days trial) edition for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&esVersion=7.6.1&email=<your_email_address>"

# ReadonlyREST Enterprise (30 days trial) edition for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&esVersion=7.6.1&email=<your_email_address>"
```

If you want to download an older version of plugin for a specific version of Elasticsearch, then use query parameter pluginVersion along with esVersion.

```bash
# ReadonlyREST Free edition - version 1.22.0 for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?edition=kbn_free&esVersion=7.6.1&pluginVersion=1.22.0&email=<your_email_address>"

# ReadonlyREST PRO (30 days trial) edition - version 1.22.0 for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&esVersion=7.6.1&pluginVersion=1.22.0&email=<your_email_address>"

# ReadonlyREST Enterprise (30 days trial) edition - version 1.22.0 for Elasticsearch 7.6.1
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&esVersion=7.6.1&pluginVersion=1.22.0&email=<your_email_address>"
```

If you are a PRO or Enterprise subscriber, the link will include an extra parameter "token" which can only be used in association with the provided email address.

You can append required plugin version and Elasticsearch version query parameters to download specific version as described above.

**NB: This URL is personal, and should be handled as a secret.**

```bash
# ReadonlyREST PRO (Official) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_pro&email=<your_email_address>&token=<your_secret_token>"

# ReadonlyREST Enterprise (30 days trial) edition
$ bin/kibana-plugin install "https://portal.readonlyrest.com/download/trial?edition=kbn_enterprise&email=<your_email_address>&token=<your_secret_token>"
```

You can obtain official links with personal secret tokens using our self service [download form](https://readonlyrest.com/download/), once your email address has been recognized as active subscriber.

### Install from zip file

```bash
$ bin/kibana-plugin install file:///home/user/downloads/readonlyrest_kbn-X.Y.Z_esW.Q.U.zip
```

Notice how we need to type in the format `file://` + absolute path (yes, with three slashes).

### Uninstall

```bash
$ bin/kibana-plugin remove readonlyrest_kbn
```

### Upgrade

Just uninstall the old version and install the new version.

```bash
$ bin/kibana-plugin remove readonlyrest_kbn
```

Install the new version of ReadonlyREST into Kibana.

```bash
$ bin/kibana-plugin install file:///home/user/downloads/readonlyrest_kbn-*.zip

# Only for older versions (until Kibana early 6.x)
$ touch optimize/bundles/readonlyrest_kbn.style.css
```

Restart Kibana.

### Using ROR with a reverse proxy

ROR - just like Kibana itself - is meant to be used either with a proxy or without one, but not both simultaneously. If you decide to set the `server.basePath` property in `kibana.yml` be sure to access ROR via a proxy, as it will not work properly when accessed directly.

## Configuration

ReadonlyREST for Kibana is completely remote-controlled from the Elasticsearch configuration. Login credentials, hidden Kibana apps, etc. are all going to be configured from the Elasticearch side via the usual "rules". This means the configuration will be kept all in one place and if you used ReadonlyREST before , it will be also very familiar.

> In this document, every time you will encounter references to "readonlyrest.yml" or "elasticsearch.yml", we will be referring to the configuration files **in the Elasticsearch plugin** (our Kibana plugins do not need a "readonlyrest.yml").

In general, by design, we tend to concentrate all configuration within the main plugin (the Elasticsearch one) as much as possible.

### Clusterwide Settings vs readonlyrest.yml

([PRO](https://readonlyrest.com/pro))

Our Kibana plugins introduce a "ReadonlyREST" Kibana app. From here, you can edit the security settings of the whole Elasticsearch cluster, and they will take effect within 10 seconds in all Elasticsearch cluster nodes without the need to restart them.

When you change the security settings from the Kibana app, they will be saved in a special index called ".readonlyrest", so all the Elasticsearch nodes will pick them up. You can customize a name of the index by setting `readonlyrest.settings_index: .my_custom_readonlyrest` in `elasticsearch.yml` file (remember to set the same value for all your ES nodes).

When an Elasticsearch node restarts, the order of settings evaluation is the following: 1. Attempt to find valid settings in readonlyrest.yml 2. If none is found, look inside elasticsearch.yml 3. Once successfully bootstrapped using file-based settings, attempt to read ".readonlyrest" index 4. If the index exists and contains valid settings, override file based settings with the ones from the index. 5. Pressing "save" in the cluster wide settings app, will **not overwrite the readonlyrest.yml** file.

Best practices:

* Build and update your production security settings from the Kibana app (will be saved in index)
* Protect the ".readonlyrest" Kibana index with an ACL rule

#### Loading settings: order of precedence

As you read, there are two possible places where the settings can be read from:

* `readonlyrest.yml` a file the user needs to create in the same directory where `elasticsearch.yml` is found.
* `.readonlyrest` index. Our Kibana plugins' GUI (PRO/Enterprise) is programmed to write this index.

When the ES plugin boots up, it follows some logic to evaluate where to read the YAML settings from. The following diagram shows how that works.

![config loading diagram](/files/DZiTCDxxEXDAFtUje4ik)

#### Malformed in-index settings

If for some reason the in-index settings get corrupted and ROR can't parse them, then neither settings from file or in-index settings can be loaded, so ES can't start. In this case ES would print message like:

```
Loading ReadonlyREST settings from index failed: Settings config content is malformed. Details: while scanning a quoted scalar
 in 'reader', line 9, column 17:
          auth_key: "admin:container
                    ^
```

To recover from this state, set `readonlyrest.force_load_from_file: true` in `elasticsearch.yaml` on one node `es1`.

Example recovery settings:

elasticsearch.yaml

```yaml
[...]
readonlyrest:
  force_load_from_file: true
```

readonlyrest.yaml

```yaml
readonlyrest:

  access_control_rules:
  - name: "::ADMIN recover::"
    auth_key: admin:dev
    indices: ["*"]
```

Then remove in-index settings index manually.

```bash
curl -X DELETE "admin:dev@es1:9200/.readonlyrest?pretty"
```

Now you can restore your settings to `readonlyrest.yml`, remove `readonlyrest.force_load_from_file: true` `from elasticsearch.yaml` and restart node.

### Example: multiuser ELK

Make sure X-Pack is uninstalled or disabled from `elasticsearch.yml` (on the Elasticsearch side) and `kibana.yml` (on the Kibana side): This is how you disable X-pack modules:

```yaml
# For X-Pack users: you may only leave monitoring on. 
# Don't add this if X-Pack is not installed at all, or Kibana won't start.
xpack.monitoring.enabled: true
xpack.security.enabled: false
xpack.watcher.enabled: false
xpack.telemetry.enabled: false
```

This is a typical example of a configuration snippet to add at the end of your `readonlyrest.yml` (the settings file of the Elasticsearch plugin), to support ReadonlyREST PRO.

```yaml
readonlyrest:

    access_control_rules:

    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["logstash-*"]

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    - name: "::RO::"
      auth_key: ro:dev
      indices: ["logstash-*"]
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:stack_management"]

    - name: "::RW::"
      auth_key: rw:dev
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:stack_management"]


    - name: "::ADMIN::"
      auth_key: admin:dev
      # KIBANA ADMIN ACCESS NEEDED TO EDIT SECURITY SETTINGS IN ROR KIBANA APP!
      kibana:
        access: admin

    - name: "::WEBSITE SEARCH BOX::"
      indices: ["public"]
      actions: ["indices:data/read/*"]
```

### Very important

Whatever your configuration ends up being, remember:

* The admin user has `kibana.access: admin`
* Remember to use `kibana.hide_apps: ["readonlyrest_kbn"]` to hide the ReadonlyREST icon from who is not meant to use it (makes for a better UX).

#### Rules ordering matters

> Blocks related to the authentication of the users should be at the top of the ACL

One of the most common mistakes is forgetting that the ACL blocks are evaluated in order from the first to the last.

So, some request with credentials can be let through from one of the first blocks and come back to Kibana with no user identity metadata associated.

Take this example of troublesome ACL:

```yaml
    # PROBLEMATIC SETTINGS (EXAMPLE) ⚠️

    access_control_rules:

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin
```

The user will be able to login because the login request will be allowed by the first ACL block. But the ACL will not have resolved any metadata about the user identity (credentials checking was ignored)!

This means the response to the Kibana login request will contain no user identity metadata (username, hidden apps, etc) and ReadonlyREST for Kibana won't be able to function correctly.

The solution to this is to reorder the ACL blocks, so the ones that authenticate Kibana users are on the top.

```yaml
    # SOLUTION: KIBANA USER AUTH RELATED BLOCKS GO FIRST! ✅👍

    access_control_rules:

    - name: "::ADMIN::"
      auth_key: admin:dev
      kibana:
        access: admin

    - name: "::FIRST BLOCK::"
      hosts: ["127.0.0.1"]
      actions: [...]
```

#### Session cookie expiration

When a user logs in, ReadonlyREST will write an encrypted cookie in the browser. This cookie has an time to live that can be tweaked with the following configuration key in `kibana.yml`.

```
readonlyrest_kbn.session_timeout_minutes: 600 # defaults to 4320 (3 days)
```

#### Clearing Session History

By default, all the session data like search history, dev tool commands history, etc, will be wiped out from the browser whenever a new user is logged in, or a user changes tenancy. To override this behaviour, use this setting:

```
readonlyrest_kbn.clearSessionOnEvents: ["never"]
```

Possible values: `"login", "tenancyHop", "never"`.

#### Kibana App strings

Examples of valid arguments for the `kibana.hide_apps: [...]` rule (readonlyrest.yml)

| hide-app key                     | App name         | App url                                                                                                                                |
| -------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| kibana:discover                  | Discover         | <http://kibana-url:5601/app/kibana#/discover>                                                                                          |
| kibana:visualize                 | Visualize        | <http://kibana-url:5601/app/kibana#/visualize>                                                                                         |
| kibana:dashboard                 | Dashboard        | <http://kibana-url:5601/app/kibana#/dashboards>                                                                                        |
| timelion                         | Timelion         | <http://kibana-url:5601/app/timelion>                                                                                                  |
| canvas                           | Canvas           | <http://kibana-url:5601/app/canvas>                                                                                                    |
| maps                             | Maps             | <http://kibana-url:5601/app/maps>                                                                                                      |
| code                             | Code (Beta)      | <http://kibana-url:5601/app/code>                                                                                                      |
| ~~readonlyrest\_kbn~~ (obsolete) | ~~ReadonlyREST~~ | ~~\~\~\[~~<http://kibana-url:5601/app/readonlyrest_kbn~~](http://kibana-url:5601/app/readonlyrest_kbn)~~~~>                            |
| ml                               | Machine Learning | <http://kibana-url:5601/app/ml>                                                                                                        |
| infra:home                       | Infrastructure   | [http://kibana-url:5601/app/infra#/infrastructure/inventory?\_g=(](http://kibana-url:5601/app/infra#/infrastructure/inventory?_g=%28)) |
| infra:logs                       | Logs             | [http://kibana-url:5601/app/infra#/logs?\_g=(](http://kibana-url:5601/app/infra#/logs?_g=%28))                                         |
| apm                              | APM              | <http://kibana-url:5601/app/apm>                                                                                                       |
| uptime                           | Uptime           | <http://kibana-url:5601/app/uptime#/>                                                                                                  |
| siem                             | SIEM             | <http://kibana-url:5601/app/siem>                                                                                                      |
| graph                            | Graph            | <http://kibana-url:5601/app/graph>                                                                                                     |
| kibana:dev\_tools                | Dev Tools        | <http://kibana-url:5601/app/kibana#/dev_tools>                                                                                         |
| monitoring                       | Stack Monitoring | <http://kibana-url:5601/app/monitoring>                                                                                                |
| kibana:stack\_management         | Stack Management | <http://kibana-url:5601/app/kibana#/management>                                                                                        |

### Kibana configuration

Activate authentication for the Kibana server: let the Kibana daemon connect to Elasticsearch using a pair of credentials we just defined in `readonlyrest.yml` (see above, the ::KIBANA-SRV:: block).

Open up `conf/kibana.yml` and add the following:

```yaml
# This is kibana.yml, but copy the exact same in elasticsearch.yml if you have to use some X-pack features.
xpack.graph.enabled: false
xpack.ml.enabled: false
xpack.monitoring.enabled: true
xpack.watcher.enabled: false

# Kibana server use ::KIBANA-SRV:: credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"
```

And of course also make sure `elasticsearch.url` points to the designated Elasticsearch instance (check also the http or https)

### Proxy Auth

ROR for Elasticsearch can delegate authentication to a reverse proxy which will enforce some kind of authentication, and pass the successfully authenticated user's name inside a `X-Forwarded-User` header.

> Today, it's possible to skip the regular ROR login form and use the "delegated authentication" technique in ROR for Kibana as well.

1. Configure ROR for ES to expect delegated authentication (see [`proxy_auth` rule](/elasticsearch#proxy_auth)) in ROR for ES documentation.
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.proxy_auth_passthrough: true`

Now ROR for Kibana will **skip the login form entirely**, and will only require that all incoming requests must carry a `X-Forwarded-User` header containing the user's name. Based on this identity, ROR for Kibana will build an encrypted cookie and handle your session normally.

#### Custom Logout link

Normally, when a user presses the logout button in ROR for Kibana, it deletes the encrypted cookie that represents the users identity and the login form is shown.

However, when the authentication is delegated to a proxy, the logout button needs to become a link to some URL capable to unregister the session a user initiated within the proxy.

For this, ROR for Kibana offers a way to customize the logout button's URL:

1. Find a link that will delete the reverse proxy's user session
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_logout_link: https://..../logout`

Now users that gained a session through delegated auth, can also click on the logout button in ROR for kibana and actually exit their session.

#### Custom Login link

When you delegate authentication to an external service, you can tell ReadonlyREST to skip the classic login form entirely and redirect users to your proxy or identity provider's login screen.

To enable this:

1. Find your authentication proxy or identity provider login URL for the ROR app
2. Open up `conf/kibana.yml` and add `readonlyrest_kbn.custom_login_link: "https://../login"`

The advantage of this approach is a streamlined user experience for users that login with an external IdP. The disadvantage is that you give up the possibility to login as a local user in ROR, as the login form will be always skipped.

#### Caveat

Enabling proxy auth passthrough will relax the requirement to provide a password. Therefore, don't enable this option if you don't make sure Kibana can **only be accessed through the reverse proxy\***.

### JWT Token Forwarding as URL Query Parameter

Alternatively to typing in credentials in the standard login form, it is possible to create an authenticated Kibana session by passing a JWT token as a query parameter in a URL.

#### Configuration

To enable this feature in ReadonlyREST, you need to:

* Have JWT authentication configured in ReadonlyREST (modifying `readonlyrest.yml` or the cluster wide settings UI in the Kibana plugin). [See how](/elasticsearch#json-web-token-jwt-auth).
* Specify the query parameter name in `kibana.yml` by adding the line `readonlyrest_kbn.jwt_query_param: "jwt"` as a string, in our case "jwt".

#### In Action

Once Kibana is restarted, you will be able to navigate to a link like this:

```
http://kibana:5601/login?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

The following will happen:

1. The Kibana plugin will forward the JWT token found in the query parameter into the `Authorization` header in a request to Elasticsearch.
2. Elasticsearch will cryptographically authenticate and resolve the user's identity from the JWT claims.
3. Kibana will write an encrypted cookie in your browser and use that from now on for the length of the authenticated session. From here onwards, the session management will be identical to the normal login form flow.
4. When the user presses logout, Kibana will delete the cookie and redirect you to the login form, or whatever link you configured as `readonlyrest_kbn.custom_logout_link`.

**Deep linking with JWT**

Because the identity is embedded in the link, and ReadonlyREST is able to authenticate the call on the fly, the JWT authentication can be used in conjunction with `nextUrl` query parameter for sharing deep links inside Kibana apps, or embedding visualizations and dashboards inside I-Frames.

**Anatomy of a JWT deep link**

```
http://kibana:5601/login?jwt=<the-token>&nextUrl=urlEncode(<kibana-path>)
```

In Javascript one can compose a JWT deep link as follows:

```javascript
var absoluteKibanaPath = '/app/kibana#/visualize/edit/28dcde30-2258-11e8-82a3-af58d04b3c02?_g=()';

var url = 'http://kibana:5601/login?jwt=' + 
           jwtToken + 
           '&nextUrl=' + 
           encodeURI(absoluteKibanaPath);

console.log("Final JWT deep link: " + url)
```

The result may look something like this:

```
http://localhost:5601/login?nextUrl=%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2F28dcde30-2258-11e8-82a3-af58d04b3c02%3F_g%3D%28%29&jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
```

## Audit log

The audit log feature is widely described in [📖docs for Elasticsearch plugin](/elasticsearch#audit). Kibana plugin has predefined dashboard representing collected audit data.

### Loading visualization

In the *Audit* tab of the ReadonlyREST Kibana app, there is a button that automatically creates a dashboard with some audit log specific visualizations.

![audit log tab](/files/x6SqW2sAMbA6SXr14wTN)

Click the *Load* button to load the dashboard and visualizations. An *Override* checkbox allows to reload the default dashboard and visualizations. It will override any previously loaded audit log dashboard.

![loading visualization](/files/626u12zHZp0NFAmYsCzi)

In detail, this feature creates three Kibana "saved objects":

* an index pattern for `readonlyrest_audit-*`
* a dashboard called `ReadonlyREST Audit Log`
* some visualizations

### Dashboard

The audit log dashboard, by default, has only a few basic visualizations. They cover security, access logs, and performance metrics.

## SAML

ReadonlyREST Enterprise supports service provider initiated via SAML. This connector supports both SSO (single sign on) and SLO (single log out). Here is how to configure it.

### Configure ReadonlyREST ES bridge

In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugin with a shared secret, that is: an arbitrarily long string.

### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

**⚠️IMPORTANT** the Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

### Kibana side

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  saml_serv1:
    enabled: true
    type: saml
    issuer: ror
    buttonName: "Partner's SSO Login"
    entryPoint: 'https://my-saml-idp/saml2/http-post/sso' # <-- identity Provider's URL, to request to sign on
    kibanaExternalHost: 'my.public.hostname.com' # <-- public URL used by the Identity Provider to call back Kibana with the "assertion" message
    protocol: http # <-- is the Kibana server listening for "http" "https" connections? Default: http
    usernameParameter: 'nameID'
    groupsParameter: 'memberOf'
    logoutUrl: 'https://my-saml-idp/saml2/http-post/slo'

    # OPTIONAL, advanced parameters
    # decryptionCert: /etc/ror/integration/certs/pub.crt
    # cert: /etc/ror/integration/certs/dag.crt
    # decryptionPvk: /etc/ror/integration/certs/decrypt_pvk.crt
    # issuer: saml_sso_idp
```

* `issuer`: issuer string to supply to identity provider during sign on request. Defaults to 'ror'
* `disableRequestedAuthnContext`: if truthy, do not request a specific authentication context. This is known to help when authenticating against Active Directory (AD FS) servers.
* `decryptionPvk`: Service Provider Private Key. Private key that will be used to attempt to decrypt any encrypted assertions that are received.
* cert: The downloadable certificate in IDP Metadata (file, absolute path)

For advanced SAML options, see [passport-saml documentation](https://github.com/bergie/passport-saml).

### Identity provider side

1. Enter the settings of your identity provider, create a new app.
2. Configure it using the information found by connecting to `http://my.public.hostname.com/ror_kbn_sso_saml_serv1/metadata.xml`

Example response:

```markup
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="onelogin_saml" ID="onelogin_saml">
  <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
    <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn_sso/notifylogout"/>
    <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
    <AssertionConsumerService index="1" isDefault="true" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://my.public.hostname.com/ror_kbn_sso/assert"/>
  </SPSSODescriptor>
</EntityDescriptor>
```

1. Create some users and some groups in the identity provider app
2. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set kibana in debug mode so ReadonlyREST will print the user profile).
3. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
4. If you want to use SAML for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting.

## OpenID Connect (OIDC)

ReadonlyREST Enterprise support OpenID Connect for authentication and authorization.

> soon we will create a specific guide only for OpenID, like the ones we have for SAML

Here is how to configure it.

### Configure ReadonlyREST ES bridge

This part is identical as seen in SAML connectors. In order for the user identity information to flow securely from Kibana to Elasticsearch, we need to set up the two plugin with a shared secret, that is: an arbitrarily long string.

### Elasticsearch side

Edit `readonlyrest.yml`

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

**⚠️IMPORTANT** the Basic HTTP auth credentials for the Kibana server are **still needed** for now, due to how Kibana works.

If you have configured OIDC with the `groupsParameter` ( *See below* ), you can also restrict ACL to specific groups:

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise instance #1 for group 1"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group1"]

    - name: "ReadonlyREST Enterprise instance #1 for group 2"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["group2"]

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

You may also use any custom claim from the OIDC `userinfo` token in ACL rules by using `{{jwt:assertion.<path_to_your_claim>}}` syntax. See the [Dynamic variables from JWT claims section](/elasticsearch#usage-examples) for more information. ( **TIP** : Do not forget the `assertion` prefix in front of you jsonpath. )

### Kibana side

We will assume the OpenID identity provider responds to port 8080 of localhost. In our example, we used Keycloak, an open source implementation of OpenID Connect identity provide.

Edit `kibana.yml` and append:

```yaml
readonlyrest_kbn.auth:
  signature_key: "my_shared_secret_kibana1(min 256 chars)"
  oidc_kc: 
            buttonName: "KeyCloak OpenID"
            type: "oidc"
            issuer: 'http://localhost:8080/auth/realms/ror'
            authorizationURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/auth'
            tokenURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/token'
            userInfoURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/userinfo'
            clientID: 'ror_oidc'
            clientSecret: '9f1d39c8-a211-460a-84b6-0a4a1499c455'
            scope: 'openid profile roles role_list email'
            usernameParameter: 'preferred_username'
            groupsParameter: 'groups'
            kibanaExternalHost: 'localhost:8080'
            logoutUrl: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/logout'
```

### Identity provider side

1. Enter the settings interface of your identity provider, and create a new OpenID app .
2. The redirect URL should be configured as `http://localhost:5601/*` assuming kibana is listening on localhost and on the default port.
3. Create some users and some groups in the identity provider if not present.
4. Check the user profile parameter names that the identity provider uses during the assertion callback ( **TIP**: set readonlyrest\_kbn.logLevel: debug\` in kibana.yml, so you will see the user profile how it's received from the identity provider right in the logs).
5. Match the name of the parameter used by the identity provider to carry the unique user ID (in the assertion message) to the `usernameParameter` kibana YAML setting.
6. If you want to use OpenID for authorization, take care of matching also the `groupsParameter` to the parameter name found in the assertion message to the kibana YAML setting. ( **TIP**: the `groupsParameter` must be present in the `userinfo` token of your OIDC provider.)
7. If kibana is accessed through a reverse proxy, kibanaExternalHost should be configured with the external hostname. if omitted, the default value is equals to `server.host:server.port` defined in kibana.yml. ( This parameter can be used also when kibana is bound to 0.0.0.0, for example, if using docker.)

## Load balancers

### Enable health check endpoint

Normally a load balancer needs a health check URL to see if the instance is still running, you can whitelist this Kibana path so the load balancer avoids a redirection to `/login`.

Edit `kibana.yml`

```
readonlyrest_kbn.whitelistedPaths: [".*/api/status$"]
```

### Session management with multiple Kibana instances

Each Kibana node stores user sessions in-memory. This will cause problems when using multiple Kibana instances behind a load balancer (especially without sticky sessions), as there would be no synchronization between nodes' sessions cache. To avoid this, session synchronization via an Elasticsearch index should be enabled. Follow these steps:

1. Come up with a string of at least 32 characters length or more to be used as the shared cookie encryption key, called `cookiePass`.
2. Open up `conf/kibana.yml` and add:
   * `readonlyrest_kbn.cookiePass: "generatedStringIn1step"` (example: "12345678901234567890123456789012")
   * `readonlyrest_kbn.cookieName` (custom cookie name - this property is optional, if not specified default cookie name would be `rorCookie`)
   * `readonlyrest_kbn.store_sessions_in_index: true` (enable session storage in index)
   * `readonlyrest_kbn.sessions_index_name: "someCustomIndexName"` (index name - this property is optional, if not specified default index would be `.readonlyrest_kbn_sessions`)
   * `readonlyrest_kbn.sessions_refresh_after: 1000` (time in milliseconds, describes how often sessions should be fetched from ES and refreshed for each node - optional, by default 2 seconds)
   * `readonlyrest_kbn.sessions_probe_interval_seconds: 15` (default 10s) how often should the browser poll Kibana to check if their session is still valid. Raise this value if you connect to Kibana through slow networks (i.e. VPN), or have very slow loading dashboards.
3. Add the above config in all Kibana nodes behind the load balancer, and restart them.

## Login screen tweaking

([PRO](https://readonlyrest.com/pro))

It is possible to customize the look of the login screen.

### Two column layout

By default,the login form appears in a single column view. ![one column](blob:https://imgur.com/f7514ca2-7f8f-4f96-aecd-09e7ea636b62)

But once title and subtitle are configured, it will switch to two columns for making room to the new text.

```
readonlyrest_kbn.login_title: "Some Title"
readonlyrest_kbn.login_subtitle: "Longer text <b>any HTML is supported<b/> including ifrmaes"
```

![two columns](https://i.imgur.com/Sqf1GIL.png)

### Add your company logo

It's recommended to use a transparent PNG, negative logo. Ideally a white foreground, and transparent background.

Open `config/kibana.yml` and append the following:

```
readonlyrest_kbn.login_custom_logo: 'https://.../logo.png'
```

### Add custom CSS/JS

You have the opportunity to inject HTML code right before the closing head tag (`</head>`).

Open `config/kibana.yml` and append the following:

```
readonlyrest_kbn.login_html_head_inject: '<style> * { color:red; }</style>'
```

## Kibana UI tweaking

([Enterprise](https://readonlyrest.com/enterprise))

With ReadonlyREST Enterprise, it's possible to inject custom CSS and Javascript to achieve a customized user experience for your users/tenants.

### Inject custom CSS in Kibana

Open `config/kibana.yml` and append the following:

```
readonlyrest_kbn.kibana_custom_css_inject: '.global-nav, kbnGlobalNav { background-color: green }'
```

Alternatively, it's possible to load the CSS from a file in the filesystem:

```
readonlyrest_kbn.kibana_custom_css_inject_file: '/tmp/custom.css'
```

### Inject custom JS in Kibana

```
readonlyrest_kbn.kibana_custom_js_inject: '$(".global-nav__logo").hide(); alert("hello!")'
```

### Map groups to aliases

You can provide a function, mapping group IDs to aliases of your choosing. To do so, add the following line to `config/kibana.yml`:

```
readonlyrest_kbn.groupsMapping: '(group) => group.toLowerCase()'
```

**⚠️IMPORTANT** The mapping function has to return a string. Otherwise, an error will be printed in kibana logs and the original group ID will be used as fallback. Also, if the mapping function is not specified, the original group ID value will be used.

## Tenancy index templating

([Enterprise](https://readonlyrest.com/enterprise))

When a tenants logs in for the first time, ReadonlyREST Enterprise will create the ".kibana" index associated to the tenancy. For example, it will create and initialize the ".kibana\_user1" index, where "user1" will store all the visualizations, dashboards, settings and index-patterns.

The issue is that "user1"'s user experience will be really raw as they will see a completely blank Kibana tenancy. Not even a default index pattern will be present. And this is particularly challenging if the tenant is supposed to be read-only (i.e. kibana\_access: "ro") because they won't even have privileges to create their own index-pattern, let alone any dashboards.

To fix this, ReadonlyREST Enterprise offers the possibility for administrators to create a template kibana index from which all the Kibana objects will be copied over to the newly initialized tenancy.

### How to use tenancy templating

An administrator will need to create the template tenancy, populate it with the default Kibana objects (index-patterns, dashboards) and configure ReadonlyREST Enterprise to take the index template it in use. Let's see this step by step:

#### Create the template tenancy

Let's start to add to our access control list (found in $ES\_PATH\_CONF/config/readonlyrest.yml, or ReadonlyREST App in Kibana) a local user "administrator" that will belong to two tenancies: the default one (stored in .kibana index), and the template one (stored in .kibana\_template index).

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

 users:
 - username: administrator
   auth_key: administrator:dev
   groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu
```

NB: If you know what you are doing, you can add a tenancy with kibana\_index: ".kibana\_template" adding a LDAP/SAML group to your administrative user.

### Configure the template tenancy

Now login as administrator in Kibana, hop into the "Template" tenancy, and start configuring the default UX for your future tenants. Add all the index patterns, create or import all the dashboards you want.

### Configure the template tenancy index in ReadonlyREST Enterprise

Open kibana.yml and add the following line:

```
readonlyrest_kbn.kibanaIndexTemplate: ".kibana_template"
```

Now, ReadonlyREST Enterprise will look for the ".kibana\_template" index, and try to copy over all its documents every time a new kibana index is initialized to support a new tenancy.

### Try it out

Restart Kibana with the new setting. Add a new tenancy to the ACL:

```yaml
readonlyrest:
  audit:
    enabled: true
    outputs:
    - type: index

  access_control_rules:

  - name: "::KIBANA-SRV::"
    auth_key: kibana:kibana
    verbosity: error

  - name: "Admin Tenancy"
    groups_any_of: ["Admins"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana"

  - name: "Template Tenancy"
    groups_any_of: ["Template"]
    verbosity: error
    kibana:
      access: admin
      index: ".kibana_template"

  # Newly added tenant!
  - name: user1
    auth_key: user1:passwd
    kibana:
      access: rw
      index: ".kibana_user1"

 users:
 - username: administrator
   auth_key: administrator:dev
   groups: ["Admins", "Template"] # can hop between two tenancies with top-left drop-down menu
`
```

Now try to login as user1, and ReadonlyREST Enterprise should initialize the index ".kibana\_user1" with all the index patterns and dashboards contained in the template tenancy.


# ReadonlyREST API

An authenticated API for changing the security settings without rebooting the ES cluster.

([Enterprise](https://readonlyrest.com/enterprise))

As an Enterprise user, you can benefit from automating the security configuration changes without the need to reboot the ES cluster.

Every request is **validated against ROR syntax first**, and rejected if syntactically or semantically incorrect. This adds to the safety of each change operation.

[Link to the API documentation](https://portal.readonlyrest.com/docs/swagger/master)


# ReadonlyREST DISA STIG Compliance

DISA STIG compliance analysis for deployments using the ReadonlyREST plugin as the authentication and authorization enforcement point.

This document answers DISA STIG (Web Server Security Requirements Guide) compliance questions for deployments where the ReadonlyREST Kibana plugin serves as the authentication and authorization enforcement point.

***

## 1. Session Management

### V-206351 — Server-side session management

**Status: Fully satisfied**

ReadonlyREST stores all session state on the server side. The client-side cookie contains only an encrypted session identifier — no session data is stored in the cookie itself.

Two storage backends are available:

* **In-memory** (default): suitable for single Kibana instance deployments.
* **Elasticsearch index** (recommended for HA): sessions are persisted in a dedicated index shared across all Kibana nodes.

See [Session management with multiple Kibana instances](/kibana#session-management-with-multiple-kibana-instances) for configuration details.

***

### V-206396 — Invalidate session identifiers upon logout or session termination

**Status: Fully satisfied**

On logout, ReadonlyREST deletes the session record from the server-side store and clears the client cookie in the same operation. Once a session is deleted, any subsequent request presenting the old cookie is rejected and redirected to the login page.

For multi-tab browser scenarios, a background probe running in each tab detects the logout event and immediately redirects all open tabs to login.

***

### V-206397 — Cookie security settings (HttpOnly, Secure flags; SameSite)

**Status: Fully satisfied**

ReadonlyREST sets appropriate security flags on every session cookie. See [Cookie settings](/kibana#cookie-settings) for defaults, behavior, and configurable attributes.

***

### V-206398 — Accept only system-generated session identifiers

**Status: Fully satisfied**

ReadonlyREST rejects any session identifier it did not create through two independent checks:

1. The cookie is encrypted with HAPI Iron (AES-256 + HMAC-SHA256) using a secret key known only to the ReadonlyREST deployment. Any tampered or externally crafted cookie fails decryption.
2. Even a structurally valid identifier must exist as an active record in the server-side session store. Identifiers not present in the store are rejected.

***

### V-206399 — Session ID generation using FIPS 140-2 approved RNG

**Status: Conditionally satisfied — depends on the Node.js runtime**

ReadonlyREST generates session IDs using the Node.js cryptographic random number generator (`crypto.randomBytes()`), which uses the operating system CSPRNG. When the Kibana process is started with a FIPS-validated Node.js build or with the `--enable-fips` flag, this call automatically uses the FIPS-validated OpenSSL DRBG, satisfying the requirement.

ReadonlyREST inherits the cryptographic posture of the Kibana runtime — enabling FIPS mode is an infrastructure and deployment concern, not a ReadonlyREST configuration option.

***

### V-206400 — Non-reproducible session identifiers

**Status: Fully satisfied**

Session IDs are UUID v4 values generated from 122 bits of independent random entropy per call. The same RNG call never produces the same output.

***

### V-206401 — Session ID length ≥ 128 bits

**Status: Fully satisfied**

UUID v4 is a 128-bit value. ReadonlyREST uses it as the session key.

***

### V-206402 — Session ID character set (A–Z, a–z, 0–9 minimum)

**Status: Fully satisfied**

UUID v4 is represented using hexadecimal characters (`0–9`, `a–f`), which satisfies the minimum alphanumeric requirement.

***

### V-206403 — Session ID entropy ≥ 50% of ID length

**Status: Fully satisfied**

UUID v4 carries 122 bits of random entropy in a 128-bit value — 95% entropy density, well above the 50% threshold.

***

### V-206414 — Absolute session timeout ≤ 8 hours

**Status: Partially satisfied — requires configuration; note architectural limitation**

ReadonlyREST enforces a configurable session timeout. For STIG compliance this must be set to 480 minutes (8 hours) or less. See [Session timeout](/kibana#session-timeout) for configuration details.

**Limitation:** ReadonlyREST's timeout is a sliding inactivity window — each user action resets the clock. There is no hard absolute cap on total session lifetime from the moment of login. A continuously active user will not be forcibly logged out after 8 hours. ReadonlyREST creates and manages its own session independently of the IdP after the initial authentication, so there is no external control point that can enforce an absolute lifetime on an active ReadonlyREST session. Strict absolute session lifetime enforcement is a known limitation of the current ReadonlyREST implementation.

***

### V-206415 — Inactive/idle session timeout

**Status: Fully satisfied — requires configuration**

ReadonlyREST terminates idle sessions and cleans them up automatically. See [Session timeout](/kibana#session-timeout) for configuration details.

***

## 2. Session IP Binding

### V-264360 — Restrict management sessions to consistent inbound source IP

### V-264361 — Restrict user sessions to consistent inbound source IP

**Status: Not implemented**

ReadonlyREST does not bind sessions to an originating IP address. A session token is valid regardless of the IP from which it is presented.

IP session binding is a known limitation of the current ReadonlyREST implementation.

***

## 3. Authorization & Access Control

### V-206355 — Enforce approved authorizations for logical access (RBAC)

**Status: Fully satisfied**

ReadonlyREST is the RBAC enforcement point for Kibana. Access control is driven by the [ReadonlyREST ACL](/elasticsearch#readonlyrest-acl) — configured in the Elasticsearch plugin's `readonlyrest.yml` — which maps authenticated identities (users, SAML attributes, OIDC claims, group memberships) to permissions. The following are enforced per session:

* **Kibana tenants / spaces:** each user or group is confined to a specific Kibana space, isolating dashboards and saved objects.
* **Elasticsearch index access:** users can only query the indices permitted by their ACL block.
* **API path restrictions:** specific Kibana API endpoints can be allowed or denied per user or group.
* **Access level:** read-only, read-write, or admin access within Kibana is configurable per ACL block.

***

### V-206394 — Prohibit anonymous user access / prevent unauthorized changes

**Status: Fully satisfied**

Every request passing through ReadonlyREST requires a valid authenticated session. Unauthenticated requests are redirected to the login page before reaching Kibana. The only paths that bypass authentication are health-check endpoints, which expose no user data and allow no modifications. See [Enable health check endpoint](/kibana#enable-health-check-endpoint) for configuring whitelisted paths.

***

### V-264342 — Individual authentication before shared account access

**Status: Fully satisfied at the plugin level**

ReadonlyREST requires each session to be established through an individual authentication event — credentials, a SAML assertion, or an OIDC token exchange. Every session is tied to a specific authenticated identity and carries its own server-side record.

Whether multiple people share the same IdP credentials is outside ReadonlyREST's scope — that is an identity provider concern.

***

## 4. Authentication

### V-222523 — Multi-Factor Authentication for privileged accounts *(CAT I)*

**Status: Conditionally satisfied — depends on IdP configuration**

ReadonlyREST does not implement MFA natively. Authentication is fully delegated to external identity providers via SAML or OIDC. When the IdP enforces MFA, that requirement is satisfied before ReadonlyREST issues a session — ReadonlyREST neither bypasses nor weakens IdP-side MFA policies.

Deployments not using SAML or OIDC have no MFA enforcement point at the ReadonlyREST Kibana plugin layer. MFA for such deployments requires migrating to SAML or OIDC with an IdP that enforces MFA.

***

### V-222543 — Plaintext credential transmission *(CAT I)*

**Status: Conditionally satisfied — requires TLS configuration**

The exposure of credentials in transit depends on the authentication method in use:

* **SAML / OIDC deployments:** ReadonlyREST does not handle raw credentials directly — authentication relies on SAML assertions or OIDC token exchanges. Only session tokens are transmitted between the browser and Kibana, and these are exposed if TLS is not configured.
* **Basic auth deployments (`auth_key`):** Username and password are transmitted from the browser on every request. Without TLS, credentials are exposed in plaintext on every authenticated request.

TLS is mandatory for STIG compliance regardless of the authentication method. See [SSL/TLS server](/kibana#ssltls-server) for configuration details.

***

## 5. Transport Security

### V-222596 — TLS protocol version enforcement *(CAT I)*

**Status: Conditionally satisfied — requires configuration**

When TLS is enabled, ReadonlyREST enforces a minimum protocol baseline: TLSv1.0, SSLv2, and SSLv3 are always disabled. TLSv1.1, TLSv1.2, and TLSv1.3 are permitted by default.

DISA STIG requires a minimum of TLS 1.2. See [SSL/TLS server](/kibana#ssltls-server) for how to restrict the allowed protocols. ReadonlyREST respects this setting and applies the configured protocol restrictions across all TLS connections.

***

### V-222571 — Cryptographic algorithms *(CAT I)*

**Status: Fully satisfied**

ReadonlyREST uses only modern, approved cryptographic algorithms internally:

| Usage                               | Algorithm                                   |
| ----------------------------------- | ------------------------------------------- |
| Session cookie encryption           | AES-256 (HAPI Iron — AES-CBC + HMAC-SHA256) |
| Session and tenancy data encryption | AES (CryptoJS)                              |
| License token verification          | ES512 (ECDSA with SHA-512)                  |

No deprecated algorithms are present in the ReadonlyREST codebase. MD5, SHA-1, DES, and RC4 are not used.

***

## 6. Audit & Logging

### V-222452 — Failed login attempt logging *(CAT II)*

**Status: Conditionally satisfied — requires audit configuration**

The ReadonlyREST Elasticsearch plugin provides formal structured audit logging for all access control decisions, including rejected authentication attempts. When enabled, FORBIDDEN events are written to a timestamped Elasticsearch index (default: `readonlyrest_audit-YYYY-MM-DD`, configurable via `index_template`). The output format is structured by default and can be customized via [serializers](/elasticsearch/audit#predefined-serializers), including support for [ECS (Elastic Common Schema)](/elasticsearch/audit#using-ecs-serializer). See [Audit configuration](/elasticsearch/audit#configuration) for how to enable audit logging.

The Kibana plugin additionally logs rejected attempts at `INFO` level in the Kibana application log (e.g. "Could not login in: …"), visible in standard production deployments.

***

### V-222463 — Privileged user action logging *(CAT II)*

**Status: Conditionally satisfied — requires audit configuration**

Every Kibana user action — including actions performed by privileged users — translates into one or more Elasticsearch REST API calls. When audit logging is enabled in the ReadonlyREST Elasticsearch plugin, every such request is audited with no exceptions based on privilege level. See [Audit configuration](/elasticsearch/audit#configuration) for how to enable it. For additional field coverage (request path, ACL history), see [Predefined serializers](/elasticsearch/audit#predefined-serializers).

***

### V-222507 — Audit log integrity *(CAT II)*

**Status: Partially satisfied — access control covered, tamper-detection outside scope**

ReadonlyREST writes audit events to a timestamped Elasticsearch index or data stream (default name: `readonlyrest_audit-YYYY-MM-DD`, configurable). The ACL can be used to prevent unauthorized modification or deletion of audit data. See [Protecting the audit index](/elasticsearch/audit#protecting-the-audit-index) for the required ACL rule.

Tamper detection (log signing, hash chaining) and audit log backup are outside ReadonlyREST scope and must be addressed at the Elasticsearch/infrastructure layer.

***

## 7. HTTP Security Headers

### V-222602 — Content-Security-Policy *(CAT II)*

**Status: Partially satisfied — architectural limitation in Kibana**

`Content-Security-Policy` is managed by Kibana, not ReadonlyREST. Kibana 7.x and 8.x do not set a CSP header by default — configure it explicitly in `kibana.yml`.

Kibana's frontend architecture hardcodes `'unsafe-inline'` in `style-src` regardless of configuration — this cannot be removed without breaking the UI. Additionally, Kibana 7.x and 8.x hardcode `'unsafe-eval'` in `script-src`; this was removed in Kibana 9.x. The configuration below restricts what operators can control:

Kibana 7.9 – 7.13 (only `csp.rules` is available):

```yaml
csp.rules:
  - "script-src 'self'"
  - "style-src 'self'"
  - "object-src 'none'"
```

Kibana 7.14+ and 8.x (per-directive settings):

```yaml
csp.script_src: ["'self'"]
csp.style_src: ["'self'"]
csp.object_src: ["'none'"]   # 8.x only; not available in 7.x
```

The resulting effective policy will always contain `'unsafe-inline'` in `style-src` (all versions) and `'unsafe-eval'` in `script-src` (Kibana 7.x and 8.x). These weaken the CSP and should be documented as accepted risks in the system's security assessment.

***

### X-Frame-Options — Clickjacking prevention *(CAT II)*

**Status: Outside ReadonlyREST scope — requires Kibana configuration**

`X-Frame-Options` is managed by Kibana, not ReadonlyREST. Configure it explicitly in `kibana.yml`:

```yaml
server.customResponseHeaders:
  X-Frame-Options: "DENY"
```

***

### Strict-Transport-Security (HSTS) *(CAT II)*

**Status: Outside ReadonlyREST scope — requires Kibana configuration**

`Strict-Transport-Security` is managed by Kibana, not ReadonlyREST. Configure it explicitly in `kibana.yml` (requires TLS to be enabled):

```yaml
server.customResponseHeaders:
  Strict-Transport-Security: "max-age=31536000; includeSubDomains"
```

***

## Summary

| STIG Control                                                                            | Requirement                              | CAT | Status                                                                                  |
| --------------------------------------------------------------------------------------- | ---------------------------------------- | --- | --------------------------------------------------------------------------------------- |
| [V-206351](#v-206351-server-side-session-management)                                    | Server-side session state                | II  | ✅ Fully satisfied                                                                       |
| [V-206396](#v-206396-invalidate-session-identifiers-upon-logout-or-session-termination) | Invalidate on logout                     | II  | ✅ Fully satisfied                                                                       |
| [V-206397](#v-206397-cookie-security-settings-httponly-secure-flags-samesite)           | HttpOnly, Secure, SameSite               | II  | ✅ Fully satisfied                                                                       |
| [V-206398](#v-206398-accept-only-system-generated-session-identifiers)                  | Only system-generated SIDs               | II  | ✅ Fully satisfied                                                                       |
| [V-206399](#v-206399-session-id-generation-using-fips-140-2-approved-rng-high)          | FIPS 140-2 RNG                           | I   | ⚠️ Depends on Node.js FIPS mode                                                         |
| [V-206400](#v-206400-non-reproducible-session-identifiers)                              | Non-reproducible SIDs                    | II  | ✅ Fully satisfied                                                                       |
| [V-206401](#v-206401-session-id-length-128-bits)                                        | SID ≥ 128 bits                           | II  | ✅ Fully satisfied                                                                       |
| [V-206402](#v-206402-session-id-character-set-az-az-09-minimum)                         | SID charset A–Z, a–z, 0–9                | II  | ✅ Fully satisfied                                                                       |
| [V-206403](#v-206403-session-id-entropy-50-of-id-length)                                | Entropy ≥ 50% of SID length              | II  | ✅ Fully satisfied                                                                       |
| [V-206414](#v-206414-absolute-session-timeout-8-hours)                                  | Absolute timeout ≤ 8 hours               | II  | ⚠️ Sliding timeout only; requires configuration                                         |
| [V-206415](#v-206415-inactiveidle-session-timeout)                                      | Idle/inactivity timeout                  | II  | ✅ Satisfied — requires configuration                                                    |
| [V-264360](#v-264360-restrict-management-sessions-to-consistent-inbound-source-ip)      | IP binding — management sessions         | II  | 🔴 Not implemented                                                                      |
| [V-264361](#v-264361-restrict-user-sessions-to-consistent-inbound-source-ip)            | IP binding — user sessions               | II  | 🔴 Not implemented                                                                      |
| [V-206355](#v-206355-enforce-approved-authorizations-for-logical-access-rbac)           | RBAC logical access control              | II  | ✅ Fully satisfied                                                                       |
| [V-206394](#v-206394-prohibit-anonymous-user-access--prevent-unauthorized-changes)      | No anonymous access                      | II  | ✅ Fully satisfied                                                                       |
| [V-264342](#v-264342-individual-authentication-before-shared-account-access)            | Individual auth before shared access     | II  | ✅ Satisfied at plugin level                                                             |
| [V-222523](#v-222523-multi-factor-authentication-for-privileged-accounts-cat-i)         | MFA for privileged accounts              | I   | ⚠️ Depends on IdP — not available without SAML/OIDC                                     |
| [V-222543](#v-222543-plaintext-credential-transmission-cat-i)                           | Plaintext credential transmission        | I   | ⚠️ Requires TLS configuration                                                           |
| [V-222596](#v-222596-tls-protocol-version-enforcement-cat-i)                            | TLS protocol version (min. 1.2)          | I   | ⚠️ Conditionally satisfied — requires configuration                                     |
| [V-222571](#v-222571-cryptographic-algorithms-cat-i)                                    | Cryptographic algorithms (no deprecated) | I   | ✅ Fully satisfied                                                                       |
| [V-222452](#v-222452-failed-login-attempt-logging-cat-ii)                               | Failed login attempt logging             | II  | ⚠️ Conditionally satisfied — requires audit configuration                               |
| [V-222463](#v-222463-privileged-user-action-logging-cat-ii)                             | Privileged user action logging           | II  | ⚠️ Conditionally satisfied — requires audit configuration                               |
| [V-222507](#v-222507-audit-log-integrity-cat-ii)                                        | Audit log integrity                      | II  | ⚠️ Partially satisfied — access control via ACL; tamper-detection outside scope         |
| [V-222602](#v-222602-content-security-policy-cat-ii)                                    | Content-Security-Policy                  | II  | ⚠️ Partially satisfied — style-src 'unsafe-inline' is a Kibana architectural limitation |

**Legend:**

* ✅ — satisfied (by default or via documented configuration)
* ⚠️ — requires configuration; control is not met if skipped
* 🔴 — not implemented


# For ECK

ReadonlyREST plugins officially support installation on Elasticsearch and Kibana working in Kubernetes cluster and managed by the [ECK operator](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html).

You can choose one of the following installation methods:

## Installation methods

### Using our Docker images from Docker Hub

The easiest and fastest method to start with a ROR-powered ELK stack on ECK is to use our official images. We will show you how to do it in the following sections:

#### Elasticsearch node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Elasticsearch cluster specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-elasticsearch.html) we should do it as below:

```yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # check https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest
  image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest 
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
    podTemplate:
        spec:
          containers:
            - name: elasticsearch
              # we have to run our image as root (id: 0) - after the required patching step Elasticsearch will be run using "elasticsearch" user (id: 1000)
              securityContext:
                runAsNonRoot: false
                runAsUser: 0
                runAsGroup: 0
              env:
                # we have to explicitly agree to patch the ES binaries (the patching step will be done only once)
                - name: I_UNDERSTAND_AND_ACCEPT_ES_PATCHING
                  value: "yes"
                # these two passwords are used by "elastic-internal" and "elastic-internal-probe" users - these users are used by ECK
                - name: INTERNAL_USR_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal
                - name: INTERNAL_PROBE_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal-probe
                # Kibana service account to handle internal Kibana requests 
                - name: KIBANA_SERVICE_ACCOUNT_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-kibana-user
                      key: token
              # the initial readonlyrest.yml file loaded by ROR plugin during ES startup
              volumeMounts:
                - name: config-ror
                  mountPath: /usr/share/elasticsearch/config/readonlyrest.yml
                  subPath: readonlyrest.yml
          volumes:
            - name: config-ror
              configMap:
                name: config-readonlyrest.yml
```

**ReadonlyREST initial settings**

The initial settings can be defined as ConfigMap like this:

```yaml
apiVersion: v1
data:
   readonlyrest.yml: |
     readonlyrest:
       access_control_rules:

       - name: "ELASTIC-INTERNAL"
         verbosity: error
         auth_key: "elastic-internal:${INTERNAL_USR_PASS}"
     
       - name: "ELASTIC INTERNAL PROBE"
         verbosity: error
         auth_key: "elastic-internal-probe:${INTERNAL_PROBE_PASS}"
       
       - name: "Kibana service account"
         verbosity: error
         token_authentication:
           token: "Bearer ${KIBANA_SERVICE_ACCOUNT_TOKEN}" 
           username: service_account

       - name: "Admin access"
         type: allow
         auth_key: "admin:admin"

kind: ConfigMap
metadata:
  name: config-readonlyrest.yml
```

Notice that if you use ROR Enterprise, you can take advantage of the [Cluster-wide Settings](https://docs.readonlyrest.com/pages/-MIs7bgEJjceIud5fsz9#cluster-wide-settings-vs-readonlyrest.yml) functionality and reload configuration on all your nodes without restarting K8s' PODs.

#### Kibana node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Kibana instance specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-kibana.html) we should do it as follows:

```yaml
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # check https://hub.docker.com/r/beshultd/kibana-readonlyrest
  image: beshultd/kibana-readonlyrest:8.14.3-ror-latest 
  count: 1
  elasticsearchRef:
    name: quickstart
  config:
    # define ROR Kibana settings 
    # readonlyrest_kbn.store_sessions_in_index: true # we have to set it to true when we define more than one node
    readonlyrest_kbn.cookiePass: "12345678901234567890123456789012345678901234567890"
  podTemplate:
    spec:
      # we have to run our image as root (id: 0) - after the required patching step Kibana will be run using "kibana" user (id: 1000)
      securityContext:
        runAsNonRoot: false
        runAsUser: 0
        runAsGroup: 0
      containers:
        - name: kibana
          env:
            # we have to explicitly agree to patch the KBN binaries (the patching step will be done only once)
            - name: I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING
              value: "yes"
            # we have to provide a ROR license if we want to use ROR Pro or Enterprise (if the license is not provided, then ROR Free is used)
            - name: ROR_ACTIVATION_KEY
              value: "<YOUR_ACTIVATION_KEY/>"
```

And these are all differences we need to make to run [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html) with ReadonlyREST instead of X-Pack security.

### Using Docker images built by you and stored in your registry

As you probably noticed, our docker images have to be run with root privileges. It's due to legal reasons and you, as a user, have to confirm that you agree to do the patching steps. If running a pod with root privileges is something you cannot accept, you can create your own image with the patching step done at the image creation level (not at runtime as our image does) and save it your your own registry.

<details>

<summary>Expand to see details</summary>

#### Elasticsearch with ROR custom image

The minimal Elasticsearch with ROR image definition looks like this:

```
# 'Dockerfile' file content
ARG ES_VERSION
FROM docker.elastic.co/elasticsearch/elasticsearch:${ES_VERSION}

ARG ES_VERSION
ARG ROR_VERSION

USER elasticsearch
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install --batch "https://portal.readonlyrest.com/download/es?esVersion=$ES_VERSION&pluginVersion=$ROR_VERSION&email=[YOUR-EMAIL-ADDRESS]"
USER root
RUN /usr/share/elasticsearch/jdk/bin/java -jar /usr/share/elasticsearch/plugins/readonlyrest/ror-tools.jar patch --I_UNDERSTAND_AND_ACCEPT_ES_PATCHING yes
USER 1000:0
```

And then you can build it as follows:

```bash
docker build --build-arg ES_VERSION=8.14.3 --build-arg ROR_VERSION=1.59.0 -t elasticsearch-with-ror  .
```

And place the `elasticsearch-with-ror` image in your registry.

#### Elasticsearch node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Elasticsearch cluster specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-elasticsearch.html) we should do it as below:

```yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # this is the image from your registry
  image: elasticsearch-with-ror
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
    podTemplate:
        spec:
          containers:
            - name: elasticsearch
              env:
                # these two passwords are used by "elastic-internal" and "elastic-internal-probe" users - these users are used by ECK
                - name: INTERNAL_USR_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal
                - name: INTERNAL_PROBE_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal-probe
                # Kibana service account to handle internal Kibana requests 
                - name: KIBANA_SERVICE_ACCOUNT_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-kibana-user
                      key: token
              # the initial readonlyrest.yml file loaded by ROR plugin during ES startup
              volumeMounts:
                - name: config-ror
                  mountPath: /usr/share/elasticsearch/config/readonlyrest.yml
                  subPath: readonlyrest.yml
          volumes:
            - name: config-ror
              configMap:
                name: config-readonlyrest.yml
```

Check [the section from the previous paragraph](#readonlyrest-initial-settings) to see how to define `config-ror`.

#### Kibana with ROR custom image

The minimal Kibana with ROR image definition looks like this:

```
# 'Dockerfile' file content
ARG KBN_VERSION

FROM docker.elastic.co/kibana/kibana:${KBN_VERSION}

ARG KBN_VERSION
ARG ROR_VERSION

RUN /usr/share/kibana/bin/kibana-plugin install "https://portal.readonlyrest.com/download/kbn?esVersion=$KBN_VERSION&pluginVersion=$ROR_VERSION&edition=kbn_universal&email=[YOUR-EMAIL-ADDRESS]"
USER root
RUN /usr/share/kibana/node/bin/node plugins/readonlyrestkbn/ror-tools.js patch --I_UNDERSTAND_AND_ACCEPT_KBN_PATCHING=yes && \
    chown -R kibana:kibana /usr/share/kibana/config
USER 1000:0
```

And then you can build it as follows:

```bash
docker build --build-arg KBN_VERSION=8.14.3 --build-arg ROR_VERSION=1.59.0 -t kibana-with-ror  .
```

And place the `kibana-with-ror` image in your registry.

#### Kibana node with ReadonlyREST plugin

If we want to add the ReadonlyREST plugin to the simple Kibana instance specification from [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-deploy-kibana.html) we should do it as follows:

```yaml
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
  name: quickstart
spec:
  version: 8.14.3
  # this is the image from your registry
  image: kibana-with-ror
  count: 1
  elasticsearchRef:
    name: quickstart
  config:
    # define ROR Kibana settings 
    # readonlyrest_kbn.store_sessions_in_index: true # we have to set it to true when we define more than one node
    readonlyrest_kbn.cookiePass: "12345678901234567890123456789012345678901234567890"
  podTemplate:
    spec:
      containers:
        - name: kibana
          env:
            # we have to provide a ROR license if we want to use ROR Pro or Enterprise (if the license is not provided, then ROR Free is used)
            - name: ROR_ACTIVATION_KEY
              value: "<YOUR_ACTIVATION_KEY/>"
```

And these are all differences we need to make to run [ECK Quickstart](https://www.elastic.co/guide/en/cloud-on-k8s/current/k8s-quickstart.html) with ReadonlyREST instead of X-Pack security.

</details>

### Using an Init Container

{% hint style="warning" %}
This is not a recommended method.
{% endhint %}

It's possible to install ROR using an [Init Container](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). Unfortunately, this method is not as easy to use in the case of ROR. This is due to the required ReadonlyREST patching step (for both plugins), which allows ROR to work with Elasticsearch/Kibana. During this step, the ROR patcher tool modifies some of the Elasticsearch and Kibana binaries. Different versions of Elasticsearch/Kibana require different binaries to be modified. In order to do this in a Kubernetes-based environment, we need to mount appropriate locations from the Elasticsearch/Kibana POD to the init container that will install ROR. The volumes should have write permissions because of the changes made by the ROR patcher. You can still use this method, but as you can see it's not that easy.

<details>

<summary>Expand to see details</summary>

If you are still interested in this one, please take a look at the examples in our repository:

* [Elasticsearch with ROR installed using the Init Container method](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/v1.58.0_es8.14.3/docker-envs/eck/kind-cluster/ror/es.yml)
* [Kibana with ROR installed using the Init Container method](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/blob/v1.58.0_es8.14.3/docker-envs/eck/kind-cluster/ror/kbn.yml)

</details>

## Handling asynchronous ROR startup

ReadonlyREST starts asynchronously during Elasticsearch initialization. In some cases, there may be a brief moment when the pod reports as ready, but ROR is still starting up. During this window, requests may receive error responses (403/401/503, depending on your settings).

If you need to ensure that the pod is only marked as ready after ROR has fully started, you can use the sidecar pattern described below.

{% hint style="info" %}
**Note:** This is an optional pattern and not required for most deployments. Elastic [does not recommend](https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s/readiness-probe#k8s_elasticsearch_versions_8_2_0_and_later) overriding the default Elasticsearch readiness probe. The sidecar pattern allows you to achieve ROR readiness checking without modifying the main Elasticsearch probe.
{% endhint %}

### Sidecar pattern for ROR readiness

The following example extends the basic Elasticsearch configuration by adding a sidecar container (`ror-ready-gate`) that includes its own readiness probe. This probe checks if ROR is ready to handle requests by testing the cluster health endpoint with proper authentication:

```yaml
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
  name: quickstart
spec:
  version: 8.14.3
  image: beshultd/elasticsearch-readonlyrest:8.14.3-ror-latest
  nodeSets:
  - name: default
    count: 1
    config:
      node.store.allow_mmap: false
    podTemplate:
        spec:
          containers:
            - name: elasticsearch
              securityContext:
                runAsNonRoot: false
                runAsUser: 0
                runAsGroup: 0
              env:
                - name: I_UNDERSTAND_AND_ACCEPT_ES_PATCHING
                  value: "yes"
                - name: INTERNAL_USR_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal
                - name: INTERNAL_PROBE_PASS
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-es-internal-users
                      key: elastic-internal-probe
                - name: KIBANA_SERVICE_ACCOUNT_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: quickstart-kibana-user
                      key: token
                # Optional: little speed up ROR initialization
                - name: ES_JAVA_OPTS
                  value: "-Dcom.readonlyrest.settings.loading.delay=0s -Dcom.readonlyrest.settings.loading.attempts.count=1"
              volumeMounts:
                - name: config-ror
                  mountPath: /usr/share/elasticsearch/config/readonlyrest.yml
                  subPath: readonlyrest.yml

            # Sidecar container that waits for ROR to be ready
            - name: ror-ready-gate
              image: curlimages/curl:8.6.0
              command: ["sh", "-c", "sleep infinity"]
              securityContext:
                runAsNonRoot: true
                runAsUser: 1000
                runAsGroup: 1000
                allowPrivilegeEscalation: false
              readinessProbe:
                exec:
                  command:
                    - sh
                    - -c
                    - |
                      curl -sf -k --max-time 2 \
                        -u "elastic-internal-probe:$(cat /mnt/probe-user/elastic-internal-probe | tr -d '\n')" \
                        https://127.0.0.1:9200/_cluster/health > /dev/null
                periodSeconds: 5
                timeoutSeconds: 5
                failureThreshold: 60
              volumeMounts:
                - name: probe-user
                  mountPath: /mnt/probe-user
                  readOnly: true

          volumes:
            - name: config-ror
              configMap:
                name: config-readonlyrest.yml
            - name: probe-user
              secret:
                secretName: quickstart-es-internal-users
```

### How it works

1. The `ror-ready-gate` sidecar container runs alongside Elasticsearch
2. Its readiness probe makes authenticated requests to the cluster health endpoint
3. The pod is only marked ready when both containers pass their readiness checks
4. This ensures ROR is fully initialized before the pod receives traffic

The readiness probe will retry for up to 5 minutes (60 failures × 5 second period) before marking the pod as failed.

## Notes

* [ReadonlyREST SSL](https://docs.readonlyrest.com/elasticsearch#encryption) can be used but it's simpler to leave `xpack.security.enabled: true` and use X-Pack SSL instead
* To figure out how to obtain the ROR License see [our guide](/universal-builds#how-to-activate-proenterprise-features-a-universal-build).

## Example

You can check the ROR-powered ECK Quickstart example by running our [one-liner script](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/tree/master/docker-envs/eck). It supports MacOS and Linux.


# Universal Builds

Starting from ReadonlyREST 1.44.0, our Kibana plugins are released in a unified format.

## What is a universal build?

A universal build is a single Kibana plugin deliverable that is compatible with a given Kibana version.

![](/files/4cUNqiXKDI1vUt0mjeIM)

If not activated, a universal build will behave exactly like the old "Free" edition.

Some activation keys can be used to unlock PRO and Enterprise features. If a trial activation key is used, these features availability will be limited in time.

## How to install a universal build?

The same way it worked before, just download it the same way you used to, from the [Downloads page](https://readonlyrest.com/download) and install it as usual.

## How to activate PRO/Enterprise features a universal build?

Check out our new [customer portal](https://readonlyrest.com/customer). If your email is associated to a customer contract with a valid ongoing subscription, you will be able to obtain an activation key. Otherwise, you will be able to obtain a trial activation key.

There are a few ways to pass the activation key to Kibana. Regardless of which one you use, the result will be that Kibana will save the activation key to an encrypted Elasticsearch index, so other Kibana instances will pick up the new activation key.

### Via Environmental variable

This method is useful for Docker deployments. Just set the `ROR_ACTIVATION_KEY` environment variable to the activation key you obtained from the customer portal.

```bash
$ export ROR_ACTIVATION_KEY=<your activation key>
$ bin/kibana 
```

### Via our plugin license API

Once Kibana is up and running, you can send a HTTP request to Kibana.

```
POST http://<kibana-host-with-ror>:5601/api/ror/license?overwrite=true
{
  "token": "your activation key" 
}
```

### Via ROR\_ACTIVATION\_KEY.txt file

The universal build is a zip archive. You can add a small text file with your activation key to this archive before installing it, so it will be picked up automatically at the first boot.

* The text file should be called `ROR_ACTIVATION_KEY.txt`
* It should contain your secret activation key string (no spaces, no new lines)
* It should be added to the **root directory** of the plugin zip archive

Now install the plugin file, start Kibana, and the activation key should be loaded.

### Interactive activation key management

This method is useful for manual deployments.

1. Start Kibana in the default "Free" edition mode
2. Login in Kibana as an `admin` or `unrestricted` kibana access
3. Toggle the ROR Menu on the top right
4. Click on the "Free" text tag

![](/files/kPw4vkKfDfGY7Rmp5feg)

1. Enter the activation key you obtained from the customer portal in the license management UI

![](/files/OCzs1E3DLwo0NeHTubC3)

### Change Activation key retrieval mode via kibana.yml

In the kibana.yml configuration file, you have the option to specify the mode for retrieving activation keys. Setting this parameter effectively means that all other activation key retrieval methods will be disregarded.

#### Default behaviour

If there is no `kibana.yml` license config provided or `activationKeyRetrievalMode: "all"` defined:

```yaml
readonlyrest_kbn:
    license:
        activationKeyRetrievalMode: "all" # "file" | "env" | "all" | "none"
```

Then the order of activation key validation is:

1. Retrieve from index
2. ENV variable
3. File
4. Default Activation key (which means free license) If AK is found in any of the aforementioned locations, the verification process will be halted.

#### From environment variable retrieval option

you can add to `kibana.yml`:

```yaml
readonlyrest_kbn:
    license:
        activationKeyRetrievalMode: "env" # "file" | "env" | "all" | "none"
```

#### From a file retrieval option

you can add to `kibana.yml`:

```yaml
readonlyrest_kbn:
    license:
        activationKeyRetrievalMode: "file" # "file" | "env" | "all" | "none"
        activationKeyFilePath: /tmp/activation.key
```


# Examples


# Multi-tenancy Elastic Stack (Enterprise)

([Enterprise](https://readonlyrest.com/enterprise))

This document will guide you through setting up your Elasticsearch and Kibana stack with ReadonlyREST such that:

* There will be two tenancies: one for Sales and one for Ops department.
* In each tenancy, 3 users will be able to login into Kibana using their own set of credentials
* Each tenancy will contain **its own, independant** Kibana dashboards, visualizations and index patterns.
* Each user within a tenancy may be restricted to visualizing distinct subsets of the whole data contained in Elasticsearch (i.e. only certain indices).

### Users and capabilities

For this tutorials, we want to have three users per tenancy, each of them has a distinct access level to a shared Kibana tenancy (set of dashboards and settings).

#### Sales Department

|                                              | "sales\_admin" | "sales\_rw\_usr" | "sales\_ro\_usr" |
| -------------------------------------------- | -------------- | ---------------- | ---------------- |
| Can create,edit,delete Sales' dashboards     | ✅              | ✅                |                  |
| Can change Kibana settings for Sales         | ✅              | ✅                |                  |
| Only sees "sales\_logstash\*" data from 2018 |                |                  | ✅                |
| Can see "add","delete","edit" buttons        | ✅              | ✅                |                  |
| "dev-tools" Kibana App is hidden             | ✅              | ✅                |                  |
| "readonlyrest" Kibana App is hidden          | ✅              |                  |                  |

#### Ops Department

|                                        | "ops\_admin" | "ops\_rw\_usr" | "ops\_ro\_usr" |
| -------------------------------------- | ------------ | -------------- | -------------- |
| Can create,edit,delete Ops dashboards  | ✅            | ✅              |                |
| Can change Kibana settings for Ops     | ✅            | ✅              |                |
| Only sees ops\_logstash data from 2018 |              |                | ✅              |
| Can see "add","delete","edit" buttons  | ✅            | ✅              |                |
| "dev-tools" Kibana App is hidden       | ✅            | ✅              |                |
| "readonlyrest" Kibana App is hidden    | ✅            |                |                |

> NB: ReadonlyREST for Elastisearch and ReadonlyREST Enterprise for Kibana have an great amount of features like groups, connector for external systems like LDAP, etc. Don't forget to visit the full documentation and the forum to know more about it.
>
> NB: The capabilities gained by admin users when they access the "readonlyrest" Kibana App **are global**, that is, they can add/remove tenancies, users, groups, etc.

## Before you start

For the scope of this guide, we will assume:

* You will have a functioning installation of Elasticsearch and Kibana
* You have [installed the ROR plugin for Elasticsearch](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#installing)
* You have [installed the ROR Enterprise plugin for Kibana](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#installation)

If you don't have the ROR [Enterprise](https://readonlyrest.com/enterprise) for Kibana plugin, get yourself a two weeks free trial build!

## Setup: the Elasticsearch side

Right beside your `elasticsearch.yml`, create a file called `readonlyrest.yml` and write the following settings into it.

```yaml
readonlyrest:

    access_control_rules:

    #########################################################
    # These credentials shall be used by the logstash daemon.
    #########################################################  
    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["*logstash-*"]


    #####################################################################################
    # These credentials have no limitations, and shall be used only by the Kibana deamon.
    #####################################################################################
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana
      verbosity: error

    ##############################
    # SALES: Actual human users...
    ##############################
    - name: "::RO_SALES::"
      auth_key: sales_ro_usr:dev1
      indices: ["logstash-2018*"]
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "kibana:dev_tools"]
        index: ".kibana_sales"

    - name: "::RW_SALES::"
      auth_key: sales_rw_usr:dev2
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]
        index: ".kibana_sales"

    - name: "::ADMIN_SALES::"
      auth_key: sales_admin_usr:dev3
      indices: ["logstash-*"]
      kibana:
        access: admin
        index: ".kibana_sales"

    ###########################
    # OPS Actual human users...
    ###########################
    - name: "::RO_OPS::"
      auth_key: ops_ro_usr:dev4
      indices: ["logstash-2018*"]
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "kibana:dev_tools"]
        index: ".kibana_ops"

    - name: "::RW_OPS::"
      auth_key: ops_rw_usr:dev5
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]
        index: ".kibana_ops"

    - name: "::ADMIN_OPS::"
      auth_key: ops_admin_usr:dev6
      indices: ["logstash-*"]
      kibana:
        access: admin
        index: ".kibana_ops"
```

## Setup: the Kibana side

With ROR, we try as much as possible to keep all the settings withing the Elasticsearch domain. Therefore, you'll notice how few settings are needed on the Kibana side, apart from actually installing the plugin.

Open up `config/kibana.yml` and add/edit the following settings:

```yaml
# Kibana server use ::KIBANA-SRV:: credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

## Running

Fire up Elasticsearch

```bash
$ bin/elasticsearch
```

And then Kibana

```bash
$ bin/kibana
```

## Logging in

Now you are ready to point your browser to the Kibana server IP (defaulting on port 5601) and you should see a login prompt. You can login as any user i.e. "sales\_rw\_usr", or "ops\_admin" and the password is always "dev".

MJust remember to login with a RW user first, so Kibana can create its own default settings.


# Multi-user Elastic Stack (PRO)

([PRO](https://readonlyrest.com/pro))

This document will guide you through setting up your Elasticsearch and Kibana stack with ReadonlyREST such that:

* 3 users will be able to login into Kibana using their own set of credentials
* All users will see the same Kibana dashboards, but may be seeing different subsets of the whole data contained in Elasticsearch.

### Users and capabilities

For this tutorials, we want to have three users, each of them has a distinct access level to a shared Kibana tenancy (set of dashboards and settings).

|                                         | "admin" | "rw\_usr" | "ro\_usr" |
| --------------------------------------- | ------- | --------- | --------- |
| Can create, edit, delete dashboards     | ✅       | ✅         |           |
| Can change Kibana settings              | ✅       | ✅         |           |
| Only sees logstash data from 2019       |         |           | ✅         |
| Can see "add", "delete", "edit" buttons | ✅       | ✅         |           |
| "dev-tools" Kibana App is hidden        | ✅       | ✅         |           |
| "readonlyrest" Kibana App is hidden     | ✅       |           |           |

NB: ReadonlyREST for Elastisearch and ReadonlyREST PRO for Kibana have an great amount of features like groups, connector for external systems like LDAP, etc. Don't forget to visit the full documentation and the forum to know more about it. NB: This guide works with ROR Enterprise as well.

## Before you start

For the scope of this guide, we will assume:

* You will have a functioning installation of Elasticsearch and Kibana
* You have [installed the ROR plugin for Elasticsearch](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#installing)
* You have [installed the ROR PRO/Enterprise plugin for Kibana](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#installation)

If you don't have the ROR [PRO](https://readonlyrest.com/pro) (or [Enterprise](https://readonlyrest.com/enterprise)) plugin for Kibana, get yourself a two weeks free trial build

## Setup: the Elasticsearch side

On the same directory with your `elasticsearch.yml` (default: `config/`, create a file called `readonlyrest.yml` and write the following settings into it.

```yaml
readonlyrest:

    access_control_rules:

    #########################################################
    # These credentials shall be used by the logstash daemon.
    #########################################################  
    - name: "::LOGSTASH::"
      auth_key: logstash:logstash
      actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
      indices: ["*logstash-*"]


    #####################################################################################
    # These credentials have no limitations, and shall be used only by the Kibana deamon.
    #####################################################################################
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    #######################
    # Actual human users...
    #######################
    - name: "::RO::"
      auth_key: ro_usr:dev
      indices: ["logstash-2019*"] # <--- can see only data from 2019
      kibana:
        access: ro
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]

    - name: "::RW::"
      auth_key: rw_usr:dev
      indices: ["logstash-*"]
      kibana:
        access: rw
        hide_apps: ["readonlyrest_kbn", "timelion", "kibana:dev_tools", "kibana:management"]

    - name: "::ADMIN::"
      auth_key: admin_usr:dev
      indices: ["logstash-*"]
      kibana:
        access: admin
```

## Setup: the Kibana side

With ROR, we try as much as possible to keep all the settings withing the Elasticsearch domain. Therefore, you'll notice how few settings are needed on the Kibana side, apart from actually installing the plugin.

Open up `config/kibana.yml` and add/edit the following settings:

```yaml
# Kibana server use ::KIBANA-SRV:: credentials
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

## Running

Fire up Elasticsearch

```bash
$ bin/elasticsearch
```

And then Kibana

```bash
$ bin/kibana
```

## Logging in

Now you are ready to point your browser to the Kibana server IP (defaulting on port 5601) and you should see a login prompt. You can login as any user i.e. "rw\_usr", or "admin" and the password is always "dev".

Just remember to login with a RW user first, so Kibana can create its own default settings.


# SAML SSO (Enterprise)

External connectors integration

([Enterprise](https://readonlyrest.com/enterprise))

With ReadonlyREST Enterprise, you can integrate with SAML 2.0 Single Sign-on identity providers for both authentication and authorization.

Follow the guides to know more.


# Keycloak

SAML SSO Integration with Keycloak as an identity provider.

This document will guide you through the task of setting up an excellent, open-source identity provider ([KeyCloak](https://www.keycloak.org)) to work as an external authenticator and authorizer system for your ELK stack. The scenario is the usual:

* A centralized, large Elasticsearch cluster
* A Kibana installation
* We want one, centralized multi-tenant Elasticsearch + Kibana

But with some more enterprise requirements:

* Users need to be able to change their passwords independently
* Users need to verify their emails
* Group managers need to be able to add, remove, block (only) their users.
* [Multi-factor authentication (MFA)](https://www.keycloak.org/docs/latest/server_admin/#one-time-password-otp-policies) is a requirement.

## What is Keycloak

Keycloak is an advanced authentication server that lets user administer their credentials and speaks many authentication protocols, Including SAML2.0 SSO.

### Setup KeyCloak

This tutorial was created using KeyCloak 8.0.1.

1. Download the standalone version of Keycloak from their official website
2. Run Keycloak: run `bin/standalone.sh` or equivalent for your platform.
3. Navigate to <http://localhost:8080> and configure the admin user's credentials **don't forget to fill the email address!**
4. Login as admin
5. Follow the explanation below, or (if your KC version is the same or close enough to this) use the import function to load this [configuration file](https://github.com/beshu-tech/readonlyrest-docs/tree/d77c4981b29a843fc82f89c4272fdddaab390d89/keycloak_601_ror_SAML.json)

If you imported the JSON file, you should have a "ror" realm, and a SAML client called "ror" (keep this ID or change the "issuer" setting in kibana.yml) in the "master" realm. Please now select "ror" realm, navigate to "clients", click "ror" client and double check everything matches with your use case, as this guide assumes both Kibana, Elasticsearch and Keycloak are running on "localhost".

### Configure Keycloak to work with ROR

First, we want to create a new dedicated "ror" realm, so we don't interfere with any other use of this Keycloak installation.

![keycloak\_screenshot](/files/1YdgJCyaF6XxrXX60HrW)

Then, let's create a SAML client for this realm:

![keycloak\_screenshot](/files/A3rViDKfcL8q9GV9AhZv)

Then, configure the SAML client according to your Kibana URL, in this example, Kibana responds to "<https://localhost:5601/k>"

![keycloak\_screenshot](/files/hTSbbbD8T4w4ULCqNSRt)

Now that the client is saved, let's observe the "configure" tab, here we will extract the two logout and login endpoints that we will use for configuring our SAML connector in "kibana.yml".

![keycloak\_screenshot](/files/SwEt7SBhiBdHUyzEkTBR)

### Install ReadonlyREST Enterprise for Kibana

Please refer to our [documentation](/kibana) on how to obtain and install ReadonlyREST Enterprise for Kibana. Also, remember that it relies on the Elasticsearch plugin to be configured as well.

### Setup the SAML connector

Provided that you have ReadonlyREST Enterprise installed and configured, you can add the following configuration:

**kibana.yml**

```yaml
# More on how to enable SSL on the official documentation of Kibana
server.ssl.enabled: true
server.ssl.key: /home/xx/selfsigned_ssl_localhost/localhost.key
server.ssl.certificate: /home/xx/selfsigned_ssl_localhost/localhost.crt

server.basePath: /k  # <-- optional, remember to change it in KC
elasticsearch:
  hosts: ["https://localhost:9200"] # <-- our Elasticsearch responds to https
  ssl.verificationMode: none
  username: kibana
  password: kibana

readonlyrest_kbn:
  logLevel: debug
  auth:
    # this secret string has to be longer than 256 chars, use environmental variables to fill it in maybe.
    signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"

    saml_kc:  # <--- Our SAML connector name, used in the path configured in KC
      buttonName: "KeyCloak SAML SSO"
      enabled: true
      type: "saml"
      issuer: "ror"  # <-- called exactly like the SAML client in KC
      entryPoint: "http://localhost:8080/auth/realms/ror/protocol/saml" # <-- from KC configuration tab!
      kibanaExternalHost: 'localhost:5601' 
      protocol: "https"  # <--- our Kibana responds to HTTPS
      usernameParameter: "nameID"
      groupsParameter: "Role"
      logoutUrl: "http://localhost:8080/auth/realms/ror/protocol/saml" # <-- from KC configuration tab!
      cert: /etc/ror/integration/certs/dag.crt # from KC realm keys tab <-- It can be also provided a string value 
```

You can find a public PEM-encoded X.509 signing certificate as a string value by selecting the "keys" tab in your newly created realm. After clicking on a cert button, you can copy the value into `kibana.yml` SAML config `cert` parameter.

![keycloak\_screenshot](/files/OEcFQOiWuBh1OT3S7BGI)

Don't forget setting up SAML requires some changes to security settings in `readonlyrest.yml` (on the Elasticsearch side). Security settings can also be changed via the ReadonlyREST Kibana app.

### Setup Elasticsearch with ReadonlyREST

Our Elasticsearch needs to be available on HTTPS (more detailed info in our [documentation](/elasticsearch#encryption)). Configure SSL according to this guide.

Then write in **readonlyrest.yml**

```yaml
readonlyrest:

    audit:
      enabled: true
      outputs:
      - type: index

    access_control_rules:
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana
      verbosity: error

    - name: "ReadonlyREST Enterprise instance #1"
      kibana:
        access: ro
        index: ".kibana_sso"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["*"]

    ror_kbn:
    - name: kbn1
      # It has to be the same string as we declared in kibana.yml.
      signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)


# Microsoft Azure AD

Integration with the managed cloud service Microsoft Azure Active Directory.

[Azure Active Directory (Azure AD)](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-whatis) is Microsoft’s cloud-based identity and access management ([IAM](https://en.wikipedia.org/wiki/Identity_management)) service. And it can be used as a SAML Single Sign-On (SSO) \[identity provider (IdP)]\(<https://en.wikipedia.org/wiki/Identity_provider_(SAML)>) for a pool of exiting users to sign in and access resources in external service providers like [ReadonlyREST Enterprise](https://readonlyrest.com/enterprise).

With Azure AD, you can graphically manage users, groups, credentials and permissions. ReadonlyREST Enterprise for Kibana will collaborate with Azure AD to authenticate, grant permissions and access to tenancies for users that are entirely managed within Azure AD.

In this guide, we are going to see how to configure Elasticsearch and Kibana with ReadonlyREST Enterprise to make use of Azure AD via the SAML protocol. The result will be a multi-user, optionally multi-tenant Kibana instance powered by [ReadonlyREST](https://readonlyrest.com).

## Install Elasticsearch and Kibana

Make sure you have a functioning installation of Kibana, backed by an instance of Elasticsearch. You can find [the installation guide](https://www.elastic.co/guide/en/kibana/current/install.html) in Elastic's website.

## Set up the ReadonlyREST plugins

In order to use ReadonlyREST Enterprise for Kibana, make sure you have installed ReadonlyREST Free for Elasticsearch first. Head to our [setup guide](/elasticsearch#installing-the-plugin) to find instructions.

Once ReadonlyREST Free plugin is installed, configure an ACL for accepting SAML sessions from ReadonlyREST Enterprise for Kibana. This is also [explained in our guide](/elasticsearch#ror_kbn_auth). Remember to choose a very long secret phrase (256+ characters)

Now head to the Kibana directory, and install a trial (or full) version of ReadonlyREST Enterprise, which can be freely downloaded from [our download page](https://readonlyrest.com/download). For [installation instructions](/kibana#installation), see our Kibana plugin guide.

Azure AD only speaks with "https" websites, so make sure your Kibana web server is configured to serve pages in https. See a guide from Elastic on how to enable SSL

### Conventions and assumptions in this guide

This tutorial assumes that Kibana runs in <https://localhost:5601>, which is clearly only valid if you are trying this authentication system in your local computer.

And it also assumes you used something like [mkcert](https://blog.filippo.io/mkcert-valid-https-certificates-for-localhost/) to let your browser trust SSL certificates for localhost URLs.

In the real world, when you are configuring Kibana in production, make sure: 1. You have a valid SSL certificate for the Kibana server 2. You replace all the references to `localhost:5601` with the publicly reachable host name of your actual Kibana server.

## ReadonlyREST Configuration

Now, **on the Elasticsearch side**, you should have the `$ES_HOME/config/readonlyrest.yml` file configured to accept SAML sessions from kibana using the `ror_kbn_auth` rule. I.e.

```
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    ... all usual blocks of rules...

    - name: "ReadonlyREST Enterprise sessions"
      ror_kbn_auth:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

**On the Kibana side**, we will now configure our Kibana plugin to speak with Azure AD. Open your `$KBN_HOME/config/kibana.yml`, it should look something like:

```yaml
elasticsearch.hosts: ["http://localhost:9200"] # <-- consider enabling "https" using the SSL feature in ReadonlyREST Free!
elasticsearch.username: "kibana"
elasticsearch.password: "kibana"
# elasticsearch.ssl.verificationMode: none  # <-- uncomment if your Elasticsearch uses "https" with self signed certificates

server.ssl.enabled: true # <-- It's mandatory for Azure AD that we enable SSL in our Kibana server!
server.ssl.certificate: '/etc/kibana/ssl_cert/localhost.pem'
server.ssl.key: '/etc/kibana/ssl_cert/localhost-key.pem'

readonlyrest_kbn:
  cookiePass: '12312313123213123213123abcdefghijklm'
  logLevel: debug
  clearSessionOnEvents: ["login"]

  auth:
    signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!

    saml_azure:
      buttonName: 'Azure AD SAML SSO'
      enabled: true
      type: saml
      issuer: 'ror'
      protocol: 'https'
      cert: '/etc/kibana/config/cert.pem' # <-- will download later from Azure enterprise app dashboard
      entryPoint: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2'
      kibanaExternalHost: 'localhost:5601'
      usernameParameter: 'http://schemas.microsoft.com/identity/claims/displayname'
      groupsParameter: 'http://schemas.microsoft.com/ws/2008/06/identity/claims/groups'
      logoutUrl: 'https://login.microsoftonline.com/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/saml2'
```

### Notes about ReadonlyREST Kibana settings

The `issuer` parameter is important and should be ideantical to what you wrote in the field *(1) Basic SAML Configuration - Identifier (Entity ID)* in the Azure AD settings.

The `entryPoint` value should be copied from the field `(4) Set up ReadonlyREST Enterprise > Login URL` in Azure AD settings.

The `kibanaExternalHost` only accepts the browser facing hostname (or IP address) and optionally the port of our Kibana server. Do not put any "https\://" prefix here.

The `cert` is an **absolute** path to the **base64** version of the certificate ReadonlyREST Enterprise will use to verify the signature of the SAML assertion coming from Azure AD. This file can be downloaded from : `(3) SAML Signing Certificate > Certificate (Base64)`

The `groupsParameter` and `usernameParameter` values represent the JSON fields names from the SAML assertion object coming from Azure AD. They represent the field names we take the username and groups information from.

An example of SAML assertion object coming from Azure AD after successful authentication looks like so:

```javascript
{
  "http://schemas.microsoft.com/claims/authnmethodsreferences": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
  "http://schemas.microsoft.com/identity/claims/displayname": "Simone Scarduzio",
  "http://schemas.microsoft.com/identity/claims/identityprovider": "https://sts.windows.net/88af1572-1347-45b6-8f65-xxxxxxxxx/",
  "http://schemas.microsoft.com/identity/claims/objectidentifier": "486abf50-a61f-40e9-8a37-3ff6a6eeda26",
  "http://schemas.microsoft.com/identity/claims/tenantid": "88af1572-1347-45b6-8f65-xxxxxxxxxxxx",
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups": [
    "00f22de3-0d59-4867-8e1a-xxxxxxxxxxxx",
    "dbe4ff5a-deba-419f-a653-xxxxxxxxxxxx",
    "3c19b288-263c-4dd1-9947-xxxxxxxxxxxx"
  ],
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/wids": "62e90394-69f5-4237-9190-xxxxxxxxxxxx",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": "Simone",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "Simone@ror-enterprise-test.com",
  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": "Scarduzio",
  "issuer": "https://sts.windows.net/88af1572-1347-45b6-8f65-xxxxxxxxxxxx/",
  "nameID": "Simone@ror-enterprise-test.com",
  "nameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
  "sessionIndex": "_97f290ee-2ff6-445f-a0c6-xxxxxxxxxxxx",
  "user": "Simone Scarduzio"
}
```

## Azure AD configuration

1. Login in your Microsoft Azure dashboard, and head to Enterprise Applications.

![Azure Dashboard](/files/2QTZYAi1FMvzNcvTPpIK)

1. Click on "Non-gallery application".

![Azure Enterprise apps](/files/gviWjDCnx4YgXkijxhfh)

1. Create a new app called "Readonlyrest Enterprise".

![Azure App Name](/files/Vz0Na0qrwj0AX7zHpGrx)

1. Click "Single Sign On" to configure the app for SAML.

![Azure ror app](/files/i5ZoPLcfGl8c5EhyxLjU)

1. Insert URLs and data about our Kibana server as shown in the picture. And press SAVE.

![Azure ror basic saml settings](/files/7oALAq3EVzXIchaasA9O)

1. Download the base64 encoded "pem" file, and place it under the **absolute path** `/etc/kibana/config/cert.pem`.

![Azure ror cert](/files/-MIs7cHtITlj1nNzAfq1)

7 Make sure this app has at least a test user assigned, and press SAVE. Otherwise the single sign-on will fail.

![Azure ror app users](/files/jIvJOoOZPWxHEYM9quaX)

## Testing if this all works.

1. Now point your browser to your Kibana installation (in the example <https://localhost:5601>).

   You should now see a new blue button that says "Azure AD SAML SSO".

![ROR login](/files/5H2WvhkumttrC1E8ySBL)

1. Press it, and you should see the Azure AD login page. Place your credentials here, or pick an already authenticated identity to enter Kibana.

![Azure Login](/files/-MIs7cHw5d2vxSh0ff03)

1. You will now be redirected to Kibana, logged in as your Azure AD identity.

![Azure Login](/files/CsDvNckU8HjeaAELuwd7)

1. You can now logout from the "ReadonlyREST SAML SSO" Azure AD Enterprise app by pressing the exit button right beside the username in the bottom right corner.

![Azure Login](/files/lnbhGqnC1P5JA13wU5cK)

## Authorization using Azure AD groups

Users in Azure AD can belong to groups. The list of group associated to a user is useful information for ReadonlyREST Enterprise for identifying sets of users that we want to authorize to:

* see certain indices
* perform certain actions over certain indices
* belong to a tenancy
* have read or read/write permission to a tenancy
* have administrative rights over ReadonlyREST cluster-wide security settings
* many more things, or even a combination of all these.

### Example: ReadonlyREST Admins group

Suppose we would like to authorise the group "ReadonlyREST Admins" to access the administrative dashboard that can oversee all the indices, and we want to grant them access to an "admin" tenancy that contains dashboards based on the real time [ReadonlyREST audit logs](/elasticsearch#audit) indices.

#### Creating and assigning the group in Azure Ad

Let's go to Azure and make sure the "ReadonlyREST Admins" group is created, and one or more users - including ours - belongs to it.

![Azure Create Groups](/files/-MIs7cHzVZn-Vh7QXIhP)

#### Finding the new group's Azure object ID

From ReadonlyREST settings, we will refer to the newly created group using its associated object ID provided by Azure platform. To discover it, navigate the Azure AD dashboard to:

`Dashboard > Enterprise applications - All applications > ReadonlyREST Enterprise - Users and groups > [your user] - Groups`

![Azure Show Groups](/files/YQ5zTjmPJwpog3aTetMH)

The object ID of the new group is "3f8ebed8-f742-42a6-94ba-2d57550fc3cf", let's take note of this. We will use it in our ACL.

#### Using ReadonlyREST ACL to authorize the group

Let's head back to Elasticsearch, and open `readonlyrest.yml`. Let's now add the authorization.

```yaml
readonlyrest:
    access_control_rules:

    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana

    - name: "Azure AD - ReadonlyREST Admins group"
      indices: ["readonlyrest-audit*"]
      kibana:
        access: "admin"
        index: ".kibana_admin_tenancy"
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["3f8ebed8-f742-42a6-94ba-2d57550fc3cf"]

    - name: "Azure AD - Anyone else"
      indices: ["readonlyrest-audit*"]
      kibana:
        access: "rw"
        index: ".kibana_generic_tenancy"
        hide_apps: ["readonlyrest_kbn"]
      ror_kbn_auth:
        name: "kbn1"
        groups_any_of: ["*"]

    ror_kbn:
    - name: kbn1
      signature_key: "my_shared_secret_kibana1_(min 256 chars)" # <- use environmental variables for better security!
```

Now we have two ACL blocks dedicated to Azure AD: one will match for users that belong to "ReadonlyREST Admins" (a.k.a object ID `3f8ebed8-f742-42a6-94ba-2d57550fc3cf`), the other will match for Azure AD users that do not belong to the group.

The key here is the use of the `roles` option in the `ror_kbn_auth` rule, as an extra constraint so that the ACL block is only matched when the user has the "3f8ebed8-f742-42a6-94ba-2d57550fc3cf" string in the list of their groups.


# Microsoft ADFS

Integration manual for ReadonlyREST Enterprise with the on-premises Active Directory Federated Services  Single Sign-on from Microsoft.

How to Connect ROR Enterprise with SAML and ADFS

ReadonlyREST (ROR) Enterprise allows for complex authentication and authorization configurations with Kibana and Elasticsearch. When Elasticsearch is combined with Kibana, a data visualization dashboard, the combination provides a powerful way to ingest logs and analyze data.

To access that data, many enterprises manage users in a central directory. This directory could be an Active Directory (AD) instance, in the case of a Windows-centric environment, or a cloud directory provider, such as Google Cloud Identity, in a cloud-based environment. Instead of integrating these services directly into a product, an abstraction layer such as SAML can provide authentication and authorization and tie into different back ends as necessary.

ReadonlyREST provides a free Elasticsearch plugin that provides advanced authentication options. When it is combined with the ReadonlyREST Enterprise plugin for Kibana, integrating SAML authentication into the authentication process becomes easy.

This article will walk through the process of setting up an entire environment in order to demonstrate how the ReadonlyREST free and Enterprise plugins integrate with Active Directory Federation Services (AD FS) to provide SAML authentication.

In this tutorial, you will learn how to:

* Provision Azure Virtual Machines to host Active Directory, Elasticsearch, and Kibana
* Install and configure Active Directory (AD) Services
* Provision sample AD users
* Install and configure Active Directory Certificate Services (AD CS)
* Install and configure Active Directory Federation Services (AD FS)
* Install and configure ElasticSearch and the ReadonlyREST Free Plugin
* Install and configure Kibana and the ReadonlyREST Enterprise Plugin

## Provisioning Azure Virtual Machines to Host Active Directory, Elasticsearch, and Kibana

Any Windows Server 2016 Virtual Machines (VM) can be used for this process; however, in this demonstration, the Microsoft Azure environment will be used to provision and host the VMs.

You can name your VMs whatever you would like. This article will refer to the names listed below for consistency.

* Virtual Machine 1: lc-win2019-02
  * **Roles**: Active Directory, AD Certificate Services, AD Federation Services, DNS
  * **Memory**: 4GB
* Virtual Machine 2: lc-win2019-03
  * **Roles**: Elasticsearch, Kibana
  * **Memory**: 8GB

These Azure VMs will be Pay-As-You-Go and Spot Instances for affordability. The example shown below is for the Elasticsearch and Kibana VM which will be duplicated for the Active Directory VM but will have 4GBs of memory instead of 8GB.

*Please note that Azure Spot Instances cannot be resized after creation.*

### Provisioning Virtual Machines

1. Log into the **Azure portal** using a **Pay-As-You-Go** subscription.
2. Create a **new virtual machine.**
3. If you do not already have a **resource group** created to serve as a home for the VMs, select **Create new** and create the **resource group.**
4. Name your virtual machine appropriately, and choose the details for your instance, as shown in the example below.
5. You can use the default hard drive sizes and **Standard HDD** disks for this environment.
6. ![](/files/-ML8MzNOB1E7Vi3n1UoG)
7. The default **Networking** options will also work here.
8. ![](/files/-ML8MzNPdlZVtGRJxd-3)
9. As will the default **Management** options.
10. ![](/files/-ML8MzNQ1TUWyAsPDp8O)
11. No additional **Advanced** options are necessary.
12. ![](/files/-ML8MzNRIT1g0V4TIFIa)
13. If you would like to tag your VMs for later categorization and tracking, you can do so here.
14. ![](/files/-ML8MzNSNAeWMIUa1Dk9)
15. Finally, create the VM.

After this VM has been created, create one more to host the Active Directory and related services. In the end, you should have two VMs as outlined above.

## Installing and Configuring Active Directory (AD) Services

After the two VMs have been provisioned, the next step is to set up directory services on the first VM, lc-win2019-02.

### Installing Active Directory Services

1. Once you are logged into lc-win2019-02, choose **Add Roles and Features** on the **Server Manager** screen.
2. Select **Role-based or feature-based installation**.
3. ![](/files/-ML8MzNVKrk8lirgAHH5)
4. Select the correct server from the server pool.
5. ![](/files/-ML8MzNWCj_ASCeITRxL)
6. Select **Active Directory Domain Services,** and add the additional features as prompted.
7. No additional features are necessary since the default options work.
8. ![](/files/-ML8MzNZWns_fS1c39gQ)
9. Click **Next** on the **Active Directory Domain Services** informational screen.
10. ![](/files/-ML8MzN_SV3pRjAhSCGx)
11. Finally, select **Restart the destination server automatically if required**. Click **Yes** when prompted, and then click **Install.**
12. Once installation has finished, click on **Close.**
13. ![](/files/-ML8MzNck-qvrqoDsVpV)

### Configuring Active Directory Services

If DNS has not been installed already, the role installation screen may pop up in the middle of the Active Directory installation. Installation instructions for the DNS role are shown after the Configuring Active Directory Services section below.

1. Click on **Promote this server to a domain controller,** which will allow you to see the **Deployment Configuration** screen.
2. ![](/files/-ML8MzNd3zkDsvP8B5An)
3. Name the domain. In this case, use ad.lc-test.local.
4. This name was arbitrarily chosen. Using a subdomain such as “ad” instead of your actual domain (i.e., \[lc-test.local]\([http://lc-test.local\\](http://lc-test.local/\)\)%20by%20itself/) is recommended.
5. ![](/files/-ML8MzNe8sRO0MYrwmWT)
6. Select **Windows Server 2016** as the **functional level**. For the **domain controller capabilities**, choose **Domain Name System (DNS) server**. Set a Directory Services Restore Mode (DSRM) **password.**
7. ![](/files/-ML8MzNfj1fBOJDPt84I)
8. The following **DNS Options** warning message can be disregarded:
9. ![](/files/-ML8MzNgGbQv2a1cxNv0)
10. Set the **NetBIOS domain name**, which is usually the short name prior to the host name (e.g., AD), and click **Next.**
11. ![](/files/-ML8MzNh9m0aok5EF9UN)
12. Use the default paths, and click on **Next.**
13. ![](/files/-ML8MzNiXFQXdGNFtXw3)
14. On the **Review Options** screen, click **Next** if everything looks correct.
15. ![](/files/-ML8MzNjtvS_-O7HQFEi)
16. On the **Prerequisites Check** screen, click **Install.**
17. You’ll see a number of warnings related to the fact that this is a test environment. They can be safely ignored.
18. ![](/files/-ML8MzNkiyFagMd-nVW2)
19. When you click **Close**, you will have a successful configuration.
20. ![](/files/-ML8MzNlvRibpGRsFk6O)
21. Click **Close** on the restart prompt.
22. ![](/files/-ML8MzNmPqox1-ETLmZX)

### Configuring the Domain Name Services (DNS) Role

1. Click on the **DNS Services** role, add the additional features as requested, and click **Next.**
2. If you are using DHCP for the server (this is not recommended for a production service), then you will see the validation warning shown below. It can be disregarded. Click on **Continue** and **Install.**
3. ![](/files/-ML8MzNpIt9aaWDLkhcC)
4. Click **Next** on the **Features** screen, since no additional features are necessary.
5. ![](/files/-ML8MzNqV4O3XudgCOya)
6. Click **Next** on the informational **DNS Server** screen.
7. ![](/files/-ML8MzNrS-yQm8CSUAXy)
8. Click **Install** on the **Confirmation** screen.
9. ![](/files/-ML8MzNsi8MMQQIZsiX0)
10. Select **Restart the destination server automatically if required,** and click **Install.** Finally, click **Close,** and you will have a successful installation.
11. ![](/files/-ML8MzNtCClbf1Hu2AEE)

### Joining Computers to the Domain

Next we need to join the second server—the one hosting Elasticsearch and Kibana—to the domain.

1. Open an RDP connection to the second server. Then, open **Notepad** as an Administrator, and open the file C:\Windows\System32\drivers\etc\hosts.
2. ![](/files/-ML8MzNuQrSeVzy5_oKp)
3. Add the IP address and hostnames for the domain controller.
4. These will reflect the IP addresses and hostnames you chose for your configuration:
   * **FQDN**: 10.0.0.5 - ad.lc-test.local
   * **NetBIOS**: 10.0.0.5 - ad
5. ![](/files/-ML8MzNvtEC_0Ni6V2R7)
6. Additionally, you will need to change your network adapter DNS to point to your domain server; in this case, it is 10.0.0.5.
7. If the system restarted, open an RDP connection, and then open the **System** screen under the **Control Pane** and select **Advanced System** settings.
8. ![](/files/-ML8MzNynDTLChI9xe6d)
9. Click on **Change** to add this server to the domain.
10. ![](/files/-ML8MzNzUmMW3DpR76X-)
11. Enter the domain (e.g., ad.lc-test.local).
12. ![](/files/-ML8MzO-yRihwCDdc5Hq)
13. Click on **OK,** and enter the credentials of the account that has privileges enabling it to add the domain.
14. ![](/files/-ML8MzO06Ik_tvTG3k5z)
15. Restart the server after joining it to the domain.

## Provisioning Sample AD Users

For testing purposes, it can be useful to provision additional users within the Active Directory. The following PowerShell script, which should be run on the domain controller, will make this easy. Note that we are setting the mail attribute which will be used for the SAML username.

Import-Module -Name 'ActiveDirectory'

$Domain = 'ad.lc-test.local'\
$OU = 'CN=Users,DC=ad,DC=lc-test,DC=local'

$Users = @{\
"TestUser1" = "testPass1"\
"TestUser2" = "testPass2"\
"TestUser3" = "testPass3"\
"TestUser4" = "testPass4"\
"TestUser5" = "testPass5"\
}

$Users.GetEnumerator() | ForEach-Object {\
$Name = $\_.Key\
$Password = $\_.Value

$Params = @{\
"Name" = $Name\
"Path" = $OU\
"AccountPassword" = (ConvertTo-SecureString -AsPlainText $Password -Force)\
"Enabled" = $True\
"DisplayName" = $Name\
"PasswordNeverExpires" = $True\
"CannotChangePassword" = $True\
"EmailAddres" = "$Name@$Domain"\
}

New-ADUser @Params\
}

## Installing Active Directory Certificate Services (AD CS)

1. Click on **Active Directory Certificate Services,** and add the additional features as prompted.
2. Since no additional features are necessary, allow defaults, and click on **Next.**
3. ![](/files/-ML8MzO3O_wdTqoS6MFC)
4. On the **Active Directory Certificate Services** informational screen, click **Next** to continue.
5. ![](/files/-ML8MzO4285d61x48hfS)
6. Select the **Certification Authority** role services, and click **Next.**
7. ![](/files/-ML8MzO5NPcH7wlyD2KG)
8. Select **Restart the destination server automatically if required,** and click on **Install.**
9. ![](/files/-ML8MzO6NgpCqbSIQOI5)
10. Finally, click on **Close** when the installation has been completed.
11. ![](/files/-ML8MzO7e8ChFU-Zt0oS)

### Configuring Active Directory Certificate Services

1. Click on **Configure Active Directory Certificate Services.**
2. ![](/files/-ML8MzO85sRb0Gq9gJlO)
3. Select the default administrative user for AD CS credentials.
4. ![](/files/-ML8MzO92lXXp7_YFoFF)
5. Under **Role Services**, check **Certification Authority,** and click Next.
6. ![](/files/-ML8MzOAGJHHzqqGvs5N)
7. Select **Enterprise CA,** and click on **Next.**
8. ![](/files/-ML8MzOBHDMGy4iFbvH2)
9. Click on **Root CA**, then click on **Next.**
10. ![](/files/-ML8MzOCgjXw_6BGmDuv)
11. Click on **Create a new private key,** and then click on **Next.**
12. ![](/files/-ML8MzODhRZ5kTQ8xsXE)
13. Select **RSA#Microsoft Software Key Storage Provider**, a default key length of **2048**, and a hash algorithm of **SHA256.** Click **Next.**
14. ![](/files/-ML8MzOEETSwPQ3aAlo3)
15. Use the defaults given for the CA Name, and click on **Next.**
16. ![](/files/-ML8MzOFYH3WT-eTnw0M)
17. Select a validity period of 5 years, and click on **Next.**
18. ![](/files/-ML8MzOG3W9RIcugKEOH)
19. Leave the default database locations in place, and click on **Next.**
20. ![](/files/-ML8MzOHGOnsvXCNKWto)
21. Click on **Configure** and **Close** when the configuration process has been completed.
22. ![](/files/-ML8MzOI6N-uAhM9lY8l)

## Installing and Configuring Active Directory Federation Services (AD FS)

Previously, it was recommended that AD FS should not be installed on the same server as the DC because IIS was installed as part of that process. As of 2012, this recommendation has changed, since AD FS does not use IIS anymore. Now, mounting AD FS and DC on the same server is advised for domains under 1000 users.

### Provisioning SSL Certificate Templates

1. Open the **Certification Authority** MMC snapin, right-click on **Certificate Templates,** and click on **Manage.**
2. ![](/files/-ML8MzOJQdF5uKiW0nUU)
3. Select **Duplicate Template** on the **Web Server** template.
4. ![](/files/-ML8MzOKvawdmixryLJ9)
5. Enter “SSL Certificates” in the box labeled **Template display name** on the **General** tab.
6. ![](/files/-ML8MzOLtvOKeXHtczYu)
7. On the **Security** tab, click on **Enroll** and **Allow for Authenticated Users,** and, finally, **Apply** the configuration.
8. ![](/files/-ML8MzOMmuJ-ckF3EZM3)
9. Right-click on **Certificate Templates → New → Certificate Template to Issue.**
10. ![](/files/-ML8MzONQ5mHS5bIlSol)
11. Select **SSL Certificates** from the **Certificate Template** list, and click **OK.**

### Provisioning the SSL Certificate

1. Open the Certificates MMC snapin for the Local Computer. Navigate to **Personal → Certificates,** and right-click to open **All Tasks → Request New Certificate.**
2. ![](/files/-ML8MzOQPEcN9hqpAbEE)
3. Click **Next** on the **Before you Begin** screen.
4. ![](/files/-ML8MzORi7gxLPl_y5D4)
5. Click **Next** on the **Select Certificate Enrollment Policy** screen.
6. ![](/files/-ML8MzOSNw4tGSPF8F62)
7. Select **SSL Certificates,** and click on **More information is required to enroll for this certificate.** Select **Click here to configure settings** to configure the certificate.
8. ![](/files/-ML8MzOTEC-LwnXStC-u)
9. Add the following details on the **Certificate Properties Subject** screen, then click **OK**:
   * **Subject Name**
     * **Common Name**: CN=lc-win2019-02.ad.lc-test.local
   * **Alternative Name**
     * **DNS**: lc-win2019-02.ad.lc-test.local
     * **DNS**: enterpriseregistration.ad.lc-test.local
10. ![](/files/-ML8MzOU2dAt-W7hdsUB)
11. Click on **Enroll** to request the certificate, then click **Finish**.
12. ![](/files/-ML8MzOV7I5XI1rfJzKC)

### Setting up Active Directory Federation Services

1. Select the **Active Directory Federation Services** role, and click **Next.**
2. ![](/files/-ML8MzOWROq8YQprgDlN)
3. Click **Next** on **Select Features**, since no additional features are needed.
4. ![](/files/-ML8MzOXqGg6okTJj30n)
5. Click **Next** on the **Active Directory Federation Services** informational screen.
6. ![](/files/-ML8MzOYdDucXXywHna2)
7. Check **Restart the destination server automatically if required,** and click **Install** and **Close** when the installation has completed.
8. ![](/files/-ML8MzOZAvzrbbjT4zos)

### Creating a Group Managed Service Account and Adding a KDS Key

It is best to use a **gMSA** (group Managed Service Account) instead of a traditional **sMSA** (standalone Managed Service Account). The primary difference between the two is that, in a gMSA, the Windows operating system manages the password for the account instead of relying on the administrator to do it.

Before we can select the gMSA, however, we need to add a **KDS Root Key**. To avoid non-blocking warnings later in the process, this key should be added with an effective date of 10 hours prior to the current date and time.

Open a **PowerShell** session as an Administrator, and run the following command to add the KDS root key:

Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))

If you do not take this step, you will see the following error when attempting to add the gMSA account:

![](/files/-ML8MzO_MlaaniFUHDH-)

To create a gMSA account to use with the AD FS service, use the PowerShell script provided below. If you get an “access denied” error when running Install-ADServiceAccount, you may need to restart the server first.

$Name = 'sa\_adfs'

$Params = @{\
"Name" = $Name\
"DNSHostName" = 'lc-win2019-02.ad.lc-test.local'\
"PrincipalsAllowedToRetrieveManagedPassword" = 'lc-win2019-02$'\
"ServicePrincipalNames" = 'http/lc-win2019-02.ad.lc-test.local'\
}

$ServiceAccount = New-ADServiceAccount @Params

Install-ADServiceAccount -Identity $Name

Add-ADComputerServiceAccount -Identity 'lc-win2019-02' -ServiceAccount $ServiceAccount

### Configuring Federation Services

1. Click on the link that says **Configure the federation service on this server**.
2. ![](/files/-ML8MzOaKdqLRmUJOaBR)
3. Select **Create the first federation server in a federation server farm,** and click **Next.**
4. ![](/files/-ML8MzObOnElJPC73Ncc)
5. On the **Connect to Active Directory Domain Services** screen, leave the default user selected, then click on **Next**.
6. ![](/files/-ML8MzOc9E-zRXlrin9P)
7. Select the previously created SSL Certificate, and enter “LC Test” for the **Federation Service Display Name.** Click **Next.**
8. ![](/files/-ML8MzOdknq8iXw81vEv)
9. On the **Specify Service Account** screen, click on **Select** to use an existing account and locate the sa\_adfs service account that was previously created. Click **Next.**
10. ![](/files/-ML8MzOeuvErqRL3tyBS)
11. Select **Create a database on this server using Windows Internal Database,** and click **Next**.
12. ![](/files/-ML8MzOfklxg8hFTSDfh)
13. Click on **Next** under **Review Options.**
14. ![](/files/-ML8MzOgzbmHNZXb-NoY)
15. Verify the **Pre-requisite Checks,** and click on **Configure.**
16. ![](/files/-ML8MzOhvUdZZKDUyAeA)
17. Click on **Close,** and restart the server.
18. The warnings shown below can be disregarded for this test instance:
19. ![](/files/-ML8MzOiZF29HwRjPbDE)
20. Once the server has restarted, open an Administrative PowerShell session, and run the following command to enable the **IdP Signon Page:**
21. Set-ADFSProperties -EnableIdPInitiatedSignonPage $True
22. Verify that AD FS metadata is being returned by navigating to the following URL:
23. [https://{FQDN](https://{fqdn) of AD FS Server}/adfs/fs/federationserverservice.asmx
24. ![](/files/-ML8MzOjmGIbhcz2aqdU)

### Setting Up ReadonlyREST Relying Trust

1. Open the **AD FS** MMC snapin, right-click on the **Relying Party Trusts** folder, and select **Add Relying Party Trust.**
2. ![](/files/-ML8MzOkjaWC1vT7GT0s)
3. Select **Claims aware,** and click **Start.**
4. Choose **Enter data about the relying party manually,** and click **Next.**
5. Enter a **Display Name** (in this case, “ror”), and click **Next.**
6. It’s not necessary to specify a token encryption certificate, so click **Next** to continue.
7. Select the option **Enable support for the SAML 2.0 SSL service URL,** and enter:
8. [https://{IP Address of Kibana Server}:5601/ror\_kbn\_sso\_saml\_adfs/assert](https://10.0.0.6:5601/ror_kbn_sso_saml_adfs/assert)
9. The saml\_adfs will change depending on the name chosen in the configuration of the kibana.yml file.
10. Enter the **Relying party trust identifiers**, in this case, “ror.” This will match the **Issuer** in the Kibana configuration. Click **Next** when you are done with this step.
11. On the **Access Control Policy** screen, select **Permit everyone,** and click **Next.**
12. Click **Next** to finish adding the trust.
13. Verify that **Configure claims insurance policy for this application** is selected, and click on **Close.**

### Configuring Claims

Though we have not yet configured claims for Kibana, the metadata for the SAML configuration in Kibana would look similar to the following, if you were able to view it:

![](/files/-ML8MzOl0_q7lubzsVMp)

The important section to note concerns the claims issuance policy. We need to return a **NameID** format in the form of an **emailAddress** by entering the following code\*\*:\*\*

\<NameIDFormat>\
urn:oasis:names:flag\_tc:SAML:1.1:nameid-format:emailAddress\
\</NameIDFormat>

Therefore, we need two rules: one to pull back the LDAP attribute from the Active Directory, and another to transform that data into the correct format.

1. Click on **Add Rule** on the **Edit Claim Issuance Policy** **for ror** screen.
2. ![](/files/-ML8MzOmnxaD7-NfnoId)
3. For the first rule, choose **Send LDAP Attributes as Claims,** and click **Next.**

![](/files/-ML8MzOno0NVs_KkvENM)

1. Choose the AD Attribute to return—in this case, the email address—and click **Finish** on the **Configure Rule** screen of the **Add Transform Claim Rule Wizard.**
   * **Claim rule name**: LDAP Email
   * **Active Store**: Active Directory
   * **LDAP Attribute**: E-Mail-Addresses
   * **Outgoing Claim Type**: E-Mail Address

![](/files/-ML8MzOohijZwoWxx-ir)

1. Click on **Add Rule,** then choose the **Transform an Incoming Claim** claim rule template, and click on **Next.**

![](/files/-ML8MzOpk9GS5vPvTx5R)

1. Enter the transformation details as listed below, and, on the **Edit Rule** screen, click on **OK.**
   * **Claim rule name**: Email Transform
   * **Incoming claim type**: E-Mail Address
   * **Outgoing claim type**: Name ID
   * **Outgoing name ID format**: Email
   * **Pass through all claim values**: Selected

![](/files/-ML8MzOq-QCmrPIRCJUa)

1. Click **OK** to save the rules.
2. Please note that the order of the rules on the **Edit Claim Issuance Policy** screen is important.
3. ![](/files/-ML8MzOrds5Dhn242dik)

### Updating Relying Party Trusts

1. Navigate to the **Relying Party Trusts** folder, right-click on the **ror trust,** and select **Properties.**

![](/files/-ML8MzOsBRiLm34RVaXv)

1. Click on the **Endpoints** tab, select the **SAML Assertion Consumer Endpoints,** and click on **Edit.**

![](/files/-ML8MzOtth3z2H3WnuSv)

1. Click on **Set the trusted URL as default,** and change the Index to 1 from 0. Click **OK.**

![](/files/-ML8MzOunpe0lnJ0MrZS)

1. On the **Endpoints** screen, click on **Add SAML,** and enter the **SAML Logout** details as follows:
   * **Endpoint Type**: SAML Logout
   * **Binding**: POST
   * **Trusted URL**: [https://{IP](https://{ip) Address of Kibana Server}:5601/ror\_kbn\_sso\_saml\_adfs/notifylogout

![](/files/-ML8MzOvty24bhIaFPei)

1. Click on **OK** to save the modified properties. ![](/files/-ML8MzOwHfU6p4YMtmyM)

## Installing and Configuring Elasticsearch and the ReadonlyREST Free Plugin

### Installing Elasticsearch

Elasticsearch will be installed on the lc-win2019-03 server provisioned with 8GB of RAM in Azure.

1. Locate a recent download of [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/windows.html), and install the [MSI](https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.6.2.msi) package.
2. At the time this article was written, the most recent version available was 7.6.2; however, you may want to check for more updated versions as they become available.
3. Launch the downloaded installer and click **Next** on the **Locations** screen, leaving the defaults in place.
4. ![](/files/-ML8MzOx6atG3wczS5iB)
5. Use the defaults on the **Service** screen, and click **Next.**
6. ![](/files/-ML8MzOy3QSS9vmQyK7k)
7. Use the defaults on the **Configuration** screen, and click **Next.**
8. ![](/files/-ML8MzOzQcjivZbrDBv4)
9. No additional plugins are necessary; therefore, click **Next.**
10. ![](/files/-ML8MzP-jE7eMASi6IeV)
11. Leave the **X-Pack** licenses set to **Basic,** and click on **Install.**
12. ![](/files/-ML8MzP0vNwmazSxkAL7)
13. Click on **Exit.**
14. ![](/files/-ML8MzP1JHl0VfWenrm9)

### Installing the Elasticsearch Plugin

1. Navigate to the [ReadonlyREST Plugin download page](https://readonlyrest.com/download/) to enter your details. You will receive the download link in your email. Make sure to choose the **Free Elasticsearch Plugin** that matches your Elasticstack version.
2. <img src="/files/-ML8MzP2jwzJI0T_r-0j" alt="" data-size="original">
3. Download the plugin, open an Administrative command prompt, and navigate to the Elasticsearch program directory. Run the plugin installation by entering the following:
4. cd "C:\Program Files\Elastic\ElasticSearch\7.6.2\bin"

   elasticsearch-plugin.bat install file:///C:/Users/lc-admin.AD/Downloads/readonlyrest-1.19.4\_es7.6.2.zip
5. <img src="/files/-ML8MzP3fIuCoc15Wf-n" alt="" data-size="original">
6. Navigate to the C:\ProgramData\Elastic\ElasticSearch\config directory, and create the file readonlyrest.yml.
7. <img src="/files/-ML8MzP4LkzP8JKcqeER" alt="" data-size="original">
8. Open the readonlyrest.yml file in Notepad to run this very basic configuration that configures the following two different access control rules: 1. **“**::KIBANA-SRV::**”**—this rule allows the Kibana server to authenticate to Elasticsearch using digest authentication with the username “kibana” and password “kibana.” 2. “ADFS Users”—this rule uses the ror\_kbn\_auth method which allows SAML authenticates to succeed.
9. Create a random 256-character signature\_key. This key will be shared between Kibana and Elasticsearch.
10. Please note that the kbn1 identifier must match in the ror\_kbn\_authentication and ror\_kbn sections; however, any names can be used for them.

    ```yaml
    readonlyrest:  
     access_control_rules:

     - name: "::KIBANA-SRV::"  
       auth_key: kibana:kibana

     - name: "ADFS Users"  
       ror_kbn_authentication:  
         name: "kbn1"

     ror_kbn:  
     - name: kbn1  
       signature_key: "VEGj@YLLhsAigspnNi2Xsopsqja_nrKUqU__eQW9VQ2!9p!RoeHwc-G.y-MVJtYYcDFCH.e3W2BKcZsoynJaHyjjXyh7kDHjsYKPkczvai-xCzP@Ez3QW23ZBFuReA7kPAqnc6pQ3VeNeFf3sWNoKeJAt_d9J7aFwEvCP2Gb-kQcA8YR*wNWHQuo-jwmmo2Qqpu_Fq3aKFCbNFWUbK@BVwmmKezxn3h687mAkuyhV4.hnfrjVjF-Rphjqmy4.tB8"
    ```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)

11. Restart **Elasticsearch** **Windows Service**. This can be done in the **Services** MMC snapin.

## Installing and Configuring Kibana and the ReadonlyREST Enterprise Plugin

### Installing Kibana

1. Locate a recent download of [Kibana](https://artifacts.elastic.co/downloads/kibana/kibana-7.6.2-windows-x86_64.zip), and download the zip package. At the time this article was written, the most recent version was 7.6.2. You may want to check for more updated links as they become available.
2. Extract the Kibana installation. Note that this is a rather large file. If you have trouble with the default Windows zip extractor, you may want to try a tool such as 7-Zip.
3. ![](/files/-ML8MzP5N8GiZa5LnT8O)
4. Move the extracted folder to C:\kibana. This may require you to rename the folder.
5. ![](/files/-ML8MzP6jKvwwrOaL0Qj)
6. Open an administrative command prompt, and navigate to the **Kibana** directory to run the kibana.bat batch file and start **Kibana.**
7. ![](/files/-ML8MzP7285XyaXkgz76)
8. Once Kibana has started, navigate to <http://localhost:5601> to verify that Kibana is functional.
9. ![](/files/-ML8MzP8siJAvZaHsxMR)

### Creating a Self-Signed Certificate for Kibana

It is necessary to make Kibana operate under SSL for AD FS to perform SAML authentication.

1. The easiest way to generate a self-signed certificate using the required format is to use **OpenSSL**. A Windows version of this tool available for download is located [here](https://slproweb.com/products/Win32OpenSSL.html).
2. <img src="/files/-ML8MzP9BVWUmnqhyW7e" alt="" data-size="original">
3. If **Microsoft Visual C++ 2017 Redistributables (64-bit)** is not already installed, click **Yes** to download the installation and run the installer first.
4. Accept the license agreement, and click on **Install.**
5. Back on the OpenSSL installation, click on **I accept the agreement**, then click on **Next.**
6. <img src="/files/-ML8MzPEKUnAMtcYJyrY" alt="" data-size="original">
7. Click **Next** on the **Destination Location** screen.
8. <img src="/files/-ML8MzPFH9eO15zetn3U" alt="" data-size="original">
9. Click **Next** on the **Select Start Menu Folder** screen.
10. <img src="/files/-ML8MzPG6anBhdd6HGkW" alt="" data-size="original">
11. Select **The** **Windows system directory** on the **Additional Tasks** screen, and click **Next.**
12. <img src="/files/-ML8MzPH5a1Zqw4yzJYw" alt="" data-size="original">
13. Click on **Install.**
14. <img src="/files/-ML8MzPI4JtF9KUOH-Pp" alt="" data-size="original">
15. Click on **Finish.**
16. <img src="/files/-ML8MzPJg_JPzswdklXM" alt="" data-size="original">
17. Open an administrative command prompt, and run the following command to create the certificates in the specific X509 PEM format that Kibana requires:
18. "C:\Program Files\OpenSSL-Win64\bin\openssl.exe"

    req -x509 -sha256 -nodes -days 730 -newkey rsa:2048 -keyout localhost-key.pem -out localhost.pem -subj "/C=US/ST=IL/L=Bloomington/O=lc-test/CN=10.0.0.6"
19. Change the subj to one that is more indicative of your installation. Make sure the CN={IP Address} matches the accessible IP of your Elasticsearch/Kibana server.
20. <img src="/files/-ML8MzPK94doTw91VUul" alt="" data-size="original">
21. Locate the newly created pem certificates and copy them to C:\kibana\ssl\_cert.
22. The ssl\_cert directory will need to be created first. For our purposes here, it has been arbitrarily named.
23. <img src="/files/-ML8MzPLOMkk6r0gGfpT" alt="" data-size="original">
24. Restart Kibana by entering **Ctrl-C** in the running command prompt window and then re-running kibana.bat.

### Installing the ReadonlyREST Enterprise Plugin

1. Navigate to the [ReadonlyREST Plugin download page](https://readonlyrest.com/download/) to enter your details. You will get the download link in your email. Making sure to choose the **Enterprise Kibana Plugin** and match it with your Elasticstack version.
2. The email that you receive will contain installation instructions. The link will be time-limited, as shown below.
3. Navigate to C:\kibana\config, and locate the kibana.yml configuration file.
4. Open the kibana.yml file in Notepad and update it with the following details:

```yaml
    elasticsearch.username: kibana  # This field matches the first part (pre-colon) of the auth\_key in the readonlyrest.yml Elasticsearch configuration file.
    elasticsearch.password: kibana # This field matches the second part (post-colon) of the auth\_key in the readonlyrest.yml Elasticsearch configuration file.
    elasticsearch.ssl.verificationMode: true # Set the value to “true” to ignore SSL errors. This is useful when working in a test environment.
    
    server.host: 10.0.0.6 # We need to use a routable address, which, in this case, is the 10.0.0.6 IP of this server.
    server.ssl.enabled: true # This is used to turn on SSL and respond to https.
    server.ssl.certificate: '/etc/kibana/ssl_cert/localhost.pem' # This is the location of the public key certificate.
    server.ssl.key: '/etc/kibana/ssl_cert/localhost-key.pem' # This is the location of the private key for the certificate.
    readonlyrest_kbn:
      logLevel: debug # The value is set to “debug” to enable troubleshooting in the console.
      clearSessionOnEvents: [ login ] # This clears the session on a successful login event.
      auth:
        signature_key: "VEGj@YLLhsAigspnNi2Xsopsqja_nrKUqU__eQW9VQ2!9p!RoeHwc-G.y-MVJtYYcDFCH.e3W2BKcZsoynJaHyjjXyh7kDHjsYKPkczvai-xCzP@Ez3QW23ZBFuReA7kPAqnc6pQ3VeNeFf3sWNoKeJAt_d9J7aFwEvCP2Gb-kQcA8YR*wNWHQuo-jwmmo2Qqpu_Fq3aKFCbNFWUbK@BVwmmKezxn3h687mAkuyhV4.hnfrjVjF-Rphjqmy4.tB8" # This must match the 256-character value in the signature\_key attribute of the readonlyrest.yml Elasticsearch configuration file.
        saml_adfs:
          buttonName: "ADFS SAML SSO" # This is the name of the login button on the login screen of Kibana.
          enabled: true # This enables the SAML SSO configuration.
          type: "saml" #  For AD FS, this must be “saml.”
          issue: "ror" #  This is the unique identifier that was defined in the AD FS Relying Party Trust configuration, in this case, “ror”.
          protocol: "https" # AD FS requires https.
          entryPoint: "https://{AD_FS Server}/adfs/ls" # This is the entry point for AD FS
          logoutUrl: "https://{AD_FS Server}/adfs/ls?wa=wsignout1.0" # This is the logout call to AD FS
          kibanaExternalHost: "10.0.0.6:5601" # This is the address and port without the protocol preceding (i.e., https).
          usernameParameter: "nameID" # This configuration is only doing authentication, and it must match the nameID parameter.
          # disableRequestedAuthnContext: false # This is optional configuration which can fix known `SAML provider returned Responder error: NoAuthnContext` https://github.com/node-saml/passport-saml/issues/226. Allowed value is true/false
          # authnContext: "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/windows" # Name identifier format to request auth context. Allowed value is a string array of strings. Default: `urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport`
          # identifierFormat: null # Name identifier format to request from identity provider. Allowed value is a string. Default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`
```

### Opening the Firewall Port

To allow the AD FS server to talk to Kibana, we need to open the 5601 port on the Kibana server since \[localhost]\([http://localhost\\](http://localhost\)) is not routable.

1. Open the **Windows Firewall with Advanced Security** screen, and add a new rule under **Inbound Rules.** Choose **Port.**
2. Add the specific local port of **5601,** and click **Next.**
3. Select **Allow the connection,** and click **Next.**
4. Choose all profiles (the default), and click **Next.**
5. Name the rule “Kibana,” and click **Finish.**

## Demonstration

1. Navigate to your Kibana URL ([https://10.0.0.6:5601\\](https://10.0.0.6/:5601\)/) using Chrome or Firefox. Do not use IE or the SSO button may not show up.
2. ![](/files/-ML8MzPMeUJYTLJLnFm_)
3. Click on ADFS, the button configured in the kibana.yml file, and log in with one of the created AD users. Use the defined mail attribute on the AD account (an email address).
4. ![](/files/-ML8MzPNHujPRxOJQ4MR)
5. With a successful login, the Kibana screen will appear, and you will see your SAML authenticated user in the lower right corner.
6. ![](/files/-ML8MzPOOQFRKWOhRIh5)

## Conclusion

ReadonlyREST combined with Elasticsearch and Kibana opens a world of advanced authentication and authorization options to you. Though only a basic configuration was outlined here, many more useful configuration options are available. You can find out more information about these advanced configurations in the ReadonlyREST documentation and in the ROR forums.


# Duo Security MFA

This tutorial is a step by step guide for the integration between [DUO](https://duo.com) multi factor authentication provider and [ReadonlyREST Enterprise](https://readonlyrest.com/enterprise).

The multi factor authentication (MFA) provided by DUO is an additional authorization step for the user after they have inserted the correct credentials. This extra step is mediated by the DUO platform and it is either an SMS, a push notification to their app, or a one time password obtained via their app or google authenticator.

For this tutorial you are going to need:

* A valid installation of ReadonlyREST Enterprise (trial, or official) Kibana plugin. If you haven't got one, [get your own trial build here](https://readonlyrest.com/enterprise).
* A valid trial or paid account in DUO website (see [pricing](https://duo.com/pricing), you are going to need the "Remote Access & Single Sign-On (SSO)" feature.

## Duo Access Gateway (DAG) server configuration

The access gateway is a piece of software released by DUO that takes care of integrating on premises service providers like ReadonlyREST SAML with arbitrary identity providers (like LDAP) and the multi factor authentication features offered by DUO platform.

### Installing Duo Gateway

Follow the instructions for Duo Gateway (<https://duo.com/docs/dag-linux>). The gateway is a Docker container and it will need the ports 80 and 443 to be available, so you will probably need a dedicated VM or Bare Metal so that Duo Gateway can properly bind to these ports.

### Configuring Authentication Source

Once the Duo Gateway is installed, open a browser and point to its web interface to configure it. When you configure the authentication sources, be sure to set the correct username attribute. Keep in mind this value because it will be mapped directly to whatever has been configured in the actual Duo.com dashboard, under `Gateway > Applications` (which we are just about to configure).

![Source](/files/v2MiCtc1zz8ESUfplA4i)

### Configure Application in Duo Admin Dashboard

In the Duo Administration dashboard, go to Applications. Click `Protect an Application`, then search for `Generic` and click `Protect this Application` button.

This will create a generic SAML provider. Set these fields:

* Service provider name: A name that refers to your ReadonlyREST Enterprise installation
* EntityID: Set an existing entity name or use the same as Service Provider Name
* Assertion Consumer Service: The SAML Url assertion found in metadata.xml, the url format is `<kibanaExternalHost>/ror_kbn_sso/assert`.

![SP](/files/tD0cZFuqOow6Ufe6PY0Y)

In SAML Response, set NameID to be the same variable name as configured previously in the authentication source. Leave the default values for the remaining settings. Click `Save Application` and scroll up and download the configuration file.

## ReadonlyRest Configuration

To configure SAML, both Kibana and Elasticsearch ROR configuration needs to be edited to enable SAML in Duo Gateway.

### Configuring the Elasticsearch plugin

Open your `readonlyrest.yml` file or login as a local administrator in your ReadonlyREST Enterprise, and add this extra configuration required for SAML authentication.

```
readonlyrest:
  access_control_rules:

#    [... all your regular ACL blocks ...]

    - name: "ReadonlyREST Enterprise Kibana instance #1"
      ror_kbn_authentication:
        name: "kbn1"

# OPTIONAL FOR SECONDARY KIBANA ###
#
#    - name: "ReadonlyREST Enterprise Kibana instance #2"
#      ror_kbn_authentication:
#        name: "kbn2"

  ror_kbn:
    - name: kbn1
      signature_key: "shared_secret_kibana1_(256+chars)" # <- use environmental variables for better security!

# OPTIONAL FOR SECONDARY KIBANA ###
#    - name: kbn2
#      signature_key: "shared_secret_kibana2(256+chars)" # <- use environmental variables for better security!
```

This authentication and authorization connector represents the secure channel (based on JWT tokens) of signed messages necessary for our Enterprise Kibana plugin to securely pass back to ES the username and groups information coming SAML identity provider.

### Configuring the Kibana plugin

Edit $KIBANA\_HOME/conf/kibana.yml configuration and append:

```
readonlyrest_kbn.auth:
  signature_key: “a very long key (more than 256 characters) goes here …..” # the same signing key added above in ES config
  saml:
    enabled: true
    entryPoint: 'https://duo-gateway.xyz/dag/saml2/idp/SSOService.php?spentityid=demo'
    kibanaExternalHost: 'ror-deployment.xyz' # <-- public URL used by the Identity Provider to call back Kibana with the "assertion" message
    usernameParameter: 'nameID'
    groupsParameter: 'memberOf'
    logoutUrl: 'https://duo-gateway.xyz/dag/saml2/idp/SingleLogoutService.php?ReturnTo=https://duo-gateway.xyz/dag/module.php/duosecurity/logout.php'
    decryptionCert: certs/dag.crt
    cert: certs/dag.crt
```

The following fields are mapped to the Duo Gateway Application Metadata:

* entryPoint: LoginUrl for the SAML Generic Application
* usernameParameter: Default SAML Generic Application value is `nameID`
* logoutUrl: a URL that points to the value found in the screen `Metadata > Logout URL`
* decryptionCert: The downloadable certificate in Metadata (absolute path)
* cert: The downloadable certificate in Metadata (absolute path)
* signature\_key: Signing key string for JWT, must match the same key value in elasticsearch ROR config
* kibanaExternalHost: The Kibana (with ReadonlyREST Enterprise) instance public hostname
* protocol: protocol schema (http or https) of the external Kibana host
* issuer: distinctive name of the identity provider (optional)
* decryptionPvk: service provider private key (string value) (optional)

For more advanced configurations and information, please refer to [passport-saml documentation](https://github.com/bergie/passport-saml)

### Elasticsearch index in Kibana ROR Dashboard

Make sure to update signature\_key in ROR Dashboard with the value. Otherwise you will get JWT errors while login with SAML.

## Login with SAML 2FA enabled

Go to ReadonlyREST Login page (<http://ror-deployment.xyz/login>) and click the SAML SSO button. This will redirect to Duo Security Gateway and ask for a two factor code to proceed. Note that the first time it will provision a two factor seed mapped to the user account.

Once Duo authenticates, it will redirect you to the private Kibana session powered by ReadonlyREST Enterprise.

## Logout from SAML from ReadonlyREST Enterprise logout button

Click the Logout button from ROR Dashboard. This will redirect you to Duo Gateway logout completion page. Follow the instructions and close the window.


# OpenID Connect (OIDC) (Enterprise)

External connectors integration

([Enterprise](https://readonlyrest.com/enterprise))

With ReadonlyREST Enterprise, you can integrate with OpenID Connect (OIDC) Single Sign-on identity providers for both authentication and authorization.

Follow the guides to know more.


# Keycloak

OpenID Connect (OIDC) SSO Integration with Keycloak as an identity provider.

This document will guide you through the task of setting up an excellent, open-source identity provider ([KeyCloak](https://www.keycloak.org)) to work as an external authenticator and authorizer system for your ELK stack. The scenario is the usual:

* A centralised, large Elasticsearch cluster
* A Kibana installation
* We want one, centralised multi tenant Elasticsearch + Kibana;

But with some more enterprise requirements:

* Users need to be able to change their passwords independently
* Users need to verify their emails
* Group managers need to be able to add, remove, block (only) their users.
* [Multi factor authentication (MFA)](https://www.keycloak.org/docs/latest/server_admin/#one-time-password-otp-policies) is a requirement.

## What is Keycloak

Keycloak is an advanced authentication server that lets user administer their credentials, and speaks many authentication protocols, Including OpenID Connect (OIDC) SSO.

### Setup KeyCloak

This tutorial was created using KeyCloak 14.0.0.

1. Download the Keycloak from their [official website](https://www.keycloak.org/archive/downloads-14.0.0.html). This guide will use [keycloak docker image](https://hub.docker.com/r/jboss/keycloak/)
2. Run Keycloak: run docker run -e KEYCLOAK\_USER= -e KEYCLOAK\_PASSWORD= jboss/keycloak where USERNAME and PASSWORD are credentials for your admin account
3. log in as admin
4. Follow the explanation below, or (if your KC version is the same or close enough to this) use the import function to load this [configuration file](https://github.com/beshu-tech/readonlyrest-docs/tree/master/examples/keycloak_ror_OIDC.json)

If you imported the JSON file, you should have a "ror" realm, and an OpenID Connect (OIDC) client called "ror\_oidc" (keep this ID or change the "clientID" setting in kibana.yml). Please now select "ror" realm, navigate to "clients", click "ror\_oidc" client and double-check everything matches with your use case, as this guide assumes both Kibana, Elasticsearch, and Keycloak are running on "localhost".

### Configure Keycloak to work with ROR

First, we want to create a new dedicated "ror" realm, so we don't interfere with any other use of this Keycloak installation.

![keycloak\_screenshot](/files/1YdgJCyaF6XxrXX60HrW)

Then, let's create an OpenId Connect client for this realm:

![keycloak\_screenshot](/files/WRTM1eWBsDlKRxAl4rh2)

Then, configure the OpenID Connect (OIDC) client

![keycloak\_screenshot](/files/Mst7wvtFqi22JSn6xYs4)

**kibana.yml** (without ssl enabled)

```yaml
# More on how to enable SSL on the official documentation of Kibana
server.ssl.enabled: false

elasticsearch:
  hosts: ["https://localhost:9200"] # <-- our Elasticsearch responds to https
  ssl.verificationMode: none
  username: kibana
  password: kibana

readonlyrest_kbn:
  logLevel: debug
  auth:
    # this secret string has to be longer than 256 chars, use environmental variables to fill it in maybe.
    signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"
    oidc_kc:
      buttonName: "KeyCloak OpenID"
      type: "oidc"
      protocol: "http"
      issuer: 'http://localhost:8080/auth/realms/ror' <-- Get it from OpenID Endpoint Configuration
      authorizationURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/auth' <-- Value from OpenID Endpoint Configuration
      tokenURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/token' <-- Value from OpenID Endpoint Configuration
      userInfoURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/userinfo' <-- Value from OpenID Endpoint Configuration
      clientID: 'ror_oidc' <-- Declared in a realm Client Scopes
      clientSecret: '35d0c1db-a2b7-42d9-9a43-bea88c6535e6'  <-- Declared in a realm ror_oidc (our created client) Credentials tab
      scope: 'openid profile roles role_list email' <-- Declared in a realm Client Scopes
      usernameParameter: 'preferred_username'
      groupsParameter: 'groups'
      kibanaExternalHost: 'localhost:5601'
      logoutUrl: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/logout' <-- Value from OpenID Endpoint Configuration
      jwksURL: 'http://localhost:8080/auth/realms/ror/protocol/openid-connect/certs' <-- Value from OpenID Endpoint Configuration
      # tokenEndpointAuthMethod: 'client_secret_post' <-- Optional value, the way the auth information will be send to the OIDC provider. Possible values 'client_secret_post' | ''client_secret_basic'
      # proxyURL: 'https://localhost:6200' <-- Optional value. Your proxy server URL
```

To verify all OpenID Endpoint Configuration-based, you can open OpenID Endpoint Configuration page in the kibana realm

![keycloak\_screenshot](/files/uZMmHpZevJRQkpOTeNlu)

To provide clientSecret value, you need to open ror\_oidc client (or your custom client name)

![keycloak\_screenshot](/files/kgw6AQ1boRtpk3ski5h1)

### Setup Elasticsearch with ReadonlyREST

Our elasticsearch can be run with or without SSL. To make it available on HTTPS (more detailed info in our [documentation](/elasticsearch#encryption)).

Then write in **readonlyrest.yml**

```yaml
readonlyrest:

    audit:
      enabled: true
      outputs: 
      - type: index

    access_control_rules:
    - name: "::KIBANA-SRV::"
      auth_key: kibana:kibana
      verbosity: error

    - name: "ReadonlyREST Enterprise instance #1"
      kibana:
        access: ro
        index: ".kibana_sso"
      ror_kbn_authentication:
        name: "kbn1"

    ror_kbn:
    - name: kbn1
      # It has to be the same string as we declared in kibana.yml.
      signature_key: "9yzBfnLaTYLfGPzyKW9es76RKYhUVgmuv6ZtehaScj5msGpBpa5FWpwk295uJYaaffTFnQC5tsknh2AguVDaTrqCLfM5zCTqdE4UGNL73h28Bg4dPrvTAFQyygQqv4xfgnevBED6VZYdfjXAQLc8J8ywaHQQSmprZqYCWGE6sM3vzNUEWWB3kmGrEKa4sGbXhmXZCvL6NDnEJhXPDJAzu9BMQxn8CzVLqrx6BxDgPYF8gZCxtyxMckXwCaYXrxAGbjkYH69F4wYhuAdHSWgRAQCuWwYmWCA6g39j4VPge5pv962XYvxwJpvn23Y5KvNZ5S5c6crdG4f4gTCXnU36x92fKMQzsQV9K4phcuNvMWkpqVB6xMA5aPzUeHcGytD93dG8D52P5BxsgaJJE6QqDrk3Y2vyLw9ZEbJhPRJxbuBKVCBtVx26Ldd46dq5eyyzmNEyQGLrjQ4qd978VtG8TNT5rkn4ETJQEju5HfCBbjm3urGLFVqxhGVawecT4YM9Rry4EqXWkRJGTFQWQRnweUFbKNbVTC9NxcXEp6K5rSPEy9trb5UYLYhhMJ9fWSBMuenGRjNSJxeurMRCaxPpNppBLFnp8qW5ezfHgCBpEjkSNNzP4uXMZFAXmdUfJ8XQdPTWuYfdHYc5TZWnzrdq9wcfFQRDpDB2zX5Myu96krDt9vA7wNKfYwkSczA6qUQV66jA8nV4Cs38cDAKVBXnxz22ddAVrPv8ajpu7hgBtULMURjvLt94Nc5FDKw79CTTQxffWEj9BJCDCpQnTufmT8xenywwVJvtj49yv2MP2mGECrVDRmcGUAYBKR8G6ZnFAYDVC9UhY46FGWDcyVX3HKwgtHeb45Ww7dsW8JdMnZYctaEU585GZmqTJp2LcAWRcQPH25JewnPX8pjzVpJNcy7avfA2bcU86bfASvQBDUCrhjgRmK2ECR6vzPwTsYKRgFrDqb62FeMdrKgJ9vKs435T5ACN7MNtdRXHQ4fj5pNpUMDW26Wd7tt9bkBTqEGf"
```

There are three rule types available, depending on what you want to achieve:

* [ror\_kbn\_authentication](/elasticsearch#ror_kbn_authentication) (handles only authentication)
* [ror\_kbn\_authorization](/elasticsearch#ror_kbn_authorization) (handles only authorization)
* [ror\_kbn\_auth](/elasticsearch#ror_kbn_auth) (authentication + authorization in a single rule)


# Impersonation (Enterprise)

Impersonation

([Enterprise](https://readonlyrest.com/enterprise))

According to [Wikipedia](https://en.wikipedia.org/wiki/Impersonator):

> An impersonator is someone who imitates or copies the behavior or actions of another.

So, an impersonation can be understood as imitating behaviors or actions. In the context of ReadonlyREST: one user could imitate an action of another user. Why would we want it? Let's suppose the first user is an admin, who has just configured access for a new user. They would like to know if the rule(s) are configured correctly. And here comes the impersonation feature. The admin can impersonate a given user in Kibana and see what the user would see if they logged in themselves.

ROR plugins support impersonation and provide UI for configuring the cluster before using it. Visit the [impersonation details page](/kibana/impersonation) to know more.


# Creating Test Settings

Creating Test Settings

For impersonation to work, some valid Test Settings should be created and saved. It's important that the main ROR setting will be unaffected, so you, as an admin/user don't need to worry that you will break something. Here is how to write test settings:

1. Open the ROR menu
2. Click the Edit security settings button

   ![Test settings ror menu](/files/iw87sWU48DPPEQerYQko)
3. Go into the Test settings tab
4. You can set the "time to live" (TTL), which is a time interval after which the test settings will be automatically deactivated and impersonation session will also abruptly exit
5. You can load current settings as test settings
6. You can deactivate settings manually
7. You can save test settings as settings

   ![test settings tab](/files/VNaMoHLddCxlIu48C5dx)

Read more about [configuring impersonation in the ROR settings](/kibana/impersonation#creating-rors-test-settings).


# Defining external services mock configurations

Defining external services mock configurations

External services mock is used to simulate the response of existing authentication or authorization service like LDAP. You don't need to create a user account for configuration testing. You will only need to define users (and their associated groups) that would normally be returned by the external services, listed in test settings.

![Auth mock](/files/YAFq8GiMbHvaNmvaQpk9)

After clicking add/edit user buttons (1), you will see a dialog with an option to add (2) or remove (3) user from an external service mock

![Add/edit external mock service](/files/QaiUeqqW157D6x5oJieA)


# Impersonating users

Impersonating users

1. Open the ROR menu
2. Click the Edit security settings button

   ![Impersonate ror menu](/files/iw87sWU48DPPEQerYQko)
3. Go into the Impersonate tab
4. You can free type impersonate. This button is available only in the situation when in some cases, the system is not able to receive all usernames. In this case, to impersonate, you need to type impersonating username manually.
5. You can add/edit user in a specific external auth mock service
6. You can impersonate a user and imitate his behavior and actions

   ![Impersonate tab](/files/zmW8krKYqmqy0XerEl62)
7. When an impersonation session is started correctly, the "impersonating" will be visible in the ROR menu as shown in the picture.
8. Click the Finish impersonation button to stop impersonation and go back into Impersonate tab

   ![Impersonate user](/files/0sPmJBK0VphMtMxcRs4R)


# ROR cluster with Elastic Cloud integration

ROR-based cluster with remote X-Pack Security cluster on Elastic Cloud integration

ReadonlyREST plugin cannot be installed on Elastic Cloud. But we can still take advantage of ROR's features with a little, smart trick - [the remote cluster](https://www.elastic.co/guide/en/elasticsearch/reference/current/remote-clusters.html) Elasticsearch feature. A self-managed ROR-based cluster can access an Elastic Cloud cluster when the latter is configured as a remote cluster in the first one.

## Solution Architecture

![Solution architecture](/files/MYuFsXDSKvduHWg8vR3l)

The two clusters will communicate at a transport level. The communication will be secured by two-way SSL. Because both clusters have to be configured to trust each other, the initial configuration procedure requires attention. But we prepared a demo that provides an interactive guide to properly set up the clusters automatically. Moreover, details of the setup are described below. Let's start!

## Configuration

Depending on what you need now, you may be interested in either:

* [Quick Start using our docker-based Playground](/examples/elastic-cloud-cluster-integration/playgroud)
* [Detailed explanation on how to set up the solution](/examples/elastic-cloud-cluster-integration/details)

## Testing

You can test the setup using Kibana sample web logs. Let's see how to do it in a few steps:

1. Open your browser and go to your Elastic Cloud deployment Kibana and add "Sample web logs".
2. In a new browser tab, open your local ROR cluster Kibana (`http://localhost:15601/`) and log in as admin (`admin:admin`).
3. Pick `Stack Management` in the Kibana menu, go to `Data views`, and click `Create data view` to create the data view to explore the sample logs from the Elastic cloud cluster.

   ![Creating data view 1](/files/a9Cyk4gbIhdEeEuSFxkG)
4. Fill out the form to create a data view:

   a) pick `Name` (it doesn't matter what you enter here) b) enter index pattern `escloud:kibana*` c) one index should be matched: `escloud:kibana_sample_logs` d) click `Save data view to Kibana`

   ![Creating data view 2](/files/TJRQLFM8AEHWBKz7cvaK)
5. Pick `Discover` in the Kibana menu. You should see the data. It's great, but currently, you are logged as `admin` who has access to all indices. Let's try with a different user. Click `Log out`.

   ![Discover - admin](/files/7UiAl0k253Sv2TdzFYv5)
6. Let's log in as `user1` (`user1:test`). This user has RO access and should be able to see `escloud:kibana_sample*` indices (check `readonlyrest.yml` or ROR's settings editor while being logged as `admin`). Go to `Discover` in the Kibana menu and check if you see all the logs from the Elastic Cloud cluster.

   ![Discover - user1](/files/7UiAl0k253Sv2TdzFYv5)
7. As you saw, the cross-cluster search and Kibana integration works well :) This is the basic setup and the simple use case. Now, you can play with it and try to do something more complicated.


# Docker-based playground

Docker-based playground

This document is a step-by-step guide on how to bootstrap a playground with a local ROR cluster in docker (one Elasticsearch node and one Kibana node) and connecting it to a real Elastic Cloud deployment using the "Trusted deployment" feature in Elastic Cloud.

This guide requires minimal knowledge because most of the process is automated. This interactive script will help you to do it quickly. As a result of the script, you will have a working local ROR cluster connected to the remote Elastic Cloud cluster.

### Before you start

1. Linux or Mac OS machine (Windows is untested)
2. Account in <https://cloud.elastic.co/> and valid deployment (a free trial is OK)
3. [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) and [Git](https://git-scm.com/) installed

### Running interactive script

1. Clone `ror-sandbox` repository:

   ```bash
   git clone git@github.com:beshu-tech/ror-sandbox.git
   cd ror-sandbox/ror-cluster-elastic-cloud-demo/
   ```
2. Run the interactive script:

   ```bash
   ./run.sh
   ```

   ![Intro](/files/b2J1y2uap8RosX0YNkEW)
3. After hitting enter, you will be asked to download the [CA file](https://en.wikipedia.org/wiki/Certificate_authority) with trusted Elastic Cloud deployment certificates:

   ![Elastic Cloud CA Cert](/files/hoywhRHB3BDvZmgIrWQw)
4. Let's assume the CA file was downloaded and saved in `/tmp` folder. Let's enter the location of the file and hit enter:

   ![Elastic Cloud CA Cert location](/files/zxbBuiBNuwg0isdGtvZg)

   The interactive script will use the CA file and generate certificates of the local cluster and its CA too. Let's hit enter to continue ...

   ![ROR cluster certs generation](/files/AiMG1mAueuJDDd2qU2xm)

   As we can see CA file `ca.crt` of the ROR cluster was created in `/tmp/ror-sandbox/ror-cluster-elastic-cloud-demi/certs/ca` folder.
5. Now, the ROR cluster CA file will be used to add a trusted deployment in Elastic Cloud:

   ![Adding trust deployment instructions](/files/ARIWHAUxjOx5XjdTXnd1)
6. The next step is to configure the Elastic Cloud remote cluster settings. Our script will ask you to provide "Proxy address" and "Server Name". Both can be found in the Elastic Cloud console.

   ![Remote cluster settings](/files/dYgAcP9P854lkswj6GDa)
7. This is all we need to do in the Elastic Cloud console. Now, we can pick Elasticsearch, Kibana and ROR versions:

   ![Picking versions](/files/0UUfDQtvUA0o2VmaKWif)
8. Now, the script will create the docker-compose environment with one node of Elasticsearch with ROR installed and connected to the remote Elastic Cloud cluster. Moreover, one node of Kibana with ROR too will be visible `http://localhost:15601`. It's time to test it now :)

   ![Summary](/files/HquxCNeM959A5Ex5MZ2W)


# Configuration details

Detailed configuration

This is a detailed description of how to configure two Elasticsearch clusters:

1. One in Elastic Cloud (managed Elasticsearch from Elastic) containing the bulk of the data
2. One self-hosted with ReadonlyREST (for enterprise-level access control and authentication)

The objective is to get the two connected using the transport protocol over SSL, so that we can attach a Kibana (with ROR Enterprise installed) to the cluster #2, and from there query the data in cluster #1 using the [Cross Cluster Search (CCS)](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-cross-cluster-search.html) feature.

## Two-way SSL configuration

The local, self-managed ROR cluster connects with the remote Elastic Cloud cluster using the Elasticsearch transport interface. The transport uses two-way SSL to authorize nodes of clusters.

To do that, we need to

1. Generate CA certificates of nodes of the local cluster (using the CA certificates of the Elastic cloud cluster)
2. Use them to add a trusted environment in the Elastic Cloud console
3. Configure the internode SSL and remote cluster settings in `elasticsearch.yml`

The CA certificates of the Elastic Cloud cluster nodes can be downloaded from the security settings of the Elastic Cloud deployment (see [screenshots](/examples/elastic-cloud-cluster-integration/playgroud#running-interactive-script)).

### Generating ROR cluster CA and nodes' certificates

To generate CA certificates in the self-hosted cluster, we will use the `elasticsearch-certutil` which can be found in the `bin` folder in your Elasticsearch location (eg. `/usr/share/elasticsearch/bin/`).

Our working directory structure will look like that:

```bash
/tmp/certs# tree
.
|-- input
`-- output

2 directories, 0 files
```

Let's move the downloaded Elastic Cloud CA certificates file to `/tmp/certs/input` as `elastic-cloud-ca.cer`:

```bash
/tmp/certs# tree
.
|-- input
|   `-- elastic-cloud-ca.cer
`-- output

2 directories, 1 file
```

Now, let's create the `instances.yml` file in the `/tmp/certs/input` directory where we will define all nodes and their properties (see [Elastic instruction for details](https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html#certutil-silent)) eg.

```yaml
instances:
  - name: "ror-es01" #{node name}
    cn:
      - "ror-es01.node.ror-cluster.ror-test" #{node name}.node.{cluster name}.{scope} (the scope will be useful during configuration of the trusted environments in Elastic Cloud deployment security settings)
    dns:
      - "localhost"
    ip:
      - "127.0.0.1"
```

Great, we have all the ingredients to generate the CA certificates of the nodes in our local ROR cluster:

```bash
mkdir -p /tmp/certs/output/ca
bin/elasticsearch-certutil ca --out /tmp/certs/output/ca/ca.p12 --pass mycapassword 
```

Details about the usage of the `elasticsearch-certutil` tool you will find in [Elastic documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html). We have the CA certificate in `p12` format. We need to convert it to `X509`. It can be done using `openssl`:

```bash
openssl pkcs12 -in /tmp/certs/output/ca/ca.p12 -out /tmp/certs/output/ca/ca.crt -nokeys --password pass:mypassword 
```

Let's use our CA and generate certificates for the ROR cluster nodes:

```bash
bin/elasticsearch-certutil cert --silent --in /tmp/certs/input/instances.yml --out /tmp/certs/output/ror-cluster.zip --ca /tmp/certs/output/ca/ca.p12 --ca-pass mypassword --pass mypassword
unzip /tmp/certs/output/ror-cluster.zip -d /tmp/certs/output/ror-cluster
```

The last thing, we need to do, is to import Elastic Cloud CA to the ROR node's keystore:

```bash
jdk/bin/keytool -importcert -noprompt -file /tmp/certs/input/elastic-cloud-ca.cer -alias 'elastic-cloud' -keystore /tmp/certs/output/ror-cluster/ror-es01/ror-es01.p12 -storepass mypassword
```

This is it. The structure of the `certs` folder should look like this:

```bash
/usr/share/elasticsearch# tree /tmp/certs
/tmp/certs
|-- input
|   |-- elastic-cloud-ca.cer
|   `-- instances.yml
`-- output
    |-- ca
    |   |-- ca.crt
    |   `-- ca.p12
    |-- ror-cluster
    |   `-- ror-es01
    |       `-- ror-es01.p12
    `-- ror-cluster.zip

5 directories, 6 files
```

### Adding a new trusted environment in the Elastic Cloud deployment

In Elastic Cloud deployment security settings, there is a Remote Connections section, where you can add a new trusted environment (see [screenshots](/examples/elastic-cloud-cluster-integration/playgroud#running-interactive-script)). The new trusted environment will be the self-managed cluster. To complete the process we need to:

1. upload the ROR cluster CA (`/tmp/certs/output/ca/ca.crt`)
2. select trusted cluster by:
   * ticking `Trust clusters whose Common Name follows the Elastic pattern`
   * entering `Scope ID` (in out example, it was `ror-test`)

* marking that we trust "All deployments" (or specific if you wish)

3. give a name of the environment (pick anything you want)
4. click `Create trust`

And that's it! Now ROR cluster should trust the Elastic Cloud cluster and vice versa.

## The minimal configuration of Elasticsearch & ReadonlyREST settings

`elasticsearch.yml` should look like this:

```yaml
cluster.name: ror-cluster # the same value used in `instances.yml`
node.name: ror-es01  # the same value used in `instances.yml`
network.host: 0.0.0.0

transport.type: ror_ssl_internode
readonlyrest: # we will put in in `elasticsearch.yml` because each node should have different certificate
  ssl_internode: 
    enable: true # we have to enable internode SSL because it's required to communicate with Elastic Cloud remote cluster
    keystore_file: "ror-cluster/ror-es01/ror-es01.p12"
    keystore_pass: "mypassword"
    truststore_file: "ror-cluster/ror-es01/ror-es01.p12"
    truststore_pass: "mypassword"
    key_pass: "mypassword"
    certificate_verification: true # it means that certificates will be validated
    client_authentication: true # ES with ROR acting as a client is going to authenticate itself

cluster.remote.escloud.mode: proxy # `escloud` is a remote cluster name - so to access `index1` on this remote cluster from the local cluster, we should refer it like that: `escloud:index1` (see `readonlyrest.yml` below) 
cluster.remote.escloud.proxy_address: '${ES_CLOUD_PROXY_ADDRESS}' # taken from Elastic Cloud deployment security settings, "Remote cluster parameters" section
cluster.remote.escloud.server_name: '${ES_CLOUD_SERVER_NAME}' # taken from Elastic Cloud deployment security settings, "Remote cluster parameters" section
```

and the `readonlyrest.yml` like this:

```yaml
readonlyrest:

  access_control_rules:

    - name: "KIBANA" # for Kibana 
      type: allow
      auth_key: kibana:kibana

    - name: "ADMIN" # admin user - can change ROR settings
      type: allow
      kibana:
        access: admin
      auth_key: admin:admin
      
    - name: "User 1" # user1 can read remote Elastic Cloud cluster (escloud) indices matching pattern kibana_sample*
      type: allow
      kibana:
        access: ro
      auth_key: "user1:test"
      indices: ["escloud:kibana_sample*"]
```

Kibana configuration doesn't contain anything special.

<details>

<summary>Expand it if you really need to see how it looks like</summary>

\\

`kibana.yml`:

```yaml
server.name: kibana-ror
server.host: 0.0.0.0
elasticsearch.hosts: [ "${ES_REST_API_URL}" ]
monitoring.ui.container.elasticsearch.enabled: true

elasticsearch.username: kibana
elasticsearch.password: kibana

# ReadonlyREST required properties
readonlyrest_kbn.cookiePass: '12312313123213123213123abcdefghijklm'
```

</details>


# Custom middleware (Enterprise)

Custom middleware

([Enterprise](https://readonlyrest.com/enterprise))

Sometimes, Enterprise users might need more flexibility and customize the plugin behavior to adjust the product to the business needs. There are two options to declare the custom middleware:

* JS file: `readonlyrest_kbn.custom_middleware_inject_file: '/path/to/your/file.js'` // You can also use a relative path here. It's relative to the Kibana root folder
* Inline: `readonlyrest_kbn.custom_middleware_inject: 'function test(req, res, next) {logger.debug("custom middleware called"); next()}'`


# Enriching the metadata

Enriching the metadata

The metadata is the user-specific data available after the Kibana user successfully logs in. Thanks to the custom middleware, you can enrich metadata and use them in the Kibana custom js file. For example to load a custom logo to the Kibana you can:

1. Declare `readonlyrest_kbn.custom_middleware_inject_file: 'path/to/custom_middleware_inject_file.js'` in the kibana.yml and declare `custom_middleware_inject_file.js`

```ts
async function customMiddleware(req, res, next) {
  const rorRequest = req.rorRequest;
  const userRequest = rorRequest && (await req.rorRequest.getUserRequestIdentity());
  const metadata = userRequest && userRequest.metadata;

  if (metadata && metadata.username === 'admin') {
    req.rorRequest.enrichIdentitySessionMetadata({
      newLogo:
        'PHN2ZyBpZD0ic3ZnIiB2ZXJzaW9uPSIxLjEiIHdpZHRoPSI0MDAiIGhlaWdodD0iMzYzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJkaXNwbGF5OiBibG9jazsiPgogICAgPGcgaWQ9InN2Z2ciPgogICAgICAgIDxwYXRoIGlkPSJwYXRoMCIKICAgICAgICAgICAgICBkPSJNMTIyLjgzNiAxMS42MTEgQyAxMTMuNTg2IDE0LjQwNCwxMDguMDAyIDkzLjQxNiwxMDkuOTgyIDE5My41MDAgQyAxMTEuNTE1IDI3MC45ODAsMTExLjI4OCAzMDAuNzQzLDEwOS4wODUgMzExLjI4MyBDIDEwNS4yNjkgMzI5LjU0NCwxMDAuNDI2IDMyNy4zNDAsOTYuMzczIDMwNS41MDAgQyA5NC44ODcgMjk3LjQ4OSw5NC42MzEgMjgxLjI3Nyw5NC4wNDMgMTU4LjAwMCBDIDkzLjM1NCAxMy40NTcsOTMuNDQzIDE2LjAwMCw4OS4wNjggMTYuMDAwIEMgNjcuMDkxIDE2LjAwMCwzMC42ODMgNDQuNjgwLDE5Ljc2MCA3MC41OTUgQyAxMS43NDggODkuNjA3LDkuMjk2IDEzMi42NTcsMTQuNzI1IDE1OS4wMDAgQyAzMC4xNjkgMjMzLjkzOCw1NC45MjIgMjg4LjYxNiw4Ny42MzYgMzIwLjA1OCBDIDEyMi4xNjAgMzUzLjIzOCwxNzAuOTYxIDM1Ny45MjAsMjMwLjAwMCAzMzMuNzE1IEMgMjQ3LjY5OSAzMjYuNDU5LDI0OC4yNjEgMzI1LjA5OCwyNDIuMjg0IDMwNC4wMDAgQyAyMjkuNzc2IDI1OS44NDYsMjE3LjE2OCAyMzkuMDE4LDE3Ni42MDQgMTk1LjUwMCBDIDE1My43NDYgMTcwLjk3OCwxNDkuMzkxIDE2NC4zMzQsMTQ1LjA4NiAxNDcuNDE5IEMgMTM3LjE3NyAxMTYuMzQ3LDE0MS4zMjcgOTIuMzg0LDE2My41MTcgNDEuMDAwIEMgMTc0LjkwNiAxNC42MjYsMTc0Ljg4OCAxNC40NjksMTYwLjM2OCAxMy4wMTUgQyAxNDguODIxIDExLjg2MCwxMjQuOTY4IDEwLjk2NywxMjIuODM2IDExLjYxMSBNMTk2LjA2MSAyMi4xNDEgQyAxOTUuNTE0IDIzLjQzOCwxOTMuMDU1IDI5LjkwMCwxOTAuNTk3IDM2LjUwMCBDIDE4OC4xNDAgNDMuMTAwLDE4My4yMTAgNTUuNTg2LDE3OS42NDQgNjQuMjQ3IEMgMTUzLjU3MiAxMjcuNTU5LDE1NC4wMjUgMTMxLjI3NCwxOTMuMDM2IDE3NC4wMDAgQyAyMjYuMjg4IDIxMC40MTksMjQ5Ljk2OSAyNTIuOTM2LDI1OC40ODEgMjkxLjUwMCBDIDI2MC44NTIgMzAyLjI0MywyNjIuNzkyIDMwOS4yMDksMjYzLjg3OCAzMTAuODc2IEMgMjY0Ljg4NiAzMTIuNDI1LDI3MC4wMzMgMzA5LjI5NCwyNzguNzI0IDMwMS44NDcgQyAyODEuMzUxIDI5OS41OTYsMjg2LjQyNSAyOTUuMjQ3LDI5MC4wMDAgMjkyLjE4MiBDIDMzMy40NTYgMjU0LjkzMCwzNzQuMTM0IDIwMS45NzMsMzgxLjkzMSAxNzIuNTAwIEMgMzkzLjczMiAxMjcuODkwLDMzNi4yMDMgNjguNTg2LDI0OS4wMDAgMzUuNDY3IEMgMjQ3LjA3NSAzNC43MzYsMjQzLjAyNSAzMy4xODMsMjQwLjAwMCAzMi4wMTUgQyAyMTMuNDEwIDIxLjc0OCwxOTcuNzE1IDE4LjIyMSwxOTYuMDYxIDIyLjE0MSAiCiAgICAgICAgICAgICAgc3Ryb2tlPSJub25lIiBmaWxsPSIjMDBiZmIyIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjwvcGF0aD4KICAgIDwvZz4KPC9zdmc+Cg=='
    });
  }

  return next();
}
```

In this example, thanks to the enrichIdentitySessionMetadata method we can pass new logo custom metadata when the logged-in user username is 'admin'. It mustn't be a static value, you can ask for external service for the logo:

```ts
if (metadata && metadata.username === 'admin') {
    const response = fetch(<EXTERNAL_SERVICE_URL>);
    req.rorRequest.enrichIdentitySessionMetadata({
      newLogo: await response.json().newLogo
    });
  }
```

Now, enriched metadata will be available in the custom kibana js script, where you can perform client-based operations like logo replacement.

**⚠️IMPORTANT** Custom middleware must return `next()` function, to not block the request

2. To replace the logo, we need to declare the custom Kibana JS file `readonlyrest_kbn.kibana_custom_js_inject_file: '/path/to/custom_kibana.js'`

```js
const logoHeader = document.querySelector('.euiHeaderLogo');

if (window.ROR_METADATA.newLogo) {
  Array.from(logoHeader.childNodes).forEach(node => {
    node.style.display = 'none';
  });

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      mutation.addedNodes.forEach(node => {
        const customLogo = document.querySelector('#customLogo');

        const createCustomLogo = () => {
          const img = document.createElement('img');
          img.src = `data:image/svg+xml;base64,${window.ROR_METADATA.newLogo}`;
          img.style.width = '32px';
          img.style.height = '32px';
          img.id = 'customLogo';
          logoHeader.appendChild(img);
        };

        const hideAllLogoElements = () => {
          Array.from(logoHeader.childNodes).forEach(node => {
            node.style.display = 'none';
          });
        };

        const handleInit = () => {
          hideAllLogoElements();
          createCustomLogo();
        };

        if (customLogo) {
          const displayCustomLogo = () => {
            customLogo.style.display = 'block';
          };
          const hideCustomLogo = () => {
            customLogo.style.display = 'none';
          };
          if (node.role === 'progressbar') {
            hideCustomLogo();
          }

          if (node.role === 'img') {
            const hideDefaultLogo = () => {
              node.style.display = 'none';
            };

            hideDefaultLogo();
            displayCustomLogo();
          }
        }

        if (node.dataset.type === 'logoElastic' && !customLogo) {
          handleInit();
        }
      });
    });
  });

  observer.observe(logoHeader, { childList: true });
}
```

All session metadata will be available via `window.ROR_METADATA` property. To get your custom logo, just use `window.ROR_METADATA.newLogo` value. In the example above, after login in as a user with username `admin` you will see a custom logo. The whole example is a little complex but seems, Kibana logo is also a loading indicator, we need to detect the loading state, replace the logo with a spinner, and after the loading, back the custom logo again.


# Reject machine-to-machine traffic using custom metadata ACL rules

Reject machine-to-machine traffic using custom metadata ACL rules

We can also reject the specific request for example based on the custom metadata

1. Define ACL in your `readonlyrest.yml` file

```yaml
  - name: ADMIN_GRP
    groups_any_of: [ administrators ]
    kibana:
       access: admin
       index: '.kibana_@{acl:current_group}'
       metadata:
          rejectBasicAuth: true
```

2. Declare custom Kibana JS file `readonlyrest_kbn.kibana_custom_js_inject_file: '/path/to/custom_kibana.js'`. it's injected at the end of the HTML Body tag of the Kibana UI frontend code.

```js
async function customMiddleware(req, res, next) {
  const rorRequest = req.rorRequest;
  const userRequest = rorRequest && (await req.rorRequest.getUserRequestIdentity());
  const metadata = userRequest && userRequest.metadata;

  const authorizationHeaders = rorRequest && (await rorRequest.getIdentitySessionHeaders());

  const headerAuth = authorizationHeaders && authorizationHeaders.get('authorization');
  const isBasicAuth = headerAuth && headerAuth.includes('Basic');

  if (metadata && metadata.customMetadata && metadata.customMetadata.rejectBasicAuth && isBasicAuth) {
    return res.status(401).json({ message: 'Machine to machine communication is not allowed' });
  }

  return next();
}

```

You can pass any custom metadata and based on it accepts or reject the specific request

**⚠️IMPORTANT** Custom middleware must return `next()` function, to not block the request


# Reordering available tenancies

Reordering available tenancies

We can change the default tenancy, and the display ordering of the tenancies in the ROR menu by providing the `defaultGroup` query parameter in the HTTP request submitted by the login form, and change the order of `availableGroups` thanks to the `enrichIdentitySessionMetadata` method.

1. Declare `readonlyrest_kbn.custom_middleware_inject_file: 'path/to/custom_middleware_inject_file.js'` in the kibana.yml and declare `custom_middleware_inject_file.js`

```js
async function customMiddleware(req, res, next) {
    const rorRequest = req.rorRequest;
    const userRequest = rorRequest && (await req.rorRequest.getUserRequestIdentity());
    const metadata = userRequest && userRequest.metadata;
    const defaultGroup = 'infosec';
    const X_FORWARDED_USER = 'x-forwarded-user';
    
    if (rorRequest.getPath() === '/login' && rorRequest.getMethod() === 'post') {
        // For the login form
        if (rorRequest.getBody().username === 'admin') {
            rorRequest.setQuery('defaultGroup', defaultGroup);
        }

        // For the SAML/OIDC login
        const token = rorRequest.getBody().conn_svc_transient_jwt;
        if (token) {
            const parsedJWT = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());

            if (parsedJWT.user === 'admin') {
                rorRequest.setQuery('defaultGroup', defaultGroup);
            }
        }
    }

    // For the Proxy authorization
    if (!metadata && req.headers[X_FORWARDED_USER]) {
        if (req.headers[X_FORWARDED_USER] === 'admin') {
            rorRequest.setQuery('defaultGroup', defaultGroup);
        }
    }

    if (metadata && rorRequest.getPath() === '/pkp/api/info') {
        const availableGroups = metadata.availableGroups;
        if (availableGroups.some(availableGroup => availableGroup.id === defaultGroup)) {
            const reorderedGroups = [...availableGroups].sort((a, b) =>
                a.id === defaultGroup ? -1 : b.id === defaultGroup ? 1 : 0
            );

            rorRequest.enrichIdentitySessionMetadata({ availableGroups: reorderedGroups });
        }
    }

    return next();
}
```

In this example, before the login to the Kibana, when the username is equal 'admin', we add default tenant `rorRequest.setQuery('defaultGroup', defaultGroup);` which means, that it will be the first tenant opened after the login. During the active Kibana session, we will also change the order of tenants displayed in the ROR menu and our default tenant will be the first on the list.

**⚠️IMPORTANT** Custom middleware must return `next()` function, to not block the request


# Available rorRequest API

Available rorRequest API

You can access the rorRequest API via `req.rorRequest` in your custom middleware. The available options are:

| Property name                                                               | Return value type                                                                                                                                       | Example return value                                                           | Description                                                                |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
| getCookies()                                                                | Record\<string, string>                                                                                                                                 | `{ 'session': 'abc123' }`                                                      | Get all cookies from the request                                           |
| getMethod()                                                                 | Method                                                                                                                                                  | `'GET'`                                                                        | Get the HTTP method of the request                                         |
| getPath()                                                                   | string                                                                                                                                                  | `'/api/v1/users'`                                                              | Get the path of the request                                                |
| getUrl()                                                                    | string                                                                                                                                                  | `'/api/v1/users?foo=bar'`                                                      | Get the full URL including query string                                    |
| getBody()                                                                   | Body                                                                                                                                                    | `{ username: 'john' }`                                                         | Get the request body                                                       |
| getParams()                                                                 | ParamsDictionary                                                                                                                                        | `{ id: '123' }`                                                                | Get route parameters                                                       |
| getQueries()                                                                | ParsedQs                                                                                                                                                | `{ page: '1' }`                                                                | Get query string parameters                                                |
| setQuery(key: string, value: string)                                        | void                                                                                                                                                    | -                                                                              | Set a query parameter on the request                                       |
| getOriginAddress()                                                          | string or undefined                                                                                                                                     | `'192.168.1.1'`                                                                | Get the origin IP address of the request                                   |
| getHeaders()                                                                | Record\<string, string>                                                                                                                                 | `{ host: 'localhost', authorization: 'Basic ...' }`                            | Get all request headers                                                    |
| isCookiePresent(cookieName: string)                                         | boolean                                                                                                                                                 | `true`                                                                         | Check if a specific cookie is present in the request                       |
| getIdentitySessionHeaders()                                                 | Promise\<Map\<string, string>>                                                                                                                          | `Map(2) {'authorization' => 'Basic BWRtaW46ZGV2', 'cookie' => 'cookie value'}` | Get the headers used during authorization                                  |
| getWhitelistedHeaders()                                                     | Promise\<Map\<string, string>>                                                                                                                          | `Map(1) {'x-custom-header' => 'value'}`                                        | Get whitelisted headers from the session                                   |
| getSid()                                                                    | Promise\<SID or null>                                                                                                                                   | `'a5442490-45ee-4a60-a9a1-e62989db3ab1'`                                       | Get the session ID from the request                                        |
| isAuthenticated(input?: { sid?: SID })                                      | Promise\<boolean>                                                                                                                                       | `true`                                                                         | Check if the session is authenticated                                      |
| getUserRequestIdentity(input?: { sid?: SID; predefinedTenancyId?: string }) | Promise<[UserRequestIdentity](https://github.com/beshu-tech/readonlyrest-docs/tree/master/examples/custom-middleware/user-request-identity.md) or null> | Check User request identity section                                            | Get the user request identity (returns `null` if not authenticated)        |
| ensuredIdentityAvailability()                                               | Promise<{ ok: true } or { ok: false; reason: string }>                                                                                                  | `{ ok: true }`                                                                 | Check if identity is available without throwing; returns reason on failure |
| enrichIdentitySessionMetadata(customMetadata: Record\<string, unknown>)     | void                                                                                                                                                    | -                                                                              | Enrich existing user session with additional custom metadata               |
| lastSessionActivityDate()                                                   | Promise\<Date or undefined>                                                                                                                             | `2023-03-23T19:50:37.932Z`                                                     | Date of the last session activity; used in the context of session timeout  |
| extractHiddenAppsNames()                                                    | Promise\<string\[]>                                                                                                                                     | `[ 'Enterprise Search, Overview', 'Observability' ]`                           | List of all hidden apps for the current user                               |
| getTenancyId(sid: SID or undefined)                                         | Promise\<string or undefined>                                                                                                                           | `'my-tenant'`                                                                  | Get the tenancy ID associated with the given session ID                    |

You also have access to the standard [Express.js](https://expressjs.com) [request](https://expressjs.com/en/api.html#req) and [response](https://expressjs.com/en/api.html#res) objects


# Secure Logstash

We have a Logstash agent installed somewhere and we want to ship the logs to our Elasticsearch cluster securely.

## Elasticsearch side

**Step 1: Bring Elasticsearch HTTP interface (port 9200) to HTTPS** When you get SSL certificates (i.e. from your IT department, or from LetsEncrypt), you should obtain a private key and a certificate chain. In order to use them with ReadonlyREST, we need to wrap them into a JKS (Java key store) file. For the sake of this example, or for your testing, we won't use real SSL certificates, we are going to create a self signed certificate.

Remember, we'll do with a self-signed certificate for example convenience, but if you deploy this to a server, use a real one!

```bash
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass readonlyrest -validity 360 -keysize 2048
```

Now copy the `keystore.jks` inside the plugin directory inside the Elasticsearch home.

```bash
cp keystore.jks /elasticsearch/config/
```

**IMPORTANT:** to enable ReadonlyREST's SSL stack, open `elasticsearch.yml` and append this one line:

```yaml
http.type: ssl_netty4
```

**Step 3** Now We need to create some credentials for logstash to login, let's say

* user = logstash
* password = logstash

**Step 4** Hash the credentials string `logstash:logstash` using SHA256. The simplest way is to paste the string in an [online tool](http://www.xorbin.com/tools/sha256-hash-calculator) You should have obtained "280ac6f756a64a80143447c980289e7e4c6918b92588c8095c7c3f049a13fbf9".

**Step 5** Let's add some configuration to our Elasticsearch: edit `conf/readonlyrest.yml` and append the following lines:

```yaml
readonlyrest:

  ssl:
    enable: true
    # keystore in the same dir with readonlyrest.yml
    keystore_file: "keystore.jks"
    keystore_pass: readonlyrest
    key_pass: readonlyrest

  global_settings:
    response_if_req_forbidden: Forbidden by ReadonlyREST ES plugin

  access_control_rules:

  - name: "::LOGSTASH::"
    auth_key_sha256: "280ac6f756a64a80143447c980289e7e4c6918b92588c8095c7c3f049a13fbf9" #logstash:logstash
    actions: ["cluster:monitor/main","indices:admin/types/exists","indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
    indices: ["logstash-*"]
```

## Logstash side

Edit the logstash configuration file and fix the output block as follows:

```ruby
 output {
   elasticsearch {
     ssl => true
     ssl_certificate_verification => false
     hosts => ["YOUR_ELASTICSEARCH_HOST:9200"]
     user => logstash
     password => logstash
   }
 }
```

The `ssl_certificate_verification` bit is necessary for accepting self-signed SSL certificates. You might also need to add cacert parameter to provide the path to your .cer or .pem file.


# Secure Metricbeat

Very similar to Logstash, here's a snippet of configuration for [Metricbeat](https://www.elastic.co/downloads/beats/metricbeat) logging agent configuration of the metricbeat - the elasticsearch section

## On the Metricbeat's side

```
output.elasticsearch:
  output.elasticsearch:
  username: metricbeat
  password: hereyourpasswordformetricbeat
  protocol: https
  hosts: ["xx.xx.xx.xx:9200"]
  worker: 1
  index: "log_metricbeat-%{+yyyy.MM}"
  template.enabled: false
  template.versions.2x.enabled: false
  ssl.enabled: true
  ssl.certificate_authorities: ["./certs/your-rootca_cert.pem"]
  ssl.certificate: "./certs/your_srv_cert.pem"
  ssl.key: "./certs/your_srv_key.pem"
```

Of course, if you do not use SSL, disable it.

## On the Elasticsearch side

```yaml
readonlyrest:
  ssl:
    enable: true
    # keystore in the same dir with elasticsearch.yml
    keystore_file: "keystore.jks"
    keystore_pass: readonlyrest
    key_pass: readonlyrest

  access_control_rules:
  - name: "metricbeat can write and create its own indices"
    auth_key_sha1: fd2e44724a234234454324253094080986e8fda
    actions: ["indices:data/read/*","indices:data/write/*","indices:admin/template/*","indices:admin/create"]
    indices: ["metricbeat-*", "log_metricbeat*"]
```


# Elastic Fleet

[Elastic Fleet](https://www.elastic.co/guide/en/fleet/current/fleet-overview.html) manages Elastic Agents centrally through Kibana. When Fleet is set up, it creates two kinds of dynamic Elasticsearch credentials that ReadonlyREST needs to recognize and validate:

* **Service tokens** - used by Fleet Server to authenticate with Elasticsearch. These are created by Kibana during Fleet setup and belong to Elasticsearch's built-in `elastic/fleet-server` service account.
* **API keys** - issued to each enrolled Elastic Agent. Fleet Server creates and rotates these automatically; each agent uses its own key to ship data.

Because both credential types are generated at runtime (not known in advance), they cannot be matched with static `auth_key` or `auth_key_sha256` rules. Instead, ReadonlyREST's `token_authentication` rule with `type: service-token` or `type: api-key` delegates validation to Elasticsearch, which has the ground truth for both.

## ReadonlyREST settings

```yaml
readonlyrest:
  access_control_rules:

    # 1. Kibana user - used by Kibana itself and by Fleet initialisation scripts.
    #    No action or index restriction; Kibana needs unrestricted access during
    #    Fleet setup (e.g. bootstrapping the Fleet Server service account).
    - name: "KIBANA"
      type: allow
      auth_key: kibana:kibana

    # 2. Fleet Server - authenticates using an Elasticsearch service token.
    #    ReadonlyREST validates the token against Elasticsearch's service account API.
    #    No action restriction: Fleet Server needs to call
    #    cluster:admin/xpack/security/api_key/create to issue API keys to
    #    enrolling agents.
    - name: "Fleet server"
      type: allow
      token_authentication:
        type: "service-token"
        username: "fleet"
      indices:
        - ".fleet-servers"
        - ".fleet-agents"
        - ".fleet-actions"
        - ".fleet-policies"
        - ".fleet-policies-leader"
        - ".fleet-enrollment-api-keys"

    # 3. Elastic Agents - each agent authenticates with its own API key, issued
    #    and rotated by Fleet Server. ReadonlyREST validates the key against Elasticsearch
    #    and grants access to the observability data-stream indices.
    - name: "Agents"
      type: allow
      token_authentication:
        type: "api-key"
        username: "fleet"
      indices:
        - ".apm-agent-configuration"
        - "metrics-*"
        - "traces-*"
        - "logs-*"

    # 4. Forbid direct token management - only Kibana and Fleet Server (matched
    #    above) should create or revoke service tokens and API keys. This block
    #    denies these actions for everyone else.
    - name: "Forbid access to service accounts and API keys"
      type: forbid
      actions:
        - "cluster:admin/xpack/security/service_account/*"
        - "cluster:admin/xpack/security/api_key/*"

    # 5. Admin user - full Kibana access.
    - name: "Admins"
      type: allow
      auth_key: admin:admin
      kibana:
        access: admin
```

## How Fleet credentials flow through ReadonlyREST

1. **Kibana creates a service token** - during Fleet setup, Kibana calls `cluster:admin/xpack/security/service_account/*` to create the Fleet Server service token. This request is authenticated by the `KIBANA` block.
2. **Fleet Server creates API keys** - Fleet Server uses its service token to call `cluster:admin/xpack/security/api_key/create`, issuing an API key to each enrolling agent. This request is authenticated by the `Fleet server` block.
3. **Elastic Agents use their API keys** - each agent presents its API key on every request to ship data to Elasticsearch. These requests are authenticated by the `Agents` block.

## Why the `forbid` block is necessary

Only Kibana and Fleet Server should be able to create service tokens and API keys - no other user needs these actions. The `KIBANA` and `Fleet server` blocks already permit these calls for the accounts that legitimately need them. The `forbid` block sits below those blocks and denies any remaining request that targets service-account or API-key management actions, preventing other authenticated users from creating, revoking or listing credentials.

## Credential rotation

You do not need to put service tokens or API key values into `readonlyrest.yml`. ReadonlyREST never sees or stores them - it asks Elasticsearch to validate each token on the fly. This means:

* Fleet Server can rotate its service token without any ReadonlyREST config change.
* Agents can be enrolled, unenrolled, and re-keyed without touching ReadonlyREST.
* The only things that must stay in sync with your deployment are the **index patterns** in the `service-token` and `api-key` blocks.

## Setting up Fleet Server and Elastic Agent

Configuring Fleet Server and enrolling Elastic Agents is covered in the [official Elastic Fleet documentation](https://www.elastic.co/guide/en/fleet/current/fleet-server.html). APM agent setup is documented in the [APM quick-start guide](https://www.elastic.co/guide/en/apm/guide/current/apm-quick-start.html).

## Running the example

A full working example with Elasticsearch, Kibana (both with ReadonlyREST), Fleet Server, an Elastic Agent (APM), a demo Node.js app, and a traffic simulator is available in the [readonlyrest-examples](https://github.com/beshu-tech/readonlyrest-examples/tree/master/examples/fleet) repository:

```bash
curl -sL https://raw.githubusercontent.com/beshu-tech/readonlyrest-examples/master/quickstart.sh | bash -s fleet
```

Once running, log into Kibana and navigate to **Management → Fleet** to see the enrolled agent and its policy, or to **Observability → APM** for traces from the demo application.


# Contribution License Agreement

Thank you for your interest in ReadonlyREST documentation (“Product”), managed by Beshu Limited, a company duly established under the laws of United Kingdom, with registration number No. 10888034, and registered address at Office 32 13-21 Crawford Street, WH1 1PG, the owner the product (“We” or “Us”). We appreciate all the Contributions, made to our Product.

The purpose of this Contribution License Agreement (“CLA”, or “Agreement”) is to clarify the intellectual property rights granted with the Contribution to the Product from any person or entity. This CLA serves as a protection for a Contributor, as well as the protection of Us, our Product and its users.

This Agreement does not change your right to use your Contribution for the other purposes.

By submitting the present Contribution to Us, you acknowledge that you have read this Contribution License Agreement (a copy of which you can download) and that you will abide and comply to the requirements of the Agreement.

## 1. Definitions

“You” means an individual, who is a copyright owner of the Contribution, or a legal entity, which is authorized by a copyright owner to make a Contribution to the Product. “Contribution” means any original work of authorship, including any modifications or additions to the existing work, in which You own or assert ownership of the Copyright, that is intentionally Submitted by You to Us for inclusion in the Product. “Submit” means any form of electronic, verbal, or written communication sent to Us, including but not limited to electronic mailing lists, source code control systems, and issue tracking systems that are managed by Us, for the purpose of discussing and improving of our Product, but excluding communications that are conspicuously marked or otherwise designated in writing by You as “Not a Contribution”. “Product” means OSS ReadonlyREST Plugin for Elasticsearch (specified on the following web-site: <http://readonlyrest.com/download.html>), which is managed by BeShu Tech, which owns the Product.

## 2. Grant of Copyright License

By signing this Agreement, being a subject to the terms and conditions of it, You hereby grant to Us a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable copyright license with the right to sublicense such rights through multiple number of sublicensees, to reproduce, prepare derivative works, modify, publicly display, publicly perform and distribute Your Contributions as a part of the Product.

## 3. Grant of Patent License

By signing this Agreement, You hereby grant to Us a perpetual, worldwide, non-exclusive, no-charge, royalty-free, transferable, irrevocable patent license with the right to sublicense these rights to multiple number of sublicensees, to make, have made, use, offer to sell, sell, import or otherwise transfer the Product, where such license applies only to those claims licensable by You that are necessarily infringed by your Contribution alone or by combination of your Contribution with the Product to which such Contribution was Submitted.

## 4. Our rights

We are not obliged to use Your Contribution as a part of the Product and We reserve the right to decide whether the Contribution is appropriate and can be included to the Product. If We include the Contribution to the Product We may license the Contribution under any licensing terms, including without limitation:

(a) open source licenses like the GPLv3 license; and

(b) binary, proprietary, or commercial licenses.

Except for the licenses granted herein, You reserve all right, title, and interest in and to the Contribution. including copyleft, permissive, commercial, or proprietary licenses.

## 5. Moral Rights

To the extent permitted by law, the You hereby irrevocably and unconditionally waive any and all moral rights conferred by Chapter IV of the UK Copyright Designs and Patents Act 1988 or any rights of a similar nature under laws now or in the future in force in any jurisdiction in and to any and all Contributions to Our Product, submitted by You and agree not to assert such moral rights against Us or any of our licensee, either direct or indirect.

## 6. Your Representations

By signing this Agreement, You represent and confirm that:

* You have a legal authority to enter into this Agreement and You are legally entitled to grant the above license;
* The Contribution is Your original creation and You own a copyright and patent claims covering the Contribution which are required to grant the rights under the sections 2 and 3 of this Agreement; Should You wish to Submit materials that are not Your original creation, You may Submit them separately to the Product if You (a) retain all copyright and license information that was in the materials as you received them, (b) in the description accompanying your Submission, include the phrase "Submission containing materials of a third party:" followed by the names of the third party and any licenses or other restrictions of which You are aware;
* The rights You grant under the Sections 2 and 3 of this Agreement does not violate any grant of rights, which You have made to the third parties;
* If You are an employee, You have received permission to make such Contribution on behalf of the employer;
* If You are less, then eighteen years old, please have Your parents or guardian sign this Agreement.

In addition, You agree to notify Us of any fact or circumstances of which you become aware that would make these representations inaccurate in any respect.

## 7. Disclaimer

EXCEPT FOR THE EXPRESS WARRANTIES IN THE SECTION 6, THE CONTRIBUTION IS PROVIDED ON “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF THE TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.

## 8. Other Provisions of the Agreement

This Agreement shall be governed and construed in accordance with the laws of the United Kingdom.

Unless you explicitly state otherwise, any Contribution shall be under the terms and conditions of this Agreement, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Us regarding such Contribution.

This Agreement sets out the entire agreement between You and Us and overrides all other agreements or understandings.

The relationship of the parties under this Agreement is that of independent contractors, and neither party will have the rights to act as the agent of the other party.

If You or We assign the rights or obligations received through this Agreement to a third party, as a conditions of the assignment, that third party must agree in writing to abide by all the rights and obligations in the Agreement.

If any provisions of this Agreement is found to be invalid or unenforceable, such provisions shall be severed from the Agreement and the remainder of this Agreement shall be interpreted so as to best reflects the original intent of the parties.


# Commercial Licenses

ReadonlyREST PRO and ReadonlyREST Enterprise are commercial subscriptions. They both include the license to use a Kibana plugin that can operate exclusively in synergy with our ReadonlyREST Elasticsearch plugin.

The Kibana plugin included in the Enterprise offer has more functionality than the one included in the PRO subscription. See [readonlyrest.com](https://readonlyrest.com) for the detailed differences.

The ReadonlyREST Elasticsearh plugin is released as open source under the GPLv3 license. However, It is possible to request a quote for obtaining a commercial license that enables you to integrate ReadonlyREST for Elasticsearch and/or for Kibana inside your commercial product.

## What is Priority Support?

Priority support is an email based, private support service with the creators of ReadonlyREST, and it covers two (2) incidents per quarter. Email <support@readonlyrest.com>.

We guarantee a max response time of 2 working days (usually less).

Please remember that the scope of priority support is limited to the resolution of ReadonlyREST issues, not Elasticsearch or Kibana issues, not your application or infrastructure issues.

Any further engagement beyond the above terms requires to either:

* go through the [public forum](https://forum.readonlyrest.com) (outside of SLA terms)
* be purchased as [consultancy days](mailto:finance@readonlyrest.com?subject=ReadonlyREST%20consultancy%20required) ($700 USD / day)
* the subscriber to buy a secondary Enterprise subscription for that year, so to double their quarterly priority support slots.

### Am I eligible of Priority support?

Non paying users, must rely only on community support alone. ReadonlyREST PRO comes with 30 days "onboarding" dedicated support (on the whole ReadonlyREST stack). ReadonlyREST Enterprise comes with 30 days "onboarding" dedicated support (on both plugins) AND **one year of priority support** via email or forum private messages.

In case you have a specific agreement with Beshu Limited (the company behind ReadonlyREST) for a commercial license that allows you to redistribute ReadonlyREST commercially, the priority SLA support does not cover your commercial customers directly. We will accept support requests from you and your staff only, and within the limits stated in the end user license agreement.

### Join the support forum

For enabling priority support:

* Register immediately to the support [forum](https://forum.readonlyrest.com).
* Register using the exact email (or same distinctive domain) used in the license registration
* Ask to be added to the PRO or Enterprise group.

When opening the account, make sure you are using the same domain as the original license email or explain your connection to the licensed company.

After that, when you actually need support, **open a support topic**. Don't forget to:

* Search for similar issues first! Often someone else already reported your issue.
* Start the topic title with `[URGENT]`, `[HIGH]` or `[NORMAL]` severity followed by a description of the issue.
* State whether you are a PRO or Enterprise customer
* State clearly the problem: the input, the desired output and the erroneous output
* Collect the logs and the configuration to reproduce the bug before opening the support topic.

### How severe is my issue?

* **URGENT**: Production is down, your business has stopped, we need to drop everything now and help you.
* **HIGH**: Production is wounded, but still functioning. You aren't sure if it's fatal, we will send help as quickly as possible.
* **NORMAL**: Production seems fine, but you have questions (this is usually the default).

## Is there a trial version?

We publish also a "Free" simplified version of our Kibana plugin, but we also offer a 30 day trial of the full PRO and Enterprise editions. After 30 days, you will need to either uninstall the plugin, or purchase a license (the software will automatically stop working otherwise).

## Can I get a discount?

You can a discount buying multiple licenses, or signing a contract for 2 or more years in advance (with advance payment).

## Licensing

Every organization running ReadonlyREST PRO or Enterprise must have a license. There's no limit to the number of cluster nodes for each cluster. Any license you buy allows you to use our software only **within the scope of your organization**. Please read your end user license agreement (EULA) for any clarification.

## When a subscription lapses

Legally, you must have an active subscription to keep ReadonlyREST PRO or Enterprise running. After a one week grace period, the software will refuse to work and you will not be eligible of priority support.\
Moreover, you won't have access to any more **security updates** or new features.

## Can I upgrade to Enterprise?

Sure, just ask for a discount coupon before deleting your previous subscription. So you'll only be charged the difference when [purchasing an Enterprise license.](https://readonlyrest.com/contact-us) **Please don't forget to mention that you are an existing PRO subscriber.**

## Can I distribute it to my customers?

The short answer is YES, but only as long as you have one valid, active [Embedded](https://readonlyrest.com/embedded/) subscription ongoing. You need to make sure your subscription remains active for as long as your product/solution containing ReadonlyREST is being offered for sale.

The reason you cannot distribute ReadonlyREST as a Free user or a PRO subscriber is that the ElasticSearch plugin is released under the GPLv3 license; and the only legal way you could bundle it into a commercial product/solution is by also releaseing all your software under a GPL compatible license.

We recognise this is rarely possible, that's why we agree to release the ElasticSearch plugin and the Kibana plugin under a commercial license that permits you to redistribute them.

For more legal information, please contact us [filing an inquiry for ReadonlyREST embed](https://readonlyrest.com/contact-us/).

## Can you transfer a license?

Licenses are **not** transferrable to another company. We will transfer the license from a user-specific email to a group email address (e.g. <john_smith@acme.com> -> <tech@acme.com>) but only for **the same domain**. It is strongly recommended that you buy the license using a group email address so the license is not attached to any one employee's email address.

## Obligations as a subscriber

Your purchase gets you access to downloading the PRO and/or Enterprise software. The license agreement requires you to keep this access private. If we find your access credentials are ever publicized:

1. We'll send you a warning email with details. You need to remove the content and change the password.
2. If your access is publicized a second time, we reserve the right to permanently remove access (but won't unless it's really egregious - sloppy contractors happen).

## Can I get a refund?

Yes, up to two weeks after purchase. Let us know the reason and maybe we can help but either way it's not a problem. Email [finance@readonlyrest.com](mailto:finance@readonlyrest.com?subject=ReadonlyREST%20refund%20required).

## What about payment methods?

For new subscriptions or renewals we offer a simplified method where you just receive an invoice, and you pay via credit card or wire transfer, or the full procurement process (quote, purchase order, invoice) at your discretion. For any questions: email [finance@readonlyrest.com](mailto:finance@readonlyrest.com?subject=Payments).


# Changelog

### (2026-07-12) What's new in **ROR 1.70.3**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-54399">CVE-2026-54399</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-54428">CVE-2026-54428</a></summary>

This release addresses two high-severity (CVSS 7.5) denial-of-service vulnerabilities in Apache HttpComponents Core, a dependency used by Elasticsearch. CVE-2026-54399 affects the HTTP/1.1 message parser — a remote attacker can send messages with an excessive number of headers or header length, causing memory exhaustion. CVE-2026-54428 affects the HTTP/2 HPACK decoder — a remote attacker can send oversized compressed header blocks, also leading to memory exhaustion before the header size limit is applied. Both vulnerabilities are fixed by updating the affected dependency.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed CSV report generation failing for users with <code>kibana.access</code>: <code>ro</code> or <code>ro_strict</code></summary>

Users with read-only (`ro`) or strict read-only (`ro_strict`) Kibana access roles were unable to generate CSV reports from saved searches or visualizations. This fix ensures that CSV report generation works correctly for these restricted roles, allowing read-only users to export data without requiring write permissions.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed the Kibana usage counter, which is now stored per tenancy index instead of being shared across tenancies</summary>

Previously, the Kibana usage counter was stored in a shared index, causing usage statistics to be mixed across different tenancies. This fix ensures that each tenancy maintains its own separate usage counter, providing accurate per-tenancy usage tracking and preventing data leakage between tenants.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a node rejecting all requests until restarted when ROR settings could not be read at startup. ROR now keeps retrying until they are available</summary>

When ROR settings (stored in the cluster's system index) were temporarily unavailable at node startup — for example, during cluster initialization or network delays — the node would reject all requests indefinitely until manually restarted. ROR now implements a retry mechanism that continuously attempts to read the settings until they become available, eliminating the need for a manual restart and improving cluster resilience during startup scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) ROR no longer falls back to the local <code>readonlyrest.yml</code> when the in-index settings exist but cannot be read, which could start a node with different rules than the rest of the cluster</summary>

If the in-index ROR settings existed but were temporarily unreadable (e.g., due to a transient error), ROR would silently fall back to the local `readonlyrest.yml` file. This could cause a node to start with a completely different set of security rules than the rest of the cluster, creating a dangerous security gap. ROR now refuses to start with the local file when in-index settings are present but unreadable, ensuring consistent security policy enforcement across all cluster nodes.

</details>

### (2026-06-21) What's new in **ROR 1.70.2**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-12143">CVE-2026-12143</a>, <a href="https://security.snyk.io/vuln/SNYK-JS-DOMPURIFY-17344526">CVE-2026-49458</a>, <a href="https://github.com/advisories/GHSA-76mc-f452-cxcm">GHSA-76mc-f452-cxcm</a>, <a href="https://github.com/advisories/GHSA-gvmj-g25r-r7wr">GHSA-gvmj-g25r-r7wr</a></summary>

🚨Security Fix (KBN) — This release addresses multiple security vulnerabilities in Kibana's bundled dependencies. CVE-2026-12143 is a CRLF injection in the `form-data` library (up to v4.0.5) that could allow header injection via crafted field names. GHSA-76mc-f452-cxcm and GHSA-gvmj-g25r-r7wr are DOMPurify vulnerabilities (up to v3.4.7) that could lead to XSS via hook-based mutation of allowed tags/attributes and template expression bypass inside `<template>` elements respectively. All dependencies have been updated to patched versions.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) 9.4.3, 9.3.7, 9.3.6, 8.19.18, 8.19.17 support</summary>

🚀New (KBN) — Added support for Kibana versions 9.4.3, 9.3.7, 9.3.6, 8.19.18, and 8.19.17. Users running these Kibana versions can now install and use the ReadonlyREST plugin.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.4.3, 9.3.7, 9.3.6, 8.19.18, 8.19.17 support</summary>

🚀New (ES) — Added support for Elasticsearch versions 9.4.3, 9.3.7, 9.3.6, 8.19.18, and 8.19.17. Users running these Elasticsearch versions can now install and use the ReadonlyREST plugin.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/ror-ent-1-70-1-9-4-2-lens-visualization-from-library-in-ro-mode-broken/2995">Visualizations not rendering for <code>kibana.access</code>: <code>ro</code>/<code>ro_strict</code> users on KBN 9.x</a></summary>

🐞Fix (KBN) — Resolved an issue where Lens visualizations from the library would fail to render for users with `kibana.access: ro` or `ro_strict` permissions on Kibana 9.x. This fix restores proper read-only visualization rendering for restricted users.

</details>

### (2026-06-12) What's new in **ROR 1.70.1**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42587">CVE-2026-42587</a></summary>

This release addresses a Netty vulnerability (CVE-2026-42587) where the HttpContentDecompressor's maxAllocation limit was silently ignored for Brotli, Zstd, and Snappy compression encodings, allowing an attacker to trigger unbounded memory allocation and denial of service via a crafted compressed payload. The fix updates the bundled Netty dependency to a patched version that properly enforces the decompression buffer limit for all supported content encodings.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/1-69-1-es9-4-2-unable-to-create-new-tenancy/2989">Fixed tenancy creation after in-place Kibana 8.x→9.x upgrade; stale tenancy indices are repaired automatically (reindex + atomic alias swap)</a></summary>

When upgrading Kibana in-place from 8.x to 9.x, existing tenancy indices could become stale and block the creation of new tenants. This fix automatically detects and repairs such stale indices by performing a reindex operation followed by an atomic alias swap, ensuring a seamless upgrade path without manual intervention.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed Grok Debugger and Painless Lab in DevTools forbidden error for <code>admin</code>, <code>RW</code>, and <code>RO</code>, <code>RO-strict</code> <code>kibana.access</code> levels</summary>

Users with admin, RW, RO, or RO-strict kibana.access levels were incorrectly receiving forbidden errors when trying to use the Grok Debugger and Painless Lab tools in DevTools. This fix ensures these built-in Kibana debugging tools are properly authorized for all standard access levels.

</details>

### (2026-06-03) What's new in **ROR 1.70.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) Fixed <code>kibana.allowed_api_paths</code> to only check Kibana and ReadonlyREST API calls, and to only be usable when <code>api_only</code> user access is configured</summary>

Fixed `kibana.allowed_api_paths` to only check Kibana and ReadonlyREST API calls, and to only be usable when `api_only` user access is configured. Previously, this setting could be misapplied to non-API requests, potentially allowing unintended access. The fix ensures it is scoped strictly to API-only user configurations.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-62718">CVE-2025-62718</a>, <a href="https://nvd.nist.gov/vuln/detail/cve-2026-41673">CVE-2026-41673</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-41907">CVE-2026-41907</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42264">CVE-2026-42264</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-6321">CVE-2026-6321</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-6322">CVE-2026-6322</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-8159">CVE-2026-8159</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-45149">CVE-2026-45149</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-8723">CVE-2026-8723</a>, <a href="https://security.snyk.io/vuln/SNYK-CHAINGUARDLATEST-WAZUHDASHBOARDFIPS-16807878">CVE-2026-46625</a></summary>

Updated Kibana plugin dependencies to patch 10 CVEs across libraries including Axios (proxy bypass, prototype pollution), xmldom (stack overflow DoS), uuid (buffer overflow), fast-uri (path normalization bypass), multiparty (regex DoS), brace-expansion (memory exhaustion), and qs (TypeError on null values). These fixes address vulnerabilities ranging from denial-of-service to credential leakage and request smuggling.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42582">CVE-2026-42582</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42583">CVE-2026-42583</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42587">CVE-2026-42587</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42584">CVE-2026-42584</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42580">CVE-2026-42580</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42585">CVE-2026-42585</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-42581">CVE-2026-42581</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-41417">CVE-2026-41417</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-34479">CVE-2026-34479</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-34480">CVE-2026-34480</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-40490">CVE-2026-40490</a></summary>

Updated Elasticsearch plugin dependencies to patch 11 CVEs across Netty (multiple DoS, request smuggling, and CRLF injection flaws), Apache Log4j (malformed XML output), and AsyncHttpClient (credential leakage on redirect). These fixes address high-severity vulnerabilities including denial-of-service via crafted packets, HTTP request smuggling, and sensitive credential exposure during cross-domain redirects.

</details>

<details>

<summary><strong>🚀New</strong> (ECK) 3.4.1 support</summary>

Added support for Elastic Cloud on Kubernetes (ECK) operator version 3.4.1, ensuring compatibility with the latest ECK release for managing Elasticsearch and Kibana deployments on Kubernetes.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Cleaned newer builds of ancient libraries to avoid false positive CVE scanner reports</summary>

Removed outdated bundled libraries from the Kibana plugin build to eliminate false positive CVE scanner alerts. This cleanup ensures security scanning tools no longer flag ancient dependencies that were present in the build artifacts but not actually used at runtime.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://forum.readonlyrest.com/t/log-files-rotations/2930/2">Added support for rolling-file logging</a></summary>

Added rolling-file logging support for the ROR Kibana plugin, addressing community requests for log file rotation. This prevents log files from growing unboundedly and makes log management easier for production deployments.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) ROR initialisation now retries automatically when Elasticsearch is not yet fully ready at Kibana startup</summary>

ROR initialization now automatically retries when Elasticsearch is not yet fully available during Kibana startup. This eliminates manual restarts in containerized or orchestrated environments where Kibana may start before Elasticsearch is ready to accept connections.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/issues-with-letsencrypt-certs-from-dehydrated-curl-error-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate/2889">External SSL now supports EC private keys produced by dehydrated and similar ACME clients</a></summary>

External SSL configuration now supports EC (Elliptic Curve) private keys generated by dehydrated and similar ACME clients. This resolves compatibility issues where Let's Encrypt certificates obtained via these tools caused SSL handshake failures in ROR's external SSL layer.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Slashed ACL evaluation overhead for wildcard index patterns: 49x more throughput, 98% lower p99 latency, and 50% less CPU compared to the previous ROR version</summary>

Drastically optimized ACL evaluation for wildcard index patterns, delivering up to 49x more throughput, 98% lower p99 latency, and 50% less CPU usage compared to the previous ROR version. This is a significant performance improvement for clusters with complex index pattern rules.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) ROR bootstrap settings in <code>elasticsearch.yml</code> are now configured via proper nested YAML blocks under <code>readonlyrest.*</code> keys</summary>

ROR bootstrap settings in `elasticsearch.yml` can now be configured using proper nested YAML blocks under `readonlyrest.*` keys, providing a cleaner and more intuitive configuration structure compared to the previous flat key format.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Improved consistency of groups rule settings - the <code>users</code> section can only be present in the config when there is at least one groups rule that uses it</summary>

Improved configuration validation for groups rules: the `users` section is now only allowed in the configuration when at least one groups rule actually references it. This prevents orphaned user definitions and makes configuration errors easier to catch at startup.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/cannot-download-generated-report-for-kibana-8-19-7/2927/38">Fixed a problem with downloading reports when multitenancy is disabled for Kibana > 8.13.x</a></summary>

Fixed a problem where downloading generated reports failed with a 404 error in Kibana versions above 8.13.x when multitenancy was disabled. The issue occurred when the `kibana.index` setting was omitted from `kibana.yml` and is now resolved.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a bug with the <code>x-ror-tenancy-id</code> header not being respected in direct Kibana requests</summary>

Fixed a bug where the `x-ror-tenancy-id` header was not being properly respected when making direct requests to Kibana. This ensures that multi-tenant routing via the custom header works correctly in all request scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a problem with the OIDC proxy where the Issuer cert endpoint wasn't passed through a proxy</summary>

Fixed an issue in the OIDC proxy where the Issuer certificate endpoint was not being passed through the configured proxy. This caused OIDC authentication failures in environments where all outbound traffic must go through a corporate proxy.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a problem with correctly setting <code>nextUrl</code> when redirecting from OIDC with an external proxy</summary>

Fixed a problem where the `nextUrl` redirect parameter was not correctly set during OIDC authentication flows when an external proxy was involved. This ensures users are redirected to the correct page after successful OIDC login in proxied environments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a problem with the relative path for <code>readonlyrest_kbn.login_custom_logo</code></summary>

Resolved an issue where the `readonlyrest_kbn.login_custom_logo` setting did not correctly handle relative paths. Custom login page logos configured with relative paths now display properly.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/authorization-via-proxy-auth-does-not-work-correctly/2969/9">Fixed a problem with logging in to Kibana when proxy auth is enabled and the <code>x-forwarded-user</code> header is set</a></summary>

Fixed a login issue in Kibana when proxy authentication is enabled and the `x-forwarded-user` header is present. The proxy auth flow now correctly processes the forwarded user identity, resolving authentication failures reported by users in proxy-based deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a problem with the ROR KBN plugin loading when different plugin versions and non-sticky sessions are used</summary>

Resolved a plugin loading issue that occurred when different ROR plugin versions were deployed across Kibana instances behind a load balancer without sticky sessions. The fix ensures consistent plugin behavior regardless of which Kibana node handles the request.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed the user metadata response returning the same Kibana index for all of a user's groups when the index uses <code>@{acl:current_group}</code></summary>

Fixed a bug where the user metadata response returned the same Kibana index for all of a user's groups when the index pattern used the `@{acl:current_group}` variable. Each group now correctly resolves to its own Kibana index as intended.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed the <code>expand_wildcards</code> parameter being ignored during index resolution: ROR now correctly filters indices and aliases by their open/closed state when resolving wildcard patterns, preventing closed indices from leaking into rewritten requests</summary>

Fixed a critical issue where the `expand_wildcards` parameter was ignored during index resolution. ROR now correctly respects the open/closed state of indices when resolving wildcard patterns, preventing closed indices from being inadvertently included in rewritten requests and causing unexpected behavior.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a missing Kibana access policy in the metadata response when a matched ACL block has no <code>kibana</code> section configured; the default unrestricted access is now always returned</summary>

Fixed a bug where the metadata response was missing the Kibana access policy when a matched ACL block had no `kibana` section configured. The default unrestricted access policy is now always returned, ensuring consistent Kibana behavior even when the ACL block doesn't explicitly define Kibana rules.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/global-checkpoint-sync-blocked/2870">The ES action <code>indices:admin/seq_no/global_checkpoint_sync</code> is now treated as an internal action and bypasses ACL evaluation. This action is dispatched by Elasticsearch internally after write operations and should never require explicit user permissions</a></summary>

The `indices:admin/seq_no/global_checkpoint_sync` action is now treated as an internal Elasticsearch action and bypasses ACL evaluation. This action is dispatched internally after write operations and should never require explicit user permissions. Previously, strict ACL rules could block this action, causing write operation failures.

</details>

### (2026-04-10) What's new in **ROR 1.69.1**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) Fixed vulnerability <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-2950">CVE-2026-2950</a></summary>

Fixed a prototype pollution vulnerability (CVE-2026-2950) in the Lodash library used by Kibana. The issue allowed attackers to bypass a previous fix (CVE-2025-13465) by using array-wrapped path segments in `_.unset` and `_.omit` functions, potentially deleting properties from built-in prototypes. The vulnerability is patched by upgrading Lodash to version 4.18.0.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) 9.4.2, 9.4.1, 9.4.0, 9.3.5, 9.3.4, 9.3.3, 9.2.8, 8.19.16, 8.19.15, 8.19.14 support</summary>

Added compatibility with the latest Kibana versions, including 9.4.x, 9.3.x, 9.2.8, and multiple 8.19.x releases. Users running these Kibana versions can now install and use ReadonlyREST without compatibility issues.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.4.2, 9.4.1, 9.4.0, 9.3.5, 9.3.4, 9.3.3, 9.2.8, 8.19.16, 8.19.15, 8.19.14 support</summary>

Added compatibility with the latest Elasticsearch versions, covering 9.4.x, 9.3.x, 9.2.8, and multiple 8.19.x releases. Users on these Elasticsearch versions can now deploy ReadonlyREST for access control.

</details>

<details>

<summary><strong>🚀New</strong> (ECK) 3.4.0 support</summary>

Added support for Elastic Cloud on Kubernetes (ECK) version 3.4.0, enabling ReadonlyREST deployment in Kubernetes environments running this ECK version.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed <code>jsonwebtoken-ancient</code> being stripped from Kibana builds earlier than 7.11.0</summary>

Fixed an issue where the `jsonwebtoken-ancient` dependency was incorrectly removed from Kibana builds for versions earlier than 7.11.0, which could cause JWT authentication failures on older Kibana deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Filtered out Fleet-based apps from search results when Management is hidden in Kibana 8.x and 9.x</summary>

Fixed a search visibility issue where Fleet-based applications (e.g., Integrations, Fleet) would still appear in Kibana search results even when the Management section was hidden by security rules. These apps are now properly filtered out.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed <code>/pkp/session-probe</code> requests being blocked by browsers that enforce async-only calls</summary>

Fixed a compatibility issue where browsers enforcing async-only fetch calls would block the `/pkp/session-probe` requests used for session health checks. This ensures seamless session validation across all modern browsers.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed a problem with redirecting to the login form after a 401 error following a session probe check</summary>

Fixed a redirect loop issue where users would not be properly redirected to the login form after receiving a 401 error during a session probe check. Users are now correctly prompted to re-authenticate.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a missing Kibana access policy in the metadata response when the matched ACL block has no <code>kibana</code> section configured</summary>

Fixed an issue where the Elasticsearch metadata response was missing the Kibana access policy when the matched ACL rule block did not explicitly define a `kibana` section. The policy is now properly included in the response metadata.

</details>

### (2026-04-02) What's new in **ROR 1.69.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-24001">CVE-2026-24001</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-69873">CVE-2025-69873</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-2391">CVE-2026-2391</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-25639">CVE-2026-25639</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-27904">CVE-2026-27904</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-3449">CVE-2026-3449</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-15599">CVE-2025-15599</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-33750">CVE-2026-33750</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2026-4867">CVE-2026-4867</a>, <a href="https://www.tenable.com/cve/CVE-2026-34601">CVE-2026-34601</a>, <a href="https://nvd.nist.gov/vuln/detail/cve-2022-31129">CVE-2022-31129</a></summary>

This release patches 11 CVEs in Kibana's bundled JavaScript dependencies, addressing denial-of-service (ReDoS/infinite loop), XSS, and crash vulnerabilities in libraries such as jsdiff, ajv, qs, axios, minimatch, @tootallnate/once, DOMPurify, brace-expansion, path-to-regexp, xmldom, and moment. All CVEs are fixed by upgrading the affected dependencies to their patched versions.

</details>

<details>

<summary><strong>🚀New</strong> (KBN/ES) <a href="https://docs.readonlyrest.com/elasticsearch/fleet">Added Fleet support via native API key and service account token authentication (ES 7.14+)</a></summary>

ReadonlyREST now supports Elastic Fleet by validating the two dynamic credential types Fleet creates: service tokens (for Fleet Server) and API keys (for Elastic Agents). The `token_authentication` rule delegates validation to Elasticsearch, so no token values need to be stored in the ROR configuration, and credential rotation requires no config changes.

</details>

<details>

<summary><strong>🚀New</strong> (KBN/ES) The ReadonlyREST Audit Dashboard available in the Kibana plugin now supports audit events written to data streams</summary>

The ReadonlyREST Audit Dashboard can now visualize audit events stored in data streams, in addition to the previously supported regular indices. This ensures compatibility with modern Elasticsearch deployments that use data streams for time-series audit data.

</details>

<details>

<summary><strong>🚀New</strong> (KBN/ES) The ReadonlyREST Audit Dashboard provided by the Kibana plugin can now be used with the ECS (Elastic Common Schema) audit index</summary>

The Audit Dashboard now supports the Elastic Common Schema (ECS) format for audit indices, allowing organizations that standardize on ECS to use the dashboard without requiring a custom audit log serializer.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) <a href="https://forum.readonlyrest.com/t/multi-tenancy-and-link-sharing/1978/3">Added support for opening different tenancies in separate tabs</a></summary>

Users can now open multiple Kibana tenancies in separate browser tabs simultaneously, making it easier to work across different tenants without repeatedly switching contexts.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) <a href="https://forum.readonlyrest.com/t/multi-tenancy-and-link-sharing/1978/3">Added support for sharing links to Kibana visualizations for the selected tenancy</a></summary>

Visualization links now respect the active tenancy context, enabling users to share direct links to Kibana dashboards and visualizations that automatically open in the correct tenancy for the recipient.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Added support for rolling upgrades when upgrading the ROR Elasticsearch plugin and ROR Kibana plugin in a cluster</summary>

Rolling upgrades are now supported for both the ROR Elasticsearch and Kibana plugins, allowing cluster administrators to upgrade nodes one at a time without taking the entire cluster offline.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Removed the need for manual username input in the impersonation mechanism</summary>

The impersonation feature no longer requires administrators to manually type the target username, streamlining the workflow and reducing the chance of typos when testing user permissions.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Fixed an error in Kibana caused by empty data streams in Kibana 8.18.0+</summary>

Resolved an error that occurred in Kibana 8.18.0+ when empty data streams were present, ensuring the Kibana UI remains stable and functional regardless of data stream state.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Added a fallback for an empty <code>indices</code> field in the Audit Dashboard</summary>

The Audit Dashboard now gracefully handles audit events where the `indices` field is empty, preventing visualization errors and ensuring the "Who uses what indices?" view remains functional.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/develop/examples/custom-middleware">Updated custom metadata examples to use the new method. <code>getIdentitySession</code> and <code>getAuthorizationHeaders</code> are now deprecated in favor of <code>getUserRequestIdentity</code>, <code>getIdentitySessionHeaders</code>, and <code>getWhitelistedHeaders</code></a></summary>

The custom middleware API has been updated with new, more clearly named methods. `getIdentitySession` and `getAuthorizationHeaders` are deprecated; users should migrate to `getUserRequestIdentity`, `getIdentitySessionHeaders`, and `getWhitelistedHeaders` for accessing request identity and header information.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch#token_authentication"><code>token_authentication</code> rule extended with <code>api_key</code> and <code>service_token</code> types</a></summary>

The `token_authentication` ACL rule now supports `api_key` and `service_token` as token types, enabling fine-grained access control for Elastic Fleet and other service-to-service authentication scenarios.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/distinguish-between-wrong-credentials-and-missing-permissions/2914">Audit log entries and ACL history now include a human-readable reason when a request is denied, making access-control troubleshooting significantly easier</a></summary>

Denied requests now include a clear, human-readable reason in both audit log entries and ACL history, making it much easier to distinguish between authentication failures (wrong credentials) and authorization failures (missing permissions) during troubleshooting.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Added the new <code>matched_block_names</code> field to audit entries created by audit log serializers other than ECS and custom serializers. The <code>reason</code> field is now deprecated.</summary>

A new `matched_block_names` field has been added to audit entries for non-ECS and non-custom serializers, listing which ACL blocks matched the request. The `reason` field is now deprecated in favor of the more descriptive human-readable reason and `matched_block_names` fields.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Users defined with LDAP, external, and <code>ror_kbn</code> authentication are no longer treated as local users by the impersonation mechanism</summary>

The impersonation mechanism now correctly distinguishes between local users and users authenticated via LDAP, external providers, or `ror_kbn`. This prevents impersonation from incorrectly applying local-user-only logic to externally managed users.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) The ROR Kibana plugin can no longer be used when the <code>prompt_for_basic_auth: true</code> setting is configured</summary>

When `prompt_for_basic_auth: true` is set in the Elasticsearch plugin configuration, the ROR Kibana plugin will now refuse to operate, preventing an incompatible and insecure configuration where Kibana's session management conflicts with the browser's basic auth prompt.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a memory leak related to direct calls via the Kibana API</summary>

Fixed a memory leak that occurred when making direct API calls to Kibana, improving long-term stability and preventing gradual memory exhaustion in production environments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) No longer shows the "Data Set Quality" and "Index management" applications to users with RO or RO_strict access</summary>

The "Data Set Quality" and "Index Management" Kibana applications are now properly hidden from users with read-only (RO) or read-only strict (RO\_strict) access, preventing confusion and ensuring the access control model is consistently enforced.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed JWT token authorization when using embedded Kibana</summary>

Resolved an issue where JWT token authorization failed when Kibana was embedded within another application, ensuring seamless SSO integration in embedded Kibana scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed the styling of the page-not-found screen for Kibana 9.x</summary>

The page-not-found (404) screen now renders with correct styling in Kibana 9.x, eliminating visual glitches and maintaining a polished user experience.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Correctly displays the "Who uses what indices?" Audit Dashboard visualization when indices are not specified in the audit events</summary>

The "Who uses what indices?" visualization in the Audit Dashboard now renders correctly even when audit events lack index information, preventing blank or broken visualizations.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/sending-logs-to-another-cluster/2925">Improved stability when sending audit logs to another cluster, so temporary remote cluster outages no longer affect the main cluster</a></summary>

When audit logs are forwarded to a remote Elasticsearch cluster, temporary outages of that remote cluster no longer impact the stability or performance of the main cluster. The audit log shipping is now resilient to connection interruptions.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed Search Profiler being inactive in Kibana 8.18.0+</summary>

The Search Profiler tool in Kibana 8.18.0+ was not functioning correctly with ROR; this has been fixed, restoring the ability to profile and analyze search query performance.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <code>beshultd/elasticsearch-readonlyrest</code> images for ES 7.16.x, 7.17.0–7.17.6, and 8.0.x–8.4.x now ship with a patched JDK, replacing bundled JDK 17.0.0–17.0.4 / JDK 18, which crashes on cgroup v2 hosts due to JDK-8287073</summary>

Docker images for the affected Elasticsearch versions now include a patched JDK, resolving crashes on cgroup v2 hosts (common in modern Linux distributions and container runtimes) caused by the JDK-8287073 bug in JDK 17.0.0–17.0.4 and JDK 18.

</details>

### (2026-01-07) What's new in **ROR 1.68.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-51999">CVE-2024-51999</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-65945">CVE-2025-65945</a></summary>

This release addresses two Kibana-related security vulnerabilities. CVE-2024-51999 was a rejected CVE issued in error and has been removed. CVE-2025-65945 fixes an improper signature verification flaw in the auth0/node-jws library that could allow attackers to bypass HMAC signature verification when using user-provided data in the secret lookup process.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-67735">CVE-2025-67735</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-66453">CVE-2025-66453</a></summary>

This release patches two Elasticsearch-related security vulnerabilities. CVE-2025-67735 addresses a CRLF injection vulnerability in the Netty framework that could lead to HTTP request smuggling attacks. CVE-2025-66453 fixes a denial-of-service vulnerability in the Rhino JavaScript engine where crafted floating-point numbers could cause excessive CPU consumption.

</details>

<details>

<summary><strong>⚠️Warning</strong> (ES) Audit outputs now use the round-robin strategy for custom audit clusters. <a href="https://docs.readonlyrest.com/elasticsearch/audit#custom-audit-cluster">Audit nodes must belong to the same Elasticsearch cluster; otherwise, audit events may be incomplete</a> for configuration guidelines.</summary>

The audit system now uses round-robin distribution for custom audit clusters. Administrators must ensure all audit nodes belong to the same Elasticsearch cluster to prevent incomplete audit events. This change improves load distribution but requires proper cluster configuration.

</details>

&#x20;       **🚀New** (KBN) 9.3.2, 9.3.1, 9.3.0, 9.2.7, 9.2.6, 9.2.5, 9.2.4, 9.1.10, 8.19.13, 8.19.12, 8.19.11, 8.19.10 support

&#x20;       **🚀New** (ES) 9.3.2, 9.3.1, 9.3.0, 9.2.7, 9.2.6, 9.2.5, 9.2.4, 9.1.10, 8.19.13, 8.19.12, 8.19.11, 8.19.10 support

<details>

<summary><strong>🚀New</strong> (KBN) Added "Remember last picked tenant" feature for external identity providers</summary>

This feature enhances user experience by remembering the last selected tenant when using external identity providers. Users no longer need to reselect their preferred tenant on each login, streamlining the authentication process for multi-tenant environments.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Introduced support for the Kibana Data Set Quality beta application</summary>

ROR now supports the Kibana Data Set Quality beta application, allowing administrators to manage and monitor data quality metrics within their secured Kibana environment. This integration ensures compatibility with Elastic's latest data management tools.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Restyled ROR menu featuring searchable tenancy selector</summary>

The ROR menu interface has been redesigned with a modern look and includes a searchable tenancy selector. This improvement makes it easier for users to find and switch between tenants in environments with large numbers of tenants.

</details>

<details>

<summary><strong>🚀New</strong> (ES) Added new rules: <a href="https://docs.readonlyrest.com/elasticsearch#jwt_authentication"><code>jwt_authentication</code></a> and <a href="https://docs.readonlyrest.com/elasticsearch#jwt_authorization"><code>jwt_authorization</code></a>, as alternatives to the existing <code>jwt_auth</code> rule</summary>

Two new JWT rules provide more granular control over authentication and authorization processes. The `jwt_authentication` rule handles user identity verification, while `jwt_authorization` manages permission assignments, offering greater flexibility compared to the combined `jwt_auth` rule.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#using-ecs-serializer">New audit log serializer compliant with Elastic Common Schema (ECS)</a></summary>

A new ECS-compliant audit log serializer ensures audit events follow Elastic's standardized format. This improves compatibility with Elastic Stack tools and makes audit data easier to analyze using ECS-aware applications and dashboards.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#configuration">The audit can be enabled or disabled on the block level</a></summary>

Audit logging can now be controlled at the individual rule block level, providing finer-grained control over what gets logged. Administrators can enable or disable auditing for specific access control blocks while maintaining global audit settings.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Disabled caching in the Login CSRF protection mechanism.</summary>

Caching has been disabled in the Login CSRF protection to enhance security. This prevents potential CSRF token reuse and ensures each authentication request uses fresh, unique tokens for improved protection against cross-site request forgery attacks.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Made the tenant indicator always visible and improved its dropdown behavior</summary>

The tenant indicator is now always visible in the UI, providing constant awareness of the current tenant context. The dropdown behavior has been improved for better usability and smoother tenant switching experience.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Added stack traces to ReadonlyREST KBN plugin error logs for easier debugging</summary>

Error logs now include full stack traces, making it easier for administrators to diagnose and troubleshoot issues. This enhancement significantly improves debugging capabilities by providing detailed error context and call paths.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/ldap-connection-timeout-leads-to-authentication-error/2899">Added LDAP connection health checking to prevent stale connection authentication failures</a></summary>

Improved LDAP connection health checking prevents authentication failures caused by stale connections in the pool. This fix addresses issues where daily login attempts would fail after periods of inactivity, particularly in environments with network proxies like Kubernetes.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#using-configurable-serializer">Enable nested field definitions in the configurable audit log serializer for more flexible audit logging</a></summary>

The configurable audit log serializer now supports nested field definitions, allowing more complex and structured audit data. This provides greater flexibility in customizing audit event formats to match specific organizational requirements.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#predefined-serializers">The predefined audit log serializers</a> now include a new <code>logged_user</code> field, which contains a human-readable username</summary>

Predefined audit log serializers now include a `logged_user` field displaying human-readable usernames. This enhancement makes audit logs more readable and easier to interpret by showing actual user identities instead of technical identifiers.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved an issue causing the Kibana Search Sessions app to fail on Kibana 8.x</summary>

Fixed a compatibility issue that prevented the Kibana Search Sessions application from functioning properly on Kibana 8.x versions. This ensures full compatibility with Elastic's search session management features.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/errors-after-upgrade-kibana-7-17-29-to-8-19-7/2887">Fixed cluster resolution issues that caused Kibana errors and unexpected logouts in versions 8.19.x and above</a></summary>

Resolved cluster resolution problems that were causing Kibana errors and unexpected user logouts after upgrading to Elasticsearch 8.19.x and later versions. This fix addresses compatibility issues introduced in recent Elasticsearch releases.

</details>

### (2025-11-29) What's new in **ROR 1.67.3**

<details>

<summary><strong>🚀New</strong> (KBN) 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, 8.19.8 support</summary>

ReadonlyREST now officially supports Kibana versions 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, and 8.19.8. This ensures compatibility with the latest Kibana security patches and features, allowing administrators to secure their Kibana instances with the most recent releases.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, 8.19.8 support</summary>

This release adds official support for Elasticsearch versions 9.2.3, 9.2.2, 9.1.9, 9.1.8, 8.19.9, and 8.19.8. Users can now deploy ReadonlyREST with these Elasticsearch versions to benefit from the latest security updates and performance improvements while maintaining full access control functionality.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Resolved index resolution compatibility issue with Elasticsearch 9.1.7</summary>

Fixed a compatibility issue where ReadonlyREST had problems resolving index patterns and aliases correctly when running with Elasticsearch 9.1.7. This fix ensures proper index resolution and access control enforcement for users upgrading to or already using Elasticsearch 9.1.7.

</details>

### (2025-11-13) What's new in **ROR 1.67.2**

<details>

<summary><strong>🚀New</strong> (KBN) 9.2.1, 9.1.7, 8.19.7 support</summary>

ReadonlyREST now officially supports Kibana versions 9.2.1, 9.1.7, and 8.19.7. This ensures compatibility with the latest Kibana security patches and features, allowing users to upgrade their Kibana deployments while maintaining ReadonlyREST security functionality.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.2.1, 9.1.7, 8.19.7 support</summary>

ReadonlyREST now officially supports Elasticsearch versions 9.2.1, 9.1.7, and 8.19.7. This update provides compatibility with the latest Elasticsearch security updates and performance improvements, ensuring seamless integration of ReadonlyREST security features with these Elasticsearch releases.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed SAML/OIDC provider support behind a reverse proxy when <code>server.rewriteBasePath: false</code> is set in kibana.yml</summary>

This fix resolves an issue where SAML and OpenID Connect authentication providers would fail when Kibana is deployed behind a reverse proxy with `server.rewriteBasePath: false` configuration. The problem occurred because ReadonlyREST was incorrectly handling URL rewriting in this specific deployment scenario, preventing successful authentication through reverse proxy setups.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Delegated handling of certain internal exceptions to Elasticsearch, preserving native error responses</summary>

This fix improves error handling by allowing Elasticsearch to process certain internal exceptions natively instead of ReadonlyREST intercepting them. This ensures that error responses maintain their original Elasticsearch format and behavior, providing better compatibility with client applications that expect specific error response structures from Elasticsearch.

</details>

### (2025-11-03) What's new in **ROR 1.67.1**

<details>

<summary><strong>🚀New</strong> (KBN) 9.2.0, 9.1.6, 8.19.6 support</summary>

Ensures compatibility and full security functionality with the latest Kibana releases, allowing safe upgrades to versions 9.2.0, 9.1.6, and 8.19.6.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.2.0, 9.1.6, 8.19.6 support</summary>

Provides official support for Elasticsearch versions 9.2.0, 9.1.6, and 8.19.6, ensuring the plugin's security features work correctly across the latest stack releases.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Allow using the <code>actions</code> rule with the <code>kibana</code> rule in the same block when <code>kibana.access: unrestricted</code></summary>

Removes a previous restriction, granting administrators greater configuration flexibility to combine fine-grained action controls with Kibana access rules in unrestricted blocks.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed JWT handling for wrong license edition</summary>

Resolves an authentication failure where JWT validation incorrectly failed based on license type, ensuring reliable JWT authentication regardless of the Elastic license edition.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Suppressed “Forbidden” toast in Discover/Dashboard on Kibana 8.x–9.x</summary>

Eliminates confusing and unnecessary 'Forbidden' pop-up notifications in Kibana's Discover and Dashboard apps when access is correctly denied by ROR rules, improving the user experience.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/unable-to-download-reports-from-kibana/2859/2">Resolved report download failure on Kibana 9.1.x</a></summary>

Fixes a critical bug that blocked users from downloading reports (PDF, PNG, CSV) from Kibana 9.1.x dashboards and visualizations, restoring essential reporting functionality.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed timeout when saving Security settings</summary>

Addresses a configuration issue where attempts to save Security settings in Kibana would hang and eventually timeout, preventing administrators from applying critical security changes.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Restored visibility of reports when multiple data streams exist for a reporting index</summary>

Corrects an issue where generated reports became invisible in the Kibana UI if the reporting index was backed by multiple data streams, ensuring all reports are accessible.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed invisible reports for non-tenancy users on Kibana 9.1.x</summary>

Resolves a bug specific to Kibana 9.1.x where users not utilizing multi-tenancy features could not see their generated reports, effectively breaking the reporting interface for them.

</details>

### (2025-10-14) What's new in **ROR 1.67.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-58754">CVE-2025-58754</a></summary>

This fix addresses CVE-2025-58754, a Denial of Service vulnerability in the Axios HTTP client library (used by the Kibana plugin). When processing `data:` URIs on Node.js, Axios ignored `maxContentLength` and `maxBodyLength` limits, allowing an attacker to trigger unlimited memory allocation and crash the process. The Axios dependency has been updated to a patched version.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-58057">CVE-2025-58057</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-58056">CVE-2025-58056</a></summary>

Two Netty vulnerabilities have been patched in the Elasticsearch plugin. CVE-2025-58057 is a DoS flaw where the BrotliDecoder and other decompression decoders could allocate unlimited byte buffers, causing Out-of-Memory errors. CVE-2025-58056 is an HTTP request smuggling vulnerability caused by Netty incorrectly accepting standalone newline characters (LF) as chunk-size line terminators instead of requiring CRLF per HTTP/1.1 spec. The Netty dependency has been updated to a fixed version.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#using-configurable-serializer">Added support for defining a custom audit serializer directly in ROR settings (no code required)</a></summary>

Previously, customizing the format of audit log events required writing a Scala or Java serializer, compiling it into a JAR, and adding it to the plugin classpath. Now you can define a custom audit serializer directly in the ROR configuration using YAML — no coding or compilation needed. This makes it much easier to tailor audit event fields to your specific monitoring and compliance requirements.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#predefined-serializers">Introduced new predefined audit serializers: <code>ReportingAllEventsAuditLogSerializer</code>, <code>ReportingAllEventsWithQueryAuditLogSerializer</code></a></summary>

Two new built-in audit serializers have been added. `ReportingAllEventsAuditLogSerializer` logs all audit events regardless of verbosity settings, while `ReportingAllEventsWithQueryAuditLogSerializer` does the same but also captures the full request body. These complement the existing serializers and give administrators more granular control over audit logging without needing custom code.

</details>

<details>

<summary><strong>🚀New</strong> (ES) Added new rules: <a href="https://docs.readonlyrest.com/elasticsearch#ror_kbn_authentication"><code>ror_kbn_authentication</code></a> and <a href="https://docs.readonlyrest.com/elasticsearch#ror_kbn_authorization"><code>ror_kbn_authorization</code></a>, as alternatives to the existing <code>ror_kbn_auth</code> rule</summary>

The existing `ror_kbn_auth` rule combined both authentication and authorization into a single rule. The new `ror_kbn_authentication` and `ror_kbn_authorization` rules allow you to split these concerns into separate ACL blocks, giving you more flexibility to define different authentication methods and authorization logic independently in your security configuration.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/kibana#clock-skew-tolerance">Added OIDC <code>clock-skew-tolerance</code> configuration option in <code>kibana.yml</code></a></summary>

A new `clock-skew-tolerance` configuration option has been added for OIDC authentication in the Kibana plugin. This allows administrators to configure how much time drift (clock skew) is tolerated between the Kibana server and the OIDC identity provider when validating token timestamps, helping to avoid authentication failures in environments with slight clock discrepancies.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/kibana#terminate-kibana-on-es-high-watermark">Added option to disable Kibana termination on watermark errors in <code>kibana.yml</code></a></summary>

Previously, when Elasticsearch disk watermark thresholds were exceeded, the ROR Kibana plugin would terminate Kibana to prevent data loss. A new configuration option has been added to `kibana.yml` that allows administrators to disable this automatic termination behavior, giving them more control over how their cluster handles high watermark scenarios.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Logout did not invalidate the app session when the <code>ror_kbn_auth</code> rule was used with local group definitions</summary>

When using the `ror_kbn_auth` rule with locally defined groups, the logout action was not properly invalidating the Kibana application session. This meant that after logging out, the session could potentially remain active. This has been fixed so that logout correctly terminates the session regardless of how groups are defined.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/kibana-data-view-filter-not-working-with-keyword/2843">Restored keyword field value suggestions in Discover/Data View filters</a></summary>

After upgrading ROR to versions 1.60+, the Discover and Data View filter dropdowns in Kibana stopped showing value suggestions for keyword fields. This regression has been fixed, restoring the expected autocomplete behavior when filtering by keyword fields in Kibana's data exploration tools.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Integration-based options were visible in search results even when the app was marked as hidden</summary>

When certain Kibana apps were configured as hidden, their integration-based options (such as dashboards or visualizations) could still appear in Kibana's global search results. This fix ensures that when an app is marked as hidden, its associated integration options are also properly excluded from search results.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Index Management appeared in app search results even when the app was declared as hidden</summary>

The Index Management app in Kibana was still appearing in global search results even when administrators had explicitly marked it as hidden in the ROR security configuration. This has been corrected so that hidden apps are fully excluded from search results.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved an issue with CSRF token override when multiple browser tabs were open</summary>

When users had multiple Kibana browser tabs open simultaneously, CSRF token management could cause one tab's token to override another's, leading to unexpected request failures. This issue has been resolved to ensure proper CSRF token isolation across multiple tabs.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed OIDC compatibility for Kibana 7.10.2 and earlier</summary>

OIDC authentication was broken on older Kibana versions (7.10.2 and earlier) due to compatibility issues with the ROR Kibana plugin. This fix restores proper OIDC support for users running these legacy Kibana versions.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Restored backward compatibility for custom audit log serializer implementations extending the <code>DefaultAuditLogSerializer</code> class. Custom serializers compiled against ROR 1.65 or 1.66 that use <code>DefaultAuditLogSerializer</code> must be recompiled to work correctly</summary>

Custom audit log serializers that were compiled against ROR 1.65 or 1.66 and extended the `DefaultAuditLogSerializer` class stopped working due to internal API changes. Backward compatibility has been restored, though custom serializers compiled against those versions must be recompiled to work correctly with this release.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed a defect that broke the "Snapshot and Restore" functionality in Kibana</summary>

A defect in the Elasticsearch plugin was preventing the Snapshot and Restore functionality in Kibana from working correctly. This has been fixed, restoring the ability to create, manage, and restore snapshots through the Kibana UI when ROR security is active.

</details>

### (2025-09-03) What's new in **ROR 1.66.1**

<details>

<summary><strong>🚀New</strong> (KBN) 9.1.5, 9.1.4, 9.0.8, 9.0.7 8.19.5, 8.19.4, 8.18.7 support</summary>

ReadonlyREST now supports Kibana versions 9.1.5, 9.1.4, 9.0.8, 9.0.7, 8.19.5, 8.19.4, and 8.18.7. This ensures compatibility with the latest Kibana releases and allows users to upgrade their Kibana instances while maintaining ROR security features.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.1.5, 9.1.4, 9.0.8, 9.0.7, 8.19.5, 8.19.4, 8.18.8, 8.18.7 support</summary>

ReadonlyREST now supports Elasticsearch versions 9.1.5, 9.1.4, 9.0.8, 9.0.7, 8.19.5, 8.19.4, 8.18.8, and 8.18.7. This update provides compatibility with the latest Elasticsearch releases across multiple version branches, ensuring users can securely run ROR with current Elasticsearch deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/ror-1-65-1-java-17/2841">Patching issue in Elasticsearch 9.x, 8.19.x, and 8.18.x that caused startup failures on Java 17</a></summary>

Fixed a compatibility issue that prevented Elasticsearch clusters from starting when using Java 17 with ROR. The patch resolves startup failures affecting Elasticsearch versions 9.x, 8.19.x, and 8.18.x, ensuring smooth operation with modern Java runtime environments.

</details>

### (2025-08-28) What's new in **ROR 1.66.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-7339">CVE-2025-7339</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-7783">CVE-2025-7783</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-54419">CVE-2025-54419</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-9288">CVE-2025-9288</a></summary>

🚨Security Fix (KBN) — Patched multiple CVEs affecting Kibana's Node.js dependencies: CVE-2025-7339 (response header manipulation via `on-headers`), CVE-2025-7783 (HTTP Parameter Pollution via `form-data`), CVE-2025-54419 (SAML assertion bypass in Node-SAML), and CVE-2025-9288 (input validation flaw in `sha.js`). These fixes address vulnerabilities ranging from data manipulation to authentication bypass, ensuring your Kibana instances remain secure.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://forum.readonlyrest.com/t/hidden-functions-are-available-through-the-search/2840/2">Prevented visibility of hidden functions through Kibana UI search</a></summary>

🚨Security Fix (KBN) — Fixed an issue where hidden functions (features restricted by ReadonlyREST rules) could still be discovered and accessed via the Kibana UI search bar. This patch ensures that restricted functionality remains fully hidden from users, closing a potential information disclosure and privilege escalation vector.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) Removed internal failure details from error responses to prevent unintended information disclosure</summary>

🚨Security Fix (ES) — Internal error messages previously exposed stack traces and implementation details in certain failure scenarios. This information is now stripped from error responses, preventing potential leakage of sensitive system internals that could aid an attacker.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) 9.1.3, 9.1.2, 9.0.6, 8.19.3, 8.18.6 support</summary>

🚀New (KBN) — Added compatibility with Kibana versions 9.1.3, 9.1.2, 9.0.6, 8.19.3, and 8.18.6. Users on these versions can now install and run ReadonlyREST Kibana plugin without compatibility issues.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.1.3, 9.1.2, 9.0.6, 8.19.3, 8.18.6 support</summary>

🚀New (ES) — Added compatibility with Elasticsearch versions 9.1.3, 9.1.2, 9.0.6, 8.19.3, and 8.18.6. The Elasticsearch plugin now fully supports these releases.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Refined user metadata selection logic during login to prioritize matched blocks associated with a defined Kibana index</summary>

🧐Enhancement (ES) — Improved the login flow so that when multiple ACL blocks match a user, the system now prioritizes the block that is associated with a defined Kibana index. This results in more predictable and correct user metadata assignment, especially in multi-block configurations.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Patching: improved handling of the consent flag when provided via environment variables for more reliable configuration</summary>

🧐Enhancement (ES) — Enhanced the patching mechanism to more reliably process the consent flag when it is supplied through environment variables. This reduces configuration errors and ensures smoother automated deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved issue with index deletion in <strong>Index Management</strong> via Kibana UI</summary>

🐞Fix (KBN) — Fixed a bug where users with appropriate permissions were unable to delete indices through the Kibana Index Management interface. Index deletion now works correctly when authorized by ReadonlyREST rules.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Corrected document display in <strong>Discover</strong> when indices are defined in the user ACL block</summary>

🐞Fix (KBN) — Resolved an issue where documents were not displayed correctly in the Kibana Discover section when indices were explicitly defined in the user's ACL block. Document browsing now works as expected in restricted index configurations.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an error preventing <strong>Spaces</strong> from being deleted in Kibana <strong>9.1.0</strong></summary>

🐞Fix (KBN) — Addressed a specific error that prevented users from deleting Kibana Spaces in version 9.1.0 when ReadonlyREST was active. Space management now functions correctly on this version.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Corrected handling of <code>readonlyrest_kbn.whitelistedPaths</code> in <code>kibana.yml</code> when <code>xpack.security.enabled: true</code></summary>

🐞Fix (KBN) — Fixed a configuration handling issue where the `readonlyrest_kbn.whitelistedPaths` setting in `kibana.yml` was not properly respected when `xpack.security.enabled` was set to `true`. Whitelisted paths now work reliably regardless of the xpack security setting.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved startup issues for Kibana versions <strong>7.9.0 → 7.10.2</strong></summary>

🐞Fix (KBN) — Fixed a compatibility regression that caused ReadonlyREST to fail during Kibana startup on versions 7.9.0 through 7.10.2. Users on these older Kibana releases can now run the plugin without startup errors.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed report generation when <code>xpack.security.enabled: true</code> and <code>xpack.encryptedSavedObjects.encryptionKey</code> is set in Kibana <strong>8.19.x</strong> and <strong>9.1.x</strong></summary>

🐞Fix (KBN) — Resolved an issue where report generation (e.g., PDF/CSV exports) would fail in Kibana 8.19.x and 9.1.x when both `xpack.security.enabled` and `xpack.encryptedSavedObjects.encryptionKey` were configured. Reports now generate successfully in these environments.

</details>

### (2025-07-15) What's new in **ROR 1.65.1**

<details>

<summary><strong>🚀New</strong> (KBN) 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, 8.17.9 support</summary>

ReadonlyREST now supports the latest Kibana versions including 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, and 8.17.9. This ensures compatibility with recent Kibana releases and their security patches, allowing users to upgrade their Kibana instances while maintaining ReadonlyREST security features.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, 8.17.9 support</summary>

The plugin now supports Elasticsearch versions 9.1.1, 9.1.0, 9.0.5, 9.0.4, 8.19.2, 8.19.1, 8.19.0, 8.18.5, 8.18.4, 8.17.10, and 8.17.9. This update provides compatibility with the latest Elasticsearch releases, including security updates and performance improvements from Elastic.

</details>

<details>

<summary><strong>🚀New</strong> (ECK) 3.1.0 support</summary>

ReadonlyREST now supports Elastic Cloud on Kubernetes (ECK) version 3.1.0. This enables users running Elasticsearch on Kubernetes through ECK to leverage ReadonlyREST's security features in their containerized environments with the latest ECK release.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Docker images now start correctly when <code>I_UNDERSTAND_AND_ACCEPT_ES_PATCHING</code> is set.</summary>

Fixed an issue where Elasticsearch Docker images with ReadonlyREST would fail to start when the environment variable `I_UNDERSTAND_AND_ACCEPT_ES_PATCHING` was set. This variable is commonly used in Elasticsearch Docker deployments to acknowledge patching terms, and the fix ensures smooth container startup.

</details>

### (2025-07-10) What's new in **ROR 1.65.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-5889">CVE-2025-5889</a></summary>

A vulnerability in the `brace-expansion` library (up to versions 1.1.11, 2.0.1, 3.0.0, 4.0.0) could lead to inefficient regular expression complexity and potential denial of service. This fix addresses the issue by updating the affected dependency to a patched version.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/cve-2024-29857">CVE-2024-29857</a> (when FIPS SSL is used)</summary>

A high-severity vulnerability (CVSS 7.5) in the Bouncy Castle cryptographic library (before 1.78) could cause excessive CPU consumption and denial of service when importing an EC certificate with specially crafted F2m parameters. This fix updates the Bouncy Castle dependency and is relevant when FIPS-compliant SSL is configured.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Added support for configuring <a href="https://www.elastic.co/docs/troubleshoot/kibana/using-kibana-server-logs">JSON log format</a> in <code>kibana.yml</code>.</summary>

Administrators can now enable structured JSON logging for Kibana, making it easier to parse, index, and analyze logs with centralized log management tools like Elasticsearch and Logstash.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch/audit#configuration">Added support for a new output type: <code>data_stream</code> in audit logging</a>.</summary>

Audit events can now be stored in Elasticsearch data streams instead of regular indices. Data streams offer better lifecycle management, automatic rollover, and simplified retention policies via ILM. If the specified data stream doesn't exist, ReadonlyREST creates it automatically along with the necessary component templates and index template.

</details>

<details>

<summary><strong>🚀New</strong> (ES) Included Elasticsearch node name and cluster name in the audit reports.</summary>

Audit log entries now contain the originating Elasticsearch node name and cluster name, providing better traceability and context when auditing requests across multi-node or multi-cluster deployments.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Logged detailed messages when the CSRF token has expired.</summary>

Improved logging now provides clearer, more descriptive messages when a CSRF token expires, helping administrators diagnose and troubleshoot authentication-related issues more efficiently.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) <a href="https://docs.readonlyrest.com/kibana#user-info-source-methods">Added <code>id_token</code> as a valid option for <code>userInfoSource</code></a>.</summary>

Administrators can now configure the `userInfoSource` setting to use the `id_token` directly as the source of user information, providing more flexibility in OIDC-based authentication workflows.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Improved handling of JVM properties related to ROR settings.</summary>

The way ReadonlyREST processes and applies JVM property-based configuration settings has been refined, resulting in more reliable behavior and better error handling when custom JVM options are used.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed OIDC logout redirection issue by switching <code>redirect_uri</code> to <code>id_token_hint</code> and using <code>post_logout_redirect_uri</code>.</summary>

The OIDC logout flow has been corrected to use the standard `id_token_hint` parameter instead of `redirect_uri`, along with proper `post_logout_redirect_uri` handling, ensuring users are correctly redirected after logout.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) The ReadonlyREST Kibana plugin now accepts custom appender names defined in <code>kibana.yml</code>.</summary>

Previously, the plugin would reject custom logging appender names configured in Kibana's logging configuration. This fix ensures compatibility with custom appender setups.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) When "Remember Group After Logout" is enabled, groups without access are correctly ignored during login.</summary>

Fixed a bug where previously remembered groups that no longer had access permissions could still be applied during re-authentication. Now only groups with valid access are considered.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed issue where the Kibana index template was not applied for Kibana versions ≥ 8.8.0.</summary>

A compatibility issue with Kibana 8.8.0 and newer prevented the ROR index template from being properly applied. This fix restores correct template application for these versions.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved a bug with <code>readonlyrest_kbn.resetKibanaIndexToTemplate: true</code> for Kibana 7.x.</summary>

The index reset functionality, which restores the Kibana index to match the expected template, was not working correctly on Kibana 7.x. This fix ensures the setting behaves as intended.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an issue where a custom session index name was not respected after Kibana restart.</summary>

When a custom session index name was configured, Kibana would revert to the default session index after a restart. This fix ensures the custom session index name is persisted and used correctly across restarts.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Fixed an issue preventing snapshots from being restored when no indices were specified.</summary>

Restoring a snapshot without explicitly specifying indices (i.e., restoring all indices) was failing under certain conditions. This fix ensures that snapshot restore operations work correctly even when no index list is provided.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) File ownership and permissions are now preserved during <code>ror-tools</code> patch and unpatch operations.</summary>

Previously, running `ror-tools` to patch or unpatch Elasticsearch could alter file ownership and permissions. This fix ensures that the original file attributes are maintained throughout the patching process.

</details>

### (2025-05-17) What's new in **ROR 1.64.2**

<details>

<summary><strong>🚀New</strong> (KBN) 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, 7.17.29 support</summary>

ReadonlyREST now supports the latest Kibana versions including 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, and 7.17.29. This ensures compatibility with recent Kibana releases and their security updates.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, 7.17.29 support</summary>

ReadonlyREST now supports the latest Elasticsearch versions including 9.0.3, 9.0.2, 8.18.3, 8.18.2, 8.17.8, 8.17.7, and 7.17.29. This provides compatibility with recent Elasticsearch releases and their security patches.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/ror-1-64-0-for-es9-0-1-windows-setup/2778">Fixed an issue with Elasticsearch patching process on Windows operating systems</a></summary>

Resolved a Windows-specific error that occurred during the Elasticsearch patching process with ReadonlyREST. The issue was successfully reproduced by the support team and fixed to ensure smooth installation on Windows environments.

</details>

### (2025-05-13) What's new in **ROR 1.64.1**

<details>

<summary><strong>🐞Fix</strong> (ES) Correct patching verification in ROR Docker image entrypoint</summary>

This fix addresses an issue in the Docker image entrypoint script where patching verification was not functioning correctly. The entrypoint script, which handles the application of security patches and configuration updates, now properly validates that patches are applied successfully before proceeding with container startup, ensuring reliable deployment of ROR-secured Elasticsearch instances.

</details>

### (2025-05-11) What's new in **ROR 1.64.0**

<details>

<summary><strong>🚨Security Fix</strong> (KBN) <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-53382">CVE-2024-53382</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-27789">CVE-2025-27789</a>, <a href="https://www.cve.org/CVERecord?id=CVE-2025-29774">CVE-2025-29774</a></summary>

This release addresses three security vulnerabilities in Kibana dependencies: CVE-2024-53382 is a DOM clobbering XSS vulnerability in PrismJS syntax highlighter (versions ≤1.29.0), CVE-2025-27789 is a performance/DoS issue in Babel's regex polyfill with quadratic complexity, and CVE-2025-29774 (details not fully available). These fixes prevent potential cross-site scripting attacks and denial of service scenarios.

</details>

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-3894">CVE-2023-3894</a>, <a href="https://nvd.nist.gov/vuln/detail/CVE-2025-25193">CVE-2025-25193</a></summary>

This release patches two Elasticsearch-related vulnerabilities: CVE-2023-3894 is a Denial of Service vulnerability in jackson-dataformats-text library (versions <2.15.0) that could cause stack overflow when parsing malicious TOML data, and CVE-2025-25193 is a Windows-specific DoS vulnerability in Netty (versions ≤4.1.118.Final) where large environment files could crash the application. These fixes enhance system stability and security.

</details>

<details>

<summary><strong>⚠️Warning</strong> (ES) Acknowledgement needs to be accepted before the Elasticsearch patching process. For scripts, you can <a href="https://docs.readonlyrest.com/elasticsearch#id-3.-patch-elasticsearch">set the flag</a> to automate the process.</summary>

When patching Elasticsearch for ReadonlyREST installation, users must now explicitly acknowledge the patching process. For automated deployments, administrators can set a configuration flag to bypass the manual acknowledgement, enabling script-based automation of the patching workflow.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Added an endpoint to retrieve all user tenancies via the ReadonlyREST API. See the <a href="https://portal.readonlyrest.com/docs/swagger/master#/User&#x27;s%20tenants/get_api_ror_user_tenants">ReadonlyREST API Documentation</a> for usage details.</summary>

A new API endpoint has been added to retrieve all tenancies associated with a user. This enables programmatic access to multi-tenancy information, allowing administrators and applications to query and manage user tenancy assignments through the ReadonlyREST API interface.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Introduced support for passing <code>x-ror-tenancy-id</code> in direct Kibana requests. See the <a href="https://portal.readonlyrest.com/docs/swagger/master#/Example%20ReadonlyREST%20headers%20usage%20with%20Kibana%20API/get_api__">ReadonlyREST API Documentation</a> for details.</summary>

Direct Kibana API requests can now include the `x-ror-tenancy-id` header to specify the target tenancy context. This allows applications and scripts to make requests within specific tenancy contexts without relying on session-based tenancy selection, improving automation and integration capabilities.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) Introduced support for passing <code>x-ror-impersonating</code> in direct Kibana requests. See the <a href="https://portal.readonlyrest.com/docs/swagger/master#/Example%20ReadonlyREST%20headers%20usage%20with%20Kibana%20API/get_api__">ReadonlyREST API Documentation</a> for details.</summary>

The new `x-ror-impersonating` header enables administrators to make Kibana API requests on behalf of other users. This feature supports administrative workflows where privileged users need to perform actions or troubleshoot issues within another user's security context while maintaining audit trails.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Retains the currently selected group information after user logout. This setting is user-configurable and disabled by default.</summary>

Kibana now optionally preserves the user's selected group/tenancy information across logout/login cycles. This user-preference setting (disabled by default) improves user experience by maintaining context between sessions, reducing the need to reselect groups upon each login.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Displays <a href="https://docs.readonlyrest.com/elasticsearch#unauthorized-response-configuration">detailed "reason" messages from the ROR Elasticsearch</a> response in the login form instead of a generic "Wrong credentials" message.</summary>

Login failures now show specific error messages from Elasticsearch's ReadonlyREST plugin rather than generic "Wrong credentials" messages. This provides users with actionable feedback about authentication issues, such as account lockouts, expired credentials, or specific authorization failures.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Added support for passing additional <a href="https://docs.readonlyrest.com/kibana#additional-parameters">SAML</a> and <a href="https://docs.readonlyrest.com/kibana#additional-parameters">OIDC</a> config parameters via <code>kibana.yml</code>.</summary>

Extended configuration options for SAML and OIDC authentication providers can now be specified directly in kibana.yml. This allows administrators to customize authentication flows with provider-specific parameters without modifying plugin code, enhancing integration flexibility with enterprise identity systems.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Adjusted ReadonlyREST plugin UI styles for compatibility with Kibana 9.x.</summary>

The ReadonlyREST plugin interface has been updated with CSS and styling adjustments to ensure proper display and functionality within Kibana 9.x environments. This maintains visual consistency and usability as Kibana evolves its user interface framework.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Username duplication check in the "users" section of ROR ES settings can <a href="https://docs.readonlyrest.com/elasticsearch#users_section_duplicate_usernames_detection">be optionally disabled</a>.</summary>

Administrators can now optionally disable the duplicate username validation in Elasticsearch settings. This provides flexibility for complex deployment scenarios where username duplication might be intentional or managed through external systems, while maintaining the default validation for security.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Added support for <a href="https://docs.readonlyrest.com/elasticsearch#global-settings"><code>readonlyrest.global_settings</code></a> in Elasticsearch ROR settings.</summary>

Elasticsearch configuration now supports `readonlyrest.global_settings` for centralized management of plugin-wide parameters. This enables consistent configuration across clusters and simplifies administration by separating global settings from rule-specific configurations.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Resolved an unhandled error when <code>logging.root.level</code> is set to <code>all</code> in <code>kibana.yml</code>.</summary>

Fixed a crash that occurred when Kibana's logging.root.level was configured as "all" in kibana.yml. The plugin now properly handles this logging configuration, preventing startup failures and ensuring compatibility with verbose logging settings for debugging purposes.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an issue with retrieving username and group information in AFDS OIDC.</summary>

Corrected a bug where Azure AD Federated Services (AFDS) OIDC authentication failed to properly extract username and group information from identity tokens. This fix ensures proper user identification and group-based authorization for Azure AD-integrated deployments.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Fixed an issue with passing <code>x-ror-correlation-id</code> to the ReadonlyREST API request.</summary>

Resolved a problem where the `x-ror-correlation-id` header was not being properly passed through to ReadonlyREST API requests. This fix ensures correlation IDs are correctly transmitted for request tracing, debugging, and audit logging across the authentication and authorization pipeline.

</details>

### (2025-03-12) What's new in **ROR 1.63.0**

&#x20;       **🚨Security Fix** (KBN) [CVE-2025-26791](https://www.cve.org/CVERecord?id=CVE-2025-26791), [CWE-772](https://cwe.mitre.org/data/definitions/772.html)

&#x20;       **🚨Security Fix** (ES) [CVE-2024-57699](https://nvd.nist.gov/vuln/detail/CVE-2024-53990) [CVE-2025-25193](https://nvd.nist.gov/vuln/detail/CVE-2025-25193) [CVE-2025-24970](https://nvd.nist.gov/vuln/detail/CVE-2025-24970)

&#x20;       **🚀New** (KBN) 9.0.1, 9.0.0, 9.0.0-rc1, 9.0.0-beta1, 8.18.1, 8.18.0, 8.17.6, 8.17.5, 8.17.4, 8.16.6 support

&#x20;       **🚀New** (ES) 9.0.1, 9.0.0, 9.0.0-rc1, 9.0.0-beta1, 8.18.1, 8.18.0, 8.17.6, 8.17.5, 8.17.4, 8.16.6 support

&#x20;       **🚀New** (ES) [Added `groups_not_any_of` and `groups_not_all_of` rules](https://forum.readonlyrest.com/t/support-kbn-ent-managing-forbidden-messages/2623)

&#x20;       **🚀New** (ES) [New unified and simplified syntax for groups rules](https://docs.readonlyrest.com/elasticsearch#groups-rules)

&#x20;       **🧐Enhancement** (KBN) For Kibana >= 8.14.0: Added backward compatibility to hide the Dashboard app by declaring Analytics|Dashboard and Analytics|Dashboards in the `kibana.hide_apps` rule

&#x20;       **🧐Enhancement** (KBN) Added information about skipping patching confirmation prompt to the patching helper

&#x20;       **🧐Enhancement** (KBN) \[When Kibana is opened in multiple browser tabs, logging into Kibana in one tab automatically logs in all browser tabs]

&#x20;       **🐞Fix** (KBN) Don't terminate Kibana when disk reaches low watermark

&#x20;       **🐞Fix** (KBN) For Kibana >= 8.15.0: Added support for reporting data stream multitenancy

&#x20;       **🐞Fix** (KBN) Silenced "Error fetching fields for index pattern" toast messages due to forbidden response in Kibana Dashboard and Discover page

&#x20;       **🐞Fix** (KBN) For Kibana >= 8.17.0: Fixed Elasticsearch navigation header being visible when `kibana.hide_apps: [ "Elasticsearch" ]`

&#x20;       **🐞Fix** (KBN) [For Kibana >= 8.5.0: Fixed Dev tools play buttons not being visible for RO users](https://forum.readonlyrest.com/t/ldap-multitenancy-with-no-group-name-to-index-name-relation/2742/8)

&#x20;       **🐞Fix** (KBN) Fixed an issue with hiding the dashboard app when using regular expressions in the kibana\_hide\_apps field

&#x20;       **🐞Fix** (ES) Fixed various issues with restoring snapshot API

&#x20;       **🐞Fix** (ES) Fixed data streams, index, and component templates being forbidden for RW users in stack management

### (2025-01-24) What's new in **ROR 1.62.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2024-53990](https://nvd.nist.gov/vuln/detail/CVE-2024-53990)

&#x20;       **🚨Security Fix** (KBN) [CVE-2024-21538](https://www.cve.org/CVERecord?id=CVE-2024-21538), [CVE-2024-47764](https://www.cve.org/CVERecord?id=CVE-2024-47764), [CVE-2024-52798](https://www.cve.org/CVERecord?id=CVE-2024-52798)

&#x20;       **⚠️Warning** (KBN) Updated [`readonlyrest_kbn: license: activationKeyRefreshInterval`](https://forum.readonlyrest.com/t/restricting-access-to-some-spaces/2633/4) - the maximum refresh interval is now set to 1 day.

&#x20;       **🚀New** (ES|KBN) Introduced support for [Elastic APM (Application Performance Monitoring)](https://www.elastic.co/observability/application-performance-monitoring).

&#x20;       **🚀New** (KBN) 8.17.3, 8.17.2, 8.17.1, 8.16.5, 8.16.4, 8.16.3, 7.17.28 support

&#x20;       **🚀New** (ES) 8.17.3, 8.17.2, 8.17.1, 8.16.5, 8.16.4, 8.16.3, 7.17.28 support

&#x20;       **🚀New** (KBN) Added [Kibana images with the preinstalled ReadonlyREST plugin for the arm64 platform](https://hub.docker.com/r/beshultd/kibana-readonlyrest) on Docker Hub.

&#x20;       **🚀New** (ES) Added [Elasticsearch images with the preinstalled ReadonlyREST plugin for the arm64 platform](https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest) on Docker Hub.

&#x20;       **🧐Enhancement** (ES) [Introduced validation to prevent multiple username entries in the users section.](https://forum.readonlyrest.com/t/ror-1-57-3-es-8-13-2-double-usernames-allowed/2621/2)

&#x20;       **🐞Fix** (KBN) [Resolved an issue with exit patching-based commands.](https://forum.readonlyrest.com/t/restricting-access-to-some-spaces/2633/6)

&#x20;       **🐞Fix** (KBN) Addressed a bug in Kibana 8.16.0 and later versions to hide the permissions tab in a space.

&#x20;       **🐞Fix** (KBN) Fixed a compatibility issue where OIDC and SAML didn't work in Kibana versions earlier than 7.11.0.

&#x20;       **🐞Fix** (KBN) Ensured user settings are overridden only for the default space.

&#x20;       **🐞Fix** (ES) Relaxed restrictions on snapshot restoration during index checks.

&#x20;       **🐞Fix** (ES) Resolved issue with Stack Monitoring access when `xpack.security.enabled: true` is configured.

### (2024-11-20) What's new in **ROR 1.61.1**

&#x20;       **🚨Security Fix** (ES) [Data leak through the ESQL API](https://forum.readonlyrest.com/t/eql-requests-returns-data-even-though-they-aren-t-allowed/2679) (for ES >= 8.11.0)

&#x20;       **🚨Security Fix** (KBN) [CVE-2024-21538](https://www.cve.org/CVERecord?id=CVE-2024-21538), [CVE-2024-47764](https://www.cve.org/CVERecord?id=CVE-2024-47764)

&#x20;       **🚨Security Fix** (ES) [CVE-2024-47535](https://nvd.nist.gov/vuln/detail/CVE-2024-47535)

&#x20;       **🚀New** (KBN) 8.17.0, 8.16.2, 8.16.1, 8.16.0, 8.15.5, 7.17.27, 7.17.26 support

&#x20;       **🚀New** (ES) 8.17.0, 8.16.2, 8.16.1, 8.15.5, 7.17.27, 7.17.26 support

&#x20;       **🚀New** (ES) ESQL support

&#x20;       **🐞Fix** (KBN) Elasticsearch red status shouldn't kill the Kibana process on initialization

### (2024-11-12) What's new in **ROR 1.61.0**

&#x20;       **🚨Security Fix** (KBN) [CVE-2024-47764](https://www.cve.org/CVERecord?id=CVE-2024-47764)

&#x20;       **⚠️Warning** (KBN) Acknowledgement needs to be accepted before a Kibana patching process. For scripts, you can [set a flag](https://docs.readonlyrest.com/kibana#patching-kibana) to automate a process (edited)

&#x20;       **🚀New** (KBN) 8.15.4 support

&#x20;       **🚀New** (ES) 8.16.0, 8.15.4 support

&#x20;       **🚀New** (ES) There is an option to define [a custom response for users in ACL block with the 'forbid' policy](https://docs.readonlyrest.com/elasticsearch#unauthorized-response-configuration)

&#x20;       **🧐Enhancement** (KBN) Set-Cookie is not returned with KBN API response

&#x20;       **🧐Enhancement** (KBN) Reduce the amount of ReadonlyREST session updates

&#x20;       **🧐Enhancement** (KBN) Kibana plugin won't start until the connection with Elasticsearch is established

&#x20;       **🧐Enhancement** (KBN) API and activation key tabs in the Security settings are visible only for the admin or unrestricted access users

&#x20;       **🧐Enhancement** (KBN) detecting issues related to high disk watermark warning

&#x20;       **🧐Enhancement** (KBN) License expiration info only for admin and unrestricted access users

&#x20;       **🧐Enhancement** (ES) index exclusion (dash) syntax support

&#x20;       **🐞Fix** (KBN) Don't stop Kibana when correlationId is not available in the session

&#x20;       **🐞Fix** (KBN) Provide additional [SAML configuration options](https://docs.readonlyrest.com/kibana#usage-with-active-directory-federation-services) to handle Active Directory Federation Services (ADFS) properly

&#x20;       **🐞Fix** (KBN) login page customization should be a PRO feature instead of an Enterprise

&#x20;       **🐞Fix** (KBN) Logging to file doesn't work for Kibana 8.x

&#x20;       **🐞Fix** (ES) Snapshot Status API - forbidden response while checking the status of all snapshots of the given repository

&#x20;       **🐞Fix** (ES) Snapshot API - misc issues for ES 6.x

### (2024-09-15) What's new in **ROR 1.60.0**

&#x20;       **🚀New** (KBN) 8.15.3, 8.15.2, 7.17.25 support

&#x20;       **🚀New** (ES) 8.15.3, 8.15.2, 7.17.25 support

&#x20;       **🚀New** (KBN|ES) [ECK support documentation](https://docs.readonlyrest.com/eck)

&#x20;       **🚀New** (ES) configurable ROR YAML settings max size

&#x20;       **⚠️Warning** (ES) The prompt for basic authorization is disabled by default. To keep the previous behavior, set `readonlyrest.prompt_for_basic_auth` to `true` in the ROR configuration

&#x20;       **🧐Enhancement** (KBN) There is an option to define [client authentication methods](https://docs.readonlyrest.com/kibana#client-authentication-methods) in the `kibana.yml` via `readonlyrest_kbn.auth.<YOUR_OIDC_CONFIG>.tokenEndpointAuthMethod`, 'client\_secret\_post' or ''client\_secret\_basic'

&#x20;       **🧐Enhancement** (KBN) Stop Kibana when enabled features are not available

&#x20;       **🐞Fix** (KBN) HTTP 400 (bad request) issue when there is a Nginx proxy server between es and Kibana

&#x20;       **🐞Fix** (KBN) Fix for the problem with correctly hiding Management features `ROR Manage Kibana` defined in the readonlyrest.yml `kibana_hide_apps` property

&#x20;       **🐞Fix** (ES) ROR KBN docker image: passing ROR settings as ENVs fixes

&#x20;       **🐞Fix** (ES) [Data stream backing indices access issue with the indices rule](https://forum.readonlyrest.com/t/requested-index-doesnt-exist/2573)

&#x20;       **🐞Fix** (ES) [Fix for the problem with remote access to data stream aliases](https://forum.readonlyrest.com/t/requested-index-doesnt-exist/2573)

### (2024-08-01) What's new in **ROR 1.59.0**

&#x20;       **🚀New** (ES) 8.15.1, 8.15.0, 7.17.24, 7.17.23, 6.7.x support

&#x20;       **🚀New** (KBN) 8.15.1, 8.15.0, 7.17.24, 7.17.23 support

&#x20;       **🧐Enhancement** (KBN) Replace a broken Alert and Connectors applications with the link to our [new tool](https://anaphora.it) for Reports and alerting for Kibana > 8.6.0 (edited)

&#x20;       **🐞Fix** (KBN) Handling reporting URL for report generation

&#x20;       **🐞Fix** (KBN) Embedding with inline JWT is a feature available only in ReadonlyREST PRO and Enterprise

&#x20;       **🐞Fix** (ES) [Patcher `UnsupportedOperationException` issue on Windows](https://forum.readonlyrest.com/t/ror-1-58-0-for-es8-14-3-windows-setup/2577)

&#x20;       **🐞Fix** (ES) for the problem with `_async_search` on ES 8.14.x

### (2024-06-30) What's new in **ROR 1.58.0**

&#x20;       **🚨Security Fix** (KBN) [CVE-2022-39353](https://www.cve.org/CVERecord?id=CVE-2022-39353), [CVE-2020-7753](https://www.cve.org/CVERecord?id=CVE-2020-7753), [CVE-2022-37616](https://www.cve.org/CVERecord?id=CVE-2022-37616), [CVE-2024-29041](https://www.cve.org/CVERecord?id=CVE-2024-29041), [CVE-2022-0691](https://www.cve.org/CVERecord?id=CVE-2022-0691), [CVE-2021-3801](https://www.cve.org/CVERecord?id=CVE-2021-3801), [CVE-2022-25883](https://www.cve.org/CVERecord?id=CVE-2022-25883), [CVE-2022-0512](https://www.cve.org/CVERecord?id=CVE-2022-0512), [CVE-2022-0686](https://www.cve.org/CVERecord?id=CVE-2022-0686), [CVE-2022-0639](https://www.cve.org/CVERecord?id=CVE-2022-0639), [CVE-2022-25881](https://www.cve.org/CVERecord?id=CVE-2022-25881), [CVE-2023-0842](https://www.cve.org/CVERecord?id=CVE-2023-0842), [CVE-2017-16137](https://www.cve.org/CVERecord?id=CVE-2017-16137), [CVE-2022-33987](https://www.cve.org/CVERecord?id=CVE-2022-33987), [CVE-2022-23647](https://www.cve.org/CVERecord?id=CVE-2022-23647), [CVE-2022-36083](https://www.cve.org/CVERecord?id=CVE-2022-36083), [CVE-2024-28176](https://www.cve.org/CVERecord?id=CVE-2024-28176)

&#x20;       **🚀New** (KBN) [Kibana images with preinstalled ReadonlyREST plugin in Docker Hub](https://hub.docker.com/r/beshultd/kibana-readonlyrest)

&#x20;       **🚀New** (KBN) 8.14.3, 8.14.2 support

&#x20;       **🚀New** (ES) 8.14.3, 8.14.2 support

&#x20;       **🚀New** (ES) ["structured groups" feature](https://github.com/beshu-tech/readonlyrest-docs/blob/develop/details/structured-groups.md) (authorization rules group names and group IDs can be defined separately)

&#x20;       **🧐Enhancement** (KBN) New `readonlyrest_kbn.cookies.secure` and `readonlyrest_kbn.cookies.sameSite` cookie settings via kibana.yml

&#x20;       **🧐Enhancement** (ES) improved error logging on the creation of LDAP connectors

&#x20;       **🧐Enhancement** (ES) Patcher - invalid state after patching detection improvements

&#x20;       **🐞Fix** (KBN) Impersonation and session probe logout issue

&#x20;       **🐞Fix** (KBN) [Problem with the number of replicas and index template, where the number of replicas was always set to 1. Now, the default value will be the same, as in the case of the Kibana index](https://forum.readonlyrest.com/t/0-replicas-for-single-node-clusters/2530)

&#x20;       **🐞Fix** (KBN) Fix problem with multi-tenancy features when xpack.security.enabled: true

### (2024-05-18) What's new in **ROR 1.57.3**

&#x20;       **🚨Security Fix** (ES) [CVE-2024-34447](https://nvd.nist.gov/vuln/detail/CVE-2024-34447)

&#x20;       **🚀New** (KBN) 8.14.1, 8.14.0, 7.17.22 support

&#x20;       **🚀New** (ES) 8.14.1, 8.14.0, 7.17.22 support

&#x20;       **🐞Fix** (KBN) The CSRF cookie name issue that caused the "Wrong credentials" error during login

&#x20;       **🐞Fix** (KBN) Automatic migration issue for Kibana >= 8.8.0 that caused the "mapping set to strict, dynamic introduction of... error

### (2024-05-05) What's new in **ROR 1.57.2**

&#x20;       **🚀New** (KBN) 8.13.4, 8.13.3, 7.17.21 support

&#x20;       **🚀New** (ES) 8.13.4, 8.13.3, 7.17.21 support

&#x20;       **🐞Fix** (KBN) Kibana <= 7.2.1 doesn't run

&#x20;       **🐞Fix** (KBN) Provides a way to migrate an existing session index to the new session

&#x20;       **🐞Fix** (ES) [Patching issue for Elasticsearch installed from packages](https://forum.readonlyrest.com/t/bootstrap-error-es/2574)

&#x20;       **🐞Fix** (ES) Patching issue for Elasticsearch OSS versions

### (2024-04-29) What's new in **ROR 1.57.1**

&#x20;       **🐞Fix** (ES) configuration parsing regression: one group definition can be a string

### (2024-04-28) What's new in **ROR 1.57.0**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2024-29025">CVE-2024-29025</a></summary>

This vulnerability affects the Netty `HttpPostRequestDecoder`, which could be exploited to accumulate unlimited data from chunked POST requests containing many small fields, potentially leading to a denial of service. The fix addresses this by properly limiting the accumulated data in the decoder.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch#configuration-notes">LDAP Connector</a> feature: groups server-side filtering</summary>

The LDAP connector now supports filtering groups directly on the LDAP server side, reducing the amount of data transferred and improving performance when dealing with large directories.

</details>

<details>

<summary><strong>🚀New</strong> (ES) <a href="https://docs.readonlyrest.com/elasticsearch#configuration-notes">LDAP Connector</a> feature: skip user search option when user attribute is <code>cn</code></summary>

A new configuration option allows skipping the user search phase when the user attribute is set to `cn` (Common Name), streamlining authentication for LDAP setups where the bind DN directly matches the user's CN.

</details>

<details>

<summary><strong>⚠️Warning</strong> (KBN|ES) Internal API incompatibilities (to take advantage of rolling update capabilities, upgrade ROR ES first)</summary>

Due to internal API changes, it is recommended to upgrade the ROR Elasticsearch plugin first before upgrading the Kibana plugin. This order allows you to leverage rolling update capabilities and minimize downtime during the upgrade process.

</details>

<details>

<summary><strong>⚠️Warning</strong> (ES) Support for ES &#x3C; 6.8.0 was dropped</summary>

This release no longer supports Elasticsearch versions older than 6.8.0. Users running older versions should plan an upgrade to a supported Elasticsearch version before updating ROR.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) User settings available for all access type users</summary>

User settings in the Kibana plugin are now accessible to all user access types, not just administrators, giving end users more control over their experience.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Add option to change the Default Route and Time zone in User settings</summary>

Users can now configure their default landing page (route) and time zone directly from the user settings interface in Kibana, improving personalization.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Provide correlation ID to Kibana logs</summary>

A correlation ID is now included in Kibana logs, making it easier to trace requests across the system and troubleshoot issues by correlating log entries.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Rich, context-based debug logging in the LDAP connector and LDAP-related rules</summary>

Debug logging has been significantly improved for the LDAP connector and related authentication/authorization rules, providing more detailed and contextual information to help administrators diagnose LDAP integration issues.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) Additional <a href="https://docs.readonlyrest.com/elasticsearch#configuring-an-acl-with-filter-fields-rules-when-using-kibana">validations</a>: <code>kibana</code> rule should not be used with some other rules in the same block</summary>

New configuration validations have been added to warn when the `kibana` rule is combined with incompatible rules (such as `fields` or `filter`) in the same ACL block, helping prevent misconfigurations that could lead to unexpected behavior.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Sometimes reports are not generated correctly for Kibana &#x3C; 8.0.0 and the "Max attempt reached" error appears</summary>

A bug causing report generation failures in Kibana versions prior to 8.0.0 has been fixed. The issue previously resulted in a "Max attempt reached" error, preventing successful report creation.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Adjust interactive API swagger dark mode colors</summary>

The color scheme for the interactive Swagger API documentation in dark mode has been adjusted to improve readability and visual consistency.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) CSRF problem when multiple ECK Kibana instances</summary>

A Cross-Site Request Forgery (CSRF) issue that occurred when running multiple Kibana instances managed by ECK (Elastic Cloud on Kubernetes) has been resolved, ensuring secure communication between instances.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Plugin doesn't run for a version Kibana &#x3C; 7.11.0 when the OIDC proxy is enabled</summary>

A compatibility issue has been fixed where the ROR Kibana plugin would fail to start on Kibana versions older than 7.11.0 when an OIDC proxy was enabled.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Session probe should log out the user when empty metadata was returned from ES ROR</summary>

The session probe now properly logs out the user when the Elasticsearch ROR plugin returns empty metadata, preventing stale or invalid sessions from persisting.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Misc issues when <code>xpack.security.enabled: true</code> is set</summary>

Various miscellaneous issues that occurred when X-Pack security was enabled alongside ReadonlyREST have been resolved, improving interoperability between the two security layers.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Patched files permission issue</summary>

A file permission issue affecting patched Elasticsearch files has been fixed, ensuring that the patching process correctly sets the appropriate permissions for all modified files.

</details>

### (2024-03-15) What's new in **ROR 1.56.0**

&#x20;       **🚀New** (KBN) Provide a way to switch light/dark mode per user

&#x20;       **🚀New** (KBN) 8.13.2, 8.13.1, 8.13.0, 7.17.20, 7.17.19 support

&#x20;       **🚀New** (ES) 8.13.2, 8.13.1, 8.13.0, 7.17.20, 7.17.19 support

&#x20;       **⚠️Warning** (ES) [for ES > 6.5 patching is required since this version of ROR](https://docs.readonlyrest.com/elasticsearch#id-5.-patch-elasticsearch)

&#x20;       **🧐Enhancement** (KBN) The activation key will be revalidated in the interval

&#x20;       **🧐Enhancement** (KBN) Provide a way to define Activation key [retrieval mode](https://docs.readonlyrest.com/v/develop/universal-builds#change-activation-key-retrieval-mode-via-kibana.yml)

&#x20;       **🐞Fix** (KBN) Sometimes reports are not generated correctly for Kibana >= 8.0.0 and "Max attempt reached" error appears

&#x20;       **🐞Fix** (KBN) The OIDC scope configuration property was not applied and the default configuration was used instead.

&#x20;       **🐞Fix** (KBN) The OIDC proxy parameter was not handled properly in case of HTTPs connection over HTTP proxy server

&#x20;       **🐞Fix** (KBN) Missing information when Kibana is not patched

&#x20;       **🐞Fix** (ES) [Repositories and Snapshots handling by ES coordinating nodes](https://forum.readonlyrest.com/t/snapshot-status-cannot-modify-incoming-request/2471)

&#x20;       **🐞Fix** (ES) [Internode SSL `certificate_verification: true` was causing problems with nodes discovery](https://forum.readonlyrest.com/t/upgrade-elasticsearch-8-2-to-8-x-leads-to-ssl-problems/2480)

&#x20;       **🐞Fix** (ES) Missing `x-elastic-product` header in the response when `fields` and `filter` rules were used

&#x20;       **🐞Fix** (ES) Proper `forbid` policy handling during processing ROR login request

&#x20;       **🐞Fix** (ES) `application/nd-json` media type handling (in case of ES `7.x` versions)

### (2024-01-29) What's new in **ROR 1.55.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-51074](https://nvd.nist.gov/vuln/detail/CVE-2023-51074)

&#x20;       **🚀New** (KBN) 8.12.2 ,8.12.1, 7.17.18, 7.17.17 support

&#x20;       **🚀New** (ES) 8.12.2, 8.12.1, 7.17.18 support

&#x20;       **🚀New** (ES) [Elasticsearch images with preinstalled ReadonlyREST plugin in Docker Hub](https://hub.docker.com/r/beshultd/elasticsearch-readonlyrest)

&#x20;       **🧐Enhancement** (KBN) Optional `readonlyrest_kbn.auth.oidc_kc.proxyURL` kibana.yml configuration for the OIDC connection which allows declaring your proxy URL

&#x20;       **🧐Enhancement** (KBN) Upon successful activation and edition changes all sessions are cleared and users are logged out

&#x20;       **🐞Fix** (KBN) Saved objects are not visible for the users on Kibana >= 8.8.0

&#x20;       **🐞Fix** (ES) [LDAP nested group IDs are properly escaped](https://forum.readonlyrest.com/t/support-kbn-ent-ldap-and-parentheses/2466)

&#x20;       **🐞Fix** (ES) Logout when a user with restricted `kibana.access` tried to see a restoration status of snapshots in Kibana

### (2023-12-17) What's new in **ROR 1.54.0**

&#x20;       **🚨Security Fix** (ES) [Scroll API: protected data could leak when the `fields` rule was used with `fls_engine` set to `es` or `es_with_lucene`](https://forum.readonlyrest.com/t/field-rule-not-working-when-exceeding-a-certain-no-of-docs/2415/7)

&#x20;       **🚀New** (KBN) 8.12.0, 8.11.4 support

&#x20;       **🚀New** (ES) 8.12.0, 8.11.4, 7.17.17 support

&#x20;       **🧐Enhancement** (KBN) Provide automatic [cleaning of stale sessions](https://docs.readonlyrest.com/kibana#automatic-session-cleanup)

&#x20;       **🧐Enhancement** (KBN) Provide automatic cleaning of stale CSRF cookies

&#x20;       **🐞Fix** (KBN) Adjust the ROR API POST license endpoint body to the contract to respect the `license` body parameter instead of a `token`

&#x20;       **🐞Fix** (KBN) \`CorelationId\`\` is changed on every session refresh

&#x20;       **🐞Fix** (ES) ["missing authorization info" problem in some situations when `xpack.security.enabled` was configured to be `true`](https://forum.readonlyrest.com/t/diana-eck/2298/75)

### (2023-11-20) What's new in **ROR 1.53.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-4586](https://nvd.nist.gov/vuln/detail/CVE-2023-4586), [CVE-2023-5072](https://nvd.nist.gov/vuln/detail/CVE-2023-5072)

&#x20;       **🚀New** (KBN) 8.11.3, 8.11.2, 8.11.1, 8.11.0, 7.17.16 support

&#x20;       **🚀New** (ES) 8.11.3, 8.11.2, 8.11.1, 8.11.0, 7.17.16 support

&#x20;       **🧐Enhancement** (KBN) Provide Activate license endpoint to the ReadonlyREST API

&#x20;       **🧐Enhancement** (ES) [when the `kibana` rule and the `indices` rule are defined in the same block](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#index), there is no need to explicitly allow kibana-related indices

&#x20;       **🐞Fix** (KBN) problem with reports generation when `kibana.index` in kibana.yml is used

&#x20;       **🐞Fix** (KBN) crash loop during license service initialization

&#x20;       **🐞Fix** (KBN) problem with logging in in KBN 7.17.13 (and above) and 8.10.4 (and above) when deployed using ECK

&#x20;       **🐞Fix** (KBN) [problem with multi-tenancy and ECK](https://forum.readonlyrest.com/t/multi-tanancy-issue/2427)

&#x20;       **🐞Fix** (KBN) problem with forbidden `/_create/config` response on Login to the Kibana

&#x20;       **🐞Fix** (ES) [patching fix, when a non-default ES path is used (e.g. on K8s)](https://forum.readonlyrest.com/t/getting-java-lang-illegalargumentexception-when-initializing-ror-in-es-8-10-4/2441)

### (2023-10-09) What's new in **ROR 1.52.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-4586](https://access.redhat.com/security/cve/cve-2023-4586)

&#x20;       **🚀New** (KBN) 8.10.4, 8.10.3, 7.17.15, 7.17.14 support

&#x20;       **🚀New** (ES) 8.10.4, 8.10.3, 7.17.15, 7.17.14 support

&#x20;       **🚀New** (ES) [New `token_authentication` rule](https://docs.readonlyrest.com/elasticsearch#token_authentication)

&#x20;       **🧐Enhancement** (KBN) Permanently hide Kibana|ES features that are impossible to support

&#x20;       **🧐Enhancement** (KBN) [License expiration reminder](https://forum.readonlyrest.com/t/license-expiration-reminder/2417)

&#x20;       **🧐Enhancement** (KBN) Make `kibana.index` setting from kibana.yml an invalid property for an Enterprise user

&#x20;       **🐞Fix** (KBN) Issue with not adding `elasticsearch.customHeaders` setting from kibana.yml to ROR requests

&#x20;       **🐞Fix** (KBN) Logout after opening Stack management Upgrading assistant

&#x20;       **🐞Fix** (KBN) Problem with logging in of two users in two tabs when two Kibana instances are used

&#x20;       **🐞Fix** (KBN) Problem with logging in when multi-tenancy is enabled and the `indices` rule is defined in the ROR settings

### (2023-09-25) What's new in **ROR 1.51.1**

&#x20;       **🚨Security Fix** (ES) [`fields` rule didn't work well in the case of ES 7.10.0 and later and more than 10 documents in the response](https://forum.readonlyrest.com/t/field-rule-not-working-when-exceeding-a-certain-no-of-docs/2415)

&#x20;       **🐞Fix** (KBN) issue with Observability Overview-based applications hiding

&#x20;       **🐞Fix** (KBN) Correct `kibana.index` handling for KBN >= 7.9.0 when multi-tenancy is disabled or unavailable

&#x20;       **🐞Fix** (KBN) Unrestricted Kibana Access on the tenancy switch when a selected tenant is not available anymore

&#x20;       **🐞Fix** (KBN) Unhandled error during login when `multiTenancyEnabled: false`

&#x20;       **🐞Fix** (ES) LDAP connectivity improvements

### (2023-09-10) What's new in **ROR 1.51.0**

&#x20;       **🚨Security Fix** (KBN) the issue with [api\_only](https://docs.readonlyrest.com/elasticsearch#kibana-related-rules) access level user and accessing via Kibana UI

&#x20;       **🚀New** (KBN) 8.10.2, 8.10.1, 8.9.2, 7.17.13 support

&#x20;       **🚀New** (ES) 8.10.2, 8.10.1, 8.10.0, 8.9.2, 7.17.13 support

&#x20;       **🚀New** (ES) [Dynamic variables transformation support](https://docs.readonlyrest.com/elasticsearch#variables-functions)

&#x20;       **🧐Enhancement** (KBN) Expose interactive Swagger as a new Security settings tab

&#x20;       **🧐Enhancement** (KBN) Provide detailed information about the invalid activation key

&#x20;       **🧐Enhancement** (ES) additional `hide_apps` validation in the `kibana` rule

&#x20;       **🐞Fix** (KBN) the issue with the persistence of an activation key provided via UI when `readonlyrest_kbn.cookiePass` was not provided. The [readonlyrest\_kbn.cookiePass](https://docs.readonlyrest.com/kibana#configuring-kibana) is required `kibana.yml` property

&#x20;       **🐞Fix** (KBN) issues for Kibana versions between 7.9.0 and 7.10.2, related to the activation key, Spaces, and readonlyREST menu crash

&#x20;       **🐞Fix** (KBN) The issue with a logout from Kibana when the link to the Kibana is open from a third-party application like `Gmail`

&#x20;       **🐞Fix** (ES) [getting data streams when not full names of backing indices are declared in the `indices` rule](https://forum.readonlyrest.com/t/forbidden-for-creating-component-templates/2372/7)

&#x20;       **🐞Fix** (ES) stack-management screen fix in case of `xpack.security.enabled: true`

### (2023-07-25) What's new in **ROR 1.50.0**

&#x20;       **🚀New** (KBN/ES) ECK support

&#x20;       **🚀New** (KBN) 8.9.1, 8.9.0, 7.17.12 support

&#x20;       **🚀New** (ES) 8.9.1, 8.9.0, 7.17.12 support

&#x20;       **🚀New** (KBN) Introduce the new ReadonlyREST API

&#x20;       **🧐Enhancement** (KBN) Remove application item info from URL on the tenant switch to avoid a 404 not found message

&#x20;       **🧐Enhancement** (KBN) Provide Reordering available tenancies for proxy auth authentication

&#x20;       **🧐Enhancement** (KBN) Provide information about granted/rejected log-in users to debug logs

### (2023-06-27) What's new in **ROR 1.49.1**

&#x20;       **🚨Security Fix** (ES) [CVE-2023-2976](https://nvd.nist.gov/vuln/detail/CVE-2023-2976)

&#x20;       **🚨Security Fix** (ES) [CVE-2023-34462](https://github.com/advisories/GHSA-6mjq-h674-j845)

&#x20;       **🚀New** (KBN) 8.8.2, 8.8.1, 8.8.0, 7.17.11 support

&#x20;       **🚀New** (ES) 8.8.2, 7.17.11 support

&#x20;       **🚀New** (ES) [LDAP nested groups support](https://docs.readonlyrest.com/elasticsearch#ldap-connector)

&#x20;       **🧐Enhancement** (KBN) [Allow setting default tenancy via `/login?defaultGroup` query param. To be used with "Custom Middleware" feature for reordering available tenancies in the ROR menu](https://docs.readonlyrest.com/examples/custom-middleware/reordering-available-tenancies)

&#x20;       **🐞Fix** (ES) [Fix for ES warnings in logs about custom action names (ROR internal actions)](https://forum.readonlyrest.com/t/invalid-action-name-cluster-ror-audit-event-put/2186)

&#x20;       **🐞Fix** (ES) [kibana access `rw` and `admin` should allow to manage component templates](https://forum.readonlyrest.com/t/forbidden-for-creating-component-templates/2372)

### (2023-05-28) What's new in **ROR 1.49.0**

&#x20;       **🚀New** (ES) 8.8.1 support

&#x20;       **🧐Enhancement** (KBN) Handle `elasticsearch.serviceAccountSupport` configuration property

&#x20;       **🧐Enhancement** (KBN) Provide a way to Hidden apps Stack management items hiding

&#x20;       **🧐Enhancement** (KBN) Provide an automated migration of tenancy indices on major Kibana version upgrade

&#x20;       **🧐Enhancement** (ES) external group ID patterns support in the external to local groups mapping

&#x20;       **🐞Fix** (KBN) the issue with the replica number being set to 0 on tenant index creation

&#x20;       **🐞Fix** (KBN) users won't log out from Kibana on the 500 status error

&#x20;       **🐞Fix** (KBN) the issue with Kibana keystore not being read by the Kibana plugin

&#x20;       **🐞Fix** (KBN < 7.9.0) logging issue when two Kibanas are handled by one browser at the same time

&#x20;       **🐞Fix** (ES) resolving ENVs to YAML number in ROR settings

### (2023-04-15) What's new in **ROR 1.48.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-45688](https://nvd.nist.gov/vuln/detail/CVE-2022-45688)

&#x20;       **🚀New** (KBN) 8.7.1, 7.17.10 support

&#x20;       **🚀New** (ES) 8.8.0, 8.7.1, 7.17.10 support

&#x20;       **🚀New** (KBN/ES) [Introducing "Custom Middleware" functionality](https://docs.readonlyrest.com/kibana#custom-middleware)

&#x20;       **🚀New** (KBN/ES) [`allowed_api_paths` support in the `kibana` ACL rule](https://docs.readonlyrest.com/elasticsearch#kibana-related-rules)

&#x20;       **🚀New** (KBN) Add CSRF protection in the login form

&#x20;       **🚀New** (KBN) Restore deprecated "kibana.index" support for Kibana > 8.x

&#x20;       **🚀New** (ES) [all Kibana-related rules are gathered in one, new `kibana` ACL rule](https://docs.readonlyrest.com/elasticsearch#kibana-related-rules)

&#x20;       **🚀New** (ES) [audit supports a new output type: `log`](https://docs.readonlyrest.com/elasticsearch/audit)

&#x20;       **🧐Enhancement** (KBN) Provide a way to disable multi-tenancy in ROR Enterprise

&#x20;       **🧐Enhancement** (KBN) Realign index templates behaviour to the old platform

&#x20;       **🧐Enhancement** (KBN) Error logs when SAML obtains an unusable username from the assertion

&#x20;       **🧐Enhancement** (KBN) Test configuration warnings improvement

&#x20;       **🧐Enhancement** (ES) [Added support to override default response code for not started ROR](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/issues/794)

&#x20;       **🐞Fix** (KBN) Security card not hidden by default

&#x20;       **🐞Fix** (KBN) Hidden apps regex with two "or" operators don't hide all kibana apps

&#x20;       **🐞Fix** (KBN) Fix Alerting Rules resulting in logout issue

&#x20;       **🐞Fix** (KBN) Fix audit dashboard

&#x20;       **🐞Fix** (KBN) Stop handling 500 error from `api/lens/existing_fields`

&#x20;       **🐞Fix** (KBN) Fix lens app

&#x20;       **🐞Fix** (KBN < 7.9.x) using a custom kibana index in cooperation with ROR Free

### (2023-02-13) What's new in **ROR 1.47.0**

&#x20;       **🚨Security Fix** (ES) "/" endpoint was not protected for ES 8.x

&#x20;       **🚨Security Fix** (ES) "/\_cat" endpoint was not protected for all ES versions

&#x20;       **🚀New** (KBN) 8.7.0, 8.6.2 support

&#x20;       **🚀New** (ES) 8.7.0, 8.6.2 support

&#x20;       **🚀New** (ES) [the `data_streams` rule](https://docs.readonlyrest.com/v/develop/elasticsearch#data_streams)

&#x20;       **🧐Enhancement** (KBN) optimisation in hidden apps feature

&#x20;       **🐞Fix** (KBN) Opening index management mappings tab forces logout

&#x20;       **🐞Fix** (KBN) Fix dark mode in the ROR menu

&#x20;       **🐞Fix** (KBN) YAML editor updates and fixes

&#x20;       **🐞Fix** (ES) Data streams support in the `indices` rule

&#x20;       **🐞Fix** (ES) NPE when `_search` with aggregations (script) and the `fields` rule were used together

### (2023-01-02) What's new in **ROR 1.46.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-1471](https://nvd.nist.gov/vuln/detail/CVE-2022-1471), [CVE-2022-41915](https://nvd.nist.gov/vuln/detail/CVE-2022-41915), [CVE-2022-36944](https://nvd.nist.gov/vuln/detail/CVE-2022-36944) in [audit Scala 2.13 jar](https://mvnrepository.com/artifact/tech.beshu.ror/audit)

&#x20;       **🚀New** (KBN) 8.6.1, 8.6.0, 7.17.9 support

&#x20;       **🚀New** (ES) 8.6.1, 8.6.0, 7.17.9 support

&#x20;       **🧐Enhancement** (KBN) Activation key management UI

&#x20;       **🧐Enhancement** (KBN) Less verbose logging in info mode

&#x20;       **🧐Enhancement** (KBN) "Stack management" kibana compatibility

&#x20;       **🐞Fix** (KBN) Test settings pop up won't show

&#x20;       **🐞Fix** (KBN) hide apps behaviour when "Management" is hidden

&#x20;       **🐞Fix** (KBN) Data view with a ":" symbol forces logout from a kibana

&#x20;       **🐞Fix** (KBN) Session probe causes constant refresh when no `kibana_access` defined

&#x20;       **🐞Fix** (ES) large report generation using data from a remote cluster with enabled x-pack security

### (2022-12-05) What's new in **ROR 1.45.1**

&#x20;       **🚀New** (KBN) 8.5.3, 7.17.8 support

&#x20;       **🚀New** (ES) 8.5.3, 7.17.8 support

&#x20;       **🐞Fix** (KBN) ROR KBN patching script

### (2022-11-29) What's new in **ROR 1.45.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-42003](https://nvd.nist.gov/vuln/detail/CVE-2022-42003), [CVE-2022-45146](https://nvd.nist.gov/vuln/detail/CVE-2022-45146)

&#x20;       **🚀New** (KBN) Activation Key API: read AK from ROR\_ACTIVATION\_KEY.txt

&#x20;       **🚀New** (KBN) Activation Key API: submit AK via POST /pkp/license (Basic auth)

&#x20;       **🚀New** (KBN) Inject CSS/JS files in login page

&#x20;       **🚀New** (KBN) Add user metadata to \<body> for extra UI customization

&#x20;       **🚀New** (ES) Added groups\_and mode to [groups\_provider\_authorization](https://docs.readonlyrest.com/elasticsearch#groups_provider_authorization) rule

&#x20;       **🧐Enhancement** (ES) all authorization rules support wildcards in group IDs

&#x20;       **🧐Enhancement** (ES) connections in the LDAP pool should not be closed unnecessarily

&#x20;       **🧐Enhancement** (KBN) Deterministic reporting index detection

&#x20;       **🧐Enhancement** (KBN) Move free type impersonation to the local users area

&#x20;       **🧐Enhancement** (KBN) don't logout when initial JWT token expires

&#x20;       **🐞Fix** (KBN) Direct Kibana API requests not aware of kibana\_index

&#x20;       **🐞Fix** (KBN) RO and RO\_strict kibana accesses

&#x20;       **🐞Fix** (ES) [when `fls_engine: es` is configured and `fields` rule is used, aggregations should be available only for allowed fields](https://forum.readonlyrest.com/t/field-level-security-and-aggregations/2133)

&#x20;       **🐞Fix** (ES) [Data streams creation issue fix](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/issues/829)

&#x20;       **🐞Fix** (ES) Unknown structure of index settings issue fix

&#x20;       **🐞Fix** (ES) resolving index names with wildcards should take into consideration the current index state and request indices options

### (2022-10-09) What's new in **ROR 1.44.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-25857](https://nvd.nist.gov/vuln/detail/CVE-2022-25857)

&#x20;       **🚀New** (KBN) 8.5.2, 8.5.1, 8.5.0, 7.17.7 support

&#x20;       **🚀New** (ES) 8.5.2, 8.5.1, 8.5.0, 7.17.7 support

&#x20;       **🚀New** (KBN) **plugin packages are now** [**universal**](https://docs.readonlyrest.com/universal-builds)

&#x20;       **🚀New** (KBN) **Manage your activation keys through the** [**customer portal**](https://readonlyrest.com/customer)

&#x20;       **🚀New** (ES) Added support for certificates in PEM format

&#x20;       **🧐Enhancement** (KBN) SAML groups list duplication made header size exceed limits

&#x20;       **🧐Enhancement** (KBN) kibana\_access: admin has now privileges to manage a Kibana cluster

&#x20;       **🧐Enhancement** (ES) added distributed and persistent Test Settings & Auth Mocks configuration for the Impersonation Feature

&#x20;       **🧐Enhancement** (ES) handling high load when LDAP rules are used

&#x20;       **🧐Enhancement** (ES) `client_authentication` settings in internode SSL configuration

&#x20;       **🧐Enhancement** (ES) `acl:available_groups` dynamic variable can be used in a single value context

&#x20;       **🐞Fix** (ES) SNI handling (internode SSL)

### (2022-08-22) What's new in **ROR 1.43.0**

&#x20;       **🚀New** (KBN) 8.4.3, 8.4.2, 8.4.1, 8.4.0, 7.17.6 support

&#x20;       **🚀New** (ES) 8.4.3, 8.4.2, 8.4.1, 8.4.0, 7.17.6 support

&#x20;       **🚀New** (KBN) `kibana_custom_js_inject_file` feature

&#x20;       **🐞Fix** (ES) [`ror-tools` fix for Windows OS (patching ES 3.x issue)](https://forum.readonlyrest.com/t/ror-plugin-for-es-8-x-patch-error/2115)

&#x20;       **🐞Fix** (ES) resolving indices in the remote x-pack cluster

&#x20;       **🐞Fix** (KBN|PRO) ROR menu title wraps when version text is too short (cosmetic)

&#x20;       **🐞Fix** (KBN) infinite loading when kibana\_access not defined for user

&#x20;       **🐞Fix** (KBN) transient error with randomly choosing off range bind port on localhost

&#x20;       **🐞Fix** (KBN) 404 on login when `xpack.spaces.enabled: false`

### (2022-07-25) What's new in **ROR 1.42.0**

&#x20;       **🚀New** (KBN|ES) 8.3.3, 8.3.2, 8.3.1, 8.3.0, 7.15.5 support

&#x20;       **🧐Enhancement** (KBN) Search box in tenancy switcher (when #tenancies > 5)

&#x20;       **🧐Enhancement** (ES) added configuration warnings in the Impersonation Feature

&#x20;       **🐞Fix** (KBN) Logout didn't delete the SAML session on the IdP

&#x20;       **🐞Fix** (KBN) 5xx errors from Elasticsearch break Kibana users' session unrecoverably

&#x20;       **🐞Fix** (ES) ROR node cooperation with X-pack nodes

### (2022-06-21) What's new in **ROR 1.41.0**

&#x20;       **🚀New** (ES) Added `groups_and` mode to [`ror_kbn_auth`](https://docs.readonlyrest.com/elasticsearch#ror_kbn_auth) and [`jwt_auth`](https://docs.readonlyrest.com/elasticsearch#jwt_auth) rules

&#x20;       **🧐Enhancement** (KBN) Prevent native credentials dialogue to appear in Kibana when ES responds 401

&#x20;       **🧐Enhancement** (KBN) Logging in after logout shows the same page you last visited

&#x20;       **🧐Enhancement** (KBN) x-ror-correlation-id header lets you audit a whole Kibana session

&#x20;       **🐞Fix** (ES|KBN) tenancy selector didn't work well with `jwt_auth` and `ror_kbn_auth` rules

&#x20;       **🐞Fix** (KBN) Support for special characters in tenancy names

&#x20;       **🐞Fix** (KBN) OIDC logout flow redirecting to bad request error

&#x20;       **🐞Fix** (KBN) OIDC connector not working in Kibana < 7.12.0

### (2022-05-24) What's new in **ROR 1.40.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2022-25647](https://nvd.nist.gov/vuln/detail/CVE-2022-25647) & [CVE-2022-24823](https://nvd.nist.gov/vuln/detail/CVE-2022-24823) & [CVE-2020-13956](https://nvd.nist.gov/vuln/detail/CVE-2020-13956) & [CVE-2020-36518](https://nvd.nist.gov/vuln/detail/CVE-2020-36518) & [CVE-2020-13956](https://nvd.nist.gov/vuln/detail/CVE-2020-13956) & [CVE-2020-36518](https://nvd.nist.gov/vuln/detail/CVE-2020-36518)

&#x20;       **🚨Security Fix** (KBN) "Security" app not entirely hidden in 8.2.x

&#x20;       **🚀New** (ES) New Support for 8.2.3, 8.2.2, 8.2.1, 7.17.4

&#x20;       **🚀New** (KBN) New Support for 8.2.2 8.2.1, 7.17.4

&#x20;       **🚀New** (ES & KBN) [The Impersonation feature](https://docs.readonlyrest.com/kibana#impersonation)

&#x20;       **🚀New** (ES) [FIPS compliant SSL mode](https://docs.readonlyrest.com/elasticsearch/fips)

&#x20;       **🧐Enhancement** (KBN) SAML cert is now required

&#x20;       **🧐Enhancement** (KBN) moved OIDC to better library

&#x20;       **🧐Enhancement** (KBN) OIDC jwksURL is now required

&#x20;       **🐞Fix** (ES) `indices: ["1"]` interpreted as integer and fails to parse

&#x20;       **🐞Fix** (KBN) /login?jwt=xxx authorization now works again

&#x20;       **🐞Fix** (KBN) OIDC/SAML assertion claims were not forwarded to ES

&#x20;       **🐞Fix** (KBN) include whitelisted headers while logging

&#x20;       **🐞Fix** (KBN) basepath handling fixes (too many redirects)

&#x20;       **🐞Fix** (KBN) Make ROR default space the actual default one

&#x20;       **🐞Fix** (KBN) OIDC connection error

### (2022-03-19) What's new in **ROR 1.39.0**

&#x20;       **🚨Security Fix** (KBN) XSS sanitize path requested

&#x20;       **🚨Security Fix** (ES) [CVE-2020-36518](https://nvd.nist.gov/vuln/detail/CVE-2020-36518) & [CVE-2022-21653](https://nvd.nist.gov/vuln/detail/CVE-2022-21653)

&#x20;       **🚀New** (KBN) New Support for 8.2.0 8.1.3, 8.1.2, 8.1.1, 8.1.0, 8.0.0, 8.0.1, 7.17.3, 7.17.2

&#x20;       **🚀New** (ES) New Support for 8.2.0, 8.1.3, 8.1.2, 8.1.1, 8.1.0, 8.0.0, 8.0.1 ([required additional patching step](https://docs.readonlyrest.com/elasticsearch#3.-patch-es))

&#x20;       **🚀New** (ES) New Support for 7.17.3, 7.17.2

&#x20;       **🚀New** (ES) [New `groups_and` ACL rule](https://docs.readonlyrest.com/elasticsearch#groups_and)

&#x20;       **🧐Enhancement** (KBN) Stop inlining whitelisted headers into Authorization header

&#x20;       **🧐Enhancement** (KBN) Log additional errors and info related to HA

&#x20;       **🧐Enhancement** (KBN) Misc internal dependencies upgrades

&#x20;       **🐞Fix** (KBN) Mandatory elasticsearch credentials in kibana.yml

&#x20;       **🐞Fix** (KBN) [Reporting page redirect on refresh when kibana\_hide\_apps: \["Stack Management"\]](https://forum.readonlyrest.com/t/when-hiding-stack-management-a-redirect-appears-with-report/2088)

&#x20;       **🐞Fix** (KBN) whitelistedPaths: log errors when 404 occurs

&#x20;       **🐞Fix** (KBN) [Issue uploading large payload](https://forum.readonlyrest.com/t/issue-uploading-large-payload/2091)

&#x20;       **🐞Fix** (KBN) `elasticsearch.requestHeadersWhitelist` should be case insensitive

&#x20;       **🐞Fix** (ES) [Issue with handling data streams by `indices` rule](https://forum.readonlyrest.com/t/ror-1-37-0-indices-rule-and-alias-within-kibana/2078)

&#x20;       **🐞Fix** (ES) X-Pack SSL nodes cooperation with ROR SSL nodes

&#x20;       **🐞Fix** (ES) \_msearch issue when filter rules was used in matched block

### (2022-01-17) What's new in **ROR 1.38.0**

&#x20;       **🚀New** (ES) New Support for 7.17.0, 7.17.1

&#x20;       **🚀New** (KBN) New Support for 7.17.0

&#x20;       **🚀New** (ES) [Configuration for custom audit cluster](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.38.x/elasticsearch.md#custom-audit-cluster)

&#x20;       **🧐Enhancement** (ES) Separate "audit" section for all audit settings

&#x20;       **🐞Fix** (KBN) Editor rendering issue with kibana basePath enabled

### (2021-12-14) What's new in **ROR 1.37.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2021-43797](https://nvd.nist.gov/vuln/detail/CVE-2021-43797)

&#x20;       **🚀New** (ES) New Support for 7.16.3, 7.16.2, 6.8.23, 6.8.22

&#x20;       **🚀New** (KBN) New Support for 7.16.3, 7.16.2, 7.16.1, 7.16.10, 6.8.23, 6.8.22, 6.8.21

&#x20;       **🧐Enhancement** (ES) fields rule handling in the context of x-Pack SQL requests

&#x20;       **🐞Fix** (ES) filter rule handling in the context of x-Pack SQL requests

&#x20;       **🐞Fix** (KBN) POST / bulk cause an 400 error in devtools console

&#x20;       **🐞Fix** (KBN) More robust Kibana patcher + better logs messages

### (2021-11-21) What's new in **ROR 1.36.0**

&#x20;       **🚀New** (ES) New Support for 7.16.1, 7.16.0, 6.8.21

&#x20;       **🚀New** (KBN) Support Kibana 7.15.2

&#x20;       **🚀New** (ES) [Added support for setting up cluster containing ES with ROR (with disabled XPack security) and ES with XPack security enabled](https://forum.readonlyrest.com/t/ssl-internode-with-elk-cluster/1916)

&#x20;       **🧐Enhancement** (KBN) kibana\_hide\_apps: \[ror|kibana] to remove kibana mgmt button

&#x20;       **🐞Fix** (ES) [/\_snapshot/\_status should return only running snapshots](https://github.com/sscarduzio/elasticsearch-readonlyrest-plugin/issues/756)

&#x20;       **🐞Fix** (ES) [Adding policy to index template bug](https://forum.readonlyrest.com/t/forbidden-by-readonlyrest-es-plugin-with-add-policy-to-index-template-action-in-kibana/1969)

&#x20;       **🐞Fix** (KBN) Index management tabs result in "forbidden" error

&#x20;       **🐞Fix** (KBN) [corrupted patch file for Kibana 7.9.x](https://forum.readonlyrest.com/t/ror-1-35-1-kibana-7-9-3-unable-to-patch/2018)

&#x20;       **🐞Fix** (KBN) [YAML editor not working in air-gapped environments](https://forum.readonlyrest.com/t/readonlyrest-security-settings-editor-loading/2014/5)

&#x20;       **🐞Fix** (KBN) [Devtools not working](https://forum.readonlyrest.com/t/kibana-devtools-error-does-not-support-having-a-body/2027)

&#x20;       **🐞Fix** (KBN) [Monitoring not working in multi-tenancy](https://forum.readonlyrest.com/t/kibana-alerting-not-working-with-readonlyrest/1986)

&#x20;       **🐞Fix** (KBN) Regression in Kibana < 6.8.x front end crash

&#x20;       **🐞Fix** (KBN) Kibana < 7.8.x prevent navigation to hidden apps from home links

&#x20;       **🐞Fix** (KBN) Kibana < 7.8.x implicitly hide kibana:dashboard when kibana:dashboards is hidden (and viceversa)

&#x20;       **🐞Fix** (KBN) Kibana < 7.8.x broken `clearSessionOnEvents: [tenancyHop]`

### (2021-10-17) What's new in **ROR 1.35.1**

&#x20;       **🚨Security Fix** (ES) [CVE-2021-21409](https://nvd.nist.gov/vuln/detail/CVE-2021-21409) & [CVE-2021-27568](https://nvd.nist.gov/vuln/detail/CVE-2021-27568)

&#x20;       **🚀New** (KBN) Support Kibana 7.15.1

&#x20;       **🚀New** (ES) New Support for 7.15.2

&#x20;       **🧐Enhancement** (KBN) Support "server.ssl.supportedProtocols" settings

&#x20;       **🧐Enhancement** (KBN) Support "server.ssl.cipherSuites"

&#x20;       **🧐Enhancement** (KBN) Always honor SSL cipher order

&#x20;       **🐞Fix** (KBN) Don'thide "Add/Remove field as column" in Discover app for RO users

&#x20;       **🐞Fix** (KBN) More alerting fixes (only for main tenancy)

### (2021-10-12) What's new in **ROR 1.35.0**

&#x20;       **🚀New** (KBN) Support Kibana 7.15.0, 7.14.2

&#x20;       **🚀New** (ES) New Support for 7.15.1, 6.8.19, 6.8.20

&#x20;       **🧐Enhancement** (ES) [local->external groups detailed mapping for groups rule](https://github.com/beshu-tech/readonlyrest-docs/blob/master/details/groups-rule-mapping.md)

&#x20;       **🧐Enhancement** (ES) when ROR is starting any request is going to end up with HTTP 403 response, instead of HTTP 503

&#x20;       **🧐Enhancement** (KBN) "server.basePath" kibana option implementation

&#x20;       **🧐Enhancement** (KBN) Support full regex in kibana\_hidden\_apps rule

&#x20;       **🧐Enhancement** (unspecified) Crash if Kibana is not patched

&#x20;       **🧐Enhancement** (KBN) Honour kibana setting "logging.dest"

&#x20;       **🧐Enhancement** (KBN) Confirm before overwriting audit log dashboard

&#x20;       **🐞Fix** (ES) verbosity: error fix in case of ROR KBN login request

&#x20;       **🐞Fix** (KBN) Make alerting work on primary tenancy

&#x20;       **🐞Fix** (KBN) OIDC fix sameSite / secure cookie options

&#x20;       **🐞Fix** (KBN) Login form is stretched when long error

&#x20;       **🐞Fix** (KBN) Login form is stretched when long error

&#x20;       **🐞Fix** (KBN-PRO) [Don't send x-ror-currentgroup in PRO](https://forum.readonlyrest.com/t/upgrading-6-7-w-1-18-to-7-14-w-1-33-ldap-from-ms-active-directory-no-longer-understands-multiple-ad-group-memberships/1973/6)

&#x20;       **🐞Fix** (KBN) Resolve browser console errors on a popover close

### (2021-09-24) What's new in **ROR 1.34.0**

&#x20;       **🚀New** (ES) New Support for 7.15.0, 7.14.2

&#x20;       **🚀New** (KBN) VS Code style YAML editor

&#x20;       **🚀New** (KBN) Skip rendering hidden app groups entirely

&#x20;       **🚀New** (KBN) Redesigned ROR Menu

&#x20;       **🚀New** (KBN) Dark theme awareness

&#x20;       **🐞Fix** (KBN) Broken Kibana Spaces

&#x20;       **🐞Fix** (KBN) Support Kibana's undocumented "server.ssl.\*" settings

&#x20;       **🐞Fix** (KBN) cookiePass config parsing broke load balancing

### (2021-08-14) What's new in **ROR 1.33.1**

&#x20;       **🚀New** (ES) New Support for 7.14.1

&#x20;       **🐞Fix** (KBN) Error in patching for 7.14.0

&#x20;       **🐞Fix** (KBN) clearSessionOnEvents now works as expected

&#x20;       **🐞Fix** (KBN) login form font loads correctly

### (2021-08-09) What's new in **ROR 1.33.0**

&#x20;       **🚨Security Fix** (KBN) xml-crypto dependency update

&#x20;       **🚀New** (KBN) New Support for 7.14.0, 6.8.18

&#x20;       **🧐Enhancement** (KBN) Parse credentials in /api/\* requests, no need for valid cookie. Supersedes whitelistedPaths

&#x20;       **🐞Fix** (KBN) Caching issues switching tenancies with dark/light theme

&#x20;       **🐞Fix** (KBN) Newly created Space shows in all tenancies when using default kibana index

&#x20;       **🐞Fix** (KBN < 7.9.x) nextUrl works again with SAML and OIDC

### (2021-07-25) What's new in **ROR 1.32.0**

&#x20;       **🚨Security Fix** (ES) [Apache Commons Codec vulnerability](https://forum.readonlyrest.com/t/security-vulnerability-for-common-codec-1-10/1906)

&#x20;       **🚨Security Fix** (KBN) upgraded dependencies due to security fixes

&#x20;       **🚨Security Fix** (KBN) disable x-powered-by to avoid fingerprinting

&#x20;       **🚀New** (ES) Support for ES 7.14.0 & 6.8.18

&#x20;       **🚀New** (KBN) Support for Kibana 7.13.x series

&#x20;       **🧐Enhancement** (KBN) honor configurations coming from ENV and CLI options

&#x20;       **🧐Enhancement** (KBN) when metadata has no username, login must be denied

&#x20;       **🧐Enhancement** (KBN) audit tab ported to new platform

&#x20;       **🧐Enhancement** (ES) improved ES resources cleaning when ROR returns FORBIDDEN response

&#x20;       **🧐Enhancement** (KBN < 7.9.x) auto clean-up dangling SAML/OIDC cookies

&#x20;       **🐞Fix** (ES) [incomplete response for request GET \*/\_alias](https://forum.readonlyrest.com/t/ror-return-incomplete-response-for-request-get-alias/1872)

&#x20;       **🐞Fix** (ES) not allowed aliases should not present in a response for a Get Index API request

&#x20;       **🐞Fix** (KBN) fix dev-tools and import saved object not working

&#x20;       **🐞Fix** (KBN) honor `requestHeadersWhitelist` in user metadata request (login)

&#x20;       **🐞Fix** (KBN < 7.9.x) do not crash on invalid metadata

### (2021-06-29) What's new in **ROR 1.31.0**

&#x20;       **🚨Security Fix** (KBN) prevent direct navigation to hidden apps

&#x20;       **🚀New** (ES) 7.13.4, 7.13.3, 7.13.2, 6.8.17 support

&#x20;       **🚀New** (KBN) new minimal Kibana Management menu when "Management" app is hidden

&#x20;       **🧐Enhancement** (KBN) logout active Kibana session if key metadata/permissions change in ACL

&#x20;       **🧐Enhancement** (KBN) better port number validation

&#x20;       **🧐Enhancement** (ES) improved cluster indices handling

&#x20;       **🐞Fix** (ES) [Kibana access rule regression fix](https://forum.readonlyrest.com/t/es7-11-2-1-30-0-enterprise-two-contexts-rw-ro-issue/1855)

&#x20;       **🐞Fix** (ES) search template API handling with `filter` and `fields` rule

&#x20;       **🐞Fix** (ES) multi-tenancy issue when groups\_provider\_authorization is used

&#x20;       **🐞Fix** (ES) `x_forwarded_for` rule: wrong handling of / request

&#x20;       **🐞Fix** (ES) Issue with handling ResizeRequest which made it unable to upgrade Kibana to version 7.12.0+

&#x20;       **🐞Fix** (KBN) some Kibana requests arrive to ES without credentials

&#x20;       **🐞Fix** (KBN) inconsistent read after write in session storage lead to issues with round robin load balancing

&#x20;       **🐞Fix** (KBN) bad multipart POST handling leads to saved object import errors

### (2021-05-26) What's new in **ROR 1.30.1**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-27568">CVE-2021-27568</a></summary>

This release addresses CVE-2021-27568, a vulnerability in the json-smart library (used by Elasticsearch) where an uncaught exception (e.g., NumberFormatException) could cause crashes or potentially expose sensitive information. ROR users are advised to update to mitigate this risk.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 7.13.0, 7.13.1 support</summary>

ReadonlyREST now supports Elasticsearch versions 7.13.0 and 7.13.1, ensuring compatibility with the latest features and improvements in those releases.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Regression in multi-tenancy handling</summary>

A regression introduced in a previous release that affected multi-tenancy behavior has been resolved. Multi-tenant configurations should now work as expected without unintended access restrictions or errors.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) Proper handling of _snapshot/_status endpoint</summary>

Fixed an issue where the `_snapshot/_status` endpoint was not being handled correctly by the security plugin. This ensures that snapshot status requests are properly authorized and processed.

</details>

### (2021-05-16) What's new in **ROR 1.30.0**

&#x20;       **🚀New** (KBN) 7.12.x compatibility

&#x20;       **🚀New** (ES) [LDAP connector circuit breaker](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.30.x/elasticsearch.md#circuit-breaker)

&#x20;       **🧐Enhancement** (ES) [Username with wildcard support in users section](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.30.x/elasticsearch.md#groups) and [groups mapping](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.30.x/elasticsearch.md#group-mapping)

&#x20;       **🧐Enhancement** (KBN < 7.9.x) OIDC errors visibility

&#x20;       **🧐Enhancement** (KBN < 7.9.x) Smarter session probe algorithm

&#x20;       **🐞Fix** (KBN >= 7.9.x) [Load CertificateAuthorities as an array if not specified as an array](https://forum.readonlyrest.com/t/kibana-crash-at-startup-with-the-new-7-10-2-version/1840)

&#x20;       **🐞Fix** (KBN < 7.9.x) Don't hide visualizations list search box in RO mode

### (2021-04-09) What's new in **ROR 1.29.0**

<details>

<summary><strong>🚨Security Fix</strong> (ES) Security Fix (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21409">CVE-2021-21409</a></summary>

This release addresses CVE-2021-21409, a Netty vulnerability (versions before 4.1.61.Final) that allows HTTP request smuggling via improper validation of the content-length header when a single Http2HeaderFrame with endStream set to true is used. The ROR plugin has been updated to use a patched version of Netty, eliminating the risk.

</details>

<details>

<summary><strong>🚀New</strong> (KBN) support 7.9.0, 7.9.1, 7.10.0, 7.10.1, 7.10.2, 7.11.0, 7.11.1, 7.11.2 (<a href="https://beta.readonlyrest.com/">with ROR new platform</a>)</summary>

ReadonlyREST now supports Kibana versions 7.9.0 through 7.11.2, powered by the new ROR platform. This expands compatibility for teams running older Kibana instances who still need enterprise-grade security for their Elasticsearch stack.

</details>

<details>

<summary><strong>🚀New</strong> (ES) 7.12.1 support</summary>

ReadonlyREST now supports Elasticsearch 7.12.1, ensuring users on this version can benefit from the plugin's fine-grained access control, field- and document-level security, and YAML-based rule configuration.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) logout if the credentials/metadata of the current user change in the ACL</summary>

Kibana sessions are now automatically terminated when the authenticated user's credentials or metadata are modified in the ACL configuration. This prevents stale sessions from persisting after access rights have been updated, improving security and ensuring that policy changes take effect immediately.

</details>

### (2021-04-01) What's new in **ROR 1.28.2**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21295">CVE-2021-21295</a></summary>

This release addresses CVE-2021-21295, an HTTP request smuggling vulnerability in Netty's `netty-codec-http2` module (versions before 4.1.60.Final). The flaw occurs when an HTTP/2 request containing a `Content-Length` header is converted to HTTP/1.1 objects and proxied to a remote peer, potentially allowing an attacker to smuggle requests within the body. The underlying Netty dependency has been updated to the patched version to mitigate this risk.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) prevent SAML/OIDC initiated Kibana sessions from expiring after <code>session_timeout_minutes</code> despite continued interaction</summary>

Fixed a bug where Kibana sessions authenticated via SAML or OIDC would expire after the configured `session_timeout_minutes` even when the user was actively interacting with the application. The session timeout now properly resets on user activity, ensuring that active users are not unexpectedly logged out.

</details>

### (2021-03-24) What's new in **ROR 1.28.1**

<details>

<summary><strong>🐞Fix</strong> (ES) Getting index templates issue when no <code>indices</code> rule was used in matched block</summary>

Fixed an issue where retrieving index templates would fail when a matched block in the ROR configuration did not include an `indices` rule. This ensures that index template operations work correctly even in configurations where access control is defined without explicit index-level restrictions.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/cannot-put-index-template-template-1/1681/25">NPE on getting template aliases</a></summary>

Resolved a NullPointerException that occurred when fetching template aliases in Elasticsearch. This fix addresses the issue reported by users who were unable to perform `PUT _index_template/template_1` operations due to the NPE, restoring proper handling of index template aliases in secured clusters.

</details>

### (2021-03-14) What's new in **ROR 1.28.0**

<details>

<summary><strong>🚀New</strong> (ES) 7.12.0, 7.11.2 support</summary>

ReadonlyREST now supports Elasticsearch versions 7.12.0 and 7.11.2, ensuring compatibility with the latest Elasticsearch releases in the 7.x line.

</details>

<details>

<summary><strong>🚀New</strong> (ES) full <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.9/index-templates.html">Index and Component Templates API</a> support</summary>

ReadonlyREST now fully supports Elasticsearch's composable index templates and component templates API (introduced in ES 7.8). This allows administrators to define fine-grained access control rules for both index templates (which configure indices/data streams upon creation) and component templates (reusable building blocks for mappings, settings, and aliases), ensuring security policies extend to template management operations.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (ES) <a href="https://forum.readonlyrest.com/t/ldap-based-user-authentication/1667">Username case sensitivity settings</a></summary>

Added a configurable flag to handle username case sensitivity in authentication backends like LDAP. When enabled, this setting allows case-insensitive username matching — useful when the LDAP server treats usernames case-insensitively but the ROR configuration requires an exact case match. Administrators can now apply string transformations (e.g., toLower, toUpper) to usernames during authentication.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/kibana-plugin-software-licensing-and-expiration/1808/5">Kibana logout event storing fix</a></summary>

Fixed an issue where Kibana logout events were not being properly stored or handled, which could interfere with session tracking and audit logging. This ensures that logout actions are correctly recorded and processed.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://forum.readonlyrest.com/t/reindex-index-not-found-exception/1708/20">Fixed remote reindex operation with "type" parameter</a></summary>

Resolved an issue where remote reindex operations failed with an "index not found" exception when the request included a "type" parameter. This fix ensures that cross-cluster reindexing works correctly even when legacy type parameters are present in the request.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Prevent cookie expiration deadlock in browsers when using SAML/OIDC</summary>

Fixed a browser-side deadlock scenario where cookie expiration could cause authentication loops when using SAML or OIDC single sign-on. This ensures a smoother login experience and prevents users from getting stuck in an infinite redirect cycle.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) When credentials change in the ACL, make it possible to login again</summary>

Fixed an issue where users could not log in again after their credentials were updated in the ACL configuration. Previously, cached or stale authentication states could prevent re-authentication with the new credentials.

</details>

<details>

<summary><strong>🐞Fix</strong> (KBN) Kibana management app ID changed from "kibana:management" to "kibana:stack_management"</summary>

Updated the Kibana management application identifier from the legacy "kibana:management" to the current "kibana:stack\_management" to align with changes in newer Kibana versions. This ensures that access control rules targeting the management section work correctly.

</details>

### (2021-02-27) What's new in **ROR 1.27.1**

<details>

<summary><strong>🚨Security Fix</strong> (ES) <a href="https://nvd.nist.gov/vuln/detail/CVE-2021-21290">CVE-2021-21290</a></summary>

This release addresses CVE-2021-21290, a security vulnerability in Netty (versions before 4.1.59.Final) affecting Unix-like systems. The issue involves insecure temporary file creation when multipart decoders store uploads to disk — files created via `File.createTempFile` in shared temporary directories have default permissions (`-rw-r--r--`), making them readable by other local users and potentially leading to local information disclosure. ROR has updated its Netty dependency to the patched version to mitigate this risk.

</details>

&#x20;       **🚀New** (ES) 7.11.1 support

### (2021-02-16) What's new in **ROR 1.27.0**

<details>

<summary><strong>🚀New</strong> (ES) 7.11.0, 7.10.2, 6.8.14 support</summary>

Added support for Elasticsearch versions 7.11.0, 7.10.2, and 6.8.14, ensuring compatibility with the latest patch releases across these major version lines.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) X-Forwarded-For copied from incoming request (or filled with source IP) before forwarding to ES</summary>

Kibana now properly propagates the X-Forwarded-For header from the incoming request to Elasticsearch. If the header is absent, it is populated with the source IP address, improving audit trail accuracy and enabling proper client IP tracking in multi-tier proxy setups.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) Kibana logout event generates a special audit log entry in ROR audit logs index</summary>

When a user logs out of Kibana, ROR now generates a dedicated audit log entry in the ROR audit logs index. This provides better visibility into user session lifecycles and helps with security auditing and compliance requirements.

</details>

<details>

<summary><strong>🧐Enhancement</strong> (KBN) ROR panel shows "reports" button if kibana:management app is hidden</summary>

The ROR panel in Kibana now displays a "Reports" button even when the kibana:management application is hidden. This ensures users can still access reporting features regardless of their Kibana management visibility settings.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) <a href="https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md#fields">blocks containing filter and/or fields won't match internal kibana requests, so kibana_* rules won't have to be placed in such blocks</a></summary>

Fixed an issue where ACL blocks containing `filter` and/or `fields` rules could incorrectly match internal Kibana requests. With this fix, internal Kibana requests bypass such blocks, meaning `kibana_*` rules no longer need to be placed inside blocks that also define field-level or document-level security rules. This simplifies ACL configuration and prevents unintended access restrictions on Kibana's internal operations.

</details>

<details>

<summary><strong>🐞Fix</strong> (ES) SQL API - better handling of invalid query</summary>

Improved error handling for the Elasticsearch SQL API when an invalid query is submitted. Instead of potentially returning unclear or inconsistent responses, ROR now handles malformed SQL queries more gracefully, providing better feedback and stability.

</details>

### (2021-01-11) What's new in **ROR 1.26.1**

&#x20;       **🐞Fix** (ES) wrong behaviour of `kibana_access` rule for ROR actions when ADMIN value is set

### (2021-01-02) What's new in **ROR 1.26.0**

&#x20;       **🚨Security Fix** (ES) [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35490) & [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35491) (removed Jackson dependency from ROR core)

&#x20;       **🚀New** (ES) [New response\_fields rule](https://forum.readonlyrest.com/t/ror-1-18-9-enterprise-es-7-2-0-enable-cluster-health-without-authentication/1567)

&#x20;       **🚀New** (ES) [Support for LDAP server discovery using \_ldaps.\_tcp SRV record](https://forum.readonlyrest.com/t/does-ror-support-dc-locator/1211)

&#x20;       **🚀New** (ES) [New configuration option allowing to ignore LDAP connectivity problems](https://forum.readonlyrest.com/t/ror-cannot-start-if-ldap-is-not-available/1748)

&#x20;       **🧐Enhancement** (ES) Full support for ILM API

&#x20;       **🧐Enhancement** (KBN) Enforce read-after-write consistency between kibana nodes

&#x20;       **🧐Enhancement** (KBN ENT) OIDC custom claims incorporated in "assertion" claim

&#x20;       **🧐Enhancement** (KBN ENT) OIDC support for configurable kibanaExternalHost (good for Docker)

&#x20;       **🧐Enhancement** (KBN ENT) ROR adds "ror-user\_" class to "body" tag for easy per-user CSS/JS

&#x20;       **🧐Enhancement** (KBN ENT/PRO) ROR adds "ror-group\_" class to "body" tag for easy per-group CSS/JS

&#x20;       **🐞Fix** (ES) [ROR authentication endpoint action](https://forum.readonlyrest.com/t/es-7-4-2-ror-1-18-9-rradmin-refreshsettings-by-block-default/1388)

&#x20;       **🐞Fix** (ES) "username" in audit entry when request is rejected ### What's new in 1.25.2

&#x20;       **🐞Fix** (ES) [removed verbose logging](https://forum.readonlyrest.com/t/elastic-message-cannot-extract-fields-for-query-after-readonlyrest-installation/1749) ### What's new in 1.25.1

&#x20;       **🚨Security Fix** (ES) [CVE-2020-25649](https://nvd.nist.gov/vuln/detail/CVE-2020-25649)

&#x20;       **🚀New** (ES) 7.10.1 support ### What's new in 1.25.0

&#x20;       **🚨Security Fix** (ES) [Common Vulnerabilities and Exposures (CVE)](https://forum.readonlyrest.com/t/update-of-jackson-databind-2-9-6-jar/176)

&#x20;       **🚀New** (ES) 7.10.0 support

&#x20;       **🚀New** (ES) [auth\_key\_pbkdf2 rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#auth_key_pbkdf2)

&#x20;       **🚀New** (ES) [Introduced configuration property defining FLS engine used by fields rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#fields)

&#x20;       **🧐Enhancement** (ES) Fields rule performance improvement

&#x20;       **🧐Enhancement** (ES) Resolved index API support

&#x20;       **🐞Fix** (ES) ["username" in audit entry when user is authenticated via proxy\_auth](https://forum.readonlyrest.com/t/ror-audit-not-logging-user-id)

&#x20;       **🐞Fix** (ES) index resolve action should be treated as readonly action

&#x20;       **🐞Fix** (ES) /\_snapshot and /\_snapshot/\_all should behave the same ### What's new in 1.24.0

&#x20;       **🚨Security Fix** (ES) search template handling fix

&#x20;       **🚀New** (ES) 7.9.3 & 6.8.13 support

&#x20;       **🧐Enhancement** (ES) full support for ES Snapshots and Restore APIs

&#x20;       **🐞Fix** (KBN) fix crash in error handling

&#x20;       **🐞Fix** (ES) don't remove ES response warning headers

&#x20;       **🐞Fix** (ES) issue when entropy of /dev/random could have been exhausted when using JwtToken rule ### What's new in 1.23.1

&#x20;       **🚀New** (ES) 7.9.2 support

&#x20;       **🐞Fix** (KBN) fix code 500 error on login in Kibana ### What's new in 1.23.0

&#x20;       **🚀New** (ES) introduced must\_involve\_indices option for indices rule

&#x20;       **🧐Enhancement** (ES) negation support in headers rules

&#x20;       **🧐Enhancement** (ES) [x-pack rollup API handling](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)

&#x20;       **🐞Fix** (KBN) deep links query parameters are now handled

&#x20;       **🐞Fix** (KBN) make sure default kibana index is always discovered (fixes reporting in 6.x)

&#x20;       **🐞Fix** (ES) [settings file permission issue with JDK 1.8.0 25.262-b10](https://forum.readonlyrest.com/t/readonlyrest-for-elastic-wont-start-1-18-8-es6-8-1/1652)

&#x20;       **🐞Fix** (ES) /\_cluster/allocation/explain request should not be forbidden if matched block doesn't have indices rules

&#x20;       **🐞Fix** (ES) remote address extracting issue

&#x20;       **🐞Fix** (ES) [fixed TYP audit field for some request types](https://forum.readonlyrest.com/t/match-wrong-index-in-forbid-block/1653/2) ### What's new in 1.22.1

&#x20;       **🐞Fix** (ES) missing handling of aliases API for ES 7.9.0 ### What's new in 1.22.0

&#x20;       **🚀New** (ES) 7.9.0 support

&#x20;       **🧐Enhancement** (ES) aliases API handling

&#x20;       **🧐Enhancement** (ES) dynamic variables support in fields rule

&#x20;       **🐞Fix** (ES) [adding aliases issue](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)

&#x20;       **🐞Fix** (ES) potential memory leak for ES 7.7.x and above

&#x20;       **🐞Fix** (ES) cross cluster search issue fix for X-Pack \_async\_search action

&#x20;       **🐞Fix** (ES) XFF entry in audit issue

&#x20;       **🐞Fix** (KBN) SAML certificate loading

&#x20;       **🐞Fix** (KBN) SAML loading groups from assertion

&#x20;       **🐞Fix** (KBN) fix reporting in pre-7.7.0 ### What's new in 1.21.0

&#x20;       **🧐Enhancement** (ES) [cluster API support improvements](https://forum.readonlyrest.com/t/settings-problems/1616)

&#x20;       **🐞Fix** (ES) X-Pack \_async\_search support

&#x20;       **🐞Fix** (ES) \_rollover request handling

&#x20;       **🐞Fix** (ES) [handling numeric ssl configuration properties](https://forum.readonlyrest.com/t/numeric-passphrases-invalid-ssl-config/1512)

&#x20;       **🐞Fix** (KBN) multitenancy+reporting regression fix (for 7.6.x and earlier)

&#x20;       **🐞Fix** (KBN) "x-" headers should be forwarded in /login route when proxy passthrough is enabled

&#x20;       **🐞Fix** (unspecified) [(KBN) Logout now redirects to login screen when using proxy](https://forum.readonlyrest.com/t/kibana-ror-1-19-5-issue/1576/24)

&#x20;       **🐞Fix** (KBN) SAML metadata.xml endpoint not responding

&#x20;       **🐞Fix** (KBN) NAT/reverse proxy support for SAML

&#x20;       **🐞Fix** (KBN) SAML login redirect error

&#x20;       **🐞Fix** (ES) \_readonlyrest/metadata/current\_user should be always allowed by filter/fields rule ### What's new in 1.20.0

&#x20;       **🚀New** (unspecified) 7.7.1, 7.8.0 support

&#x20;       **🧐Enhancement** (KBN) tidy up audit page

&#x20;       **🧐Enhancement** (KBN FREE) clearly inform when features are not available

&#x20;       **🧐Enhancement** (KBN) ship license report of libraries

&#x20;       **🧐Enhancement** (ES) filter rule performance improvement

&#x20;       **🐞Fix** (KBN) proxy\_auth: avoid logout-login loop

&#x20;       **🐞Fix** (KBN) 404 error on font CSS file

&#x20;       **🐞Fix** (ES) [wildcard in filter query issue](https://forum.readonlyrest.com/t/wildcard-in-dls-filter-gives-error/1551)

&#x20;       **🐞Fix** (ES) [forbidden /\_snapshot issue](https://forum.readonlyrest.com/t/get-snapshot-permission-issue/1594)

&#x20;       **🐞Fix** (ES) /\_mget handling by indices rule when no index from a list is found

&#x20;       **🐞Fix** (ES) available groups order in metadata response should match the order in which groups appear in ACL

&#x20;       **🐞Fix** (ES) .readonlyrest and audit index - removed usage of explicit index type

&#x20;       **🐞Fix** (ES) [tasks leak bug](https://forum.readonlyrest.com/t/lots-of-active-tasks-in-cat-tasks/1593) ### What's new in 1.19.5

&#x20;       **🚀New** (unspecified) 7.7.0, 7.6.2, 6.8.9, 6.8.8 support

&#x20;       **🧐Enhancement** (ES/KBN) kibana\_access can be explicitly set to unrestricted

&#x20;       **🧐Enhancement** (ES) [LDAP connection pool improvement](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)

&#x20;       **🐞Fix** (ES) [better LDAP request timeout handling](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)

&#x20;       **🐞Fix** (ES) remote indices searching bug

&#x20;       **🐞Fix** (ES) cross cluster search support for \_field\_caps request

&#x20;       **🚨Security Fix** (ES) create and delete templates handling

&#x20;       **🐞Fix** (KBN) Regression in proxy\_auth\_passthrough

&#x20;       **🧐Enhancement** (KBN) whitelistedPaths now accepts basic auth credentials

&#x20;       **🧐Enhancement** (KBN) Dump logout button, [new ROR Panel](https://forum.readonlyrest.com/t/new-logout-button-design-new-ror-panel/1476)

&#x20;       **🧐Enhancement** (KBN) removed ROR from Kibana sidebar. Admins have a link in new panel.

&#x20;       **🧐Enhancement** (KBN) avoid show login form redirecting from SAML IdP

&#x20;       **🚀New** (KBN) [OpenID Connect (OIDC) authentication connector](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#openid-connect-oidc)

&#x20;       **🚀New** (KBN) [login\_title, login\_subtitle enable 2 column login page](https://forum.readonlyrest.com/t/ror-enterprise-show-support-contact-on-login-page/1508/2)

&#x20;       **🚨Security Fix** (KBN) server-side navigation prevention to hidden apps ### What's new in 1.19.4

&#x20;       **🐞Fix** (ES) Interpolating config with environment variables in SSL section

&#x20;       **🐞Fix** (KBN Ent 6.x) Fixed default space creation in

&#x20;       **🐞Fix** (KBN 6.x) Fixed error toast notification not showing

&#x20;       **🐞Fix** (KBN Ent) Fixed missing Axios dependency

&#x20;       **🐞Fix** (KBN Ent) Fixed SAML connector

&#x20;       **🐞Fix** (KBN) Toast notification overlap with logout bar

&#x20;       **🧐Enhancement** (KBN) Restyled logout bar

&#x20;       **🧐Enhancement** (KBN) Configurable periodic session checker ### What's new in 1.19.3

&#x20;       **🚀New** (ES/KBN) 7.6.1 compatibility

&#x20;       **🚀New** (ES) customizable name of settings index

&#x20;       **🧐Enhancement** (KBN) configurable ROR cookie name

&#x20;       **🧐Enhancement** (ES/KBN) handling of encoded ROR headers in Authorization header values

&#x20;       **🧐Enhancement** (KBN) user feedback on why login failed

&#x20;       **🐞Fix** (ES) support for multiple header values

&#x20;       **🐞Fix** (ES) releasing LDAP connection pool on reloading ROR settings

&#x20;       **🐞Fix** (KBN) multitenancy issue with 7.6.0+

&#x20;       **🐞Fix** (KBN) creation of default space for new tenant

&#x20;       **🐞Fix** (KBN 6.x) in RO mode, don't hide add/remove over fields in discovery

&#x20;       **🐞Fix** (KBN 6.x) index template & in-index session manager issues ### What's new in 1.19.2

&#x20;       **🚀New** (KBN) 7.6.0 support

&#x20;       **🧐Enhancement** (KBN) less verbose info logging

&#x20;       **🧐Enhancement** (KBN) start up time semantic check for settings

&#x20;       **🐞Fix** (KBN Free) missing logout button

&#x20;       **🐞Fix** (KBN) error message creating internal proxy

&#x20;       **🐞Fix** (KBN 6.x) add field to filter button invisible in RO mode ### What's new in 1.19.1

&#x20;       **\<unknown>** (KBN) [Launched ReadonlyREST Free for Kibana!](https://forum.readonlyrest.com/t/provide-kibana-login-page-for-ror-oss-version/1441/2?u=sscarduzio)

&#x20;       **🚀New** (ES) 7.6.0 support, Kibana support coming soon

&#x20;       **🚀New** (KBN) Audit log dashboard

&#x20;       **🚀New** (KBN) Template index can now be declared per tenant instead of globally

&#x20;       **🚀New** (ES) custom trust store file and password options in ROR settings

&#x20;       **🧐Enhancement** (ES) When "prompt\_for\_basic\_auth" is enabled, ROR is going to return 401 instead of 404 when the index is not found or a user is not allowed to see the index

&#x20;       **🧐Enhancement** (ES) literal ipv6 with zone Id is acceptable network address

&#x20;       **🧐Enhancement** (ES) LDAP client cache improvements

&#x20;       **🐞Fix** (ES) /\_all/\_settings API issue

&#x20;       **🐞Fix** (ES) Index stats API & Index shard stores API issue

&#x20;       **🐞Fix** (ES) readonlyrest.force\_load\_from\_file setting decoding issue

&#x20;       **🐞Fix** (KBN) allowing user to be logged in in two tabs at the same time

&#x20;       **🐞Fix** (KBN) logging with JWT parameter issue

&#x20;       **🐞Fix** (KBN) parsing of sessions fetched from ES index

&#x20;       **🐞Fix** (KBN) logout issue ### What's new in 1.19.0

&#x20;       **🚀New** (KBN) Configurable option to delete docs from tenant index when not present in template

&#x20;       **🧐Enhancement** (ES) Less verbose logging of blocks history

&#x20;       **🧐Enhancement** (ES) Enriched logs and audit with attempted username

&#x20;       **🧐Enhancement** (ES) Better settings validation - only one authentication rule can be used in given block

&#x20;       **🧐Enhancement** (ES/KBN) Plugin versions printing in logs on launch

&#x20;       **🧐Enhancement** (ES) When user doesn't have access to given index, ROR pretends that the index doesn't exist and return 404 instead of 403

&#x20;       **🐞Fix** (ES) Searching for nonexistent/forbidden index with wildcard mirrors default ES behaviour instead of returning 403

&#x20;       **🐞Fix** (KBN) Switching groups bug ### What's new in 1.18.10

&#x20;       **🚀New** (ES/KBN) Support v6.8.6, v7.5.0, v7.5.1

&#x20;       **🚀New** (KBN) Group IDs can now be mapped to aliases

&#x20;       **🚀New** (ES) New, more robust and simple method of creating custom audit log serializers

&#x20;       **🚀New** (ES) Example projects with custom audit log serializers

&#x20;       **\<unknown>** (KBN) Prevent index migration after kibana startup

&#x20;       **🧐Enhancement** (KBN) If default space doesn't exist in kibana index then copy from default one

&#x20;       **🧐Enhancement** (KBN) Crypto improvements - store init vector with encrypted data as base64 encoded json.

&#x20;       **🧐Enhancement** (ES) Better settings validation - prevent duplicated keys in readonlyrest.yml ### What's new in 1.18.9

&#x20;       **🚀New** (ES/KBN) Support v7.4.1, v7.4.2

&#x20;       **🚀New** (KBN) Kibana sessions stored in ES index

&#x20;       **\<unknown>** (ES) issue with in-index settings auto-reloading

&#x20;       **\<unknown>** (ES) \_cat/indices empty response when matched block doesn't contain 'indices' rule ### What's new in 1.18.8

&#x20;       **🚀New** (ES/KBN) Support v7.4.0

&#x20;       **🚀New** (ES) Elasticsearch SQL Support

&#x20;       **🚀New** (ES) Internode ssl support for es5x, es60x, es61x and es62x

&#x20;       **🚀New** (ES) new runtime variable @{acl:current\_group}

&#x20;       **🚀New** (ES) namespace for user variable and support for both versions: @{user} and @{acl:user}

&#x20;       **🚀New** (ES) support for multiple values in uri\_re rule

&#x20;       **🧐Enhancement** (ES) more reliable in-index settings loading of ES with ROR startup

&#x20;       **🧐Enhancement** (ES) less verbose logs in JWT rules

&#x20;       **🧐Enhancement** (ES) Better response from ROR API when plugin is disabled

&#x20;       **🧐Enhancement** (ES) Splitting verification ssl property to client\_authentication and certificate\_verification

&#x20;       **🐞Fix** (ES) issue with backward compatibility of proxy\_auth settings

&#x20;       **🐞Fix** (ES) /\_render/template request NPE

&#x20;       **🐞Fix** (ES) \_cat/indices API bug fixes

&#x20;       **🐞Fix** (ES) \_cat/templates API return empty list instead of FORBIDDEN when no indices are found

&#x20;       **🐞Fix** (ES) updated regex for kibana access rule to support 7.3 ES

&#x20;       **🐞Fix** (ES) proper resolving of non-string ENV variables in readonlyrest.yml

&#x20;       **🐞Fix** (ES) lang-mustache search template handling ### What's new in 1.18.7

&#x20;       **🚀New** (ES) Field level security (FLS) supports nested JSON fields

&#x20;       **🐞Fix** (ES) Authorization headers appeared in clear in logs

&#x20;       **🧐Enhancement** (KBN) Don't logout users when they are not allowed to search a index-pattern

&#x20;       **🧐Enhancement** (ES) Headers obfuscation is now case insensitive ### What's new in 1.18.6

&#x20;       **🚀New** (ES/KBN) Support v7.3.1, v7.3.2

&#x20;       **🚀New** (ES) Configurable header names whose value should be obfuscated in logs

&#x20;       **🚀New** (KBN) Dynamic variables from user identity available in custom\_logout\_link

&#x20;       **🧐Enhancement** (ES) Richer logs for JWT errors

&#x20;       **🧐Enhancement** (ENT) nextUrl works also with SAML now

&#x20;       **🧐Enhancement** (ENT) SAML assertion object available in ACL dynamic variables

&#x20;       **🧐Enhancement** (KBN) Validate LDAP server(s) before accepting new YAML settings

&#x20;       **🧐Enhancement** (KBN) Ensure a read-only UX for 'ro' users in older Kibana

&#x20;       **🐞Fix** (ES) Fix memory leak from dependency (snakeYAML) ### What's new in 1.18.5

&#x20;       **🐞Fix** (ES) indices rule can now properly handle also the templates API

&#x20;       **🧐Enhancement** (ES) Array dynamic variables are serialized as CSV wrapped in double quotes

&#x20;       **🧐Enhancement** (ES) Cleaner debug logs (no stacktraces on forbidden requests)

&#x20;       **🧐Enhancement** (ES) LDAP debug logs fire also when cache is hit

&#x20;       **🚀New** (ES/KBN) Support v7.2.1, v7.3.0

&#x20;       **🐞Fix** (PRO) PRO plugin crashing for some Kibana versions

&#x20;       **🐞Fix** (ENT) SAML library wrote a too large cookie sometimes

&#x20;       **🐞Fix** (ENT) SAML logout not working

&#x20;       **🐞Fix** (ENT) JWT fix exception "cannot set requestHeadersWhitelist"

&#x20;       **🐞Fix** (PRO/ENT) Hide more UI elements for RO users

&#x20;       **🐞Fix** (PRO/ENT) Sometimes not all the available groups appear in tenancy selector

&#x20;       **🐞Fix** (PRO/ENT) Feature "nextUrl" broke

&#x20;       **🐞Fix** (PRO/ENT) prevent user kick-out when APM is not configured and you are not an admin

&#x20;       **🚀New** (PRO/ENT) Kibana request path/method now sent to ES (good for policing dev-tools) ### What's new in 1.18.4

&#x20;       **🚀New** (ES) User impersonation API

&#x20;       **🚀New** (ES) Support latest 6.x and 5.x versions

&#x20;       **🐞Fix** (ES) filter/fields rules leak

&#x20;       **🐞Fix** (KBN/ENT) allow more action for kibana\_access, prevent sudden logout

&#x20;       **🐞Fix** (KBN/ENT) temporarily roll back "support for unlimited tenancies" ### What's new in 1.18.3

&#x20;       **🚀New** (unspecified) Support added for ES/Kibana 6.8.1

&#x20;       **🧐Enhancement** (ES) Crash ES on invalid settings instead of stalling forever

&#x20;       **🧐Enhancement** (ES) Better logging on JWT, JSON-paths, LDAP, YAML errors

&#x20;       **🧐Enhancement** (ES) Block level settings validation to user with precious hints

&#x20;       **🧐Enhancement** (ES) If force\_load\_from\_file: true, don't poll index settings

&#x20;       **🧐Enhancement** (ES) Order now counts declaring LDAP Failover HA servers

&#x20;       **🐞Fix** (ES) "EsIndexJsonContentProvider" had a null pointer exception

&#x20;       **🐞Fix** (ES) "es.set.netty.runtime.available.processors" exception

&#x20;       **🧐Enhancement** (KBN) Collapsible logout button

&#x20;       **🧐Enhancement** (KBN) ROR App now uses a HA http client

&#x20;       **🧐Enhancement** (KBN) Automatic logout for inactivity

&#x20;       **🧐Enhancement** (KBN) Support unlimited amount of tenancies

&#x20;       **🐞Fix** (KBN/ENT) concurrent multitenancy bug

&#x20;       **🐞Fix** (KBN) Avoid sporadic errors on Save/Load buttons ### What's new in 1.18.2

&#x20;       **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.2.0

&#x20;       **🐞Fix** (ES) restore indices ("IDX") in audit logging

&#x20;       **🧐Enhancement** (ES) New algorithm of setting evaluation order

&#x20;       **🚀New** (ES) JWT claims as dynamic variables. I.e. "@{jwt:claim.json.path}"

&#x20;       **🚀New** (ES) "explode" dynamic variables. I.e. indices: \["@explode{x-indices}"]

&#x20;       **🐞Fix** (PRO/Enterprise) preserve comments and formatting in YAML editor

&#x20;       **🐞Fix** (PRO/Enterprise) Print error message when session is expired

&#x20;       **🐞Fix** (PRO/Enterprise) Redirect to original link after login

&#x20;       **🐞Fix** (PRO/Enterprise) Broken CSV reporting

&#x20;       **🧐Enhancement** (PRO/Enterprise) Prevent navigating away from YAML editor w/ unsaved changes

&#x20;       **🐞Fix** (Enterprise) Exception when SAML connectors were all disabled

&#x20;       **🐞Fix** (Enterprise) Concurrent tenants could mix up each other kibana index

&#x20;       **🐞Fix** (Enterprise) Cannot inject custom JS if no custom CSS was also declared

&#x20;       **🐞Fix** (Enterprise) Injected JS had no effect on ROR logout button

&#x20;       **🐞Fix** (Enterprise) On narrow screens, the YAML editor showed buttons twice ### What's new in 1.18.1

&#x20;       **🐞Fix** (Elasticsearch) Reindex requests failed for a regression in indices extraction

&#x20;       **🐞Fix** (Elasticsearch) Groups rule erratically failed

&#x20;       **🐞Fix** (Elasticsearch) JWT claims can now contain special characters

&#x20;       **🧐Enhancement** (Elasticsearch) Better ACL History logging

&#x20;       **🧐Enhancement** (Elasticsearch) QueryLogSerializer and old custom log serializers work again

&#x20;       **🐞Fix** (PRO/Enterprise) ReadonlyREST icon in Kibana was white on white

&#x20;       **🐞Fix** (Enterprise) SAML connectors could not be disabled

&#x20;       **🐞Fix** (Enterprise) SAML connector "buttonName" didn't work ### What's new in 1.18.0

&#x20;       **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.0.1

&#x20;       **🧐Enhancement** (Elasticsearch) empty array values in settings are invalid

&#x20;       **🐞Fix** (Elasticsearch) arbitrary x-cluster search referencing local cluster

&#x20;       **🐞Fix** (Elasticsearch) ArrayOutOfBoundException on snapshot operations

&#x20;       **🧐Enhancement** (PRO/Enterprise) History cleaning can now be disabled ("clearSessionOnEvents") ### What's new in 1.17.7

&#x20;       **🚀New** (unspecified) Support for Elasticsearch 7.0.0 (Kibana is coming soon)

&#x20;       **🧐Enhancement** (Elasticsearch) rewritten LDAP connector

&#x20;       **🧐Enhancement** (Elasticsearch) new core written in Scala is now GA

&#x20;       **🐞Fix** (Enterprise) devtools requests now honor the currently selected tenancy

&#x20;       **🐞Fix** (Enterprise/PRO) Fix "connectorsService" error in installation ### What's new in 1.17.5

&#x20;       **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.1

&#x20;       **🧐Enhancement** (Enterprise >= Kibana 6.6.0) Multiple SAML identity provider

&#x20;       **🐞Fix** (Enterprise/PRO) Don't pass auth headers back to the browser

&#x20;       **🐞Fix** (Enterprise/PRO) Missing null check caused error in reporting (CSV)

&#x20;       **🐞Fix** (Enterprise) Don't reject requests if SAML groups are not configured

&#x20;       **🐞Fix** (unspecified) filter/fields rules not working in msearch (in 6.7.x)

&#x20;       **🧐Enhancement** (unspecified) Print whole LDAP search query in debug log ### What's new in 1.17.4

&#x20;       **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.0

&#x20;       **🧐Enhancement** (PRO/Enterprise) JWT query param is the preferred credentials provider

&#x20;       **🧐Enhancement** (PRO/Enterprise) admin users can use indices management

&#x20;       **🧐Enhancement** (PRO/Enterprise) ro users can dismiss telemetry form

&#x20;       **🐞Fix** (unspecified) Audit logging in 5.1.x now works again

&#x20;       **🐞Fix** (unspecified) unpredictable behaviour of "filter" and "fields" when using external auth

&#x20;       **🐞Fix** (unspecified) LDAP ConcurrentModificationException

&#x20;       **🐞Fix** (unspecified) Audit logging in 5.1.x now works again

&#x20;       **🐞Fix** (PRO/Enterprise) JWT deep-link works again ### What's new in 1.17.3 1.17.2 went unreleased, all changes have been merged in 1.17.3 directly

&#x20;       **🐞Fix** (Enterprise) Tenancy selector showing if user belonged to one group

&#x20;       **🐞Fix** (PRO/Enterprise) RW buttons not hiding for RO users in React Kibana apps

&#x20;       **🐞Fix** (Enterprise) Tenancy templating now works much more reliably

&#x20;       **🐞Fix** (Enterprise) Missing tenancy selector icon after switching tenancy

&#x20;       **🐞Fix** (PRO/Enterprise) barring static files requests caused sudden logout

&#x20;       **🐞Fix** (unspecified) Numerous fixes to better support Kibana 6.6.x

&#x20;       **🐞Fix** (unspecified) Critical fixes in new Scala core

&#x20;       **🐞Fix** (unspecified) Exception in reindex requests caused tenancy templating to fail

&#x20;       **🧐Enhancement** (unspecified) Bypass cross-cluster search logic if single cluster ### What's new in 1.17.1

&#x20;       **🐞Fix** (PRO/Enterprise) SAML now works well in 6.6.x

&#x20;       **🐞Fix** (PRO/Enterprise) "undefined" authentication error before login

&#x20;       **🐞Fix** (Enterprise) Default space creation failures for new tenants

&#x20;       **🐞Fix** (Enterprise) Icons/titles CSS misalignment in sidebar (Firefox)

&#x20;       **🧐Enhancement** (Enterprise) UX: Larger tenancy selector

&#x20;       **🐞Fix** (Enterprise) Privilege escalation when changing tenancies under monitoring

&#x20;       **🐞Fix** (Elasticsearch) compatibility fixes to support new Kibana features

&#x20;       **🧐Enhancement** (Elasticsearch) New core and LDAP connector written in Scala is finished, now under QA. ### What's new in 1.17.0

&#x20;       **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.6.0, 6.6.1

&#x20;       **🚀New** (unspecified) Internode SSL (ES 6.3.x onwards)

&#x20;       **🧐Enhancement** (PRO/Enterprise) UI appearence

&#x20;       **🧐Enhancement** (unspecified) Made HTTP Connection configurable (PR #410)

&#x20;       **🐞Fix** (unspecified) slow boot due to SecureRandom waiting for sufficient entropy

&#x20;       **🐞Fix** (unspecified) Enable kibana\_access:ro to create short urls in es6.3+ (PR #408) ### What's new in 1.16.34

&#x20;       **🧐Enhancement** (unspecified) X-Forwarded-For header in printed es logs ("XFF")

&#x20;       **🧐Enhancement** (unspecified) kibana\_index: ".kibana\_@{user}" when user is "John Doe" becomes .kibana\_john\_doe

&#x20;       **🐞Fix** (Enteprise) parse SAML groups from assertion as array of strings

&#x20;       **🐞Fix** (Enteprise) SAMLRequest in location header was URLEncoded twice, broke on some IdP

&#x20;       **🐞Fix** (PRO/Enteprise) "cookiePass" works again, no more need for sticky cookies in load balancers!

&#x20;       **🐞Fix** (PRO/Enteprise) fix redirect loop with JWT deep linking when JWT token expires

&#x20;       **🧐Enhancement** (PRO/Enteprise) fix audit demo page CSS

&#x20;       **🧐Enhancement** (Enteprise) SAML more configuration parameters available

&#x20;       **🚀New** (PRO/Enteprise) set ROR to debug mode (readonlyrest\_kbn.logLevel: "debug") ### What's new in 1.16.33

&#x20;       **🐞Fix** (PRO/Enteprise) compatibility problems with older Kibana versions

&#x20;       **🐞Fix** (PRO/Enteprise) compatibility problems with OSS Kibana version ### What's new in 1.16.32

&#x20;       **🚀New** (unspecified) "kibanaIndexTemplate": default dashboards and spaces for new tenants

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.4

&#x20;       **🧐Enhancement** (unspecified) Upgraded LDAP library

&#x20;       **🧐Enhancement** (Enterprise) Now tenants save their CSV exports in their own reporting index

&#x20;       **🐞Fix** (PRO/Enteprise) Support passwords that start and/or end with spaces

&#x20;       **🐞Fix** (PRO/Enterprise) Now reporting works again ### What's new in 1.16.31

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.2, 6.5.3

&#x20;       **\<unknown>** (unspecified) : Laid out the foundation for LDAP HA support ### What's new in 1.16.29

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.3

&#x20;       **🚀New** (PRO/Enterprise) configurable server side session duration

&#x20;       **🚀New** (unspecified) \[LDAP] High Availability: Round Robin or Failover ### What's new in 1.16.28

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.2

&#x20;       **🐞Fix** (Enterprise) Multi tenancy: sometimes changing tenancy would not change kibana index

&#x20;       **🐞Fix** (Enterprise/PRO) Avoid echoing Base64 encoded credentials in login form error message

&#x20;       **🧐Enhancement** (Enterprise/PRO) Remove latest search/visualization/dashboard history on logout

&#x20;       **🧐Enhancement** (Enterprise/PRO) Clear transient authentication cookies on login error to avoid authentication deadlocks

&#x20;       **🐞Fix** (unspecified) : External JWT verification may throw ArrayOutOfBoundException

&#x20;       **\<unknown>** (unspecified) : Laid out the foundation for internode SSL transport (port 9300) ### What's new in 1.16.27

&#x20;       **🚀New** (unspecified) \[JWT] external validator: it's now possible to avoid storing the private key in settings

&#x20;       **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.1

&#x20;       **🧐Enhancement** (unspecified) Rewritten big part of ES plugin [documentation](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md)

&#x20;       **🧐Enhancement** (unspecified) SAML Single log out flow

&#x20;       **🐞Fix** (Enterprise/PRO) [cookiePass](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#common-cookie-encryption-secret) works again, but only for Kibana 5.x. Newer Kibana needs sticky sessions in LB.

&#x20;       **🧐Enhancement** (Enterprise/PRO) much faster logout ### What's new in 1.16.26

&#x20;       **🐞Fix** (PRO/Enterprise) bugs during plugin packaging and installation process ### What's new in 1.16.25

&#x20;       **🚀New** (unspecified) Users rule: easily restrict external authentication to a list of users

&#x20;       **🧐Enhancement** (unspecified) Support for ES 5.6.11

&#x20;       **🐞Fix** (Enterprise/PRO) Error 404 when logging in with older versions of Kibana ### What's new in 1.16.24

&#x20;       **🚀New** (Enterprise) SAML Authentication

&#x20;       **🚀New** (unspecified) Support for Elasticsearch and Kibana 6.4.0

&#x20;       **🚀New** (unspecified) Headers rule now split in headers\_or and headers\_and

&#x20;       **🧐Enhancement** (unspecified) Headers rule now allows wildcards

&#x20;       **🚀New** (Enterprise) Multi-tenancy now works also with JSON groups provider

&#x20;       **🐞Fix** (unspecified) Multi-tenancy (Enterprise) incoherent initial kibana\_index and current group ### What's new in 1.16.23

&#x20;       **🧐Enhancement** (unspecified) Support for Elastic Stack 6.3.1 and 5.6.10

&#x20;       **🚀New** (Enterprise) Custom CSS injection for Kibana

&#x20;       **🚀New** (Enterprise) Custom Javascript injection for Kibana

&#x20;       **🚀New** (PRO/Enterprise) access paths without need to login (i.e. /api/status)

&#x20;       **🐞Fix** (PRO/Enterprise) Navigating to X-Pack APM caused hidden Kibana apps to reappear ### What's new in 1.16.22

&#x20;       **🚀New** (unspecified) map LDAP groups to local groups (a.k.a. role mapping)

&#x20;       **🐞Fix** (Elasticsearch) wildcard aliases resolution not working in "indices" rule.

&#x20;       **🧐Enhancement** (unspecified) it is now possible now to use JDK 9 and 10

&#x20;       **🐞Fix** (PRO/Enterprise) wait forever for login request (i.e. slow LDAP servers)

&#x20;       **🐞Fix** (PRO/Enterprise) add spinner and block UI if login request is being sent

&#x20;       **🐞Fix** (PRO/Enterprise) if user is logged out because of LDAP cache expiring + slow authentication, redirect to login.

&#x20;       **🐞Fix** (PRO/Enterprise) let RO users delete/edit search filters ### What's new in 1.16.21

&#x20;       **🚀New** (unspecified) Introducing support for Elasticsearch and Kibana v6.3.0

&#x20;       **🐞Fix** (Enterprise) multi tenancy - switching tenancy does not always switch kibana index ### What's new in 1.16.20 ## ReadonlyREST PRO/Enterprise for Kibana

&#x20;       **🧐Enhancement** (unspecified) : when login, forward "elasticsearch.requestHeadersWhitelist" headers. (useful for "headers" rule and "proxy\_auth" to work well.) ## ReadonlyREST for Elasticsearch

&#x20;       **🚀New** (unspecified) : DLS (with dynamic variables suppoort) Thanks [DataSweet](http://www.datasweet.fr/)!

&#x20;       **🚀New** (unspecified) : Field level security

&#x20;       **🚀New** (unspecified) : Snapshot, Repositories, Headers

&#x20;       **🧐Enhancement** (unspecified) : custom audit serializers: the request content is available

&#x20;       **🐞Fix** (unspecified) readonlyrest.yml path discovery

&#x20;       **🐞Fix** (unspecified) LDAP available groups discovery (tenancy switcher) corner cases

&#x20;       **🐞Fix** (unspecified) : auth\_key\_sha1, auth\_key\_sha256 hashes in settings should be case insensitive

&#x20;       **🐞Fix** (unspecified) : LDAP authentication didn't work with local group

### (2021-01-02) What's new in **ROR 1.26.0**

* **🚨Security Fix** (ES) [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35490) & [CVE-2020-35490](https://nvd.nist.gov/vuln/detail/CVE-2020-35491) (removed Jackson dependency from ROR core)
* **🚀New** (ES) [New response\_fields rule](https://forum.readonlyrest.com/t/ror-1-18-9-enterprise-es-7-2-0-enable-cluster-health-without-authentication/1567)
* **🚀New** (ES) [Support for LDAP server discovery using \_ldaps.\_tcp SRV record](https://forum.readonlyrest.com/t/does-ror-support-dc-locator/1211)
* **🚀New** (ES) [New configuration option allowing to ignore LDAP connectivity problems](https://forum.readonlyrest.com/t/ror-cannot-start-if-ldap-is-not-available/1748)
* **🧐Enhancement** (ES) Full support for ILM API
* **🧐Enhancement** (KBN) Enforce read-after-write consistency between kibana nodes
* **🧐Enhancement** (KBN ENT) OIDC custom claims incorporated in "assertion" claim
* **🧐Enhancement** (KBN ENT) OIDC support for configurable kibanaExternalHost (good for Docker)
* **🧐Enhancement** (KBN ENT) ROR adds "ror-user\_" class to "body" tag for easy per-user CSS/JS
* **🧐Enhancement** (KBN ENT/PRO) ROR adds "ror-group\_" class to "body" tag for easy per-group CSS/JS
* **🐞Fix** (ES) [ROR authentication endpoint action](https://forum.readonlyrest.com/t/es-7-4-2-ror-1-18-9-rradmin-refreshsettings-by-block-default/1388)
* **🐞Fix** (ES) "username" in audit entry when request is rejected ### What's new in 1.25.2
* **🐞Fix** (ES) [removed verbose logging](https://forum.readonlyrest.com/t/elastic-message-cannot-extract-fields-for-query-after-readonlyrest-installation/1749) ### What's new in 1.25.1
* **🚨Security Fix** (ES) [CVE-2020-25649](https://nvd.nist.gov/vuln/detail/CVE-2020-25649)
* **🚀New** (ES) 7.10.1 support ### What's new in 1.25.0
* **🚨Security Fix** (ES) [Common Vulnerabilities and Exposures (CVE)](https://forum.readonlyrest.com/t/update-of-jackson-databind-2-9-6-jar/176)
* **🚀New** (ES) 7.10.0 support
* **🚀New** (ES) [auth\_key\_pbkdf2 rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#auth_key_pbkdf2)
* **🚀New** (ES) [Introduced configuration property defining FLS engine used by fields rule](https://github.com/beshu-tech/readonlyrest-docs/blob/v1.25.x/elasticsearch.md#fields)
* **🧐Enhancement** (ES) Fields rule performance improvement
* **🧐Enhancement** (ES) Resolved index API support
* **🐞Fix** (ES) ["username" in audit entry when user is authenticated via proxy\_auth](https://forum.readonlyrest.com/t/ror-audit-not-logging-user-id)
* **🐞Fix** (ES) index resolve action should be treated as readonly action
* **🐞Fix** (ES) /\_snapshot and /\_snapshot/\_all should behave the same ### What's new in 1.24.0
* **🚨Security Fix** (ES) search template handling fix
* **🚀New** (ES) 7.9.3 & 6.8.13 support
* **🧐Enhancement** (ES) full support for ES Snapshots and Restore APIs
* **🐞Fix** (KBN) fix crash in error handling
* **🐞Fix** (ES) don't remove ES response warning headers
* **🐞Fix** (ES) issue when entropy of /dev/random could have been exhausted when using JwtToken rule ### What's new in 1.23.1
* **🚀New** (ES) 7.9.2 support
* **🐞Fix** (KBN) fix code 500 error on login in Kibana ### What's new in 1.23.0
* **🚀New** (ES) introduced must\_involve\_indices option for indices rule
* **🧐Enhancement** (ES) negation support in headers rules
* **🧐Enhancement** (ES) [x-pack rollup API handling](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)
* **🐞Fix** (KBN) deep links query parameters are now handled
* **🐞Fix** (KBN) make sure default kibana index is always discovered (fixes reporting in 6.x)
* **🐞Fix** (ES) [settings file permission issue with JDK 1.8.0 25.262-b10](https://forum.readonlyrest.com/t/readonlyrest-for-elastic-wont-start-1-18-8-es6-8-1/1652)
* **🐞Fix** (ES) /\_cluster/allocation/explain request should not be forbidden if matched block doesn't have indices rules
* **🐞Fix** (ES) remote address extracting issue
* **🐞Fix** (ES) [fixed TYP audit field for some request types](https://forum.readonlyrest.com/t/match-wrong-index-in-forbid-block/1653/2) ### What's new in 1.22.1
* **🐞Fix** (ES) missing handling of aliases API for ES 7.9.0 ### What's new in 1.22.0
* **🚀New** (ES) 7.9.0 support
* **🧐Enhancement** (ES) aliases API handling
* **🧐Enhancement** (ES) dynamic variables support in fields rule
* **🐞Fix** (ES) [adding aliases issue](https://forum.readonlyrest.com/t/actions-still-forbidden-to-unrestricted-user/1659)
* **🐞Fix** (ES) potential memory leak for ES 7.7.x and above
* **🐞Fix** (ES) cross cluster search issue fix for X-Pack \_async\_search action
* **🐞Fix** (ES) XFF entry in audit issue
* **🐞Fix** (KBN) SAML certificate loading
* **🐞Fix** (KBN) SAML loading groups from assertion
* **🐞Fix** (KBN) fix reporting in pre-7.7.0 ### What's new in 1.21.0
* **🧐Enhancement** (ES) [cluster API support improvements](https://forum.readonlyrest.com/t/settings-problems/1616)
* **🐞Fix** (ES) X-Pack \_async\_search support
* **🐞Fix** (ES) \_rollover request handling
* **🐞Fix** (ES) [handling numeric ssl configuration properties](https://forum.readonlyrest.com/t/numeric-passphrases-invalid-ssl-config/1512)
* **🐞Fix** (KBN) multitenancy+reporting regression fix (for 7.6.x and earlier)
* **🐞Fix** (KBN) "x-" headers should be forwarded in /login route when proxy passthrough is enabled
* **🐞Fix** (unspecified) [(KBN) Logout now redirects to login screen when using proxy](https://forum.readonlyrest.com/t/kibana-ror-1-19-5-issue/1576/24)
* **🐞Fix** (KBN) SAML metadata.xml endpoint not responding
* **🐞Fix** (KBN) NAT/reverse proxy support for SAML
* **🐞Fix** (KBN) SAML login redirect error
* **🐞Fix** (ES) \_readonlyrest/metadata/current\_user should be always allowed by filter/fields rule ### What's new in 1.20.0
* **🚀New** (unspecified) 7.7.1, 7.8.0 support
* **🧐Enhancement** (KBN) tidy up audit page
* **🧐Enhancement** (KBN FREE) clearly inform when features are not available
* **🧐Enhancement** (KBN) ship license report of libraries
* **🧐Enhancement** (ES) filter rule performance improvement
* **🐞Fix** (KBN) proxy\_auth: avoid logout-login loop
* **🐞Fix** (KBN) 404 error on font CSS file
* **🐞Fix** (ES) [wildcard in filter query issue](https://forum.readonlyrest.com/t/wildcard-in-dls-filter-gives-error/1551)
* **🐞Fix** (ES) [forbidden /\_snapshot issue](https://forum.readonlyrest.com/t/get-snapshot-permission-issue/1594)
* **🐞Fix** (ES) /\_mget handling by indices rule when no index from a list is found
* **🐞Fix** (ES) available groups order in metadata response should match the order in which groups appear in ACL
* **🐞Fix** (ES) .readonlyrest and audit index - removed usage of explicit index type
* **🐞Fix** (ES) [tasks leak bug](https://forum.readonlyrest.com/t/lots-of-active-tasks-in-cat-tasks/1593) ### What's new in 1.19.5
* **🚀New** (unspecified) 7.7.0, 7.6.2, 6.8.9, 6.8.8 support
* **🧐Enhancement** (ES/KBN) kibana\_access can be explicitly set to unrestricted
* **🧐Enhancement** (ES) [LDAP connection pool improvement](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)
* **🐞Fix** (ES) [better LDAP request timeout handling](https://forum.readonlyrest.com/t/losing-connections-to-ldap-servers/1485)
* **🐞Fix** (ES) remote indices searching bug
* **🐞Fix** (ES) cross cluster search support for \_field\_caps request
* **🚨Security Fix** (ES) create and delete templates handling
* **🐞Fix** (KBN) Regression in proxy\_auth\_passthrough
* **🧐Enhancement** (KBN) whitelistedPaths now accepts basic auth credentials
* **🧐Enhancement** (KBN) Dump logout button, [new ROR Panel](https://forum.readonlyrest.com/t/new-logout-button-design-new-ror-panel/1476)
* **🧐Enhancement** (KBN) removed ROR from Kibana sidebar. Admins have a link in new panel.
* **🧐Enhancement** (KBN) avoid show login form redirecting from SAML IdP
* **🚀New** (KBN) [OpenID Connect (OIDC) authentication connector](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#openid-connect-oidc)
* **🚀New** (KBN) [login\_title, login\_subtitle enable 2 column login page](https://forum.readonlyrest.com/t/ror-enterprise-show-support-contact-on-login-page/1508/2)
* **🚨Security Fix** (KBN) server-side navigation prevention to hidden apps ### What's new in 1.19.4
* **🐞Fix** (ES) Interpolating config with environment variables in SSL section
* **🐞Fix** (KBN Ent 6.x) Fixed default space creation in
* **🐞Fix** (KBN 6.x) Fixed error toast notification not showing
* **🐞Fix** (KBN Ent) Fixed missing Axios dependency
* **🐞Fix** (KBN Ent) Fixed SAML connector
* **🐞Fix** (KBN) Toast notification overlap with logout bar
* **🧐Enhancement** (KBN) Restyled logout bar
* **🧐Enhancement** (KBN) Configurable periodic session checker ### What's new in 1.19.3
* **🚀New** (ES/KBN) 7.6.1 compatibility
* **🚀New** (ES) customizable name of settings index
* **🧐Enhancement** (KBN) configurable ROR cookie name
* **🧐Enhancement** (ES/KBN) handling of encoded ROR headers in Authorization header values
* **🧐Enhancement** (KBN) user feedback on why login failed
* **🐞Fix** (ES) support for multiple header values
* **🐞Fix** (ES) releasing LDAP connection pool on reloading ROR settings
* **🐞Fix** (KBN) multitenancy issue with 7.6.0+
* **🐞Fix** (KBN) creation of default space for new tenant
* **🐞Fix** (KBN 6.x) in RO mode, don't hide add/remove over fields in discovery
* **🐞Fix** (KBN 6.x) index template & in-index session manager issues ### What's new in 1.19.2
* **🚀New** (KBN) 7.6.0 support
* **🧐Enhancement** (KBN) less verbose info logging
* **🧐Enhancement** (KBN) start up time semantic check for settings
* **🐞Fix** (KBN Free) missing logout button
* **🐞Fix** (KBN) error message creating internal proxy
* **🐞Fix** (KBN 6.x) add field to filter button invisible in RO mode ### What's new in 1.19.1
* **\<unknown>** (KBN) [Launched ReadonlyREST Free for Kibana!](https://forum.readonlyrest.com/t/provide-kibana-login-page-for-ror-oss-version/1441/2?u=sscarduzio)
* **🚀New** (ES) 7.6.0 support, Kibana support coming soon
* **🚀New** (KBN) Audit log dashboard
* **🚀New** (KBN) Template index can now be declared per tenant instead of globally
* **🚀New** (ES) custom trust store file and password options in ROR settings
* **🧐Enhancement** (ES) When "prompt\_for\_basic\_auth" is enabled, ROR is going to return 401 instead of 404 when the index is not found or a user is not allowed to see the index
* **🧐Enhancement** (ES) literal ipv6 with zone Id is acceptable network address
* **🧐Enhancement** (ES) LDAP client cache improvements
* **🐞Fix** (ES) /\_all/\_settings API issue
* **🐞Fix** (ES) Index stats API & Index shard stores API issue
* **🐞Fix** (ES) readonlyrest.force\_load\_from\_file setting decoding issue
* **🐞Fix** (KBN) allowing user to be logged in in two tabs at the same time
* **🐞Fix** (KBN) logging with JWT parameter issue
* **🐞Fix** (KBN) parsing of sessions fetched from ES index
* **🐞Fix** (KBN) logout issue ### What's new in 1.19.0
* **🚀New** (KBN) Configurable option to delete docs from tenant index when not present in template
* **🧐Enhancement** (ES) Less verbose logging of blocks history
* **🧐Enhancement** (ES) Enriched logs and audit with attempted username
* **🧐Enhancement** (ES) Better settings validation - only one authentication rule can be used in given block
* **🧐Enhancement** (ES/KBN) Plugin versions printing in logs on launch
* **🧐Enhancement** (ES) When user doesn't have access to given index, ROR pretends that the index doesn't exist and return 404 instead of 403
* **🐞Fix** (ES) Searching for nonexistent/forbidden index with wildcard mirrors default ES behaviour instead of returning 403
* **🐞Fix** (KBN) Switching groups bug ### What's new in 1.18.10
* **🚀New** (ES/KBN) Support v6.8.6, v7.5.0, v7.5.1
* **🚀New** (KBN) Group IDs can now be mapped to aliases
* **🚀New** (ES) New, more robust and simple method of creating custom audit log serializers
* **🚀New** (ES) Example projects with custom audit log serializers
* **\<unknown>** (KBN) Prevent index migration after kibana startup
* **🧐Enhancement** (KBN) If default space doesn't exist in kibana index then copy from default one
* **🧐Enhancement** (KBN) Crypto improvements - store init vector with encrypted data as base64 encoded json.
* **🧐Enhancement** (ES) Better settings validation - prevent duplicated keys in readonlyrest.yml ### What's new in 1.18.9
* **🚀New** (ES/KBN) Support v7.4.1, v7.4.2
* **🚀New** (KBN) Kibana sessions stored in ES index
* **\<unknown>** (ES) issue with in-index settings auto-reloading
* **\<unknown>** (ES) \_cat/indices empty response when matched block doesn't contain 'indices' rule ### What's new in 1.18.8
* **🚀New** (ES/KBN) Support v7.4.0
* **🚀New** (ES) Elasticsearch SQL Support
* **🚀New** (ES) Internode ssl support for es5x, es60x, es61x and es62x
* **🚀New** (ES) new runtime variable @{acl:current\_group}
* **🚀New** (ES) namespace for user variable and support for both versions: @{user} and @{acl:user}
* **🚀New** (ES) support for multiple values in uri\_re rule
* **🧐Enhancement** (ES) more reliable in-index settings loading of ES with ROR startup
* **🧐Enhancement** (ES) less verbose logs in JWT rules
* **🧐Enhancement** (ES) Better response from ROR API when plugin is disabled
* **🧐Enhancement** (ES) Splitting verification ssl property to client\_authentication and certificate\_verification
* **🐞Fix** (ES) issue with backward compatibility of proxy\_auth settings
* **🐞Fix** (ES) /\_render/template request NPE
* **🐞Fix** (ES) \_cat/indices API bug fixes
* **🐞Fix** (ES) \_cat/templates API return empty list instead of FORBIDDEN when no indices are found
* **🐞Fix** (ES) updated regex for kibana access rule to support 7.3 ES
* **🐞Fix** (ES) proper resolving of non-string ENV variables in readonlyrest.yml
* **🐞Fix** (ES) lang-mustache search template handling ### What's new in 1.18.7
* **🚀New** (ES) Field level security (FLS) supports nested JSON fields
* **🐞Fix** (ES) Authorization headers appeared in clear in logs
* **🧐Enhancement** (KBN) Don't logout users when they are not allowed to search a index-pattern
* **🧐Enhancement** (ES) Headers obfuscation is now case insensitive ### What's new in 1.18.6
* **🚀New** (ES/KBN) Support v7.3.1, v7.3.2
* **🚀New** (ES) Configurable header names whose value should be obfuscated in logs
* **🚀New** (KBN) Dynamic variables from user identity available in custom\_logout\_link
* **🧐Enhancement** (ES) Richer logs for JWT errors
* **🧐Enhancement** (ENT) nextUrl works also with SAML now
* **🧐Enhancement** (ENT) SAML assertion object available in ACL dynamic variables
* **🧐Enhancement** (KBN) Validate LDAP server(s) before accepting new YAML settings
* **🧐Enhancement** (KBN) Ensure a read-only UX for 'ro' users in older Kibana
* **🐞Fix** (ES) Fix memory leak from dependency (snakeYAML) ### What's new in 1.18.5
* **🐞Fix** (ES) indices rule can now properly handle also the templates API
* **🧐Enhancement** (ES) Array dynamic variables are serialized as CSV wrapped in double quotes
* **🧐Enhancement** (ES) Cleaner debug logs (no stacktraces on forbidden requests)
* **🧐Enhancement** (ES) LDAP debug logs fire also when cache is hit
* **🚀New** (ES/KBN) Support v7.2.1, v7.3.0
* **🐞Fix** (PRO) PRO plugin crashing for some Kibana versions
* **🐞Fix** (ENT) SAML library wrote a too large cookie sometimes
* **🐞Fix** (ENT) SAML logout not working
* **🐞Fix** (ENT) JWT fix exception "cannot set requestHeadersWhitelist"
* **🐞Fix** (PRO/ENT) Hide more UI elements for RO users
* **🐞Fix** (PRO/ENT) Sometimes not all the available groups appear in tenancy selector
* **🐞Fix** (PRO/ENT) Feature "nextUrl" broke
* **🐞Fix** (PRO/ENT) prevent user kick-out when APM is not configured and you are not an admin
* **🚀New** (PRO/ENT) Kibana request path/method now sent to ES (good for policing dev-tools) ### What's new in 1.18.4
* **🚀New** (ES) User impersonation API
* **🚀New** (ES) Support latest 6.x and 5.x versions
* **🐞Fix** (ES) filter/fields rules leak
* **🐞Fix** (KBN/ENT) allow more action for kibana\_access, prevent sudden logout
* **🐞Fix** (KBN/ENT) temporarily roll back "support for unlimited tenancies" ### What's new in 1.18.3
* **🚀New** (unspecified) Support added for ES/Kibana 6.8.1
* **🧐Enhancement** (ES) Crash ES on invalid settings instead of stalling forever
* **🧐Enhancement** (ES) Better logging on JWT, JSON-paths, LDAP, YAML errors
* **🧐Enhancement** (ES) Block level settings validation to user with precious hints
* **🧐Enhancement** (ES) If force\_load\_from\_file: true, don't poll index settings
* **🧐Enhancement** (ES) Order now counts declaring LDAP Failover HA servers
* **🐞Fix** (ES) "EsIndexJsonContentProvider" had a null pointer exception
* **🐞Fix** (ES) "es.set.netty.runtime.available.processors" exception
* **🧐Enhancement** (KBN) Collapsible logout button
* **🧐Enhancement** (KBN) ROR App now uses a HA http client
* **🧐Enhancement** (KBN) Automatic logout for inactivity
* **🧐Enhancement** (KBN) Support unlimited amount of tenancies
* **🐞Fix** (KBN/ENT) concurrent multitenancy bug
* **🐞Fix** (KBN) Avoid sporadic errors on Save/Load buttons ### What's new in 1.18.2
* **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.2.0
* **🐞Fix** (ES) restore indices ("IDX") in audit logging
* **🧐Enhancement** (ES) New algorithm of setting evaluation order
* **🚀New** (ES) JWT claims as dynamic variables. I.e. "@{jwt:claim.json.path}"
* **🚀New** (ES) "explode" dynamic variables. I.e. indices: \["@explode{x-indices}"]
* **🐞Fix** (PRO/Enterprise) preserve comments and formatting in YAML editor
* **🐞Fix** (PRO/Enterprise) Print error message when session is expired
* **🐞Fix** (PRO/Enterprise) Redirect to original link after login
* **🐞Fix** (PRO/Enterprise) Broken CSV reporting
* **🧐Enhancement** (PRO/Enterprise) Prevent navigating away from YAML editor w/ unsaved changes
* **🐞Fix** (Enterprise) Exception when SAML connectors were all disabled
* **🐞Fix** (Enterprise) Concurrent tenants could mix up each other kibana index
* **🐞Fix** (Enterprise) Cannot inject custom JS if no custom CSS was also declared
* **🐞Fix** (Enterprise) Injected JS had no effect on ROR logout button
* **🐞Fix** (Enterprise) On narrow screens, the YAML editor showed buttons twice ### What's new in 1.18.1
* **🐞Fix** (Elasticsearch) Reindex requests failed for a regression in indices extraction
* **🐞Fix** (Elasticsearch) Groups rule erratically failed
* **🐞Fix** (Elasticsearch) JWT claims can now contain special characters
* **🧐Enhancement** (Elasticsearch) Better ACL History logging
* **🧐Enhancement** (Elasticsearch) QueryLogSerializer and old custom log serializers work again
* **🐞Fix** (PRO/Enterprise) ReadonlyREST icon in Kibana was white on white
* **🐞Fix** (Enterprise) SAML connectors could not be disabled
* **🐞Fix** (Enterprise) SAML connector "buttonName" didn't work ### What's new in 1.18.0
* **🚀New** (unspecified) Support for Elasticsearch & Kibana 7.0.1
* **🧐Enhancement** (Elasticsearch) empty array values in settings are invalid
* **🐞Fix** (Elasticsearch) arbitrary x-cluster search referencing local cluster
* **🐞Fix** (Elasticsearch) ArrayOutOfBoundException on snapshot operations
* **🧐Enhancement** (PRO/Enterprise) History cleaning can now be disabled ("clearSessionOnEvents") ### What's new in 1.17.7
* **🚀New** (unspecified) Support for Elasticsearch 7.0.0 (Kibana is coming soon)
* **🧐Enhancement** (Elasticsearch) rewritten LDAP connector
* **🧐Enhancement** (Elasticsearch) new core written in Scala is now GA
* **🐞Fix** (Enterprise) devtools requests now honor the currently selected tenancy
* **🐞Fix** (Enterprise/PRO) Fix "connectorsService" error in installation ### What's new in 1.17.5
* **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.1
* **🧐Enhancement** (Enterprise >= Kibana 6.6.0) Multiple SAML identity provider
* **🐞Fix** (Enterprise/PRO) Don't pass auth headers back to the browser
* **🐞Fix** (Enterprise/PRO) Missing null check caused error in reporting (CSV)
* **🐞Fix** (Enterprise) Don't reject requests if SAML groups are not configured
* **🐞Fix** (unspecified) filter/fields rules not working in msearch (in 6.7.x)
* **🧐Enhancement** (unspecified) Print whole LDAP search query in debug log ### What's new in 1.17.4
* **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.7.0
* **🧐Enhancement** (PRO/Enterprise) JWT query param is the preferred credentials provider
* **🧐Enhancement** (PRO/Enterprise) admin users can use indices management
* **🧐Enhancement** (PRO/Enterprise) ro users can dismiss telemetry form
* **🐞Fix** (unspecified) Audit logging in 5.1.x now works again
* **🐞Fix** (unspecified) unpredictable behaviour of "filter" and "fields" when using external auth
* **🐞Fix** (unspecified) LDAP ConcurrentModificationException
* **🐞Fix** (unspecified) Audit logging in 5.1.x now works again
* **🐞Fix** (PRO/Enterprise) JWT deep-link works again ### What's new in 1.17.3 1.17.2 went unreleased, all changes have been merged in 1.17.3 directly
* **🐞Fix** (Enterprise) Tenancy selector showing if user belonged to one group
* **🐞Fix** (PRO/Enterprise) RW buttons not hiding for RO users in React Kibana apps
* **🐞Fix** (Enterprise) Tenancy templating now works much more reliably
* **🐞Fix** (Enterprise) Missing tenancy selector icon after switching tenancy
* **🐞Fix** (PRO/Enterprise) barring static files requests caused sudden logout
* **🐞Fix** (unspecified) Numerous fixes to better support Kibana 6.6.x
* **🐞Fix** (unspecified) Critical fixes in new Scala core
* **🐞Fix** (unspecified) Exception in reindex requests caused tenancy templating to fail
* **🧐Enhancement** (unspecified) Bypass cross-cluster search logic if single cluster ### What's new in 1.17.1
* **🐞Fix** (PRO/Enterprise) SAML now works well in 6.6.x
* **🐞Fix** (PRO/Enterprise) "undefined" authentication error before login
* **🐞Fix** (Enterprise) Default space creation failures for new tenants
* **🐞Fix** (Enterprise) Icons/titles CSS misalignment in sidebar (Firefox)
* **🧐Enhancement** (Enterprise) UX: Larger tenancy selector
* **🐞Fix** (Enterprise) Privilege escalation when changing tenancies under monitoring
* **🐞Fix** (Elasticsearch) compatibility fixes to support new Kibana features
* **🧐Enhancement** (Elasticsearch) New core and LDAP connector written in Scala is finished, now under QA. ### What's new in 1.17.0
* **🚀New** (unspecified) Support for Kibana/Elasticsearch 6.6.0, 6.6.1
* **🚀New** (unspecified) Internode SSL (ES 6.3.x onwards)
* **🧐Enhancement** (PRO/Enterprise) UI appearence
* **🧐Enhancement** (unspecified) Made HTTP Connection configurable (PR #410)
* **🐞Fix** (unspecified) slow boot due to SecureRandom waiting for sufficient entropy
* **🐞Fix** (unspecified) Enable kibana\_access:ro to create short urls in es6.3+ (PR #408) ### What's new in 1.16.34
* **🧐Enhancement** (unspecified) X-Forwarded-For header in printed es logs ("XFF")
* **🧐Enhancement** (unspecified) kibana\_index: ".kibana\_@{user}" when user is "John Doe" becomes .kibana\_john\_doe
* **🐞Fix** (Enteprise) parse SAML groups from assertion as array of strings
* **🐞Fix** (Enteprise) SAMLRequest in location header was URLEncoded twice, broke on some IdP
* **🐞Fix** (PRO/Enteprise) "cookiePass" works again, no more need for sticky cookies in load balancers!
* **🐞Fix** (PRO/Enteprise) fix redirect loop with JWT deep linking when JWT token expires
* **🧐Enhancement** (PRO/Enteprise) fix audit demo page CSS
* **🧐Enhancement** (Enteprise) SAML more configuration parameters available
* **🚀New** (PRO/Enteprise) set ROR to debug mode (readonlyrest\_kbn.logLevel: "debug") ### What's new in 1.16.33
* **🐞Fix** (PRO/Enteprise) compatibility problems with older Kibana versions
* **🐞Fix** (PRO/Enteprise) compatibility problems with OSS Kibana version ### What's new in 1.16.32
* **🚀New** (unspecified) "kibanaIndexTemplate": default dashboards and spaces for new tenants
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.4
* **🧐Enhancement** (unspecified) Upgraded LDAP library
* **🧐Enhancement** (Enterprise) Now tenants save their CSV exports in their own reporting index
* **🐞Fix** (PRO/Enteprise) Support passwords that start and/or end with spaces
* **🐞Fix** (PRO/Enterprise) Now reporting works again ### What's new in 1.16.31
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.5.2, 6.5.3
* **\<unknown>** (unspecified) : Laid out the foundation for LDAP HA support ### What's new in 1.16.29
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.3
* **🚀New** (PRO/Enterprise) configurable server side session duration
* **🚀New** (unspecified) \[LDAP] High Availability: Round Robin or Failover ### What's new in 1.16.28
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.2
* **🐞Fix** (Enterprise) Multi tenancy: sometimes changing tenancy would not change kibana index
* **🐞Fix** (Enterprise/PRO) Avoid echoing Base64 encoded credentials in login form error message
* **🧐Enhancement** (Enterprise/PRO) Remove latest search/visualization/dashboard history on logout
* **🧐Enhancement** (Enterprise/PRO) Clear transient authentication cookies on login error to avoid authentication deadlocks
* **🐞Fix** (unspecified) : External JWT verification may throw ArrayOutOfBoundException
* **\<unknown>** (unspecified) : Laid out the foundation for internode SSL transport (port 9300) ### What's new in 1.16.27
* **🚀New** (unspecified) \[JWT] external validator: it's now possible to avoid storing the private key in settings
* **🧐Enhancement** (unspecified) Support for ES/Kibana 6.4.1
* **🧐Enhancement** (unspecified) Rewritten big part of ES plugin [documentation](https://github.com/beshu-tech/readonlyrest-docs/blob/master/elasticsearch.md)
* **🧐Enhancement** (unspecified) SAML Single log out flow
* **🐞Fix** (Enterprise/PRO) [cookiePass](https://github.com/beshu-tech/readonlyrest-docs/blob/master/kibana.md#common-cookie-encryption-secret) works again, but only for Kibana 5.x. Newer Kibana needs sticky sessions in LB.
* **🧐Enhancement** (Enterprise/PRO) much faster logout ### What's new in 1.16.26
* **🐞Fix** (PRO/Enterprise) bugs during plugin packaging and installation process ### What's new in 1.16.25
* **🚀New** (unspecified) Users rule: easily restrict external authentication to a list of users
* **🧐Enhancement** (unspecified) Support for ES 5.6.11
* **🐞Fix** (Enterprise/PRO) Error 404 when logging in with older versions of Kibana ### What's new in 1.16.24
* **🚀New** (Enterprise) SAML Authentication
* **🚀New** (unspecified) Support for Elasticsearch and Kibana 6.4.0
* **🚀New** (unspecified) Headers rule now split in headers\_or and headers\_and
* **🧐Enhancement** (unspecified) Headers rule now allows wildcards
* **🚀New** (Enterprise) Multi-tenancy now works also with JSON groups provider
* **🐞Fix** (unspecified) Multi-tenancy (Enterprise) incoherent initial kibana\_index and current group ### What's new in 1.16.23
* **🧐Enhancement** (unspecified) Support for Elastic Stack 6.3.1 and 5.6.10
* **🚀New** (Enterprise) Custom CSS injection for Kibana
* **🚀New** (Enterprise) Custom Javascript injection for Kibana
* **🚀New** (PRO/Enterprise) access paths without need to login (i.e. /api/status)
* **🐞Fix** (PRO/Enterprise) Navigating to X-Pack APM caused hidden Kibana apps to reappear ### What's new in 1.16.22
* **🚀New** (unspecified) map LDAP groups to local groups (a.k.a. role mapping)
* **🐞Fix** (Elasticsearch) wildcard aliases resolution not working in "indices" rule.
* **🧐Enhancement** (unspecified) it is now possible now to use JDK 9 and 10
* **🐞Fix** (PRO/Enterprise) wait forever for login request (i.e. slow LDAP servers)
* **🐞Fix** (PRO/Enterprise) add spinner and block UI if login request is being sent
* **🐞Fix** (PRO/Enterprise) if user is logged out because of LDAP cache expiring + slow authentication, redirect to login.
* **🐞Fix** (PRO/Enterprise) let RO users delete/edit search filters ### What's new in 1.16.21
* **🚀New** (unspecified) Introducing support for Elasticsearch and Kibana v6.3.0
* **🐞Fix** (Enterprise) multi tenancy - switching tenancy does not always switch kibana index ### What's new in 1.16.20 ## ReadonlyREST PRO/Enterprise for Kibana
* **🧐Enhancement** (unspecified) : when login, forward "elasticsearch.requestHeadersWhitelist" headers. (useful for "headers" rule and "proxy\_auth" to work well.) ## ReadonlyREST for Elasticsearch
* **🚀New** (unspecified) : DLS (with dynamic variables suppoort) Thanks [DataSweet](http://www.datasweet.fr/)!
* **🚀New** (unspecified) : Field level security
* **🚀New** (unspecified) : Snapshot, Repositories, Headers
* **🧐Enhancement** (unspecified) : custom audit serializers: the request content is available
* **🐞Fix** (unspecified) readonlyrest.yml path discovery
* **🐞Fix** (unspecified) LDAP available groups discovery (tenancy switcher) corner cases
* **🐞Fix** (unspecified) : auth\_key\_sha1, auth\_key\_sha256 hashes in settings should be case insensitive
* **🐞Fix** (unspecified) : LDAP authentication didn't work with local group


