From 0c3466180fa414399ab52a233cc8473978f6c3ee Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Mon, 12 Sep 2016 17:53:33 +0200 Subject: [PATCH] Licensing: Add start date to licenses (elastic/elasticsearch#3385) Start dates are a required feature for cloud. This functionality adds support for specifying and enforcing a start date on licenses. Behaviour: If the start date is > than now, the license will be rejected. Due to another field in the license class, the version of the License class as well as its serialization methods are adapted to this. Closes elastic/elasticsearch#3370 Original commit: elastic/x-pack-elasticsearch@eb2a6f5be363658a6dc946ce7a257cb57f3aa824 --- .../org/elasticsearch/license/License.java | 45 +++++++++++++---- .../elasticsearch/license/LicenseService.java | 2 +- .../license/LicenseOperationModeTests.java | 4 +- .../license/LicensesAcknowledgementTests.java | 1 - .../license/LicensesManagerServiceTests.java | 13 ++--- .../license/LicensesTransportTests.java | 48 ++++++++++++++++++- .../org/elasticsearch/license/TestUtils.java | 7 ++- .../test/license/20_put_license.yaml | 27 +++++++++-- 8 files changed, 119 insertions(+), 28 deletions(-) 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 }