diff --git a/server/src/main/java/org/elasticsearch/http/DefaultRestChannel.java b/server/src/main/java/org/elasticsearch/http/DefaultRestChannel.java index b2e7ad9e8baf2..b72777d0c4bad 100644 --- a/server/src/main/java/org/elasticsearch/http/DefaultRestChannel.java +++ b/server/src/main/java/org/elasticsearch/http/DefaultRestChannel.java @@ -123,7 +123,7 @@ public void sendResponse(RestResponse restResponse) { // Add all custom headers addCustomHeaders(httpResponse, restResponse.getHeaders()); - addCustomHeaders(httpResponse, threadContext.getResponseHeaders()); + addCustomHeaders(httpResponse, restResponse.filterHeaders(threadContext.getResponseHeaders())); // If our response doesn't specify a content-type header, set one setHeaderField(httpResponse, CONTENT_TYPE, restResponse.contentType(), false); diff --git a/server/src/main/java/org/elasticsearch/rest/RestResponse.java b/server/src/main/java/org/elasticsearch/rest/RestResponse.java index d0d6fa752d68e..60a6dc4895676 100644 --- a/server/src/main/java/org/elasticsearch/rest/RestResponse.java +++ b/server/src/main/java/org/elasticsearch/rest/RestResponse.java @@ -89,4 +89,8 @@ public Map> getHeaders() { return customHeaders; } } + + public Map> filterHeaders(Map> headers) { + return headers; + } } 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 ea7294c179b14..1dc07cf76dd0e 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 @@ -82,6 +82,11 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste */ static final TimeValue GRACE_PERIOD_DURATION = days(7); + /** + * Period before the license expires when warning starts being added to the response header + */ + static final TimeValue LICENSE_EXPIRATION_WARNING_PERIOD = days(7); + public static final long BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS = XPackInfoResponse.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; @@ -125,7 +130,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste public static final String LICENSE_JOB = "licenseJob"; - private static final DateFormatter DATE_FORMATTER = DateFormatter.forPattern("EEEE, MMMM dd, yyyy"); + public static final DateFormatter DATE_FORMATTER = DateFormatter.forPattern("EEEE, MMMM dd, yyyy"); 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:"; @@ -476,7 +481,7 @@ private void updateLicenseState(LicensesMetadata licensesMetadata) { protected void updateLicenseState(final License license, Version mostRecentTrialVersion) { if (license == LicensesMetadata.LICENSE_TOMBSTONE) { // implies license has been explicitly deleted - licenseState.update(License.OperationMode.MISSING, false, mostRecentTrialVersion); + licenseState.update(License.OperationMode.MISSING, false, license.expiryDate(), mostRecentTrialVersion); return; } if (license != null) { @@ -489,7 +494,7 @@ protected void updateLicenseState(final License license, Version mostRecentTrial // date that is near Long.MAX_VALUE active = time >= license.issueDate() && time - GRACE_PERIOD_DURATION.getMillis() < license.expiryDate(); } - licenseState.update(license.operationMode(), active, mostRecentTrialVersion); + licenseState.update(license.operationMode(), active, license.expiryDate(), mostRecentTrialVersion); if (active) { if (time < license.expiryDate()) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseStateListener.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseStateListener.java index ef3302613c333..fb909138741b8 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseStateListener.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseStateListener.java @@ -15,7 +15,7 @@ public interface LicenseStateListener { /** - * Callback when the license state changes. See {@link XPackLicenseState#update(License.OperationMode, boolean, Version)}. + * Callback when the license state changes. See {@link XPackLicenseState#update(License.OperationMode, boolean, long, Version)}. */ void licenseStateChanged(); 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 bed17458cb953..e91ed5cf5c3e5 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 @@ -8,6 +8,8 @@ import org.elasticsearch.Version; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.SuppressForbidden; +import org.elasticsearch.common.logging.HeaderWarning; import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.License.OperationMode; @@ -19,10 +21,12 @@ 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.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAccumulator; import java.util.function.BiFunction; import java.util.function.Function; @@ -30,6 +34,8 @@ import java.util.function.Predicate; import java.util.stream.Collectors; +import static org.elasticsearch.license.LicenseService.LICENSE_EXPIRATION_WARNING_PERIOD; + /** * A holder for the current state of the license for all xpack features. */ @@ -399,7 +405,7 @@ private static boolean isBasic(OperationMode mode) { return mode == OperationMode.BASIC; } - /** A wrapper for the license mode and state, to allow atomically swapping. */ + /** A wrapper for the license mode, state, and expiration date, to allow atomically swapping. */ private static class Status { /** The current "mode" of the license (ie license type). */ @@ -408,9 +414,13 @@ private static class Status { /** True if the license is active, or false if it is expired. */ final boolean active; - Status(OperationMode mode, boolean active) { + /** The current expiration date of the license; Long.MAX_VALUE if not available yet. */ + final long licenseExpiryDate; + + Status(OperationMode mode, boolean active, long licenseExpiryDate) { this.mode = mode; this.active = active; + this.licenseExpiryDate = licenseExpiryDate; } } @@ -424,7 +434,7 @@ private static class Status { // XPackLicenseState. However, if status is read multiple times in a method, it can change in between // reads. Methods should use `executeAgainstStatus` and `checkAgainstStatus` to ensure that the status // is only read once. - private volatile Status status = new Status(OperationMode.TRIAL, true); + private volatile Status status = new Status(OperationMode.TRIAL, true, Long.MAX_VALUE); public XPackLicenseState(Settings settings, LongSupplier epochMillisProvider) { this.listeners = new CopyOnWriteArrayList<>(); @@ -472,12 +482,13 @@ private boolean checkAgainstStatus(Predicate statusPredicate) { * * @param mode The mode (type) of the current license. * @param active True if the current license exists and is within its allowed usage period; false if it is expired or missing. + * @param expirationDate Expiration date of the current license. * @param mostRecentTrialVersion If this cluster has, at some point commenced a trial, the most recent version on which they did that. * 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, @Nullable Version mostRecentTrialVersion) { - status = new Status(mode, active); + void update(OperationMode mode, boolean active, long expirationDate, @Nullable Version mostRecentTrialVersion) { + status = new Status(mode, active, expirationDate); listeners.forEach(LicenseStateListener::licenseStateChanged); } @@ -513,12 +524,26 @@ boolean isActive() { /** * Checks whether the given feature is allowed, tracking the last usage time. */ + @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()); } + + if (feature.minimumOperationMode.compareTo(OperationMode.BASIC) > 0 && + LICENSE_EXPIRATION_WARNING_PERIOD.getMillis() > diff) { + final long days = TimeUnit.MILLISECONDS.toDays(diff); + final String expiryMessage = (days == 0 && diff > 0)? "expires today": + (diff > 0? String.format(Locale.ROOT, "will expire in [%d] days", days): + String.format(Locale.ROOT, "expired on [%s]", LicenseService.DATE_FORMATTER.formatMillis(licenseExpiryDate))); + HeaderWarning.addWarning("Your license {}. " + + "Contact your administrator or update your license for continued use of features", expiryMessage); + } + return allowed; } @@ -635,6 +660,11 @@ public boolean isAllowedByLicense(OperationMode minimumMode, boolean needActive) }); } + /** Return the current license expiration date. */ + public long getLicenseExpiryDate() { + return executeAgainstStatus(status -> status.licenseExpiryDate); + } + /** * A convenient method to test whether a feature is by license status. * @see #isAllowedByLicense(OperationMode, boolean) 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 27222105f3e39..6a0d0f34c7f34 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 @@ -360,21 +360,23 @@ public static class AssertingLicenseState extends XPackLicenseState { public final List modeUpdates = new ArrayList<>(); public final List activeUpdates = new ArrayList<>(); public final List trialVersionUpdates = new ArrayList<>(); + public final List expirationDateUpdates = new ArrayList<>(); public AssertingLicenseState() { super(Settings.EMPTY, () -> 0); } @Override - void update(License.OperationMode mode, boolean active, Version mostRecentTrialVersion) { + void update(License.OperationMode mode, boolean active, long expirationDate, Version mostRecentTrialVersion) { modeUpdates.add(mode); activeUpdates.add(active); + expirationDateUpdates.add(expirationDate); trialVersionUpdates.add(mostRecentTrialVersion); } } /** - * A license state that makes the {@link #update(License.OperationMode, boolean, Version)} + * A license state that makes the {@link #update(License.OperationMode, boolean, long, Version)} * method public for use in tests. */ public static class UpdatableLicenseState extends XPackLicenseState { @@ -387,8 +389,8 @@ public UpdatableLicenseState(Settings settings) { } @Override - public void update(License.OperationMode mode, boolean active, Version mostRecentTrialVersion) { - super.update(mode, active, mostRecentTrialVersion); + public void update(License.OperationMode mode, boolean active, long expirationDate, Version mostRecentTrialVersion) { + super.update(mode, active, expirationDate, mostRecentTrialVersion); } } 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 4aa2ebbb1e9d6..ca3a70011f3ad 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 @@ -38,7 +38,7 @@ public class XPackLicenseStateTests extends ESTestCase { /** Creates a license state with the given license type and active state, and checks the given method returns expected. */ void assertAllowed(OperationMode mode, boolean active, Predicate predicate, boolean expected) { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(mode, active, null); + licenseState.update(mode, active, Long.MAX_VALUE, null); assertEquals(expected, predicate.test(licenseState)); } @@ -102,7 +102,7 @@ public void testTransportSslDoesNotAutomaticallyEnableSecurityOnTrialLicense() { public void testSecurityBasicWithoutExplicitSecurityEnabled() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(BASIC, true, null); + licenseState.update(BASIC, true, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); @@ -120,7 +120,7 @@ public void testSecurityBasicWithoutExplicitSecurityEnabled() { public void testSecurityBasicWithExplicitSecurityEnabled() { final Settings settings = Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build(); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(BASIC, true, null); + licenseState.update(BASIC, true, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); @@ -137,7 +137,7 @@ public void testSecurityBasicWithExplicitSecurityEnabled() { public void testSecurityDefaultBasicExpired() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(BASIC, false, null); + licenseState.update(BASIC, false, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(false)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); @@ -152,7 +152,7 @@ public void testSecurityDefaultBasicExpired() { public void testSecurityEnabledBasicExpired() { Settings settings = Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build(); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(BASIC, false, null); + licenseState.update(BASIC, false, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); @@ -168,7 +168,7 @@ public void testSecurityStandard() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(STANDARD, true, null); + licenseState.update(STANDARD, true, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); @@ -182,7 +182,7 @@ public void testSecurityStandardExpired() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(STANDARD, false, null); + licenseState.update(STANDARD, false, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(false)); @@ -196,7 +196,7 @@ public void testSecurityGold() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(GOLD, true, null); + licenseState.update(GOLD, true, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); @@ -213,7 +213,7 @@ public void testSecurityGoldExpired() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(GOLD, false, null); + licenseState.update(GOLD, false, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); @@ -230,7 +230,7 @@ public void testSecurityPlatinum() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(PLATINUM, true, null); + licenseState.update(PLATINUM, true, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); @@ -247,7 +247,7 @@ public void testSecurityPlatinumExpired() { Settings settings = randomFrom(Settings.EMPTY, Settings.builder().put(XPackSettings.SECURITY_ENABLED.getKey(), true).build()); XPackLicenseState licenseState = new XPackLicenseState(settings, () -> 0); - licenseState.update(PLATINUM, false, null); + licenseState.update(PLATINUM, false, Long.MAX_VALUE, null); assertThat(licenseState.isSecurityEnabled(), is(true)); assertThat(licenseState.checkFeature(Feature.SECURITY_IP_FILTERING), is(true)); @@ -262,7 +262,7 @@ public void testSecurityPlatinumExpired() { public void testNewTrialDefaultsSecurityOff() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(TRIAL, true, VersionUtils.randomVersionBetween(random(), Version.V_6_3_0, Version.CURRENT)); + licenseState.update(TRIAL, true, Long.MAX_VALUE, VersionUtils.randomVersionBetween(random(), Version.V_6_3_0, Version.CURRENT)); assertThat(licenseState.isSecurityEnabled(), is(false)); assertSecurityNotAllowed(licenseState); @@ -419,7 +419,7 @@ public void testSqlDefaults() { public void testSqlBasic() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(BASIC, true, null); + licenseState.update(BASIC, true, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(true)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(false)); @@ -427,7 +427,7 @@ public void testSqlBasic() { public void testSqlBasicExpired() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(BASIC, false, null); + licenseState.update(BASIC, false, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(false)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(false)); @@ -435,7 +435,7 @@ public void testSqlBasicExpired() { public void testSqlStandard() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(STANDARD, true, null); + licenseState.update(STANDARD, true, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(true)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(false)); @@ -443,7 +443,7 @@ public void testSqlStandard() { public void testSqlStandardExpired() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(STANDARD, false, null); + licenseState.update(STANDARD, false, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(false)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(false)); @@ -451,7 +451,7 @@ public void testSqlStandardExpired() { public void testSqlGold() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(GOLD, true, null); + licenseState.update(GOLD, true, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(true)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(false)); @@ -459,7 +459,7 @@ public void testSqlGold() { public void testSqlGoldExpired() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(GOLD, false, null); + licenseState.update(GOLD, false, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(false)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(false)); @@ -467,7 +467,7 @@ public void testSqlGoldExpired() { public void testSqlPlatinum() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(PLATINUM, true, null); + licenseState.update(PLATINUM, true, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(true)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(true)); @@ -475,7 +475,7 @@ public void testSqlPlatinum() { public void testSqlPlatinumExpired() { XPackLicenseState licenseState = TestUtils.newTestLicenseState(); - licenseState.update(PLATINUM, false, null); + licenseState.update(PLATINUM, false, Long.MAX_VALUE, null); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.SQL), is(false)); assertThat(licenseState.checkFeature(XPackLicenseState.Feature.JDBC), is(false)); @@ -496,56 +496,56 @@ public void testCcrDefaults() { public void testCcrBasic() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(BASIC, true, null); + state.update(BASIC, true, Long.MAX_VALUE, null); assertThat(state.checkFeature(XPackLicenseState.Feature.CCR), is(false)); } public void testCcrBasicExpired() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(BASIC, false, null); + state.update(BASIC, false, Long.MAX_VALUE, null); assertThat(state.checkFeature(XPackLicenseState.Feature.CCR), is(false)); } public void testCcrStandard() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(STANDARD, true, null); + state.update(STANDARD, true, Long.MAX_VALUE, null); assertThat(state.checkFeature(XPackLicenseState.Feature.CCR), is(false)); } public void testCcrStandardExpired() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(STANDARD, false, null); + state.update(STANDARD, false, Long.MAX_VALUE, null); assertThat(state.checkFeature(XPackLicenseState.Feature.CCR), is(false)); } public void testCcrGold() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(GOLD, true, null); + state.update(GOLD, true, Long.MAX_VALUE, null); assertThat(state.checkFeature(XPackLicenseState.Feature.CCR), is(false)); } public void testCcrGoldExpired() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(GOLD, false, null); + state.update(GOLD, false, Long.MAX_VALUE, null); assertThat(state.checkFeature(XPackLicenseState.Feature.CCR), is(false)); } public void testCcrPlatinum() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(PLATINUM, true, null); + state.update(PLATINUM, true, Long.MAX_VALUE, null); assertTrue(state.checkFeature(XPackLicenseState.Feature.CCR)); } public void testCcrPlatinumExpired() { final XPackLicenseState state = TestUtils.newTestLicenseState(); - state.update(PLATINUM, false, null); + state.update(PLATINUM, false, Long.MAX_VALUE, null); assertFalse(state.checkFeature(XPackLicenseState.Feature.CCR)); } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/test/SecuritySettingsSourceField.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/test/SecuritySettingsSourceField.java index 12082b770436d..eb0c450bc2107 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/test/SecuritySettingsSourceField.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/test/SecuritySettingsSourceField.java @@ -10,6 +10,7 @@ public final class SecuritySettingsSourceField { public static final SecureString TEST_PASSWORD_SECURE_STRING = new SecureString("x-pack-test-password".toCharArray()); public static final String TEST_PASSWORD = "x-pack-test-password"; + public static final String TEST_INVALID_PASSWORD = "invalid-test-password"; private SecuritySettingsSourceField() {} } diff --git a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/rest/action/IdpBaseRestHandlerTests.java b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/rest/action/IdpBaseRestHandlerTests.java index 2593c362b582f..6eb4206a00ad9 100644 --- a/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/rest/action/IdpBaseRestHandlerTests.java +++ b/x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/saml/rest/action/IdpBaseRestHandlerTests.java @@ -38,7 +38,7 @@ private IdpBaseRestHandler buildHandler(License.OperationMode licenseMode) { .put("xpack.idp.enabled", true) .build(); final TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState(settings); - licenseState.update(licenseMode, true, null); + licenseState.update(licenseMode, true, Long.MAX_VALUE, null); return new IdpBaseRestHandler(licenseState) { @Override protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException { diff --git a/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/license/MachineLearningLicensingIT.java b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/license/MachineLearningLicensingIT.java index 05f564c13348e..3678d669bae92 100644 --- a/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/license/MachineLearningLicensingIT.java +++ b/x-pack/plugin/ml/src/internalClusterTest/java/org/elasticsearch/license/MachineLearningLicensingIT.java @@ -852,7 +852,7 @@ public static void disableLicensing() { public static void disableLicensing(License.OperationMode operationMode) { for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { - licenseState.update(operationMode, false, null); + licenseState.update(operationMode, false, Long.MAX_VALUE, null); } } @@ -862,7 +862,7 @@ public static void enableLicensing() { public static void enableLicensing(License.OperationMode operationMode) { for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { - licenseState.update(operationMode, true, null); + licenseState.update(operationMode, true, Long.MAX_VALUE, null); } } } 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 ded11e8444cab..6b2ff9d6ba3ce 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 @@ -5,6 +5,7 @@ */ package org.elasticsearch.license; +import org.apache.http.Header; import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; @@ -15,6 +16,11 @@ import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.Client; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.CollectionUtils; import org.elasticsearch.discovery.DiscoveryModule; @@ -26,24 +32,30 @@ import org.elasticsearch.test.MockHttpTransport; import org.elasticsearch.test.SecurityIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; +import org.elasticsearch.test.SecuritySettingsSourceField; import org.elasticsearch.transport.Netty4Plugin; import org.elasticsearch.transport.TransportInfo; import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.security.LocalStateSecurity; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.discovery.SettingsBasedSeedHostsProvider.DISCOVERY_SEED_HOSTS_SETTING; +import static org.elasticsearch.license.LicenseService.LICENSE_EXPIRATION_WARNING_PERIOD; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; +import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; @@ -190,6 +202,69 @@ public void testNodeJoinWithoutSecurityExplicitlyEnabled() throws Exception { } } + public void testWarningHeader() throws Exception { + Request request = new Request("GET", "/_security/user"); + RequestOptions.Builder options = request.getOptions().toBuilder(); + options.addHeader("Authorization", basicAuthHeaderValue(SecuritySettingsSource.TEST_USER_NAME, + new SecureString(SecuritySettingsSourceField.TEST_PASSWORD.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()); + 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")); + + newExpirationDate = now + 300000; + setLicensingExpirationDate(mode, newExpirationDate); + response = getRestClient().performRequest(request); + afterWarningHeaders= getWarningHeaders(response.getHeaders()); + assertThat(afterWarningHeaders, Matchers.hasSize(1)); + assertThat(afterWarningHeaders.get(0), Matchers.containsString("Your license expires today. " + + "Contact your administrator or update your license for continued use of features")); + + newExpirationDate = now - 300000; + setLicensingExpirationDate(mode, newExpirationDate); + response = getRestClient().performRequest(request); + afterWarningHeaders= getWarningHeaders(response.getHeaders()); + assertThat(afterWarningHeaders, Matchers.hasSize(1)); + long finalNewExpirationDate = newExpirationDate; + String expiredMessage = String.format(Locale.ROOT, "Your license expired on [%s]. ", + LicenseService.DATE_FORMATTER.formatMillis(finalNewExpirationDate)); + assertThat(afterWarningHeaders.get(0), Matchers.containsString(expiredMessage + + "Contact your administrator or update your license for continued use of features")); + } + + 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, + new SecureString(SecuritySettingsSourceField.TEST_INVALID_PASSWORD.toCharArray()))); + request.setOptions(options); + 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); + Header[] headers = null; + try { + getRestClient().performRequest(request); + } catch (ResponseException e) { + headers = e.getResponse().getHeaders(); + List afterWarningHeaders= getWarningHeaders(e.getResponse().getHeaders()); + assertThat(afterWarningHeaders, Matchers.hasSize(0)); + } + assertThat(headers != null && headers.length == 3, is(true)); + } + private static void assertElasticsearchSecurityException(ThrowingRunnable runnable) { ElasticsearchSecurityException ee = expectThrows(ElasticsearchSecurityException.class, runnable); assertThat(ee.getMetadata(LicenseUtils.EXPIRED_FEATURE_METADATA), hasItem(XPackField.SECURITY)); @@ -211,7 +286,7 @@ private void disableLicensing() throws Exception { // apply the disabling of the license once the cluster is stable for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { - licenseState.update(OperationMode.BASIC, false, null); + licenseState.update(OperationMode.BASIC, false, Long.MAX_VALUE, null); } }, 30L, TimeUnit.SECONDS); } @@ -223,7 +298,7 @@ private void enableLicensing(License.OperationMode operationMode) throws Excepti assertBusy(() -> { // first update the license so we can execute monitoring actions for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { - licenseState.update(operationMode, true, null); + licenseState.update(operationMode, true, Long.MAX_VALUE, null); } ensureGreen(); @@ -233,8 +308,32 @@ private void enableLicensing(License.OperationMode operationMode) throws Excepti // re-apply the update in case any node received an updated cluster state that triggered the license state // to change for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { - licenseState.update(operationMode, true, null); + licenseState.update(operationMode, true, Long.MAX_VALUE, null); } }, 30L, TimeUnit.SECONDS); } + + private void setLicensingExpirationDate(License.OperationMode operationMode, long expirationDate) throws Exception { + assertBusy(() -> { + for (XPackLicenseState licenseState : internalCluster().getInstances(XPackLicenseState.class)) { + licenseState.update(operationMode, true, expirationDate, null); + } + + ensureGreen(); + ensureClusterSizeConsistency(); + ensureClusterStateConsistency(); + }, 30L, TimeUnit.SECONDS); + } + + private List getWarningHeaders(Header[] headers) { + List warnings = new ArrayList<>(); + + for (Header header : headers) { + if (header.getName().equals("Warning")) { + warnings.add(header.getValue()); + } + } + + return warnings; + } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java index 69c444aa9bf4d..b6a9844dbcc7a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/SecurityRestFilter.java @@ -12,6 +12,7 @@ import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.util.Maps; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.http.HttpChannel; import org.elasticsearch.license.XPackLicenseState; @@ -30,6 +31,7 @@ import java.io.IOException; import java.util.List; +import java.util.Map; public class SecurityRestFilter implements RestHandler { @@ -98,6 +100,14 @@ private void handleException(String actionType, RestRequest request, RestChannel @Override protected boolean skipStackTrace() { return restStatus == RestStatus.UNAUTHORIZED; } + @Override + public Map> filterHeaders(Map> headers) { + if (headers.containsKey("Warning")) { + return Maps.copyMapWithRemovedEntry(headers, "Warning"); + } + return headers; + } + }); } catch (Exception inner) { inner.addSuppressed(e); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index adfff5b6950bb..3e6478ed64417 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -7,6 +7,7 @@ import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.Version; +import org.elasticsearch.action.ActionListener; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; @@ -29,9 +30,11 @@ import org.elasticsearch.license.TestUtils; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.plugins.MapperPlugin; +import org.elasticsearch.rest.RestRequest; import org.elasticsearch.script.ScriptService; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.VersionUtils; +import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackField; @@ -50,6 +53,7 @@ import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; +import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.Realms; import org.hamcrest.Matchers; import org.junit.After; @@ -62,6 +66,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Predicate; @@ -421,7 +426,8 @@ public void testGetFieldFilterSecurityEnabledLicenseNoFLS() throws Exception { Function> fieldFilter = security.getFieldFilter(); assertNotSame(MapperPlugin.NOOP_FIELD_FILTER, fieldFilter); licenseState.update( - randomFrom(License.OperationMode.BASIC, License.OperationMode.STANDARD, License.OperationMode.GOLD), true, null); + randomFrom(License.OperationMode.BASIC, License.OperationMode.STANDARD, License.OperationMode.GOLD), + true, Long.MAX_VALUE, null); assertNotSame(MapperPlugin.NOOP_FIELD_FILTER, fieldFilter); assertSame(MapperPlugin.NOOP_FIELD_PREDICATE, fieldFilter.apply(randomAlphaOfLengthBetween(3, 6))); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/SecondaryAuthenticatorTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/SecondaryAuthenticatorTests.java index b662326056779..5ee13eec8b996 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/SecondaryAuthenticatorTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/SecondaryAuthenticatorTests.java @@ -109,7 +109,7 @@ public void setupMocks() throws Exception { when(client.threadPool()).thenReturn(threadPool); final TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState(); - licenseState.update(License.OperationMode.PLATINUM, true, null); + licenseState.update(License.OperationMode.PLATINUM, true, Long.MAX_VALUE, null); final Clock clock = Clock.systemUTC(); 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 03a243e867dc6..05dfaa4587304 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 @@ -752,7 +752,8 @@ public void testCustomRolesProvidersLicensing() { UpdatableLicenseState xPackLicenseState = new UpdatableLicenseState(SECURITY_ENABLED_SETTINGS); // these licenses don't allow custom role providers - xPackLicenseState.update(randomFrom(OperationMode.BASIC, OperationMode.GOLD, OperationMode.STANDARD), true, null); + xPackLicenseState.update(randomFrom(OperationMode.BASIC, OperationMode.GOLD, OperationMode.STANDARD), true, + Long.MAX_VALUE, null); final AtomicReference> effectiveRoleDescriptors = new AtomicReference>(); final DocumentSubsetBitsetCache documentSubsetBitsetCache = buildBitsetCache(); CompositeRolesStore compositeRolesStore = new CompositeRolesStore( @@ -775,7 +776,8 @@ Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(Nativ Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); // these licenses allow custom role providers - xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.ENTERPRISE, OperationMode.TRIAL), true, null); + xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.ENTERPRISE, OperationMode.TRIAL), true, + Long.MAX_VALUE, null); roleNames = Sets.newHashSet("roleA"); future = new PlainActionFuture<>(); compositeRolesStore.roles(roleNames, future); @@ -791,7 +793,8 @@ Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(Nativ Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache, mock(ApiKeyService.class), documentSubsetBitsetCache, rds -> effectiveRoleDescriptors.set(rds)); - xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.ENTERPRISE, OperationMode.TRIAL), false, null); + xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.ENTERPRISE, OperationMode.TRIAL), false, + Long.MAX_VALUE, null); roleNames = Sets.newHashSet("roleA"); future = new PlainActionFuture<>(); compositeRolesStore.roles(roleNames, future); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java index b7ab884ce42b8..f79763f3e5cca 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/saml/SamlBaseRestHandlerTests.java @@ -45,7 +45,7 @@ private SamlBaseRestHandler buildHandler(License.OperationMode licenseMode) { .put(XPackSettings.SECURITY_ENABLED.getKey(), true) .build(); final TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState(settings); - licenseState.update(licenseMode, true, null); + licenseState.update(licenseMode, true, Long.MAX_VALUE, null); return new SamlBaseRestHandler(settings, licenseState) { diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/LocalStateSpatialPlugin.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/LocalStateSpatialPlugin.java index 42c0769c3bd6b..02dd11e8b9be9 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/LocalStateSpatialPlugin.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/LocalStateSpatialPlugin.java @@ -22,7 +22,7 @@ public class LocalStateSpatialPlugin extends SpatialPlugin { protected XPackLicenseState getLicenseState() { TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState(); License.OperationMode operationMode = License.OperationMode.TRIAL; - licenseState.update(operationMode, true, VersionUtils.randomVersion(LuceneTestCase.random())); + licenseState.update(operationMode, true, Long.MAX_VALUE, VersionUtils.randomVersion(LuceneTestCase.random())); return licenseState; } } diff --git a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialPluginTests.java b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialPluginTests.java index a9337a2c65265..9646630206cbd 100644 --- a/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialPluginTests.java +++ b/x-pack/plugin/spatial/src/test/java/org/elasticsearch/xpack/spatial/SpatialPluginTests.java @@ -82,7 +82,7 @@ private SpatialPlugin getPluginWithOperationMode(License.OperationMode operation return new SpatialPlugin() { protected XPackLicenseState getLicenseState() { TestUtils.UpdatableLicenseState licenseState = new TestUtils.UpdatableLicenseState(); - licenseState.update(operationMode, true, VersionUtils.randomVersion(random())); + licenseState.update(operationMode, true, Long.MAX_VALUE, VersionUtils.randomVersion(random())); return licenseState; } };