Kibana API ACLs


We are using Kibana and ES 7.12.1 with ROR Enterprise 1.39.0. We are trying to use Kibana APIs for different actions, and we found a strange behavior.
We defined a basic auth user in ROR config and the following ACL blocks for it:

  - name: "testapi Kibana"
    groups: ["testapi"]
    indices: [".kibana_testapi", "metric*"]
    kibana_access: "ro_strict"
    kibana_index: ".kibana_testapi"
  - name: "testapi 0"
    groups: ["testapi"]
    indices: ["metric*"]
    actions: ["indices:data/read/*"]

We are doing basic GETs for the Kibana API, for example:

    "statusCode": 404,
    "error": "Not Found",
    "message": "Saved object [index-pattern/metricbeat-*] not found"

Which is normal, because there is no such index pattern on the .kibana_testapi tenancy.

What is strange:
When doing a POST such as importing objects:
-XPOST "" --form file=@full-03-03-2020.ndjson
the result is successful, but all the objects are imported in the .kibana tenant.
Also from the log file:

[2022-08-24T09:40:06,792][INFO ][tech.beshu.ror.accesscontrol.logging.AccessControlLoggingDecorator] ALLOWED by { name: 'testapi Kibana', policy: ALLOW, rules: [groups,kibana_index,kibana_access,indices] req={ ID:2046087262-440317826#18639, TYP:RRUserMetadataRequest, CGR:N/A, USR:M2M000, BRS:true, KDX:.kibana_testapi, ACT:cluster:ror/user_metadata/get, OA:, XFF:null, DA:, IDX:<N/A>, MET:GET, PTH:/_readonlyrest/metadata/current_user, CNT:<N/A>, HDR:Accept-Encoding=gzip,deflate, Accept=*/*, Authorization=<OMITTED>, Connection=close, Host=, User-Agent=node-fetch/1.0 (+, content-length=0, HIS:[Full Admin Kibana-> RULES:[groups->false]], [Full Admin Users-> RULES:[groups->false]], [Client Admin Group Kibana-> RULES:[groups->false]], [Client Admin Group 0-> RULES:[groups->false]], [testapi Kibana-> RULES:[groups->true, kibana_index->true, kibana_access->true, indices->true] RESOLVED:[user=M2M0000;group=testapi;av_groups=testapi;kibana_idx=.kibana_testapi]], [testapi 0-> RULES:[groups->true, actions->false] RESOLVED:[user=M2M0000;group=testapi;av_groups=testapi]], }

So, i conclude that this user is able to read from the correct tenant (.kibana_testapi) and is able to write to another tenant (.kibana). Do you have any suggestion for this issue?

Also, is there a way to restrict users’ actions on the Kibana API from the ROR config? e.g not being able to import dashboards, create index patterns etc.


Hello, thanks for your message. I will analyze this topic as soon as possible and will try to find a solution.


Did you have the time to look into this topic?


Sorry for the no response. I can confirm that we found strange behavior in the case of the /saved_objects kibana API call (in the case of saved objects directly via kibana panel everything works as expected). We are looking at the root of this issue now and trying to find the proper solution.

1 Like

Finally, we found what is the actual issue and now we work on fixing it. About

Also, is there a way to restrict users’ actions on the Kibana API from the ROR config? e.g not being able to import dashboards, create index patterns etc.

There is no way to achieve it, but I discussed this internally with the team. The conclusion is, that we have the infrastructure to implement this feature, but we need to find whether is it possible to differentiate Kibana API requests from the browser requests.

1 Like

Let me expand the idea behind this is: we could easily add a kibana_api_acl Kibana API level access control list and allow/reject a series of endpoints like:

POST /api/alerting/rule/<id>/_disable
POST /s/<space_id>/api/alerting/rule/<id>/_disable
POST /api/alerting/rule/<rule_id>/alert/<alert_id>/_mute
POST /s/<space_id>/api/alerting/rule/<rule_id>/alert/<alert_id>/_mute
GET /api/alerting/rules/_find
GET /s/<space_id>/api/alerting/rules/_find
DELETE /api/alerting/rule/<id>
DELETE /s/<space_id>/api/alerting/rule/<id>
POST /api/alerting/rule/<id>
POST /s/<space_id>/api/alerting/rule/<id>
POST /api/alerting/rule/<id>/_unmute_all
POST /s/<space_id>/api/alerting/rule/<id>/_unmute_all
POST /api/alerting/rule/<id>/_enable
POST /s/<space_id>/api/alerting/rule/<id>/_enable
POST /api/alerts/alert/<id>/alert_instance/<alert_instance_id>/_unmute
POST /s/<space_id>/api/alerts/alert/<id>/alert_instance/<alert_instance_id>/_unmute
DELETE /api/alerts/alert/<id>
DELETE /s/<space_id>/api/alerts/alert/<id>
POST /api/alerts/alert/<id>/_disable
POST /s/<space_id>/api/alerts/alert/<id>/_disable
POST /api/alerts/alert/<id>
POST /s/<space_id>/api/alerts/alert/<id>
PUT /api/alerts/alert/<id>
PUT /s/<space_id>/api/alerts/alert/<id>
POST /api/alerts/alert/<id>/_enable
POST /s/<space_id>/api/alerts/alert/<id>/_enable
POST /api/alerts/alert/<id>/_mute_all
POST /s/<space_id>/api/alerts/alert/<id>/_mute_all
GET /api/alerts/alert/<id>
GET /s/<space_id>/api/alerts/alert/<id>
POST /api/alerts/alert/<id>/_unmute_all
POST /s/<space_id>/api/alerts/alert/<id>/_unmute_all
GET /api/alerts/list_alert_types
GET /s/<space_id>/api/alerts/list_alert_types
GET /api/alerts/_health
GET /s/<space_id>/api/alerts/_health
GET /api/alerts/_find
GET /s/<space_id>/api/alerts/_find
POST /api/alerts/alert/<id>/alert_instance/<alert_instance_id>/_mute
POST /s/<space_id>/api/alerts/alert/<id>/alert_instance/<alert_instance_id>/_mute
POST /api/alerting/rule/<rule_id>/alert/<alert_id>/_unmute
POST /s/<space_id>/api/alerting/rule/<rule_id>/alert/<alert_id>/_unmute
POST /api/alerting/rule/<id>/_mute_all
POST /s/<space_id>/api/alerting/rule/<id>/_mute_all
GET /api/alerting/rule_types
GET /s/<space_id>/api/alerting/rule_types
PUT /api/alerting/rule/<id>
PUT /s/<space_id>/api/alerting/rule/<id>
GET /api/alerting/_health
GET /s/<space_id>/api/alerting/_health
GET /api/alerting/rule/<id>
GET /s/<space_id>/api/alerting/rule/<id>
GET /api/upgrade_assistant/status
POST /api/upgrade_assistant/reindex/myIndex/cancel
POST /api/upgrade_assistant/reindex/myIndex
GET /api/upgrade_assistant/reindex/myIndex
DELETE /api/actions/connector/<id>
DELETE /s/<space_id>/api/actions/connector/<id>
POST /api/actions/connector
POST /s/<space_id>/api/actions/connector
PUT /api/actions/connector/<id>
PUT /s/<space_id>/api/actions/connector/<id>
DELETE /api/actions/action/<id>
DELETE /s/<space_id>/api/actions/action/<id>
POST /api/actions/action
POST /s/<space_id>/api/actions/action
PUT /api/actions/action/<id>
PUT /s/<space_id>/api/actions/action/<id>
GET /api/actions/action/<id>
GET /s/<space_id>/api/actions/action/<id>
GET /api/actions/list_action_types
GET /s/<space_id>/api/actions/list_action_types
GET /api/actions
GET /s/<space_id>/api/actions
POST /api/actions/action/<id>/_execute
POST /s/<space_id>/api/actions/action/<id>/_execute
GET /api/actions/connector/<id>
GET /s/<space_id>/api/actions/connector/<id>
GET /api/actions/connector_types
GET /s/<space_id>/api/actions/connector_types
GET /api/actions/connectors
GET /s/<space_id>/api/actions/connectors
POST /api/actions/connector/<id>/_execute
POST /s/<space_id>/api/actions/connector/<id>/_execute
PUT /api/logstash/pipeline/<id>
GET /api/logstash/pipeline/<id>
GET /api/logstash/pipelines
DELETE /api/logstash/pipeline/<id>
GET /api/task_manager/_health
DELETE /api/index_patterns/index_pattern/<id>
DELETE /s/<space_id>/api/index_patterns/index_pattern/<id>
POST /api/index_patterns/index_pattern/<id>/fields
POST /s/<space_id>/api/index_patterns/index_pattern/<id>/fields
POST /api/index_patterns/default
POST /s/<space_id>/api/index_patterns/default
POST /api/index_patterns/index_pattern
POST /s/<space_id>/api/index_patterns/index_pattern
POST /api/index_patterns/index_pattern/<id>
POST /s/<space_id>/api/index_patterns/index_pattern/<id>
GET /api/index_patterns/index_pattern/<id>
GET /s/<space_id>/api/index_patterns/index_pattern/<id>
GET /api/index_patterns/default
GET /s/<space_id>/api/index_patterns/default
DELETE /api/index_patterns/index_pattern/<index_pattern_id>/runtime_field/<name>
DELETE /s/<space_id>/api/index_patterns/index_pattern/<index_pattern_id>/runtime_field/<name>
POST /api/index_patterns/index_pattern/<index_pattern_id>/runtime_field
POST /s/<space_id>/api/index_patterns/index_pattern/<index_pattern_id>/runtime_field
POST /api/index_patterns/index_pattern/<index_pattern_id>/runtime_field/<name>
POST /s/<space_id>/api/index_patterns/index_pattern/<index_pattern_id>/runtime_field/<name>
GET /api/index_patterns/index_pattern/<index_pattern_id>/runtime_field/<name>
GET /s/<space_id>/api/index_patterns/index_pattern/<index_pattern_id>/runtime_field/<name>
PUT /api/index_patterns/index_pattern/<index_pattern_id>/runtime_field
PUT /s/<space_id>/api/index_patterns/index_pattern/<index_pattern_id>/runtime_field
DELETE /api/saved_objects/<type>/<id>
DELETE /s/<space_id>/api/saved_objects/<type>/<id>
POST /api/saved_objects/_import
POST /s/<space_id>/api/saved_objects/_import
POST /api/saved_objects/<type>
POST /api/saved_objects/<type>/<id>
POST /s/<space_id>/api/saved_objects/<type>
POST /s/<space_id>/api/saved_objects/<type>/<id>
PUT /api/saved_objects/<type>/<id>
PUT /s/<space_id>/api/saved_objects/<type>/<id>
POST /api/saved_objects/_bulk_create
POST /s/<space_id>/api/saved_objects/_bulk_create
GET /api/saved_objects/<type>/<id>
GET /s/<space_id>/api/saved_objects/<type>/<id>
GET /api/saved_objects/resolve/<type>/<id>
GET /s/<space_id>/api/saved_objects/resolve/<type>/<id>
POST /api/saved_objects/_bulk_get
POST /s/<space_id>/api/saved_objects/_bulk_get
POST /api/saved_objects/_export
POST /s/<space_id>/api/saved_objects/_export
POST /api/saved_objects/_resolve_import_errors
POST /s/<space_id>/api/saved_objects/_resolve_import_errors
GET /api/saved_objects/_find
GET /s/<space_id>/api/saved_objects/_find
POST /api/encrypted_saved_objects/_rotate_key
POST /api/kibana/dashboards/import
POST /s/<space-id>/api/kibana/dashboards/import
GET /api/kibana/dashboards/export
GET /s/<space-id>/api/kibana/dashboards/export
GET /api/features
DELETE /api/security/role/my_admin_role
PUT /api/security/role/my_kibana_role
GET /api/security/role
GET /api/security/role/my_restricted_kibana_role
POST /api/shorten_url
DELETE /api/spaces/space/marketing
PUT /api/spaces/space/<space_id>
POST /api/spaces/space
POST /api/spaces/_resolve_copy_saved_objects_errors
POST /s/<space_id>/api/spaces/_resolve_copy_saved_objects_errors
GET /api/spaces/space/marketing
POST /api/spaces/_copy_saved_objects
POST /s/<space_id>/api/spaces/_copy_saved_objects
GET /api/spaces/space
POST /api/security/session/_invalidate

The issue is that the average Kibana browser session touches a ton of those endpoints, and the list varies every Kibana version.
If the average ROR admin starts blocking/allowing even a few routes, they can easily break the Kibana UX, and the whole solution would be perceived as brittle, unreliable and unpredictable by final users.

Proposed solution

Let’s define “API only” Kibana user as a user is authenticated by an ACL block containing the kibana_api_acl rule.

A viable strategy would be: “API only” users cannot login via ROR login form (if they try, we show a descriptive error).

This way, we can segregate the purely API-based user agents from regular Kibana users, and apply access control to what endpoints they can call.


Thank you very much for your answer. I believe that the solution you proposed is a good one.
As an approximation, in order for us to know how to plan the use of it, when do you predict it will be available?


Not before next month. We are very busy with the Kibana plugin universal builds and the new customers’ portal.


Is there any update on this issue?


I can see this is in progress. @coutoPL can you share any updates?

It’s under development right now, so we will provide sth to test soon.

We have it implemented. If you are interested in testing it, please let me know about ES & KBN version you use currently.

1 Like

here are the builds:
ROR 1.48.0-pre9 for ES 7.17.2
ROR 1.48.0-pre3 for KBN 7.17.2

here you find an example how to configure it.

Hello @coutoPL ,

Thank you. We will start testing and will let you know if there are any problems.

1 Like

the feature was released with ROR 1.48.0. See our docs

1 Like

I have also encountered some problems with the Kibana API and ACL. I am using ROR Enterprise 1.39.0 and Kibana 7.12.1. I have a similar question, how to limit user actions in Kibana API from ROR configuration? I found the following information in the ROR documentation:
“kibana_access”: restricts access to Kibana using certain access modes. It can be set to “ro” for read only access or “rw” for read and write access.
“actions”: allows or denies access to specific operations on indices, e.g. “indices:data/read/” allows reading data and “indices:data/write/” allows writing data.
I also learned that you can use “kibana_global”: false to restrict access to Kibana to only certain indexes, such as “.kibana_testapi”. But I haven’t found a way to restrict user actions in the Kibana API from the ROR configuration, for example to prevent importing dashboards or creating index templates. I would be grateful for any further advice or information on this topic.

Hello @MajorSherburne, welcome to the forum. I’m not aware of any setting with “kibana_global” as a value. Where did you find a reference to it?

Can you explain more in detail what you are trying to achieve?
From what I can tell so far you are trying to limit what users can do on the Kibana API level. As @coutoPL shared, we are now able to run specific parts of the ACL inside Kibana thanks to the new kibana.allowed_api_paths sub-rule:

- name: "::My ACL Block::"
  auth_key: user:pass
    access: ro 
    - http_method: POST
      http_path: "^/api/saved_objects/.*$"

Notice this whole new syntax is only available in ReadonlyREST 1.48.0 onwards. So you should update both Elasticsearch and Kibana plugins.

Maybe you should also take a look at the api_only kibana.access level: For Elasticsearch - ReadonlyREST