Kibana API ACLs

Hello,

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:
GET https://127.0.0.1:5601/api/saved_objects/index-pattern/metricbeat-*
Response:

{
    "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 "https://127.0.0.1:5601/api/saved_objects/_import?createNewCopies=true" --form [email protected]
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:127.0.0.1/32, XFF:null, DA:127.0.0.1/32, IDX:<N/A>, MET:GET, PTH:/_readonlyrest/metadata/current_user, CNT:<N/A>, HDR:Accept-Encoding=gzip,deflate, Accept=*/*, Authorization=<OMITTED>, Connection=close, Host=127.0.0.1:9200, User-Agent=node-fetch/1.0 (+https://github.com/bitinn/node-fetch), 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.

Thanks!

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

Hello,

Did you have the time to look into this topic?

Thanks,
Diana

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.

Hello,

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?

Thanks,
Diana

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