From 5a8277651bdbbde2babcb79a76c645d63de298c8 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Thu, 12 Aug 2021 21:07:10 +0300 Subject: [PATCH 1/5] Add kibana-system service account This change introduces a Service Account for Kibana to use when authenticating to Elasticsearch. The Service Account with kibana-system service name under the elastic namespace, uses the same RoleDescriptor as the existing kibana_system built-in user and is its functional equivalent when it comes to AuthZ. --- .../authentication/service-accounts.asciidoc | 6 +- .../authz/store/ReservedRolesStore.java | 150 +++--- .../authc/service/ElasticServiceAccounts.java | 5 +- .../service/ElasticServiceAccountsTests.java | 459 ++++++++++++++++++ .../service/ServiceAccountServiceTests.java | 5 +- .../test/service_accounts/10_basic.yml | 68 ++- 6 files changed, 604 insertions(+), 89 deletions(-) diff --git a/x-pack/docs/en/security/authentication/service-accounts.asciidoc b/x-pack/docs/en/security/authentication/service-accounts.asciidoc index 4907ce37d20d6..fc762a06c6a83 100644 --- a/x-pack/docs/en/security/authentication/service-accounts.asciidoc +++ b/x-pack/docs/en/security/authentication/service-accounts.asciidoc @@ -40,11 +40,15 @@ the format of `/`, where the `namespace` is a top-level grouping of service accounts, and `service` is the name of the service and must be unique within its namespace. -Service accounts are predefined in code. Currently, only one service account is available: +Service accounts are predefined in code. The following service accounts are +available: `elastic/fleet-server`:: The service account used by the {fleet} server to communicate with {es}. +`elastic/kibana-system`:: The service account used by {kib} to communicate with +{es}. + // tag::service-accounts-usage[] IMPORTANT: Do not attempt to use service accounts for authenticating individual users. Service accounts can only be authenticated with service tokens, which are 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 bde1ef8978cf7..d1eb7adaac7f7 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 @@ -21,7 +21,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges.ManageApplicationPrivileges; import org.elasticsearch.xpack.core.security.support.MetadataUtils; -import org.elasticsearch.xpack.core.security.user.KibanaUser; +import org.elasticsearch.xpack.core.security.user.KibanaSystemUser; import org.elasticsearch.xpack.core.security.user.UsernamesField; import org.elasticsearch.xpack.core.transform.transforms.persistence.TransformInternalIndexConstants; import org.elasticsearch.xpack.core.watcher.execution.TriggeredWatchStoreField; @@ -125,78 +125,7 @@ private static Map initializeReservedRoles() { null, null, MetadataUtils.getDeprecatedReservedMetadata("Please use Kibana feature privileges instead"), null)) - .put(KibanaUser.ROLE_NAME, new RoleDescriptor(KibanaUser.ROLE_NAME, - new String[] { - "monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc", - InvalidateApiKeyAction.NAME, "grant_api_key", - GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME, - // To facilitate ML UI functionality being controlled using Kibana security privileges - "manage_ml", - // The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core - "cluster:admin/analyze", - // To facilitate using the file uploader functionality - "monitor_text_structure", - // To cancel tasks and delete async searches - "cancel_task" - }, - new RoleDescriptor.IndicesPrivileges[] { - RoleDescriptor.IndicesPrivileges.builder() - .indices(".kibana*", ".reporting-*").privileges("all").build(), - RoleDescriptor.IndicesPrivileges.builder() - .indices(".monitoring-*").privileges("read", "read_cross_cluster").build(), - RoleDescriptor.IndicesPrivileges.builder() - .indices(".management-beats").privileges("create_index", "read", "write").build(), - // To facilitate ML UI functionality being controlled using Kibana security privileges - RoleDescriptor.IndicesPrivileges.builder() - .indices(".ml-anomalies*", ".ml-stats-*") - .privileges("read").build(), - RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*", ".ml-notifications*") - .privileges("read", "write").build(), - // APM agent configuration - RoleDescriptor.IndicesPrivileges.builder() - .indices(".apm-agent-configuration").privileges("all").build(), - // APM custom link index creation - RoleDescriptor.IndicesPrivileges.builder() - .indices(".apm-custom-link").privileges("all").build(), - // APM telemetry queries APM indices in kibana task runner - RoleDescriptor.IndicesPrivileges.builder() - .indices("apm-*") - .privileges("read", "read_cross_cluster").build(), - // Data telemetry reads mappings, metadata and stats of indices - RoleDescriptor.IndicesPrivileges.builder() - .indices("*") - .privileges("view_index_metadata", "monitor").build(), - // Endpoint diagnostic information. Kibana reads from these indices to send telemetry - RoleDescriptor.IndicesPrivileges.builder() - .indices(".logs-endpoint.diagnostic.collection-*") - .privileges("read").build(), - // Fleet Server indices. Kibana create this indice before Fleet Server use them. - // Fleet Server indices. Kibana read and write to this indice to manage Elastic Agents - RoleDescriptor.IndicesPrivileges.builder() - .indices(".fleet*") - .privileges("all").build(), - // Legacy "Alerts as data" index. Kibana user will create this index. - // Kibana user will read / write to these indices - RoleDescriptor.IndicesPrivileges.builder() - .indices(ReservedRolesStore.LEGACY_ALERTS_INDEX) - .privileges("all").build(), - // "Alerts as data" index. Kibana user will create this index. - // Kibana user will read / write to these indices - RoleDescriptor.IndicesPrivileges.builder() - .indices(ReservedRolesStore.ALERTS_INDEX) - .privileges("all").build(), - // Endpoint / Fleet policy responses. Kibana requires read access to send telemetry - RoleDescriptor.IndicesPrivileges.builder() - .indices("metrics-endpoint.policy-*") - .privileges("read").build(), - // Endpoint metrics. Kibana requires read access to send telemetry - RoleDescriptor.IndicesPrivileges.builder() - .indices("metrics-endpoint.metrics-*") - .privileges("read").build() - }, - null, - new ConfigurableClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) }, - null, MetadataUtils.DEFAULT_RESERVED_METADATA, null)) + .put(KibanaSystemUser.ROLE_NAME, kibanaSystemUserRole(KibanaSystemUser.ROLE_NAME)) .put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME}, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) .put("beats_admin", new RoleDescriptor("beats_admin", @@ -434,6 +363,81 @@ private static RoleDescriptor kibanaAdminUser(String name, Map m null, null, metadata, null); } + public static RoleDescriptor kibanaSystemUserRole(String name) { + return new RoleDescriptor(name, + new String[] { + "monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc", + InvalidateApiKeyAction.NAME, "grant_api_key", + GetBuiltinPrivilegesAction.NAME, "delegate_pki", GetLifecycleAction.NAME, PutLifecycleAction.NAME, + // To facilitate ML UI functionality being controlled using Kibana security privileges + "manage_ml", + // The symbolic constant for this one is in SecurityActionMapper, so not accessible from X-Pack core + "cluster:admin/analyze", + // To facilitate using the file uploader functionality + "monitor_text_structure", + // To cancel tasks and delete async searches + "cancel_task" + }, + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder() + .indices(".kibana*", ".reporting-*").privileges("all").build(), + RoleDescriptor.IndicesPrivileges.builder() + .indices(".monitoring-*").privileges("read", "read_cross_cluster").build(), + RoleDescriptor.IndicesPrivileges.builder() + .indices(".management-beats").privileges("create_index", "read", "write").build(), + // To facilitate ML UI functionality being controlled using Kibana security privileges + RoleDescriptor.IndicesPrivileges.builder() + .indices(".ml-anomalies*", ".ml-stats-*") + .privileges("read").build(), + RoleDescriptor.IndicesPrivileges.builder().indices(".ml-annotations*", ".ml-notifications*") + .privileges("read", "write").build(), + // APM agent configuration + RoleDescriptor.IndicesPrivileges.builder() + .indices(".apm-agent-configuration").privileges("all").build(), + // APM custom link index creation + RoleDescriptor.IndicesPrivileges.builder() + .indices(".apm-custom-link").privileges("all").build(), + // APM telemetry queries APM indices in kibana task runner + RoleDescriptor.IndicesPrivileges.builder() + .indices("apm-*") + .privileges("read", "read_cross_cluster").build(), + // Data telemetry reads mappings, metadata and stats of indices + RoleDescriptor.IndicesPrivileges.builder() + .indices("*") + .privileges("view_index_metadata", "monitor").build(), + // Endpoint diagnostic information. Kibana reads from these indices to send telemetry + RoleDescriptor.IndicesPrivileges.builder() + .indices(".logs-endpoint.diagnostic.collection-*") + .privileges("read").build(), + // Fleet Server indices. Kibana create this indice before Fleet Server use them. + // Fleet Server indices. Kibana read and write to this indice to manage Elastic Agents + RoleDescriptor.IndicesPrivileges.builder() + .indices(".fleet*") + .privileges("all").build(), + // Legacy "Alerts as data" index. Kibana user will create this index. + // Kibana user will read / write to these indices + RoleDescriptor.IndicesPrivileges.builder() + .indices(ReservedRolesStore.LEGACY_ALERTS_INDEX) + .privileges("all").build(), + // "Alerts as data" index. Kibana user will create this index. + // Kibana user will read / write to these indices + RoleDescriptor.IndicesPrivileges.builder() + .indices(ReservedRolesStore.ALERTS_INDEX) + .privileges("all").build(), + // Endpoint / Fleet policy responses. Kibana requires read access to send telemetry + RoleDescriptor.IndicesPrivileges.builder() + .indices("metrics-endpoint.policy-*") + .privileges("read").build(), + // Endpoint metrics. Kibana requires read access to send telemetry + RoleDescriptor.IndicesPrivileges.builder() + .indices("metrics-endpoint.metrics-*") + .privileges("read").build() + }, + null, + new ConfigurableClusterPrivilege[] { new ManageApplicationPrivileges(Collections.singleton("kibana-*")) }, + null, MetadataUtils.DEFAULT_RESERVED_METADATA, null); + } + public static boolean isReserved(String role) { return RESERVED_ROLES.containsKey(role) || UsernamesField.SYSTEM_ROLE.equals(role) || UsernamesField.XPACK_ROLE.equals(role) || UsernamesField.ASYNC_SEARCH_ROLE.equals(role); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java index 75d0ad260c37a..d49a556d88e5f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.user.User; import java.util.List; @@ -43,8 +44,10 @@ final class ElasticServiceAccounts { null, null )); + private static final ServiceAccount KIBANA_SYSTEM_ACCOUNT = + new ElasticServiceAccount("kibana-system", ReservedRolesStore.kibanaSystemUserRole(NAMESPACE + "/kibana-system")); - static final Map ACCOUNTS = List.of(FLEET_ACCOUNT).stream() + static final Map ACCOUNTS = List.of(FLEET_ACCOUNT, KIBANA_SYSTEM_ACCOUNT).stream() .collect(Collectors.toMap(a -> a.id().asPrincipal(), Function.identity()));; private ElasticServiceAccounts() {} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java index bf40fd4e351ab..852d2a02c9b22 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java @@ -7,12 +7,21 @@ package org.elasticsearch.xpack.security.authc.service; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; +import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; +import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; +import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; import org.elasticsearch.action.admin.indices.create.AutoCreateAction; import org.elasticsearch.action.admin.indices.create.CreateIndexAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; +import org.elasticsearch.action.admin.indices.get.GetIndexAction; +import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsAction; import org.elasticsearch.action.admin.indices.mapping.put.AutoPutMappingAction; import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; import org.elasticsearch.action.admin.indices.stats.IndicesStatsAction; +import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction; +import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; import org.elasticsearch.action.bulk.BulkAction; import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.get.GetAction; @@ -20,21 +29,111 @@ import org.elasticsearch.action.index.IndexAction; import org.elasticsearch.action.search.MultiSearchAction; import org.elasticsearch.action.search.SearchAction; +import org.elasticsearch.action.update.UpdateAction; import org.elasticsearch.cluster.metadata.IndexAbstraction; import org.elasticsearch.common.Strings; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.ilm.action.DeleteLifecycleAction; +import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction; +import org.elasticsearch.xpack.core.ilm.action.PutLifecycleAction; +import org.elasticsearch.xpack.core.ilm.action.StartILMAction; +import org.elasticsearch.xpack.core.ilm.action.StopILMAction; +import org.elasticsearch.xpack.core.ml.action.CloseJobAction; +import org.elasticsearch.xpack.core.ml.action.DeleteCalendarAction; +import org.elasticsearch.xpack.core.ml.action.DeleteCalendarEventAction; +import org.elasticsearch.xpack.core.ml.action.DeleteDatafeedAction; +import org.elasticsearch.xpack.core.ml.action.DeleteExpiredDataAction; +import org.elasticsearch.xpack.core.ml.action.DeleteFilterAction; +import org.elasticsearch.xpack.core.ml.action.DeleteForecastAction; +import org.elasticsearch.xpack.core.ml.action.DeleteJobAction; +import org.elasticsearch.xpack.core.ml.action.DeleteModelSnapshotAction; +import org.elasticsearch.xpack.core.ml.action.DeleteTrainedModelAction; +import org.elasticsearch.xpack.core.ml.action.EstimateModelMemoryAction; +import org.elasticsearch.xpack.core.ml.action.EvaluateDataFrameAction; +import org.elasticsearch.xpack.core.ml.action.ExplainDataFrameAnalyticsAction; +import org.elasticsearch.xpack.core.ml.action.FinalizeJobExecutionAction; +import org.elasticsearch.xpack.core.ml.action.FlushJobAction; +import org.elasticsearch.xpack.core.ml.action.ForecastJobAction; +import org.elasticsearch.xpack.core.ml.action.GetBucketsAction; +import org.elasticsearch.xpack.core.ml.action.GetCalendarEventsAction; +import org.elasticsearch.xpack.core.ml.action.GetCalendarsAction; +import org.elasticsearch.xpack.core.ml.action.GetCategoriesAction; +import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsAction; +import org.elasticsearch.xpack.core.ml.action.GetDataFrameAnalyticsStatsAction; +import org.elasticsearch.xpack.core.ml.action.GetDatafeedsAction; +import org.elasticsearch.xpack.core.ml.action.GetDatafeedsStatsAction; +import org.elasticsearch.xpack.core.ml.action.GetFiltersAction; +import org.elasticsearch.xpack.core.ml.action.GetInfluencersAction; +import org.elasticsearch.xpack.core.ml.action.GetJobsAction; +import org.elasticsearch.xpack.core.ml.action.GetJobsStatsAction; +import org.elasticsearch.xpack.core.ml.action.GetModelSnapshotsAction; +import org.elasticsearch.xpack.core.ml.action.GetOverallBucketsAction; +import org.elasticsearch.xpack.core.ml.action.GetRecordsAction; +import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsAction; +import org.elasticsearch.xpack.core.ml.action.GetTrainedModelsStatsAction; +import org.elasticsearch.xpack.core.ml.action.InternalInferModelAction; +import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction; +import org.elasticsearch.xpack.core.ml.action.KillProcessAction; +import org.elasticsearch.xpack.core.ml.action.MlInfoAction; +import org.elasticsearch.xpack.core.ml.action.OpenJobAction; +import org.elasticsearch.xpack.core.ml.action.PersistJobAction; +import org.elasticsearch.xpack.core.ml.action.PostCalendarEventsAction; +import org.elasticsearch.xpack.core.ml.action.PostDataAction; +import org.elasticsearch.xpack.core.ml.action.PreviewDatafeedAction; +import org.elasticsearch.xpack.core.ml.action.PutCalendarAction; +import org.elasticsearch.xpack.core.ml.action.PutDataFrameAnalyticsAction; +import org.elasticsearch.xpack.core.ml.action.PutDatafeedAction; +import org.elasticsearch.xpack.core.ml.action.PutFilterAction; +import org.elasticsearch.xpack.core.ml.action.PutJobAction; +import org.elasticsearch.xpack.core.ml.action.PutTrainedModelAction; +import org.elasticsearch.xpack.core.ml.action.RevertModelSnapshotAction; +import org.elasticsearch.xpack.core.ml.action.SetUpgradeModeAction; +import org.elasticsearch.xpack.core.ml.action.StartDataFrameAnalyticsAction; +import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction; +import org.elasticsearch.xpack.core.ml.action.StopDataFrameAnalyticsAction; +import org.elasticsearch.xpack.core.ml.action.StopDatafeedAction; +import org.elasticsearch.xpack.core.ml.action.UpdateCalendarJobAction; +import org.elasticsearch.xpack.core.ml.action.UpdateDatafeedAction; +import org.elasticsearch.xpack.core.ml.action.UpdateFilterAction; +import org.elasticsearch.xpack.core.ml.action.UpdateJobAction; +import org.elasticsearch.xpack.core.ml.action.UpdateModelSnapshotAction; +import org.elasticsearch.xpack.core.ml.action.UpdateProcessAction; +import org.elasticsearch.xpack.core.ml.action.ValidateDetectorAction; +import org.elasticsearch.xpack.core.ml.action.ValidateJobConfigAction; +import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction; import org.elasticsearch.xpack.core.security.action.CreateApiKeyAction; import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction; import org.elasticsearch.xpack.core.security.action.GetApiKeyAction; import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.GrantApiKeyAction; import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction; import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction; +import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest; +import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest; +import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest; +import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateAction; +import org.elasticsearch.xpack.core.security.action.saml.SamlPrepareAuthenticationAction; +import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction; +import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.Role; +import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; +import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; +import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.core.textstructure.action.FindStructureAction; import org.elasticsearch.xpack.security.authc.service.ElasticServiceAccounts.ElasticServiceAccount; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -46,6 +145,280 @@ public class ElasticServiceAccountsTests extends ESTestCase { + public void testKibanaSystemPrivileges() { + final String readCrossClusterName = "internal:transport/proxy/indices:data/read/query"; + final Role role = Role.builder(ElasticServiceAccounts.ACCOUNTS.get("elastic/kibana-system").roleDescriptor(), null).build(); + final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); + assertThat(role.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); + + // ILM + assertThat(role.cluster().check(GetLifecycleAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutLifecycleAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteLifecycleAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StartILMAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StopILMAction.NAME, request, authentication), is(false)); + + // SAML and token + assertThat(role.cluster().check(SamlPrepareAuthenticationAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(SamlAuthenticateAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(InvalidateTokenAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(CreateTokenAction.NAME, request, authentication), is(true)); + + // API keys + assertThat(role.cluster().check(InvalidateApiKeyAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GrantApiKeyAction.NAME, request, authentication), is(true)); + + // ML + assertRoleHasManageMl(role); + + // Text Structure + assertThat(role.cluster().check(FindStructureAction.NAME, request, authentication), is(true)); + + // Application Privileges + DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" }); + DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" }); + assertThat(role.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges, authentication), is(true)); + assertThat(role.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges, authentication), is(false)); + + GetPrivilegesRequest getKibanaPrivileges = new GetPrivilegesRequest(); + getKibanaPrivileges.application("kibana-.kibana-sales"); + GetPrivilegesRequest getApmPrivileges = new GetPrivilegesRequest(); + getApmPrivileges.application("apm"); + assertThat(role.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges, authentication), is(true)); + assertThat(role.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges, authentication), is(false)); + + PutPrivilegesRequest putKibanaPrivileges = new PutPrivilegesRequest(); + putKibanaPrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( + "kibana-.kibana-" + randomAlphaOfLengthBetween(2,6), "all", Collections.emptySet(), Collections.emptyMap()))); + PutPrivilegesRequest putSwiftypePrivileges = new PutPrivilegesRequest(); + putSwiftypePrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( + "swiftype-kibana" , "all", Collections.emptySet(), Collections.emptyMap()))); + assertThat(role.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges, authentication), is(true)); + assertThat(role.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges, authentication), is(false)); + + assertThat(role.cluster().check(GetBuiltinPrivilegesAction.NAME, request, authentication), is(true)); + + // Everything else + assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); + assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(true)); + + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction("foo")), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(".reporting")), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(randomAlphaOfLengthBetween(8, 24))), + is(false)); + + Arrays.asList( + ".kibana", + ".kibana-devnull", + ".reporting-" + randomAlphaOfLength(randomIntBetween(0, 13)), + ".apm-agent-configuration", + ".apm-custom-link", + ReservedRolesStore.LEGACY_ALERTS_INDEX + randomAlphaOfLength(randomIntBetween(0, 13)), + ReservedRolesStore.ALERTS_INDEX + randomAlphaOfLength(randomIntBetween(0, 13)) + ).forEach((index) -> { + logger.info("index name [{}]", index); + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + // inherits from 'all' + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); + }); + + // read-only index access, including cross cluster + Arrays.asList(".monitoring-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { + logger.info("index name [{}]", index); + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); + }); + + // read-only index access, excluding cross cluster + Arrays.asList( + ".ml-anomalies-" + randomAlphaOfLength(randomIntBetween(0, 13)), + ".ml-stats-" + randomAlphaOfLength(randomIntBetween(0, 13)) + ).forEach((index) -> { + logger.trace("index name [{}]", index); + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); + }); + + // read/write index access, excluding cross cluster + Arrays.asList( + ".ml-annotations-" + randomAlphaOfLength(randomIntBetween(0, 13)), + ".ml-notifications-" + randomAlphaOfLength(randomIntBetween(0, 13)) + ).forEach((index) -> { + logger.trace("index name [{}]", index); + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); + }); + + // read-only indices for APM telemetry + Arrays.asList("apm-*").forEach((index) -> { + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); + }); + + // read-only indices for Endpoint diagnostic information + Arrays.asList(".logs-endpoint.diagnostic.collection-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); + }); + + Arrays.asList( + ".fleet", + ".fleet-agents", + ".fleet-actions", + ".fleet-enrollment-api-keys", + ".fleet-policies", + ".fleet-actions-results", + ".fleet-servers" + ).forEach((index) -> { + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); + }); + + + // Data telemetry reads mappings, metadata and stats of indices + Arrays.asList(randomAlphaOfLengthBetween(8, 24), "packetbeat-*", "logs-*").forEach((index) -> { + logger.info("index name [{}]", index); + assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetMappingsAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(IndicesStatsAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); + }); + + // read-only datastream for Endpoint policy responses + Arrays.asList("metrics-endpoint.policy-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); + }); + + // read-only datastream for Endpoint metrics + Arrays.asList("metrics-endpoint.metrics-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); + }); + + // Beats management index + final String index = ".management-beats"; + assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); + assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); + + assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES); + assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2)); + + } + public void testElasticFleetPrivileges() { final Role role = Role.builder(ElasticServiceAccounts.ACCOUNTS.get("elastic/fleet-server").roleDescriptor(), null).build(); final Authentication authentication = mock(Authentication.class); @@ -130,4 +503,90 @@ private IndexAbstraction mockIndexAbstraction(String name) { IndexAbstraction.Type.ALIAS, IndexAbstraction.Type.DATA_STREAM)); return mock; } + + private void assertNoAccessAllowed(Role role, Collection indices) { + for (String index : indices) { + assertNoAccessAllowed(role, index); + } + } + + private void assertNoAccessAllowed(Role role, String index) { + assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(UpdateAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); + assertThat(role.indices().allowedIndicesMatcher(BulkAction.NAME).test(mockIndexAbstraction(index)), is(false)); + } + + private void assertRoleHasManageMl(Role role) { + final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); + + assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteTrainedModelAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(EstimateModelMemoryAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(EvaluateDataFrameAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ExplainDataFrameAnalyticsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameAnalyticsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameAnalyticsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetTrainedModelsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetTrainedModelsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(InternalInferModelAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutDataFrameAnalyticsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutTrainedModelAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StartDataFrameAnalyticsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StopDataFrameAnalyticsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(true)); + } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java index d2c5e25be76ee..544345b358ce9 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java @@ -59,12 +59,12 @@ import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; @@ -121,7 +121,8 @@ public void stopThreadPool() { } public void testGetServiceAccountPrincipals() { - assertThat(ServiceAccountService.getServiceAccountPrincipals(), equalTo(Set.of("elastic/fleet-server"))); + assertThat(ServiceAccountService.getServiceAccountPrincipals(), + containsInAnyOrder("elastic/fleet-server", "elastic/kibana-system")); } public void testTryParseToken() throws IOException, IllegalAccessException { diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml index 31dc539e11bfe..9af355e6bd912 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml @@ -10,21 +10,30 @@ teardown: security.delete_service_token: namespace: elastic service: fleet-server - name: api-token-1 + name: api-token-fleet + ignore: 404 + + - do: + security.delete_service_token: + namespace: elastic + service: kibana-system + name: api-token-kibana ignore: 404 --- "Test get service accounts": - do: security.get_service_accounts: {} - - length: { '': 1 } + - length: { '': 2 } - is_true: "elastic/fleet-server" + - is_true: "elastic/kibana-system" - do: security.get_service_accounts: namespace: elastic - - length: { '': 1 } + - length: { '': 2 } - is_true: "elastic/fleet-server" + - is_true: "elastic/kibana-system" - do: security.get_service_accounts: @@ -41,26 +50,36 @@ teardown: security.create_service_token: namespace: elastic service: fleet-server - name: api-token-1 + name: api-token-fleet - is_true: created - - match: { "token.name": "api-token-1" } - - set: { "token.value": service_token } + - match: { "token.name": "api-token-fleet" } + - set: { "token.value": service_token_fleet } + + - do: + security.create_service_token: + namespace: elastic + service: kibana-system + name: api-token-kibana + + - is_true: created + - match: { "token.name": "api-token-kibana" } + - set: { "token.value": service_token_kibana } - do: headers: - Authorization: Bearer ${service_token} + Authorization: Bearer ${service_token_fleet} security.authenticate: {} - match: { username: "elastic/fleet-server" } - match: { roles: [] } - match: { full_name: "Service account - elastic/fleet-server" } - - match: { "token.name": "api-token-1" } + - match: { "token.name": "api-token-fleet" } - do: catch: forbidden headers: - Authorization: Bearer ${service_token} + Authorization: Bearer ${service_token_fleet} security.delete_user: username: foo @@ -68,6 +87,16 @@ teardown: - match: error.reason: "action [cluster:admin/xpack/security/user/delete] is unauthorized for user [elastic/fleet-server], this action is granted by the cluster privileges [manage_security,all]" + - do: + headers: + Authorization: Bearer ${service_token_kibana} + security.authenticate: {} + + - match: { username: "elastic/kibana-system" } + - match: { roles: [] } + - match: { full_name: "Service account - elastic/kibana-system" } + - match: { "token.name": "api-token-kibana" } + - do: security.get_service_credentials: namespace: elastic @@ -75,7 +104,7 @@ teardown: - match: { "service_account": "elastic/fleet-server" } - match: { "count": 2 } - - match: { "tokens": { "api-token-1": {} } } + - match: { "tokens": { "api-token-fleet": {} } } - match: { "nodes_credentials._nodes.failed": 0 } - is_true: nodes_credentials.file_tokens.token1 - is_true: nodes_credentials.file_tokens.token1.nodes @@ -85,7 +114,15 @@ teardown: security.clear_cached_service_tokens: namespace: elastic service: fleet-server - name: api-token-1 + name: api-token-fleet + + - match: { _nodes.failed: 0 } + + - do: + security.clear_cached_service_tokens: + namespace: elastic + service: kibana-system + name: api-token-kibana - match: { _nodes.failed: 0 } @@ -93,9 +130,16 @@ teardown: security.clear_cached_service_tokens: namespace: elastic service: fleet-server - name: [ "api-token-1", "does-not-exist" ] + name: [ "api-token-fleet", "does-not-exist" ] - match: { _nodes.failed: 0 } + - do: + security.clear_cached_service_tokens: + namespace: elastic + service: kibana-system + name: [ "api-token-kibana", "does-not-exist-either" ] + + - match: { _nodes.failed: 0 } From 7859a4ba6d275392d3d32880ab119d7163dda760 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Fri, 13 Aug 2021 13:20:58 +0300 Subject: [PATCH 2/5] Address feedback --- .../authentication/service-accounts.asciidoc | 10 +- .../authz/store/ReservedRolesStore.java | 4 +- .../authc/service/ServiceAccountIT.java | 18 + .../authc/service/ElasticServiceAccounts.java | 2 +- .../service/ElasticServiceAccountsTests.java | 315 +----------------- .../service/ServiceAccountServiceTests.java | 2 +- .../test/service_accounts/10_basic.yml | 16 +- 7 files changed, 46 insertions(+), 321 deletions(-) diff --git a/x-pack/docs/en/security/authentication/service-accounts.asciidoc b/x-pack/docs/en/security/authentication/service-accounts.asciidoc index fc762a06c6a83..6964d2e0785d6 100644 --- a/x-pack/docs/en/security/authentication/service-accounts.asciidoc +++ b/x-pack/docs/en/security/authentication/service-accounts.asciidoc @@ -18,12 +18,12 @@ prevents credential sharing between multiple instances of the same external service. Each instance can assume the same identity while using their own distinct service token for authentication. -Service accounts provide flexibility over <> +Service accounts provide flexibility over <> because they: * Do not rely on the <>, and aren't always required to rely on the `.security` index -* Use a role descriptor named after the service account principal instead of +* Use a role descriptor named after the service account principal instead of traditional roles * Support multiple credentials through service account tokens @@ -46,7 +46,7 @@ available: `elastic/fleet-server`:: The service account used by the {fleet} server to communicate with {es}. -`elastic/kibana-system`:: The service account used by {kib} to communicate with +`elastic/kibana`:: The service account used by {kib} to communicate with {es}. // tag::service-accounts-usage[] @@ -87,11 +87,11 @@ the bearer token in the HTTP response Both of these methods create a service token with a guaranteed secret string length of `22`. The minimal, acceptable length of a secret string for a service -token is `10`. If the secret string doesn't meet this minimal length, +token is `10`. If the secret string doesn't meet this minimal length, authentication with {es} will fail without even checking the value of the service token. -Service tokens never expire. You must actively +Service tokens never expire. You must actively <> them if they are no longer needed. [discrete] 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 d1eb7adaac7f7..367a0afb599cb 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 @@ -125,7 +125,7 @@ private static Map initializeReservedRoles() { null, null, MetadataUtils.getDeprecatedReservedMetadata("Please use Kibana feature privileges instead"), null)) - .put(KibanaSystemUser.ROLE_NAME, kibanaSystemUserRole(KibanaSystemUser.ROLE_NAME)) + .put(KibanaSystemUser.ROLE_NAME, kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME)) .put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME}, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) .put("beats_admin", new RoleDescriptor("beats_admin", @@ -363,7 +363,7 @@ private static RoleDescriptor kibanaAdminUser(String name, Map m null, null, metadata, null); } - public static RoleDescriptor kibanaSystemUserRole(String name) { + public static RoleDescriptor kibanaSystemRoleDescriptor(String name) { return new RoleDescriptor(name, new String[] { "monitor", "manage_index_templates", MonitoringBulkAction.NAME, "manage_saml", "manage_token", "manage_oidc", diff --git a/x-pack/plugin/security/qa/service-account/src/javaRestTest/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountIT.java b/x-pack/plugin/security/qa/service-account/src/javaRestTest/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountIT.java index d73b05ba1488c..e2645ec3226cb 100644 --- a/x-pack/plugin/security/qa/service-account/src/javaRestTest/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountIT.java +++ b/x-pack/plugin/security/qa/service-account/src/javaRestTest/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountIT.java @@ -12,7 +12,11 @@ import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.Response; import org.elasticsearch.client.ResponseException; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.core.PathUtils; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; @@ -20,6 +24,8 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.rest.ESRestTestCase; +import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; +import org.elasticsearch.xpack.core.security.user.KibanaSystemUser; import org.junit.BeforeClass; import java.io.FileNotFoundException; @@ -165,6 +171,18 @@ public void testGetServiceAccount() throws IOException { assertServiceAccountRoleDescriptor(getServiceAccountResponse3, "elastic/fleet-server", ELASTIC_FLEET_SERVER_ROLE_DESCRIPTOR); + final Request getServiceAccountRequestKibana = new Request("GET", "_security/service/elastic/kibana"); + final Response getServiceAccountResponseKibana = client().performRequest(getServiceAccountRequestKibana); + assertOK(getServiceAccountResponseKibana); + assertServiceAccountRoleDescriptor( + getServiceAccountResponseKibana, + "elastic/kibana", + Strings.toString( + ReservedRolesStore.kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME) + .toXContent(JsonXContent.contentBuilder(), ToXContent.EMPTY_PARAMS) + ) + ); + final String requestPath = "_security/service/" + randomFrom("foo", "elastic/foo", "foo/bar"); final Request getServiceAccountRequest4 = new Request("GET", requestPath); final Response getServiceAccountResponse4 = client().performRequest(getServiceAccountRequest4); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java index d49a556d88e5f..96ce558cae9ea 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccounts.java @@ -45,7 +45,7 @@ final class ElasticServiceAccounts { null )); private static final ServiceAccount KIBANA_SYSTEM_ACCOUNT = - new ElasticServiceAccount("kibana-system", ReservedRolesStore.kibanaSystemUserRole(NAMESPACE + "/kibana-system")); + new ElasticServiceAccount("kibana", ReservedRolesStore.kibanaSystemRoleDescriptor(NAMESPACE + "/kibana")); static final Map ACCOUNTS = List.of(FLEET_ACCOUNT, KIBANA_SYSTEM_ACCOUNT).stream() .collect(Collectors.toMap(a -> a.id().asPrincipal(), Function.identity()));; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java index 852d2a02c9b22..b81d4fc507f10 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ElasticServiceAccountsTests.java @@ -7,21 +7,12 @@ package org.elasticsearch.xpack.security.authc.service; -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; import org.elasticsearch.action.admin.indices.create.AutoCreateAction; import org.elasticsearch.action.admin.indices.create.CreateIndexAction; import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.get.GetIndexAction; -import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsAction; import org.elasticsearch.action.admin.indices.mapping.put.AutoPutMappingAction; import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; import org.elasticsearch.action.admin.indices.stats.IndicesStatsAction; -import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; import org.elasticsearch.action.bulk.BulkAction; import org.elasticsearch.action.delete.DeleteAction; import org.elasticsearch.action.get.GetAction; @@ -34,11 +25,6 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.core.ilm.action.DeleteLifecycleAction; -import org.elasticsearch.xpack.core.ilm.action.GetLifecycleAction; -import org.elasticsearch.xpack.core.ilm.action.PutLifecycleAction; -import org.elasticsearch.xpack.core.ilm.action.StartILMAction; -import org.elasticsearch.xpack.core.ilm.action.StopILMAction; import org.elasticsearch.xpack.core.ml.action.CloseJobAction; import org.elasticsearch.xpack.core.ml.action.DeleteCalendarAction; import org.elasticsearch.xpack.core.ml.action.DeleteCalendarEventAction; @@ -101,39 +87,21 @@ import org.elasticsearch.xpack.core.ml.action.UpdateProcessAction; import org.elasticsearch.xpack.core.ml.action.ValidateDetectorAction; import org.elasticsearch.xpack.core.ml.action.ValidateJobConfigAction; -import org.elasticsearch.xpack.core.monitoring.action.MonitoringBulkAction; import org.elasticsearch.xpack.core.security.action.CreateApiKeyAction; import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest; -import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationAction; import org.elasticsearch.xpack.core.security.action.GetApiKeyAction; import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; -import org.elasticsearch.xpack.core.security.action.GrantApiKeyAction; import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction; import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; -import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction; -import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest; -import org.elasticsearch.xpack.core.security.action.privilege.GetBuiltinPrivilegesAction; -import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction; -import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest; -import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesAction; -import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest; -import org.elasticsearch.xpack.core.security.action.saml.SamlAuthenticateAction; -import org.elasticsearch.xpack.core.security.action.saml.SamlPrepareAuthenticationAction; -import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction; -import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.Role; -import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; -import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; +import org.elasticsearch.xpack.core.security.user.KibanaSystemUser; import org.elasticsearch.xpack.core.security.user.User; -import org.elasticsearch.xpack.core.textstructure.action.FindStructureAction; import org.elasticsearch.xpack.security.authc.service.ElasticServiceAccounts.ElasticServiceAccount; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -146,277 +114,16 @@ public class ElasticServiceAccountsTests extends ESTestCase { public void testKibanaSystemPrivileges() { - final String readCrossClusterName = "internal:transport/proxy/indices:data/read/query"; - final Role role = Role.builder(ElasticServiceAccounts.ACCOUNTS.get("elastic/kibana-system").roleDescriptor(), null).build(); - final TransportRequest request = mock(TransportRequest.class); - final Authentication authentication = mock(Authentication.class); - assertThat(role.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); - assertThat(role.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); - - // ILM - assertThat(role.cluster().check(GetLifecycleAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(PutLifecycleAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(DeleteLifecycleAction.NAME, request, authentication), is(false)); - assertThat(role.cluster().check(StartILMAction.NAME, request, authentication), is(false)); - assertThat(role.cluster().check(StopILMAction.NAME, request, authentication), is(false)); - - // SAML and token - assertThat(role.cluster().check(SamlPrepareAuthenticationAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(SamlAuthenticateAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(InvalidateTokenAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(CreateTokenAction.NAME, request, authentication), is(true)); - - // API keys - assertThat(role.cluster().check(InvalidateApiKeyAction.NAME, request, authentication), is(true)); - assertThat(role.cluster().check(GrantApiKeyAction.NAME, request, authentication), is(true)); - - // ML - assertRoleHasManageMl(role); - - // Text Structure - assertThat(role.cluster().check(FindStructureAction.NAME, request, authentication), is(true)); - - // Application Privileges - DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" }); - DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" }); - assertThat(role.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges, authentication), is(true)); - assertThat(role.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges, authentication), is(false)); - - GetPrivilegesRequest getKibanaPrivileges = new GetPrivilegesRequest(); - getKibanaPrivileges.application("kibana-.kibana-sales"); - GetPrivilegesRequest getApmPrivileges = new GetPrivilegesRequest(); - getApmPrivileges.application("apm"); - assertThat(role.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges, authentication), is(true)); - assertThat(role.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges, authentication), is(false)); - - PutPrivilegesRequest putKibanaPrivileges = new PutPrivilegesRequest(); - putKibanaPrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( - "kibana-.kibana-" + randomAlphaOfLengthBetween(2,6), "all", Collections.emptySet(), Collections.emptyMap()))); - PutPrivilegesRequest putSwiftypePrivileges = new PutPrivilegesRequest(); - putSwiftypePrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( - "swiftype-kibana" , "all", Collections.emptySet(), Collections.emptyMap()))); - assertThat(role.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges, authentication), is(true)); - assertThat(role.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges, authentication), is(false)); - - assertThat(role.cluster().check(GetBuiltinPrivilegesAction.NAME, request, authentication), is(true)); - - // Everything else - assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); - assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(true)); - - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction("foo")), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(".reporting")), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(randomAlphaOfLengthBetween(8, 24))), - is(false)); - - Arrays.asList( - ".kibana", - ".kibana-devnull", - ".reporting-" + randomAlphaOfLength(randomIntBetween(0, 13)), - ".apm-agent-configuration", - ".apm-custom-link", - ReservedRolesStore.LEGACY_ALERTS_INDEX + randomAlphaOfLength(randomIntBetween(0, 13)), - ReservedRolesStore.ALERTS_INDEX + randomAlphaOfLength(randomIntBetween(0, 13)) - ).forEach((index) -> { - logger.info("index name [{}]", index); - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - // inherits from 'all' - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); - }); - - // read-only index access, including cross cluster - Arrays.asList(".monitoring-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { - logger.info("index name [{}]", index); - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); - }); - - // read-only index access, excluding cross cluster - Arrays.asList( - ".ml-anomalies-" + randomAlphaOfLength(randomIntBetween(0, 13)), - ".ml-stats-" + randomAlphaOfLength(randomIntBetween(0, 13)) - ).forEach((index) -> { - logger.trace("index name [{}]", index); - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); - }); - - // read/write index access, excluding cross cluster - Arrays.asList( - ".ml-annotations-" + randomAlphaOfLength(randomIntBetween(0, 13)), - ".ml-notifications-" + randomAlphaOfLength(randomIntBetween(0, 13)) - ).forEach((index) -> { - logger.trace("index name [{}]", index); - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); - }); - - // read-only indices for APM telemetry - Arrays.asList("apm-*").forEach((index) -> { - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); - }); - - // read-only indices for Endpoint diagnostic information - Arrays.asList(".logs-endpoint.diagnostic.collection-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); - }); - - Arrays.asList( - ".fleet", - ".fleet-agents", - ".fleet-actions", - ".fleet-enrollment-api-keys", - ".fleet-policies", - ".fleet-actions-results", - ".fleet-servers" - ).forEach((index) -> { - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(true)); - }); - - - // Data telemetry reads mappings, metadata and stats of indices - Arrays.asList(randomAlphaOfLengthBetween(8, 24), "packetbeat-*", "logs-*").forEach((index) -> { - logger.info("index name [{}]", index); - assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetMappingsAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(IndicesStatsAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); - }); - - // read-only datastream for Endpoint policy responses - Arrays.asList("metrics-endpoint.policy-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); - }); - - // read-only datastream for Endpoint metrics - Arrays.asList("metrics-endpoint.metrics-" + randomAlphaOfLength(randomIntBetween(0, 13))).forEach((index) -> { - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); - }); - - // Beats management index - final String index = ".management-beats"; - assertThat(role.indices().allowedIndicesMatcher("indices:foo").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher("indices:bar").test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(DeleteAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(mockIndexAbstraction(index)), is(false)); - assertThat(role.indices().allowedIndicesMatcher(SearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(GetAction.NAME).test(mockIndexAbstraction(index)), is(true)); - assertThat(role.indices().allowedIndicesMatcher(readCrossClusterName).test(mockIndexAbstraction(index)), is(false)); - - assertNoAccessAllowed(role, RestrictedIndicesNames.RESTRICTED_NAMES); - assertNoAccessAllowed(role, RestrictedIndicesNames.ASYNC_SEARCH_PREFIX + randomAlphaOfLengthBetween(0, 2)); - + final RoleDescriptor serviceAccountRoleDescriptor = ElasticServiceAccounts.ACCOUNTS.get("elastic/kibana").roleDescriptor(); + final RoleDescriptor reservedRolesStoreRoleDescriptor = ReservedRolesStore.kibanaSystemRoleDescriptor(KibanaSystemUser.ROLE_NAME); + assertThat(serviceAccountRoleDescriptor.getClusterPrivileges(), equalTo(reservedRolesStoreRoleDescriptor.getClusterPrivileges())); + assertThat(serviceAccountRoleDescriptor.getApplicationPrivileges(), + equalTo(reservedRolesStoreRoleDescriptor.getApplicationPrivileges())); + assertThat(serviceAccountRoleDescriptor.getIndicesPrivileges(), equalTo(reservedRolesStoreRoleDescriptor.getIndicesPrivileges())); + assertThat(serviceAccountRoleDescriptor.getConditionalClusterPrivileges(), + equalTo(reservedRolesStoreRoleDescriptor.getConditionalClusterPrivileges())); + assertThat(serviceAccountRoleDescriptor.getRunAs(), equalTo(reservedRolesStoreRoleDescriptor.getRunAs())); + assertThat(serviceAccountRoleDescriptor.getMetadata(), equalTo(reservedRolesStoreRoleDescriptor.getMetadata())); } public void testElasticFleetPrivileges() { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java index 544345b358ce9..b737726589444 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/service/ServiceAccountServiceTests.java @@ -122,7 +122,7 @@ public void stopThreadPool() { public void testGetServiceAccountPrincipals() { assertThat(ServiceAccountService.getServiceAccountPrincipals(), - containsInAnyOrder("elastic/fleet-server", "elastic/kibana-system")); + containsInAnyOrder("elastic/fleet-server", "elastic/kibana")); } public void testTryParseToken() throws IOException, IllegalAccessException { diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml index 9af355e6bd912..45e89a79a87e1 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/service_accounts/10_basic.yml @@ -16,7 +16,7 @@ teardown: - do: security.delete_service_token: namespace: elastic - service: kibana-system + service: kibana name: api-token-kibana ignore: 404 @@ -26,14 +26,14 @@ teardown: security.get_service_accounts: {} - length: { '': 2 } - is_true: "elastic/fleet-server" - - is_true: "elastic/kibana-system" + - is_true: "elastic/kibana" - do: security.get_service_accounts: namespace: elastic - length: { '': 2 } - is_true: "elastic/fleet-server" - - is_true: "elastic/kibana-system" + - is_true: "elastic/kibana" - do: security.get_service_accounts: @@ -59,7 +59,7 @@ teardown: - do: security.create_service_token: namespace: elastic - service: kibana-system + service: kibana name: api-token-kibana - is_true: created @@ -92,9 +92,9 @@ teardown: Authorization: Bearer ${service_token_kibana} security.authenticate: {} - - match: { username: "elastic/kibana-system" } + - match: { username: "elastic/kibana" } - match: { roles: [] } - - match: { full_name: "Service account - elastic/kibana-system" } + - match: { full_name: "Service account - elastic/kibana" } - match: { "token.name": "api-token-kibana" } - do: @@ -121,7 +121,7 @@ teardown: - do: security.clear_cached_service_tokens: namespace: elastic - service: kibana-system + service: kibana name: api-token-kibana - match: { _nodes.failed: 0 } @@ -137,7 +137,7 @@ teardown: - do: security.clear_cached_service_tokens: namespace: elastic - service: kibana-system + service: kibana name: [ "api-token-kibana", "does-not-exist-either" ] - match: { _nodes.failed: 0 } From 5c7bc7bbe0ad084a17a4791fe1acfcdee93f1cdc Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Fri, 13 Aug 2021 14:06:27 +0300 Subject: [PATCH 3/5] Temporarily disable restCompatTest until backport --- rest-api-spec/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index fe1ca824479e2..418211a99cba2 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -85,7 +85,7 @@ def v7compatibilityNotSupportedTests = { 'search/340_type_query/type query', //#47207 type query throws exception in compatible mode 'search.aggregation/200_top_hits_metric/top_hits aggregation with sequence numbers', // #42809 the use nested path and filter sort throws an exception 'search/310_match_bool_prefix/multi_match multiple fields with cutoff_frequency throws exception', //#42654 cutoff_frequency, common terms are not supported. Throwing an exception - + 'service_accounts/10_basic/Test get service accounts', //#76449, will remove upon backport ] } From d0da8010ebacf9507f77fe38308b6b5e4eb473f1 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Fri, 13 Aug 2021 14:38:24 +0300 Subject: [PATCH 4/5] properly mute test for rest compat --- rest-api-spec/build.gradle | 1 - x-pack/plugin/build.gradle | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rest-api-spec/build.gradle b/rest-api-spec/build.gradle index 418211a99cba2..50202cc02fb4c 100644 --- a/rest-api-spec/build.gradle +++ b/rest-api-spec/build.gradle @@ -85,7 +85,6 @@ def v7compatibilityNotSupportedTests = { 'search/340_type_query/type query', //#47207 type query throws exception in compatible mode 'search.aggregation/200_top_hits_metric/top_hits aggregation with sequence numbers', // #42809 the use nested path and filter sort throws an exception 'search/310_match_bool_prefix/multi_match multiple fields with cutoff_frequency throws exception', //#42654 cutoff_frequency, common terms are not supported. Throwing an exception - 'service_accounts/10_basic/Test get service accounts', //#76449, will remove upon backport ] } diff --git a/x-pack/plugin/build.gradle b/x-pack/plugin/build.gradle index cdc08b15f1333..af0f1372d75a6 100644 --- a/x-pack/plugin/build.gradle +++ b/x-pack/plugin/build.gradle @@ -153,7 +153,8 @@ def v7compatibilityNotSupportedTests = { // a type field was added to cat.ml_trained_models #73660, this is a backwards compatible change. // still this is a cat api, and we don't support them with rest api compatibility. (the test would be very hard to transform too) - 'ml/trained_model_cat_apis/Test cat trained models' + 'ml/trained_model_cat_apis/Test cat trained models', + 'service_accounts/10_basic/Test get service accounts', //#76449, will remove upon backport ] } From 786517756d101f65ca0a177716b8fc460f2b6b03 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Fri, 13 Aug 2021 15:13:49 +0300 Subject: [PATCH 5/5] Bring test class from existing PR --- ...TransportGetServiceAccountActionTests.java | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/service/TransportGetServiceAccountActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/service/TransportGetServiceAccountActionTests.java index 77af6fe2ef889..0204932509e0e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/service/TransportGetServiceAccountActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/service/TransportGetServiceAccountActionTests.java @@ -14,11 +14,15 @@ import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountRequest; import org.elasticsearch.xpack.core.security.action.service.GetServiceAccountResponse; +import org.elasticsearch.xpack.core.security.action.service.ServiceAccountInfo; import org.elasticsearch.xpack.security.authc.support.HttpTlsRuntimeCheck; import org.junit.Before; +import java.util.Arrays; import java.util.Collections; +import java.util.stream.Collectors; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; @@ -43,23 +47,33 @@ public void init() { } public void testDoExecute() { - final GetServiceAccountRequest request1 = randomFrom( - new GetServiceAccountRequest(null, null), - new GetServiceAccountRequest("elastic", null), - new GetServiceAccountRequest("elastic", "fleet-server")); + final GetServiceAccountRequest request1 = randomFrom(new GetServiceAccountRequest(null, null), + new GetServiceAccountRequest("elastic", null)); final PlainActionFuture future1 = new PlainActionFuture<>(); transportGetServiceAccountAction.doExecute(mock(Task.class), request1, future1); final GetServiceAccountResponse getServiceAccountResponse1 = future1.actionGet(); - assertThat(getServiceAccountResponse1.getServiceAccountInfos().length, equalTo(1)); - assertThat(getServiceAccountResponse1.getServiceAccountInfos()[0].getPrincipal(), equalTo("elastic/fleet-server")); + assertThat(getServiceAccountResponse1.getServiceAccountInfos().length, equalTo(2)); + assertThat( + Arrays.stream(getServiceAccountResponse1.getServiceAccountInfos()) + .map(ServiceAccountInfo::getPrincipal) + .collect(Collectors.toList()), + containsInAnyOrder("elastic/fleet-server", "elastic/kibana") + ); - final GetServiceAccountRequest request2 = randomFrom( - new GetServiceAccountRequest("foo", null), - new GetServiceAccountRequest("elastic", "foo"), - new GetServiceAccountRequest("foo", "bar")); + final GetServiceAccountRequest request2 = new GetServiceAccountRequest("elastic", "fleet-server"); final PlainActionFuture future2 = new PlainActionFuture<>(); transportGetServiceAccountAction.doExecute(mock(Task.class), request2, future2); final GetServiceAccountResponse getServiceAccountResponse2 = future2.actionGet(); - assertThat(getServiceAccountResponse2.getServiceAccountInfos().length, equalTo(0)); + assertThat(getServiceAccountResponse2.getServiceAccountInfos().length, equalTo(1)); + assertThat(getServiceAccountResponse2.getServiceAccountInfos()[0].getPrincipal(), equalTo("elastic/fleet-server")); + + final GetServiceAccountRequest request3 = randomFrom( + new GetServiceAccountRequest("foo", null), + new GetServiceAccountRequest("elastic", "foo"), + new GetServiceAccountRequest("foo", "bar")); + final PlainActionFuture future3 = new PlainActionFuture<>(); + transportGetServiceAccountAction.doExecute(mock(Task.class), request3, future3); + final GetServiceAccountResponse getServiceAccountResponse3 = future3.actionGet(); + assertThat(getServiceAccountResponse3.getServiceAccountInfos().length, equalTo(0)); } }