For Elasticsearch
Last updated
Was this helpful?
Last updated
Was this helpful?
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) and lays the technological foundations for the companion Kibana plugin which is released in two versions: ReadonlyREST PRO and ReadonlyREST 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 instead.
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).
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.
The User Agent (i.e. cURL, Kibana) sends a search request to Elasticsearch using port 9200 and the HTTPS URL schema.
The HTTPS filter in the ReadonlyREST plugin unwraps the SSL layer and hands over the request to the Elasticsearch HTTP stack
The HTTP stack in Elasticsearch parses the HTTP request
The HTTP handler in Elasticsearch extracts the indices, action, request type, and creates a SearchRequest
(internal Elasticsearch format).
The SearchRequest goes through the ACL (access control list), external systems like LDAP can be asynchronously queried, and an exit result is eventually produced.
The exit result is used by the audit event serializer, to write a record to index and/or Elasticsearch log file
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.
In case the ACL matches a type: allow
block, the request is forwarded to the search engine
The Elasticsearch code creates a search response containing the results of the query
The search response is converted to an HTTP response by the Elasticsearch code
The HTTP response flows back to ReadonlyREST's HTTPS filter and to the User agent
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:
OR with Docker Compose:
(To run the docker-compose.yml call docker compose up
)
Any of these methods, runs Elasticsearch container with ReadonlyREST with init settings.
When the service is started you can test it using curl or Postman:
You can create locally customized readonlyrest.yml
file and mount it as a docker volume. Assuming that your ROR settings file is located in /tmp/my-readonlyrest.yml
you can use it like that:
OR
To install the ReadonlyREST plugin for Elasticsearch:
From the official download page. Select your Elasticsearch version and send yourself a link to the compatible ReadonlyREST zip file.
Notice how we need to type in the format file://
+ absolute path (yes, with three slashes).
When prompted about additional permissions, answer y.
If you are using Elasticsearch 6.5.x or newer, you need an extra post-installation step. Depending on the Elasticsearch version, this command might tweak the main Elasticsearch installation files and/or copy some jars to plugins/readonlyrest
directory.
⚠️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
:
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:
or
NB: In case of any problems with the ror-tools
, please call:
Create and edit the readonlyrest.yml
settings file in the same directory where elasticsearch.yml
is found:
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.
(applies to ES 6.4.0 or greater)
ReadonlyREST and X-Pack security module can't run together, so the latter needs to be disabled.
Edit elasticsearch.yml
and append xpack.security.enabled: false
.
or:
Depending on your environment.
Now you should be able to see the logs and ReadonlyREST-related lines like the one below:
The following command should succeed, and the response should show a status code 200.
The following command should not succeed, and the response should show a status code 401
To upgrade ReadonlyREST for Elasticsearch:
Either kill the process manually, or use:
depending on your environment.
If you are using Elasticsearch 6.5.x or newer, you need an extra pre-uninstallation step. This will remove all previously copied jars from ROR's installation directory.
You can verify if Elasticsearch was correctly unpatched using the command verify
:
NB: In case of any problems with the ror-tools
, please call:
e.g.
If you are using Elasticsearch 6.5.x or newer, you need an extra post-installation step. Depending on the Elasticsearch version, this command might tweak the main Elasticsearch installation files and/or copy some jars to the plugins/readonlyrest
directory.
⚠️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
:
NB: In case of any problems with the ror-tools
, please call:
or:
Depending on your environment.
Now you should be able to see the logs and ReadonlyREST-related lines like the one below:
Either kill the process manually, or use:
depending on your environment.
If you are using Elasticsearch 6.5.x or newer, you need an extra pre-uninstallation step. This will remove all previously copied jars from ROR's installation directory.
You can verify if Elasticsearch was correctly unpatched using the command verify
:
NB: In case of any problems with the ror-tools
, please call:
or:
Depending on your environment.
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:
(Optional) disable the HTTP interface from all the existing nodes
Create a new, lightweight, dedicated node without shards, nor master eligibility.
Configure ReadonlyREST with SSL encryption in the new node
Configure Logstash to connect to the new node directly in HTTPS.
⚠️IMPORTANT By default when the fields
rule is used, it's required to install the ReadonlyREST plugin in all the data nodes.
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.
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 or 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.
An SSL-encrypted connection is a prerequisite for secure exchange of credentials and data over the network. To make use of it you need to have certificate and private key. Letsencrypt certificates work just fine (see tutorial below). Before ReadonlyREST 1.44.0 both files, certificate and private key, had to be placed inside PKCS#12 or JKS keystore. See the tutorial at the end of this section. ReadonlyREST 1.44.0 or newer supports using PEM files directly, without the need to use a keystore.
ReadonlyREST can be configured to encrypt network traffic on two independent levels:
HTTP (port 9200)
Internode communication - transport module (port 9300)
An Elasticsearch node with ReadonlyREST can join an existing cluster based on native SSL from
xpack.security
module. This configuration is useful to deploy ReadonlyREST Enterprise for Kibana to an existing large production cluster without disrupting any configuration. More on this in the dedicated paragraph of this section.
It wraps connection between client and exposed REST API in SSL context, hence making it encrypted and secure. ⚠️IMPORTANT: To enable SSL for REST API, open elasticsearch.yml
and append this one line:
Now in readonlyrest.yml
add the following settings:
The keystore should be stored in the same directory with elasticsearch.yml
and readonlyrest.yml
.
This option encrypts communication between nodes forming Elasticsearch cluster.
⚠️IMPORTANT: To enable SSL for internode communication open elasticsearch.yml
and append this one line:
In readonlyrest.yml
following settings must be added (it's just example configuration presenting most important properties):
Similar to ssl
for HTTP, the keystore should be stored in the same directory with elasticsearch.yml
and readonlyrest.yml
. This config must be added to all nodes taking part in encrypted communication within cluster.
Internode communication with XPack nodes
It is possible to set up internode SSL between ROR and XPack nodes. It works only for ES above 6.3.
To set up cluster in such configuration you have to generate certificate for ROR node according to this description https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html#generate-certificates.
Generated elastic-certificates.p12
could be then used in ROR node with such configuration
By default the certificate verification is disabled. It means that certificate is not validated in any way, so all certificates are accepted. It is useful on local/test environment, where security is not the most important concern. On production environment it is advised to enable this option. It can be done by means of:
under ssl_internode
section. This option is applicable only for internode SSL.
By default the hostname verification is disabled. This means that hostname or IP address is not verified to match the names in the certificate. To enable hostname verification add the following lines in the ssl_internode
section:
By default the client authentication is disabled. When enabled, the server asks the client about its certificate, so ES is able to verify the client's identity. It can be enabled by means of:
under ssl
section. This option is applicable for REST API external SSL and internode SSL.
Optionally, it's possible to specify a list allowed SSL protocols and SSL ciphers. Connections from clients that don't support the listed protocols or ciphers will be dropped.
ReadonlyREST will log a list of available ciphers and protocols supported by the current JVM at startup.
ReadonlyREST allows using custom truststore, replacing (provided by JRE) default one. Custom truststore can be set with:
under ssl
or ssl_internode
section. This option is applicable for both ssl modes - external ssl and internode ssl. The truststore should be stored in the same directory with elasticsearch.yml
and readonlyrest.yml
(like keystore). When not specified, ReadonlyREST uses default truststore.
If you are using ReadonlyREST 1.44.0 or newer then you are able to use PEM files directly without the need of placing them inside a keystore or truststore.
To use PEM files instead of keystore file, use such configuration instead of keystore_file
, keystore_pass
, key_pass
fields:
To use PEM file instead of truststore file, use such configuration instead of truststore_file
, truststore_pass
fields:
We are going to show how to first add all the certificates and private key into PKCS#12 keystore, and then (optionally) converting it to JKS keystore. ReadonlyREST supports both formats.
⚠️IMPORTANT: if you are using ReadonlyREST in version above 1.44.0 then you don't have to create a keystore. You are able to use PEM files directly using the description above.
This tutorial can be a useful example on how to use certificates from other providers.
1. Create keys
Now change to the directory (probably /etc/letsencrypt/live/DOMAIN.tld) where the certificates were created.
2. Create a PKCS12 keystore with the full chain and private key
3. Convert PKCS12 to JKS Keystore (Optional)
The STORE_PASS is the password which was entered in step 2) as a password for the pkcs12 file.
If you happen to get a java.io.IOException: failed to decrypt safe contents entry: javax.crypto.BadPaddingException: Given final block not properly padded
, you have probably forgotten to enter the correct password from step 2.
(Credits for the original JKS tutorial to Maximilian Boehm)
Each incoming request to the Elasticsearch node passes to the installed plugin. During Elasticsearch node startup, the plugin rejects incoming requests until it starts. The plugin rejects such requests with the 403
forbidden responses by default. To overwrite this behavior, append to elasticsearch.yml
readonlyrest.not_started_response_code
- HTTP code returned when the plugin does not start yet. Possible values are 403
(default) and 503
.
readonlyrest.failed_to_start_response_code
- HTTP code returned when the plugin failed to start (e.g. by malformed ACL). Possible values are 403
(default) and 503
.
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
.
Example: the simplest example of an allow block.
⚠️IMPORTANT: if no blocks are configured, ReadonlyREST rejects all requests.
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.
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. 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 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. The value is a string like username:password
hashed in 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 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. The value is hashed in the same way as it's done in auth_key_sha512
rule, but it uses 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:
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 (notice that the salt has to base Base64 encoded).
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
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):
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 is supported by this rule without an extra configuration.
token_authentication
The authentication rule that accepts token sent in the HTTP header (the Authorization
header is the default if no custom header name is configured in the token_authentication.header
field).
For the HTTP header in the format Authorization: Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2Vu
, the Bearer AAEAAWVsYXN0aWMva2liYW5hL3Rva2Vu
is the token value set in the token
field.
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 is supported by this rule without an extra configuration.
groups_or
(or groups
)groups_or: ["group1", "group2"]
The ACL block will match when the user belongs to any of the specified groups. The information about what users belong to what groups is defined in the users
section, typically situated after the ACL, further down in the YAML.
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" 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:
For details see User management.
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
groups_and
groups_and: ["group1", "group2"]
This rule is identical to the above defined groups
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 groups
rule.
ldap_authentication
simple version: ldap_authentication: ldap1
extended version:
It handles LDAP authentication only using the configured LDAP connector (here ldap1
). Check the LDAP connector section to see how to configure the connector.
ldap_authorization
It handles LDAP authorization only using the configured LDAP connector (here ldap1
). It matches when previously authenticated user has groups in LDAP and when he belongs to at least one of the configured groups
(OR logic). Alternatively, groups_and
can be used to require users belong to all the listed groups (AND logic). Check the LDAP connector section 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
).
The same functionality can be achieved using the two rules described below:
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:
Or equivalently:
See the dedicated LDAP section
Impersonation support by LDAP rules requires to add an extra configuration.
For more information on the ROR's authorization rules, see Authorization rules details
jwt_auth
See below, the dedicated JSON Web Tokens section. It's an authentication and authorization rule at the same time.
Impersonation is not currently supported by this rule.
external_authentication
Used to delegate authentication to another server that supports HTTP Basic Auth. See below, the dedicated External BASIC Auth section
Impersonation support by this rule requires to add an extra configuration.
For more information on the ROR's authorization rules, see Authorization rules details
groups_provider_authorization
Used to delegate groups resolution for a user to a JSON microservice. See below, the dedicated Groups Provider Authorization section
Impersonation support by this rule requires to add an extra configuration.
For more information on the ROR's authorization rules, see Authorization rules details
ror_kbn_auth
(Enterprise)For Enterprise customers only, required for SAML authentication. From ROR's perspective it authenticates and authorize users.
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
Impersonation is currently not supported by this rule.
For more information on the ROR's authorization rules, see Authorization rules details
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:
kibana
The kibana
rule underpins all ROR Kibana-related settings that may be needed to provide great user experience.
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
), 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 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)
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.
⚠️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:
template_index
(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.
hide_apps
(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.
allowed_api_paths
Used to define which parts of Kibana REST API can be used. The sub-rule requires to define a list of regular expressions 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:
metadata
(Enterprise)
User to define the Custom ROR Kibana Metadata which can be used in Custom middleware. The kibana.metadata
in ReadonlyREST settings is an unstructured YAML object.
It supports dynamic variables.
Sample usage:
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.
kibana_access
kibana_access: ro
⚠️Deprecated: it's equivalent of kibana.access
. Should no longer be used.
kibana_index
(Enterprise)kibana_index: .kibana-user1
⚠️Deprecated: it's equivalent of kibana.index
. Should no longer be used.
kibana_template_index
(Enterprise)kibana_template_index: .kibana_template
⚠️Deprecated: it's equivalent of kibana.template_index
. Should no longer be used.
kibana_hide_apps
(PRO)kibana_hide_apps: [ "Security", "Enterprise Search"]
⚠️Deprecated: it's equivalent of kibana.hide_apps
. Should no longer be used.
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:
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.
It's also worth mentioning, that when 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. 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.
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.
Example actions (see above for the full list):
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"
.
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).
⚠️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.
⚠️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.
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
⚠️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.
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.
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).
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.
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:
After adding the filter
rule (using the block duplication strategy).
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:
You can write this fields
rule containing a pattern that uses the *
right after the ~
:
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:
but after configuring such rule: