From f796ceb7c662f5b3e15d72823abb5fec8ed4f364 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Wed, 5 Jan 2022 16:11:38 +1100 Subject: [PATCH] Allow read template with cluster monitor privilege (#82244) 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 Backport of: #82046 --- .../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 51833b65776f6..df80d2823b129 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; @@ -69,7 +72,12 @@ public class ClusterPrivilegeResolver { private static final Set MANAGE_SERVICE_ACCOUNT_PATTERN = Collections.singleton( "cluster:admin/xpack/security/service_account/*" ); - private static final Set MONITOR_PATTERN = Collections.singleton("cluster:monitor/*"); + private static final Set MONITOR_PATTERN = Sets.newHashSet( + "cluster:monitor/*", + GetIndexTemplatesAction.NAME, + GetComponentTemplateAction.NAME, + GetComposableIndexTemplateAction.NAME + ); private static final Set MONITOR_TEXT_STRUCTURE_PATTERN = Collections.singleton("cluster:monitor/text_structure/*"); private static final Set MONITOR_TRANSFORM_PATTERN = Collections.unmodifiableSet( Sets.newHashSet("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..2159ded00cefa 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.Arrays; + public class ClusterPrivilegeTests extends ESTestCase { public void testMonitorPrivilegeWillGrantActions() { assertGranted(ClusterPrivilegeResolver.MONITOR, EnrichStatsAction.INSTANCE); } + public void testMonitorPrivilegeGrantsGetTemplateActions() { + for (ActionType action : Arrays.asList( + 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 d6872673a597b..046559704da04 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 @@ -1206,7 +1206,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 48de739b10d7b..043760d0c02f1 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 @@ -123,9 +123,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");