Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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,45 @@ 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) {
if (XPackPlugin.isReadyForXPackCustomMetadata(currentClusterState) == false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to completely remove this whole if block if you choose to ... i started to remove these across the board... but kinda forgot about it: #95428

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thank you for the indication!
I also wanted to ask about it, but similarly omitted....
I've pushed 0c360c2 .

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);
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 +490,12 @@ private boolean checkForExpiredLicense(License license) {
*/
private void onUpdate(final LicensesMetadata currentLicensesMetadata) {
final License license = getLicenseFromLicensesMetadata(currentLicensesMetadata);
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);
scheduler.add(new SchedulerEngine.Job(LICENSE_JOB, nextLicenseCheck(license)));
for (ExpirationCallback expirationCallback : expirationCallbacks) {
scheduler.add(
Expand All @@ -517,24 +511,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 +545,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 +554,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