diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java index 0ccd1aabeba..c6dc6910d97 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java @@ -315,9 +315,11 @@ public final class Role { public static final String MANAGE_PIPELINE = "manage_pipeline"; public static final String MANAGE_CCR = "manage_ccr"; public static final String READ_CCR = "read_ccr"; + public static final String MANAGE_ILM = "manage_ilm"; + public static final String READ_ILM = "read_ilm"; public static final String[] ALL_ARRAY = new String[] { NONE, ALL, MONITOR, MONITOR_ML, MONITOR_WATCHER, MONITOR_ROLLUP, MANAGE, MANAGE_ML, MANAGE_WATCHER, MANAGE_ROLLUP, MANAGE_INDEX_TEMPLATES, MANAGE_INGEST_PIPELINES, TRANSPORT_CLIENT, - MANAGE_SECURITY, MANAGE_SAML, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR }; + MANAGE_SECURITY, MANAGE_SAML, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM }; } /** @@ -338,8 +340,9 @@ public final class Role { public static final String CREATE_INDEX = "create_index"; public static final String VIEW_INDEX_METADATA = "view_index_metadata"; public static final String MANAGE_FOLLOW_INDEX = "manage_follow_index"; + public static final String MANAGE_ILM = "manage_ilm"; public static final String[] ALL_ARRAY = new String[] { NONE, ALL, READ, READ_CROSS, CREATE, INDEX, DELETE, WRITE, MONITOR, MANAGE, - DELETE_INDEX, CREATE_INDEX, VIEW_INDEX_METADATA, MANAGE_FOLLOW_INDEX }; + DELETE_INDEX, CREATE_INDEX, VIEW_INDEX_METADATA, MANAGE_FOLLOW_INDEX, MANAGE_ILM }; } } diff --git a/distribution/src/config/jvm.options b/distribution/src/config/jvm.options index d8b651231cb..2b30d6a87b4 100644 --- a/distribution/src/config/jvm.options +++ b/distribution/src/config/jvm.options @@ -45,6 +45,15 @@ # 10-:-XX:+UseG1GC # 10-:-XX:InitiatingHeapOccupancyPercent=75 +## DNS cache policy +# cache ttl in seconds for positive DNS lookups noting that this overrides the +# JDK security property networkaddress.cache.ttl; set to -1 to cache forever +-Des.networkaddress.cache.ttl=60 +# cache ttl in seconds for negative DNS lookups noting that this overrides the +# JDK security property networkaddress.cache.negative ttl; set to -1 to cache +# forever +-Des.networkaddress.cache.negative.ttl=10 + ## optimizations # pre-touch memory pages used by the JVM during initialization diff --git a/docs/reference/setup/sysconfig/dns-cache.asciidoc b/docs/reference/setup/sysconfig/dns-cache.asciidoc index 76ef4f52042..54a1e20a15a 100644 --- a/docs/reference/setup/sysconfig/dns-cache.asciidoc +++ b/docs/reference/setup/sysconfig/dns-cache.asciidoc @@ -2,17 +2,18 @@ === DNS cache settings Elasticsearch runs with a security manager in place. With a security manager in -place, the JVM defaults to caching positive hostname resolutions -indefinitely. If your Elasticsearch nodes rely on DNS in an environment where -DNS resolutions vary with time (e.g., for node-to-node discovery) then you might -want to modify the default JVM behavior. This can be modified by adding +place, the JVM defaults to caching positive hostname resolutions indefinitely +and defaults to caching negative hostname resolutions for ten +seconds. Elasticsearch overrides this behavior with default values to cache +positive lookups for sixty seconds, and to cache negative lookups for ten +seconds. These values should be suitable for most environments, including +environments where DNS resolutions vary with time. If not, you can edit the +values `es.networkaddress.cache.ttl` and `es.networkaddress.cache.negative.ttl` +in the <>. Note that the values http://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html[`networkaddress.cache.ttl=`] -to your -http://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html[Java -security policy]. Any hosts that fail to resolve will be logged. Note also that -with the Java security manager in place, the JVM defaults to caching negative -hostname resolutions for ten seconds. This can be modified by adding +and http://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html[`networkaddress.cache.negative.ttl=`] -to your +in the http://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html[Java -security policy]. \ No newline at end of file +security policy] are ignored by Elasticsearch unless you remove the settings for +`es.networkaddress.cache.ttl` and `es.networkaddress.cache.negative.ttl`. diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index 1cf88c6245f..48cf0016455 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -36,6 +36,7 @@ import org.elasticsearch.node.NodeValidationException; import java.io.IOException; import java.nio.file.Path; import java.security.Permission; +import java.security.Security; import java.util.Arrays; import java.util.Locale; @@ -72,13 +73,19 @@ class Elasticsearch extends EnvironmentAwareCommand { * Main entry point for starting elasticsearch */ public static void main(final String[] args) throws Exception { - // we want the JVM to think there is a security manager installed so that if internal policy decisions that would be based on the - // presence of a security manager or lack thereof act as if there is a security manager present (e.g., DNS cache policy) + overrideDnsCachePolicyProperties(); + /* + * We want the JVM to think there is a security manager installed so that if internal policy decisions that would be based on the + * presence of a security manager or lack thereof act as if there is a security manager present (e.g., DNS cache policy). This + * forces such policies to take effect immediately. + */ System.setSecurityManager(new SecurityManager() { + @Override public void checkPermission(Permission perm) { // grant all permissions so that we can later set the security manager to the one that we want } + }); LogConfigurator.registerErrorListener(); final Elasticsearch elasticsearch = new Elasticsearch(); @@ -88,6 +95,22 @@ class Elasticsearch extends EnvironmentAwareCommand { } } + private static void overrideDnsCachePolicyProperties() { + for (final String property : new String[] {"networkaddress.cache.ttl", "networkaddress.cache.negative.ttl" }) { + final String overrideProperty = "es." + property; + final String overrideValue = System.getProperty(overrideProperty); + if (overrideValue != null) { + try { + // round-trip the property to an integer and back to a string to ensure that it parses properly + Security.setProperty(property, Integer.toString(Integer.valueOf(overrideValue))); + } catch (final NumberFormatException e) { + throw new IllegalArgumentException( + "failed to parse [" + overrideProperty + "] with value [" + overrideValue + "]", e); + } + } + } + } + static int main(final String[] args, final Elasticsearch elasticsearch, final Terminal terminal) throws Exception { return elasticsearch.main(args, terminal); } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceAggregationBuilder.java index 333eb777ac4..acc0d2ee20b 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceAggregationBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceAggregationBuilder.java @@ -207,7 +207,9 @@ public abstract class MultiValuesSourceAggregationBuilder fieldEntry : fields.entrySet()) { + builder.field(fieldEntry.getKey(), fieldEntry.getValue()); + } } if (format != null) { builder.field(CommonFields.FORMAT.getPreferredName(), format); diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfig.java b/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfig.java index 56ceae69ff7..fbc3081758f 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfig.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfig.java @@ -25,16 +25,17 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.ObjectParser; -import org.elasticsearch.common.xcontent.ToXContentFragment; +import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.script.Script; import org.joda.time.DateTimeZone; import java.io.IOException; +import java.util.Objects; import java.util.function.BiFunction; -public class MultiValuesSourceFieldConfig implements Writeable, ToXContentFragment { +public class MultiValuesSourceFieldConfig implements Writeable, ToXContentObject { private String fieldName; private Object missing; private Script script; @@ -110,6 +111,7 @@ public class MultiValuesSourceFieldConfig implements Writeable, ToXContentFragme @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); if (missing != null) { builder.field(ParseField.CommonFields.MISSING.getPreferredName(), missing); } @@ -120,11 +122,33 @@ public class MultiValuesSourceFieldConfig implements Writeable, ToXContentFragme builder.field(ParseField.CommonFields.FIELD.getPreferredName(), fieldName); } if (timeZone != null) { - builder.field(ParseField.CommonFields.TIME_ZONE.getPreferredName(), timeZone); + builder.field(ParseField.CommonFields.TIME_ZONE.getPreferredName(), timeZone.getID()); } + builder.endObject(); return builder; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MultiValuesSourceFieldConfig that = (MultiValuesSourceFieldConfig) o; + return Objects.equals(fieldName, that.fieldName) + && Objects.equals(missing, that.missing) + && Objects.equals(script, that.script) + && Objects.equals(timeZone, that.timeZone); + } + + @Override + public int hashCode() { + return Objects.hash(fieldName, missing, script, timeZone); + } + + @Override + public String toString() { + return Strings.toString(this); + } + public static class Builder { private String fieldName; private Object missing = null; diff --git a/server/src/test/java/org/elasticsearch/discovery/ZenFaultDetectionTests.java b/server/src/test/java/org/elasticsearch/discovery/ZenFaultDetectionTests.java index 03c0df43591..fbe3ef00a06 100644 --- a/server/src/test/java/org/elasticsearch/discovery/ZenFaultDetectionTests.java +++ b/server/src/test/java/org/elasticsearch/discovery/ZenFaultDetectionTests.java @@ -32,7 +32,7 @@ import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; -import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.discovery.zen.FaultDetection; import org.elasticsearch.discovery.zen.MasterFaultDetection; import org.elasticsearch.discovery.zen.NodesFaultDetection; @@ -43,10 +43,10 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.transport.MockTcpTransport; import org.elasticsearch.transport.TransportConnectionListener; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.transport.nio.MockNioTransport; import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.junit.After; @@ -138,8 +138,8 @@ public class ZenFaultDetectionTests extends ESTestCase { // trace zenfd actions but keep the default otherwise .putList(TransportService.TRACE_LOG_EXCLUDE_SETTING.getKey(), TransportLivenessAction.NAME) .build(), - new MockTcpTransport(settings, threadPool, BigArrays.NON_RECYCLING_INSTANCE, circuitBreakerService, - namedWriteableRegistry, new NetworkService(Collections.emptyList()), version), + new MockNioTransport(settings, version, threadPool, new NetworkService(Collections.emptyList()), + PageCacheRecycler.NON_RECYCLING_INSTANCE, namedWriteableRegistry, circuitBreakerService), threadPool, TransportService.NOOP_TRANSPORT_INTERCEPTOR, (boundAddress) -> diff --git a/server/src/test/java/org/elasticsearch/discovery/zen/UnicastZenPingTests.java b/server/src/test/java/org/elasticsearch/discovery/zen/UnicastZenPingTests.java index 1c0329a51e3..c380341b51b 100644 --- a/server/src/test/java/org/elasticsearch/discovery/zen/UnicastZenPingTests.java +++ b/server/src/test/java/org/elasticsearch/discovery/zen/UnicastZenPingTests.java @@ -38,6 +38,7 @@ import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.ConcurrentCollections; import org.elasticsearch.common.util.concurrent.EsExecutors; @@ -56,6 +57,7 @@ import org.elasticsearch.transport.TransportException; import org.elasticsearch.transport.TransportRequestOptions; import org.elasticsearch.transport.TransportResponseHandler; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.transport.nio.MockNioTransport; import org.junit.After; import org.junit.Before; import org.mockito.Matchers; @@ -143,14 +145,14 @@ public class UnicastZenPingTests extends ESTestCase { NetworkService networkService = new NetworkService(Collections.emptyList()); - final BiFunction supplier = (s, v) -> new MockTcpTransport( + final BiFunction supplier = (s, v) -> new MockNioTransport( s, + v, threadPool, - BigArrays.NON_RECYCLING_INSTANCE, - new NoneCircuitBreakerService(), - new NamedWriteableRegistry(Collections.emptyList()), networkService, - v); + PageCacheRecycler.NON_RECYCLING_INSTANCE, + new NamedWriteableRegistry(Collections.emptyList()), + new NoneCircuitBreakerService()); NetworkHandle handleA = startServices(settings, threadPool, "UZP_A", Version.CURRENT, supplier); closeables.push(handleA.transportService); @@ -268,14 +270,14 @@ public class UnicastZenPingTests extends ESTestCase { final NetworkService networkService = new NetworkService(Collections.emptyList()); final Map addresses = new HashMap<>(); - final BiFunction supplier = (s, v) -> new MockTcpTransport( + final BiFunction supplier = (s, v) -> new MockNioTransport( s, + v, threadPool, - BigArrays.NON_RECYCLING_INSTANCE, - new NoneCircuitBreakerService(), - new NamedWriteableRegistry(Collections.emptyList()), networkService, - v) { + PageCacheRecycler.NON_RECYCLING_INSTANCE, + new NamedWriteableRegistry(Collections.emptyList()), + new NoneCircuitBreakerService()) { @Override public TransportAddress[] addressesFromString(String address, int perAddressLimit) throws UnknownHostException { final TransportAddress[] transportAddresses = addresses.get(address); @@ -634,14 +636,14 @@ public class UnicastZenPingTests extends ESTestCase { NetworkService networkService = new NetworkService(Collections.emptyList()); - final BiFunction supplier = (s, v) -> new MockTcpTransport( + final BiFunction supplier = (s, v) -> new MockNioTransport( s, + v, threadPool, - BigArrays.NON_RECYCLING_INSTANCE, - new NoneCircuitBreakerService(), - new NamedWriteableRegistry(Collections.emptyList()), networkService, - v); + PageCacheRecycler.NON_RECYCLING_INSTANCE, + new NamedWriteableRegistry(Collections.emptyList()), + new NoneCircuitBreakerService()); NetworkHandle handleA = startServices(settings, threadPool, "UZP_A", Version.CURRENT, supplier, EnumSet.allOf(Role.class)); closeables.push(handleA.transportService); @@ -689,15 +691,14 @@ public class UnicastZenPingTests extends ESTestCase { public void testInvalidHosts() throws InterruptedException { final Logger logger = mock(Logger.class); - final NetworkService networkService = new NetworkService(Collections.emptyList()); - final Transport transport = new MockTcpTransport( + final Transport transport = new MockNioTransport( Settings.EMPTY, + Version.CURRENT, threadPool, - BigArrays.NON_RECYCLING_INSTANCE, - new NoneCircuitBreakerService(), + new NetworkService(Collections.emptyList()), + PageCacheRecycler.NON_RECYCLING_INSTANCE, new NamedWriteableRegistry(Collections.emptyList()), - networkService, - Version.CURRENT) { + new NoneCircuitBreakerService()) { @Override public BoundTransportAddress boundAddress() { return new BoundTransportAddress( diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/metrics/weighted_avg/WeightedAvgAggregationBuilderTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/weighted_avg/WeightedAvgAggregationBuilderTests.java new file mode 100644 index 00000000000..0b7c5cd0f86 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/search/aggregations/metrics/weighted_avg/WeightedAvgAggregationBuilderTests.java @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.search.aggregations.metrics.weighted_avg; + +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.SearchModule; +import org.elasticsearch.search.aggregations.AggregatorFactories; +import org.elasticsearch.search.aggregations.metrics.WeightedAvgAggregationBuilder; +import org.elasticsearch.search.aggregations.support.MultiValuesSourceFieldConfig; +import org.elasticsearch.test.AbstractSerializingTestCase; +import org.junit.Before; + +import java.io.IOException; +import java.util.Collections; + +import static org.hamcrest.Matchers.hasSize; + +public class WeightedAvgAggregationBuilderTests extends AbstractSerializingTestCase { + String aggregationName; + + @Before + public void setupName() { + aggregationName = randomAlphaOfLength(10); + } + + @Override + protected NamedXContentRegistry xContentRegistry() { + SearchModule searchModule = new SearchModule(Settings.EMPTY, false, Collections.emptyList()); + return new NamedXContentRegistry(searchModule.getNamedXContents()); + } + + @Override + protected WeightedAvgAggregationBuilder doParseInstance(XContentParser parser) throws IOException { + assertSame(XContentParser.Token.START_OBJECT, parser.nextToken()); + AggregatorFactories.Builder parsed = AggregatorFactories.parseAggregators(parser); + assertThat(parsed.getAggregatorFactories(), hasSize(1)); + assertThat(parsed.getPipelineAggregatorFactories(), hasSize(0)); + WeightedAvgAggregationBuilder agg = (WeightedAvgAggregationBuilder) parsed.getAggregatorFactories().iterator().next(); + assertNull(parser.nextToken()); + assertNotNull(agg); + return agg; + } + + @Override + protected WeightedAvgAggregationBuilder createTestInstance() { + MultiValuesSourceFieldConfig valueConfig = new MultiValuesSourceFieldConfig.Builder().setFieldName("value_field").build(); + MultiValuesSourceFieldConfig weightConfig = new MultiValuesSourceFieldConfig.Builder().setFieldName("weight_field").build(); + WeightedAvgAggregationBuilder aggregationBuilder = new WeightedAvgAggregationBuilder(aggregationName) + .value(valueConfig) + .weight(weightConfig); + return aggregationBuilder; + } + + @Override + protected Writeable.Reader instanceReader() { + return WeightedAvgAggregationBuilder::new; + } +} diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfigTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfigTests.java index ac1c07a4049..5007784a3d9 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfigTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/support/MultiValuesSourceFieldConfigTests.java @@ -19,12 +19,37 @@ package org.elasticsearch.search.aggregations.support; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.script.Script; -import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.AbstractSerializingTestCase; +import org.joda.time.DateTimeZone; + +import java.io.IOException; import static org.hamcrest.Matchers.equalTo; -public class MultiValuesSourceFieldConfigTests extends ESTestCase { +public class MultiValuesSourceFieldConfigTests extends AbstractSerializingTestCase { + + @Override + protected MultiValuesSourceFieldConfig doParseInstance(XContentParser parser) throws IOException { + return MultiValuesSourceFieldConfig.PARSER.apply(true, true).apply(parser, null).build(); + } + + @Override + protected MultiValuesSourceFieldConfig createTestInstance() { + String field = randomAlphaOfLength(10); + Object missing = randomBoolean() ? randomAlphaOfLength(10) : null; + DateTimeZone timeZone = randomBoolean() ? randomDateTimeZone() : null; + return new MultiValuesSourceFieldConfig.Builder() + .setFieldName(field).setMissing(missing).setScript(null).setTimeZone(timeZone).build(); + } + + @Override + protected Writeable.Reader instanceReader() { + return MultiValuesSourceFieldConfig::new; + } + public void testMissingFieldScript() { IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new MultiValuesSourceFieldConfig.Builder().build()); assertThat(e.getMessage(), equalTo("[field] and [script] cannot both be null. Please specify one or the other.")); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java index 9c03941b031..fba595e7a09 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java @@ -9,6 +9,8 @@ import org.apache.lucene.util.automaton.Automaton; import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.MapBuilder; +import org.elasticsearch.xpack.core.indexlifecycle.action.GetLifecycleAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.GetStatusAction; import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.action.token.RefreshTokenAction; import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; @@ -47,6 +49,8 @@ public final class ClusterPrivilege extends Privilege { private static final Automaton MANAGE_CCR_AUTOMATON = patterns("cluster:admin/xpack/ccr/*", ClusterStateAction.NAME, HasPrivilegesAction.NAME); private static final Automaton READ_CCR_AUTOMATON = patterns(ClusterStateAction.NAME, HasPrivilegesAction.NAME); + private static final Automaton MANAGE_ILM_AUTOMATON = patterns("cluster:admin/ilm/*"); + private static final Automaton READ_ILM_AUTOMATON = patterns(GetLifecycleAction.NAME, GetStatusAction.NAME); public static final ClusterPrivilege NONE = new ClusterPrivilege("none", Automatons.EMPTY); public static final ClusterPrivilege ALL = new ClusterPrivilege("all", ALL_CLUSTER_AUTOMATON); @@ -69,6 +73,8 @@ public final class ClusterPrivilege extends Privilege { public static final ClusterPrivilege MANAGE_PIPELINE = new ClusterPrivilege("manage_pipeline", "cluster:admin/ingest/pipeline/*"); public static final ClusterPrivilege MANAGE_CCR = new ClusterPrivilege("manage_ccr", MANAGE_CCR_AUTOMATON); public static final ClusterPrivilege READ_CCR = new ClusterPrivilege("read_ccr", READ_CCR_AUTOMATON); + public static final ClusterPrivilege MANAGE_ILM = new ClusterPrivilege("manage_ilm", MANAGE_ILM_AUTOMATON); + public static final ClusterPrivilege READ_ILM = new ClusterPrivilege("read_ilm", READ_ILM_AUTOMATON); public static final Predicate ACTION_MATCHER = ClusterPrivilege.ALL.predicate(); @@ -92,6 +98,8 @@ public final class ClusterPrivilege extends Privilege { .put("manage_rollup", MANAGE_ROLLUP) .put("manage_ccr", MANAGE_CCR) .put("read_ccr", READ_CCR) + .put("manage_ilm", MANAGE_ILM) + .put("read_ilm", READ_ILM) .immutableMap(); private static final ConcurrentHashMap, ClusterPrivilege> CACHE = new ConcurrentHashMap<>(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java index e7a1a820423..3a92c08704e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.xpack.core.ccr.action.PutFollowAction; import org.elasticsearch.xpack.core.ccr.action.UnfollowAction; +import org.elasticsearch.xpack.core.indexlifecycle.action.ExplainLifecycleAction; import org.elasticsearch.xpack.core.security.support.Automatons; import java.util.Arrays; @@ -57,9 +58,11 @@ public final class IndexPrivilege extends Privilege { private static final Automaton DELETE_INDEX_AUTOMATON = patterns(DeleteIndexAction.NAME); private static final Automaton VIEW_METADATA_AUTOMATON = patterns(GetAliasesAction.NAME, AliasesExistAction.NAME, GetIndexAction.NAME, IndicesExistsAction.NAME, GetFieldMappingsAction.NAME + "*", GetMappingsAction.NAME, - ClusterSearchShardsAction.NAME, TypesExistsAction.NAME, ValidateQueryAction.NAME + "*", GetSettingsAction.NAME); + ClusterSearchShardsAction.NAME, TypesExistsAction.NAME, ValidateQueryAction.NAME + "*", GetSettingsAction.NAME, + ExplainLifecycleAction.NAME); private static final Automaton MANAGE_FOLLOW_INDEX_AUTOMATON = patterns(PutFollowAction.NAME, UnfollowAction.NAME, CloseIndexAction.NAME + "*"); + private static final Automaton MANAGE_ILM_AUTOMATON = patterns("indices:admin/ilm/*"); public static final IndexPrivilege NONE = new IndexPrivilege("none", Automatons.EMPTY); public static final IndexPrivilege ALL = new IndexPrivilege("all", ALL_AUTOMATON); @@ -75,6 +78,7 @@ public final class IndexPrivilege extends Privilege { public static final IndexPrivilege CREATE_INDEX = new IndexPrivilege("create_index", CREATE_INDEX_AUTOMATON); public static final IndexPrivilege VIEW_METADATA = new IndexPrivilege("view_index_metadata", VIEW_METADATA_AUTOMATON); public static final IndexPrivilege MANAGE_FOLLOW_INDEX = new IndexPrivilege("manage_follow_index", MANAGE_FOLLOW_INDEX_AUTOMATON); + public static final IndexPrivilege MANAGE_ILM = new IndexPrivilege("manage_ilm", MANAGE_ILM_AUTOMATON); private static final Map VALUES = MapBuilder.newMapBuilder() .put("none", NONE) @@ -91,6 +95,7 @@ public final class IndexPrivilege extends Privilege { .put("view_index_metadata", VIEW_METADATA) .put("read_cross_cluster", READ_CROSS_CLUSTER) .put("manage_follow_index", MANAGE_FOLLOW_INDEX) + .put("manage_ilm", MANAGE_ILM) .immutableMap(); public static final Predicate ACTION_MATCHER = ALL.predicate(); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java index f2bfd8d2d8e..58432cdf6c7 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java @@ -138,4 +138,57 @@ public class PrivilegeTests extends ESTestCase { assertThat(predicate.test("cluster:admin/xpack/whatever"), is(false)); } + public void testIlmPrivileges() { + { + Predicate predicate = ClusterPrivilege.MANAGE_ILM.predicate(); + // check cluster actions + assertThat(predicate.test("cluster:admin/ilm/delete"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/_move/post"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/put"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/start"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/stop"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/brand_new_api"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/get"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/operation_mode/get"), is(true)); + // check non-ilm action + assertThat(predicate.test("cluster:admin/whatever"), is(false)); + } + + { + Predicate predicate = ClusterPrivilege.READ_ILM.predicate(); + // check cluster actions + assertThat(predicate.test("cluster:admin/ilm/delete"), is(false)); + assertThat(predicate.test("cluster:admin/ilm/_move/post"), is(false)); + assertThat(predicate.test("cluster:admin/ilm/put"), is(false)); + assertThat(predicate.test("cluster:admin/ilm/start"), is(false)); + assertThat(predicate.test("cluster:admin/ilm/stop"), is(false)); + assertThat(predicate.test("cluster:admin/ilm/brand_new_api"), is(false)); + assertThat(predicate.test("cluster:admin/ilm/get"), is(true)); + assertThat(predicate.test("cluster:admin/ilm/operation_mode/get"), is(true)); + // check non-ilm action + assertThat(predicate.test("cluster:admin/whatever"), is(false)); + } + + { + Predicate predicate = IndexPrivilege.MANAGE_ILM.predicate(); + // check indices actions + assertThat(predicate.test("indices:admin/ilm/retry"), is(true)); + assertThat(predicate.test("indices:admin/ilm/remove_policy"), is(true)); + assertThat(predicate.test("indices:admin/ilm/brand_new_api"), is(true)); + assertThat(predicate.test("indices:admin/ilm/explain"), is(true)); + // check non-ilm action + assertThat(predicate.test("indices:admin/whatever"), is(false)); + } + + { + Predicate predicate = IndexPrivilege.VIEW_METADATA.predicate(); + // check indices actions + assertThat(predicate.test("indices:admin/ilm/retry"), is(false)); + assertThat(predicate.test("indices:admin/ilm/remove_policy"), is(false)); + assertThat(predicate.test("indices:admin/ilm/brand_new_api"), is(false)); + assertThat(predicate.test("indices:admin/ilm/explain"), is(true)); + // check non-ilm action + assertThat(predicate.test("indices:admin/whatever"), is(false)); + } + } } diff --git a/x-pack/plugin/ilm/qa/with-security/roles.yml b/x-pack/plugin/ilm/qa/with-security/roles.yml index baf89bea345..64d437bbfd2 100644 --- a/x-pack/plugin/ilm/qa/with-security/roles.yml +++ b/x-pack/plugin/ilm/qa/with-security/roles.yml @@ -1,8 +1,11 @@ ilm: cluster: - monitor - - manage + - manage_ilm indices: + - names: [ 'view-only-*' ] + privileges: + - view_index_metadata - names: [ 'ilm-*' ] privileges: - monitor diff --git a/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java b/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java index 01eb07bb35b..a0c21f4614d 100644 --- a/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java +++ b/x-pack/plugin/ilm/qa/with-security/src/test/java/org/elasticsearch/xpack/security/PermissionsIT.java @@ -119,6 +119,13 @@ public class PermissionsIT extends ESRestTestCase { }); } + public void testCanViewExplainOnUnmanagedIndex() throws Exception { + createIndexAsAdmin("view-only-ilm", indexSettingsWithPolicy, ""); + Request request = new Request("GET", "/view-only-ilm/_ilm/explain"); + // test_ilm user has permissions to view + assertOK(client().performRequest(request)); + } + private void createNewSingletonPolicy(String policy, String phaseName, LifecycleAction action) throws IOException { Phase phase = new Phase(phaseName, TimeValue.ZERO, singletonMap(action.getWriteableName(), action)); LifecyclePolicy lifecyclePolicy = new LifecyclePolicy(policy, singletonMap(phase.getName(), phase));