diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/License.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/License.java index f7270441966..45e1440069f 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/License.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/License.java @@ -31,7 +31,8 @@ import java.util.Locale; public class License implements ToXContent { public static final int VERSION_START = 1; public static final int VERSION_NO_FEATURE_TYPE = 2; - public static final int VERSION_CURRENT = VERSION_NO_FEATURE_TYPE; + public static final int VERSION_START_DATE = 3; + public static final int VERSION_CURRENT = VERSION_START_DATE; /** * XContent param name to deserialize license(s) with @@ -51,12 +52,7 @@ public class License implements ToXContent { */ public static final String LICENSE_VERSION_MODE = "license_version"; - public static final Comparator LATEST_ISSUE_DATE_FIRST = new Comparator() { - @Override - public int compare(License right, License left) { - return Long.compare(left.issueDate(), right.issueDate()); - } - }; + public static final Comparator LATEST_ISSUE_DATE_FIRST = Comparator.comparing(License::issueDate).reversed(); private final int version; private final String uid; @@ -68,6 +64,7 @@ public class License implements ToXContent { private final String feature; private final String signature; private final long expiryDate; + private final long startDate; private final int maxNodes; private final OperationMode operationMode; @@ -111,7 +108,7 @@ public class License implements ToXContent { } private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type, - String subscriptionType, String feature, String signature, long expiryDate, int maxNodes) { + String subscriptionType, String feature, String signature, long expiryDate, int maxNodes, long startDate) { this.version = version; this.uid = uid; this.issuer = issuer; @@ -123,6 +120,7 @@ public class License implements ToXContent { this.signature = signature; this.expiryDate = expiryDate; this.maxNodes = maxNodes; + this.startDate = startDate; if (version == VERSION_START) { // in 1.x: the acceptable values for 'subscription_type': none | dev | silver | gold | platinum this.operationMode = OperationMode.resolve(subscriptionType); @@ -161,6 +159,13 @@ public class License implements ToXContent { return issueDate; } + /** + * @return the startDate in milliseconds + */ + public long startDate() { + return startDate; + } + /** * @return the expiry date in milliseconds */ @@ -290,6 +295,9 @@ public class License implements ToXContent { builder.issuedTo(in.readString()); builder.issuer(in.readString()); builder.signature(in.readOptionalString()); + if (version >= VERSION_START_DATE) { + builder.startDate(in.readLong()); + } return builder.build(); } @@ -309,6 +317,9 @@ public class License implements ToXContent { out.writeString(issuedTo); out.writeString(issuer); out.writeOptionalString(signature); + if (version >= VERSION_START_DATE) { + out.writeLong(startDate); + } } @Override @@ -369,6 +380,9 @@ public class License implements ToXContent { if (restViewMode) { builder.humanReadable(previouslyHumanReadable); } + if (version >= VERSION_START_DATE) { + builder.dateField(Fields.START_DATE_IN_MILLIS, Fields.START_DATE, startDate); + } return builder; } @@ -394,6 +408,8 @@ public class License implements ToXContent { builder.feature(parser.text()); } else if (Fields.EXPIRY_DATE.equals(currentFieldName)) { builder.expiryDate(parseDate(parser, "expiration", true)); + } else if (Fields.START_DATE.equals(currentFieldName)) { + builder.startDate(parseDate(parser, "start", false)); } else if (Fields.EXPIRY_DATE_IN_MILLIS.equals(currentFieldName)) { builder.expiryDate(parser.longValue()); } else if (Fields.MAX_NODES.equals(currentFieldName)) { @@ -508,6 +524,7 @@ public class License implements ToXContent { if (issueDate != license.issueDate) return false; if (expiryDate != license.expiryDate) return false; + if (startDate!= license.startDate) return false; if (maxNodes != license.maxNodes) return false; if (version != license.version) return false; if (uid != null ? !uid.equals(license.uid) : license.uid != null) return false; @@ -532,6 +549,7 @@ public class License implements ToXContent { result = 31 * result + (feature != null ? feature.hashCode() : 0); result = 31 * result + (signature != null ? signature.hashCode() : 0); result = 31 * result + (int) (expiryDate ^ (expiryDate >>> 32)); + result = 31 * result + (int) (startDate ^ (startDate>>> 32)); result = 31 * result + maxNodes; result = 31 * result + version; return result; @@ -547,6 +565,8 @@ public class License implements ToXContent { public static final String FEATURE = "feature"; public static final String EXPIRY_DATE_IN_MILLIS = "expiry_date_in_millis"; public static final String EXPIRY_DATE = "expiry_date"; + public static final String START_DATE_IN_MILLIS = "start_date_in_millis"; + public static final String START_DATE = "start_date"; public static final String MAX_NODES = "max_nodes"; public static final String ISSUED_TO = "issued_to"; public static final String ISSUER = "issuer"; @@ -589,6 +609,7 @@ public class License implements ToXContent { private String feature; private String signature; private long expiryDate = -1; + private long startDate = -1; private int maxNodes = -1; public Builder uid(String uid) { @@ -648,11 +669,17 @@ public class License implements ToXContent { return this; } + public Builder startDate(long startDate) { + this.startDate = startDate; + return this; + } + public Builder fromLicenseSpec(License license, String signature) { return uid(license.uid()) .version(license.version()) .issuedTo(license.issuedTo()) .issueDate(license.issueDate()) + .startDate(license.startDate()) .type(license.type()) .subscriptionType(license.subscriptionType) .feature(license.feature) @@ -676,7 +703,7 @@ public class License implements ToXContent { public License build() { return new License(version, uid, issuer, issuedTo, issueDate, type, - subscriptionType, feature, signature, expiryDate, maxNodes); + subscriptionType, feature, signature, expiryDate, maxNodes, startDate); } public Builder validate() { diff --git a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseService.java b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseService.java index 4a9dee62c5f..1fb63917455 100644 --- a/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/elasticsearch/x-pack/license-plugin/src/main/java/org/elasticsearch/license/LicenseService.java @@ -166,7 +166,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste public void registerLicense(final PutLicenseRequest request, final ActionListener listener) { final License newLicense = request.license(); final long now = clock.millis(); - if (!LicenseVerifier.verifyLicense(newLicense) || newLicense.issueDate() > now) { + if (!LicenseVerifier.verifyLicense(newLicense) || newLicense.issueDate() > now || newLicense.startDate() > now) { listener.onResponse(new PutLicenseResponse(true, LicensesStatus.INVALID)); } else if (newLicense.expiryDate() < now) { listener.onResponse(new PutLicenseResponse(true, LicensesStatus.EXPIRED)); diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java index e803d8772f0..7a6dc03c7bc 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicenseOperationModeTests.java @@ -48,7 +48,9 @@ public class LicenseOperationModeTests extends ESTestCase { } public void testResolveUnknown() { - String[] types = { "unknown", "fake" }; + // '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" }; for (String type : types) { try { diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java index faf468eef07..f0b71fd0e7d 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java @@ -9,7 +9,6 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.common.unit.TimeValue; -import static org.elasticsearch.license.TestUtils.generateSignedLicense; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.mockito.Matchers.any; diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java index b21bc43241f..887e681ee47 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java @@ -5,11 +5,6 @@ */ package org.elasticsearch.license; -import java.util.Collection; -import java.util.Collections; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; - import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; import org.elasticsearch.cluster.service.ClusterService; @@ -20,10 +15,12 @@ import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackSettings; -import static org.elasticsearch.license.TestUtils.generateSignedLicense; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; public class LicensesManagerServiceTests extends ESSingleNodeTestCase { diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesTransportTests.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesTransportTests.java index c42edc5d7aa..7094e02205c 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesTransportTests.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/LicensesTransportTests.java @@ -8,15 +8,17 @@ package org.elasticsearch.license; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.node.Node; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESSingleNodeTestCase; import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackSettings; import java.util.Collection; import java.util.Collections; +import java.util.UUID; +import static org.elasticsearch.license.TestUtils.dateMath; import static org.elasticsearch.license.TestUtils.generateExpiredLicense; import static org.elasticsearch.license.TestUtils.generateSignedLicense; import static org.hamcrest.CoreMatchers.equalTo; @@ -164,4 +166,48 @@ public class LicensesTransportTests extends ESSingleNodeTestCase { getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get(); assertNull(getLicenseResponse.license()); } + + public void testLicenseIsRejectWhenStartDateLaterThanNow() throws Exception { + long now = System.currentTimeMillis(); + final License.Builder builder = License.builder() + .uid(UUID.randomUUID().toString()) + .version(License.VERSION_CURRENT) + .expiryDate(dateMath("now+2h", now)) + .startDate(dateMath("now+1h", now)) + .issueDate(now) + .type(License.OperationMode.TRIAL.toString()) + .issuedTo("customer") + .issuer("elasticsearch") + .maxNodes(5); + License license = TestUtils.generateSignedLicense(builder); + + PutLicenseRequestBuilder putLicenseRequestBuilder = + new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(license) + .setAcknowledge(true); + PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get(); + assertThat(putLicenseResponse.isAcknowledged(), equalTo(true)); + assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.INVALID)); + } + + public void testLicenseIsAcceptedWhenStartDateBeforeThanNow() throws Exception { + long now = System.currentTimeMillis(); + final License.Builder builder = License.builder() + .uid(UUID.randomUUID().toString()) + .version(License.VERSION_CURRENT) + .expiryDate(dateMath("now+2h", now)) + .startDate(now) + .issueDate(now) + .type(License.OperationMode.TRIAL.toString()) + .issuedTo("customer") + .issuer("elasticsearch") + .maxNodes(5); + License license = TestUtils.generateSignedLicense(builder); + + PutLicenseRequestBuilder putLicenseRequestBuilder = + new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(license) + .setAcknowledge(true); + PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get(); + assertThat(putLicenseResponse.isAcknowledged(), equalTo(true)); + assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID)); + } } \ No newline at end of file diff --git a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TestUtils.java b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TestUtils.java index a871dfdc181..f5f3ab48072 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TestUtils.java +++ b/elasticsearch/x-pack/license-plugin/src/test/java/org/elasticsearch/license/TestUtils.java @@ -33,8 +33,6 @@ import java.util.concurrent.atomic.AtomicReference; import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean; import static com.carrotsearch.randomizedtesting.RandomizedTest.randomInt; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; -import static org.elasticsearch.test.ESTestCase.assertNotNull; -import static org.elasticsearch.test.ESTestCase.awaitBusy; import static org.elasticsearch.test.ESTestCase.randomAsciiOfLength; import static org.elasticsearch.test.ESTestCase.randomFrom; import static org.elasticsearch.test.ESTestCase.randomIntBetween; @@ -276,6 +274,11 @@ public class TestUtils { return signer.sign(builder.build()); } + public static License generateSignedLicense(License.Builder builder) throws Exception { + LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath()); + return signer.sign(builder.build()); + } + public static License generateExpiredLicense(long expiryDate) throws Exception { return generateExpiredLicense(randomFrom("basic", "silver", "dev", "gold", "platinum"), expiryDate); } diff --git a/elasticsearch/x-pack/license-plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yaml b/elasticsearch/x-pack/license-plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yaml index a5873dba99f..8f59ca5a572 100644 --- a/elasticsearch/x-pack/license-plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yaml +++ b/elasticsearch/x-pack/license-plugin/src/test/resources/rest-api-spec/test/license/20_put_license.yaml @@ -20,8 +20,8 @@ teardown: - do: xpack.license.get: {} - ## a license object has 10 attributes - - length: { license: 10 } + ## a license object has 11 attributes + - length: { license: 11 } ## bwc for licenses format - do: @@ -35,7 +35,7 @@ teardown: - do: xpack.license.get: {} - - length: { license: 10 } + - length: { license: 11 } ## license version: 1.x - do: @@ -49,7 +49,7 @@ teardown: - do: xpack.license.get: {} - - length: { license: 10 } + - length: { license: 11 } ## multiple licenses version: 1.x - do: @@ -63,7 +63,7 @@ teardown: - do: xpack.license.get: {} - - length: { license: 10 } + - length: { license: 11 } - match: { license.uid: "893361dc-9749-4997-93cb-802e3dofh7aa" } --- @@ -76,3 +76,20 @@ teardown: - do: xpack.license.get: {} catch: missing + +--- +"Should install a feature type license": + + # VERSION_NO_FEATURE_TYPE license version + - do: + xpack.license.post: + acknowledge: true + body: | + {"license": {"uid":"893361dc-9749-4997-93cb-802e3d7fa4a8","type":"basic","issue_date_in_millis":1411948800000,"expiry_date_in_millis":1914278399999,"max_nodes":1,"issued_to":"issuedTo","issuer":"issuer","signature":"AAAAAgAAAA1JRPDxCvXkXtEVEB1OAAABmC9ZN0hjZDBGYnVyRXpCOW5Bb3FjZDAxOWpSbTVoMVZwUzRxVk1PSmkxakxZdW5IMlhlTHNoN1N2MXMvRFk4d3JTZEx3R3RRZ0pzU3lobWJKZnQvSEFva0ppTHBkWkprZWZSQi9iNmRQNkw1SlpLN0lDalZCS095MXRGN1lIZlpYcVVTTnFrcTE2dzhJZmZrdFQrN3JQeGwxb0U0MXZ0dDJHSERiZTVLOHNzSDByWnpoZEphZHBEZjUrTVBxRENNSXNsWWJjZllaODdzVmEzUjNiWktNWGM5TUhQV2plaUo4Q1JOUml4MXNuL0pSOEhQaVB2azhmUk9QVzhFeTFoM1Q0RnJXSG53MWk2K055c28zSmRnVkF1b2JSQkFLV2VXUmVHNDZ2R3o2VE1qbVNQS2lxOHN5bUErZlNIWkZSVmZIWEtaSU9wTTJENDVvT1NCYklacUYyK2FwRW9xa0t6dldMbmMzSGtQc3FWOTgzZ3ZUcXMvQkt2RUZwMFJnZzlvL2d2bDRWUzh6UG5pdENGWFRreXNKNkE9PQAAAQAALuQ44S3IG6SzolcXVJ6Z4CIXORDrYQ+wdLCeey0XdujTslAOj+k+vNgo6wauc7Uswi01esHu4lb5IgpvKy7RRCbh5bj/z2ubu2qMJqopp9BQyD7VQjVfqmG6seUMJwJ1a5Avvm9r41YPSPcrii3bKK2e1l6jK6N8ibCvnTyY/XkYGCJrBWTSJePDbg6ErbyodrZ37x1StLbPWcNAkmweyHjDJnvYnbeZZO7A3NmubXZjW7Ttf8/YwQyE00PqMcl7fVPY3hkKpAeHf8aaJbqkKYbqZuER3EWJX7ZvLVb1dNdNg8aXRn7YrkQcYwWgptYQpfV+D7yEJ4j5muAEoler"}} + + - match: { license_status: "valid" } + + - do: + xpack.license.get: {} + + - length: { license: 11 }