From f5586c43d8f27caca12baa31c5a85fd1190c5b64 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Thu, 23 Dec 2021 14:05:08 +1100 Subject: [PATCH] Allow read template with cluster monitor privilege All three template types (legacy templates, composable index templates and component templates) are stored in cluster state metadata (in fields "templates", "index_template" and "component_template"). This cluster state is readable (via GET /_cluster/state) for users who have the monitor privilege at the cluster level. However, calling the explicit read endpoints for these templates required the manage_index_templates privilege. This change grants access to the template specific retrieval APIs for all users (or API Keys) with the cluster monitor privilge so that they can make use of these fit-for-purpose APIs instead of parsing data directly from cluster metadata Relates: https://github.com/elastic/beats/issues/29554, #78832 --- .../authz/privilege/ClusterPrivilegeResolver.java | 10 +++++++++- .../authz/privilege/ClusterPrivilegeTests.java | 15 +++++++++++++++ .../authz/store/ReservedRolesStoreTests.java | 2 +- .../ClusterPrivilegeIntegrationTests.java | 8 ++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) 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 ba6fa1bf79003..935e453b60400 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 @@ -15,6 +15,9 @@ import org.elasticsearch.action.admin.cluster.snapshots.get.GetSnapshotsAction; import org.elasticsearch.action.admin.cluster.snapshots.status.SnapshotsStatusAction; import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; +import org.elasticsearch.action.admin.indices.template.get.GetComponentTemplateAction; +import org.elasticsearch.action.admin.indices.template.get.GetComposableIndexTemplateAction; +import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction; import org.elasticsearch.action.ingest.GetPipelineAction; import org.elasticsearch.action.ingest.SimulatePipelineAction; import org.elasticsearch.common.Strings; @@ -64,7 +67,12 @@ public class ClusterPrivilegeResolver { private static final Set MANAGE_API_KEY_PATTERN = Set.of("cluster:admin/xpack/security/api_key/*"); private static final Set MANAGE_SERVICE_ACCOUNT_PATTERN = Set.of("cluster:admin/xpack/security/service_account/*"); private static final Set GRANT_API_KEY_PATTERN = Set.of(GrantApiKeyAction.NAME + "*"); - private static final Set MONITOR_PATTERN = Set.of("cluster:monitor/*"); + private static final Set MONITOR_PATTERN = Set.of( + "cluster:monitor/*", + GetIndexTemplatesAction.NAME, + GetComponentTemplateAction.NAME, + GetComposableIndexTemplateAction.NAME + ); private static final Set MONITOR_ML_PATTERN = Set.of("cluster:monitor/xpack/ml/*"); 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/*"); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeTests.java index 267d04984fd0c..ad9f02cf27ea7 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeTests.java @@ -8,16 +8,31 @@ package org.elasticsearch.xpack.core.security.authz.privilege; import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.admin.indices.template.get.GetComponentTemplateAction; +import org.elasticsearch.action.admin.indices.template.get.GetComposableIndexTemplateAction; +import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.enrich.action.EnrichStatsAction; import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; +import java.util.List; + public class ClusterPrivilegeTests extends ESTestCase { public void testMonitorPrivilegeWillGrantActions() { assertGranted(ClusterPrivilegeResolver.MONITOR, EnrichStatsAction.INSTANCE); } + public void testMonitorPrivilegeGrantsGetTemplateActions() { + for (var action : List.of( + GetComponentTemplateAction.INSTANCE, + GetComposableIndexTemplateAction.INSTANCE, + GetIndexTemplatesAction.INSTANCE + )) { + assertGranted(ClusterPrivilegeResolver.MONITOR, action); + } + } + public static void assertGranted(ClusterPrivilege clusterPrivilege, ActionType actionType) { assertTrue(clusterPrivilege.buildPermission(ClusterPermission.builder()).build().check(actionType.name(), null, null)); } 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 97f2e17a4bda5..adb78891770e2 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 @@ -1204,7 +1204,7 @@ public void testRemoteMonitoringCollectorRole() { assertThat(remoteMonitoringCollectorRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); assertThat(remoteMonitoringCollectorRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); assertThat(remoteMonitoringCollectorRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); - assertThat(remoteMonitoringCollectorRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringCollectorRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); assertThat(remoteMonitoringCollectorRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); assertThat(remoteMonitoringCollectorRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(false)); assertThat(remoteMonitoringCollectorRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/ClusterPrivilegeIntegrationTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/ClusterPrivilegeIntegrationTests.java index 566de1b45ce0c..91861f054b0e4 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/ClusterPrivilegeIntegrationTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/integration/ClusterPrivilegeIntegrationTests.java @@ -127,9 +127,17 @@ public void testThatClusterPrivilegesWorkAsExpectedViaHttp() throws Exception { assertAccessIsAllowed("user_b", "GET", "/_nodes/stats"); assertAccessIsAllowed("user_b", "GET", "/_nodes/hot_threads"); assertAccessIsAllowed("user_b", "GET", "/_nodes/infos"); + // monitoring allows template retrieval (because it's implied by having read access to cluster state + assertAccessIsAllowed("user_b", "GET", "/_cat/templates/" + (randomBoolean() ? "" : randomAlphaOfLengthBetween(2, 8))); + assertAccessIsAllowed("user_b", "GET", "/_template/"); + assertAccessIsAllowed("user_b", "GET", "/_index_template/"); + assertAccessIsAllowed("user_b", "GET", "/_component_template/"); // but no admin stuff assertAccessIsDenied("user_b", "POST", "/_cluster/reroute"); assertAccessIsDenied("user_b", "PUT", "/_cluster/settings", "{ \"transient\" : { \"search.default_search_timeout\": \"1m\" } }"); + assertAccessIsDenied("user_b", "DELETE", "/_template/" + randomAlphaOfLengthBetween(2, 8)); + assertAccessIsDenied("user_b", "DELETE", "/_index_template/" + randomAlphaOfLengthBetween(2, 8)); + assertAccessIsDenied("user_b", "DELETE", "/_component_template/" + randomAlphaOfLengthBetween(2, 8)); // sorry user_c, you are not allowed anything assertAccessIsDenied("user_c", "GET", "/_cluster/state");