diff --git a/build.gradle b/build.gradle index d567e231f9f65..d0b63619cac42 100644 --- a/build.gradle +++ b/build.gradle @@ -138,9 +138,9 @@ tasks.register("verifyVersions") { * after the backport of the backcompat code is complete. */ -boolean bwc_tests_enabled = true +boolean bwc_tests_enabled = false // place a PR link here when committing bwc changes: -String bwc_tests_disabled_issue = "" +String bwc_tests_disabled_issue = "https://github.com/elastic/elasticsearch/pull/76476" /* * FIPS 140-2 behavior was fixed in 7.11.0. Before that there is no way to run elasticsearch in a * JVM that is properly configured to be in fips mode with BCFIPS. For now we need to disable 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 index ebec0b088185f..ac7c28c032092 100644 --- 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 @@ -7,12 +7,14 @@ package org.elasticsearch.license; +import org.elasticsearch.Version; 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 org.elasticsearch.core.Nullable; import java.io.IOException; import java.time.Instant; @@ -20,23 +22,31 @@ import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; +import java.util.Objects; public class GetFeatureUsageResponse extends ActionResponse implements ToXContentObject { public static class FeatureUsageInfo implements Writeable { - public final String name; - public final ZonedDateTime lastUsedTime; + private final String name; + private final ZonedDateTime lastUsedTime; + private final String context; public final String licenseLevel; - public FeatureUsageInfo(String name, ZonedDateTime lastUsedTime, String licenseLevel) { - this.name = name; - this.lastUsedTime = lastUsedTime; - this.licenseLevel = licenseLevel; + public FeatureUsageInfo(String name, ZonedDateTime lastUsedTime, @Nullable String context, String licenseLevel) { + this.name = Objects.requireNonNull(name, "Feature name may not be null"); + this.lastUsedTime = Objects.requireNonNull(lastUsedTime, "Last used time may not be null"); + this.context = context; + this.licenseLevel = Objects.requireNonNull(licenseLevel, "License level may not be null"); } public FeatureUsageInfo(StreamInput in) throws IOException { this.name = in.readString(); this.lastUsedTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(in.readLong()), ZoneOffset.UTC); + if (in.getVersion().onOrAfter(Version.V_7_15_0)) { + this.context = in.readOptionalString(); + } else { + this.context = null; + } this.licenseLevel = in.readString(); } @@ -44,6 +54,9 @@ public FeatureUsageInfo(StreamInput in) throws IOException { public void writeTo(StreamOutput out) throws IOException { out.writeString(name); out.writeLong(lastUsedTime.toEpochSecond()); + if (out.getVersion().onOrAfter(Version.V_7_15_0)) { + out.writeOptionalString(this.context); + } out.writeString(licenseLevel); } } @@ -74,6 +87,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws for (FeatureUsageInfo feature : features) { builder.startObject(); builder.field("name", feature.name); + builder.field("context", feature.context); builder.field("last_used", feature.lastUsedTime.toString()); builder.field("license_level", feature.licenseLevel); builder.endObject(); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java index fa3a14e8612a8..36eb683ea5758 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java @@ -32,6 +32,7 @@ import org.elasticsearch.protocol.xpack.license.DeleteLicenseRequest; import org.elasticsearch.protocol.xpack.license.LicensesStatus; import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.XPackSettings; @@ -130,7 +131,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste private static final String ACKNOWLEDGEMENT_HEADER = "This license update requires acknowledgement. To acknowledge the license, " + "please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:"; - public LicenseService(Settings settings, ClusterService clusterService, Clock clock, Environment env, + public LicenseService(Settings settings, ThreadPool threadPool, ClusterService clusterService, Clock clock, Environment env, ResourceWatcherService resourceWatcherService, XPackLicenseState licenseState) { this.settings = settings; this.clusterService = clusterService; @@ -143,6 +144,8 @@ public LicenseService(Settings settings, ClusterService clusterService, Clock cl () -> updateLicenseState(getLicensesMetadata())); this.scheduler.register(this); populateExpirationCallbacks(); + + threadPool.scheduleWithFixedDelay(licenseState::cleanupUsageTracking, TimeValue.timeValueHours(1), ThreadPool.Names.GENERIC); } private void logExpirationWarning(long expirationMillis, boolean expired) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensedFeature.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensedFeature.java new file mode 100644 index 0000000000000..6937caa285f67 --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensedFeature.java @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.license; + +import java.util.Objects; + +/** + * A base class for checking licensed features against the license. + */ +public abstract class LicensedFeature { + + /** + * A Momentary feature is one that is tracked at the moment the license is checked. + */ + public static class Momentary extends LicensedFeature { + + private Momentary(String name, License.OperationMode minimumOperationMode, boolean needsActive) { + super(name, minimumOperationMode, needsActive); + } + + /** + * Checks whether the feature is allowed by the given license state, and + * updates the last time the feature was used. + */ + public boolean check(XPackLicenseState state) { + if (state.isAllowed(this)) { + state.featureUsed(this); + return true; + } else { + return false; + } + } + } + + /** + * A Persistent feature is one that is tracked starting when the license is checked, and later may be untracked. + */ + public static class Persistent extends LicensedFeature { + private Persistent(String name, License.OperationMode minimumOperationMode, boolean needsActive) { + super(name, minimumOperationMode, needsActive); + } + + /** + * Checks whether the feature is allowed by the given license state, and + * begins tracking the feature as "on" for the given context. + */ + public boolean checkAndStartTracking(XPackLicenseState state, String contextName) { + if (state.isAllowed(this)) { + state.enableUsageTracking(this, contextName); + return true; + } else { + return false; + } + } + + /** + * Stop tracking the feature so that the current time will be the last that it was used. + */ + public void stopTracking(XPackLicenseState state, String contextName) { + state.disableUsageTracking(this, contextName); + } + } + + final String name; + final License.OperationMode minimumOperationMode; + final boolean needsActive; + + public LicensedFeature(String name, License.OperationMode minimumOperationMode, boolean needsActive) { + this.name = name; + this.minimumOperationMode = minimumOperationMode; + this.needsActive = needsActive; + } + + /** Create a momentary feature for hte given license level */ + public static Momentary momentary(String name, License.OperationMode licenseLevel) { + return new Momentary(name, licenseLevel, true); + } + + /** Create a persistent feature for the given license level */ + public static Persistent persistent(String name, License.OperationMode licenseLevel) { + return new Persistent(name, licenseLevel, true); + } + + /** + * Creates a momentary feature, but one that is lenient as + * to whether the license needs to be active to allow the feature. + */ + @Deprecated + public static Momentary momentaryLenient(String name, License.OperationMode licenseLevel) { + return new Momentary(name, licenseLevel, false); + } + + /** + * Creates a persistent feature, but one that is lenient as + * to whether the license needs to be active to allow the feature. + */ + @Deprecated + public static Persistent persistentLenient(String name, License.OperationMode licenseLevel) { + return new Persistent(name, licenseLevel, false); + } + + /** + * Returns whether the feature is allowed by the current license + * without affecting feature tracking. + */ + public final boolean checkWithoutTracking(XPackLicenseState state) { + return state.isAllowed(this); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LicensedFeature that = (LicensedFeature) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} 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 index 32dee3b1e6923..46d36b7a22962 100644 --- 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 @@ -20,7 +20,6 @@ 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 { @@ -40,15 +39,19 @@ public TransportGetFeatureUsageAction(TransportService transportService, ActionF @Override protected void doExecute(Task task, GetFeatureUsageRequest request, ActionListener listener) { - Map featureUsage = licenseState.getLastUsed(); - List usageInfos = new ArrayList<>(); - for (var 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)); - } + Map featureUsage = licenseState.getLastUsed(); + List usageInfos = new ArrayList<>(featureUsage.size()); + featureUsage.forEach((usage, lastUsed) -> { + ZonedDateTime lastUsedTime = Instant.ofEpochMilli(lastUsed).atZone(ZoneOffset.UTC); + usageInfos.add( + new GetFeatureUsageResponse.FeatureUsageInfo( + usage.featureName(), + lastUsedTime, + usage.contextName(), + usage.minimumOperationMode().description() + ) + ); + }); 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 c783176af7f5b..f1ca053e4c58f 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 @@ -7,28 +7,26 @@ package org.elasticsearch.license; import org.elasticsearch.Version; -import org.elasticsearch.core.Nullable; import org.elasticsearch.common.Strings; -import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.common.logging.HeaderWarning; import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.TimeValue; import org.elasticsearch.license.License.OperationMode; import org.elasticsearch.xpack.core.XPackField; 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.Locale; import java.util.Map; import java.util.Objects; -import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.LongAccumulator; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.LongSupplier; @@ -48,11 +46,8 @@ public class XPackLicenseState { * Each value defines the licensed state necessary for the feature to be allowed. */ public enum Feature { - SECURITY_IP_FILTERING(OperationMode.GOLD, false), SECURITY_AUDITING(OperationMode.GOLD, false), SECURITY_DLS_FLS(OperationMode.PLATINUM, false), - SECURITY_ALL_REALMS(OperationMode.PLATINUM, false), - SECURITY_STANDARD_REALMS(OperationMode.GOLD, false), SECURITY_CUSTOM_ROLE_PROVIDERS(OperationMode.PLATINUM, true), SECURITY_TOKEN_SERVICE(OperationMode.STANDARD, false), SECURITY_AUTHORIZATION_REALM(OperationMode.PLATINUM, true), @@ -89,24 +84,21 @@ public enum Feature { AUTOSCALING(OperationMode.ENTERPRISE, true); - final OperationMode minimumOperationMode; - final boolean needsActive; + // NOTE: this is temporary. The Feature enum will go away in favor of LicensedFeature. + // Embedding the feature instance here is a stopgap to allow smaller initial PR, + // followed by PRs to convert the current consumers of the license state. + final LicensedFeature.Momentary feature; Feature(OperationMode minimumOperationMode, boolean needsActive) { assert minimumOperationMode.compareTo(OperationMode.BASIC) > 0: minimumOperationMode.toString(); - this.minimumOperationMode = minimumOperationMode; - this.needsActive = needsActive; + if (needsActive) { + this.feature = LicensedFeature.momentary(name().toLowerCase(Locale.ROOT), minimumOperationMode); + } else { + this.feature = LicensedFeature.momentaryLenient(name().toLowerCase(Locale.ROOT), minimumOperationMode); + } } } - // 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 = 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,7 +390,14 @@ private static class Status { } private final List listeners; - private final Map lastUsed; + + /** + * A Map of features for which usage is tracked by a feature identifier and a last-used-time. + * A last used time of {@code -1} means that the feature is "on" and should report the current time as the last-used-time + * (See: {@link #epochMillisProvider}, {@link #getLastUsed}). + */ + private final Map usage; + private final LongSupplier epochMillisProvider; // Since Status is the only field that can be updated, we do not need to synchronize access to @@ -409,24 +408,19 @@ private static class Status { public XPackLicenseState(LongSupplier epochMillisProvider) { this.listeners = new CopyOnWriteArrayList<>(); - - // 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 (NON_TRACKED_FEATURES.contains(feature) == false) { - lastUsed.put(feature, new LongAccumulator(Long::max, 0)); - } - } - this.lastUsed = lastUsed; + this.usage = new ConcurrentHashMap<>(); this.epochMillisProvider = epochMillisProvider; } - private XPackLicenseState(List listeners, Status status, Map lastUsed, - LongSupplier epochMillisProvider) { + private XPackLicenseState( + List listeners, + Status status, + Map usage, + LongSupplier epochMillisProvider + ) { this.listeners = listeners; this.status = status; - this.lastUsed = lastUsed; + this.usage = usage; this.epochMillisProvider = epochMillisProvider; } @@ -450,7 +444,7 @@ private boolean checkAgainstStatus(Predicate statusPredicate) { * May be {@code null} if they have never generated a trial license on this cluster, or the most recent * trial was prior to this metadata being tracked (6.1) */ - void update(OperationMode mode, boolean active, long expirationDate, @Nullable Version mostRecentTrialVersion) { + protected void update(OperationMode mode, boolean active, long expirationDate, @Nullable Version mostRecentTrialVersion) { status = new Status(mode, active, expirationDate); listeners.forEach(LicenseStateListener::licenseStateChanged); } @@ -470,33 +464,66 @@ public OperationMode getOperationMode() { return executeAgainstStatus(status -> status.mode); } - /** - * Checks that the cluster has a valid licence of any level. - * @see #isActive() - */ - public boolean allowForAllLicenses() { - return checkAgainstStatus(status -> status.active); - } - // Package private for tests /** Return true if the license is currently within its time boundaries, false otherwise. */ public boolean isActive() { return checkAgainstStatus(status -> status.active); } + @Deprecated + public boolean checkFeature(Feature feature) { + return feature.feature.check(this); + } + + void featureUsed(LicensedFeature feature) { + usage.put(new FeatureUsage(feature, null), epochMillisProvider.getAsLong()); + checkForExpiry(feature); + } + + void enableUsageTracking(LicensedFeature feature, String contextName) { + Objects.requireNonNull(contextName, "Context name cannot be null"); + usage.put(new FeatureUsage(feature, contextName), -1L); + checkForExpiry(feature); + } + + void disableUsageTracking(LicensedFeature feature, String contextName) { + Objects.requireNonNull(contextName, "Context name cannot be null"); + usage.replace(new FeatureUsage(feature, contextName), -1L, epochMillisProvider.getAsLong()); + } + + void cleanupUsageTracking() { + long cutoffTime = epochMillisProvider.getAsLong() - TimeValue.timeValueHours(24).getMillis(); + usage.entrySet().removeIf(e -> { + long timeMillis = e.getValue(); + if (timeMillis == -1) { + return false; // feature is still on, don't remove + } + return timeMillis < cutoffTime; // true if it has not been used in more than 24 hours + }); + } + /** - * Checks whether the given feature is allowed, tracking the last usage time. + * Checks whether the given feature is allowed by the current license. + *

+ * This method should only be used when serializing whether a feature is allowed for telemetry. */ - @SuppressForbidden(reason = "Argument to Math.abs() is definitely not Long.MIN_VALUE") - public boolean checkFeature(Feature feature) { - boolean allowed = isAllowed(feature); - LongAccumulator maxEpochAccumulator = lastUsed.get(feature); - final long licenseExpiryDate = getLicenseExpiryDate(); - final long diff = licenseExpiryDate - System.currentTimeMillis(); - if (maxEpochAccumulator != null) { - maxEpochAccumulator.accumulate(epochMillisProvider.getAsLong()); + @Deprecated + public boolean isAllowed(Feature feature) { + return isAllowed(feature.feature); + } + + // Package protected: Only allowed to be called by LicensedFeature + boolean isAllowed(LicensedFeature feature) { + if (isAllowedByLicense(feature.minimumOperationMode, feature.needsActive)) { + return true; } + return false; + } + private void checkForExpiry(LicensedFeature feature) { + final long licenseExpiryDate = getLicenseExpiryDate(); + // TODO: this should use epochMillisProvider to avoid a system call + testability + final long diff = licenseExpiryDate - System.currentTimeMillis(); if (feature.minimumOperationMode.compareTo(OperationMode.BASIC) > 0 && LICENSE_EXPIRATION_WARNING_PERIOD.getMillis() > diff) { final long days = TimeUnit.MILLISECONDS.toDays(diff); @@ -506,17 +533,6 @@ public boolean checkFeature(Feature feature) { HeaderWarning.addWarning("Your license {}. " + "Contact your administrator or update your license for continued use of features", expiryMessage); } - - return allowed; - } - - /** - * Checks whether the given feature is allowed by the current license. - *

- * This method should only be used when serializing whether a feature is allowed for telemetry. - */ - public boolean isAllowed(Feature feature) { - return isAllowedByLicense(feature.minimumOperationMode, feature.needsActive); } /** @@ -524,10 +540,10 @@ public boolean isAllowed(Feature feature) { * * 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 Map getLastUsed() { + long currentTimeMillis = epochMillisProvider.getAsLong(); + Function timeConverter = v -> v == -1 ? currentTimeMillis : v; + return usage.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> timeConverter.apply(e.getValue()))); } public static boolean isMachineLearningAllowedForOperationMode(final OperationMode operationMode) { @@ -578,7 +594,7 @@ public static boolean isAllowedByOperationMode( */ public XPackLicenseState copyCurrentLicenseState() { return executeAgainstStatus(status -> - new XPackLicenseState(listeners, status, lastUsed, epochMillisProvider)); + new XPackLicenseState(listeners, status, usage, epochMillisProvider)); } /** @@ -589,6 +605,7 @@ public XPackLicenseState copyCurrentLicenseState() { * * @return true if feature is allowed, otherwise false */ + @Deprecated public boolean isAllowedByLicense(OperationMode minimumMode, boolean needActive) { return checkAgainstStatus(status -> { if (needActive && false == status.active) { @@ -613,4 +630,45 @@ public boolean isAllowedByLicense(OperationMode minimumMode) { return isAllowedByLicense(minimumMode, true); } + public static class FeatureUsage { + private final LicensedFeature feature; + + @Nullable + private final String context; + + private FeatureUsage(LicensedFeature feature, String context) { + this.feature = Objects.requireNonNull(feature, "Feature cannot be null"); + this.context = context; + } + + @Override + public String toString() { + return context == null ? feature.name : feature.name + ":" + context; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FeatureUsage usage = (FeatureUsage) o; + return Objects.equals(feature, usage.feature) && Objects.equals(context, usage.context); + } + + @Override + public int hashCode() { + return Objects.hash(feature, context); + } + + public String featureName() { + return feature.name; + } + + public String contextName() { + return context; + } + + public OperationMode minimumOperationMode() { + return feature.minimumOperationMode; + } + } } 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 354ca147d1d88..6e8372f021c7d 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 @@ -273,7 +273,7 @@ public Collection createComponents(Client client, ClusterService cluster List components = new ArrayList<>(); final SSLService sslService = createSSLService(environment, resourceWatcherService); - setLicenseService(new LicenseService(settings, clusterService, getClock(), + setLicenseService(new LicenseService(settings, threadPool, clusterService, getClock(), environment, resourceWatcherService, getLicenseState())); setEpochMillisSupplier(threadPool::absoluteTimeInMillis); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java index f7c4212a2a17f..efbae3917ade4 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java @@ -18,6 +18,8 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.TestThreadPool; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.watcher.watch.ClockMock; @@ -40,6 +42,7 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase { protected ClockMock clock; protected DiscoveryNodes discoveryNodes; protected Environment environment; + protected ThreadPool threadPool; protected String licenseType; @Before @@ -49,6 +52,12 @@ public void init() throws Exception { discoveryNodes = mock(DiscoveryNodes.class); resourceWatcherService = mock(ResourceWatcherService.class); environment = mock(Environment.class); + threadPool = new TestThreadPool("license-test"); + } + + @After + public void shutdown() { + threadPool.shutdown(); } protected void setInitialState(License license, XPackLicenseState licenseState, Settings settings) { @@ -60,7 +69,7 @@ protected void setInitialState(License license, XPackLicenseState licenseState, when(environment.configFile()).thenReturn(tempDir); licenseType = selfGeneratedType; settings = Settings.builder().put(settings).put(LicenseService.SELF_GENERATED_LICENSE_TYPE.getKey(), licenseType).build(); - licenseService = new LicenseService(settings, clusterService, clock, environment, resourceWatcherService, licenseState); + licenseService = new LicenseService(settings, threadPool, clusterService, clock, environment, resourceWatcherService, licenseState); ClusterState state = mock(ClusterState.class); final ClusterBlocks noBlock = ClusterBlocks.builder().build(); when(state.blocks()).thenReturn(noBlock); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java index 346f3390a05ab..e054bc64c88f6 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseServiceTests.java @@ -27,6 +27,7 @@ import org.elasticsearch.protocol.xpack.license.PutLicenseResponse; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.TestMatchers; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -46,6 +47,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.startsWith; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -127,17 +129,19 @@ private void tryRegisterLicense(Settings baseSettings, License.LicenseType licen .put("discovery.type", "single-node") // So we skip TLS checks .build(); - final ClusterState clusterState = Mockito.mock(ClusterState.class); + final ClusterState clusterState = mock(ClusterState.class); Mockito.when(clusterState.metadata()).thenReturn(Metadata.EMPTY_METADATA); - final ClusterService clusterService = Mockito.mock(ClusterService.class); + final ClusterService clusterService = mock(ClusterService.class); Mockito.when(clusterService.state()).thenReturn(clusterState); final Clock clock = randomBoolean() ? Clock.systemUTC() : Clock.systemDefaultZone(); final Environment env = TestEnvironment.newEnvironment(settings); - final ResourceWatcherService resourceWatcherService = Mockito.mock(ResourceWatcherService.class); - final XPackLicenseState licenseState = Mockito.mock(XPackLicenseState.class); - final LicenseService service = new LicenseService(settings, clusterService, clock, env, resourceWatcherService, licenseState); + final ResourceWatcherService resourceWatcherService = mock(ResourceWatcherService.class); + final XPackLicenseState licenseState = mock(XPackLicenseState.class); + final ThreadPool threadPool = mock(ThreadPool.class); + final LicenseService service = + new LicenseService(settings, threadPool, clusterService, clock, env, resourceWatcherService, licenseState); final PutLicenseRequest request = new PutLicenseRequest(); request.license(spec(licenseType, TimeValue.timeValueDays(randomLongBetween(1, 1000))), XContentType.JSON); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/MockLicenseState.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/MockLicenseState.java new file mode 100644 index 0000000000000..3b95f14d5180d --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/MockLicenseState.java @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.license; + +import java.util.function.LongSupplier; + +/** A license state that may be mocked by testing because the internal methods are made public */ +public class MockLicenseState extends XPackLicenseState { + + public MockLicenseState(LongSupplier epochMillisProvider) { + super(epochMillisProvider); + } + + @Override + public boolean isAllowed(LicensedFeature feature) { + return super.isAllowed(feature); + } + + @Override + public void enableUsageTracking(LicensedFeature feature, String contextName) { + super.enableUsageTracking(feature, contextName); + } +} 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 90a97d50b4f0f..a34d617f87877 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 @@ -46,6 +46,9 @@ import static org.elasticsearch.test.ESTestCase.randomIntBetween; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TestUtils { @@ -368,7 +371,7 @@ public AssertingLicenseState() { } @Override - void update(License.OperationMode mode, boolean active, long expirationDate, Version mostRecentTrialVersion) { + protected void update(License.OperationMode mode, boolean active, long expirationDate, Version mostRecentTrialVersion) { modeUpdates.add(mode); activeUpdates.add(active); expirationDateUpdates.add(expirationDate); @@ -402,4 +405,14 @@ public static XPackLicenseState newTestLicenseState() { public static void putLicense(Metadata.Builder builder, License license) { builder.putCustom(LicensesMetadata.TYPE, new LicensesMetadata(license, null)); } + + public static MockLicenseState newMockLicenceState() { + MockLicenseState mock = mock(MockLicenseState.class); + // These are deprecated methods, but we haven't replaced all usage of them yet + // By calling the real methods, we force everything through a small number of mockable methods like + // XPackLicenseState.isAllowed(LicensedFeature) + when(mock.isAllowed(any(XPackLicenseState.Feature.class))).thenCallRealMethod(); + when(mock.checkFeature(any(XPackLicenseState.Feature.class))).thenCallRealMethod(); + return mock; + } } 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 2c11be1013ab4..01d8742ecef7f 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 @@ -6,12 +6,14 @@ */ package org.elasticsearch.license; +import org.elasticsearch.common.util.iterable.Iterables; import org.elasticsearch.license.License.OperationMode; import org.elasticsearch.license.XPackLicenseState.Feature; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.XPackField; import java.util.Arrays; +import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -23,8 +25,11 @@ 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.aMapWithSize; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -import static org.hamcrest.collection.IsMapContaining.hasEntry; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.collection.IsMapContaining.hasKey; import static org.hamcrest.core.IsNot.not; @@ -79,10 +84,8 @@ public static OperationMode randomBasicStandardOrGold() { public void testSecurityDefaults() { XPackLicenseState licenseState = new XPackLicenseState(() -> 0); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(true)); - assertThat(licenseState.checkFeature(Feature.SECURITY_ALL_REALMS), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(true)); } @@ -90,7 +93,6 @@ public void testSecurityStandard() { XPackLicenseState licenseState = new XPackLicenseState(() -> 0); licenseState.update(STANDARD, true, Long.MAX_VALUE, null); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false)); @@ -101,7 +103,6 @@ public void testSecurityStandardExpired() { XPackLicenseState licenseState = new XPackLicenseState( () -> 0); licenseState.update(STANDARD, false, Long.MAX_VALUE, null); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false)); @@ -112,7 +113,6 @@ public void testSecurityBasic() { XPackLicenseState licenseState = new XPackLicenseState( () -> 0); licenseState.update(BASIC, true, Long.MAX_VALUE, null); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false)); @@ -125,10 +125,8 @@ public void testSecurityGold() { XPackLicenseState licenseState = new XPackLicenseState(() -> 0); licenseState.update(GOLD, true, Long.MAX_VALUE, null); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(false)); - assertThat(licenseState.checkFeature(Feature.SECURITY_STANDARD_REALMS), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true)); } @@ -137,10 +135,8 @@ public void testSecurityGoldExpired() { XPackLicenseState licenseState = new XPackLicenseState(() -> 0); licenseState.update(GOLD, false, Long.MAX_VALUE, null); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(false)); - assertThat(licenseState.checkFeature(Feature.SECURITY_STANDARD_REALMS), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true)); } @@ -149,10 +145,8 @@ public void testSecurityPlatinum() { XPackLicenseState licenseState = new XPackLicenseState(() -> 0); licenseState.update(PLATINUM, true, Long.MAX_VALUE, null); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(true)); - assertThat(licenseState.checkFeature(Feature.SECURITY_ALL_REALMS), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true)); } @@ -161,10 +155,8 @@ public void testSecurityPlatinumExpired() { XPackLicenseState licenseState = new XPackLicenseState(() -> 0); licenseState.update(PLATINUM, false, Long.MAX_VALUE, null); - assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_AUDITING), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_DLS_FLS), is(true)); - assertThat(licenseState.checkFeature(Feature.SECURITY_ALL_REALMS), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_CUSTOM_ROLE_PROVIDERS), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE), is(true)); } @@ -451,17 +443,63 @@ public void testCcrAckTrialOrPlatinumToNotTrialOrPlatinum() { assertAckMessages(XPackField.CCR, randomTrialOrPlatinumMode(), randomBasicStandardOrGold(), 1); } - public void testLastUsed() { - Feature goldFeature = Feature.SECURITY_DLS_FLS; + public void testLastUsedMomentaryFeature() { + LicensedFeature.Momentary goldFeature = LicensedFeature.momentary("goldFeature", GOLD); AtomicInteger currentTime = new AtomicInteger(100); // non zero start time XPackLicenseState licenseState = new XPackLicenseState(currentTime::get); - assertThat("initial epoch time", licenseState.getLastUsed(), not(hasKey(goldFeature))); + Map lastUsed = licenseState.getLastUsed(); + assertThat("initial epoch time", lastUsed, not(hasKey(goldFeature))); + licenseState.isAllowed(goldFeature); - assertThat("isAllowed does not track", licenseState.getLastUsed(), not(hasKey(goldFeature))); - licenseState.checkFeature(goldFeature); - assertThat("checkFeature tracks used time", licenseState.getLastUsed(), hasEntry(goldFeature, 100L)); + lastUsed = licenseState.getLastUsed(); + assertThat("isAllowed does not track", lastUsed, not(hasKey(goldFeature))); + + goldFeature.check(licenseState); + lastUsed = licenseState.getLastUsed(); + assertThat("feature.check tracks usage", lastUsed, aMapWithSize(1)); + + XPackLicenseState.FeatureUsage usage = Iterables.get(lastUsed.keySet(), 0); + assertThat(usage.featureName(), equalTo("goldFeature")); + assertThat(usage.contextName(), nullValue()); + assertThat(lastUsed.get(usage), equalTo(100L)); + currentTime.set(200); - licenseState.checkFeature(goldFeature); - assertThat("checkFeature updates tracked time", licenseState.getLastUsed(), hasEntry(goldFeature, 200L)); + goldFeature.check(licenseState); + lastUsed = licenseState.getLastUsed(); + assertThat("feature.check updates usage", lastUsed.keySet(), containsInAnyOrder(usage)); + assertThat(lastUsed.get(usage), equalTo(200L)); + } + + public void testLastUsedPersistentFeature() { + LicensedFeature.Persistent goldFeature = LicensedFeature.persistent("goldFeature", GOLD); + AtomicInteger currentTime = new AtomicInteger(100); // non zero start time + XPackLicenseState licenseState = new XPackLicenseState(currentTime::get); + Map lastUsed = licenseState.getLastUsed(); + assertThat("initial epoch time", lastUsed, not(hasKey(goldFeature))); + + licenseState.isAllowed(goldFeature); + lastUsed = licenseState.getLastUsed(); + assertThat("isAllowed does not track", lastUsed, not(hasKey(goldFeature))); + + goldFeature.checkAndStartTracking(licenseState, "somecontext"); + currentTime.set(200); // advance time after starting tracking + lastUsed = licenseState.getLastUsed(); + assertThat(lastUsed, aMapWithSize(1)); + + XPackLicenseState.FeatureUsage usage = Iterables.get(lastUsed.keySet(), 0); + assertThat(usage.featureName(), equalTo("goldFeature")); + assertThat(usage.contextName(), equalTo("somecontext")); + assertThat(lastUsed.get(usage), equalTo(200L)); + + currentTime.set(300); + goldFeature.stopTracking(licenseState, "somecontext"); + lastUsed = licenseState.getLastUsed(); + assertThat("stopTracking sets time to current", lastUsed.keySet(), containsInAnyOrder(usage)); + assertThat(lastUsed.get(usage), equalTo(300L)); + + currentTime.set(400); + lastUsed = licenseState.getLastUsed(); + assertThat("last used no longer returns current", lastUsed.keySet(), containsInAnyOrder(usage)); + assertThat(lastUsed.get(usage), equalTo(300L)); } } diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/license/LicensingTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/license/LicensingTests.java index 8fd24370ad412..30dce4994fe0c 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/license/LicensingTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/license/LicensingTests.java @@ -37,6 +37,7 @@ import org.elasticsearch.transport.Netty4Plugin; import org.elasticsearch.transport.TransportInfo; import org.elasticsearch.xpack.core.XPackField; +import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.security.LocalStateSecurity; import org.hamcrest.Matchers; import org.junit.After; @@ -63,6 +64,9 @@ import static org.hamcrest.Matchers.notNullValue; public class LicensingTests extends SecurityIntegTestCase { + + private static final SecureString HASH_PASSWD = new SecureString(Hasher.BCRYPT4.hash(new SecureString("passwd".toCharArray()))); + private static final String ROLES = SecuritySettingsSource.TEST_ROLE + ":\n" + " cluster: [ all ]\n" + @@ -80,6 +84,9 @@ public class LicensingTests extends SecurityIntegTestCase { " indices:\n" + " - names: 'a'\n" + " privileges: [all]\n" + + " - names: 'test-dls'\n" + + " privileges: [read]\n" + + " query: '{\"term\":{\"field\":\"value\"} }'\n" + "\n" + "role_b:\n" + " indices:\n" + @@ -99,8 +106,8 @@ protected String configRoles() { @Override protected String configUsers() { return SecuritySettingsSource.CONFIG_STANDARD_USER + - "user_a:{plain}passwd\n" + - "user_b:{plain}passwd\n"; + "user_a:" + HASH_PASSWD + "\n" + + "user_b:" + HASH_PASSWD + "\n"; } @Override @@ -204,22 +211,24 @@ public void testNodeJoinWithoutSecurityExplicitlyEnabled() throws Exception { } public void testWarningHeader() throws Exception { - Request request = new Request("GET", "/_security/user"); + License.OperationMode mode = randomFrom(License.OperationMode.PLATINUM, License.OperationMode.ENTERPRISE); + enableLicensing(mode); + + // We test with "user_a" (that has a DLS-enabled role), so that we exercise licensed functionality and generate the warning header. + // Functionality under a basic (or SSPL) license does not necessarily generate expiration warnings + Request request = new Request("GET", "/_security/_authenticate"); RequestOptions.Builder options = request.getOptions().toBuilder(); - options.addHeader("Authorization", basicAuthHeaderValue(SecuritySettingsSource.TEST_USER_NAME, - new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.toCharArray()))); + options.addHeader("Authorization", basicAuthHeaderValue("user_a", new SecureString("passwd".toCharArray()))); request.setOptions(options); Response response = getRestClient().performRequest(request); List beforeWarningHeaders = getWarningHeaders(response.getHeaders()); assertTrue(beforeWarningHeaders.isEmpty()); - License.OperationMode mode = randomFrom(License.OperationMode.GOLD, License.OperationMode.PLATINUM, - License.OperationMode.ENTERPRISE, License.OperationMode.STANDARD); long now = System.currentTimeMillis(); long newExpirationDate = now + LICENSE_EXPIRATION_WARNING_PERIOD.getMillis() - 1; setLicensingExpirationDate(mode, newExpirationDate); response = getRestClient().performRequest(request); - List afterWarningHeaders= getWarningHeaders(response.getHeaders()); + List afterWarningHeaders = getWarningHeaders(response.getHeaders()); assertThat(afterWarningHeaders, Matchers.hasSize(1)); assertThat(afterWarningHeaders.get(0), Matchers.containsString("Your license will expire in [6] days. " + "Contact your administrator or update your license for continued use of features")); @@ -244,7 +253,7 @@ public void testWarningHeader() throws Exception { "Contact your administrator or update your license for continued use of features")); } - public void testNoWarningHeaderWhenAuthenticationFailed() throws Exception { + public void testNoWarningHeaderWhenAuthenticationFailed() throws Exception { Request request = new Request("GET", "/_security/user"); RequestOptions.Builder options = request.getOptions().toBuilder(); options.addHeader("Authorization", basicAuthHeaderValue(SecuritySettingsSource.TEST_USER_NAME, 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 fcb755088abba..05f439e7af4fb 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 @@ -54,6 +54,7 @@ import org.elasticsearch.ingest.Processor; import org.elasticsearch.license.License; import org.elasticsearch.license.LicenseService; +import org.elasticsearch.license.LicensedFeature; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState.Feature; import org.elasticsearch.plugins.ClusterPlugin; @@ -348,6 +349,20 @@ public class Security extends Plugin implements SystemIndexPlugin, IngestPlugin, public static final String SECURITY_CRYPTO_THREAD_POOL_NAME = XPackField.SECURITY + "-crypto"; + // TODO: ip filtering does not actually track license usage yet + public static final LicensedFeature.Momentary IP_FILTERING_FEATURE = + LicensedFeature.momentaryLenient("security_ip_filtering", License.OperationMode.GOLD); + public static final LicensedFeature.Momentary AUDITING_FEATURE = + LicensedFeature.momentaryLenient("security_auditing", License.OperationMode.GOLD); + + // Builtin realms (file/native) realms are Basic licensed, so don't need to be checked or tracked + // Standard realms (LDAP, AD, PKI, etc) are Gold+ + // SSO realms are Platinum+ + public static final LicensedFeature.Persistent STANDARD_REALMS_FEATURE = + LicensedFeature.persistentLenient("security_standard_realms", License.OperationMode.GOLD); + public static final LicensedFeature.Persistent ALL_REALMS_FEATURE = + LicensedFeature.persistentLenient("security_all_realms", License.OperationMode.PLATINUM); + private static final Logger logger = LogManager.getLogger(Security.class); public static final SystemIndexDescriptor SECURITY_MAIN_INDEX_DESCRIPTOR = getSecurityMainIndexDescriptor(); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java index 438eea7b642d3..cd37249698c15 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/InternalRealms.java @@ -67,17 +67,6 @@ public final class InternalRealms { private static final Set STANDARD_TYPES = Collections.unmodifiableSet(Sets.newHashSet(NativeRealmSettings.TYPE, FileRealmSettings.TYPE, LdapRealmSettings.AD_TYPE, LdapRealmSettings.LDAP_TYPE, PkiRealmSettings.TYPE)); - /** - * Determines whether type is an internal realm-type that is provided by x-pack, - * including the {@link ReservedRealm} - */ - static boolean isXPackRealm(String type) { - if (XPACK_TYPES.contains(type)) { - return true; - } - return ReservedRealm.TYPE.equals(type); - } - public static Collection getConfigurableRealmsTypes() { return Collections.unmodifiableSet(XPACK_TYPES); } @@ -91,6 +80,10 @@ static boolean isStandardRealm(String type) { return STANDARD_TYPES.contains(type); } + static boolean isBuiltinRealm(String type) { + return FileRealmSettings.TYPE.equals(type) || NativeRealmSettings.TYPE.equals(type); + } + /** * Creates {@link Realm.Factory factories} for each internal realm type. * This excludes the {@link ReservedRealm}, as it cannot be created dynamically. @@ -147,4 +140,5 @@ public static List getBootstrapChecks(final Settings globalSetti .collect(Collectors.toList()); return checks; } + } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java index 6418120cbb75f..f59c92fa17175 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java @@ -19,7 +19,6 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.license.XPackLicenseState.Feature; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.authc.Realm; import org.elasticsearch.xpack.core.security.authc.RealmConfig; @@ -27,6 +26,7 @@ import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings; import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings; import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import java.util.ArrayList; @@ -89,7 +89,7 @@ public Realms(Settings settings, Environment env, Map fac standardRealms.add(realm); } - if (FileRealmSettings.TYPE.equals(realm.type()) || NativeRealmSettings.TYPE.equals(realm.type())) { + if (InternalRealms.isBuiltinRealm(realm.type())) { basicRealms.add(realm); } } @@ -117,7 +117,7 @@ public List getUnlicensedRealms() { final XPackLicenseState licenseStateSnapshot = licenseState.copyCurrentLicenseState(); // If all realms are allowed, then nothing is unlicensed - if (licenseStateSnapshot.checkFeature(Feature.SECURITY_ALL_REALMS)) { + if (Security.ALL_REALMS_FEATURE.checkWithoutTracking(licenseStateSnapshot)) { return Collections.emptyList(); } @@ -128,8 +128,7 @@ public List getUnlicensedRealms() { } // Otherwise, we return anything in "all realms" that is not in the allowed realm list - List unlicensed = realms.stream().filter(r -> allowedRealms.contains(r) == false).collect(Collectors.toList()); - return Collections.unmodifiableList(unlicensed); + return realms.stream().filter(r -> allowedRealms.contains(r) == false).collect(Collectors.toUnmodifiableList()); } public Stream stream() { @@ -137,16 +136,21 @@ public Stream stream() { } public List asList() { - final XPackLicenseState licenseStateSnapshot = licenseState.copyCurrentLicenseState(); + // TODO : Recalculate this when the license changes rather than on every call + return realms.stream().filter(r -> checkLicense(r, licenseState)).collect(Collectors.toUnmodifiableList()); + } - if (licenseStateSnapshot.checkFeature(Feature.SECURITY_ALL_REALMS)) { - return realms; - } else if (licenseStateSnapshot.checkFeature(Feature.SECURITY_STANDARD_REALMS)) { - return standardRealmsOnly; - } else { - // native realms are basic licensed, and always allowed, even for an expired license - return nativeRealmsOnly; + private static boolean checkLicense(Realm realm, XPackLicenseState licenseState) { + if (ReservedRealm.TYPE.equals(realm.type())) { + return true; + } + if (InternalRealms.isBuiltinRealm(realm.type())) { + return true; + } + if (InternalRealms.isStandardRealm(realm.type())) { + return Security.STANDARD_REALMS_FEATURE.checkAndStartTracking(licenseState, realm.name()); } + return Security.ALL_REALMS_FEATURE.checkAndStartTracking(licenseState, realm.name()); } public Realm realm(String name) { @@ -313,7 +317,7 @@ private List buildRealmConfigs() { throw new IllegalArgumentException("unknown realm type [" + identifier.getType() + "] for realm [" + identifier + "]"); } RealmConfig config = new RealmConfig(identifier, settings, env, threadContext); - if (FileRealmSettings.TYPE.equals(identifier.getType()) || NativeRealmSettings.TYPE.equals(identifier.getType())) { + if (InternalRealms.isBuiltinRealm(identifier.getType())) { // this is an internal realm factory, let's make sure we didn't already registered one // (there can only be one instance of an internal realm) if (internalTypes.contains(identifier.getType())) { @@ -337,7 +341,7 @@ private List buildRealmConfigs() { private Set findDisabledBasicRealmTypes(List realmConfigs) { return realmConfigs.stream() - .filter(rc -> FileRealmSettings.TYPE.equals(rc.type()) || NativeRealmSettings.TYPE.equals(rc.type())) + .filter(rc -> InternalRealms.isBuiltinRealm(rc.type())) .filter(rc -> false == rc.enabled()) .map(RealmConfig::type) .collect(Collectors.toUnmodifiableSet()); @@ -380,12 +384,12 @@ private static Map convertToMapOfLists(Map map) } public static boolean isRealmTypeAvailable(XPackLicenseState licenseState, String type) { - if (licenseState.checkFeature(Feature.SECURITY_ALL_REALMS)) { + if (Security.ALL_REALMS_FEATURE.checkWithoutTracking(licenseState)) { return true; - } else if (licenseState.checkFeature(Feature.SECURITY_STANDARD_REALMS)) { + } else if (Security.STANDARD_REALMS_FEATURE.checkWithoutTracking(licenseState)) { return InternalRealms.isStandardRealm(type) || ReservedRealm.TYPE.equals(type); } else { - return FileRealmSettings.TYPE.equals(type) || NativeRealmSettings.TYPE.equals(type); + return InternalRealms.isBuiltinRealm(type); } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestDelegatePkiAuthenticationAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestDelegatePkiAuthenticationAction.java index 640e7584474e6..7f2373e65704c 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestDelegatePkiAuthenticationAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/RestDelegatePkiAuthenticationAction.java @@ -15,7 +15,6 @@ import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.license.XPackLicenseState.Feature; import org.elasticsearch.rest.BytesRestResponse; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.rest.RestResponse; @@ -25,6 +24,7 @@ import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationRequest; import org.elasticsearch.xpack.core.security.action.DelegatePkiAuthenticationResponse; import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.action.TransportDelegatePkiAuthenticationAction; import java.io.IOException; @@ -55,7 +55,7 @@ protected Exception checkFeatureAvailable(RestRequest request) { Exception failedFeature = super.checkFeatureAvailable(request); if (failedFeature != null) { return failedFeature; - } else if (licenseState.checkFeature(Feature.SECURITY_STANDARD_REALMS)) { + } else if (Security.STANDARD_REALMS_FEATURE.checkWithoutTracking(licenseState)) { return null; } else { logger.info("The '{}' realm is not available under the current license", PkiRealmSettings.TYPE); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java index 64c06a1cfa642..b60e4bbc8417e 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java @@ -18,8 +18,8 @@ import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.license.XPackLicenseState.Feature; import org.elasticsearch.transport.TransportSettings; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.audit.AuditTrail; import org.elasticsearch.xpack.security.audit.AuditTrailService; @@ -245,7 +245,7 @@ private void setHttpFiltering(boolean enabled) { } public boolean accept(String profile, InetSocketAddress peerAddress) { - if (licenseState.checkFeature(Feature.SECURITY_IP_FILTERING) == false) { + if (Security.IP_FILTERING_FEATURE.checkWithoutTracking(licenseState) == false) { return true; } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 0100528e6f6ad..c067b557ce9e3 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -51,6 +51,7 @@ import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.seqno.SequenceNumbers; import org.elasticsearch.index.shard.ShardId; +import org.elasticsearch.license.MockLicenseState; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState.Feature; import org.elasticsearch.rest.RestRequest; @@ -88,6 +89,7 @@ import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.audit.AuditTrail; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.audit.AuditUtil; @@ -145,6 +147,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.same; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -215,8 +218,8 @@ public void init() throws Exception { .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true) .put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true) .build(); - XPackLicenseState licenseState = mock(XPackLicenseState.class); - when(licenseState.checkFeature(Feature.SECURITY_ALL_REALMS)).thenReturn(true); + MockLicenseState licenseState = mock(MockLicenseState.class); + when(licenseState.isAllowed(Security.ALL_REALMS_FEATURE)).thenReturn(true); when(licenseState.checkFeature(Feature.SECURITY_TOKEN_SERVICE)).thenReturn(true); when(licenseState.copyCurrentLicenseState()).thenReturn(licenseState); when(licenseState.checkFeature(Feature.SECURITY_AUDITING)).thenReturn(true); @@ -444,8 +447,9 @@ public void testAuthenticateSmartRealmOrdering() { verify(auditTrail).authenticationFailed(reqId.get(), firstRealm.name(), token, "_action", transportRequest); verify(firstRealm, times(2)).name(); // used above one time - verify(secondRealm, times(2)).name(); - verify(secondRealm, times(2)).type(); // used to create realm ref + verify(firstRealm, atLeastOnce()).type(); + verify(secondRealm, Mockito.atLeast(3)).name(); // also used in license tracking + verify(secondRealm, Mockito.atLeast(3)).type(); // used to create realm ref, and license tracking verify(firstRealm, times(2)).token(threadContext); verify(secondRealm, times(2)).token(threadContext); verify(firstRealm).supports(token); @@ -569,8 +573,9 @@ public void testAuthenticateSmartRealmOrderingDisabled() { }, this::logAndFail)); verify(auditTrail, times(2)).authenticationFailed(reqId.get(), firstRealm.name(), token, "_action", transportRequest); verify(firstRealm, times(3)).name(); // used above one time - verify(secondRealm, times(2)).name(); - verify(secondRealm, times(2)).type(); // used to create realm ref + verify(firstRealm, atLeastOnce()).type(); + verify(secondRealm, Mockito.atLeast(3)).name(); + verify(secondRealm, Mockito.atLeast(3)).type(); // used to create realm ref verify(firstRealm, times(2)).token(threadContext); verify(secondRealm, times(2)).token(threadContext); verify(firstRealm, times(2)).supports(token); @@ -633,8 +638,10 @@ public void testAuthenticateCached() throws Exception { assertThat(result.v1(), is(authentication)); assertThat(result.v1().getAuthenticationType(), is(AuthenticationType.REALM)); verifyZeroInteractions(auditTrail); - verifyZeroInteractions(firstRealm); - verifyZeroInteractions(secondRealm); + verify(firstRealm, atLeastOnce()).type(); + verify(secondRealm, atLeastOnce()).type(); + verify(secondRealm, atLeastOnce()).name(); // This realm is license-tracked, which uses the name + verifyNoMoreInteractions(firstRealm, secondRealm); verifyZeroInteractions(operatorPrivilegesService); } @@ -913,7 +920,9 @@ public void testAuthenticateTransportContextAndHeader() throws Exception { verifyZeroInteractions(operatorPrivilegesService); }, this::logAndFail)); assertTrue(completed.compareAndSet(true, false)); - verifyZeroInteractions(firstRealm); + verify(firstRealm, atLeastOnce()).type(); + verify(firstRealm, atLeastOnce()).name(); + verifyNoMoreInteractions(firstRealm); reset(firstRealm); } finally { terminate(threadPool1); @@ -961,7 +970,9 @@ public void testAuthenticateTransportContextAndHeader() throws Exception { verifyZeroInteractions(operatorPrivilegesService); }, this::logAndFail)); assertTrue(completed.get()); - verifyZeroInteractions(firstRealm); + verify(firstRealm, atLeastOnce()).type(); + verify(firstRealm, atLeastOnce()).name(); + verifyNoMoreInteractions(firstRealm); } finally { terminate(threadPool2); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java index 96d5470c08c9d..d2e2affb0ed60 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java @@ -13,8 +13,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; import org.elasticsearch.env.TestEnvironment; -import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.license.XPackLicenseState.Feature; +import org.elasticsearch.license.MockLicenseState; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; @@ -27,6 +26,7 @@ import org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings; import org.elasticsearch.xpack.core.security.authc.saml.SamlRealmSettings; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.junit.Before; @@ -54,11 +54,12 @@ import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class RealmsTests extends ESTestCase { private Map factories; - private XPackLicenseState licenseState; + private MockLicenseState licenseState; private ThreadContext threadContext; private ReservedRealm reservedRealm; private int randomRealmTypesCount; @@ -74,7 +75,7 @@ public void init() throws Exception { String name = "type_" + i; factories.put(name, config -> new DummyRealm(name, config)); } - licenseState = mock(XPackLicenseState.class); + licenseState = mock(MockLicenseState.class); when(licenseState.copyCurrentLicenseState()).thenReturn(licenseState); threadContext = new ThreadContext(Settings.EMPTY); reservedRealm = mock(ReservedRealm.class); @@ -84,18 +85,18 @@ public void init() throws Exception { } private void allowAllRealms() { - when(licenseState.checkFeature(Feature.SECURITY_ALL_REALMS)).thenReturn(true); - when(licenseState.checkFeature(Feature.SECURITY_STANDARD_REALMS)).thenReturn(true); + when(licenseState.isAllowed(Security.ALL_REALMS_FEATURE)).thenReturn(true); + when(licenseState.isAllowed(Security.STANDARD_REALMS_FEATURE)).thenReturn(true); } private void allowOnlyStandardRealms() { - when(licenseState.checkFeature(Feature.SECURITY_ALL_REALMS)).thenReturn(false); - when(licenseState.checkFeature(Feature.SECURITY_STANDARD_REALMS)).thenReturn(true); + when(licenseState.isAllowed(Security.ALL_REALMS_FEATURE)).thenReturn(false); + when(licenseState.isAllowed(Security.STANDARD_REALMS_FEATURE)).thenReturn(true); } private void allowOnlyNativeRealms() { - when(licenseState.checkFeature(Feature.SECURITY_ALL_REALMS)).thenReturn(false); - when(licenseState.checkFeature(Feature.SECURITY_STANDARD_REALMS)).thenReturn(false); + when(licenseState.isAllowed(Security.ALL_REALMS_FEATURE)).thenReturn(false); + when(licenseState.isAllowed(Security.STANDARD_REALMS_FEATURE)).thenReturn(false); } public void testWithSettings() throws Exception { @@ -257,6 +258,9 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception { assertThat(realms.getUnlicensedRealms(), empty()); assertThat(realms.getUnlicensedRealms(), sameInstance(realms.getUnlicensedRealms())); + for (i = 0; i < randomRealmTypesCount; i++) { + verify(licenseState).enableUsageTracking(Security.ALL_REALMS_FEATURE, "realm_" + i); + } allowOnlyNativeRealms(); @@ -282,9 +286,10 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception { public void testUnlicensedWithInternalRealms() throws Exception { factories.put(LdapRealmSettings.LDAP_TYPE, config -> new DummyRealm(LdapRealmSettings.LDAP_TYPE, config)); assertThat(factories.get("type_0"), notNullValue()); + String ldapRealmName = randomAlphaOfLengthBetween(3, 8); Settings.Builder builder = Settings.builder() .put("path.home", createTempDir()) - .put("xpack.security.authc.realms.ldap.foo.order", "0") + .put("xpack.security.authc.realms.ldap." + ldapRealmName + ".order", "0") .put("xpack.security.authc.realms.type_0.custom.order", "1"); final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); @@ -305,6 +310,7 @@ public void testUnlicensedWithInternalRealms() throws Exception { assertThat(realms.getUnlicensedRealms(), empty()); assertThat(realms.getUnlicensedRealms(), sameInstance(realms.getUnlicensedRealms())); + verify(licenseState).enableUsageTracking(Security.STANDARD_REALMS_FEATURE, ldapRealmName); allowOnlyStandardRealms(); iter = realms.iterator(); @@ -331,7 +337,7 @@ public void testUnlicensedWithInternalRealms() throws Exception { assertThat(realms.getUnlicensedRealms(), iterableWithSize(2)); realm = realms.getUnlicensedRealms().get(0); assertThat(realm.type(), equalTo("ldap")); - assertThat(realm.name(), equalTo("foo")); + assertThat(realm.name(), equalTo(ldapRealmName)); realm = realms.getUnlicensedRealms().get(1); assertThat(realm.type(), equalTo("type_0")); assertThat(realm.name(), equalTo("custom")); @@ -366,6 +372,7 @@ public void testUnlicensedWithBasicRealmSettings() throws Exception { assertThat(realm.type(), is(type)); assertThat(iter.hasNext(), is(false)); assertThat(realms.getUnlicensedRealms(), empty()); + verify(licenseState).enableUsageTracking(Security.STANDARD_REALMS_FEATURE, "foo"); allowOnlyNativeRealms(); iter = realms.iterator(); @@ -391,9 +398,10 @@ public void testUnlicensedWithBasicRealmSettings() throws Exception { public void testUnlicensedWithNonStandardRealms() throws Exception { final String selectedRealmType = randomFrom(SamlRealmSettings.TYPE, KerberosRealmSettings.TYPE, OpenIdConnectRealmSettings.TYPE); factories.put(selectedRealmType, config -> new DummyRealm(selectedRealmType, config)); + String realmName = randomAlphaOfLengthBetween(3, 8); Settings.Builder builder = Settings.builder() - .put("path.home", createTempDir()) - .put("xpack.security.authc.realms." + selectedRealmType + ".foo.order", "0"); + .put("path.home", createTempDir()) + .put("xpack.security.authc.realms." + selectedRealmType + "." + realmName + ".order", "0"); final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); Settings settings = builder.build(); @@ -408,6 +416,7 @@ public void testUnlicensedWithNonStandardRealms() throws Exception { realm = iter.next(); assertThat(realm.type(), is(selectedRealmType)); assertThat(realms.getUnlicensedRealms(), empty()); + verify(licenseState).enableUsageTracking(Security.ALL_REALMS_FEATURE, realmName); allowOnlyStandardRealms(); iter = realms.iterator(); @@ -419,7 +428,7 @@ public void testUnlicensedWithNonStandardRealms() throws Exception { assertThat(realms.getUnlicensedRealms(), iterableWithSize(1)); realm = realms.getUnlicensedRealms().get(0); assertThat(realm.type(), equalTo(selectedRealmType)); - assertThat(realm.name(), equalTo("foo")); + assertThat(realm.name(), equalTo(realmName)); allowOnlyNativeRealms(); iter = realms.iterator(); @@ -431,7 +440,7 @@ public void testUnlicensedWithNonStandardRealms() throws Exception { assertThat(realms.getUnlicensedRealms(), iterableWithSize(1)); realm = realms.getUnlicensedRealms().get(0); assertThat(realm.type(), equalTo(selectedRealmType)); - assertThat(realm.name(), equalTo("foo")); + assertThat(realm.name(), equalTo(realmName)); } public void testDisabledRealmsAreNotAdded() throws Exception { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/filter/IPFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/filter/IPFilterTests.java index 7bdfc7670698f..76e56801d0c25 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/filter/IPFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/filter/IPFilterTests.java @@ -15,14 +15,15 @@ import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; -import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.license.XPackLicenseState.Feature; +import org.elasticsearch.license.MockLicenseState; +import org.elasticsearch.license.TestUtils; import org.elasticsearch.node.MockNode; import org.elasticsearch.node.Node; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.junit.annotations.Network; import org.elasticsearch.transport.Transport; import org.elasticsearch.xpack.security.LocalStateSecurity; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.audit.AuditTrail; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.junit.Before; @@ -51,7 +52,7 @@ public class IPFilterTests extends ESTestCase { private IPFilter ipFilter; - private XPackLicenseState licenseState; + private MockLicenseState licenseState; private AuditTrail auditTrail; private AuditTrailService auditTrailService; private Transport transport; @@ -60,9 +61,9 @@ public class IPFilterTests extends ESTestCase { @Before public void init() { - licenseState = mock(XPackLicenseState.class); - when(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING)).thenReturn(true); - when(licenseState.checkFeature(Feature.SECURITY_AUDITING)).thenReturn(true); + licenseState = TestUtils.newMockLicenceState(); + when(licenseState.isAllowed(Security.IP_FILTERING_FEATURE)).thenReturn(true); + when(licenseState.isAllowed(Security.AUDITING_FEATURE)).thenReturn(true); auditTrail = mock(AuditTrail.class); auditTrailService = new AuditTrailService(Collections.singletonList(auditTrail), licenseState); clusterSettings = new ClusterSettings(Settings.EMPTY, new HashSet<>(Arrays.asList( @@ -252,7 +253,7 @@ public void testThatAllAddressesAreAllowedWhenLicenseDisablesSecurity() { Settings settings = Settings.builder() .put("xpack.security.transport.filter.deny", "_all") .build(); - when(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING)).thenReturn(false); + when(licenseState.isAllowed(Security.IP_FILTERING_FEATURE)).thenReturn(false); ipFilter = new IPFilter(settings, auditTrailService, clusterSettings, licenseState); ipFilter.setBoundTransportAddress(transport.boundAddress(), transport.profileBoundAddresses()); @@ -263,7 +264,7 @@ public void testThatAllAddressesAreAllowedWhenLicenseDisablesSecurity() { verifyZeroInteractions(auditTrail); // for sanity enable license and check that it is denied - when(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING)).thenReturn(true); + when(licenseState.isAllowed(Security.IP_FILTERING_FEATURE)).thenReturn(true); ipFilter = new IPFilter(settings, auditTrailService, clusterSettings, licenseState); ipFilter.setBoundTransportAddress(transport.boundAddress(), transport.profileBoundAddresses()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IpFilterRemoteAddressFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IpFilterRemoteAddressFilterTests.java index 7fe30c5c43806..7ac3ff3cf270e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IpFilterRemoteAddressFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/netty4/IpFilterRemoteAddressFilterTests.java @@ -14,10 +14,11 @@ import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; -import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.license.XPackLicenseState.Feature; +import org.elasticsearch.license.MockLicenseState; +import org.elasticsearch.license.TestUtils; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.Transport; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.junit.Before; @@ -57,8 +58,8 @@ public void init() throws Exception { IPFilter.TRANSPORT_FILTER_DENY_SETTING, IPFilter.PROFILE_FILTER_ALLOW_SETTING, IPFilter.PROFILE_FILTER_DENY_SETTING))); - XPackLicenseState licenseState = mock(XPackLicenseState.class); - when(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING)).thenReturn(true); + MockLicenseState licenseState = TestUtils.newMockLicenceState(); + when(licenseState.isAllowed(Security.IP_FILTERING_FEATURE)).thenReturn(true); AuditTrailService auditTrailService = new AuditTrailService(Collections.emptyList(), licenseState); IPFilter ipFilter = new IPFilter(settings, auditTrailService, clusterSettings, licenseState); ipFilter.setBoundTransportAddress(transport.boundAddress(), transport.profileBoundAddresses()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/NioIPFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/NioIPFilterTests.java index 0ab94868a9764..8c3c3f0647f0c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/NioIPFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/transport/nio/NioIPFilterTests.java @@ -13,11 +13,12 @@ import org.elasticsearch.common.transport.BoundTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; -import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.license.XPackLicenseState.Feature; +import org.elasticsearch.license.MockLicenseState; +import org.elasticsearch.license.TestUtils; import org.elasticsearch.nio.NioChannelHandler; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.Transport; +import org.elasticsearch.xpack.security.Security; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.junit.Before; @@ -60,8 +61,8 @@ public void init() throws Exception { IPFilter.TRANSPORT_FILTER_DENY_SETTING, IPFilter.PROFILE_FILTER_ALLOW_SETTING, IPFilter.PROFILE_FILTER_DENY_SETTING))); - XPackLicenseState licenseState = mock(XPackLicenseState.class); - when(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING)).thenReturn(true); + MockLicenseState licenseState = TestUtils.newMockLicenceState(); + when(licenseState.isAllowed(Security.IP_FILTERING_FEATURE)).thenReturn(true); AuditTrailService auditTrailService = new AuditTrailService(Collections.emptyList(), licenseState); ipFilter = new IPFilter(settings, auditTrailService, clusterSettings, licenseState); ipFilter.setBoundTransportAddress(transport.boundAddress(), transport.profileBoundAddresses());