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@eb2a6f5be3
This commit is contained in:
parent
caf4bd2c82
commit
0c3466180f
|
@ -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<License> LATEST_ISSUE_DATE_FIRST = new Comparator<License>() {
|
||||
@Override
|
||||
public int compare(License right, License left) {
|
||||
return Long.compare(left.issueDate(), right.issueDate());
|
||||
}
|
||||
};
|
||||
public static final Comparator<License> 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() {
|
||||
|
|
|
@ -166,7 +166,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
|
|||
public void registerLicense(final PutLicenseRequest request, final ActionListener<PutLicenseResponse> 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));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
|
Loading…
Reference in New Issue