Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a warning header when a license is about to expire #64948

Merged
merged 21 commits into from
Dec 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f85a624
This change adds a warning header when a license is about to expire
BigPandaToo Nov 11, 2020
af38237
Merge branch 'master' into Warning_header
elasticmachine Nov 11, 2020
70461a2
Merge branch 'master' into Warning_header
elasticmachine Nov 11, 2020
f6e3ceb
Merge branch 'master' into Warning_header
elasticmachine Nov 11, 2020
e52f231
Merge branch 'master' into Warning_header
elasticmachine Nov 12, 2020
1002999
This change adds realm name of the realm used to perform authenticati…
BigPandaToo Nov 12, 2020
7b1b8da
Adding doc for the new API introduced by #64517 - /_security/saml/met…
BigPandaToo Nov 17, 2020
ffcd72b
Merge branch 'master' into Warning_header
elasticmachine Nov 17, 2020
34efa0e
Adding a warning header when a license is about to expire
BigPandaToo Nov 19, 2020
fbbd2fa
Merge branch 'master' into Warning_header
elasticmachine Nov 23, 2020
a57d4b1
Merge branch 'master' into Warning_header
elasticmachine Nov 23, 2020
1ecfa75
Addressing the PR feedback
BigPandaToo Nov 23, 2020
1ed1cb2
Merge branch 'master' into Warning_header
elasticmachine Nov 26, 2020
80ca874
Merge branch 'master' into Warning_header
elasticmachine Nov 26, 2020
07fa3e2
Switching back to adding the header during featureCheck to allow
BigPandaToo Nov 26, 2020
39e7ca7
Merge branch 'master' into Warning_header
elasticmachine Nov 30, 2020
d27df6f
Changing the wording for "expired" message to be consistent with the log
BigPandaToo Dec 1, 2020
a3f2eba
Merge branch 'master' into Warning_header
elasticmachine Dec 3, 2020
b8773e3
Small changes in the way we verify header in tests
BigPandaToo Dec 3, 2020
8dc8b9c
Nit changes
BigPandaToo Dec 4, 2020
a939ae8
Merge branch 'master' into Warning_header
elasticmachine Dec 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions server/src/main/java/org/elasticsearch/rest/RestResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,8 @@ public Map<String, List<String>> getHeaders() {
return customHeaders;
}
}

public Map<String, List<String>> filterHeaders(Map<String, List<String>> headers) {
return headers;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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;

Expand Down Expand Up @@ -124,7 +129,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:";
Expand Down Expand Up @@ -475,7 +480,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) {
Expand All @@ -488,7 +493,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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,17 +21,21 @@
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;
import java.util.function.LongSupplier;
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.
*/
Expand Down Expand Up @@ -395,7 +401,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). */
Expand All @@ -404,9 +410,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;
}
}

Expand All @@ -420,7 +430,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<>();
Expand Down Expand Up @@ -468,12 +478,13 @@ private boolean checkAgainstStatus(Predicate<Status> 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);
}

Expand Down Expand Up @@ -509,12 +520,26 @@ public 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")
BigPandaToo marked this conversation as resolved.
Show resolved Hide resolved
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);
BigPandaToo marked this conversation as resolved.
Show resolved Hide resolved
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;
}

Expand Down Expand Up @@ -631,6 +656,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,21 +360,23 @@ public static class AssertingLicenseState extends XPackLicenseState {
public final List<License.OperationMode> modeUpdates = new ArrayList<>();
public final List<Boolean> activeUpdates = new ArrayList<>();
public final List<Version> trialVersionUpdates = new ArrayList<>();
public final List<Long> 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 {
Expand All @@ -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);
}
}

Expand Down
Loading