Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions docs/changelog/102919.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 102919
summary: Error log when license verification fails locally
area: License
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -413,14 +413,6 @@ public void clusterChanged(ClusterChangedEvent event) {
final ClusterState previousClusterState = event.previousState();
final ClusterState currentClusterState = event.state();
if (currentClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK) == false) {
if (XPackPlugin.isReadyForXPackCustomMetadata(currentClusterState) == false) {
logger.debug(
"cannot add license to cluster as the following nodes might not understand the license metadata: {}",
() -> XPackPlugin.nodesNotReadyForXPackCustomMetadata(currentClusterState)
);
return;
}

final LicensesMetadata prevLicensesMetadata = previousClusterState.getMetadata().custom(LicensesMetadata.TYPE);
final LicensesMetadata currentLicensesMetadata = currentClusterState.getMetadata().custom(LicensesMetadata.TYPE);
// notify all interested plugins
Expand All @@ -439,26 +431,7 @@ public void clusterChanged(ClusterChangedEvent event) {
} else {
logger.trace("license unchanged [{}]", currentLicensesMetadata);
}

License currentLicense = null;
boolean noLicenseInPrevMetadata = prevLicensesMetadata == null || prevLicensesMetadata.getLicense() == null;
if (noLicenseInPrevMetadata == false) {
currentLicense = prevLicensesMetadata.getLicense();
}
boolean noLicenseInCurrentMetadata = (currentLicensesMetadata == null || currentLicensesMetadata.getLicense() == null);
if (noLicenseInCurrentMetadata == false) {
currentLicense = currentLicensesMetadata.getLicense();
}

boolean noLicense = noLicenseInPrevMetadata && noLicenseInCurrentMetadata;
// auto-generate license if no licenses ever existed or if the current license is basic and
// needs extended or if the license signature needs to be updated. this will trigger a subsequent cluster changed event
if (currentClusterState.getNodes().isLocalNodeElectedMaster()
&& (noLicense
|| LicenseUtils.licenseNeedsExtended(currentLicense)
|| LicenseUtils.signatureNeedsUpdate(currentLicense, currentClusterState.nodes()))) {
registerOrUpdateSelfGeneratedLicense();
}
maybeRegisterOrUpdateLicense(previousClusterState, currentClusterState);
} else if (logger.isDebugEnabled()) {
logger.debug("skipped license notifications reason: [{}]", GatewayService.STATE_NOT_RECOVERED_BLOCK);
}
Expand All @@ -468,24 +441,38 @@ private void updateXPackLicenseState(License license) {
if (license == LicensesMetadata.LICENSE_TOMBSTONE) {
// implies license has been explicitly deleted
xPacklicenseState.update(LicenseUtils.getXPackLicenseStatus(license, clock));
return;
}
checkForExpiredLicense(license);
}

private boolean checkForExpiredLicense(License license) {
if (license != null) {
} else if (license != null) {
XPackLicenseStatus xPackLicenseStatus = LicenseUtils.getXPackLicenseStatus(license, clock);
xPacklicenseState.update(xPackLicenseStatus);
if (xPackLicenseStatus.active()) {
logger.debug("license [{}] - valid", license.uid());
return false;
} else {
logger.warn("license [{}] - expired", license.uid());
return true;
}
}
return false;
}

private void maybeRegisterOrUpdateLicense(ClusterState previousClusterState, ClusterState currentClusterState) {
final LicensesMetadata prevLicensesMetadata = previousClusterState.getMetadata().custom(LicensesMetadata.TYPE);
final LicensesMetadata currentLicensesMetadata = currentClusterState.getMetadata().custom(LicensesMetadata.TYPE);
License currentLicense = null;
boolean noLicenseInPrevMetadata = prevLicensesMetadata == null || prevLicensesMetadata.getLicense() == null;
if (noLicenseInPrevMetadata == false) {
currentLicense = prevLicensesMetadata.getLicense();
}
boolean noLicenseInCurrentMetadata = (currentLicensesMetadata == null || currentLicensesMetadata.getLicense() == null);
if (noLicenseInCurrentMetadata == false) {
currentLicense = currentLicensesMetadata.getLicense();
}
boolean noLicense = noLicenseInPrevMetadata && noLicenseInCurrentMetadata;
// auto-generate license if no licenses ever existed or if the current license is basic and
// needs extended or if the license signature needs to be updated. this will trigger a subsequent cluster changed event
if (currentClusterState.getNodes().isLocalNodeElectedMaster()
&& (noLicense
|| LicenseUtils.licenseNeedsExtended(currentLicense)
|| LicenseUtils.signatureNeedsUpdate(currentLicense, currentClusterState.nodes()))) {
registerOrUpdateSelfGeneratedLicense();
}
}

/**
Expand All @@ -496,12 +483,14 @@ private boolean checkForExpiredLicense(License license) {
*/
private void onUpdate(final LicensesMetadata currentLicensesMetadata) {
final License license = getLicenseFromLicensesMetadata(currentLicensesMetadata);
// first update the XPackLicenseState
updateXPackLicenseState(license);
// license can be null if the trial license is yet to be auto-generated
// in this case, it is a no-op
if (license != null) {
final License previousLicense = currentLicenseHolder.get();
final License previousLicense = currentLicenseHolder.getAndSet(license);
if (license.equals(previousLicense) == false) {
currentLicenseHolder.set(license);
// then register periodic job to update the XPackLicenseState with the latest expiration message
scheduler.add(new SchedulerEngine.Job(LICENSE_JOB, nextLicenseCheck(license)));
for (ExpirationCallback expirationCallback : expirationCallbacks) {
scheduler.add(
Expand All @@ -517,24 +506,25 @@ private void onUpdate(final LicensesMetadata currentLicensesMetadata) {
}
logger.info("license [{}] mode [{}] - valid", license.uid(), license.operationMode().name().toLowerCase(Locale.ROOT));
}
updateXPackLicenseState(license);
}
}

// pkg private for tests
SchedulerEngine.Schedule nextLicenseCheck(License license) {
final long licenseIssueDate = license.issueDate();
final long licenseExpiryDate = LicenseUtils.getExpiryDate(license);
return (startTime, time) -> {
if (time < license.issueDate()) {
if (time < licenseIssueDate) {
// when we encounter a license with a future issue date
// which can happen with autogenerated license,
// we want to schedule a notification on the license issue date
// so the license is notified once it is valid
// see https://github.com/elastic/x-plugins/issues/983
return license.issueDate();
} else if (time < LicenseUtils.getExpiryDate(license)) {
return licenseIssueDate;
} else if (time < licenseExpiryDate) {
// Re-check the license every day during the warning period up to the license expiration.
// This will cause the warning message to be updated that is emitted on soon-expiring license use.
long nextTime = LicenseUtils.getExpiryDate(license) - LicenseSettings.LICENSE_EXPIRATION_WARNING_PERIOD.getMillis();
long nextTime = licenseExpiryDate - LicenseSettings.LICENSE_EXPIRATION_WARNING_PERIOD.getMillis();
while (nextTime <= time) {
nextTime += TimeValue.timeValueDays(1).getMillis();
}
Expand All @@ -550,6 +540,7 @@ public License getLicense(final Metadata metadata) {
}

// visible for tests
@Nullable
License getLicenseFromLicensesMetadata(@Nullable final LicensesMetadata metadata) {
if (metadata != null) {
License license = metadata.getLicense();
Expand All @@ -558,6 +549,13 @@ License getLicenseFromLicensesMetadata(@Nullable final LicensesMetadata metadata
} else if (license != null) {
if (license.verified()) {
return license;
} else {
// this is an "error" level because an unverified license should not be present in the cluster state in the first place
logger.error(
"{} with uid [{}] failed verification on the local node.",
License.isAutoGeneratedLicense(license.signature()) ? "Autogenerated license" : "License",
license.uid()
);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
*/
package org.elasticsearch.license;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefIterator;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.hash.MessageDigests;
import org.elasticsearch.core.Streams;
import org.elasticsearch.xcontent.ToXContent;
import org.elasticsearch.xcontent.XContentBuilder;
Expand All @@ -28,11 +31,15 @@
import java.util.Base64;
import java.util.Collections;

import static org.elasticsearch.core.Strings.format;

/**
* Responsible for verifying signed licenses
*/
public class LicenseVerifier {

private static final Logger logger = LogManager.getLogger(LicenseVerifier.class);

/**
* verifies the license content with the signature using the packaged
* public key
Expand Down Expand Up @@ -65,7 +72,17 @@ public static boolean verifyLicense(final License license, PublicKey publicKey)
while ((ref = iterator.next()) != null) {
rsa.update(ref.bytes, ref.offset, ref.length);
}
return rsa.verify(signedContent);
boolean verifyResult = rsa.verify(signedContent);
if (verifyResult == false) {
logger.warn(
() -> format(
"License with uid [%s] failed signature verification with the public key with sha256 [%s].",
license.uid(),
PUBLIC_KEY_DIGEST_HEX_STRING
)
);
}
return verifyResult;
} catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
throw new IllegalStateException(e);
} finally {
Expand All @@ -76,12 +93,15 @@ public static boolean verifyLicense(final License license, PublicKey publicKey)
}

private static final PublicKey PUBLIC_KEY;
private static final String PUBLIC_KEY_DIGEST_HEX_STRING;

static {
try (InputStream is = LicenseVerifier.class.getResourceAsStream("/public.key")) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Streams.copy(is, out);
PUBLIC_KEY = CryptUtils.readPublicKey(out.toByteArray());
byte[] publicKeyBytes = out.toByteArray();
PUBLIC_KEY = CryptUtils.readPublicKey(publicKeyBytes);
PUBLIC_KEY_DIGEST_HEX_STRING = MessageDigests.toHexString(MessageDigests.sha256().digest(publicKeyBytes));
} catch (IOException e) {
throw new AssertionError("key file is part of the source and must deserialize correctly", e);
}
Expand Down