From 79d4cfc02aa1ffbed8e66ffb15f4e02ae90a3a58 Mon Sep 17 00:00:00 2001 From: Pete Gillin Date: Thu, 18 Dec 2025 19:07:09 +0000 Subject: [PATCH 1/3] Add RBAC for reindex management APIs This adds new `manage_reindex` and `monitor_reindex` cluster privileges, and new `reindex_admin` and `reindex_user` reserved roles. --- docs/reference/elasticsearch/roles.md | 8 +++- .../elasticsearch/security-privileges.md | 5 ++ .../privilege/ClusterPrivilegeResolver.java | 6 +++ .../authz/store/ReservedRolesStore.java | 34 ++++++++++++++ .../authz/store/ReservedRolesStoreTests.java | 47 +++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/docs/reference/elasticsearch/roles.md b/docs/reference/elasticsearch/roles.md index 21a84836c0e9d..1ef7b11edfb34 100644 --- a/docs/reference/elasticsearch/roles.md +++ b/docs/reference/elasticsearch/roles.md @@ -111,6 +111,12 @@ $$$built-in-roles-ml-user$$$ `machine_learning_user` $$$built-in-roles-monitoring-user$$$ `monitoring_user` : Grants the minimum privileges required for any user of {{monitoring}} other than those required to use {{kib}}. This role grants access to the monitoring indices and grants privileges necessary for reading basic cluster information. This role also includes all [Kibana privileges](docs-content://deploy-manage/users-roles/cluster-or-deployment-auth/kibana-privileges.md) for the {{stack-monitor-features}}. Monitoring users should also be assigned the `kibana_admin` role, or another role with [access to the {{kib}} instance](docs-content://deploy-manage/users-roles/cluster-or-deployment-auth/built-in-roles.md). +$$$built-in-roles-reporting-admin$$$ `reindex_admin` {applies_to}`TBD DO NOT MERGE` +: Allows users to manage reindex tasks, including getting, listing, cancelling, and rethrottling them. + +$$$built-in-roles-reporting-user$$$ `reindex_user` {applies_to}`TBD DO NOT MERGE` +: Allows users to monitor reindex tasks, including getting abd listing them. + $$$built-in-roles-remote-monitoring-agent$$$ `remote_monitoring_agent` : Grants the minimum privileges required to write data into the monitoring indices (`.monitoring-*`). This role also has the privileges necessary to create {{metricbeat}} indices (`metricbeat-*`) and write data into them. @@ -119,7 +125,7 @@ $$$built-in-roles-remote-monitoring-collector$$$ `remote_monitoring_collector` $$$built-in-roles-reporting-user$$$ `reporting_user` {applies_to}`stack: deprecated 9.0` : This role is deprecated. Use [{{kib}} feature privileges](docs-content://deploy-manage/users-roles/cluster-or-deployment-auth/kibana-privileges.md#kibana-feature-privileges) instead. - + Grants the necessary privileges required to use {{report-features}} in {{kib}}, including generating and downloading reports. This role implicitly grants access to all {{kib}} reporting features, with each user having access only to their own reports. Note that reporting users should also be assigned additional roles that grant read access to the [indices](docs-content://deploy-manage/users-roles/cluster-or-deployment-auth/role-structure.md#roles-indices-priv) that will be used to generate reports. $$$built-in-roles-rollup-admin$$$ `rollup_admin` diff --git a/docs/reference/elasticsearch/security-privileges.md b/docs/reference/elasticsearch/security-privileges.md index 67d1b3d4e9b47..e2e5d976f0c2c 100644 --- a/docs/reference/elasticsearch/security-privileges.md +++ b/docs/reference/elasticsearch/security-privileges.md @@ -125,6 +125,9 @@ When creating roles, refer to this page for a complete list of available privile `manage_pipeline` : All operations on ingest pipelines. +`manage_reindex` {applies_to}`TBD DO NOT MERGE` +: All operations on reindex tasks, including listing, getting status, cancelling, and rethrottling + `manage_rollup` {applies_to}`serverless: unavailable` : All rollup operations, including creating, starting, stopping and deleting rollup jobs. @@ -190,6 +193,8 @@ When creating roles, refer to this page for a complete list of available privile `monitor_rollup` {applies_to}`serverless: unavailable` : All read-only rollup operations, such as viewing the list of historical and currently running rollup jobs and their capabilities. +`monitor_reindex` {applies_to}`TBD DO NOT MERGE` +: All read-only operations on reindex tasks, including listing and getting status `monitor_snapshot` {applies_to}`serverless: unavailable` : Privileges to list and view details on existing repositories and snapshots. diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java index 5ad40afa3c86e..ae8268a6ccc88 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java @@ -108,6 +108,7 @@ public class ClusterPrivilegeResolver { private static final Set MONITOR_TEXT_STRUCTURE_PATTERN = Set.of("cluster:monitor/text_structure/*"); private static final Set MONITOR_TRANSFORM_PATTERN = Set.of("cluster:monitor/data_frame/*", "cluster:monitor/transform/*"); private static final Set MONITOR_WATCHER_PATTERN = Set.of("cluster:monitor/xpack/watcher/*"); + private static final Set MONITOR_REINDEX_PATTERN = Set.of("cluster:monitor/reindex/*"); private static final Set MONITOR_ROLLUP_PATTERN = Set.of("cluster:monitor/xpack/rollup/*"); private static final Set MONITOR_ENRICH_PATTERN = Set.of("cluster:monitor/xpack/enrich/*", "cluster:admin/xpack/enrich/get"); private static final Set MONITOR_ESQL_PATTERN = Set.of("cluster:monitor/xpack/esql/*"); @@ -135,6 +136,7 @@ public class ClusterPrivilegeResolver { "cluster:admin/transform/*" ); private static final Set MANAGE_WATCHER_PATTERN = Set.of("cluster:admin/xpack/watcher/*", "cluster:monitor/xpack/watcher/*"); + private static final Set MANAGE_REINDEX_PATTERN = Set.of("cluster:admin/reindex/*", "cluster:monitor/reindex/*"); private static final Set TRANSPORT_CLIENT_PATTERN = Set.of("cluster:monitor/nodes/liveness", "cluster:monitor/state"); private static final Set MANAGE_IDX_TEMPLATE_PATTERN = Set.of( "indices:admin/template/*", @@ -248,6 +250,7 @@ public class ClusterPrivilegeResolver { MONITOR_TRANSFORM_PATTERN ); public static final NamedClusterPrivilege MONITOR_WATCHER = new ActionClusterPrivilege("monitor_watcher", MONITOR_WATCHER_PATTERN); + public static final NamedClusterPrivilege MONITOR_REINDEX = new ActionClusterPrivilege("monitor_reindex", MONITOR_REINDEX_PATTERN); public static final NamedClusterPrivilege MONITOR_ROLLUP = new ActionClusterPrivilege("monitor_rollup", MONITOR_ROLLUP_PATTERN); public static final NamedClusterPrivilege MONITOR_ENRICH = new ActionClusterPrivilege("monitor_enrich", MONITOR_ENRICH_PATTERN); public static final NamedClusterPrivilege MONITOR_ESQL = new ActionClusterPrivilege("monitor_esql", MONITOR_ESQL_PATTERN); @@ -262,6 +265,7 @@ public class ClusterPrivilegeResolver { public static final NamedClusterPrivilege MANAGE_TRANSFORM = new ActionClusterPrivilege("manage_transform", MANAGE_TRANSFORM_PATTERN); public static final NamedClusterPrivilege MANAGE_TOKEN = new ActionClusterPrivilege("manage_token", MANAGE_TOKEN_PATTERN); public static final NamedClusterPrivilege MANAGE_WATCHER = new ActionClusterPrivilege("manage_watcher", MANAGE_WATCHER_PATTERN); + public static final NamedClusterPrivilege MANAGE_REINDEX = new ActionClusterPrivilege("manage_reindex", MANAGE_REINDEX_PATTERN); public static final NamedClusterPrivilege MANAGE_ROLLUP = new ActionClusterPrivilege("manage_rollup", MANAGE_ROLLUP_PATTERN); public static final NamedClusterPrivilege MANAGE_IDX_TEMPLATES = new ActionClusterPrivilege( "manage_index_templates", @@ -431,6 +435,7 @@ public class ClusterPrivilegeResolver { MONITOR_TRANSFORM_DEPRECATED, MONITOR_TRANSFORM, MONITOR_WATCHER, + MONITOR_REINDEX, MONITOR_ROLLUP, MONITOR_ENRICH, MONITOR_ESQL, @@ -443,6 +448,7 @@ public class ClusterPrivilegeResolver { MANAGE_TRANSFORM, MANAGE_TOKEN, MANAGE_WATCHER, + MANAGE_REINDEX, MANAGE_IDX_TEMPLATES, MANAGE_INGEST_PIPELINES, READ_PIPELINE, diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java index 8c76c630049ce..b40701a716885 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStore.java @@ -647,6 +647,40 @@ private static Map initializeReservedRoles() { "Grants read access to the .watches index, the get watch action and the watcher stats." ) ), + entry( + "reindex_admin", + new RoleDescriptor( + "reindex_admin", + new String[] { "manage_reindex" }, + null, + null, + null, + null, + MetadataUtils.DEFAULT_RESERVED_METADATA, + null, + null, + null, + null, + "Allows users to manage reindex tasks, including getting, listing, cancelling, and rethrottling them." + ) + ), + entry( + "reindex_user", + new RoleDescriptor( + "reindex_user", + new String[] { "monitor_reindex" }, + null, + null, + null, + null, + MetadataUtils.DEFAULT_RESERVED_METADATA, + null, + null, + null, + null, + "Allows users to monitor reindex tasks, including getting abd listing them." + ) + ), entry( "logstash_admin", new RoleDescriptor( diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index 9110333027d79..5a071669ea2ff 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java @@ -67,6 +67,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.reindex.ReindexPlugin; import org.elasticsearch.rest.root.MainRestPlugin; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; @@ -280,6 +281,8 @@ public void testIsReserved() { assertThat(ReservedRolesStore.isReserved("transform_admin"), is(true)); assertThat(ReservedRolesStore.isReserved("watcher_user"), is(true)); assertThat(ReservedRolesStore.isReserved("watcher_admin"), is(true)); + assertThat(ReservedRolesStore.isReserved("reindex_user"), is(true)); + assertThat(ReservedRolesStore.isReserved("reindex_admin"), is(true)); assertThat(ReservedRolesStore.isReserved("beats_admin"), is(true)); assertThat(ReservedRolesStore.isReserved(UsernamesField.LOGSTASH_ROLE), is(true)); assertThat(ReservedRolesStore.isReserved(UsernamesField.BEATS_ROLE), is(true)); @@ -3786,6 +3789,50 @@ public void testWatcherUserRole() { assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2)); } + public void testReindexAdminRole() { + final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = AuthenticationTestHelper.builder().build(); + + RoleDescriptor roleDescriptor = ReservedRolesStore.roleDescriptor("reindex_admin"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role role = Role.buildFromRoleDescriptor(roleDescriptor, new FieldPermissionsCache(Settings.EMPTY), RESTRICTED_INDICES); + // TODO: Add assertions for the other reindex actions here DO NOT MERGE + assertThat(role.cluster().check(ReindexPlugin.RETHROTTLE_ACTION.name(), request, authentication), is(true)); + + assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(TransportIndexAction.NAME).test(mockIndexAbstraction("foo")), is(false)); + assertThat( + role.indices().allowedIndicesMatcher(TransportIndexAction.NAME).test(mockIndexAbstraction(TriggeredWatchStoreField.INDEX_NAME)), + is(false) + ); + assertNoAccessAllowed(role, TestRestrictedIndices.SAMPLE_RESTRICTED_NAMES); + assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2)); + } + + public void testReindexUserRole() { + final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = AuthenticationTestHelper.builder().build(); + + RoleDescriptor roleDescriptor = ReservedRolesStore.roleDescriptor("reindex_user"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role role = Role.buildFromRoleDescriptor(roleDescriptor, new FieldPermissionsCache(Settings.EMPTY), RESTRICTED_INDICES); + // TODO: Add assertions for the other reindex actions here DO NOT MERGE + assertThat(role.cluster().check(ReindexPlugin.RETHROTTLE_ACTION.name(), request, authentication), is(false)); + + assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(TransportIndexAction.NAME).test(mockIndexAbstraction("foo")), is(false)); + assertThat( + role.indices().allowedIndicesMatcher(TransportIndexAction.NAME).test(mockIndexAbstraction(TriggeredWatchStoreField.INDEX_NAME)), + is(false) + ); + assertNoAccessAllowed(role, TestRestrictedIndices.SAMPLE_RESTRICTED_NAMES); + assertNoAccessAllowed(role, XPackPlugin.ASYNC_RESULTS_INDEX + randomAlphaOfLengthBetween(0, 2)); + } + public void testPredefinedViewerRole() { final TransportRequest request = mock(TransportRequest.class); final Authentication authentication = AuthenticationTestHelper.builder().build(); From 723c627291fc7d5fbe515c7b34f24474209f4fda Mon Sep 17 00:00:00 2001 From: Pete Gillin Date: Fri, 19 Dec 2025 12:36:21 +0000 Subject: [PATCH 2/3] update fragile yaml test with count --- .../resources/rest-api-spec/test/privileges/11_builtin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml index 2e501ad016592..6139ab5c618db 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/privileges/11_builtin.yml @@ -15,5 +15,5 @@ setup: # This is fragile - it needs to be updated every time we add a new cluster/index privilege # I would much prefer we could just check that specific entries are in the array, but we don't have # an assertion for that - - length: { "cluster" : 63 } + - length: { "cluster" : 65 } - length: { "index" : 24 } From 1e440ede4fc4f6a3552bb4aecbca4b15365ec9b8 Mon Sep 17 00:00:00 2001 From: Pete Gillin Date: Fri, 19 Dec 2025 15:13:29 +0000 Subject: [PATCH 3/3] replace TBD with more findable TODO --- docs/reference/elasticsearch/roles.md | 4 ++-- docs/reference/elasticsearch/security-privileges.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/elasticsearch/roles.md b/docs/reference/elasticsearch/roles.md index 1ef7b11edfb34..cc427ce0f6f06 100644 --- a/docs/reference/elasticsearch/roles.md +++ b/docs/reference/elasticsearch/roles.md @@ -111,10 +111,10 @@ $$$built-in-roles-ml-user$$$ `machine_learning_user` $$$built-in-roles-monitoring-user$$$ `monitoring_user` : Grants the minimum privileges required for any user of {{monitoring}} other than those required to use {{kib}}. This role grants access to the monitoring indices and grants privileges necessary for reading basic cluster information. This role also includes all [Kibana privileges](docs-content://deploy-manage/users-roles/cluster-or-deployment-auth/kibana-privileges.md) for the {{stack-monitor-features}}. Monitoring users should also be assigned the `kibana_admin` role, or another role with [access to the {{kib}} instance](docs-content://deploy-manage/users-roles/cluster-or-deployment-auth/built-in-roles.md). -$$$built-in-roles-reporting-admin$$$ `reindex_admin` {applies_to}`TBD DO NOT MERGE` +$$$built-in-roles-reporting-admin$$$ `reindex_admin` {applies_to}`TODO fill this in DO NOT MERGE` : Allows users to manage reindex tasks, including getting, listing, cancelling, and rethrottling them. -$$$built-in-roles-reporting-user$$$ `reindex_user` {applies_to}`TBD DO NOT MERGE` +$$$built-in-roles-reporting-user$$$ `reindex_user` {applies_to}`TODO fill this in DO NOT MERGE` : Allows users to monitor reindex tasks, including getting abd listing them. $$$built-in-roles-remote-monitoring-agent$$$ `remote_monitoring_agent` diff --git a/docs/reference/elasticsearch/security-privileges.md b/docs/reference/elasticsearch/security-privileges.md index e2e5d976f0c2c..6f5965fbf6648 100644 --- a/docs/reference/elasticsearch/security-privileges.md +++ b/docs/reference/elasticsearch/security-privileges.md @@ -125,7 +125,7 @@ When creating roles, refer to this page for a complete list of available privile `manage_pipeline` : All operations on ingest pipelines. -`manage_reindex` {applies_to}`TBD DO NOT MERGE` +`manage_reindex` {applies_to}`TODO fill this in DO NOT MERGE` : All operations on reindex tasks, including listing, getting status, cancelling, and rethrottling `manage_rollup` {applies_to}`serverless: unavailable` @@ -193,7 +193,7 @@ When creating roles, refer to this page for a complete list of available privile `monitor_rollup` {applies_to}`serverless: unavailable` : All read-only rollup operations, such as viewing the list of historical and currently running rollup jobs and their capabilities. -`monitor_reindex` {applies_to}`TBD DO NOT MERGE` +`monitor_reindex` {applies_to}`TODO fill this in DO NOT MERGE` : All read-only operations on reindex tasks, including listing and getting status `monitor_snapshot` {applies_to}`serverless: unavailable`