From 3189ef49a5d4de87ec906764e7d703445df0c0dc Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 10 Jul 2018 10:06:41 -0700 Subject: [PATCH] [X-Pack] Beats centralized management: security role + licensing (#30520) * Adding Beats x-pack plugin + index templates * Adding built-in roles for Beats central management * Fixing typo * Refactoring: extract common code into method * More refactoring for more code reuse * Use a single index for Beats management * Rename "fragment" to "block" * Adding configuration block type * Expand kibana_system role to include Beats management index privileges * Fixing syntax * Adding test * Adding asserting for reserved role * Fixing privileges * Updating template * Removing beats plugin * Fixing tests * Fixing role variable name * Fixing assertions * Switching to preferred syntax for boolean false checks * Making class final * Making variables final * Updating Basic license message to be more accurate --- .../license/XPackLicenseState.java | 48 ++++++++++++------- .../xpack/core/XPackClientPlugin.java | 3 ++ .../elasticsearch/xpack/core/XPackField.java | 2 + .../xpack/core/XPackSettings.java | 4 ++ .../core/beats/BeatsFeatureSetUsage.java | 24 ++++++++++ .../authz/store/ReservedRolesStore.java | 10 +++- .../authz/store/ReservedRolesStoreTests.java | 48 +++++++++++++++++++ 7 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/beats/BeatsFeatureSetUsage.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java index e58c5eda063..ea30e30ae3c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/XPackLicenseState.java @@ -52,6 +52,9 @@ public class XPackLicenseState { messages.put(XPackField.LOGSTASH, new String[] { "Logstash will continue to poll centrally-managed pipelines" }); + messages.put(XPackField.BEATS, new String[] { + "Beats will continue to poll centrally-managed configuration" + }); messages.put(XPackField.DEPRECATION, new String[] { "Deprecation APIs are disabled" }); @@ -81,6 +84,7 @@ public class XPackLicenseState { messages.put(XPackField.GRAPH, XPackLicenseState::graphAcknowledgementMessages); messages.put(XPackField.MACHINE_LEARNING, XPackLicenseState::machineLearningAcknowledgementMessages); messages.put(XPackField.LOGSTASH, XPackLicenseState::logstashAcknowledgementMessages); + messages.put(XPackField.BEATS, XPackLicenseState::beatsAcknowledgementMessages); messages.put(XPackField.SQL, XPackLicenseState::sqlAcknowledgementMessages); ACKNOWLEDGMENT_MESSAGES = Collections.unmodifiableMap(messages); } @@ -205,12 +209,19 @@ public class XPackLicenseState { private static String[] logstashAcknowledgementMessages(OperationMode currentMode, OperationMode newMode) { switch (newMode) { case BASIC: - switch (currentMode) { - case TRIAL: - case STANDARD: - case GOLD: - case PLATINUM: - return new String[] { "Logstash will no longer poll for centrally-managed pipelines" }; + if (isBasic(currentMode) == false) { + return new String[] { "Logstash will no longer poll for centrally-managed pipelines" }; + } + break; + } + return Strings.EMPTY_ARRAY; + } + + private static String[] beatsAcknowledgementMessages(OperationMode currentMode, OperationMode newMode) { + switch (newMode) { + case BASIC: + if (isBasic(currentMode) == false) { + return new String[] { "Beats will no longer be able to use centrally-managed configuration" }; } break; } @@ -232,6 +243,10 @@ public class XPackLicenseState { return Strings.EMPTY_ARRAY; } + private static boolean isBasic(OperationMode mode) { + return mode == OperationMode.BASIC; + } + /** A wrapper for the license mode and state, to allow atomically swapping. */ private static class Status { @@ -500,20 +515,17 @@ public class XPackLicenseState { */ public boolean isLogstashAllowed() { Status localStatus = status; + return localStatus.active && (isBasic(localStatus.mode) == false); + } - if (localStatus.active == false) { - return false; - } + /** + * Beats is allowed as long as there is an active license of type TRIAL, STANDARD, GOLD or PLATINUM + * @return {@code true} as long as there is a valid license + */ + public boolean isBeatsAllowed() { + Status localStatus = status; + return localStatus.active && (isBasic(localStatus.mode) == false); - switch (localStatus.mode) { - case TRIAL: - case GOLD: - case PLATINUM: - case STANDARD: - return true; - default: - return false; - } } /** diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java index d3ddac32899..aa60456d805 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java @@ -42,6 +42,7 @@ import org.elasticsearch.xpack.core.deprecation.DeprecationInfoAction; import org.elasticsearch.xpack.core.graph.GraphFeatureSetUsage; import org.elasticsearch.xpack.core.graph.action.GraphExploreAction; import org.elasticsearch.xpack.core.logstash.LogstashFeatureSetUsage; +import org.elasticsearch.xpack.core.beats.BeatsFeatureSetUsage; import org.elasticsearch.xpack.core.ml.MachineLearningFeatureSetUsage; import org.elasticsearch.xpack.core.ml.MlMetadata; import org.elasticsearch.xpack.core.ml.action.CloseJobAction; @@ -320,6 +321,8 @@ public class XPackClientPlugin extends Plugin implements ActionPlugin, NetworkPl new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.GRAPH, GraphFeatureSetUsage::new), // logstash new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.LOGSTASH, LogstashFeatureSetUsage::new), + // beats + new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.BEATS, BeatsFeatureSetUsage::new), // ML - Custom metadata new NamedWriteableRegistry.Entry(MetaData.Custom.class, "ml", MlMetadata::new), new NamedWriteableRegistry.Entry(NamedDiff.class, "ml", MlMetadata.MlMetadataDiff::new), diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java index dd482c4e22d..70eb047c8ed 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java @@ -19,6 +19,8 @@ public final class XPackField { public static final String MACHINE_LEARNING = "ml"; /** Name constant for the Logstash feature. */ public static final String LOGSTASH = "logstash"; + /** Name constant for the Beats feature. */ + public static final String BEATS = "beats"; /** Name constant for the Deprecation API feature. */ public static final String DEPRECATION = "deprecation"; /** Name constant for the upgrade feature. */ diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java index b0d0c4f2c2e..8c4c5e2c760 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackSettings.java @@ -67,6 +67,10 @@ public class XPackSettings { public static final Setting LOGSTASH_ENABLED = Setting.boolSetting("xpack.logstash.enabled", true, Setting.Property.NodeScope); + /** Setting for enabling or disabling Beats extensions. Defaults to true. */ + public static final Setting BEATS_ENABLED = Setting.boolSetting("xpack.beats.enabled", true, + Setting.Property.NodeScope); + /** Setting for enabling or disabling TLS. Defaults to false. */ public static final Setting TRANSPORT_SSL_ENABLED = Setting.boolSetting("xpack.security.transport.ssl.enabled", false, Property.NodeScope); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/beats/BeatsFeatureSetUsage.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/beats/BeatsFeatureSetUsage.java new file mode 100644 index 00000000000..1702bf3869d --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/beats/BeatsFeatureSetUsage.java @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.core.beats; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.xpack.core.XPackFeatureSet; +import org.elasticsearch.xpack.core.XPackField; + +import java.io.IOException; + +public final class BeatsFeatureSetUsage extends XPackFeatureSet.Usage { + + public BeatsFeatureSetUsage(StreamInput in) throws IOException { + super(in); + } + + public BeatsFeatureSetUsage(boolean available, boolean enabled) { + super(XPackField.BEATS, available, enabled); + } + +} 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 059c4dfbb65..aeb448faa9c 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 @@ -80,11 +80,19 @@ public class ReservedRolesStore { new RoleDescriptor.IndicesPrivileges[] { RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build(), RoleDescriptor.IndicesPrivileges.builder() - .indices(".monitoring-*").privileges("read", "read_cross_cluster").build() + .indices(".monitoring-*").privileges("read", "read_cross_cluster").build(), + RoleDescriptor.IndicesPrivileges.builder() + .indices(".management-beats").privileges("create_index", "read", "write").build() }, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) .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", + null, + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices(".management-beats").privileges("all").build() + }, + null, MetadataUtils.DEFAULT_RESERVED_METADATA)) .put(UsernamesField.BEATS_ROLE, new RoleDescriptor(UsernamesField.BEATS_ROLE, new String[] { "monitor", MonitoringBulkAction.NAME}, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) .put("machine_learning_user", new RoleDescriptor("machine_learning_user", new String[] { "monitor_ml" }, diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index b25f3f374b3..85d2bc16dd0 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java @@ -132,6 +132,7 @@ public class ReservedRolesStoreTests extends ESTestCase { assertThat(ReservedRolesStore.isReserved("watcher_user"), is(true)); assertThat(ReservedRolesStore.isReserved("watcher_admin"), is(true)); assertThat(ReservedRolesStore.isReserved("kibana_dashboard_only_user"), is(true)); + assertThat(ReservedRolesStore.isReserved("beats_admin"), is(true)); assertThat(ReservedRolesStore.isReserved(XPackUser.ROLE_NAME), is(true)); assertThat(ReservedRolesStore.isReserved(LogstashSystemUser.ROLE_NAME), is(true)); assertThat(ReservedRolesStore.isReserved(BeatsSystemUser.ROLE_NAME), is(true)); @@ -220,6 +221,20 @@ public class ReservedRolesStoreTests extends ESTestCase { assertThat(kibanaRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(true)); }); + + // Beats management index + final String index = ".management-beats"; + assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false)); + assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(false)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(false)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(false)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_NAME).test(index), is(false)); } public void testKibanaUserRole() { @@ -478,6 +493,39 @@ public class ReservedRolesStoreTests extends ESTestCase { is(false)); } + public void testBeatsAdminRole() { + final RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("beats_admin"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + final Role beatsAdminRole = Role.builder(roleDescriptor, null).build(); + assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME), is(false)); + assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false)); + assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME), is(false)); + + assertThat(beatsAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); + + assertThat(beatsAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), + is(false)); + + final String index = ".management-beats"; + logger.info("index name [{}]", index); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true)); + assertThat(beatsAdminRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); + } + public void testBeatsSystemRole() { RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(BeatsSystemUser.ROLE_NAME); assertNotNull(roleDescriptor);