diff --git a/libs/core/src/main/java/org/elasticsearch/common/MemoizedSupplier.java b/libs/core/src/main/java/org/elasticsearch/common/MemoizedSupplier.java new file mode 100644 index 00000000000..d010eac5205 --- /dev/null +++ b/libs/core/src/main/java/org/elasticsearch/common/MemoizedSupplier.java @@ -0,0 +1,40 @@ +/* + * 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.common; + +import java.util.function.Supplier; + +public class MemoizedSupplier implements Supplier { + private Supplier supplier; + private T value; + + public MemoizedSupplier(Supplier supplier) { + this.supplier = supplier; + } + + @Override + public T get() { + if (supplier != null) { + value = supplier.get(); + supplier = null; + } + return value; + } +} diff --git a/x-pack/plugin/core/build.gradle b/x-pack/plugin/core/build.gradle index ff84ac5ad89..a77cd87c149 100644 --- a/x-pack/plugin/core/build.gradle +++ b/x-pack/plugin/core/build.gradle @@ -7,6 +7,7 @@ import java.nio.file.Paths apply plugin: 'elasticsearch.esplugin' apply plugin: 'elasticsearch.publish' apply plugin: 'elasticsearch.internal-cluster-test' +apply plugin: 'elasticsearch.yaml-rest-test' archivesBaseName = 'x-pack-core' @@ -57,6 +58,8 @@ dependencies { transitive = false } + yamlRestTestImplementation project(':x-pack:plugin:core') + } ext.expansions = [ @@ -143,7 +146,18 @@ thirdPartyAudit.ignoreMissingClasses( 'javax.servlet.ServletContextListener' ) -// xpack modules are installed in real clusters as the meta plugin, so -// installing them as individual plugins for integ tests doesn't make sense, -// so we disable integ tests -integTest.enabled = false +restResources { + restApi { + includeCore '*' + } +} + +testClusters.yamlRestTest { + testDistribution = 'default' + setting 'xpack.security.enabled', 'true' + setting 'xpack.license.self_generated.type', 'trial' + keystore 'bootstrap.password', 'x-pack-test-password' + user username: "x_pack_rest_user", password: "x-pack-test-password" +} + +testingConventions.enabled = false diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/GetFeatureUsageRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/GetFeatureUsageRequest.java new file mode 100644 index 00000000000..d8bddd86cc3 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/GetFeatureUsageRequest.java @@ -0,0 +1,27 @@ +/* + * 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.license; + +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.common.io.stream.StreamInput; + +import java.io.IOException; + +public class GetFeatureUsageRequest extends ActionRequest { + + public GetFeatureUsageRequest() {} + + public GetFeatureUsageRequest(StreamInput in) throws IOException { + super(in); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/GetFeatureUsageResponse.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/GetFeatureUsageResponse.java new file mode 100644 index 00000000000..335190d6bc4 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/GetFeatureUsageResponse.java @@ -0,0 +1,84 @@ +/* + * 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.license; + +import org.elasticsearch.action.ActionResponse; +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.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.List; + +public class GetFeatureUsageResponse extends ActionResponse implements ToXContentObject { + + public static class FeatureUsageInfo implements Writeable { + public final String name; + public final ZonedDateTime lastUsedTime; + public final String licenseLevel; + + public FeatureUsageInfo(String name, ZonedDateTime lastUsedTime, String licenseLevel) { + this.name = name; + this.lastUsedTime = lastUsedTime; + this.licenseLevel = licenseLevel; + } + + public FeatureUsageInfo(StreamInput in) throws IOException { + this.name = in.readString(); + this.lastUsedTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(in.readLong()), ZoneOffset.UTC); + this.licenseLevel = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + out.writeLong(lastUsedTime.toEpochSecond()); + out.writeString(licenseLevel); + } + } + + private List features; + + public GetFeatureUsageResponse(List features) { + this.features = Collections.unmodifiableList(features); + } + + public GetFeatureUsageResponse(StreamInput in) throws IOException { + this.features = in.readList(FeatureUsageInfo::new); + } + + public List getFeatures() { + return features; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeList(features); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.startArray("features"); + for (FeatureUsageInfo feature : features) { + builder.startObject(); + builder.field("name", feature.name); + builder.field("last_used", feature.lastUsedTime.toString()); + builder.field("license_level", feature.licenseLevel); + builder.endObject(); + } + builder.endArray(); + builder.endObject(); + return builder; + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/Licensing.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/Licensing.java index 295f8d8cf9e..1d9cb73a896 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/Licensing.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/Licensing.java @@ -66,7 +66,8 @@ public class Licensing implements ActionPlugin { new ActionHandler<>(PostStartTrialAction.INSTANCE, TransportPostStartTrialAction.class), new ActionHandler<>(GetTrialStatusAction.INSTANCE, TransportGetTrialStatusAction.class), new ActionHandler<>(PostStartBasicAction.INSTANCE, TransportPostStartBasicAction.class), - new ActionHandler<>(GetBasicStatusAction.INSTANCE, TransportGetBasicStatusAction.class)); + new ActionHandler<>(GetBasicStatusAction.INSTANCE, TransportGetBasicStatusAction.class), + new ActionHandler<>(TransportGetFeatureUsageAction.TYPE, TransportGetFeatureUsageAction.class)); } @Override @@ -81,6 +82,7 @@ public class Licensing implements ActionPlugin { handlers.add(new RestGetBasicStatus()); handlers.add(new RestPostStartTrialLicense()); handlers.add(new RestPostStartBasicLicense()); + handlers.add(new RestGetFeatureUsageAction()); return handlers; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetFeatureUsageAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetFeatureUsageAction.java new file mode 100644 index 00000000000..dd4cc4feab9 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestGetFeatureUsageAction.java @@ -0,0 +1,37 @@ +/* + * 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.license; + +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import static org.elasticsearch.rest.RestRequest.Method.GET; + +public class RestGetFeatureUsageAction extends BaseRestHandler { + + @Override + public String getName() { + return "get_feature_usage"; + } + + @Override + public List routes() { + return Collections.singletonList(new Route(GET, "/_license/feature_usage")); + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + return channel -> client.execute(TransportGetFeatureUsageAction.TYPE, new GetFeatureUsageRequest(), + new RestToXContentListener<>(channel)); + } +} diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetFeatureUsageAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetFeatureUsageAction.java new file mode 100644 index 00000000000..b0dd78f94b4 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetFeatureUsageAction.java @@ -0,0 +1,53 @@ +/* + * 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.license; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.ActionType; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.transport.TransportService; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class TransportGetFeatureUsageAction extends HandledTransportAction { + + public static final ActionType TYPE = + new ActionType<>("cluster:admin/xpack/license/feature_usage", GetFeatureUsageResponse::new); + + private final XPackLicenseState licenseState; + + @Inject + public TransportGetFeatureUsageAction(TransportService transportService, ActionFilters actionFilters, + XPackLicenseState licenseState) { + super(TYPE.name(), transportService, actionFilters, GetFeatureUsageRequest::new); + this.licenseState = licenseState; + } + + + @Override + protected void doExecute(Task task, GetFeatureUsageRequest request, ActionListener listener) { + Map featureUsage = licenseState.getLastUsed(); + List usageInfos = new ArrayList<>(); + for (Map.Entry entry : featureUsage.entrySet()) { + XPackLicenseState.Feature feature = entry.getKey(); + String name = feature.name().toLowerCase(Locale.ROOT); + ZonedDateTime lastUsedTime = Instant.ofEpochMilli(entry.getValue()).atZone(ZoneOffset.UTC); + String licenseLevel = feature.minimumOperationMode.name().toLowerCase(Locale.ROOT); + usageInfos.add(new GetFeatureUsageResponse.FeatureUsageInfo(name, lastUsedTime, licenseLevel)); + } + listener.onResponse(new GetFeatureUsageResponse(usageInfos)); + } +} 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 cb8c894d6c9..c8d07a3e58a 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 @@ -16,14 +16,19 @@ import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.monitoring.MonitoringField; import java.util.Collections; +import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.LongAccumulator; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.LongSupplier; import java.util.function.Predicate; +import java.util.stream.Collectors; /** * A holder for the current state of the license for all xpack features. @@ -106,6 +111,14 @@ public class XPackLicenseState { } } + // temporarily non tracked feeatures which need rework in how they are checked + // so they are not tracked as always used + private static final Set NON_TRACKED_FEATURES = org.elasticsearch.common.collect.Set.of( + Feature.SECURITY_IP_FILTERING, + Feature.SECURITY_ALL_REALMS, + Feature.SECURITY_STANDARD_REALMS + ); + /** Messages for each feature which are printed when the license expires. */ static final Map EXPIRATION_MESSAGES; static { @@ -398,6 +411,8 @@ public class XPackLicenseState { private final List listeners; private final boolean isSecurityEnabled; private final boolean isSecurityExplicitlyEnabled; + private final Map lastUsed; + private final LongSupplier epochMillisProvider; // Since Status is the only field that can be updated, we do not need to synchronize access to // XPackLicenseState. However, if status is read multiple times in a method, it can change in between @@ -405,18 +420,31 @@ public class XPackLicenseState { // is only read once. private volatile Status status = new Status(OperationMode.TRIAL, true); - public XPackLicenseState(Settings settings) { + public XPackLicenseState(Settings settings, LongSupplier epochMillisProvider) { this.listeners = new CopyOnWriteArrayList<>(); this.isSecurityEnabled = XPackSettings.SECURITY_ENABLED.get(settings); this.isSecurityExplicitlyEnabled = isSecurityEnabled && isSecurityExplicitlyEnabled(settings); + + // prepopulate feature last used map with entries for non basic features, which are the ones we + // care to actually keep track of + Map lastUsed = new EnumMap<>(Feature.class); + for (Feature feature : Feature.values()) { + if (feature.minimumOperationMode.compareTo(OperationMode.BASIC) > 0 && NON_TRACKED_FEATURES.contains(feature) == false) { + lastUsed.put(feature, new LongAccumulator(Long::max, 0)); + } + } + this.lastUsed = lastUsed; + this.epochMillisProvider = epochMillisProvider; } private XPackLicenseState(List listeners, boolean isSecurityEnabled, boolean isSecurityExplicitlyEnabled, - Status status) { + Status status, Map lastUsed, LongSupplier epochMillisProvider) { this.listeners = listeners; this.isSecurityEnabled = isSecurityEnabled; this.isSecurityExplicitlyEnabled = isSecurityExplicitlyEnabled; this.status = status; + this.lastUsed = lastUsed; + this.epochMillisProvider = epochMillisProvider; } private static boolean isSecurityExplicitlyEnabled(Settings settings) { @@ -480,8 +508,12 @@ public class XPackLicenseState { * Checks whether the given feature is allowed, tracking the last usage time. */ public boolean checkFeature(Feature feature) { - // TODO: usage tracking is not yet implemented - return isAllowed(feature); + boolean allowed = isAllowed(feature); + LongAccumulator maxEpochAccumulator = lastUsed.get(feature); + if (maxEpochAccumulator != null) { + maxEpochAccumulator.accumulate(epochMillisProvider.getAsLong()); + } + return allowed; } /** @@ -493,6 +525,17 @@ public class XPackLicenseState { return isAllowedByLicense(feature.minimumOperationMode, feature.needsActive); } + /** + * Returns a mapping of gold+ features to the last time that feature was used. + * + * Note that if a feature has not been used, it will not appear in the map. + */ + public Map getLastUsed() { + return lastUsed.entrySet().stream() + .filter(e -> e.getValue().get() != 0) // feature was never used + .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get())); + } + public static boolean isMachineLearningAllowedForOperationMode(final OperationMode operationMode) { return isAllowedByOperationMode(operationMode, OperationMode.PLATINUM); } @@ -566,7 +609,7 @@ public class XPackLicenseState { */ public XPackLicenseState copyCurrentLicenseState() { return executeAgainstStatus(status -> - new XPackLicenseState(listeners, isSecurityEnabled, isSecurityExplicitlyEnabled, status)); + new XPackLicenseState(listeners, isSecurityEnabled, isSecurityExplicitlyEnabled, status, lastUsed, epochMillisProvider)); } /** diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackPlugin.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackPlugin.java index ef0e80faf74..6b8ae63de5b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackPlugin.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackPlugin.java @@ -85,6 +85,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.LongSupplier; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -136,6 +137,7 @@ public class XPackPlugin extends XPackClientPlugin implements ExtensiblePlugin, private static final SetOnce licenseState = new SetOnce<>(); private static final SetOnce sslService = new SetOnce<>(); private static final SetOnce licenseService = new SetOnce<>(); + private static final SetOnce epochMillisSupplier = new SetOnce<>(); public XPackPlugin( final Settings settings, @@ -146,7 +148,7 @@ public class XPackPlugin extends XPackClientPlugin implements ExtensiblePlugin, this.settings = settings; this.transportClientMode = transportClientMode(settings); - setLicenseState(new XPackLicenseState(settings)); + setLicenseState(new XPackLicenseState(settings, () -> getEpochMillisSupplier().getAsLong())); this.licensing = new Licensing(settings); } @@ -159,9 +161,13 @@ public class XPackPlugin extends XPackClientPlugin implements ExtensiblePlugin, protected SSLService getSslService() { return getSharedSslService(); } protected LicenseService getLicenseService() { return getSharedLicenseService(); } protected XPackLicenseState getLicenseState() { return getSharedLicenseState(); } + protected LongSupplier getEpochMillisSupplier() { return getSharedEpochMillisSupplier(); } protected void setSslService(SSLService sslService) { XPackPlugin.sslService.set(sslService); } protected void setLicenseService(LicenseService licenseService) { XPackPlugin.licenseService.set(licenseService); } protected void setLicenseState(XPackLicenseState licenseState) { XPackPlugin.licenseState.set(licenseState); } + protected void setEpochMillisSupplier(LongSupplier epochMillisSupplier) { + XPackPlugin.epochMillisSupplier.set(epochMillisSupplier); + } public static SSLService getSharedSslService() { final SSLService ssl = XPackPlugin.sslService.get(); @@ -172,6 +178,7 @@ public class XPackPlugin extends XPackClientPlugin implements ExtensiblePlugin, } public static LicenseService getSharedLicenseService() { return licenseService.get(); } public static XPackLicenseState getSharedLicenseState() { return licenseState.get(); } + public static LongSupplier getSharedEpochMillisSupplier() { return epochMillisSupplier.get(); } /** * Checks if the cluster state allows this node to add x-pack metadata to the cluster state, @@ -268,6 +275,8 @@ public class XPackPlugin extends XPackClientPlugin implements ExtensiblePlugin, setLicenseService(new LicenseService(settings, clusterService, getClock(), environment, resourceWatcherService, getLicenseState())); + setEpochMillisSupplier(threadPool::absoluteTimeInMillis); + // It is useful to override these as they are what guice is injecting into actions components.add(sslService); components.add(getLicenseService()); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseFIPSTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseFIPSTests.java index eb357661d50..01a3a421eba 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseFIPSTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseFIPSTests.java @@ -28,7 +28,7 @@ public class LicenseFIPSTests extends AbstractLicenseServiceTestCase { .put("xpack.security.transport.ssl.enabled", true) .put("xpack.security.fips_mode.enabled", randomBoolean()) .build(); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); setInitialState(null, licenseState, settings); licenseService.start(); @@ -52,7 +52,7 @@ public class LicenseFIPSTests extends AbstractLicenseServiceTestCase { .put("xpack.security.transport.ssl.enabled", true) .put("xpack.security.fips_mode.enabled", true) .build(); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); setInitialState(null, licenseState, settings); licenseService.start(); @@ -67,7 +67,7 @@ public class LicenseFIPSTests extends AbstractLicenseServiceTestCase { .put("xpack.security.transport.ssl.enabled", true) .put("xpack.security.fips_mode.enabled", false) .build(); - licenseState = new XPackLicenseState(settings); + licenseState = new XPackLicenseState(settings, () -> 0); setInitialState(null, licenseState, settings); licenseService.start(); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTLSTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTLSTests.java index 754b398cd6c..2015461a2a7 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTLSTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTLSTests.java @@ -33,7 +33,7 @@ public class LicenseTLSTests extends AbstractLicenseServiceTestCase { request.acknowledge(true); request.license(newLicense); Settings settings = Settings.builder().put("xpack.security.enabled", true).build(); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); inetAddress = InetAddress.getLoopbackAddress(); setInitialState(null, licenseState, settings); @@ -48,7 +48,7 @@ public class LicenseTLSTests extends AbstractLicenseServiceTestCase { .put("discovery.type", "single-node") .build(); licenseService.stop(); - licenseState = new XPackLicenseState(settings); + licenseState = new XPackLicenseState(settings, () -> 0); setInitialState(null, licenseState, settings); licenseService.start(); licenseService.registerLicense(request, responseFuture); @@ -62,7 +62,7 @@ public class LicenseTLSTests extends AbstractLicenseServiceTestCase { request.acknowledge(true); request.license(newLicense); Settings settings = Settings.builder().put("xpack.security.enabled", true).build(); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); inetAddress = TransportAddress.META_ADDRESS; setInitialState(null, licenseState, settings); @@ -74,7 +74,7 @@ public class LicenseTLSTests extends AbstractLicenseServiceTestCase { settings = Settings.builder().put("xpack.security.enabled", false).build(); licenseService.stop(); - licenseState = new XPackLicenseState(settings); + licenseState = new XPackLicenseState(settings, () -> 0); setInitialState(null, licenseState, settings); licenseService.start(); licenseService.registerLicense(request, responseFuture); @@ -85,7 +85,7 @@ public class LicenseTLSTests extends AbstractLicenseServiceTestCase { .put("xpack.security.transport.ssl.enabled", true) .build(); licenseService.stop(); - licenseState = new XPackLicenseState(settings); + licenseState = new XPackLicenseState(settings, () -> 0); setInitialState(null, licenseState, settings); licenseService.start(); licenseService.registerLicense(request, responseFuture); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java index 02129a099b7..27222105f3e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/TestUtils.java @@ -362,7 +362,7 @@ public class TestUtils { public final List trialVersionUpdates = new ArrayList<>(); public AssertingLicenseState() { - super(Settings.EMPTY); + super(Settings.EMPTY, () -> 0); } @Override @@ -383,7 +383,7 @@ public class TestUtils { } public UpdatableLicenseState(Settings settings) { - super(settings); + super(settings, () -> 0); } @Override @@ -393,7 +393,7 @@ public class TestUtils { } public static XPackLicenseState newTestLicenseState() { - return new XPackLicenseState(Settings.EMPTY); + return new XPackLicenseState(Settings.EMPTY, () -> 0); } public static void putLicense(Metadata.Builder builder, License license) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java index 287f23e31e9..4aa2ebbb1e9 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.XPackSettings; import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -25,6 +26,9 @@ import static org.elasticsearch.license.License.OperationMode.PLATINUM; import static org.elasticsearch.license.License.OperationMode.STANDARD; import static org.elasticsearch.license.License.OperationMode.TRIAL; import static org.hamcrest.Matchers.is; +import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.hamcrest.collection.IsMapContaining.hasKey; +import static org.hamcrest.core.IsNot.not; /** * Unit tests for the {@link XPackLicenseState} @@ -77,7 +81,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityDefaults() { Settings settings = Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build(); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true)); @@ -92,8 +96,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testTransportSslDoesNotAutomaticallyEnableSecurityOnTrialLicense() { Settings settings = Settings.builder().put(XPackSettings.TRANSPORT_SSL_ENABLED.getKey(), true).build(); - final XPackLicenseState licenseState; - licenseState = new XPackLicenseState(settings); + final XPackLicenseState licenseState= new XPackLicenseState(settings, () -> 0); assertSecurityNotAllowed(licenseState); } @@ -116,7 +119,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityBasicWithExplicitSecurityEnabled() { final Settings settings = Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build(); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(BASIC, true, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -148,7 +151,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityEnabledBasicExpired() { Settings settings = Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build(); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(BASIC, false, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -164,7 +167,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityStandard() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(STANDARD, true, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -178,7 +181,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityStandardExpired() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(STANDARD, false, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -192,7 +195,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityGold() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(GOLD, true, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -209,7 +212,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityGoldExpired() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(GOLD, false, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -226,7 +229,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityPlatinum() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(PLATINUM, true, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -243,7 +246,7 @@ public class XPackLicenseStateTests extends ESTestCase { public void testSecurityPlatinumExpired() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); - XPackLicenseState licenseState = new XPackLicenseState(settings); + XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); licenseState.update(PLATINUM, false, null); assertThat(licenseState.isSecurityEnabled(), is(true)); @@ -566,4 +569,24 @@ public class XPackLicenseStateTests extends ESTestCase { public void testTransformInactiveBasic() { assertAllowed(BASIC, false, s -> s.checkFeature(Feature.TRANSFORM), false); } + + public void testLastUsed() { + Feature basicFeature = Feature.SECURITY; + Feature goldFeature = Feature.SECURITY_DLS_FLS; + AtomicInteger currentTime = new AtomicInteger(100); // non zero start time + XPackLicenseState licenseState = new XPackLicenseState(Settings.EMPTY, currentTime::get); + assertThat("basic features not tracked", licenseState.getLastUsed(), not(hasKey(basicFeature))); + assertThat("initial epoch time", licenseState.getLastUsed(), not(hasKey(goldFeature))); + licenseState.isAllowed(basicFeature); + assertThat("basic features still not tracked", licenseState.getLastUsed(), not(hasKey(basicFeature))); + licenseState.isAllowed(goldFeature); + assertThat("isAllowed does not track", licenseState.getLastUsed(), not(hasKey(goldFeature))); + licenseState.checkFeature(basicFeature); + assertThat("basic features still not tracked", licenseState.getLastUsed(), not(hasKey(basicFeature))); + licenseState.checkFeature(goldFeature); + assertThat("checkFeature tracks used time", licenseState.getLastUsed(), hasEntry(goldFeature, 100L)); + currentTime.set(200); + licenseState.checkFeature(goldFeature); + assertThat("checkFeature updates tracked time", licenseState.getLastUsed(), hasEntry(goldFeature, 200L)); + } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java index 083555ebcd5..a9066e7faca 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java @@ -90,6 +90,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Function; +import java.util.function.LongSupplier; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -103,6 +104,7 @@ public class LocalStateCompositeXPackPlugin extends XPackPlugin implements Scrip private XPackLicenseState licenseState; private SSLService sslService; private LicenseService licenseService; + private LongSupplier epochMillisSupplier; protected List plugins = new ArrayList<>(); public LocalStateCompositeXPackPlugin(final Settings settings, final Path configPath) { @@ -150,6 +152,15 @@ public class LocalStateCompositeXPackPlugin extends XPackPlugin implements Scrip return modules; } + protected LongSupplier getEpochMillisSupplier() { + return epochMillisSupplier; + } + + @Override + protected void setEpochMillisSupplier(LongSupplier epochMillisSupplier) { + this.epochMillisSupplier = epochMillisSupplier; + } + @Override public Collection createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, ResourceWatcherService resourceWatcherService, ScriptService scriptService, diff --git a/x-pack/plugin/core/src/yamlRestTest/java/org/elasticsearch/license/XPackCoreClientYamlTestSuiteIT.java b/x-pack/plugin/core/src/yamlRestTest/java/org/elasticsearch/license/XPackCoreClientYamlTestSuiteIT.java new file mode 100644 index 00000000000..dce47903125 --- /dev/null +++ b/x-pack/plugin/core/src/yamlRestTest/java/org/elasticsearch/license/XPackCoreClientYamlTestSuiteIT.java @@ -0,0 +1,39 @@ +/* + * 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.license; + +import com.carrotsearch.randomizedtesting.annotations.Name; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate; +import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase; + +import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; + +public class XPackCoreClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase { + + private static final String BASIC_AUTH_VALUE = + basicAuthHeaderValue("x_pack_rest_user", new SecureString("x-pack-test-password")); + + public XPackCoreClientYamlTestSuiteIT(@Name("yaml") ClientYamlTestCandidate testCandidate) { + super(testCandidate); + } + + @ParametersFactory + public static Iterable parameters() throws Exception { + return ESClientYamlSuiteTestCase.createParameters(); + } + + @Override + protected Settings restClientSettings() { + return Settings.builder() + .put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE) + .build(); + } +} diff --git a/x-pack/plugin/core/src/yamlRestTest/resources/rest-api-spec/api/license.get_feature_usage.json b/x-pack/plugin/core/src/yamlRestTest/resources/rest-api-spec/api/license.get_feature_usage.json new file mode 100644 index 00000000000..57faa00c161 --- /dev/null +++ b/x-pack/plugin/core/src/yamlRestTest/resources/rest-api-spec/api/license.get_feature_usage.json @@ -0,0 +1,17 @@ +{ + "license.get_feature_usage":{ + "stability":"experimental", + "url":{ + "paths":[ + { + "path":"/_license/feature_usage", + "methods":[ + "GET" + ] + } + ] + }, + "params":{ + } + } +} diff --git a/x-pack/plugin/core/src/yamlRestTest/resources/rest-api-spec/test/license/10_feature_usage.yml b/x-pack/plugin/core/src/yamlRestTest/resources/rest-api-spec/test/license/10_feature_usage.yml new file mode 100644 index 00000000000..385bc57f1e8 --- /dev/null +++ b/x-pack/plugin/core/src/yamlRestTest/resources/rest-api-spec/test/license/10_feature_usage.yml @@ -0,0 +1,6 @@ +--- +"No features should be used just by starting up with default configuration": + - do: + license.get_feature_usage: {} + + - length: { features: 0 } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/InvalidLicenseEnforcer.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/InvalidLicenseEnforcer.java index d89e3c75660..5be9a3c91dd 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/InvalidLicenseEnforcer.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/InvalidLicenseEnforcer.java @@ -48,7 +48,7 @@ public class InvalidLicenseEnforcer implements LicenseStateListener { @Override public void licenseStateChanged() { assert licenseStateListenerRegistered; - if (licenseState.checkFeature(XPackLicenseState.Feature.MACHINE_LEARNING) == false) { + if (licenseState.isAllowed(XPackLicenseState.Feature.MACHINE_LEARNING) == false) { // if the license has expired, close jobs and datafeeds threadPool.generic().execute(new AbstractRunnable() { @Override diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java index 2d1f99e32b7..98220753815 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/MachineLearningFeatureSetTests.java @@ -338,7 +338,7 @@ public class MachineLearningFeatureSetTests extends ESTestCase { } public void testUsageWithOrphanedTask() throws Exception { - when(licenseState.checkFeature(XPackLicenseState.Feature.MACHINE_LEARNING)).thenReturn(true); + when(licenseState.isAllowed(XPackLicenseState.Feature.MACHINE_LEARNING)).thenReturn(true); Settings.Builder settings = Settings.builder().put(commonSettings); settings.put("xpack.ml.enabled", true); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index dfe327aa984..36f1fbf21be 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -1057,11 +1057,14 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, if (enabled) { return index -> { XPackLicenseState licenseState = getLicenseState(); - if (licenseState.isSecurityEnabled() == false || licenseState.checkFeature(Feature.SECURITY_DLS_FLS) == false) { + if (licenseState.isSecurityEnabled() == false) { return MapperPlugin.NOOP_FIELD_PREDICATE; } IndicesAccessControl indicesAccessControl = threadContext.get().getTransient( AuthorizationServiceField.INDICES_PERMISSIONS_KEY); + if (indicesAccessControl == null) { + return MapperPlugin.NOOP_FIELD_PREDICATE; + } IndicesAccessControl.IndexAccessControl indexPermissions = indicesAccessControl.getIndexPermissions(index); if (indexPermissions == null) { return MapperPlugin.NOOP_FIELD_PREDICATE; @@ -1073,6 +1076,10 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, if (fieldPermissions.hasFieldLevelSecurity() == false) { return MapperPlugin.NOOP_FIELD_PREDICATE; } + if (licenseState.checkFeature(Feature.SECURITY_DLS_FLS) == false) { + // check license last, once we know FLS is actually used + return MapperPlugin.NOOP_FIELD_PREDICATE; + } return fieldPermissions::grantsAccessTo; }; } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/BulkShardRequestInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/BulkShardRequestInterceptor.java index c8c4f0c7516..6947407cf64 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/BulkShardRequestInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/BulkShardRequestInterceptor.java @@ -12,6 +12,7 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.bulk.BulkItemRequest; import org.elasticsearch.action.bulk.BulkShardRequest; import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.common.MemoizedSupplier; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState.Feature; @@ -41,7 +42,8 @@ public class BulkShardRequestInterceptor implements RequestInterceptor { @Override public void intercept(RequestInfo requestInfo, AuthorizationEngine authzEngine, AuthorizationInfo authorizationInfo, ActionListener listener) { - boolean shouldIntercept = licenseState.isSecurityEnabled() && licenseState.checkFeature(Feature.SECURITY_DLS_FLS); + boolean shouldIntercept = licenseState.isSecurityEnabled(); + MemoizedSupplier licenseChecker = new MemoizedSupplier<>(() -> licenseState.checkFeature(Feature.SECURITY_DLS_FLS)); if (requestInfo.getRequest() instanceof BulkShardRequest && shouldIntercept) { IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY); @@ -54,7 +56,7 @@ public class BulkShardRequestInterceptor implements RequestInterceptor { boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); boolean dls = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions(); if (fls || dls) { - if (bulkItemRequest.request() instanceof UpdateRequest) { + if (licenseChecker.get() && bulkItemRequest.request() instanceof UpdateRequest) { found = true; logger.trace("aborting bulk item update request for index [{}]", bulkItemRequest.index()); bulkItemRequest.abort(bulkItemRequest.index(), new ElasticsearchSecurityException("Can't execute a bulk " + diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java index 600b11532dd..44d8eff03f8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/FieldAndDocumentLevelSecurityRequestInterceptor.java @@ -9,6 +9,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.IndicesRequest; +import org.elasticsearch.common.MemoizedSupplier; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState.Feature; @@ -39,7 +40,8 @@ abstract class FieldAndDocumentLevelSecurityRequestInterceptor implements Reques ActionListener listener) { if (requestInfo.getRequest() instanceof IndicesRequest) { IndicesRequest indicesRequest = (IndicesRequest) requestInfo.getRequest(); - boolean shouldIntercept = licenseState.isSecurityEnabled() && licenseState.checkFeature(Feature.SECURITY_DLS_FLS); + boolean shouldIntercept = licenseState.isSecurityEnabled(); + MemoizedSupplier licenseChecker = new MemoizedSupplier<>(() -> licenseState.checkFeature(Feature.SECURITY_DLS_FLS)); if (supports(indicesRequest) && shouldIntercept) { final IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY); @@ -48,7 +50,7 @@ abstract class FieldAndDocumentLevelSecurityRequestInterceptor implements Reques if (indexAccessControl != null) { boolean fieldLevelSecurityEnabled = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); boolean documentLevelSecurityEnabled = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions(); - if (fieldLevelSecurityEnabled || documentLevelSecurityEnabled) { + if ((fieldLevelSecurityEnabled || documentLevelSecurityEnabled) && licenseChecker.get()) { logger.trace("intercepted request for index [{}] with field level access controls [{}] " + "document level access controls [{}]. disabling conflicting features", index, fieldLevelSecurityEnabled, documentLevelSecurityEnabled); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/IndicesAliasesRequestInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/IndicesAliasesRequestInterceptor.java index b95e8410f81..0a2ae493554 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/IndicesAliasesRequestInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/IndicesAliasesRequestInterceptor.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.interceptor; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; +import org.elasticsearch.common.MemoizedSupplier; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; @@ -52,23 +53,23 @@ public final class IndicesAliasesRequestInterceptor implements RequestIntercepto final XPackLicenseState frozenLicenseState = licenseState.copyCurrentLicenseState(); final AuditTrail auditTrail = auditTrailService.get(); if (frozenLicenseState.isSecurityEnabled()) { - if (frozenLicenseState.checkFeature(Feature.SECURITY_DLS_FLS)) { - IndicesAccessControl indicesAccessControl = - threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY); - for (IndicesAliasesRequest.AliasActions aliasAction : request.getAliasActions()) { - if (aliasAction.actionType() == IndicesAliasesRequest.AliasActions.Type.ADD) { - for (String index : aliasAction.indices()) { - IndicesAccessControl.IndexAccessControl indexAccessControl = - indicesAccessControl.getIndexPermissions(index); - if (indexAccessControl != null) { - final boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); - final boolean dls = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions(); - if (fls || dls) { - listener.onFailure(new ElasticsearchSecurityException("Alias requests are not allowed for " + - "users who have field or document level security enabled on one of the indices", - RestStatus.BAD_REQUEST)); - return; - } + MemoizedSupplier licenseChecker = + new MemoizedSupplier<>(() -> frozenLicenseState.checkFeature(Feature.SECURITY_DLS_FLS)); + IndicesAccessControl indicesAccessControl = + threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY); + for (IndicesAliasesRequest.AliasActions aliasAction : request.getAliasActions()) { + if (aliasAction.actionType() == IndicesAliasesRequest.AliasActions.Type.ADD) { + for (String index : aliasAction.indices()) { + IndicesAccessControl.IndexAccessControl indexAccessControl = + indicesAccessControl.getIndexPermissions(index); + if (indexAccessControl != null) { + final boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); + final boolean dls = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions(); + if ((fls || dls) && licenseChecker.get()) { + listener.onFailure(new ElasticsearchSecurityException("Alias requests are not allowed for " + + "users who have field or document level security enabled on one of the indices", + RestStatus.BAD_REQUEST)); + return; } } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/ResizeRequestInterceptor.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/ResizeRequestInterceptor.java index 976acae3a29..4d9db4d4019 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/ResizeRequestInterceptor.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/interceptor/ResizeRequestInterceptor.java @@ -8,6 +8,7 @@ package org.elasticsearch.xpack.security.authz.interceptor; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.indices.shrink.ResizeRequest; +import org.elasticsearch.common.MemoizedSupplier; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState.Feature; @@ -48,19 +49,19 @@ public final class ResizeRequestInterceptor implements RequestInterceptor { final XPackLicenseState frozenLicenseState = licenseState.copyCurrentLicenseState(); final AuditTrail auditTrail = auditTrailService.get(); if (frozenLicenseState.isSecurityEnabled()) { - if (frozenLicenseState.checkFeature(Feature.SECURITY_DLS_FLS)) { - IndicesAccessControl indicesAccessControl = - threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY); - IndicesAccessControl.IndexAccessControl indexAccessControl = - indicesAccessControl.getIndexPermissions(request.getSourceIndex()); - if (indexAccessControl != null) { - final boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); - final boolean dls = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions(); - if (fls || dls) { - listener.onFailure(new ElasticsearchSecurityException("Resize requests are not allowed for users when " + - "field or document level security is enabled on the source index", RestStatus.BAD_REQUEST)); - return; - } + MemoizedSupplier licenseChecker = + new MemoizedSupplier<>(() -> frozenLicenseState.checkFeature(Feature.SECURITY_DLS_FLS)); + IndicesAccessControl indicesAccessControl = + threadContext.getTransient(AuthorizationServiceField.INDICES_PERMISSIONS_KEY); + IndicesAccessControl.IndexAccessControl indexAccessControl = + indicesAccessControl.getIndexPermissions(request.getSourceIndex()); + if (indexAccessControl != null) { + final boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity(); + final boolean dls = indexAccessControl.getDocumentPermissions().hasDocumentLevelPermissions(); + if ((fls || dls) && licenseChecker.get()) { + listener.onFailure(new ElasticsearchSecurityException("Resize requests are not allowed for users when " + + "field or document level security is enabled on the source index", RestStatus.BAD_REQUEST)); + return; } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index 46ae8f37dec..2e6c691f236 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -168,12 +168,14 @@ public class CompositeRolesStore { rolesRetrievalResult.getMissingRoles())); } final Set effectiveDescriptors; - if (licenseState.checkFeature(Feature.SECURITY_DLS_FLS)) { - effectiveDescriptors = rolesRetrievalResult.getRoleDescriptors(); + Set roleDescriptors = rolesRetrievalResult.getRoleDescriptors(); + if (roleDescriptors.stream().anyMatch(RoleDescriptor::isUsingDocumentOrFieldLevelSecurity) && + licenseState.checkFeature(Feature.SECURITY_DLS_FLS) == false) { + effectiveDescriptors = roleDescriptors.stream() + .filter(r -> r.isUsingDocumentOrFieldLevelSecurity() == false) + .collect(Collectors.toSet()); } else { - effectiveDescriptors = rolesRetrievalResult.getRoleDescriptors().stream() - .filter((rd) -> rd.isUsingDocumentOrFieldLevelSecurity() == false) - .collect(Collectors.toSet()); + effectiveDescriptors = roleDescriptors; } logger.trace(() -> new ParameterizedMessage("Exposing effective role descriptors [{}] for role names [{}]", effectiveDescriptors, roleNames)); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java index 7473a0a2781..88410d858ec 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java @@ -12,6 +12,7 @@ import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.MemoizedSupplier; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; @@ -176,14 +177,15 @@ public class FileRolesStore implements BiConsumer, ActionListener roleSegments = roleSegments(path); - final boolean flsDlsLicensed = licenseState.checkFeature(Feature.SECURITY_DLS_FLS); + MemoizedSupplier licenseChecker = + new MemoizedSupplier<>(() -> licenseState.checkFeature(Feature.SECURITY_DLS_FLS)); for (String segment : roleSegments) { RoleDescriptor descriptor = parseRoleDescriptor(segment, path, logger, resolvePermission, settings, xContentRegistry); if (descriptor != null) { if (ReservedRolesStore.isReserved(descriptor.getName())) { logger.warn("role [{}] is reserved. the relevant role definition in the mapping file will be ignored", descriptor.getName()); - } else if (flsDlsLicensed == false && descriptor.isUsingDocumentOrFieldLevelSecurity()) { + } else if (descriptor.isUsingDocumentOrFieldLevelSecurity() && licenseChecker.get() == false) { logger.warn("role [{}] uses document and/or field level security, which is not enabled by the current license" + ". this role will be ignored", descriptor.getName()); // we still put the role in the map to avoid unnecessary negative lookups diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index b471b66cfaa..ed531fe1b8c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -201,9 +201,7 @@ public class NativeRolesStore implements BiConsumer, ActionListener< } public void putRole(final PutRoleRequest request, final RoleDescriptor role, final ActionListener listener) { - if (licenseState.checkFeature(Feature.SECURITY_DLS_FLS)) { - innerPutRole(request, role, listener); - } else if (role.isUsingDocumentOrFieldLevelSecurity()) { + if (role.isUsingDocumentOrFieldLevelSecurity() && licenseState.checkFeature(Feature.SECURITY_DLS_FLS) == false) { listener.onFailure(LicenseUtils.newComplianceException("field and document level security")); } else { innerPutRole(request, role, listener); @@ -382,30 +380,25 @@ public class NativeRolesStore implements BiConsumer, ActionListener< // we pass true as last parameter because we do not want to reject permissions if the field permissions // are given in 2.x syntax RoleDescriptor roleDescriptor = RoleDescriptor.parse(name, sourceBytes, true, XContentType.JSON); - if (licenseState.checkFeature(Feature.SECURITY_DLS_FLS)) { - return roleDescriptor; - } else { - final boolean dlsEnabled = - Arrays.stream(roleDescriptor.getIndicesPrivileges()).anyMatch(IndicesPrivileges::isUsingDocumentLevelSecurity); - final boolean flsEnabled = - Arrays.stream(roleDescriptor.getIndicesPrivileges()).anyMatch(IndicesPrivileges::isUsingFieldLevelSecurity); - if (dlsEnabled || flsEnabled) { - List unlicensedFeatures = new ArrayList<>(2); - if (flsEnabled) { - unlicensedFeatures.add("fls"); - } - if (dlsEnabled) { - unlicensedFeatures.add("dls"); - } - Map transientMap = new HashMap<>(2); - transientMap.put("unlicensed_features", unlicensedFeatures); - transientMap.put("enabled", false); - return new RoleDescriptor(roleDescriptor.getName(), roleDescriptor.getClusterPrivileges(), - roleDescriptor.getIndicesPrivileges(), roleDescriptor.getRunAs(), roleDescriptor.getMetadata(), transientMap); - } else { - return roleDescriptor; + final boolean dlsEnabled = + Arrays.stream(roleDescriptor.getIndicesPrivileges()).anyMatch(IndicesPrivileges::isUsingDocumentLevelSecurity); + final boolean flsEnabled = + Arrays.stream(roleDescriptor.getIndicesPrivileges()).anyMatch(IndicesPrivileges::isUsingFieldLevelSecurity); + if ((dlsEnabled || flsEnabled) && licenseState.checkFeature(Feature.SECURITY_DLS_FLS) == false) { + List unlicensedFeatures = new ArrayList<>(2); + if (flsEnabled) { + unlicensedFeatures.add("fls"); } - + if (dlsEnabled) { + unlicensedFeatures.add("dls"); + } + Map transientMap = new HashMap<>(2); + transientMap.put("unlicensed_features", unlicensedFeatures); + transientMap.put("enabled", false); + return new RoleDescriptor(roleDescriptor.getName(), roleDescriptor.getClusterPrivileges(), + roleDescriptor.getIndicesPrivileges(), roleDescriptor.getRunAs(), roleDescriptor.getMetadata(), transientMap); + } else { + return roleDescriptor; } } catch (Exception e) { logger.error(new ParameterizedMessage("error in the format of data for role [{}]", name), e); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java index 41c98049775..6c0b19b1126 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java @@ -90,7 +90,7 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { arg0.updateLocalNodeInfo(localNode); return null; }).when(clusterService).addListener(Mockito.isA(LoggingAuditTrail.class)); - apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), mock(Client.class), new XPackLicenseState(settings), + apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), mock(Client.class), new XPackLicenseState(settings, () -> 0), mock(SecurityIndexManager.class), clusterService, mock(ThreadPool.class)); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java index 78060b1e044..2df38c07c41 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java @@ -220,7 +220,7 @@ public class LoggingAuditTrailTests extends ESTestCase { } logger = CapturingLogger.newCapturingLogger(randomFrom(Level.OFF, Level.FATAL, Level.ERROR, Level.WARN, Level.INFO), patternLayout); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, new XPackLicenseState(settings), + apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, new XPackLicenseState(settings, () -> 0), securityIndexManager, clusterService, mock(ThreadPool.class)); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index e6eb2e511c8..a9344f107a6 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -689,7 +689,7 @@ public class AuthorizationServiceTests extends ESTestCase { final AnonymousUser anonymousUser = new AnonymousUser(settings); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrailService, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, null, Collections.emptySet(), - new XPackLicenseState(settings), new IndexNameExpressionResolver()); + new XPackLicenseState(settings, () -> 0), new IndexNameExpressionResolver()); RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null); @@ -717,7 +717,7 @@ public class AuthorizationServiceTests extends ESTestCase { final Authentication authentication = createAuthentication(new AnonymousUser(settings)); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrailService, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, new AnonymousUser(settings), null, - Collections.emptySet(), new XPackLicenseState(settings), new IndexNameExpressionResolver()); + Collections.emptySet(), new XPackLicenseState(settings, () -> 0), new IndexNameExpressionResolver()); RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 6222d58203a..103cf46c42d 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -354,7 +354,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache(); final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(settings), - new XPackLicenseState(settings), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, + new XPackLicenseState(settings, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor @@ -393,7 +393,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor @@ -480,7 +480,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, inMemoryProvider2), - new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS), + new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); @@ -709,7 +709,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, failingProvider), - new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS), + new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); final Set roleNames = Sets.newHashSet("roleA", "roleB", "unknown"); @@ -821,7 +821,7 @@ public class CompositeRolesStoreTests extends ESTestCase { CompositeRolesStore compositeRolesStore = new CompositeRolesStore( Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(Settings.EMPTY), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> {}) { @Override public void invalidateAll() { @@ -875,7 +875,7 @@ public class CompositeRolesStoreTests extends ESTestCase { CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> {}) { @Override public void invalidateAll() { @@ -971,7 +971,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor @@ -1012,7 +1012,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, @@ -1035,7 +1035,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore()); ThreadContext threadContext = new ThreadContext(SECURITY_ENABLED_SETTINGS); ApiKeyService apiKeyService = spy(new ApiKeyService(SECURITY_ENABLED_SETTINGS, Clock.systemUTC(), mock(Client.class), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), mock(SecurityIndexManager.class), mock(ClusterService.class), + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), mock(SecurityIndexManager.class), mock(ClusterService.class), mock(ThreadPool.class))); NativePrivilegeStore nativePrivStore = mock(NativePrivilegeStore.class); doAnswer(invocationOnMock -> { @@ -1050,7 +1050,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, nativePrivStore, Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, apiKeyService, documentSubsetBitsetCache, + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, apiKeyService, documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); AuditUtil.getOrGenerateRequestId(threadContext); final Version version = randomFrom(Version.CURRENT, VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_8_1)); @@ -1088,7 +1088,7 @@ public class CompositeRolesStoreTests extends ESTestCase { ThreadContext threadContext = new ThreadContext(SECURITY_ENABLED_SETTINGS); ApiKeyService apiKeyService = spy(new ApiKeyService(SECURITY_ENABLED_SETTINGS, Clock.systemUTC(), mock(Client.class), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), mock(SecurityIndexManager.class), mock(ClusterService.class), + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), mock(SecurityIndexManager.class), mock(ClusterService.class), mock(ThreadPool.class))); NativePrivilegeStore nativePrivStore = mock(NativePrivilegeStore.class); doAnswer(invocationOnMock -> { @@ -1103,7 +1103,7 @@ public class CompositeRolesStoreTests extends ESTestCase { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, nativePrivStore, Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache, apiKeyService, documentSubsetBitsetCache, + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, apiKeyService, documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); AuditUtil.getOrGenerateRequestId(threadContext); final Version version = randomFrom(Version.CURRENT, VersionUtils.randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_8_1)); @@ -1238,7 +1238,7 @@ public class CompositeRolesStoreTests extends ESTestCase { nativePrivStore, Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS), + new XPackLicenseState(SECURITY_ENABLED_SETTINGS, () -> 0), cache, apiKeyService, documentSubsetBitsetCache, @@ -1364,7 +1364,7 @@ public class CompositeRolesStoreTests extends ESTestCase { }).when(privilegeStore).getPrivileges(isA(Set.class), isA(Set.class), any(ActionListener.class)); } if (licenseState == null) { - licenseState = new XPackLicenseState(settings); + licenseState = new XPackLicenseState(settings, () -> 0); } if (apiKeyService == null) { apiKeyService = mock(ApiKeyService.class); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java index d54495a23fb..23b73dc7155 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java @@ -215,13 +215,13 @@ public class ServerTransportFilterTests extends ESTestCase { Settings settings = Settings.builder().put("path.home", createTempDir()).build(); ThreadContext threadContext = new ThreadContext(settings); return new ServerTransportFilter.ClientProfile(authcService, authzService, threadContext, false, destructiveOperations, - reservedRealmEnabled, new SecurityContext(settings, threadContext), new XPackLicenseState(settings)); + reservedRealmEnabled, new SecurityContext(settings, threadContext), new XPackLicenseState(settings, () -> 0)); } private ServerTransportFilter.NodeProfile getNodeFilter(boolean reservedRealmEnabled) throws IOException { Settings settings = Settings.builder().put("path.home", createTempDir()).build(); ThreadContext threadContext = new ThreadContext(settings); return new ServerTransportFilter.NodeProfile(authcService, authzService, threadContext, false, destructiveOperations, - reservedRealmEnabled, new SecurityContext(settings, threadContext), new XPackLicenseState(settings)); + reservedRealmEnabled, new SecurityContext(settings, threadContext), new XPackLicenseState(settings, () -> 0)); } }