Configuration:
- name: "Allow Kibana main RO access"
kibana_access: "ro"
kibana_hide_apps: ["Analytics|Overview", "Observability", "Security", "readonlyrest_kbn", "Management", "Enterprise Search", "ROR Manage Kibana"]
groups: ["kibana_main_ro"]
indices: [".kibana", ".reporting.kibana-*", "aaa_dummy_index"]
- name: "Dynamic data access rule"
type: "allow"
groups: ["kibana_main_ro", "*", "aaa_dummy_index", "elasticsearch", "kibana"]
actions: ["indices:data/read/*", "indices:admin/mappings/fields/get", "indices:admin/mappings/get"]
indices: ["aaa_dummy_index", "@explode{acl:available_groups}-*", "@explode{acl:available_groups}", "@explode{acl:available_groups}_*", ".ds-@explode{acl:available_groups}-*"]
User:
users:
- username: test_ro_user
auth_key: test_ro_user:XXX
groups: ["kibana_main_ro", "elasticsearch"]
Relevant audit document:
{
"_index": "readonlyrest_audit-2026.06",
"_id": "5cdee58e-2c07-4b21-bf1c-407e8e61e9c6-688276354#3682535",
"_version": 1,
"_ignored": [
"acl_history.keyword",
"content.keyword"
],
"_source": {
"headers": [
"accept",
"x-ror-kibana-index",
"x-elastic-client-meta",
"x-ror-kibana-request-path",
"x-ror-current-group",
"Authorization",
"Host",
"x-forwarded-for",
"tracestate",
"x-ror-correlation-id",
"x-elastic-product-origin",
"user-agent",
"x-ror-tenancy",
"x-opaque-id",
"traceparent",
"Content-Length",
"x-ror-kibana-request-method",
"keep-alive",
"content-type",
"cookie",
"Accept-Charset",
"connection"
],
"acl_history": "[Allow Kibana main RO access: NOT_MATCHED (AUTHZ_FAIL) -> RULES:[groups_any_of->true, kibana_hide_apps->true, kibana_access->false]], [Dynamic data access rule: NOT_MATCHED (AUTHZ_FAIL) -> RULES:[groups_any_of->true, actions->false]]",
"origin": "x.x.x.x/32",
"match": false,
"final_state": "FORBIDDEN",
"destination": "x.x.x.x/32",
"task_id": 3682535,
"type": "BulkRequest",
"req_method": "POST",
"content": "{\"update\":{\"_id\":\"legacy-url-alias:default:index-pattern:b878e66b-25ac-456a-b85e-696ec3bc0493\",\"_index\":\".kibana\",\"_source\":true}}\n{\"script\":{\"source\":\"\\n if (ctx._source[params.type].disabled != true) {\\n if (ctx._source[params.type].resolveCounter == null) {\\n ctx._source[params.type].resolveCounter = 1;\\n }\\n else {\\n ctx._source[params.type].resolveCounter += 1;\\n }\\n ctx._source[params.type].lastResolved = params.time;\\n ctx._source.updated_at = params.time;\\n }\\n \",\"lang\":\"painless\",\"params\":{\"type\":\"legacy-url-alias\",\"time\":\"2026-06-23T07:31:28.558Z\"}}}\n",
"path": "/_bulk",
"indices": [],
"@timestamp": "2026-06-23T07:31:28Z",
"content_len_kb": 0,
"correlation_id": "5cdee58e-2c07-4b21-bf1c-407e8e61e9c6",
"processingMillis": 12,
"xff": "localhost:24001",
"action": "indices:data/write/bulk",
"block": "default",
"id": "5cdee58e-2c07-4b21-bf1c-407e8e61e9c6-688276354#3682535",
"content_len": 706,
"user": "test_ro_user"
},
"fields": {
"headers.keyword": [
"accept",
"x-ror-kibana-index",
"x-elastic-client-meta",
"x-ror-kibana-request-path",
"x-ror-current-group",
"Authorization",
"Host",
"x-forwarded-for",
"tracestate",
"x-ror-correlation-id",
"x-elastic-product-origin",
"user-agent",
"x-ror-tenancy",
"x-opaque-id",
"traceparent",
"Content-Length",
"x-ror-kibana-request-method",
"keep-alive",
"content-type",
"cookie",
"Accept-Charset",
"connection"
],
"origin": [
"x.x.x.x/32"
],
"destination": [
"x.x.x.x/32"
],
"final_state": [
"FORBIDDEN"
],
"task_id": [
3682535
],
"req_method.keyword": [
"POST"
],
"type": [
"BulkRequest"
],
"final_state.keyword": [
"FORBIDDEN"
],
"user.keyword": [
"test_ro_user"
],
"content": [
"{\"update\":{\"_id\":\"legacy-url-alias:default:index-pattern:b878e66b-25ac-456a-b85e-696ec3bc0493\",\"_index\":\".kibana\",\"_source\":true}}\n{\"script\":{\"source\":\"\\n if (ctx._source[params.type].disabled != true) {\\n if (ctx._source[params.type].resolveCounter == null) {\\n ctx._source[params.type].resolveCounter = 1;\\n }\\n else {\\n ctx._source[params.type].resolveCounter += 1;\\n }\\n ctx._source[params.type].lastResolved = params.time;\\n ctx._source.updated_at = params.time;\\n }\\n \",\"lang\":\"painless\",\"params\":{\"type\":\"legacy-url-alias\",\"time\":\"2026-06-23T07:31:28.558Z\"}}}\n"
],
"path": [
"/_bulk"
],
"id.keyword": [
"5cdee58e-2c07-4b21-bf1c-407e8e61e9c6-688276354#3682535"
],
"xff.keyword": [
"localhost:24001"
],
"type.keyword": [
"BulkRequest"
],
"correlation_id.keyword": [
"5cdee58e-2c07-4b21-bf1c-407e8e61e9c6"
],
"action": [
"indices:data/write/bulk"
],
"block": [
"default"
],
"id": [
"5cdee58e-2c07-4b21-bf1c-407e8e61e9c6-688276354#3682535"
],
"content_len": [
706
],
"action.keyword": [
"indices:data/write/bulk"
],
"headers": [
"accept",
"x-ror-kibana-index",
"x-elastic-client-meta",
"x-ror-kibana-request-path",
"x-ror-current-group",
"Authorization",
"Host",
"x-forwarded-for",
"tracestate",
"x-ror-correlation-id",
"x-elastic-product-origin",
"user-agent",
"x-ror-tenancy",
"x-opaque-id",
"traceparent",
"Content-Length",
"x-ror-kibana-request-method",
"keep-alive",
"content-type",
"cookie",
"Accept-Charset",
"connection"
],
"destination.keyword": [
"x.x.x.x/32"
],
"acl_history": [
"[Allow Kibana main RO access: NOT_MATCHED (AUTHZ_FAIL) -> RULES:[groups_any_of->true, kibana_hide_apps->true, kibana_access->false]], [Dynamic data access rule: NOT_MATCHED (AUTHZ_FAIL) -> RULES:[groups_any_of->true, actions->false]]"
],
"match": [
false
],
"req_method": [
"POST"
],
"@timestamp": [
"2026-06-23T07:31:28.000Z"
],
"content_len_kb": [
0
],
"origin.keyword": [
"x.x.x.x/32"
],
"block.keyword": [
"default"
],
"correlation_id": [
"5cdee58e-2c07-4b21-bf1c-407e8e61e9c6"
],
"processingMillis": [
12
],
"xff": [
"localhost:24001"
],
"user": [
"test_ro_user"
],
"path.keyword": [
"/_bulk"
]
},
"ignored_field_values": {
"acl_history.keyword": [
"[Allow Kibana main RO access: NOT_MATCHED (AUTHZ_FAIL) -> RULES:[groups_any_of->true, kibana_hide_apps->true, kibana_access->false]], [Dynamic data access rule: NOT_MATCHED (AUTHZ_FAIL) -> RULES:[groups_any_of->true, actions->false]]"
],
"content.keyword": [
"{\"update\":{\"_id\":\"legacy-url-alias:default:index-pattern:b878e66b-25ac-456a-b85e-696ec3bc0493\",\"_index\":\".kibana\",\"_source\":true}}\n{\"script\":{\"source\":\"\\n if (ctx._source[params.type].disabled != true) {\\n if (ctx._source[params.type].resolveCounter == null) {\\n ctx._source[params.type].resolveCounter = 1;\\n }\\n else {\\n ctx._source[params.type].resolveCounter += 1;\\n }\\n ctx._source[params.type].lastResolved = params.time;\\n ctx._source.updated_at = params.time;\\n }\\n \",\"lang\":\"painless\",\"params\":{\"type\":\"legacy-url-alias\",\"time\":\"2026-06-23T07:31:28.558Z\"}}}\n"
]
}
}
I had the prune the acl_history because of internal policy. But the relevant part is in there.
I see this forbidden 3 times when trying to generate the report.
If I login with admin user and check the report, I see:
We have configured ROR to be clear about it blocking things with:
response_if_req_forbidden: "Computer says no!"
I tried CSV export via “Discover in dashboard” and “Discover session via discover”, both don’t work.
If I switch user to RW role, the CSV export works.
On RO role:
From the users perspective it looks like:
Setup details:
In test setup we have 1 kibana node.

And 3 Elasticsearch nodes:
XXXC001 readonlyrest 1.70.2
XXXV001 readonlyrest 1.70.2
XXXV002 readonlyrest 1.70.2
XXXC001 dirt
XXXV001 m
XXXV002 dirt
Kibana points to XXXV002.
Hope this helps.