diff --git a/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java b/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java index 14720e2d4b6ef..2125e7d52227e 100644 --- a/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java +++ b/x-pack/license-tools/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java @@ -88,6 +88,9 @@ protected void execute(Terminal terminal, OptionSet options) throws Exception { ExitCodes.USAGE, "Must specify either --license or --licenseFile"); } + if (licenseSpec == null) { + throw new UserException(ExitCodes.DATA_ERROR, "Could not parse license spec"); + } // sign License license = new LicenseSigner(privateKeyPath, publicKeyPath).sign(licenseSpec); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java index e39b5b7dcc196..6731518f5b534 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/License.java @@ -5,15 +5,6 @@ */ package org.elasticsearch.license; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; - import org.apache.lucene.util.CollectionUtil; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchParseException; @@ -31,11 +22,83 @@ import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.protocol.xpack.license.LicenseStatus; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; + /** * Data structure for license. Use {@link Builder} to build a license. * Provides serialization/deserialization & validation methods for license object */ public class License implements ToXContentObject { + + public enum LicenseType { + BASIC, + STANDARD, + GOLD, + PLATINUM, + ENTERPRISE, + TRIAL; + + public String getTypeName() { + return name().toLowerCase(Locale.ROOT); + } + + public static LicenseType parse(String type) throws IllegalArgumentException { + try { + return LicenseType.valueOf(type.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("unrecognised license type [ " + type + "], supported license types are [" + + Stream.of(values()).map(LicenseType::getTypeName).collect(Collectors.joining(",")) + "]"); + } + } + + /** + * Backward compatible license type parsing for older license models + */ + public static LicenseType resolve(String name) { + switch (name.toLowerCase(Locale.ROOT)) { + case "missing": + return null; + case "trial": + case "none": // bwc for 1.x subscription_type field + case "dev": // bwc for 1.x subscription_type field + case "development": // bwc for 1.x subscription_type field + return TRIAL; + case "basic": + return BASIC; + case "standard": + return STANDARD; + case "silver": + case "gold": + return GOLD; + case "platinum": + case "cloud_internal": + case "internal": // bwc for 1.x subscription_type field + return PLATINUM; + case "enterprise": + return ENTERPRISE; + default: + throw new IllegalArgumentException("unknown license type [" + name + "]"); + } + } + + static boolean isBasic(String typeName) { + return BASIC.getTypeName().equals(typeName); + } + + static boolean isTrial(String typeName) { + return TRIAL.getTypeName().equals(typeName); + } + } + public static final int VERSION_START = 1; public static final int VERSION_NO_FEATURE_TYPE = 2; public static final int VERSION_START_DATE = 3; @@ -102,28 +165,25 @@ public static int compare(OperationMode opMode1, OperationMode opMode2) { return Integer.compare(opMode1.id, opMode2.id); } - public static OperationMode resolve(String type) { - switch (type.toLowerCase(Locale.ROOT)) { - case "missing": - return MISSING; - case "trial": - case "none": // bwc for 1.x subscription_type field - case "dev": // bwc for 1.x subscription_type field - case "development": // bwc for 1.x subscription_type field - return TRIAL; - case "basic": + public static OperationMode resolve(String typeName) { + LicenseType type = LicenseType.resolve(typeName); + if (type == null) { + return MISSING; + } + switch (type) { + case BASIC: return BASIC; - case "standard": + case STANDARD: return STANDARD; - case "silver": - case "gold": + case GOLD: return GOLD; - case "platinum": - case "cloud_internal": - case "internal": // bwc for 1.x subscription_type field + case PLATINUM: + case ENTERPRISE: // TODO Add an explicit enterprise operating mode return PLATINUM; + case TRIAL: + return TRIAL; default: - throw new IllegalArgumentException("unknown type [" + type + "]"); + throw new IllegalArgumentException("unsupported license type [" + type.getTypeName() + "]"); } } @@ -301,7 +361,7 @@ private void validate() { throw new IllegalStateException("maxNodes has to be set"); } else if (expiryDate == -1) { throw new IllegalStateException("expiryDate has to be set"); - } else if (expiryDate == LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS && "basic".equals(type) == false) { + } else if (expiryDate == LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS && LicenseType.isBasic(type) == false) { throw new IllegalStateException("only basic licenses are allowed to have no expiration"); } } @@ -689,6 +749,10 @@ public Builder issueDate(long issueDate) { return this; } + public Builder type(LicenseType type) { + return type(type.getTypeName()); + } + public Builder type(String type) { this.type = type; return this; @@ -778,6 +842,7 @@ public Builder validate() { } return this; } + } } 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 f750d1349a0ad..f16cb2fbe3932 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 @@ -39,15 +39,14 @@ import java.time.Clock; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; /** * Service responsible for managing {@link LicensesMetaData}. @@ -59,19 +58,17 @@ public class LicenseService extends AbstractLifecycleComponent implements ClusterStateListener, SchedulerEngine.Listener { private static final Logger logger = LogManager.getLogger(LicenseService.class); - public static final Setting SELF_GENERATED_LICENSE_TYPE = new Setting<>("xpack.license.self_generated.type", - (s) -> "basic", (s) -> { - if (SelfGeneratedLicense.validSelfGeneratedType(s)) { - return s; - } else { - throw new IllegalArgumentException("Illegal self generated license type [" + s + "]. Must be trial or basic."); - } + public static final Setting SELF_GENERATED_LICENSE_TYPE = new Setting<>("xpack.license.self_generated.type", + (s) -> License.LicenseType.BASIC.getTypeName(), (s) -> { + final License.LicenseType type = License.LicenseType.parse(s); + return SelfGeneratedLicense.validateSelfGeneratedType(type); }, Setting.Property.NodeScope); // pkg private for tests static final TimeValue NON_BASIC_SELF_GENERATED_LICENSE_DURATION = TimeValue.timeValueHours(30 * 24); - static final Set VALID_TRIAL_TYPES = new HashSet<>(Arrays.asList("trial", "platinum", "gold")); + static final Set VALID_TRIAL_TYPES = Set.of( + License.LicenseType.GOLD, License.LicenseType.PLATINUM, License.LicenseType.ENTERPRISE, License.LicenseType.TRIAL); /** * Duration of grace period after a license has expired @@ -79,7 +76,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste static final TimeValue GRACE_PERIOD_DURATION = days(7); public static final long BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS = - XPackInfoResponse.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; + XPackInfoResponse.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; private final Settings settings; @@ -117,7 +114,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste private 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:"; + "please read the following messages and update the license again, this time with the \"acknowledge=true\" parameter:"; public LicenseService(Settings settings, ClusterService clusterService, Clock clock, Environment env, ResourceWatcherService resourceWatcherService, XPackLicenseState licenseState) { @@ -199,7 +196,7 @@ public void registerLicense(final PutLicenseRequest request, final ActionListene final long now = clock.millis(); if (!LicenseVerifier.verifyLicense(newLicense) || newLicense.issueDate() > now || newLicense.startDate() > now) { listener.onResponse(new PutLicenseResponse(true, LicensesStatus.INVALID)); - } else if (newLicense.type().equals("basic")) { + } else if (newLicense.type().equals(License.LicenseType.BASIC.getTypeName())) { listener.onFailure(new IllegalArgumentException("Registering basic licenses is not allowed.")); } else if (newLicense.expiryDate() < now) { listener.onResponse(new PutLicenseResponse(true, LicensesStatus.EXPIRED)); @@ -212,7 +209,7 @@ public void registerLicense(final PutLicenseRequest request, final ActionListene if (acknowledgeMessages.isEmpty() == false) { // needs acknowledgement listener.onResponse(new PutLicenseResponse(false, LicensesStatus.VALID, ACKNOWLEDGEMENT_HEADER, - acknowledgeMessages)); + acknowledgeMessages)); return; } } @@ -239,36 +236,49 @@ && isProductionMode(settings, clusterService.localNode())) { } clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new - AckedClusterStateUpdateTask(request, listener) { - @Override - protected PutLicenseResponse newResponse(boolean acknowledged) { - return new PutLicenseResponse(acknowledged, LicensesStatus.VALID); - } + AckedClusterStateUpdateTask(request, listener) { + @Override + protected PutLicenseResponse newResponse(boolean acknowledged) { + return new PutLicenseResponse(acknowledged, LicensesStatus.VALID); + } - @Override - public ClusterState execute(ClusterState currentState) throws Exception { - XPackPlugin.checkReadyForXPackCustomMetadata(currentState); - MetaData currentMetadata = currentState.metaData(); - LicensesMetaData licensesMetaData = currentMetadata.custom(LicensesMetaData.TYPE); - Version trialVersion = null; - if (licensesMetaData != null) { - trialVersion = licensesMetaData.getMostRecentTrialVersion(); - } - MetaData.Builder mdBuilder = MetaData.builder(currentMetadata); - mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense, trialVersion)); - return ClusterState.builder(currentState).metaData(mdBuilder).build(); + @Override + public ClusterState execute(ClusterState currentState) throws Exception { + XPackPlugin.checkReadyForXPackCustomMetadata(currentState); + final Version oldestNodeVersion = currentState.nodes().getSmallestNonClientNodeVersion(); + if (licenseIsCompatible(newLicense, oldestNodeVersion) == false) { + throw new IllegalStateException("The provided license is not compatible with node version [" + + oldestNodeVersion + "]"); + } + MetaData currentMetadata = currentState.metaData(); + LicensesMetaData licensesMetaData = currentMetadata.custom(LicensesMetaData.TYPE); + Version trialVersion = null; + if (licensesMetaData != null) { + trialVersion = licensesMetaData.getMostRecentTrialVersion(); } - }); + MetaData.Builder mdBuilder = MetaData.builder(currentMetadata); + mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense, trialVersion)); + return ClusterState.builder(currentState).metaData(mdBuilder).build(); + } + }); + } + } + + private static boolean licenseIsCompatible(License license, Version version) { + if (License.LicenseType.ENTERPRISE.getTypeName().equalsIgnoreCase(license.type())) { + return version.onOrAfter(Version.V_8_0_0); + } else { + return true; } } public static Map getAckMessages(License newLicense, License currentLicense) { Map acknowledgeMessages = new HashMap<>(); if (!License.isAutoGeneratedLicense(currentLicense.signature()) // current license is not auto-generated - && currentLicense.issueDate() > newLicense.issueDate()) { // and has a later issue date - acknowledgeMessages.put("license", new String[]{ - "The new license is older than the currently installed license. " + - "Are you sure you want to override the current license?"}); + && currentLicense.issueDate() > newLicense.issueDate()) { // and has a later issue date + acknowledgeMessages.put("license", new String[] { + "The new license is older than the currently installed license. " + + "Are you sure you want to override the current license?" }); } XPackLicenseState.ACKNOWLEDGMENT_MESSAGES.forEach((feature, ackMessages) -> { String[] messages = ackMessages.apply(currentLicense.operationMode(), newLicense.operationMode()); @@ -293,8 +303,8 @@ public void triggered(SchedulerEngine.Event event) { updateLicenseState(license, licensesMetaData.getMostRecentTrialVersion()); } else if (event.getJobName().startsWith(ExpirationCallback.EXPIRATION_JOB_PREFIX)) { expirationCallbacks.stream() - .filter(expirationCallback -> expirationCallback.getId().equals(event.getJobName())) - .forEach(expirationCallback -> expirationCallback.on(license)); + .filter(expirationCallback -> expirationCallback.getId().equals(event.getJobName())) + .forEach(expirationCallback -> expirationCallback.on(license)); } } } @@ -304,27 +314,27 @@ public void triggered(SchedulerEngine.Event event) { */ public void removeLicense(final DeleteLicenseRequest request, final ActionListener listener) { clusterService.submitStateUpdateTask("delete license", - new AckedClusterStateUpdateTask(request, listener) { - @Override - protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { - return new ClusterStateUpdateResponse(acknowledged); - } + new AckedClusterStateUpdateTask(request, listener) { + @Override + protected ClusterStateUpdateResponse newResponse(boolean acknowledged) { + return new ClusterStateUpdateResponse(acknowledged); + } - @Override - public ClusterState execute(ClusterState currentState) throws Exception { - MetaData metaData = currentState.metaData(); - final LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE); - if (currentLicenses.getLicense() != LicensesMetaData.LICENSE_TOMBSTONE) { - MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); - LicensesMetaData newMetadata = new LicensesMetaData(LicensesMetaData.LICENSE_TOMBSTONE, - currentLicenses.getMostRecentTrialVersion()); - mdBuilder.putCustom(LicensesMetaData.TYPE, newMetadata); - return ClusterState.builder(currentState).metaData(mdBuilder).build(); - } else { - return currentState; - } + @Override + public ClusterState execute(ClusterState currentState) throws Exception { + MetaData metaData = currentState.metaData(); + final LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE); + if (currentLicenses.getLicense() != LicensesMetaData.LICENSE_TOMBSTONE) { + MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); + LicensesMetaData newMetadata = new LicensesMetaData(LicensesMetaData.LICENSE_TOMBSTONE, + currentLicenses.getMostRecentTrialVersion()); + mdBuilder.putCustom(LicensesMetaData.TYPE, newMetadata); + return ClusterState.builder(currentState).metaData(mdBuilder).build(); + } else { + return currentState; } - }); + } + }); } public License getLicense() { @@ -337,9 +347,10 @@ private LicensesMetaData getLicensesMetaData() { } void startTrialLicense(PostStartTrialRequest request, final ActionListener listener) { - if (VALID_TRIAL_TYPES.contains(request.getType()) == false) { - throw new IllegalArgumentException("Cannot start trial of type [" + request.getType() + "]. Valid trial types are " - + VALID_TRIAL_TYPES + "."); + License.LicenseType requestedType = License.LicenseType.parse(request.getType()); + if (VALID_TRIAL_TYPES.contains(requestedType) == false) { + throw new IllegalArgumentException("Cannot start trial of type [" + requestedType.getTypeName() + "]. Valid trial types are [" + + VALID_TRIAL_TYPES.stream().map(License.LicenseType::getTypeName).sorted().collect(Collectors.joining(",")) + "]"); } StartTrialClusterTask task = new StartTrialClusterTask(logger, clusterService.getClusterName().value(), clock, request, listener); clusterService.submitStateUpdateTask("started trial license", task); @@ -358,7 +369,7 @@ void startBasicLicense(PostStartBasicRequest request, final ActionListener - expirationCallback.nextScheduledTimeForExpiry(license.expiryDate(), startTime, now))); + (startTime, now) -> + expirationCallback.nextScheduledTimeForExpiry(license.expiryDate(), startTime, now))); } if (previousLicense != null) { // remove operationModeFileWatcher to gc the old license object previousLicense.removeOperationModeFileWatcher(); } logger.info("license [{}] mode [{}] - valid", license.uid(), - license.operationMode().name().toLowerCase(Locale.ROOT)); + license.operationMode().name().toLowerCase(Locale.ROOT)); } updateLicenseState(license, currentLicensesMetaData.getMostRecentTrialVersion()); } @@ -547,7 +558,7 @@ static License getLicense(final LicensesMetaData metaData) { } else if (license != null) { boolean autoGeneratedLicense = License.isAutoGeneratedLicense(license.signature()); if ((autoGeneratedLicense && SelfGeneratedLicense.verify(license)) - || (!autoGeneratedLicense && LicenseVerifier.verifyLicense(license))) { + || (!autoGeneratedLicense && LicenseVerifier.verifyLicense(license))) { return license; } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java index c39b37373ea13..dd4cb1ae6a589 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicenseUtils.java @@ -7,6 +7,7 @@ import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.license.License.LicenseType; import org.elasticsearch.rest.RestStatus; public class LicenseUtils { @@ -36,7 +37,8 @@ public static boolean isLicenseExpiredException(ElasticsearchSecurityException e } public static boolean licenseNeedsExtended(License license) { - return "basic".equals(license.type()) && license.expiryDate() != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; + return LicenseType.isBasic(license.type()) && + license.expiryDate() != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; } /** @@ -46,7 +48,8 @@ public static boolean licenseNeedsExtended(License license) { public static boolean signatureNeedsUpdate(License license, DiscoveryNodes currentNodes) { assert License.VERSION_CRYPTO_ALGORITHMS == License.VERSION_CURRENT : "update this method when adding a new version"; - return ("basic".equals(license.type()) || "trial".equals(license.type())) && + String typeName = license.type(); + return (LicenseType.isBasic(typeName) || LicenseType.isTrial(typeName)) && // only upgrade signature when all nodes are ready to deserialize the new signature (license.version() < License.VERSION_CRYPTO_ALGORITHMS && compatibleLicenseVersion(currentNodes) == License.VERSION_CRYPTO_ALGORITHMS diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensesMetaData.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensesMetaData.java index 26223e1340e0a..05f762d16f311 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensesMetaData.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensesMetaData.java @@ -39,7 +39,7 @@ public class LicensesMetaData extends AbstractNamedDiffable imp * ever existed in the cluster state */ public static final License LICENSE_TOMBSTONE = License.builder() - .type("trial") + .type(License.LicenseType.TRIAL) .issuer("elasticsearch") .uid("TOMBSTONE") .issuedTo("") diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java index 4cdba7845561c..0e79d306f1dd5 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPostStartTrialLicense.java @@ -29,7 +29,7 @@ public class RestPostStartTrialLicense extends BaseRestHandler { @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { PostStartTrialRequest startTrialRequest = new PostStartTrialRequest(); - startTrialRequest.setType(request.param("type", "trial")); + startTrialRequest.setType(request.param("type", License.LicenseType.TRIAL.getTypeName())); startTrialRequest.acknowledge(request.paramAsBoolean("acknowledge", false)); return channel -> client.execute(PostStartTrialAction.INSTANCE, startTrialRequest, new RestBuilderListener<>(channel) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPutLicenseAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPutLicenseAction.java index ef3a14e37ae64..bac9946d05797 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPutLicenseAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/RestPutLicenseAction.java @@ -41,9 +41,9 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC putLicenseRequest.timeout(request.paramAsTime("timeout", putLicenseRequest.timeout())); putLicenseRequest.masterNodeTimeout(request.paramAsTime("master_timeout", putLicenseRequest.masterNodeTimeout())); - if ("basic".equals(putLicenseRequest.license().type())) { + if (License.LicenseType.isBasic(putLicenseRequest.license().type())) { throw new IllegalArgumentException("Installing basic licenses is no longer allowed. Use the POST " + - "/_license/start_basic API to install a basic license that does not expire."); + "/_license/start_basic API to install a basic license that does not expire."); } return channel -> client.execute(PutLicenseAction.INSTANCE, putLicenseRequest, new RestToXContentListener<>(channel)); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java index fb9b167d3db52..536e189082a6f 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/SelfGeneratedLicense.java @@ -20,10 +20,10 @@ import java.util.Base64; import java.util.Collections; -import static org.elasticsearch.license.CryptUtils.encryptV3Format; -import static org.elasticsearch.license.CryptUtils.encrypt; -import static org.elasticsearch.license.CryptUtils.decryptV3Format; import static org.elasticsearch.license.CryptUtils.decrypt; +import static org.elasticsearch.license.CryptUtils.decryptV3Format; +import static org.elasticsearch.license.CryptUtils.encrypt; +import static org.elasticsearch.license.CryptUtils.encryptV3Format; class SelfGeneratedLicense { @@ -83,7 +83,13 @@ public static boolean verify(final License license) { } } - public static boolean validSelfGeneratedType(String type) { - return "basic".equals(type) || "trial".equals(type); + static License.LicenseType validateSelfGeneratedType(License.LicenseType type) { + switch (type) { + case BASIC: + case TRIAL: + return type; + } + throw new IllegalArgumentException("invalid self generated license type [" + type + "], only " + + License.LicenseType.BASIC + " and " + License.LicenseType.TRIAL + " are accepted"); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartBasicClusterTask.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartBasicClusterTask.java index 468f1799a07b9..3c6c00a5f86d1 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartBasicClusterTask.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartBasicClusterTask.java @@ -51,7 +51,7 @@ public void clusterStateProcessed(String source, ClusterState oldState, ClusterS if (acknowledgeMessages.isEmpty() == false) { listener.onResponse(new PostStartBasicResponse(PostStartBasicResponse.Status.NEED_ACKNOWLEDGEMENT, acknowledgeMessages, ACKNOWLEDGEMENT_HEADER)); - } else if (oldLicense != null && oldLicense.type().equals("basic")) { + } else if (oldLicense != null && License.LicenseType.isBasic(oldLicense.type())) { listener.onResponse(new PostStartBasicResponse(PostStartBasicResponse.Status.ALREADY_USING_BASIC)); } else { listener.onResponse(new PostStartBasicResponse(PostStartBasicResponse.Status.GENERATED_BASIC)); @@ -63,7 +63,7 @@ public ClusterState execute(ClusterState currentState) throws Exception { XPackPlugin.checkReadyForXPackCustomMetadata(currentState); LicensesMetaData licensesMetaData = currentState.metaData().custom(LicensesMetaData.TYPE); License currentLicense = LicensesMetaData.extractLicense(licensesMetaData); - if (currentLicense == null || currentLicense.type().equals("basic") == false) { + if (currentLicense == null || License.LicenseType.isBasic(currentLicense.type()) == false) { long issueDate = clock.millis(); MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); License.Builder specBuilder = License.builder() @@ -71,7 +71,7 @@ public ClusterState execute(ClusterState currentState) throws Exception { .issuedTo(clusterName) .maxNodes(LicenseService.SELF_GENERATED_LICENSE_MAX_NODES) .issueDate(issueDate) - .type("basic") + .type(License.LicenseType.BASIC) .expiryDate(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS); License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes()); if (request.isAcknowledged() == false && currentLicense != null) { diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java index 49ee35c277938..738bf0bb028e9 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/StartupSelfGeneratedLicenseTask.java @@ -5,8 +5,8 @@ */ package org.elasticsearch.license; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.Version; @@ -54,11 +54,8 @@ public ClusterState execute(ClusterState currentState) throws Exception { final LicensesMetaData currentLicensesMetaData = metaData.custom(LicensesMetaData.TYPE); // do not generate a license if any license is present if (currentLicensesMetaData == null) { - String type = LicenseService.SELF_GENERATED_LICENSE_TYPE.get(settings); - if (SelfGeneratedLicense.validSelfGeneratedType(type) == false) { - throw new IllegalArgumentException("Illegal self generated license type [" + type + - "]. Must be trial or basic."); - } + License.LicenseType type = SelfGeneratedLicense.validateSelfGeneratedType( + LicenseService.SELF_GENERATED_LICENSE_TYPE.get(settings)); return updateWithLicense(currentState, type); } else if (LicenseUtils.signatureNeedsUpdate(currentLicensesMetaData.getLicense(), currentState.nodes())) { return updateLicenseSignature(currentState, currentLicensesMetaData); @@ -76,7 +73,7 @@ private ClusterState updateLicenseSignature(ClusterState currentState, LicensesM long issueDate = license.issueDate(); long expiryDate = license.expiryDate(); // extend the basic license expiration date if needed since extendBasic will not be called now - if ("basic".equals(type) && expiryDate != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS) { + if (License.LicenseType.isBasic(type) && expiryDate != LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS) { expiryDate = LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; } License.Builder specBuilder = License.builder() @@ -117,18 +114,18 @@ private LicensesMetaData createBasicLicenseFromExistingLicense(LicensesMetaData .issuedTo(currentLicense.issuedTo()) .maxNodes(selfGeneratedLicenseMaxNodes) .issueDate(currentLicense.issueDate()) - .type("basic") + .type(License.LicenseType.BASIC) .expiryDate(LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS); License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentLicense.version()); Version trialVersion = currentLicenseMetadata.getMostRecentTrialVersion(); return new LicensesMetaData(selfGeneratedLicense, trialVersion); } - private ClusterState updateWithLicense(ClusterState currentState, String type) { + private ClusterState updateWithLicense(ClusterState currentState, License.LicenseType type) { long issueDate = clock.millis(); MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); long expiryDate; - if ("basic".equals(type)) { + if (type == License.LicenseType.BASIC) { expiryDate = LicenseService.BASIC_SELF_GENERATED_LICENSE_EXPIRATION_MILLIS; } else { expiryDate = issueDate + LicenseService.NON_BASIC_SELF_GENERATED_LICENSE_DURATION.getMillis(); @@ -142,7 +139,7 @@ private ClusterState updateWithLicense(ClusterState currentState, String type) { .expiryDate(expiryDate); License selfGeneratedLicense = SelfGeneratedLicense.create(specBuilder, currentState.nodes()); LicensesMetaData licensesMetaData; - if ("trial".equals(type)) { + if (License.LicenseType.TRIAL.equals(type)) { licensesMetaData = new LicensesMetaData(selfGeneratedLicense, Version.CURRENT); } else { licensesMetaData = new LicensesMetaData(selfGeneratedLicense, null); diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetBasicStatusAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetBasicStatusAction.java index 2705173b39c7f..809f61c5a005a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetBasicStatusAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/license/TransportGetBasicStatusAction.java @@ -49,7 +49,7 @@ protected void masterOperation(Task task, GetBasicStatusRequest request, Cluster listener.onResponse(new GetBasicStatusResponse(true)); } else { License license = licensesMetaData.getLicense(); - listener.onResponse(new GetBasicStatusResponse(license == null || license.type().equals("basic") == false)); + listener.onResponse(new GetBasicStatusResponse(license == null || License.LicenseType.isBasic(license.type()) == false)); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java index 7a6dc03c7bc2a..648f48ff2ea13 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java @@ -47,10 +47,13 @@ public void testResolvePlatinum() { assertResolve(OperationMode.PLATINUM, "PlAtINum", "platinum"); } + public void testResolveEnterpriseAsPlatinum() { + assertResolve(OperationMode.PLATINUM, License.LicenseType.ENTERPRISE.getTypeName()); + assertResolve(OperationMode.PLATINUM, License.LicenseType.ENTERPRISE.name()); + } + public void testResolveUnknown() { - // 'enterprise' is a type that exists in cloud but should be rejected under normal operation - // See https://github.com/elastic/x-plugins/issues/3371 - String[] types = { "unknown", "fake", "enterprise" }; + String[] types = { "unknown", "fake", "commercial" }; for (String type : types) { try { @@ -59,7 +62,7 @@ public void testResolveUnknown() { fail(String.format(Locale.ROOT, "[%s] should not be recognized as an operation mode", type)); } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), equalTo("unknown type [" + type + "]")); + assertThat(e.getMessage(), equalTo("unknown license type [" + type + "]")); } } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java index e6cc8f2bd89f2..aa209f9a520ac 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/LicenseTests.java @@ -6,21 +6,28 @@ package org.elasticsearch.license; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.UUIDs; import org.elasticsearch.common.bytes.BytesArray; +import org.elasticsearch.common.xcontent.NamedXContentRegistry; +import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; import java.nio.BufferUnderflowException; import java.nio.charset.StandardCharsets; +import java.util.concurrent.TimeUnit; +import static org.elasticsearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.elasticsearch.test.TestMatchers.throwableWithMessage; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; public class LicenseTests extends ESTestCase { public void testFromXContent() throws Exception { - String licenseString = "{\"license\":" + "{\"uid\":\"4056779d-b823-4c12-a9cb-efa4a8d8c422\"," + "\"type\":\"gold\"," + @@ -46,6 +53,39 @@ public void testFromXContent() throws Exception { assertThat(license.issueDate(), equalTo(1546589020459L)); } + public void testLicenseToAndFromXContentForEveryLicenseType() throws Exception { + for (License.LicenseType type : License.LicenseType.values()) { + final License license1 = License.builder() + .uid(UUIDs.randomBase64UUID(random())) + .type(type) + .issueDate(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(randomIntBetween(1, 10))) + .expiryDate(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(randomIntBetween(1, 1000))) + .maxNodes(randomIntBetween(1, 100)) + .issuedTo(randomAlphaOfLengthBetween(5, 50)) + .issuer(randomAlphaOfLengthBetween(5, 50)) + // We need a signature that parses correctly, but it doesn't need to verify + .signature("AAAAAgAAAA34V2kfTJVtvdL2LttwAAABmFJ6NGRnbEM3WVQrZVQwNkdKQmR1VytlMTMyM1J0dTZ1WGwyY2ZCVFhqMGtJU2gzZ3pnNTVpOW" + + "F5Y1NaUkwyN2VsTEtCYnlZR2c5WWtjQ0phaDlhRjlDUXViUmUwMWhjSkE2TFcwSGdneTJHbUV4N2RHUWJxV20ybjRsZHRzV2xkN0ZmdDlYblJmNVc" + + "xMlBWeU81V1hLUm1EK0V1dmF3cFdlSGZzTU5SZE1qUmFra3JkS1hCanBWVmVTaFFwV3BVZERzeG9Sci9rYnlJK2toODZXY09tNmFHUVNUL3IyUHEx" + + "V3VSTlBneWNJcFQ0bXl0cmhNNnRwbE1CWE4zWjJ5eGFuWFo0NGhsb3B5WFd1eTdYbFFWQkxFVFFPSlBERlB0eVVJYXVSZ0lsR2JpRS9rN1h4MSsvN" + + "UpOcGN6cU1NOHN1cHNtSTFIUGN1bWNGNEcxekhrblhNOXZ2VEQvYmRzQUFwbytUZEpRR3l6QU5oS2ZFSFdSbGxxNDZyZ0xvUHIwRjdBL2JqcnJnNG" + + "FlK09Cek9pYlJ5Umc9PQAAAQAth77fQLF7CCEL7wA6Z0/UuRm/weECcsjW/50kBnPLO8yEs+9/bPa5LSU0bF6byEXOVeO0ebUQfztpjulbXh8TrBD" + + "SG+6VdxGtohPo2IYPBaXzGs3LOOor6An/lhptxBWdwYmfbcp0m8mnXZh1vN9rmbTsZXnhBIoPTaRDwUBi3vJ3Ms3iLaEm4S8Slrfmtht2jUjgGZ2v" + + "AeZ9OHU2YsGtrSpz6f") + .build(); + XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, + Strings.toString(license1)); + License license2 = License.fromXContent(parser); + assertThat(license2, notNullValue()); + assertThat(license2.type(), equalTo(type.getTypeName())); + assertThat(license2.uid(), equalTo(license1.uid())); + assertThat(license2.issuer(), equalTo(license1.issuer())); + assertThat(license2.issuedTo(), equalTo(license1.issuedTo())); + assertThat(license2.expiryDate(), equalTo(license1.expiryDate())); + assertThat(license2.issueDate(), equalTo(license1.issueDate())); + } + } + public void testNotEnoughBytesFromXContent() throws Exception { String licenseString = "{\"license\": " + @@ -86,6 +126,9 @@ public void testMalformedSignatureFromXContent() throws Exception { License.fromSource(new BytesArray(licenseString.getBytes(StandardCharsets.UTF_8)), XContentType.JSON); }); + // When parsing a license, we read the signature bytes to verify the _version_. + // Random alphabetic sig bytes will generate a bad version + assertThat(exception, throwableWithMessage(containsString("Unknown license version found"))); } public void testUnableToBase64DecodeFromXContent() throws Exception { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/StartTrialLicenseTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/StartTrialLicenseTests.java index 537df2a4a51ed..bc05ecf647c06 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/license/StartTrialLicenseTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/license/StartTrialLicenseTests.java @@ -22,6 +22,7 @@ import java.util.Collection; import static org.elasticsearch.test.ESIntegTestCase.Scope.SUITE; +import static org.hamcrest.Matchers.containsString; @ESIntegTestCase.ClusterScope(scope = SUITE) public class StartTrialLicenseTests extends AbstractLicensesIntegrationTestCase { @@ -67,21 +68,21 @@ public void testStartTrial() throws Exception { assertEquals("basic", getLicenseResponse.license().type()); }); - String type = randomFrom(LicenseService.VALID_TRIAL_TYPES); + License.LicenseType type = randomFrom(LicenseService.VALID_TRIAL_TYPES); Request ackRequest = new Request("POST", "/_license/start_trial"); ackRequest.addParameter("acknowledge", "true"); - ackRequest.addParameter("type", type); + ackRequest.addParameter("type", type.getTypeName()); Response response3 = restClient.performRequest(ackRequest); String body3 = Streams.copyToString(new InputStreamReader(response3.getEntity().getContent(), StandardCharsets.UTF_8)); assertEquals(200, response3.getStatusLine().getStatusCode()); - assertTrue(body3.contains("\"trial_was_started\":true")); - assertTrue(body3.contains("\"type\":\"" + type + "\"")); - assertTrue(body3.contains("\"acknowledged\":true")); + assertThat(body3, containsString("\"trial_was_started\":true")); + assertThat(body3, containsString("\"type\":\"" + type.getTypeName() + "\"")); + assertThat(body3, containsString("\"acknowledged\":true")); assertBusy(() -> { GetLicenseResponse postTrialLicenseResponse = licensingClient.prepareGetLicense().get(); - assertEquals(type, postTrialLicenseResponse.license().type()); + assertEquals(type.getTypeName(), postTrialLicenseResponse.license().type()); }); Response response4 = restClient.performRequest(new Request("GET", "/_license/trial_status")); @@ -89,11 +90,11 @@ public void testStartTrial() throws Exception { assertEquals(200, response4.getStatusLine().getStatusCode()); assertEquals("{\"eligible_to_start_trial\":false}", body4); - String secondAttemptType = randomFrom(LicenseService.VALID_TRIAL_TYPES); + License.LicenseType secondAttemptType = randomFrom(LicenseService.VALID_TRIAL_TYPES); Request startTrialWhenStartedRequest = new Request("POST", "/_license/start_trial"); startTrialWhenStartedRequest.addParameter("acknowledge", "true"); - startTrialWhenStartedRequest.addParameter("type", secondAttemptType); + startTrialWhenStartedRequest.addParameter("type", secondAttemptType.getTypeName()); ResponseException ex = expectThrows(ResponseException.class, () -> restClient.performRequest(startTrialWhenStartedRequest)); Response response5 = ex.getResponse(); String body5 = Streams.copyToString(new InputStreamReader(response5.getEntity().getContent(), StandardCharsets.UTF_8)); @@ -111,8 +112,8 @@ public void testInvalidType() throws Exception { Response response = ex.getResponse(); String body = Streams.copyToString(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8)); assertEquals(400, response.getStatusLine().getStatusCode()); - assertTrue(body.contains("\"type\":\"illegal_argument_exception\"")); - assertTrue(body.contains("\"reason\":\"Cannot start trial of type [basic]. Valid trial types are [")); + assertThat(body, containsString("\"type\":\"illegal_argument_exception\"")); + assertThat(body, containsString("\"reason\":\"Cannot start trial of type [basic]. Valid trial types are [")); } private void ensureStartingWithBasic() throws Exception {