[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
This commit is contained in:
parent
dcbb1154bf
commit
3189ef49a5
|
@ -52,6 +52,9 @@ public class XPackLicenseState {
|
||||||
messages.put(XPackField.LOGSTASH, new String[] {
|
messages.put(XPackField.LOGSTASH, new String[] {
|
||||||
"Logstash will continue to poll centrally-managed pipelines"
|
"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[] {
|
messages.put(XPackField.DEPRECATION, new String[] {
|
||||||
"Deprecation APIs are disabled"
|
"Deprecation APIs are disabled"
|
||||||
});
|
});
|
||||||
|
@ -81,6 +84,7 @@ public class XPackLicenseState {
|
||||||
messages.put(XPackField.GRAPH, XPackLicenseState::graphAcknowledgementMessages);
|
messages.put(XPackField.GRAPH, XPackLicenseState::graphAcknowledgementMessages);
|
||||||
messages.put(XPackField.MACHINE_LEARNING, XPackLicenseState::machineLearningAcknowledgementMessages);
|
messages.put(XPackField.MACHINE_LEARNING, XPackLicenseState::machineLearningAcknowledgementMessages);
|
||||||
messages.put(XPackField.LOGSTASH, XPackLicenseState::logstashAcknowledgementMessages);
|
messages.put(XPackField.LOGSTASH, XPackLicenseState::logstashAcknowledgementMessages);
|
||||||
|
messages.put(XPackField.BEATS, XPackLicenseState::beatsAcknowledgementMessages);
|
||||||
messages.put(XPackField.SQL, XPackLicenseState::sqlAcknowledgementMessages);
|
messages.put(XPackField.SQL, XPackLicenseState::sqlAcknowledgementMessages);
|
||||||
ACKNOWLEDGMENT_MESSAGES = Collections.unmodifiableMap(messages);
|
ACKNOWLEDGMENT_MESSAGES = Collections.unmodifiableMap(messages);
|
||||||
}
|
}
|
||||||
|
@ -205,11 +209,7 @@ public class XPackLicenseState {
|
||||||
private static String[] logstashAcknowledgementMessages(OperationMode currentMode, OperationMode newMode) {
|
private static String[] logstashAcknowledgementMessages(OperationMode currentMode, OperationMode newMode) {
|
||||||
switch (newMode) {
|
switch (newMode) {
|
||||||
case BASIC:
|
case BASIC:
|
||||||
switch (currentMode) {
|
if (isBasic(currentMode) == false) {
|
||||||
case TRIAL:
|
|
||||||
case STANDARD:
|
|
||||||
case GOLD:
|
|
||||||
case PLATINUM:
|
|
||||||
return new String[] { "Logstash will no longer poll for centrally-managed pipelines" };
|
return new String[] { "Logstash will no longer poll for centrally-managed pipelines" };
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -217,6 +217,17 @@ public class XPackLicenseState {
|
||||||
return Strings.EMPTY_ARRAY;
|
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;
|
||||||
|
}
|
||||||
|
return Strings.EMPTY_ARRAY;
|
||||||
|
}
|
||||||
|
|
||||||
private static String[] sqlAcknowledgementMessages(OperationMode currentMode, OperationMode newMode) {
|
private static String[] sqlAcknowledgementMessages(OperationMode currentMode, OperationMode newMode) {
|
||||||
switch (newMode) {
|
switch (newMode) {
|
||||||
case BASIC:
|
case BASIC:
|
||||||
|
@ -232,6 +243,10 @@ public class XPackLicenseState {
|
||||||
return Strings.EMPTY_ARRAY;
|
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. */
|
/** A wrapper for the license mode and state, to allow atomically swapping. */
|
||||||
private static class Status {
|
private static class Status {
|
||||||
|
|
||||||
|
@ -500,20 +515,17 @@ public class XPackLicenseState {
|
||||||
*/
|
*/
|
||||||
public boolean isLogstashAllowed() {
|
public boolean isLogstashAllowed() {
|
||||||
Status localStatus = status;
|
Status localStatus = status;
|
||||||
|
return localStatus.active && (isBasic(localStatus.mode) == false);
|
||||||
if (localStatus.active == false) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (localStatus.mode) {
|
/**
|
||||||
case TRIAL:
|
* Beats is allowed as long as there is an active license of type TRIAL, STANDARD, GOLD or PLATINUM
|
||||||
case GOLD:
|
* @return {@code true} as long as there is a valid license
|
||||||
case PLATINUM:
|
*/
|
||||||
case STANDARD:
|
public boolean isBeatsAllowed() {
|
||||||
return true;
|
Status localStatus = status;
|
||||||
default:
|
return localStatus.active && (isBasic(localStatus.mode) == false);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.elasticsearch.xpack.core.deprecation.DeprecationInfoAction;
|
||||||
import org.elasticsearch.xpack.core.graph.GraphFeatureSetUsage;
|
import org.elasticsearch.xpack.core.graph.GraphFeatureSetUsage;
|
||||||
import org.elasticsearch.xpack.core.graph.action.GraphExploreAction;
|
import org.elasticsearch.xpack.core.graph.action.GraphExploreAction;
|
||||||
import org.elasticsearch.xpack.core.logstash.LogstashFeatureSetUsage;
|
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.MachineLearningFeatureSetUsage;
|
||||||
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.CloseJobAction;
|
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),
|
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.GRAPH, GraphFeatureSetUsage::new),
|
||||||
// logstash
|
// logstash
|
||||||
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.LOGSTASH, LogstashFeatureSetUsage::new),
|
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.LOGSTASH, LogstashFeatureSetUsage::new),
|
||||||
|
// beats
|
||||||
|
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.BEATS, BeatsFeatureSetUsage::new),
|
||||||
// ML - Custom metadata
|
// ML - Custom metadata
|
||||||
new NamedWriteableRegistry.Entry(MetaData.Custom.class, "ml", MlMetadata::new),
|
new NamedWriteableRegistry.Entry(MetaData.Custom.class, "ml", MlMetadata::new),
|
||||||
new NamedWriteableRegistry.Entry(NamedDiff.class, "ml", MlMetadata.MlMetadataDiff::new),
|
new NamedWriteableRegistry.Entry(NamedDiff.class, "ml", MlMetadata.MlMetadataDiff::new),
|
||||||
|
|
|
@ -19,6 +19,8 @@ public final class XPackField {
|
||||||
public static final String MACHINE_LEARNING = "ml";
|
public static final String MACHINE_LEARNING = "ml";
|
||||||
/** Name constant for the Logstash feature. */
|
/** Name constant for the Logstash feature. */
|
||||||
public static final String LOGSTASH = "logstash";
|
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. */
|
/** Name constant for the Deprecation API feature. */
|
||||||
public static final String DEPRECATION = "deprecation";
|
public static final String DEPRECATION = "deprecation";
|
||||||
/** Name constant for the upgrade feature. */
|
/** Name constant for the upgrade feature. */
|
||||||
|
|
|
@ -67,6 +67,10 @@ public class XPackSettings {
|
||||||
public static final Setting<Boolean> LOGSTASH_ENABLED = Setting.boolSetting("xpack.logstash.enabled", true,
|
public static final Setting<Boolean> LOGSTASH_ENABLED = Setting.boolSetting("xpack.logstash.enabled", true,
|
||||||
Setting.Property.NodeScope);
|
Setting.Property.NodeScope);
|
||||||
|
|
||||||
|
/** Setting for enabling or disabling Beats extensions. Defaults to true. */
|
||||||
|
public static final Setting<Boolean> BEATS_ENABLED = Setting.boolSetting("xpack.beats.enabled", true,
|
||||||
|
Setting.Property.NodeScope);
|
||||||
|
|
||||||
/** Setting for enabling or disabling TLS. Defaults to false. */
|
/** Setting for enabling or disabling TLS. Defaults to false. */
|
||||||
public static final Setting<Boolean> TRANSPORT_SSL_ENABLED = Setting.boolSetting("xpack.security.transport.ssl.enabled", false,
|
public static final Setting<Boolean> TRANSPORT_SSL_ENABLED = Setting.boolSetting("xpack.security.transport.ssl.enabled", false,
|
||||||
Property.NodeScope);
|
Property.NodeScope);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -80,11 +80,19 @@ public class ReservedRolesStore {
|
||||||
new RoleDescriptor.IndicesPrivileges[] {
|
new RoleDescriptor.IndicesPrivileges[] {
|
||||||
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build(),
|
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build(),
|
||||||
RoleDescriptor.IndicesPrivileges.builder()
|
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))
|
null, MetadataUtils.DEFAULT_RESERVED_METADATA))
|
||||||
.put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME},
|
.put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME},
|
||||||
null, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
|
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,
|
.put(UsernamesField.BEATS_ROLE, new RoleDescriptor(UsernamesField.BEATS_ROLE,
|
||||||
new String[] { "monitor", MonitoringBulkAction.NAME}, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
|
new String[] { "monitor", MonitoringBulkAction.NAME}, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
|
||||||
.put("machine_learning_user", new RoleDescriptor("machine_learning_user", new String[] { "monitor_ml" },
|
.put("machine_learning_user", new RoleDescriptor("machine_learning_user", new String[] { "monitor_ml" },
|
||||||
|
|
|
@ -132,6 +132,7 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
||||||
assertThat(ReservedRolesStore.isReserved("watcher_user"), is(true));
|
assertThat(ReservedRolesStore.isReserved("watcher_user"), is(true));
|
||||||
assertThat(ReservedRolesStore.isReserved("watcher_admin"), is(true));
|
assertThat(ReservedRolesStore.isReserved("watcher_admin"), is(true));
|
||||||
assertThat(ReservedRolesStore.isReserved("kibana_dashboard_only_user"), 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(XPackUser.ROLE_NAME), is(true));
|
||||||
assertThat(ReservedRolesStore.isReserved(LogstashSystemUser.ROLE_NAME), is(true));
|
assertThat(ReservedRolesStore.isReserved(LogstashSystemUser.ROLE_NAME), is(true));
|
||||||
assertThat(ReservedRolesStore.isReserved(BeatsSystemUser.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(GetAction.NAME).test(index), is(true));
|
||||||
assertThat(kibanaRole.indices().allowedIndicesMatcher(READ_CROSS_CLUSTER_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() {
|
public void testKibanaUserRole() {
|
||||||
|
@ -478,6 +493,39 @@ public class ReservedRolesStoreTests extends ESTestCase {
|
||||||
is(false));
|
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() {
|
public void testBeatsSystemRole() {
|
||||||
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(BeatsSystemUser.ROLE_NAME);
|
RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(BeatsSystemUser.ROLE_NAME);
|
||||||
assertNotNull(roleDescriptor);
|
assertNotNull(roleDescriptor);
|
||||||
|
|
Loading…
Reference in New Issue