Skip to content

Commit 50d6552

Browse files
Error log when license verification fails locally (#102919)
This change implements logging if the license verification fails on local nodes (after the the license has already been (erroneously) published in the cluster state).
1 parent 89faf44 commit 50d6552

File tree

3 files changed

+70
-47
lines changed

3 files changed

+70
-47
lines changed

docs/changelog/102919.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 102919
2+
summary: Error log when license verification fails locally
3+
area: License
4+
type: bug
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/license/ClusterStateLicenseService.java

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -413,14 +413,6 @@ public void clusterChanged(ClusterChangedEvent event) {
413413
final ClusterState previousClusterState = event.previousState();
414414
final ClusterState currentClusterState = event.state();
415415
if (currentClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK) == false) {
416-
if (XPackPlugin.isReadyForXPackCustomMetadata(currentClusterState) == false) {
417-
logger.debug(
418-
"cannot add license to cluster as the following nodes might not understand the license metadata: {}",
419-
() -> XPackPlugin.nodesNotReadyForXPackCustomMetadata(currentClusterState)
420-
);
421-
return;
422-
}
423-
424416
final LicensesMetadata prevLicensesMetadata = previousClusterState.getMetadata().custom(LicensesMetadata.TYPE);
425417
final LicensesMetadata currentLicensesMetadata = currentClusterState.getMetadata().custom(LicensesMetadata.TYPE);
426418
// notify all interested plugins
@@ -439,26 +431,7 @@ public void clusterChanged(ClusterChangedEvent event) {
439431
} else {
440432
logger.trace("license unchanged [{}]", currentLicensesMetadata);
441433
}
442-
443-
License currentLicense = null;
444-
boolean noLicenseInPrevMetadata = prevLicensesMetadata == null || prevLicensesMetadata.getLicense() == null;
445-
if (noLicenseInPrevMetadata == false) {
446-
currentLicense = prevLicensesMetadata.getLicense();
447-
}
448-
boolean noLicenseInCurrentMetadata = (currentLicensesMetadata == null || currentLicensesMetadata.getLicense() == null);
449-
if (noLicenseInCurrentMetadata == false) {
450-
currentLicense = currentLicensesMetadata.getLicense();
451-
}
452-
453-
boolean noLicense = noLicenseInPrevMetadata && noLicenseInCurrentMetadata;
454-
// auto-generate license if no licenses ever existed or if the current license is basic and
455-
// needs extended or if the license signature needs to be updated. this will trigger a subsequent cluster changed event
456-
if (currentClusterState.getNodes().isLocalNodeElectedMaster()
457-
&& (noLicense
458-
|| LicenseUtils.licenseNeedsExtended(currentLicense)
459-
|| LicenseUtils.signatureNeedsUpdate(currentLicense, currentClusterState.nodes()))) {
460-
registerOrUpdateSelfGeneratedLicense();
461-
}
434+
maybeRegisterOrUpdateLicense(previousClusterState, currentClusterState);
462435
} else if (logger.isDebugEnabled()) {
463436
logger.debug("skipped license notifications reason: [{}]", GatewayService.STATE_NOT_RECOVERED_BLOCK);
464437
}
@@ -468,24 +441,38 @@ private void updateXPackLicenseState(License license) {
468441
if (license == LicensesMetadata.LICENSE_TOMBSTONE) {
469442
// implies license has been explicitly deleted
470443
xPacklicenseState.update(LicenseUtils.getXPackLicenseStatus(license, clock));
471-
return;
472-
}
473-
checkForExpiredLicense(license);
474-
}
475-
476-
private boolean checkForExpiredLicense(License license) {
477-
if (license != null) {
444+
} else if (license != null) {
478445
XPackLicenseStatus xPackLicenseStatus = LicenseUtils.getXPackLicenseStatus(license, clock);
479446
xPacklicenseState.update(xPackLicenseStatus);
480447
if (xPackLicenseStatus.active()) {
481448
logger.debug("license [{}] - valid", license.uid());
482-
return false;
483449
} else {
484450
logger.warn("license [{}] - expired", license.uid());
485-
return true;
486451
}
487452
}
488-
return false;
453+
}
454+
455+
private void maybeRegisterOrUpdateLicense(ClusterState previousClusterState, ClusterState currentClusterState) {
456+
final LicensesMetadata prevLicensesMetadata = previousClusterState.getMetadata().custom(LicensesMetadata.TYPE);
457+
final LicensesMetadata currentLicensesMetadata = currentClusterState.getMetadata().custom(LicensesMetadata.TYPE);
458+
License currentLicense = null;
459+
boolean noLicenseInPrevMetadata = prevLicensesMetadata == null || prevLicensesMetadata.getLicense() == null;
460+
if (noLicenseInPrevMetadata == false) {
461+
currentLicense = prevLicensesMetadata.getLicense();
462+
}
463+
boolean noLicenseInCurrentMetadata = (currentLicensesMetadata == null || currentLicensesMetadata.getLicense() == null);
464+
if (noLicenseInCurrentMetadata == false) {
465+
currentLicense = currentLicensesMetadata.getLicense();
466+
}
467+
boolean noLicense = noLicenseInPrevMetadata && noLicenseInCurrentMetadata;
468+
// auto-generate license if no licenses ever existed or if the current license is basic and
469+
// needs extended or if the license signature needs to be updated. this will trigger a subsequent cluster changed event
470+
if (currentClusterState.getNodes().isLocalNodeElectedMaster()
471+
&& (noLicense
472+
|| LicenseUtils.licenseNeedsExtended(currentLicense)
473+
|| LicenseUtils.signatureNeedsUpdate(currentLicense, currentClusterState.nodes()))) {
474+
registerOrUpdateSelfGeneratedLicense();
475+
}
489476
}
490477

491478
/**
@@ -496,12 +483,14 @@ private boolean checkForExpiredLicense(License license) {
496483
*/
497484
private void onUpdate(final LicensesMetadata currentLicensesMetadata) {
498485
final License license = getLicenseFromLicensesMetadata(currentLicensesMetadata);
486+
// first update the XPackLicenseState
487+
updateXPackLicenseState(license);
499488
// license can be null if the trial license is yet to be auto-generated
500489
// in this case, it is a no-op
501490
if (license != null) {
502-
final License previousLicense = currentLicenseHolder.get();
491+
final License previousLicense = currentLicenseHolder.getAndSet(license);
503492
if (license.equals(previousLicense) == false) {
504-
currentLicenseHolder.set(license);
493+
// then register periodic job to update the XPackLicenseState with the latest expiration message
505494
scheduler.add(new SchedulerEngine.Job(LICENSE_JOB, nextLicenseCheck(license)));
506495
for (ExpirationCallback expirationCallback : expirationCallbacks) {
507496
scheduler.add(
@@ -517,24 +506,25 @@ private void onUpdate(final LicensesMetadata currentLicensesMetadata) {
517506
}
518507
logger.info("license [{}] mode [{}] - valid", license.uid(), license.operationMode().name().toLowerCase(Locale.ROOT));
519508
}
520-
updateXPackLicenseState(license);
521509
}
522510
}
523511

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

552542
// visible for tests
543+
@Nullable
553544
License getLicenseFromLicensesMetadata(@Nullable final LicensesMetadata metadata) {
554545
if (metadata != null) {
555546
License license = metadata.getLicense();
@@ -558,6 +549,13 @@ License getLicenseFromLicensesMetadata(@Nullable final LicensesMetadata metadata
558549
} else if (license != null) {
559550
if (license.verified()) {
560551
return license;
552+
} else {
553+
// this is an "error" level because an unverified license should not be present in the cluster state in the first place
554+
logger.error(
555+
"{} with uid [{}] failed verification on the local node.",
556+
License.isAutoGeneratedLicense(license.signature()) ? "Autogenerated license" : "License",
557+
license.uid()
558+
);
561559
}
562560
}
563561
}

x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseVerifier.java

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
*/
77
package org.elasticsearch.license;
88

9+
import org.apache.logging.log4j.LogManager;
10+
import org.apache.logging.log4j.Logger;
911
import org.apache.lucene.util.BytesRef;
1012
import org.apache.lucene.util.BytesRefIterator;
1113
import org.elasticsearch.common.bytes.BytesReference;
14+
import org.elasticsearch.common.hash.MessageDigests;
1215
import org.elasticsearch.core.Streams;
1316
import org.elasticsearch.xcontent.ToXContent;
1417
import org.elasticsearch.xcontent.XContentBuilder;
@@ -28,11 +31,15 @@
2831
import java.util.Base64;
2932
import java.util.Collections;
3033

34+
import static org.elasticsearch.core.Strings.format;
35+
3136
/**
3237
* Responsible for verifying signed licenses
3338
*/
3439
public class LicenseVerifier {
3540

41+
private static final Logger logger = LogManager.getLogger(LicenseVerifier.class);
42+
3643
/**
3744
* verifies the license content with the signature using the packaged
3845
* public key
@@ -65,7 +72,17 @@ public static boolean verifyLicense(final License license, PublicKey publicKey)
6572
while ((ref = iterator.next()) != null) {
6673
rsa.update(ref.bytes, ref.offset, ref.length);
6774
}
68-
return rsa.verify(signedContent);
75+
boolean verifyResult = rsa.verify(signedContent);
76+
if (verifyResult == false) {
77+
logger.warn(
78+
() -> format(
79+
"License with uid [%s] failed signature verification with the public key with sha256 [%s].",
80+
license.uid(),
81+
PUBLIC_KEY_DIGEST_HEX_STRING
82+
)
83+
);
84+
}
85+
return verifyResult;
6986
} catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
7087
throw new IllegalStateException(e);
7188
} finally {
@@ -76,12 +93,15 @@ public static boolean verifyLicense(final License license, PublicKey publicKey)
7693
}
7794

7895
private static final PublicKey PUBLIC_KEY;
96+
private static final String PUBLIC_KEY_DIGEST_HEX_STRING;
7997

8098
static {
8199
try (InputStream is = LicenseVerifier.class.getResourceAsStream("/public.key")) {
82100
ByteArrayOutputStream out = new ByteArrayOutputStream();
83101
Streams.copy(is, out);
84-
PUBLIC_KEY = CryptUtils.readPublicKey(out.toByteArray());
102+
byte[] publicKeyBytes = out.toByteArray();
103+
PUBLIC_KEY = CryptUtils.readPublicKey(publicKeyBytes);
104+
PUBLIC_KEY_DIGEST_HEX_STRING = MessageDigests.toHexString(MessageDigests.sha256().digest(publicKeyBytes));
85105
} catch (IOException e) {
86106
throw new AssertionError("key file is part of the source and must deserialize correctly", e);
87107
}

0 commit comments

Comments
 (0)