initial merge of license

Original commit: elastic/x-pack-elasticsearch@d520d6be57
This commit is contained in:
uboness 2015-07-13 12:37:12 +02:00
parent 082406f364
commit dbb9c5d918
99 changed files with 0 additions and 8825 deletions

View File

@ -1,36 +0,0 @@
elasticsearch-license
=====================
Elasticsearch Licensing core, tools and plugin
## Core
Contains core data structures, utilities used by **Licensor** and **Plugin**.
See `core/` and `core-shaded/`
## Licensor
Contains a collection of tools to generate key-pairs, licenses and validate licenses.
See `licensor/`
see [wiki] (https://github.com/elasticsearch/elasticsearch-license/wiki) for documentation on
[Licensing Tools Usage & Reference] (https://github.com/elasticsearch/elasticsearch-license/wiki/License-Tools-Usage-&-Reference)
## Plugin
**NOTE**: The license plugin has to be packaged with the right public key when being deployed to public repositories in maven
or uploaded to s3. Use `-Dkeys.path=<PATH_TO_KEY_DIR>` with maven command to package the plugin with a specified key.
See `plugin/`
see [Getting Started] (https://github.com/elasticsearch/elasticsearch-license/blob/master/docs/getting-started.asciidoc) to install license plugin.
see [Licensing REST APIs] (https://github.com/elasticsearch/elasticsearch-license/blob/master/docs/license.asciidoc)
to use the license plugin from an elasticsearch deployment.
see [wiki] (https://github.com/elasticsearch/elasticsearch-license/wiki) for documentation on
- [License Plugin Consumer Interface] (https://github.com/elasticsearch/elasticsearch-license/wiki/License---Consumer-Interface)
- [License Plugin Release Process] (https://github.com/elasticsearch/elasticsearch-license/wiki/Plugin-Release-Process)
- [License Plugin Design] (https://github.com/elasticsearch/elasticsearch-license/wiki/License-Plugin--Design)

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>elasticsearch-license</artifactId>
<groupId>org.elasticsearch</groupId>
<version>2.0.0.beta1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
<!-- we aren't really a plugin... -->
<skip.integ.tests>true</skip.integ.tests>
</properties>
<artifactId>elasticsearch-license-core</artifactId>
</project>

View File

@ -1,245 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.core;
import org.elasticsearch.common.Base64;
import javax.crypto.*;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class CryptUtils {
private static final int minimumPadding = 20;
private static final byte[] salt = {
(byte) 0xA9, (byte) 0xA2, (byte) 0xB5, (byte) 0xDE,
(byte) 0x2A, (byte) 0x8A, (byte) 0x9A, (byte) 0xE6
};
private static final int iterationCount = 1024;
private static final int aesKeyLength = 128;
private static final String keyAlgorithm = "RSA";
private static final String passHashAlgorithm = "SHA-512";
private static final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
private static final SecureRandom random = new SecureRandom();
/**
* Read encrypted private key file content with default pass phrase
*/
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents) {
try {
return readEncryptedPrivateKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/**
* Read encrypted public key file content with default pass phrase
*/
public static PublicKey readEncryptedPublicKey(byte[] fileContents) {
try {
return readEncryptedPublicKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/**
* Returns encrypted public key file content with default pass phrase
*/
public static byte[] writeEncryptedPublicKey(PublicKey publicKey) {
try {
return writeEncryptedPublicKey(publicKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/**
* Returns encrypted private key file content with default pass phrase
*/
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey) {
try {
return writeEncryptedPrivateKey(privateKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/**
* Read encrypted private key file content with provided <code>passPhrase</code>
*/
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents, char[] passPhrase) {
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decrypt(fileContents, passPhrase));
try {
return KeyFactory.getInstance(keyAlgorithm).generatePrivate(privateKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalStateException(e);
}
}
/**
* Read encrypted public key file content with provided <code>passPhrase</code>
*/
public static PublicKey readEncryptedPublicKey(byte[] fileContents, char[] passPhrase) {
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decrypt(fileContents, passPhrase));
try {
return KeyFactory.getInstance(CryptUtils.keyAlgorithm).generatePublic(publicKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalStateException(e);
}
}
/**
* Returns encrypted public key file content with provided <code>passPhrase</code>
*/
public static byte[] writeEncryptedPublicKey(PublicKey publicKey, char[] passPhrase) {
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
return encrypt(encodedKeySpec.getEncoded(), passPhrase);
}
/**
* Returns encrypted private key file content with provided <code>passPhrase</code>
*/
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey, char[] passPhrase) {
PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
return encrypt(encodedKeySpec.getEncoded(), passPhrase);
}
/**
* Encrypts provided <code>data</code> with <code>DEFAULT_PASS_PHRASE</code>
*/
public static byte[] encrypt(byte[] data) {
try {
return encrypt(data, hashPassPhrase(DEFAULT_PASS_PHRASE));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/**
* Decrypts provided <code>encryptedData</code> with <code>DEFAULT_PASS_PHRASE</code>
*/
public static byte[] decrypt(byte[] encryptedData) {
try {
return decrypt(encryptedData, hashPassPhrase(DEFAULT_PASS_PHRASE));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
/**
* Encrypts provided <code>data</code> with <code>passPhrase</code>
*/
public static byte[] encrypt(byte[] data, char[] passPhrase) {
try {
final Cipher encryptionCipher = getEncryptionCipher(getSecretKey(passPhrase));
return encryptionCipher.doFinal(pad(data, minimumPadding));
} catch (InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException(e);
}
}
/**
* Decrypts provided <code>encryptedData</code> with <code>passPhrase</code>
*/
private static byte[] decrypt(byte[] encryptedData, char[] passPhrase) {
try {
final Cipher cipher = getDecryptionCipher(getSecretKey(passPhrase));
return unPad(cipher.doFinal(encryptedData));
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) {
throw new IllegalStateException(e);
}
}
private static SecretKey getSecretKey(char[] passPhrase) throws InvalidKeySpecException {
try {
PBEKeySpec keySpec = new PBEKeySpec(passPhrase, salt, iterationCount, aesKeyLength);
byte[] shortKey = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede").
generateSecret(keySpec).getEncoded();
byte[] intermediaryKey = new byte[aesKeyLength / 8];
for (int i = 0, j = 0; i < aesKeyLength / 8; i++) {
intermediaryKey[i] = shortKey[j];
if (++j == shortKey.length)
j = 0;
}
return new SecretKeySpec(intermediaryKey, "AES");
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalStateException(e);
}
}
private static Cipher getEncryptionCipher(SecretKey secretKey) {
return getCipher(Cipher.ENCRYPT_MODE, secretKey);
}
private static Cipher getDecryptionCipher(SecretKey secretKey) {
return getCipher(Cipher.DECRYPT_MODE, secretKey);
}
private static Cipher getCipher(int mode, SecretKey secretKey) {
try {
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
cipher.init(mode, secretKey, random);
return cipher;
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
}
}
private static byte[] pad(byte[] bytes, int length) {
if (bytes.length >= length) {
byte[] out = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, out, 0, bytes.length);
out[bytes.length] = (byte) 1;
return out;
}
byte[] out = new byte[length + 1];
int i = 0;
for (; i < bytes.length; i++)
out[i] = bytes[i];
int padded = length - i;
// fill the rest with random bytes
byte[] fill = new byte[padded - 1];
random.nextBytes(fill);
System.arraycopy(fill, 0, out, i, padded - 1);
out[length] = (byte) (padded + 1);
return out;
}
private static byte[] unPad(byte[] bytes) {
int padded = (int) bytes[bytes.length - 1];
int targetLength = bytes.length - padded;
byte[] out = new byte[targetLength];
System.arraycopy(bytes, 0, out, 0, targetLength);
return out;
}
private static char[] hashPassPhrase(String passPhrase) throws NoSuchAlgorithmException {
final byte[] passBytes = passPhrase.getBytes(StandardCharsets.UTF_8);
final byte[] digest = MessageDigest.getInstance(passHashAlgorithm).digest(passBytes);
return new String(Base64.encodeBytesToBytes(digest), StandardCharsets.UTF_8).toCharArray();
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.core;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.joda.time.MutableDateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
public class DateUtils {
private final static FormatDateTimeFormatter formatDateOnlyFormatter = Joda.forPattern("yyyy-MM-dd");
private final static DateTimeFormatter dateOnlyFormatter = formatDateOnlyFormatter.parser().withZoneUTC();
private final static DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTime().withZoneUTC();
public static long endOfTheDay(String date) {
try {
// Try parsing using complete date/time format
return dateTimeFormatter.parseDateTime(date).getMillis();
} catch (IllegalArgumentException ex) {
// Fall back to the date only format
MutableDateTime dateTime = dateOnlyFormatter.parseMutableDateTime(date);
dateTime.millisOfDay().set(dateTime.millisOfDay().getMaximumValue());
return dateTime.getMillis();
}
}
public static long beginningOfTheDay(String date) {
try {
// Try parsing using complete date/time format
return dateTimeFormatter.parseDateTime(date).getMillis();
} catch (IllegalArgumentException ex) {
// Fall back to the date only format
return dateOnlyFormatter.parseDateTime(date).getMillis();
}
}
}

View File

@ -1,433 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.core;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentBuilderString;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
/**
* Data structure for license. Use {@link Builder} to build a license.
* Provides serialization/deserialization & validation methods for license object
*/
public class License implements ToXContent {
public final static int VERSION_START = 1;
public final static int VERSION_CURRENT = VERSION_START;
private final String uid;
private final String issuer;
private final String issuedTo;
private final long issueDate;
private final String type;
private final String subscriptionType;
private final String feature;
private final String signature;
private final long expiryDate;
private final int maxNodes;
private License(String uid, String issuer, String issuedTo, long issueDate, String type,
String subscriptionType, String feature, String signature, long expiryDate, int maxNodes) {
this.uid = uid;
this.issuer = issuer;
this.issuedTo = issuedTo;
this.issueDate = issueDate;
this.type = type;
this.subscriptionType = subscriptionType;
this.feature = feature;
this.signature = signature;
this.expiryDate = expiryDate;
this.maxNodes = maxNodes;
}
/**
* @return a unique identifier for a license
*/
public String uid() {
return uid;
}
/**
* @return type of the license [trial, subscription, internal]
*/
public String type() {
return type;
}
/**
* @return subscription type of the license [none, silver, gold, platinum]
*/
public String subscriptionType() {
return subscriptionType;
}
/**
* @return the issueDate in milliseconds
*/
public long issueDate() {
return issueDate;
}
/**
* @return the featureType for the license [shield, marvel]
*/
public String feature() {
return feature;
}
/**
* @return the expiry date in milliseconds
*/
public long expiryDate() {
return expiryDate;
}
/**
* @return the maximum number of nodes this license has been issued for
*/
public int maxNodes() {
return maxNodes;
}
/**
* @return a string representing the entity this licenses has been issued to
*/
public String issuedTo() {
return issuedTo;
}
/**
* @return a string representing the entity responsible for issuing this license (internal)
*/
public String issuer() {
return issuer;
}
/**
* @return a string representing the signature of the license used for license verification
*/
public String signature() {
return signature;
}
public void validate() {
if (issuer == null) {
throw new IllegalStateException("issuer can not be null");
} else if (issuedTo == null) {
throw new IllegalStateException("issuedTo can not be null");
} else if (issueDate == -1) {
throw new IllegalStateException("issueDate has to be set");
} else if (type == null) {
throw new IllegalStateException("type can not be null");
} else if (subscriptionType == null) {
throw new IllegalStateException("subscriptionType can not be null");
} else if (uid == null) {
throw new IllegalStateException("uid can not be null");
} else if (feature == null) {
throw new IllegalStateException("at least one feature has to be enabled");
} else if (signature == null) {
throw new IllegalStateException("signature can not be null");
} else if (maxNodes == -1) {
throw new IllegalStateException("maxNodes has to be set");
} else if (expiryDate == -1) {
throw new IllegalStateException("expiryDate has to be set");
}
}
static License readLicense(StreamInput in) throws IOException {
int version = in.readVInt(); // Version for future extensibility
if (version > VERSION_CURRENT) {
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch-license plugin");
}
Builder builder = builder();
builder.uid(in.readString());
builder.type(in.readString());
builder.subscriptionType(in.readString());
builder.issueDate(in.readLong());
builder.feature(in.readString());
builder.expiryDate(in.readLong());
builder.maxNodes(in.readInt());
builder.issuedTo(in.readString());
builder.issuer(in.readString());
builder.signature(in.readOptionalString());
return builder.build();
}
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(VERSION_CURRENT);
out.writeString(uid);
out.writeString(type);
out.writeString(subscriptionType);
out.writeLong(issueDate);
out.writeString(feature);
out.writeLong(expiryDate);
out.writeInt(maxNodes);
out.writeString(issuedTo);
out.writeString(issuer);
out.writeOptionalString(signature);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
boolean licenseSpecMode = params.paramAsBoolean(Licenses.LICENSE_SPEC_VIEW_MODE, false);
boolean restViewMode = params.paramAsBoolean(Licenses.REST_VIEW_MODE, false);
boolean previouslyHumanReadable = builder.humanReadable();
if (licenseSpecMode && restViewMode) {
throw new IllegalArgumentException("can have either " + Licenses.REST_VIEW_MODE + " or " + Licenses.LICENSE_SPEC_VIEW_MODE);
} else if (restViewMode) {
if (!previouslyHumanReadable) {
builder.humanReadable(true);
}
}
builder.startObject();
if (restViewMode) {
String status = "active";
long now = System.currentTimeMillis();
if (issueDate > now) {
status = "invalid";
} else if (expiryDate < now) {
status = "expired";
}
builder.field(XFields.STATUS, status);
}
builder.field(XFields.UID, uid);
builder.field(XFields.TYPE, type);
builder.field(XFields.SUBSCRIPTION_TYPE, subscriptionType);
builder.dateValueField(XFields.ISSUE_DATE_IN_MILLIS, XFields.ISSUE_DATE, issueDate);
builder.field(XFields.FEATURE, feature);
builder.dateValueField(XFields.EXPIRY_DATE_IN_MILLIS, XFields.EXPIRY_DATE, expiryDate);
builder.field(XFields.MAX_NODES, maxNodes);
builder.field(XFields.ISSUED_TO, issuedTo);
builder.field(XFields.ISSUER, issuer);
if (!licenseSpecMode && !restViewMode && signature != null) {
builder.field(XFields.SIGNATURE, signature);
}
builder.endObject();
if (restViewMode) {
builder.humanReadable(previouslyHumanReadable);
}
return builder;
}
final static class Fields {
static final String STATUS = "status";
static final String UID = "uid";
static final String TYPE = "type";
static final String SUBSCRIPTION_TYPE = "subscription_type";
static final String ISSUE_DATE_IN_MILLIS = "issue_date_in_millis";
static final String ISSUE_DATE = "issue_date";
static final String FEATURE = "feature";
static final String EXPIRY_DATE_IN_MILLIS = "expiry_date_in_millis";
static final String EXPIRY_DATE = "expiry_date";
static final String MAX_NODES = "max_nodes";
static final String ISSUED_TO = "issued_to";
static final String ISSUER = "issuer";
static final String SIGNATURE = "signature";
}
private final static class XFields {
static final XContentBuilderString STATUS = new XContentBuilderString(Fields.STATUS);
static final XContentBuilderString UID = new XContentBuilderString(Fields.UID);
static final XContentBuilderString TYPE = new XContentBuilderString(Fields.TYPE);
static final XContentBuilderString SUBSCRIPTION_TYPE = new XContentBuilderString(Fields.SUBSCRIPTION_TYPE);
static final XContentBuilderString ISSUE_DATE_IN_MILLIS = new XContentBuilderString(Fields.ISSUE_DATE_IN_MILLIS);
static final XContentBuilderString ISSUE_DATE = new XContentBuilderString(Fields.ISSUE_DATE);
static final XContentBuilderString FEATURE = new XContentBuilderString(Fields.FEATURE);
static final XContentBuilderString EXPIRY_DATE_IN_MILLIS = new XContentBuilderString(Fields.EXPIRY_DATE_IN_MILLIS);
static final XContentBuilderString EXPIRY_DATE = new XContentBuilderString(Fields.EXPIRY_DATE);
static final XContentBuilderString MAX_NODES = new XContentBuilderString(Fields.MAX_NODES);
static final XContentBuilderString ISSUED_TO = new XContentBuilderString(Fields.ISSUED_TO);
static final XContentBuilderString ISSUER = new XContentBuilderString(Fields.ISSUER);
static final XContentBuilderString SIGNATURE = new XContentBuilderString(Fields.SIGNATURE);
}
private static long parseDate(XContentParser parser, String description, boolean endOfTheDay) throws IOException {
if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
return parser.longValue();
} else {
try {
if (endOfTheDay) {
return DateUtils.endOfTheDay(parser.text());
} else {
return DateUtils.beginningOfTheDay(parser.text());
}
} catch (IllegalArgumentException ex) {
throw new ElasticsearchParseException("invalid " + description + " date format " + parser.text());
}
}
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String uid;
private String issuer;
private String issuedTo;
private long issueDate = -1;
private String type;
private String subscriptionType = "none";
private String feature;
private String signature;
private long expiryDate = -1;
private int maxNodes = -1;
public Builder uid(String uid) {
this.uid = uid;
return this;
}
public Builder issuer(String issuer) {
this.issuer = issuer;
return this;
}
public Builder issuedTo(String issuedTo) {
this.issuedTo = issuedTo;
return this;
}
public Builder issueDate(long issueDate) {
this.issueDate = issueDate;
return this;
}
public Builder type(String type) {
this.type = type;
return this;
}
public Builder subscriptionType(String subscriptionType) {
this.subscriptionType = subscriptionType;
return this;
}
public Builder feature(String feature) {
this.feature = feature;
return this;
}
public Builder expiryDate(long expiryDate) {
this.expiryDate = expiryDate;
return this;
}
public Builder maxNodes(int maxNodes) {
this.maxNodes = maxNodes;
return this;
}
public Builder signature(String signature) {
if (signature != null) {
this.signature = signature;
}
return this;
}
public Builder fromLicenseSpec(License license, String signature) {
return uid(license.uid())
.issuedTo(license.issuedTo())
.issueDate(license.issueDate())
.type(license.type())
.subscriptionType(license.subscriptionType())
.feature(license.feature())
.maxNodes(license.maxNodes())
.expiryDate(license.expiryDate())
.issuer(license.issuer())
.signature(signature);
}
public Builder fromXContent(XContentParser parser) throws IOException {
XContentParser.Token token = parser.currentToken();
if (token == XContentParser.Token.START_OBJECT) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
String currentFieldName = parser.currentName();
token = parser.nextToken();
if (token.isValue()) {
if (Fields.UID.equals(currentFieldName)) {
uid(parser.text());
} else if (Fields.TYPE.equals(currentFieldName)) {
type(parser.text());
} else if (Fields.SUBSCRIPTION_TYPE.equals(currentFieldName)) {
subscriptionType(parser.text());
} else if (Fields.ISSUE_DATE.equals(currentFieldName)) {
issueDate(parseDate(parser, "issue", false));
} else if (Fields.ISSUE_DATE_IN_MILLIS.equals(currentFieldName)) {
issueDate(parser.longValue());
} else if (Fields.FEATURE.equals(currentFieldName)) {
feature(parser.text());
} else if (Fields.EXPIRY_DATE.equals(currentFieldName)) {
expiryDate(parseDate(parser, "expiration", true));
} else if (Fields.EXPIRY_DATE_IN_MILLIS.equals(currentFieldName)) {
expiryDate(parser.longValue());
} else if (Fields.MAX_NODES.equals(currentFieldName)) {
maxNodes(parser.intValue());
} else if (Fields.ISSUED_TO.equals(currentFieldName)) {
issuedTo(parser.text());
} else if (Fields.ISSUER.equals(currentFieldName)) {
issuer(parser.text());
} else if (Fields.SIGNATURE.equals(currentFieldName)) {
signature(parser.text());
}
// Ignore unknown elements - might be new version of license
} else if (token == XContentParser.Token.START_ARRAY) {
// It was probably created by newer version - ignoring
parser.skipChildren();
} else if (token == XContentParser.Token.START_OBJECT) {
// It was probably created by newer version - ignoring
parser.skipChildren();
}
}
}
} else {
throw new ElasticsearchParseException("failed to parse licenses expected a license object");
}
return this;
}
public License build() {
return new License(uid, issuer, issuedTo, issueDate, type,
subscriptionType, feature, signature, expiryDate, maxNodes);
}
public Builder validate() {
if (issuer == null) {
throw new IllegalStateException("issuer can not be null");
} else if (issuedTo == null) {
throw new IllegalStateException("issuedTo can not be null");
} else if (issueDate == -1) {
throw new IllegalStateException("issueDate has to be set");
} else if (type == null) {
throw new IllegalStateException("type can not be null");
} else if (subscriptionType == null) {
throw new IllegalStateException("subscriptionType can not be null");
} else if (uid == null) {
throw new IllegalStateException("uid can not be null");
} else if (feature == null) {
throw new IllegalStateException("at least one feature has to be enabled");
} else if (signature == null) {
throw new IllegalStateException("signature can not be null");
} else if (maxNodes == -1) {
throw new IllegalStateException("maxNodes has to be set");
} else if (expiryDate == -1) {
throw new IllegalStateException("expiryDate has to be set");
}
return this;
}
}
}

View File

@ -1,159 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.core;
import org.elasticsearch.common.Base64;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
/**
* Responsible for verifying signed licenses
* The signed licenses are expected to have signatures with the appropriate spec
* (see {@link org.elasticsearch.license.core.LicenseVerifier})
* along with the appropriate encrypted public key
*/
public class LicenseVerifier {
/**
* Verifies Licenses using {@link #verifyLicense(License, byte[])} using the public key from the
* resources
*/
public static boolean verifyLicenses(final Collection<License> licenses) {
final byte[] encryptedPublicKeyData = getPublicKeyContentFromResource("/public.key");
try {
for (License license : licenses) {
if (!verifyLicense(license, encryptedPublicKeyData)) {
return false;
}
}
return true;
} finally {
Arrays.fill(encryptedPublicKeyData, (byte) 0);
}
}
/**
* Verifies Licenses using {@link #verifyLicense(License, byte[])} using the provided public key
*/
public static boolean verifyLicenses(final Collection<License> licenses, final Path publicKeyPath) throws IOException {
final byte[] encryptedPublicKeyData = Files.readAllBytes(publicKeyPath);
try {
for (License license : licenses) {
if (!verifyLicense(license, encryptedPublicKeyData)) {
return false;
}
}
return true;
} finally {
Arrays.fill(encryptedPublicKeyData, (byte) 0);
}
}
public static boolean verifyLicense(final License license) {
final byte[] encryptedPublicKeyData = getPublicKeyContentFromResource("/public.key");
try {
return verifyLicense(license, encryptedPublicKeyData);
} finally {
Arrays.fill(encryptedPublicKeyData, (byte) 0);
}
}
/**
* verifies the license content with the signature and ensures that an expected public key is used
* @param license to verify
* @return true if valid, false otherwise
*/
public static boolean verifyLicense(final License license, final byte[] encryptedPublicKeyData) {
LicenseSignature licenseSignature = null;
try {
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
license.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(Licenses.LICENSE_SPEC_VIEW_MODE, "true")));
licenseSignature = parseSignature(license.signature());
if(!verifyContent(encryptedPublicKeyData, contentBuilder.bytes().toBytes(), licenseSignature.contentSignature)) {
return false;
}
final byte[] hash = Base64.encodeBytesToBytes(encryptedPublicKeyData);
return Arrays.equals(hash, licenseSignature.hash);
} catch (IOException e) {
throw new IllegalStateException(e);
} finally {
if (licenseSignature != null) {
licenseSignature.clear();
}
}
}
private static boolean verifyContent(byte[] encryptedPublicKeyData, byte[] data, byte[] contentSignature) {
try {
Signature rsa = Signature.getInstance("SHA512withRSA");
rsa.initVerify(CryptUtils.readEncryptedPublicKey(encryptedPublicKeyData));
rsa.update(data);
return rsa.verify(contentSignature);
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
throw new IllegalStateException(e);
}
}
private static byte[] getPublicKeyContentFromResource(String resource) {
try {
return Streams.copyToBytesFromClasspath(resource);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
/**
* Signature structure:
* | VERSION | MAGIC | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
*/
private static LicenseSignature parseSignature(String signature) throws IOException {
byte[] signatureBytes = Base64.decode(signature);
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
int version = byteBuffer.getInt();
int magicLen = byteBuffer.getInt();
byte[] magic = new byte[magicLen];
byteBuffer.get(magic);
int hashLen = byteBuffer.getInt();
byte[] hash = new byte[hashLen];
byteBuffer.get(hash);
int signedContentLen = byteBuffer.getInt();
byte[] signedContent = new byte[signedContentLen];
byteBuffer.get(signedContent);
return new LicenseSignature(version, hash, signedContent);
}
private static class LicenseSignature {
private final int version;
private final byte[] hash;
private final byte[] contentSignature;
private LicenseSignature(int version, byte[] hash, byte[] contentSignature) {
this.version = version;
this.hash = hash;
this.contentSignature = contentSignature;
}
private void clear() {
Arrays.fill(hash, (byte)0);
Arrays.fill(contentSignature, (byte)0);
}
}
}

View File

@ -1,149 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.core;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Utility class for operating on a collection of {@link License}s.
* Provides serialization/deserialization methods and reduce
* operations
*/
public final class Licenses {
/**
* XContent param name to deserialize license(s) with
* an additional <code>status</code> field, indicating whether a
* particular license is 'active' or 'expired' and no signature
* and in a human readable format
*/
public static final String REST_VIEW_MODE = "rest_view";
/**
* XContent param name to deserialize license(s) with
* no signature
*/
public static final String LICENSE_SPEC_VIEW_MODE = "license_spec_view";
private final static class Fields {
static final String LICENSES = "licenses";
}
private final static class XFields {
static final XContentBuilderString LICENSES = new XContentBuilderString(Fields.LICENSES);
}
private Licenses() {}
public static void toXContent(Collection<License> licenses, XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.startArray(XFields.LICENSES);
for (License license : licenses) {
license.toXContent(builder, params);
}
builder.endArray();
builder.endObject();
}
public static List<License> fromSource(String content) throws IOException {
return fromSource(content.getBytes(StandardCharsets.UTF_8), true);
}
public static List<License> fromSource(byte[] bytes) throws IOException {
return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), true);
}
public static List<License> fromSource(byte[] bytes, boolean verify) throws IOException {
return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), verify);
}
public static List<License> fromXContent(XContentParser parser, boolean verify) throws IOException {
List<License> licenses = new ArrayList<>();
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
if (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
String currentFieldName = parser.currentName();
if (Fields.LICENSES.equals(currentFieldName)) {
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
License.Builder builder = License.builder().fromXContent(parser);
if (verify) {
builder.validate();
}
licenses.add(builder.build());
}
} else {
throw new ElasticsearchParseException("failed to parse licenses expected an array of licenses");
}
}
// Ignore all other fields - might be created with new version
} else {
throw new ElasticsearchParseException("failed to parse licenses expected field");
}
} else {
throw new ElasticsearchParseException("failed to parse licenses expected start object");
}
return licenses;
}
public static List<License> readFrom(StreamInput in) throws IOException {
int size = in.readVInt();
List<License> licenses = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
licenses.add(License.readLicense(in));
}
return licenses;
}
public static void writeTo(List<License> licenses, StreamOutput out) throws IOException {
out.writeVInt(licenses.size());
for (License license : licenses) {
license.writeTo(out);
}
}
/**
* Given a set of {@link License}s, reduces the set to one license per feature.
* Uses {@link #putIfAppropriate(java.util.Map, License)} to reduce.
*
* @param licensesSet a set of licenses to be reduced
* @return a map of (feature, license)
*/
public static ImmutableMap<String, License> reduceAndMap(Set<License> licensesSet) {
Map<String, License> map = new HashMap<>(licensesSet.size());
for (License license : licensesSet) {
putIfAppropriate(map, license);
}
return ImmutableMap.copyOf(map);
}
/**
* Adds or updates the <code>license</code> to <code>licenseMap</code> if the <code>license</code>
* has not expired already and if the <code>license</code> has a later expiry date from any
* existing licenses in <code>licenseMap</code> for the same feature
*
* @param licenseMap a map of (feature, license)
* @param license a new license to be added to <code>licenseMap</code>
*/
private static void putIfAppropriate(Map<String, License> licenseMap, License license) {
final String featureType = license.feature();
if (licenseMap.containsKey(featureType)) {
final License previousLicense = licenseMap.get(featureType);
if (license.expiryDate() > previousLicense.expiryDate()) {
licenseMap.put(featureType, license);
}
} else {
licenseMap.put(featureType, license);
}
}
}

View File

@ -1,115 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.core;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
public class LicenseSerializationTests extends ElasticsearchTestCase {
@Test
public void testSimpleIssueExpiryDate() throws Exception {
long now = System.currentTimeMillis();
String issueDate = TestUtils.dateMathString("now", now);
String expiryDate = TestUtils.dateMathString("now+10d/d", now);
String licenseSpecs = TestUtils.generateLicenseSpecString(Arrays.asList(new TestUtils.LicenseSpec("shield", issueDate, expiryDate)));
Set<License> licensesOutput = new HashSet<>(Licenses.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8), false));
License generatedLicense = licensesOutput.iterator().next();
assertThat(licensesOutput.size(), equalTo(1));
assertThat(generatedLicense.issueDate(), equalTo(DateUtils.beginningOfTheDay(issueDate)));
assertThat(generatedLicense.expiryDate(), equalTo(DateUtils.endOfTheDay(expiryDate)));
}
@Test
public void testMultipleIssueExpiryDate() throws Exception {
long now = System.currentTimeMillis();
String shieldIssueDate = TestUtils.dateMathString("now", now);
String shieldExpiryDate = TestUtils.dateMathString("now+30d/d", now);
String marvelIssueDate = TestUtils.dateMathString("now", now);
String marvelExpiryDate = TestUtils.dateMathString("now+60d/d", now);
String licenseSpecs = TestUtils.generateLicenseSpecString(Arrays.asList(new TestUtils.LicenseSpec("shield", shieldIssueDate, shieldExpiryDate)));
String licenseSpecs1 = TestUtils.generateLicenseSpecString(Arrays.asList(new TestUtils.LicenseSpec("marvel", marvelIssueDate, marvelExpiryDate)));
Set<License> licensesOutput = new HashSet<>();
licensesOutput.addAll(Licenses.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8), false));
licensesOutput.addAll(Licenses.fromSource(licenseSpecs1.getBytes(StandardCharsets.UTF_8), false));
assertThat(licensesOutput.size(), equalTo(2));
for (License license : licensesOutput) {
assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay((license.feature().equals("shield")) ? shieldIssueDate : marvelIssueDate)));
assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay((license.feature().equals("shield")) ? shieldExpiryDate : marvelExpiryDate)));
}
}
@Test
public void testLicensesFields() throws Exception {
Map<String, TestUtils.LicenseSpec> licenseSpecs = new HashMap<>();
for (int i = 0; i < randomIntBetween(1, 5); i++) {
TestUtils.LicenseSpec randomLicenseSpec = TestUtils.generateRandomLicenseSpec();
licenseSpecs.put(randomLicenseSpec.feature, randomLicenseSpec);
}
ArrayList<TestUtils.LicenseSpec> specs = new ArrayList<>(licenseSpecs.values());
String licenseSpecsSource = TestUtils.generateLicenseSpecString(specs);
Set<License> licensesOutput = new HashSet<>(Licenses.fromSource(licenseSpecsSource.getBytes(StandardCharsets.UTF_8), false));
assertThat(licensesOutput.size(), equalTo(licenseSpecs.size()));
for (License license : licensesOutput) {
TestUtils.LicenseSpec spec = licenseSpecs.get(license.feature());
assertThat(spec, notNullValue());
TestUtils.assertLicenseSpec(spec, license);
}
}
@Test
public void testLicenseRestView() throws Exception {
long now = System.currentTimeMillis();
String expiredLicenseExpiryDate = TestUtils.dateMathString("now-1d/d", now);
String validLicenseIssueDate = TestUtils.dateMathString("now-10d/d", now);
String invalidLicenseIssueDate = TestUtils.dateMathString("now+1d/d", now);
String validLicenseExpiryDate = TestUtils.dateMathString("now+2d/d", now);
Set<License> licenses = TestUtils.generateLicenses(Arrays.asList(new TestUtils.LicenseSpec("expired_feature", validLicenseIssueDate, expiredLicenseExpiryDate)
, new TestUtils.LicenseSpec("valid_feature", validLicenseIssueDate, validLicenseExpiryDate),
new TestUtils.LicenseSpec("invalid_feature", invalidLicenseIssueDate, validLicenseExpiryDate)));
assertThat(licenses.size(), equalTo(3));
for (License license : licenses) {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
license.toXContent(builder, new ToXContent.MapParams(ImmutableMap.of(Licenses.REST_VIEW_MODE, "true")));
builder.flush();
Map<String, Object> map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
// should have an extra status field, human readable issue_data and expiry_date
assertThat(map.get("status"), notNullValue());
assertThat(map.get("issue_date"), notNullValue());
assertThat(map.get("expiry_date"), notNullValue());
if (license.feature().equals("valid_feature")) {
assertThat((String) map.get("status"), equalTo("active"));
} else if (license.feature().equals("expired_feature")) {
assertThat((String) map.get("status"), equalTo("expired"));
} else if (license.feature().equals("invalid_feature")) {
assertThat((String) map.get("status"), equalTo("invalid"));
}
}
for (License license : licenses) {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
license.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.flush();
Map<String, Object> map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
assertThat(map.get("status"), nullValue());
}
}
}

View File

@ -1,224 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.core;
import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.hamcrest.MatcherAssert;
import org.joda.time.format.DateTimeFormatter;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Callable;
import static com.carrotsearch.randomizedtesting.RandomizedTest.*;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.test.ElasticsearchTestCase.randomFrom;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
public class TestUtils {
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
public static void isSame(Set<License> firstLicenses, Set<License> secondLicenses) {
// we do the verifyAndBuild to make sure we weed out any expired licenses
final Map<String, License> licenses1 = Licenses.reduceAndMap(firstLicenses);
final Map<String, License> licenses2 = Licenses.reduceAndMap(secondLicenses);
// check if the effective licenses have the same feature set
assertThat(licenses1.size(), equalTo(licenses2.size()));
// for every feature license, check if all the attributes are the same
for (String featureType : licenses1.keySet()) {
License license1 = licenses1.get(featureType);
License license2 = licenses2.get(featureType);
isSame(license1, license2);
}
}
public static void isSame(License license1, License license2) {
assertThat(license1.uid(), equalTo(license2.uid()));
assertThat(license1.feature(), equalTo(license2.feature()));
assertThat(license1.subscriptionType(), equalTo(license2.subscriptionType()));
assertThat(license1.type(), equalTo(license2.type()));
assertThat(license1.issuedTo(), equalTo(license2.issuedTo()));
assertThat(license1.signature(), equalTo(license2.signature()));
assertThat(license1.expiryDate(), equalTo(license2.expiryDate()));
assertThat(license1.issueDate(), equalTo(license2.issueDate()));
assertThat(license1.maxNodes(), equalTo(license2.maxNodes()));
}
public static String dateMathString(String time, final long now) {
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
@Override
public Long call() throws Exception {
return now;
}
}));
}
public static long dateMath(String time, final long now) {
return dateMathParser.parse(time, new Callable<Long>() {
@Override
public Long call() throws Exception {
return now;
}
});
}
public static LicenseSpec generateRandomLicenseSpec() {
boolean datesInMillis = randomBoolean();
long now = System.currentTimeMillis();
String uid = UUID.randomUUID().toString();
String feature = "feature__" + randomInt();
String issuer = "issuer__" + randomInt();
String issuedTo = "issuedTo__" + randomInt();
String type = randomFrom("subscription", "internal", "development");
String subscriptionType = randomFrom("none", "gold", "silver", "platinum");
int maxNodes = randomIntBetween(5, 100);
if (datesInMillis) {
long issueDateInMillis = dateMath("now", now);
long expiryDateInMillis = dateMath("now+10d/d", now);
return new LicenseSpec(uid, feature, issueDateInMillis, expiryDateInMillis, type, subscriptionType, issuedTo, issuer, maxNodes);
} else {
String issueDate = dateMathString("now", now);
String expiryDate = dateMathString("now+10d/d", now);
return new LicenseSpec(uid, feature, issueDate, expiryDate, type, subscriptionType, issuedTo, issuer, maxNodes);
}
}
public static String generateLicenseSpecString(List<LicenseSpec> licenseSpecs) throws IOException {
XContentBuilder licenses = jsonBuilder();
licenses.startObject();
licenses.startArray("licenses");
for (LicenseSpec licenseSpec : licenseSpecs) {
licenses.startObject()
.field("uid", licenseSpec.uid)
.field("type", licenseSpec.type)
.field("subscription_type", licenseSpec.subscriptionType)
.field("issued_to", licenseSpec.issuedTo)
.field("issuer", licenseSpec.issuer)
.field("feature", licenseSpec.feature)
.field("max_nodes", licenseSpec.maxNodes);
if (licenseSpec.issueDate != null) {
licenses.field("issue_date", licenseSpec.issueDate);
} else {
licenses.field("issue_date_in_millis", licenseSpec.issueDateInMillis);
}
if (licenseSpec.expiryDate != null) {
licenses.field("expiry_date", licenseSpec.expiryDate);
} else {
licenses.field("expiry_date_in_millis", licenseSpec.expiryDateInMillis);
}
licenses.endObject();
}
licenses.endArray();
licenses.endObject();
return licenses.string();
}
public static Set<License> generateLicenses(List<LicenseSpec> licenseSpecs) {
Set<License> unSignedLicenses = new HashSet<>();
for (TestUtils.LicenseSpec spec : licenseSpecs) {
License.Builder builder = License.builder()
.uid(spec.uid)
.feature(spec.feature)
.type(spec.type)
.subscriptionType(spec.subscriptionType)
.issuedTo(spec.issuedTo)
.issuer(spec.issuer)
.maxNodes(spec.maxNodes);
if (spec.expiryDate != null) {
builder.expiryDate(DateUtils.endOfTheDay(spec.expiryDate));
} else {
builder.expiryDate(spec.expiryDateInMillis);
}
if (spec.issueDate != null) {
builder.issueDate(DateUtils.beginningOfTheDay(spec.issueDate));
} else {
builder.issueDate(spec.issueDateInMillis);
}
unSignedLicenses.add(builder.build());
}
return unSignedLicenses;
}
public static void assertLicenseSpec(LicenseSpec spec, License license) {
MatcherAssert.assertThat(license.uid(), equalTo(spec.uid));
MatcherAssert.assertThat(license.feature(), equalTo(spec.feature));
MatcherAssert.assertThat(license.issuedTo(), equalTo(spec.issuedTo));
MatcherAssert.assertThat(license.issuer(), equalTo(spec.issuer));
MatcherAssert.assertThat(license.type(), equalTo(spec.type));
MatcherAssert.assertThat(license.subscriptionType(), equalTo(spec.subscriptionType));
MatcherAssert.assertThat(license.maxNodes(), equalTo(spec.maxNodes));
if (spec.issueDate != null) {
MatcherAssert.assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay(spec.issueDate)));
} else {
MatcherAssert.assertThat(license.issueDate(), equalTo(spec.issueDateInMillis));
}
if (spec.expiryDate != null) {
MatcherAssert.assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay(spec.expiryDate)));
} else {
MatcherAssert.assertThat(license.expiryDate(), equalTo(spec.expiryDateInMillis));
}
}
public static class LicenseSpec {
public final String feature;
public final String issueDate;
public final long issueDateInMillis;
public final String expiryDate;
public final long expiryDateInMillis;
public final String uid;
public final String type;
public final String subscriptionType;
public final String issuedTo;
public final String issuer;
public final int maxNodes;
public LicenseSpec(String feature, String issueDate, String expiryDate) {
this(UUID.randomUUID().toString(), feature, issueDate, expiryDate, "trial", "none", "customer", "elasticsearch", 5);
}
public LicenseSpec(String uid, String feature, long issueDateInMillis, long expiryDateInMillis, String type,
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
this.feature = feature;
this.issueDateInMillis = issueDateInMillis;
this.issueDate = null;
this.expiryDateInMillis = expiryDateInMillis;
this.expiryDate = null;
this.uid = uid;
this.type = type;
this.subscriptionType = subscriptionType;
this.issuedTo = issuedTo;
this.issuer = issuer;
this.maxNodes = maxNodes;
}
public LicenseSpec(String uid, String feature, String issueDate, String expiryDate, String type,
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
this.feature = feature;
this.issueDate = issueDate;
this.issueDateInMillis = -1;
this.expiryDate = expiryDate;
this.expiryDateInMillis = -1;
this.uid = uid;
this.type = type;
this.subscriptionType = subscriptionType;
this.issuedTo = issuedTo;
this.issuer = issuer;
this.maxNodes = maxNodes;
}
}
}

View File

@ -1,11 +0,0 @@
es.logger.level=INFO
log4j.rootLogger=${es.logger.level}, out
log4j.logger.org.apache.http=INFO, out
log4j.additivity.org.apache.http=false
log4j.logger.org.elasticsearch.license=TRACE
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n

Binary file not shown.

View File

@ -1,3 +0,0 @@
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßÇ<09>ş]d™}o§OčľId®Č
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{Ľ˛Áô­śátť¤7ůřĂę #˛Vó»ktwmŚ]ĎLőŁz"| QlźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5ŻŤĘĘ7%ŘęD
xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|Ţ ÓŞĎÁ*ń§D`<60>Ú?ůxU/3>x­UÓ“+ č

View File

@ -1,15 +0,0 @@
ELASTICSEARCH CONFIDENTIAL
__________________
[2014] Elasticsearch Incorporated
All Rights Reserved.
NOTICE: All information contained herein is, and remains
the property of Elasticsearch Incorporated and its suppliers,
if any. The intellectual and technical concepts contained
herein are proprietary to Elasticsearch Incorporated
and its suppliers and may be covered by U.S. and Foreign Patents,
patents in process, and are protected by trade secret or copyright law.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Elasticsearch Incorporated.

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<additionalHeaders>
<javadoc_style>
<firstLine>/*</firstLine>
<beforeEachLine> * </beforeEachLine>
<endLine> */</endLine>
<!--skipLine></skipLine-->
<firstLineDetectionPattern>(\s|\t)*/\*.*$</firstLineDetectionPattern>
<lastLineDetectionPattern>.*\*/(\s|\t)*$</lastLineDetectionPattern>
<allowBlankLines>false</allowBlankLines>
<isMultiline>true</isMultiline>
</javadoc_style>
</additionalHeaders>

View File

@ -1,55 +0,0 @@
#!/bin/sh
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.
CDPATH=""
SCRIPT="$0"
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
# Drop everything prior to ->
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
# determine license home
LICENSE_HOME=`dirname "$SCRIPT"`/..
# make LICENSE_HOME absolute
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
# setup classpath
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
if [ -x "$JAVA_HOME/bin/java" ]; then
JAVA=$JAVA_HOME/bin/java
else
JAVA=`which java`
fi
# real getopt cannot be used because we need to hand options over to the KeyPairGeneratorTool
while [ $# -gt 0 ]; do
case $1 in
-D*=*)
properties="$properties $1"
;;
-D*)
var=$1
shift
properties="$properties $var=$1"
;;
*)
args="$args $1"
esac
shift
done
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool $args

View File

@ -1,55 +0,0 @@
#!/bin/sh
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.
CDPATH=""
SCRIPT="$0"
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
# Drop everything prior to ->
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
# determine license home
LICENSE_HOME=`dirname "$SCRIPT"`/..
# make LICENSE_HOME absolute
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
# setup classpath
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
if [ -x "$JAVA_HOME/bin/java" ]; then
JAVA=$JAVA_HOME/bin/java
else
JAVA=`which java`
fi
# real getopt cannot be used because we need to hand options over to the LicenseGeneratorTool
while [ $# -gt 0 ]; do
case $1 in
-D*=*)
properties="$properties $1"
;;
-D*)
var=$1
shift
properties="$properties $var=$1"
;;
*)
args="$args $1"
esac
shift
done
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" org.elasticsearch.license.licensor.tools.LicenseGeneratorTool $args

View File

@ -1,55 +0,0 @@
#!/bin/sh
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License;
# you may not use this file except in compliance with the Elastic License.
CDPATH=""
SCRIPT="$0"
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
while [ -h "$SCRIPT" ] ; do
ls=`ls -ld "$SCRIPT"`
# Drop everything prior to ->
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
SCRIPT="$link"
else
SCRIPT=`dirname "$SCRIPT"`/"$link"
fi
done
# determine license home
LICENSE_HOME=`dirname "$SCRIPT"`/..
# make LICENSE_HOME absolute
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
# setup classpath
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
if [ -x "$JAVA_HOME/bin/java" ]; then
JAVA=$JAVA_HOME/bin/java
else
JAVA=`which java`
fi
# real getopt cannot be used because we need to hand options over to the LicenseVerificationTool
while [ $# -gt 0 ]; do
case $1 in
-D*=*)
properties="$properties $1"
;;
-D*)
var=$1
shift
properties="$properties $var=$1"
;;
*)
args="$args $1"
esac
shift
done
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" org.elasticsearch.license.licensor.tools.LicenseVerificationTool $args

View File

@ -1,58 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>elasticsearch-license</artifactId>
<groupId>org.elasticsearch</groupId>
<version>2.0.0.beta1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>elasticsearch-license-licensor</artifactId>
<properties>
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
<elasticsearch.assembly.descriptor>${basedir}/src/main/assemblies/exec.xml</elasticsearch.assembly.descriptor>
<elasticsearch.assembly.appendId>true</elasticsearch.assembly.appendId>
<!-- we aren't really a plugin... -->
<skip.integ.tests>true</skip.integ.tests>
</properties>
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license-core</artifactId>
<version>2.0.0.beta1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>for-exec</id>
<phase>prepare-package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classifier>exec</classifier>
<excludes>
<!-- exclude public key -->
<exclude>public.key</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1 +0,0 @@
{"licenses":[{"uid": "893361dc-9749-4997-93cb-802e3d7fa4a8", "type":"internal","subscription_type":"none","issued_to":"issuedTo","issuer":"issuer","issue_date":"2014-09-29","expiry_date":"2015-08-29","feature":"shield","max_nodes":1}]}

View File

@ -1,47 +0,0 @@
<?xml version="1.0"?>
<assembly>
<id>exec</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<fileSets>
<fileSet>
<filtered>true</filtered>
<directory>bin</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
<directoryMode>0755</directoryMode>
<lineEnding>unix</lineEnding>
<includes>
<include>key-pair-generator</include>
<include>license-generator</include>
<include>verify-license</include>
</includes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>/lib</outputDirectory>
<useProjectArtifact>false</useProjectArtifact>
<useProjectAttachments>true</useProjectAttachments>
<useTransitiveFiltering>true</useTransitiveFiltering>
<includes>
<include>org.elasticsearch:elasticsearch-license-licensor:*:exec</include>
<include>org.elasticsearch:elasticsearch</include>
</includes>
<excludes>
<exclude>org.apache.lucene:*</exclude>
</excludes>
</dependencySet>
<dependencySet>
<outputDirectory>/lib</outputDirectory>
<useProjectArtifact>false</useProjectArtifact>
<useProjectAttachments>true</useProjectAttachments>
<useTransitiveFiltering>true</useTransitiveFiltering>
<includes>
<include>org.apache.lucene:lucene-core</include>
</includes>
</dependencySet>
</dependencySets>
</assembly>

View File

@ -1,104 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor;
import com.google.common.collect.ImmutableSet;
import org.elasticsearch.common.Base64;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.core.CryptUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.util.Collections;
import java.util.Set;
/**
* Responsible for generating a license signature according to
* the signature spec and sign it with the provided encrypted private key
*/
@SuppressForbidden(reason = "can we avoid bare string paths and resolve from Environment or similar?")
public class LicenseSigner {
private final static int MAGIC_LENGTH = 13;
private final Path publicKeyPath;
private final Path privateKeyPath;
public LicenseSigner(final String privateKeyPath, final String publicKeyPath) {
this(PathUtils.get(privateKeyPath), PathUtils.get(publicKeyPath));
}
public LicenseSigner(final Path privateKeyPath, final Path publicKeyPath) {
this.publicKeyPath = publicKeyPath;
this.privateKeyPath = privateKeyPath;
}
public ImmutableSet<License> sign(Set<License> licenseSpecs) throws IOException {
final ImmutableSet.Builder<License> builder = ImmutableSet.builder();
for (License licenseSpec : licenseSpecs) {
builder.add(sign(licenseSpec));
}
return builder.build();
}
/**
* Generates a signature for the <code>licenseSpec</code>.
* Signature structure:
* | VERSION | MAGIC | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
*
* @return a signed License
* @throws java.io.IOException
*/
public License sign(License licenseSpec) throws IOException {
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
licenseSpec.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(Licenses.LICENSE_SPEC_VIEW_MODE, "true")));
final byte[] signedContent = sign(contentBuilder.bytes().toBytes(), privateKeyPath);
final byte[] magic = new byte[MAGIC_LENGTH];
SecureRandom random = new SecureRandom();
random.nextBytes(magic);
final byte[] hash = Base64.encodeBytesToBytes(Files.readAllBytes(publicKeyPath));
assert hash != null;
byte[] bytes = new byte[4 + 4 + MAGIC_LENGTH + 4 + hash.length + 4 + signedContent.length];
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.putInt(License.VERSION_CURRENT)
.putInt(magic.length)
.put(magic)
.putInt(hash.length)
.put(hash)
.putInt(signedContent.length)
.put(signedContent);
return License.builder()
.fromLicenseSpec(licenseSpec, Base64.encodeBytes(bytes))
.validate()
.build();
}
private static byte[] sign(byte[] data, Path privateKeyPath) {
try {
final Signature rsa = Signature.getInstance("SHA512withRSA");
rsa.initSign(CryptUtils.readEncryptedPrivateKey(Files.readAllBytes(privateKeyPath)));
rsa.update(data);
return rsa.sign();
} catch (InvalidKeyException | IOException | NoSuchAlgorithmException | SignatureException e) {
throw new IllegalStateException(e);
}
}
}

View File

@ -1,102 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor.tools;
import org.apache.commons.cli.CommandLine;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
import static org.elasticsearch.common.cli.CliToolConfig.config;
import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPrivateKey;
import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPublicKey;
public class KeyPairGeneratorTool extends CliTool {
public static final String NAME = "key-pair-generator";
private static final CliToolConfig CONFIG = config("licensor", KeyPairGeneratorTool.class)
.cmds(KeyGenerator.CMD)
.build();
public KeyPairGeneratorTool() {
super(CONFIG);
}
@Override
protected Command parse(String s, CommandLine commandLine) throws Exception {
return KeyGenerator.parse(terminal, commandLine, env);
}
public static class KeyGenerator extends Command {
private static final CliToolConfig.Cmd CMD = cmd(NAME, KeyGenerator.class)
.options(
option("pub", "publicKeyPath").required(true).hasArg(true),
option("pri", "privateKeyPath").required(true).hasArg(true)
).build();
public final Path publicKeyPath;
public final Path privateKeyPath;
protected KeyGenerator(Terminal terminal, Path publicKeyPath, Path privateKeyPath) {
super(terminal);
this.privateKeyPath = privateKeyPath;
this.publicKeyPath = publicKeyPath;
}
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) {
Path publicKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("publicKeyPath"));
Path privateKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("privateKeyPath"));
if (Files.exists(privateKeyPath)) {
return exitCmd(ExitStatus.USAGE, terminal, privateKeyPath + " already exists");
} else if (Files.exists(publicKeyPath)) {
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " already exists");
}
return new KeyGenerator(terminal, publicKeyPath, privateKeyPath);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
KeyPair keyPair = generateKeyPair(privateKeyPath, publicKeyPath);
terminal.println(Terminal.Verbosity.VERBOSE, "generating key pair [public key: " + publicKeyPath + ", private key: " + privateKeyPath + "]");
return (keyPair != null) ? ExitStatus.OK : ExitStatus.CANT_CREATE;
}
private static KeyPair generateKeyPair(Path privateKeyPath, Path publicKeyPath) throws IOException, NoSuchAlgorithmException {
SecureRandom random = new SecureRandom();
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048, random);
KeyPair keyPair = keyGen.generateKeyPair();
saveKeyPairToFiles(keyPair, privateKeyPath, publicKeyPath);
return keyPair;
}
}
private static void saveKeyPairToFiles(KeyPair keyPair, Path privateKeyPath, Path publicKeyPath) throws IOException {
Files.write(privateKeyPath, writeEncryptedPrivateKey(keyPair.getPrivate()));
Files.write(publicKeyPath, writeEncryptedPublicKey(keyPair.getPublic()));
}
public static void main(String[] args) throws Exception {
int status = new KeyPairGeneratorTool().execute(args);
System.exit(status);
}
}

View File

@ -1,126 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor.tools;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.cli.CommandLine;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.licensor.LicenseSigner;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
import static org.elasticsearch.common.cli.CliToolConfig.config;
public class LicenseGeneratorTool extends CliTool {
public static final String NAME = "license-generator";
private static final CliToolConfig CONFIG = config("licensor", LicenseGeneratorTool.class)
.cmds(LicenseGenerator.CMD)
.build();
public LicenseGeneratorTool() {
super(CONFIG);
}
@Override
protected Command parse(String s, CommandLine commandLine) throws Exception {
return LicenseGenerator.parse(terminal, commandLine, env);
}
public static class LicenseGenerator extends Command {
private static final CliToolConfig.Cmd CMD = cmd(NAME, LicenseGenerator.class)
.options(
option("pub", "publicKeyPath").required(true).hasArg(true),
option("pri", "privateKeyPath").required(true).hasArg(true),
option("l", "license").required(false).hasArg(true),
option("lf", "licenseFile").required(false).hasArg(true)
).build();
public final Set<License> licenseSpecs;
public final Path publicKeyFilePath;
public final Path privateKeyFilePath;
public LicenseGenerator(Terminal terminal, Path publicKeyFilePath, Path privateKeyFilePath, Set<License> licenseSpecs) {
super(terminal);
this.licenseSpecs = licenseSpecs;
this.privateKeyFilePath = privateKeyFilePath;
this.publicKeyFilePath = publicKeyFilePath;
}
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) throws IOException {
Path publicKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("publicKeyPath"));
Path privateKeyPath = environment.homeFile().resolve(commandLine.getOptionValue("privateKeyPath"));
String[] licenseSpecSources = commandLine.getOptionValues("license");
String[] licenseSpecSourceFiles = commandLine.getOptionValues("licenseFile");
if (!Files.exists(privateKeyPath)) {
return exitCmd(ExitStatus.USAGE, terminal, privateKeyPath + " does not exist");
} else if (!Files.exists(publicKeyPath)) {
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
}
Set<License> licenseSpecs = new HashSet<>();
if (licenseSpecSources != null) {
for (String licenseSpec : licenseSpecSources) {
licenseSpecs.addAll(Licenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8), false));
}
}
if (licenseSpecSourceFiles != null) {
for (String licenseSpecFilePath : licenseSpecSourceFiles) {
Path licenseSpecPath = environment.homeFile().resolve(licenseSpecFilePath);
if (!Files.exists(licenseSpecPath)) {
return exitCmd(ExitStatus.USAGE, terminal, licenseSpecFilePath + " does not exist");
}
licenseSpecs.addAll(Licenses.fromSource(Files.readAllBytes(licenseSpecPath), false));
}
}
if (licenseSpecs.size() == 0) {
return exitCmd(ExitStatus.USAGE, terminal, "no license spec provided");
}
return new LicenseGenerator(terminal, publicKeyPath, privateKeyPath, licenseSpecs);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
// sign
ImmutableSet<License> signedLicences = new LicenseSigner(privateKeyFilePath, publicKeyFilePath).sign(licenseSpecs);
// dump
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
Licenses.toXContent(signedLicences, builder, ToXContent.EMPTY_PARAMS);
builder.flush();
terminal.print(builder.string());
return ExitStatus.OK;
}
}
public static void main(String[] args) throws Exception {
int status = new LicenseGeneratorTool().execute(args);
System.exit(status);
}
}

View File

@ -1,126 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor.tools;
import org.apache.commons.cli.CommandLine;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
import static org.elasticsearch.common.cli.CliToolConfig.config;
public class LicenseVerificationTool extends CliTool {
public static final String NAME = "verify-license";
private static final CliToolConfig CONFIG = config("licensor", LicenseVerificationTool.class)
.cmds(LicenseVerifier.CMD)
.build();
public LicenseVerificationTool() {
super(CONFIG);
}
@Override
protected Command parse(String s, CommandLine commandLine) throws Exception {
return LicenseVerifier.parse(terminal, commandLine, env);
}
public static class LicenseVerifier extends Command {
private static final CliToolConfig.Cmd CMD = cmd(NAME, LicenseVerifier.class)
.options(
option("pub", "publicKeyPath").required(true).hasArg(true),
option("l", "license").required(false).hasArg(true),
option("lf", "licenseFile").required(false).hasArg(true)
).build();
public final Set<License> licenses;
public final Path publicKeyPath;
public LicenseVerifier(Terminal terminal, Set<License> licenses, Path publicKeyPath) {
super(terminal);
this.licenses = licenses;
this.publicKeyPath = publicKeyPath;
}
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) throws IOException {
String publicKeyPathString = commandLine.getOptionValue("publicKeyPath");
String[] licenseSources = commandLine.getOptionValues("license");
String[] licenseSourceFiles = commandLine.getOptionValues("licenseFile");
Set<License> licenses = new HashSet<>();
if (licenseSources != null) {
for (String licenseSpec : licenseSources) {
licenses.addAll(Licenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8)));
}
}
if (licenseSourceFiles != null) {
for (String licenseFilePath : licenseSourceFiles) {
Path licensePath = environment.homeFile().resolve(licenseFilePath);
if (!Files.exists(licensePath)) {
return exitCmd(ExitStatus.USAGE, terminal, licenseFilePath + " does not exist");
}
licenses.addAll(Licenses.fromSource(Files.readAllBytes(licensePath)));
}
}
if (licenses.size() == 0) {
return exitCmd(ExitStatus.USAGE, terminal, "no license provided");
}
Path publicKeyPath = environment.homeFile().resolve(publicKeyPathString);
if (!Files.exists(publicKeyPath)) {
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
}
return new LicenseVerifier(terminal, licenses, publicKeyPath);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
// verify
Map<String, License> effectiveLicenses = Licenses.reduceAndMap(licenses);
if (!org.elasticsearch.license.core.LicenseVerifier.verifyLicenses(effectiveLicenses.values(), publicKeyPath)) {
terminal.println("Invalid License(s)!");
return ExitStatus.DATA_ERROR;
}
// dump effective licences
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
Licenses.toXContent(effectiveLicenses.values(), builder, ToXContent.EMPTY_PARAMS);
builder.flush();
terminal.print(builder.string());
return ExitStatus.OK;
}
}
public static void main(String[] args) throws Exception {
int status = new LicenseVerificationTool().execute(args);
System.exit(status);
}
}

View File

@ -1,22 +0,0 @@
NAME
key-pair-generator - generates a key pair with RSA 2048-bit security
SYNOPSIS
key-pair-generator -pub publicKeyPath -pri privateKeyPath
DESCRIPTION
This tool generates and saves a key pair to the provided publicKeyPath
and privateKeyPath. The tool checks the existence of the provided key paths
and will not override if any existing keys are found.
OPTIONS
-h,--help Shows this message
-pub,--publicKeyPath <path> Save the generated public key to path
-pri,--privateKeyPath <path> Save the generated private key to path

View File

@ -1,26 +0,0 @@
NAME
license-generator - generates signed elasticsearch license(s) for a given license spec(s)
SYNOPSIS
license-generator -l licenseSpec -pub publicKeyPath -pri privateKeyPath
DESCRIPTION
This tool generate elasticsearch license(s) for the provided license spec(s). The tool
can take arbitrary number of `--license` and/or `--licenseFile` to generate corrosponding
signed license(s).
OPTIONS
-h,--help Shows this message
-l,--license <license spec> License spec to generate a signed license from
-lf,--licenseFile <path> Path to a license spec file
-pub,--publicKeyPath <path> Path to public key to be used
-pri,--privateKeyPath <path> Path to private key to be used

View File

@ -1,28 +0,0 @@
NAME
verify-license - verifies the integrity of elasticsearch signed license(s)
SYNOPSIS
verify-license -l signedLicense -pub publicKeyPath
DESCRIPTION
This tool assumes the configured public key to be the same as that of the production license plugin public key.
The tool can take arbitrary number of `--license` and/or `--licenseFile` for verifying signed license(s). If any
of the provided license(s) are invalid, the tool will error out, otherwise it will output a effective licenses file.
Effective Licenses:
A set of licenses that only has one effective sub-license for every feature provided through the input license file.
Where effective sub-licenses are identified as the sub-licenses with the latest `expiry_date` for a `feature`
and the sub-license has not already expired.
OPTIONS
-h,--help Shows this message
-l,--license <signed license> signed license(s) string
-lf,--licenseFile <path> Path to signed license(s) file
-pub,--publicKeyPath <path> Path to public key to verify against

View File

@ -1,97 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor;/*
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.DateUtils;
import org.elasticsearch.license.core.License;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.After;
import org.junit.Before;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
public abstract class AbstractLicensingTestBase extends ElasticsearchTestCase {
protected String pubKeyPath = null;
protected String priKeyPath = null;
@Before
public void setup() throws Exception {
pubKeyPath = getResourcePath("/public.key");
priKeyPath = getResourcePath("/private.key");
}
@After
public void cleanUp() {
pubKeyPath = null;
priKeyPath = null;
}
public static Set<License> generateSignedLicenses(List<TestUtils.LicenseSpec> licenseSpecs, String pubKeyPath, String priKeyPath) throws Exception {
LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath);
Set<License> unSignedLicenses = new HashSet<>();
for (TestUtils.LicenseSpec spec : licenseSpecs) {
License.Builder builder = License.builder()
.uid(spec.uid)
.feature(spec.feature)
.type(spec.type)
.subscriptionType(spec.subscriptionType)
.issuedTo(spec.issuedTo)
.issuer(spec.issuer)
.maxNodes(spec.maxNodes);
if (spec.expiryDate != null) {
builder.expiryDate(DateUtils.endOfTheDay(spec.expiryDate));
} else {
builder.expiryDate(spec.expiryDateInMillis);
}
if (spec.issueDate != null) {
builder.issueDate(DateUtils.beginningOfTheDay(spec.issueDate));
} else {
builder.issueDate(spec.issueDateInMillis);
}
unSignedLicenses.add(builder.build());
}
return signer.sign(unSignedLicenses);
}
public static License generateSignedLicense(String feature, TimeValue expiryDuration, String pubKeyPath, String priKeyPath) throws Exception {
return generateSignedLicense(feature, -1, expiryDuration, pubKeyPath, priKeyPath);
}
public static License generateSignedLicense(String feature, long issueDate, TimeValue expiryDuration, String pubKeyPath, String priKeyPath) throws Exception {
long issue = (issueDate != -1l) ? issueDate : System.currentTimeMillis();
final License licenseSpec = License.builder()
.uid(UUID.randomUUID().toString())
.feature(feature)
.expiryDate(issue + expiryDuration.getMillis())
.issueDate(issue)
.type("subscription")
.subscriptionType("gold")
.issuedTo("customer")
.issuer("elasticsearch")
.maxNodes(5)
.build();
LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath);
return signer.sign(licenseSpec);
}
public String getTestPriKeyPath() throws Exception {
return getResourcePath("/private.key");
}
public String getTestPubKeyPath() throws Exception {
return getResourcePath("/public.key");
}
private String getResourcePath(String resource) throws Exception {
return getDataPath(resource).toString();
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import static org.hamcrest.Matchers.equalTo;
public class LicenseVerificationTests extends AbstractLicensingTestBase {
@Test
public void testGeneratedLicenses() throws Exception {
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24), pubKeyPath, priKeyPath);
assertThat(LicenseVerifier.verifyLicense(shieldLicense), equalTo(true));
}
@Test
public void testMultipleFeatureLicenses() throws Exception {
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24), pubKeyPath, priKeyPath);
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2 * 24), pubKeyPath, priKeyPath);
assertThat(LicenseVerifier.verifyLicenses(Arrays.asList(shieldLicense, marvelLicense)), equalTo(true));
}
@Test
public void testLicenseTampering() throws Exception {
License license = generateSignedLicense("shield", TimeValue.timeValueHours(2), pubKeyPath, priKeyPath);
final License tamperedLicense = License.builder()
.fromLicenseSpec(license, license.signature())
.expiryDate(license.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
.validate()
.build();
assertThat(LicenseVerifier.verifyLicense(tamperedLicense), equalTo(false));
}
@Test
public void testRandomLicenseVerification() throws Exception {
int n = randomIntBetween(5, 15);
List<TestUtils.LicenseSpec> licenseSpecs = new ArrayList<>();
for (int i = 0; i < n; i++) {
licenseSpecs.add(TestUtils.generateRandomLicenseSpec());
}
Set<License> generatedLicenses = generateSignedLicenses(licenseSpecs, pubKeyPath, priKeyPath);
assertThat(generatedLicenses.size(), equalTo(n));
for (License generatedLicense: generatedLicenses) {
assertThat(LicenseVerifier.verifyLicense(generatedLicense), equalTo(true));
}
}
}

View File

@ -1,213 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor;
import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.license.core.DateUtils;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.hamcrest.MatcherAssert;
import org.joda.time.format.DateTimeFormatter;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Callable;
import static com.carrotsearch.randomizedtesting.RandomizedTest.*;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.test.ElasticsearchTestCase.randomFrom;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
public class TestUtils {
public static final String PUBLIC_KEY_RESOURCE = "/public.key";
public static final String PRIVATE_KEY_RESOURCE = "/private.key";
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
public static void isSame(Set<License> firstLicenses, Set<License> secondLicenses) {
// we do the verifyAndBuild to make sure we weed out any expired licenses
final Map<String, License> licenses1 = Licenses.reduceAndMap(firstLicenses);
final Map<String, License> licenses2 = Licenses.reduceAndMap(secondLicenses);
// check if the effective licenses have the same feature set
assertThat(licenses1.size(), equalTo(licenses2.size()));
// for every feature license, check if all the attributes are the same
for (String featureType : licenses1.keySet()) {
License license1 = licenses1.get(featureType);
License license2 = licenses2.get(featureType);
isSame(license1, license2);
}
}
public static void isSame(License license1, License license2) {
assertThat(license1.uid(), equalTo(license2.uid()));
assertThat(license1.feature(), equalTo(license2.feature()));
assertThat(license1.subscriptionType(), equalTo(license2.subscriptionType()));
assertThat(license1.type(), equalTo(license2.type()));
assertThat(license1.issuedTo(), equalTo(license2.issuedTo()));
assertThat(license1.signature(), equalTo(license2.signature()));
assertThat(license1.expiryDate(), equalTo(license2.expiryDate()));
assertThat(license1.issueDate(), equalTo(license2.issueDate()));
assertThat(license1.maxNodes(), equalTo(license2.maxNodes()));
}
public static String dumpLicense(License license) throws Exception {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
Licenses.toXContent(Collections.singletonList(license), builder, ToXContent.EMPTY_PARAMS);
builder.flush();
return builder.string();
}
public static String dateMathString(String time, final long now) {
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
@Override
public Long call() throws Exception {
return now;
}
}));
}
public static long dateMath(String time, final long now) {
return dateMathParser.parse(time, new Callable<Long>() {
@Override
public Long call() throws Exception {
return now;
}
});
}
public static LicenseSpec generateRandomLicenseSpec() {
boolean datesInMillis = randomBoolean();
long now = System.currentTimeMillis();
String uid = UUID.randomUUID().toString();
String feature = "feature__" + randomInt();
String issuer = "issuer__" + randomInt();
String issuedTo = "issuedTo__" + randomInt();
String type = randomFrom("subscription", "internal", "development");
String subscriptionType = randomFrom("none", "gold", "silver", "platinum");
int maxNodes = randomIntBetween(5, 100);
if (datesInMillis) {
long issueDateInMillis = dateMath("now", now);
long expiryDateInMillis = dateMath("now+10d/d", now);
return new LicenseSpec(uid, feature, issueDateInMillis, expiryDateInMillis, type, subscriptionType, issuedTo, issuer, maxNodes);
} else {
String issueDate = dateMathString("now", now);
String expiryDate = dateMathString("now+10d/d", now);
return new LicenseSpec(uid, feature, issueDate, expiryDate, type, subscriptionType, issuedTo, issuer, maxNodes);
}
}
public static String generateLicenseSpecString(List<LicenseSpec> licenseSpecs) throws IOException {
XContentBuilder licenses = jsonBuilder();
licenses.startObject();
licenses.startArray("licenses");
for (LicenseSpec licenseSpec : licenseSpecs) {
licenses.startObject()
.field("uid", licenseSpec.uid)
.field("type", licenseSpec.type)
.field("subscription_type", licenseSpec.subscriptionType)
.field("issued_to", licenseSpec.issuedTo)
.field("issuer", licenseSpec.issuer)
.field("feature", licenseSpec.feature)
.field("max_nodes", licenseSpec.maxNodes);
if (licenseSpec.issueDate != null) {
licenses.field("issue_date", licenseSpec.issueDate);
} else {
licenses.field("issue_date_in_millis", licenseSpec.issueDateInMillis);
}
if (licenseSpec.expiryDate != null) {
licenses.field("expiry_date", licenseSpec.expiryDate);
} else {
licenses.field("expiry_date_in_millis", licenseSpec.expiryDateInMillis);
}
licenses.endObject();
}
licenses.endArray();
licenses.endObject();
return licenses.string();
}
public static void assertLicenseSpec(LicenseSpec spec, License license) {
MatcherAssert.assertThat(license.uid(), equalTo(spec.uid));
MatcherAssert.assertThat(license.feature(), equalTo(spec.feature));
MatcherAssert.assertThat(license.issuedTo(), equalTo(spec.issuedTo));
MatcherAssert.assertThat(license.issuer(), equalTo(spec.issuer));
MatcherAssert.assertThat(license.type(), equalTo(spec.type));
MatcherAssert.assertThat(license.subscriptionType(), equalTo(spec.subscriptionType));
MatcherAssert.assertThat(license.maxNodes(), equalTo(spec.maxNodes));
if (spec.issueDate != null) {
MatcherAssert.assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay(spec.issueDate)));
} else {
MatcherAssert.assertThat(license.issueDate(), equalTo(spec.issueDateInMillis));
}
if (spec.expiryDate != null) {
MatcherAssert.assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay(spec.expiryDate)));
} else {
MatcherAssert.assertThat(license.expiryDate(), equalTo(spec.expiryDateInMillis));
}
}
public static class LicenseSpec {
public final String feature;
public final String issueDate;
public final long issueDateInMillis;
public final String expiryDate;
public final long expiryDateInMillis;
public final String uid;
public final String type;
public final String subscriptionType;
public final String issuedTo;
public final String issuer;
public final int maxNodes;
public LicenseSpec(String feature, String issueDate, String expiryDate) {
this(UUID.randomUUID().toString(), feature, issueDate, expiryDate, "trial", "none", "customer", "elasticsearch", 5);
}
public LicenseSpec(String uid, String feature, long issueDateInMillis, long expiryDateInMillis, String type,
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
this.feature = feature;
this.issueDateInMillis = issueDateInMillis;
this.issueDate = null;
this.expiryDateInMillis = expiryDateInMillis;
this.expiryDate = null;
this.uid = uid;
this.type = type;
this.subscriptionType = subscriptionType;
this.issuedTo = issuedTo;
this.issuer = issuer;
this.maxNodes = maxNodes;
}
public LicenseSpec(String uid, String feature, String issueDate, String expiryDate, String type,
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
this.feature = feature;
this.issueDate = issueDate;
this.issueDateInMillis = -1;
this.expiryDate = expiryDate;
this.expiryDateInMillis = -1;
this.uid = uid;
this.type = type;
this.subscriptionType = subscriptionType;
this.issuedTo = issuedTo;
this.issuer = issuer;
this.maxNodes = maxNodes;
}
}
}

View File

@ -1,100 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor.tools;
import org.apache.commons.cli.MissingOptionException;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool.KeyGenerator;
import org.junit.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.elasticsearch.common.cli.CliTool.Command;
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.core.IsEqual.equalTo;
public class KeyPairGenerationToolTests extends CliToolTestCase {
@Test
public void testParsingMissingPath() throws Exception {
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
Path tempFile = createTempFile();
try {
keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, args(
"--privateKeyPath " + tempFile.toAbsolutePath()));
fail("no public key path provided");
} catch (MissingOptionException e) {
assertThat(e.getMessage(), containsString("pub"));
}
try {
keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, args(
"--publicKeyPath " + tempFile.toAbsolutePath()));
fail("no private key path provided");
} catch (MissingOptionException e) {
assertThat(e.getMessage(), containsString("pri"));
}
}
@Test
public void testParsingNeverOverrideKey() throws Exception {
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
Path tempFile = createTempFile();
Path tempFile2 = createTempFile();
String nonExistentFilePath = tempFile2.toAbsolutePath().toString();
Files.delete(tempFile2);
assertThat(Files.exists(tempFile2), equalTo(false));
Command command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] {"--privateKeyPath", tempFile.toAbsolutePath().toString(),
"--publicKeyPath", nonExistentFilePath });
assertThat(command, instanceOf(Command.Exit.class));
Command.Exit exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] {"--publicKeyPath", tempFile.toAbsolutePath().toString(),
"--privateKeyPath", nonExistentFilePath });
assertThat(command, instanceOf(Command.Exit.class));
exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
}
@Test
public void testToolSimple() throws Exception {
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
Path publicKeyFilePath = createTempFile().toAbsolutePath();
Path privateKeyFilePath = createTempFile().toAbsolutePath();
Settings settings = Settings.builder().put("path.home", createTempDir("KeyPairGenerationToolTests")).build();
Files.delete(publicKeyFilePath);
Files.delete(privateKeyFilePath);
assertThat(Files.exists(publicKeyFilePath), equalTo(false));
assertThat(Files.exists(privateKeyFilePath), equalTo(false));
Command command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] { "--privateKeyPath", privateKeyFilePath.toString(),
"--publicKeyPath", publicKeyFilePath.toString() });
assertThat(command, instanceOf(KeyGenerator.class));
KeyGenerator keyGenerator = (KeyGenerator) command;
assertThat(keyGenerator.privateKeyPath, equalTo(privateKeyFilePath));
assertThat(keyGenerator.publicKeyPath, equalTo(publicKeyFilePath));
assertThat(Files.exists(publicKeyFilePath), equalTo(false));
assertThat(Files.exists(privateKeyFilePath), equalTo(false));
assertThat(keyGenerator.execute(settings, new Environment(settings)), equalTo(ExitStatus.OK));
assertThat(Files.exists(publicKeyFilePath), equalTo(true));
assertThat(Files.exists(privateKeyFilePath), equalTo(true));
Files.delete(publicKeyFilePath);
Files.delete(privateKeyFilePath);
}
}

View File

@ -1,197 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor.tools;
import org.apache.commons.cli.MissingOptionException;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.licensor.TestUtils;
import org.junit.Before;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import static org.elasticsearch.common.cli.CliTool.Command;
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
import static org.elasticsearch.license.licensor.tools.LicenseGeneratorTool.LicenseGenerator;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.core.IsEqual.equalTo;
public class LicenseGenerationToolTests extends CliToolTestCase {
protected Path pubKeyPath = null;
protected Path priKeyPath = null;
@Before
public void setup() throws Exception {
logger.error("project.basedir [{}]", System.getProperty("project.basedir"));
pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
}
@Test
public void testParsingNonExistentKeyFile() throws Exception {
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
new String[] {"--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
"--publicKeyPath", pubKeyPath.toString().concat("invalid"),
"--privateKeyPath", priKeyPath.toString() });
assertThat(command, instanceOf(Command.Exit.class));
Command.Exit exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
new String[] {"--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
"--privateKeyPath", priKeyPath.toString().concat("invalid"),
"--publicKeyPath", pubKeyPath.toString() });
assertThat(command, instanceOf(Command.Exit.class));
exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
}
@Test
public void testParsingMissingLicenseSpec() throws Exception {
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
new String[] { "--publicKeyPath", pubKeyPath.toString(),
"--privateKeyPath", priKeyPath.toString() });
assertThat(command, instanceOf(Command.Exit.class));
Command.Exit exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
}
@Test
public void testParsingMissingArgs() throws Exception {
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
boolean pubKeyMissing = randomBoolean();
try {
licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
new String[] { "--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
((pubKeyMissing) ? "--privateKeyPath" : "--publicKeyPath"),
((pubKeyMissing) ? priKeyPath.toString() : pubKeyPath.toString()) });
fail("missing argument: " + ((pubKeyMissing) ? "publicKeyPath" : "privateKeyPath") + " should throw an exception");
} catch (MissingOptionException e) {
assertThat(e.getMessage(), containsString((pubKeyMissing) ? "pub" : "pri"));
}
}
@Test
public void testParsingSimple() throws Exception {
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
new String[]{"--license", TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)),
"--publicKeyPath", pubKeyPath.toString(),
"--privateKeyPath", priKeyPath.toString() });
assertThat(command, instanceOf(LicenseGenerator.class));
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(1));
License outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
}
@Test
public void testParsingLicenseFile() throws Exception {
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec();
Path tempFile = createTempFile();
Files.write(tempFile, TestUtils.generateLicenseSpecString(Arrays.asList(inputLicenseSpec)).getBytes(StandardCharsets.UTF_8));
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
new String[] { "--licenseFile", tempFile.toAbsolutePath().toString(),
"--publicKeyPath", pubKeyPath.toString(),
"--privateKeyPath", priKeyPath.toString() });
assertThat(command, instanceOf(LicenseGenerator.class));
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(1));
License outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
}
@Test
public void testParsingMultipleLicense() throws Exception {
int n = randomIntBetween(2, 5);
Map<String, TestUtils.LicenseSpec> inputLicenseSpecs = new HashMap<>();
for (int i = 0; i < n; i++) {
TestUtils.LicenseSpec licenseSpec = TestUtils.generateRandomLicenseSpec();
inputLicenseSpecs.put(licenseSpec.feature, licenseSpec);
}
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
new String[] { "--license", TestUtils.generateLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())),
"--publicKeyPath", pubKeyPath.toString(),
"--privateKeyPath", priKeyPath.toString() });
assertThat(command, instanceOf(LicenseGenerator.class));
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(inputLicenseSpecs.size()));
for (License outputLicenseSpec : licenseGenerator.licenseSpecs) {
TestUtils.LicenseSpec inputLicenseSpec = inputLicenseSpecs.get(outputLicenseSpec.feature());
assertThat(inputLicenseSpec, notNullValue());
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
}
}
@Test
public void testTool() throws Exception {
int n = randomIntBetween(1, 5);
Map<String, TestUtils.LicenseSpec> inputLicenseSpecs = new HashMap<>();
for (int i = 0; i < n; i++) {
TestUtils.LicenseSpec licenseSpec = TestUtils.generateRandomLicenseSpec();
inputLicenseSpecs.put(licenseSpec.feature, licenseSpec);
}
List<License> licenseSpecs = Licenses.fromSource(TestUtils.generateLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())).getBytes(StandardCharsets.UTF_8), false);
String output = runLicenseGenerationTool(pubKeyPath, priKeyPath, new HashSet<>(licenseSpecs), ExitStatus.OK);
List<License> outputLicenses = Licenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
assertThat(outputLicenses.size(), equalTo(inputLicenseSpecs.size()));
for (License outputLicense : outputLicenses) {
TestUtils.LicenseSpec inputLicenseSpec = inputLicenseSpecs.get(outputLicense.feature());
assertThat(inputLicenseSpec, notNullValue());
TestUtils.assertLicenseSpec(inputLicenseSpec, outputLicense);
}
}
private String runLicenseGenerationTool(Path pubKeyPath, Path priKeyPath, Set<License> licenseSpecs, ExitStatus expectedExitStatus) throws Exception {
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
Settings settings = Settings.builder().put("path.home", createTempDir("LicenseGenerationToolTests")).build();
LicenseGenerator licenseGenerator = new LicenseGenerator(outputTerminal, pubKeyPath, priKeyPath, licenseSpecs);
assertThat(execute(licenseGenerator, settings), equalTo(expectedExitStatus));
assertThat(outputTerminal.getTerminalOutput().size(), equalTo(1));
return outputTerminal.getTerminalOutput().get(0);
}
private ExitStatus execute(Command cmd, Settings settings) throws Exception {
Environment env = new Environment(settings);
return cmd.execute(settings, env);
}
}

View File

@ -1,203 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor.tools;
import org.apache.commons.cli.MissingOptionException;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.licensor.AbstractLicensingTestBase;
import org.elasticsearch.license.licensor.TestUtils;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import static org.elasticsearch.common.cli.CliTool.Command;
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
import static org.elasticsearch.license.licensor.tools.LicenseVerificationTool.LicenseVerifier;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.IsEqual.equalTo;
public class LicenseVerificationToolTests extends CliToolTestCase {
@Test
public void testParsingMissingLicense() throws Exception {
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, args(" --publicKeyPath " + path));
assertThat(command, instanceOf(Command.Exit.class));
Command.Exit exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
}
@Test
public void testParsingMissingPublicKeyPath() throws Exception {
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
License inputLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1",
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
try {
licenseVerificationTool.parse(LicenseVerificationTool.NAME,
args("--license " + TestUtils.dumpLicense(inputLicense)));
} catch (MissingOptionException e) {
assertThat(e.getMessage(), containsString("pub"));
}
}
@Test
public void testParsingNonExistentPublicKeyPath() throws Exception {
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, args(" --publicKeyPath "
+ path.toString().concat(".invalid")));
assertThat(command, instanceOf(Command.Exit.class));
Command.Exit exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
}
@Test
public void testParsingSimple() throws Exception {
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
License inputLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1",
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
args("--license " + TestUtils.dumpLicense(inputLicense)
+ " --publicKeyPath " + getDataPath(TestUtils.PUBLIC_KEY_RESOURCE)));
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(licenseVerifier.licenses.size(), equalTo(1));
License outputLicense = licenseVerifier.licenses.iterator().next();
TestUtils.isSame(inputLicense, outputLicense);
}
@Test
public void testParsingLicenseFile() throws Exception {
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
License inputLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1",
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
new String[] { "--licenseFile", dumpLicenseAsFile(inputLicense),
"--publicKeyPath", getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString() });
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(licenseVerifier.licenses.size(), equalTo(1));
License outputLicense = licenseVerifier.licenses.iterator().next();
TestUtils.isSame(inputLicense, outputLicense);
}
@Test
public void testParsingMultipleLicense() throws Exception {
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
int n = randomIntBetween(2, 5);
Map<String, License> inputLicenses = new HashMap<>();
for (int i = 0; i < n; i++) {
License license = AbstractLicensingTestBase.generateSignedLicense("feature__" + i,
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
inputLicenses.put(license.feature(), license);
}
StringBuilder argsBuilder = new StringBuilder();
for (License inputLicense : inputLicenses.values()) {
argsBuilder.append(" --license ")
.append(TestUtils.dumpLicense(inputLicense));
}
argsBuilder.append(" --publicKeyPath ").append(getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString());
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, args(argsBuilder.toString()));
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(licenseVerifier.licenses.size(), equalTo(inputLicenses.size()));
for (License outputLicense : licenseVerifier.licenses) {
License inputLicense = inputLicenses.get(outputLicense.feature());
assertThat(inputLicense, notNullValue());
TestUtils.isSame(inputLicense, outputLicense);
}
}
@Test
public void testToolSimple() throws Exception {
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
int n = randomIntBetween(2, 5);
Map<String, License> inputLicenses = new HashMap<>();
for (int i = 0; i < n; i++) {
License license = AbstractLicensingTestBase.generateSignedLicense("feature__" + i,
TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
inputLicenses.put(license.feature(), license);
}
String output = runLicenseVerificationTool(new HashSet<>(inputLicenses.values()), getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.OK);
List<License> outputLicenses = Licenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
assertThat(outputLicenses.size(), equalTo(inputLicenses.size()));
for (License outputLicense : outputLicenses) {
License inputLicense = inputLicenses.get(outputLicense.feature());
assertThat(inputLicense, notNullValue());
TestUtils.isSame(inputLicense, outputLicense);
}
}
@Test
public void testToolInvalidLicense() throws Exception {
Path pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Path priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
License signedLicense = AbstractLicensingTestBase.generateSignedLicense("feature__1"
, TimeValue.timeValueHours(1), pubKeyPath.toString(), priKeyPath.toString());
License tamperedLicense = License.builder()
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + randomIntBetween(1, 1000)).build();
runLicenseVerificationTool(Collections.singleton(tamperedLicense), getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.DATA_ERROR);
}
private String dumpLicenseAsFile(License license) throws Exception {
Path tempFile = createTempFile();
Files.write(tempFile, TestUtils.dumpLicense(license).getBytes(StandardCharsets.UTF_8));
return tempFile.toAbsolutePath().toString();
}
private String runLicenseVerificationTool(Set<License> licenses, Path publicKeyPath, ExitStatus expectedExitStatus) throws Exception {
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
Settings settings = Settings.builder().put("path.home", createTempDir("LicenseVerificationToolTests")).build();
LicenseVerifier licenseVerifier = new LicenseVerifier(outputTerminal, licenses, publicKeyPath);
assertThat(execute(licenseVerifier, settings), equalTo(expectedExitStatus));
if (expectedExitStatus == ExitStatus.OK) {
assertThat(outputTerminal.getTerminalOutput().size(), equalTo(1));
return outputTerminal.getTerminalOutput().get(0);
} else {
return null;
}
}
private ExitStatus execute(Command cmd, Settings settings) throws Exception {
Environment env = new Environment(settings);
return cmd.execute(settings, env);
}
}

View File

@ -1,11 +0,0 @@
es.logger.level=INFO
log4j.rootLogger=${es.logger.level}, out
log4j.logger.org.apache.http=INFO, out
log4j.additivity.org.apache.http=false
log4j.logger.org.elasticsearch.license=TRACE
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n

View File

@ -1,3 +0,0 @@
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßÇ<09>ş]d™}o§OčľId®Č
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{Ľ˛Áô­śátť¤7ůřĂę #˛Vó»ktwmŚ]ĎLőŁz"| QlźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5ŻŤĘĘ7%ŘęD
xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|Ţ ÓŞĎÁ*ń§D`<60>Ú?ůxU/3>x­UÓ“+ č

View File

@ -1 +0,0 @@
/eclipse-build/

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>elasticsearch-license</artifactId>
<groupId>org.elasticsearch</groupId>
<version>2.0.0.beta1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>elasticsearch-license-plugin-api</artifactId>
<properties>
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
<!-- we aren't really a plugin... -->
<skip.integ.tests>true</skip.integ.tests>
</properties>
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license-licensor</artifactId>
<version>2.0.0.beta1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license-core</artifactId>
<version>2.0.0.beta1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

View File

@ -1,191 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.Version;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.license.core.License;
import java.io.IOException;
import java.io.Serializable;
@SuppressWarnings("deprecation")
public class LicenseVersion implements Serializable {
// The logic for ID is: XXYYZZAA, where XX is major version, YY is minor version, ZZ is revision, and AA is Beta/RC indicator
// AA values below 50 are beta builds, and below 99 are RC builds, with 99 indicating a release
// the (internal) format of the id is there so we can easily do after/before checks on the id
public static final int V_1_0_0_ID = /*00*/1000099;
public static final int V_2_0_0_beta1_ID = /*00*/2000001;
public static final LicenseVersion V_1_0_0 = new LicenseVersion(V_1_0_0_ID, false, License.VERSION_START, Version.V_1_4_0_Beta1);
public static final LicenseVersion V_2_0_0_beta1 = new LicenseVersion(V_2_0_0_beta1_ID, true, License.VERSION_START, Version.V_2_0_0_beta1);
public static final LicenseVersion CURRENT = V_2_0_0_beta1;
public static LicenseVersion readVersion(StreamInput in) throws IOException {
return fromId(in.readVInt());
}
public static LicenseVersion fromId(int id) {
switch (id) {
case V_1_0_0_ID:
return V_1_0_0;
default:
return new LicenseVersion(id, null, License.VERSION_CURRENT, Version.CURRENT);
}
}
public static void writeVersion(LicenseVersion version, StreamOutput out) throws IOException {
out.writeVInt(version.id);
}
/**
* Returns the smallest version between the 2.
*/
public static LicenseVersion smallest(LicenseVersion version1, LicenseVersion version2) {
return version1.id < version2.id ? version1 : version2;
}
/**
* Returns the version given its string representation, current version if the argument is null or empty
*/
public static LicenseVersion fromString(String version) {
if (!Strings.hasLength(version)) {
return LicenseVersion.CURRENT;
}
String[] parts = version.split("\\.|\\-");
if (parts.length < 3 || parts.length > 4) {
throw new IllegalArgumentException("the version needs to contain major, minor and revision, and optionally the build");
}
try {
//we reverse the version id calculation based on some assumption as we can't reliably reverse the modulo
int major = Integer.parseInt(parts[0]) * 1000000;
int minor = Integer.parseInt(parts[1]) * 10000;
int revision = Integer.parseInt(parts[2]) * 100;
int build = 99;
if (parts.length == 4) {
String buildStr = parts[3];
if (buildStr.startsWith("beta")) {
build = Integer.parseInt(buildStr.substring(4));
}
if (buildStr.startsWith("rc")) {
build = Integer.parseInt(buildStr.substring(2)) + 50;
}
}
return fromId(major + minor + revision + build);
} catch(NumberFormatException e) {
throw new IllegalArgumentException("unable to parse version " + version, e);
}
}
public final int id;
public final byte major;
public final byte minor;
public final byte revision;
public final byte build;
public final Boolean snapshot;
public final int minSignatureVersion;
public final Version minEsCompatibilityVersion;
LicenseVersion(int id, @Nullable Boolean snapshot, int minSignatureVersion, Version minEsCompatibilityVersion) {
this.id = id;
this.major = (byte) ((id / 1000000) % 100);
this.minor = (byte) ((id / 10000) % 100);
this.revision = (byte) ((id / 100) % 100);
this.build = (byte) (id % 100);
this.snapshot = snapshot;
this.minSignatureVersion = minSignatureVersion;
this.minEsCompatibilityVersion = minEsCompatibilityVersion;
}
public boolean snapshot() {
return snapshot != null && snapshot;
}
public boolean after(LicenseVersion version) {
return version.id < id;
}
public boolean before(LicenseVersion version) {
return version.id > id;
}
/**
* Returns the minimum compatible version based on the current
* version. Ie a node needs to have at least the return version in order
* to communicate with a node running the current version. The returned version
* is in most of the cases the smallest major version release unless the current version
* is a beta or RC release then the version itself is returned.
*/
public LicenseVersion minimumCompatibilityVersion() {
return LicenseVersion.smallest(this, fromId(major * 1000000 + 99));
}
/**
* @return The minimum elasticsearch version this license version is compatible with.
*/
public Version minimumEsCompatiblityVersion() {
return minEsCompatibilityVersion;
}
/**
* @return The minimum license signature version this license plugin is compatible with.
*/
public int minimumSignatureVersion() {
return minSignatureVersion;
}
/**
* Just the version number (without -SNAPSHOT if snapshot).
*/
public String number() {
StringBuilder sb = new StringBuilder();
sb.append(major).append('.').append(minor).append('.').append(revision);
if (build < 50) {
sb.append("-beta").append(build);
} else if (build < 99) {
sb.append("-rc").append(build - 50);
}
return sb.toString();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(number());
if (snapshot()) {
sb.append("-SNAPSHOT");
}
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LicenseVersion that = (LicenseVersion) o;
if (id != that.id) return false;
return true;
}
@Override
public int hashCode() {
return id;
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.core;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.rest.RestStatus;
public class LicenseUtils {
public final static String EXPIRED_FEATURE_HEADER = "es.license.expired.feature";
/**
* Exception to be thrown when a feature action requires a valid license, but license
* has expired
*
* <code>feature</code> accessible through {@link #EXPIRED_FEATURE_HEADER} in the
* exception's rest header
*/
public static ElasticsearchSecurityException newExpirationException(String feature) {
ElasticsearchSecurityException e = new ElasticsearchSecurityException("license expired for feature [{}]", RestStatus.UNAUTHORIZED, feature);
e.addHeader(EXPIRED_FEATURE_HEADER, feature);
return e;
}
}

View File

@ -1,163 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.core;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import java.util.Collection;
public interface LicensesClientService {
public interface Listener {
/**
* Called to enable a feature
*/
public void onEnabled(License license);
/**
* Called to disable a feature
*/
public void onDisabled(License license);
}
/**
* Registers a feature for licensing
*
* @param feature - name of the feature to register (must be in sync with license Generator feature name)
* @param trialLicenseOptions - Trial license specification used to generate a one-time trial license for the feature;
* use <code>null</code> if no trial license should be generated for the feature
* @param expirationCallbacks - A collection of Pre and/or Post expiration callbacks
* @param listener - used to notify on feature enable/disable
*/
void register(String feature, TrialLicenseOptions trialLicenseOptions, Collection<ExpirationCallback> expirationCallbacks, Listener listener);
public static class TrialLicenseOptions {
final TimeValue duration;
final int maxNodes;
public TrialLicenseOptions(TimeValue duration, int maxNodes) {
this.duration = duration;
this.maxNodes = maxNodes;
}
}
public static interface LicenseCallback {
void on(License license, ExpirationStatus status);
}
public static abstract class ExpirationCallback implements LicenseCallback {
public enum Orientation { PRE, POST }
public static abstract class Pre extends ExpirationCallback {
/**
* Callback schedule prior to license expiry
*
* @param min latest relative time to execute before license expiry
* @param max earliest relative time to execute before license expiry
* @param frequency interval between execution
*/
public Pre(TimeValue min, TimeValue max, TimeValue frequency) {
super(Orientation.PRE, min, max, frequency);
}
@Override
public boolean matches(long expirationDate, long now) {
long expiryDuration = expirationDate - now;
if (expiryDuration > 0l) {
if (expiryDuration <= max().getMillis()) {
return expiryDuration >= min().getMillis();
}
}
return false;
}
}
public static abstract class Post extends ExpirationCallback {
/**
* Callback schedule after license expiry
*
* @param min earliest relative time to execute after license expiry
* @param max latest relative time to execute after license expiry
* @param frequency interval between execution
*/
public Post(TimeValue min, TimeValue max, TimeValue frequency) {
super(Orientation.POST, min, max, frequency);
}
@Override
public boolean matches(long expirationDate, long now) {
long postExpiryDuration = now - expirationDate;
if (postExpiryDuration > 0l) {
if (postExpiryDuration <= max().getMillis()) {
return postExpiryDuration >= min().getMillis();
}
}
return false;
}
}
private final Orientation orientation;
private final TimeValue min;
private final TimeValue max;
private final TimeValue frequency;
private ExpirationCallback(Orientation orientation, TimeValue min, TimeValue max, TimeValue frequency) {
this.orientation = orientation;
this.min = (min == null) ? TimeValue.timeValueMillis(0) : min;
this.max = (max == null) ? TimeValue.timeValueMillis(Long.MAX_VALUE) : max;
this.frequency = frequency;
if (frequency == null) {
throw new IllegalArgumentException("frequency can not be null");
}
}
public Orientation orientation() {
return orientation;
}
public TimeValue min() {
return min;
}
public TimeValue max() {
return max;
}
public TimeValue frequency() {
return frequency;
}
public abstract boolean matches(long expirationDate, long now);
}
public static class ExpirationStatus {
private final boolean expired;
private final TimeValue time;
ExpirationStatus(boolean expired, TimeValue time) {
this.expired = expired;
this.time = time;
}
public boolean expired() {
return expired;
}
public TimeValue time() {
return time;
}
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
*
*/
public class LicenseVersionTests extends ElasticsearchTestCase {
@Test
public void testStrings() throws Exception {
boolean beta = randomBoolean();
int buildNumber = beta ? randomIntBetween(0, 49) : randomIntBetween(0, 48);
int major = randomIntBetween(0, 20);
int minor = randomIntBetween(0, 20);
int revision = randomIntBetween(0, 20);
String build = buildNumber == 0 ? "" :
beta ? "-beta" + buildNumber : "-rc" + buildNumber;
String versionName = new StringBuilder()
.append(major)
.append(".").append(minor)
.append(".").append(revision)
.append(build).toString();
LicenseVersion version = LicenseVersion.fromString(versionName);
logger.info("version: {}", versionName);
assertThat(version.major, is((byte) major));
assertThat(version.minor, is((byte) minor));
assertThat(version.revision, is((byte) revision));
if (buildNumber == 0) {
assertThat(version.build, is((byte) 99));
} else if (beta) {
assertThat(version.build, is((byte) buildNumber));
} else {
assertThat(version.build, is((byte) (buildNumber + 50)));
}
assertThat(version.number(), equalTo(versionName));
}
}

1
plugin/.gitignore vendored
View File

@ -1 +0,0 @@
/eclipse-build/

View File

@ -1,125 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>elasticsearch-license</artifactId>
<groupId>org.elasticsearch</groupId>
<version>2.0.0.beta1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>elasticsearch-license-plugin</artifactId>
<properties>
<!-- TODO: do we want to always enforce non-default keys
-->
<keys.path>${basedir}/src/test/resources</keys.path>
<license.basedir combine.self="override">${project.parent.basedir}</license.basedir>
<tests.rest.suite>license</tests.rest.suite>
<tests.rest.load_packaged>false</tests.rest.load_packaged>
</properties>
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license-licensor</artifactId>
<version>2.0.0.beta1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license-core</artifactId>
<version>2.0.0.beta1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license-plugin-api</artifactId>
<version>2.0.0.beta1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>${keys.path}</directory>
<filtering>false</filtering>
<includes>
<include>public.key</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<!-- TODO: do we want to always enforce non-default keys -->
<execution>
<id>enforce-property</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireProperty>
<property>keys.path</property>
<message>"You must set a keys.path property!"</message>
</requireProperty>
<requireFilesExist>
<files>
<file>${keys.path}/public.key</file>
</files>
<message>"public.key file does not exist in ${keys.path} directory!"</message>
</requireFilesExist>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>for-plugin</id>
<phase>prepare-package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.carrotsearch.randomizedtesting</groupId>
<artifactId>junit4-maven-plugin</artifactId>
<executions>
<execution>
<id>tests</id>
<phase>test</phase>
<goals>
<goal>junit4</goal>
</goals>
<configuration>
<heartbeat combine.self="override">40</heartbeat>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,19 +0,0 @@
{
"licenses": {
"documentation": "https://www.elastic.co/guide/en/shield/current/license-management.html",
"methods": ["GET"],
"url": {
"path": "/_licenses",
"paths": ["/_licenses"],
"parts" : {
},
"params": {
"local": {
"type" : "boolean",
"description" : "Return local information, do not retrieve the state from master node (default: false)"
}
}
},
"body": null
}
}

View File

@ -1,4 +0,0 @@
---
"licenses endpoint exists":
- do: {licenses: {}}
- match: {licenses: []}

View File

@ -1,22 +0,0 @@
<?xml version="1.0"?>
<assembly>
<id>plugin</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<includes>
<include>org.elasticsearch:elasticsearch-license-plugin</include>
<include>org.elasticsearch:elasticsearch-license-plugin-api</include>
<include>org.elasticsearch:elasticsearch-license-core</include>
</includes>
<excludes>
<exclude>org.elasticsearch:elasticsearch</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@ -1,21 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Scopes;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.plugin.core.LicensesClientService;
import org.elasticsearch.license.plugin.core.LicensesService;
public class LicenseModule extends AbstractModule {
@Override
protected void configure() {
bind(LicenseVerifier.class).in(Scopes.SINGLETON);
bind(LicensesService.class).in(Scopes.SINGLETON);
bind(LicensesClientService.class).to(LicensesService.class).in(Scopes.SINGLETON);
}
}

View File

@ -1,93 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import org.elasticsearch.action.ActionModule;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Module;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
import org.elasticsearch.license.plugin.action.delete.TransportDeleteLicenseAction;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
import org.elasticsearch.license.plugin.action.get.TransportGetLicenseAction;
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
import org.elasticsearch.license.plugin.action.put.TransportPutLicenseAction;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.license.plugin.rest.RestDeleteLicenseAction;
import org.elasticsearch.license.plugin.rest.RestGetLicenseAction;
import org.elasticsearch.license.plugin.rest.RestPutLicenseAction;
import org.elasticsearch.plugins.AbstractPlugin;
import org.elasticsearch.rest.RestModule;
import java.util.Collection;
public class LicensePlugin extends AbstractPlugin {
public static final String NAME = "license";
private final boolean isEnabled;
static {
MetaData.registerPrototype(LicensesMetaData.TYPE, LicensesMetaData.PROTO);
}
@Inject
public LicensePlugin(Settings settings) {
if (DiscoveryNode.clientNode(settings)) {
// Enable plugin only on node clients
this.isEnabled = "node".equals(settings.get(Client.CLIENT_TYPE_SETTING));
} else {
this.isEnabled = true;
}
}
@Override
public String name() {
return NAME;
}
@Override
public String description() {
return "Internal Elasticsearch Licensing Plugin";
}
public void onModule(RestModule module) {
// Register REST endpoint
module.addRestAction(RestPutLicenseAction.class);
module.addRestAction(RestGetLicenseAction.class);
module.addRestAction(RestDeleteLicenseAction.class);
}
public void onModule(ActionModule module) {
module.registerAction(PutLicenseAction.INSTANCE, TransportPutLicenseAction.class);
module.registerAction(GetLicenseAction.INSTANCE, TransportGetLicenseAction.class);
module.registerAction(DeleteLicenseAction.INSTANCE, TransportDeleteLicenseAction.class);
}
@Override
public Collection<Class<? extends LifecycleComponent>> services() {
Collection<Class<? extends LifecycleComponent>> services = Lists.newArrayList();
if (isEnabled) {
services.add(LicensesService.class);
}
return services;
}
@Override
public Collection<Class<? extends Module>> modules() {
if (isEnabled) {
return ImmutableSet.<Class<? extends Module>>of(LicenseModule.class);
}
return ImmutableSet.of();
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.delete;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
public class DeleteLicenseAction extends Action<DeleteLicenseRequest, DeleteLicenseResponse, DeleteLicenseRequestBuilder> {
public static final DeleteLicenseAction INSTANCE = new DeleteLicenseAction();
public static final String NAME = "cluster:admin/plugin/license/delete";
private DeleteLicenseAction() {
super(NAME);
}
@Override
public DeleteLicenseResponse newResponse() {
return new DeleteLicenseResponse();
}
@Override
public DeleteLicenseRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new DeleteLicenseRequestBuilder(client, this);
}
}

View File

@ -1,56 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.delete;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.master.AcknowledgedRequest;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class DeleteLicenseRequest extends AcknowledgedRequest<DeleteLicenseRequest> {
private String[] features;
public DeleteLicenseRequest() {
}
public DeleteLicenseRequest(String... features) {
this.features = features;
}
public void features(Set<String> features) {
this.features = features.toArray(new String[features.size()]);
}
public Set<String> features() {
return new HashSet<>(Arrays.asList(features));
}
@Override
public ActionRequestValidationException validate() {
return null;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
features = in.readStringArray();
readTimeout(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeStringArray(features);
writeTimeout(out);
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.delete;
import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder;
import org.elasticsearch.client.ClusterAdminClient;
import org.elasticsearch.client.ElasticsearchClient;
import java.util.Set;
public class DeleteLicenseRequestBuilder extends AcknowledgedRequestBuilder<DeleteLicenseRequest, DeleteLicenseResponse, DeleteLicenseRequestBuilder> {
/**
* Creates new get licenses request builder
*
* @param client elasticsearch client
*/
public DeleteLicenseRequestBuilder(ElasticsearchClient client, DeleteLicenseAction action) {
super(client, action, new DeleteLicenseRequest());
}
public DeleteLicenseRequestBuilder setFeatures(Set<String> features) {
request.features(features);
return this;
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.delete;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import java.io.IOException;
public class DeleteLicenseResponse extends AcknowledgedResponse {
DeleteLicenseResponse() {
}
DeleteLicenseResponse(boolean acknowledged) {
super(acknowledged);
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
readAcknowledged(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
writeAcknowledged(out);
}
}

View File

@ -1,67 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.delete;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import static org.elasticsearch.license.plugin.core.LicensesService.DeleteLicenseRequestHolder;
public class TransportDeleteLicenseAction extends TransportMasterNodeAction<DeleteLicenseRequest, DeleteLicenseResponse> {
private final LicensesManagerService licensesManagerService;
@Inject
public TransportDeleteLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, LicensesManagerService licensesManagerService,
ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
super(settings, DeleteLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, DeleteLicenseRequest.class);
this.licensesManagerService = licensesManagerService;
}
@Override
protected String executor() {
return ThreadPool.Names.MANAGEMENT;
}
@Override
protected DeleteLicenseResponse newResponse() {
return new DeleteLicenseResponse();
}
@Override
protected ClusterBlockException checkBlock(DeleteLicenseRequest request, ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
}
@Override
protected void masterOperation(final DeleteLicenseRequest request, ClusterState state, final ActionListener<DeleteLicenseResponse> listener) throws ElasticsearchException {
final DeleteLicenseRequestHolder requestHolder = new DeleteLicenseRequestHolder(request, "delete licenses []");
licensesManagerService.removeLicenses(requestHolder, new ActionListener<ClusterStateUpdateResponse>() {
@Override
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
listener.onResponse(new DeleteLicenseResponse(clusterStateUpdateResponse.isAcknowledged()));
}
@Override
public void onFailure(Throwable e) {
listener.onFailure(e);
}
});
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.get;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
public class GetLicenseAction extends Action<GetLicenseRequest, GetLicenseResponse, GetLicenseRequestBuilder> {
public static final GetLicenseAction INSTANCE = new GetLicenseAction();
public static final String NAME = "cluster:admin/plugin/license/get";
private GetLicenseAction() {
super(NAME);
}
@Override
public GetLicenseResponse newResponse() {
return new GetLicenseResponse();
}
@Override
public GetLicenseRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new GetLicenseRequestBuilder(client, this);
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.get;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.master.MasterNodeReadRequest;
public class GetLicenseRequest extends MasterNodeReadRequest<GetLicenseRequest> {
public GetLicenseRequest() {
}
@Override
public ActionRequestValidationException validate() {
return null;
}
}

View File

@ -1,21 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.get;
import org.elasticsearch.action.support.master.MasterNodeReadOperationRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
public class GetLicenseRequestBuilder extends MasterNodeReadOperationRequestBuilder<GetLicenseRequest, GetLicenseResponse, GetLicenseRequestBuilder> {
/**
* Creates new get licenses request builder
*
* @param client elasticsearch client
*/
public GetLicenseRequestBuilder(ElasticsearchClient client, GetLicenseAction action) {
super(client, action, new GetLicenseRequest());
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.get;
import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class GetLicenseResponse extends ActionResponse {
private List<License> licenses = new ArrayList<>();
GetLicenseResponse() {
}
GetLicenseResponse(List<License> licenses) {
this.licenses = licenses;
}
public List<License> licenses() {
return licenses;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
licenses = Licenses.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
Licenses.writeTo(licenses, out);
}
}

View File

@ -1,53 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.get;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.TransportMasterNodeReadAction;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
public class TransportGetLicenseAction extends TransportMasterNodeReadAction<GetLicenseRequest, GetLicenseResponse> {
private final LicensesManagerService licensesManagerService;
@Inject
public TransportGetLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService, LicensesManagerService licensesManagerService,
ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
super(settings, GetLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, GetLicenseRequest.class);
this.licensesManagerService = licensesManagerService;
}
@Override
protected String executor() {
return ThreadPool.Names.MANAGEMENT;
}
@Override
protected GetLicenseResponse newResponse() {
return new GetLicenseResponse();
}
@Override
protected ClusterBlockException checkBlock(GetLicenseRequest request, ClusterState state) {
return state.blocks().indexBlockedException(ClusterBlockLevel.METADATA_READ, "");
}
@Override
protected void masterOperation(final GetLicenseRequest request, ClusterState state, final ActionListener<GetLicenseResponse> listener) throws ElasticsearchException {
listener.onResponse(new GetLicenseResponse(licensesManagerService.getLicenses()));
}
}

View File

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.put;
import org.elasticsearch.action.Action;
import org.elasticsearch.client.ElasticsearchClient;
public class PutLicenseAction extends Action<PutLicenseRequest, PutLicenseResponse, PutLicenseRequestBuilder> {
public static final PutLicenseAction INSTANCE = new PutLicenseAction();
public static final String NAME = "cluster:admin/plugin/license/put";
private PutLicenseAction() {
super(NAME);
}
@Override
public PutLicenseResponse newResponse() {
return new PutLicenseResponse();
}
@Override
public PutLicenseRequestBuilder newRequestBuilder(ElasticsearchClient client) {
return new PutLicenseRequestBuilder(client, this);
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.put;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.master.AcknowledgedRequest;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.util.List;
public class PutLicenseRequest extends AcknowledgedRequest<PutLicenseRequest> {
private List<License> licenses;
public PutLicenseRequest() {
}
@Override
public ActionRequestValidationException validate() {
return null;
}
/**
* Parses licenses from json format to an instance of {@link org.elasticsearch.license.core.Licenses}
*
* @param licenseDefinition licenses definition
*/
public PutLicenseRequest licenses(String licenseDefinition) {
try {
return licenses(Licenses.fromSource(licenseDefinition));
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse licenses source", e);
}
}
public PutLicenseRequest licenses(List<License> licenses) {
this.licenses = licenses;
return this;
}
public List<License> licenses() {
return licenses;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
licenses = Licenses.readFrom(in);
readTimeout(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
Licenses.writeTo(licenses, out);
writeTimeout(out);
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.put;
import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder;
import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.license.core.License;
import java.util.List;
/**
* Register license request builder
*/
public class PutLicenseRequestBuilder extends AcknowledgedRequestBuilder<PutLicenseRequest, PutLicenseResponse, PutLicenseRequestBuilder> {
/**
* Constructs register license request
*
* @param client elasticsearch client
*/
public PutLicenseRequestBuilder(ElasticsearchClient client, PutLicenseAction action) {
super(client, action, new PutLicenseRequest());
}
/**
* Sets the license
*
* @param licenses license
* @return this builder
*/
public PutLicenseRequestBuilder setLicense(List<License> licenses) {
request.licenses(licenses);
return this;
}
public PutLicenseRequestBuilder setLicense(String licenseSource) {
request.licenses(licenseSource);
return this;
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.put;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import java.io.IOException;
public class PutLicenseResponse extends AcknowledgedResponse {
private LicensesStatus status;
PutLicenseResponse() {
}
PutLicenseResponse(boolean acknowledged, LicensesStatus status) {
super(acknowledged);
this.status = status;
}
public LicensesStatus status() {
return status;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
readAcknowledged(in);
status = LicensesStatus.fromId(in.readVInt());
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
writeAcknowledged(out);
out.writeVInt(status.id());
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.action.put;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.block.ClusterBlockException;
import org.elasticsearch.cluster.block.ClusterBlockLevel;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService;
import static org.elasticsearch.license.plugin.core.LicensesService.LicensesUpdateResponse;
import static org.elasticsearch.license.plugin.core.LicensesService.PutLicenseRequestHolder;
public class TransportPutLicenseAction extends TransportMasterNodeAction<PutLicenseRequest, PutLicenseResponse> {
private final LicensesManagerService licensesManagerService;
@Inject
public TransportPutLicenseAction(Settings settings, TransportService transportService, ClusterService clusterService,
LicensesManagerService licensesManagerService, ThreadPool threadPool, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) {
super(settings, PutLicenseAction.NAME, transportService, clusterService, threadPool, actionFilters, indexNameExpressionResolver, PutLicenseRequest.class);
this.licensesManagerService = licensesManagerService;
}
@Override
protected String executor() {
return ThreadPool.Names.MANAGEMENT;
}
@Override
protected PutLicenseResponse newResponse() {
return new PutLicenseResponse();
}
@Override
protected ClusterBlockException checkBlock(PutLicenseRequest request, ClusterState state) {
return state.blocks().indexBlockedException(ClusterBlockLevel.METADATA_WRITE, "");
}
@Override
protected void masterOperation(final PutLicenseRequest request, ClusterState state, final ActionListener<PutLicenseResponse> listener) throws ElasticsearchException {
licensesManagerService.registerLicenses(new PutLicenseRequestHolder(request, "put licenses []"), new ActionListener<LicensesUpdateResponse>() {
@Override
public void onResponse(LicensesUpdateResponse licensesUpdateResponse) {
listener.onResponse(new PutLicenseResponse(licensesUpdateResponse.isAcknowledged(), licensesUpdateResponse.status()));
}
@Override
public void onFailure(Throwable e) {
listener.onFailure(e);
}
});
}
}

View File

@ -1,43 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.core;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.common.inject.ImplementedBy;
import org.elasticsearch.license.core.License;
import java.util.List;
import java.util.Set;
import static org.elasticsearch.license.plugin.core.LicensesService.*;
@ImplementedBy(LicensesService.class)
public interface LicensesManagerService {
/**
* Registers new licenses in the cluster
* <p/>
* This method can be only called on the master node. It tries to create a new licenses on the master
* and if provided license(s) is VALID it is added to cluster state metadata {@link org.elasticsearch.license.plugin.core.LicensesMetaData}
*/
public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener<LicensesService.LicensesUpdateResponse> listener);
/**
* Remove only signed license(s) for provided features from the cluster state metadata
*/
public void removeLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener<ClusterStateUpdateResponse> listener);
/**
* @return the set of features that are currently enabled
*/
public Set<String> enabledFeatures();
/**
* @return a list of licenses, contains one license (with the latest expiryDate) per registered features sorted by latest issueDate
*/
public List<License> getLicenses();
}

View File

@ -1,155 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.core;
import com.google.common.collect.ImmutableList;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
/**
* Contains metadata about registered licenses
*/
public class LicensesMetaData extends AbstractDiffable<MetaData.Custom> implements MetaData.Custom {
public static final String TYPE = "licenses";
public static final LicensesMetaData PROTO = new LicensesMetaData(Collections.<License>emptyList(), Collections.<License>emptyList());
private final ImmutableList<License> signedLicenses;
private final ImmutableList<License> trialLicenses;
/**
* Constructs new licenses metadata
*
* @param signedLicenses list of signed Licenses
* @param trialLicenses set of encoded trial licenses
*/
public LicensesMetaData(List<License> signedLicenses, List<License> trialLicenses) {
this.signedLicenses = ImmutableList.copyOf(signedLicenses);
this.trialLicenses = ImmutableList.copyOf(trialLicenses);
}
public List<License> getSignedLicenses() {
return signedLicenses;
}
public List<License> getTrialLicenses() {
return trialLicenses;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LicensesMetaData that = (LicensesMetaData) obj;
return signedLicenses.equals(that.signedLicenses)
&& trialLicenses.equals(that.trialLicenses);
}
@Override
public int hashCode() {
return signedLicenses.hashCode() + 31 * trialLicenses.hashCode();
}
@Override
public String type() {
return TYPE;
}
@Override
public LicensesMetaData fromXContent(XContentParser parser) throws IOException {
List<License> trialLicenses = new ArrayList<>();
List<License> signedLicenses = new ArrayList<>();
XContentParser.Token token;
while (parser.currentToken() != XContentParser.Token.END_OBJECT) {
token = parser.nextToken();
if (token == XContentParser.Token.FIELD_NAME) {
String fieldName = parser.text();
if (fieldName != null) {
if (fieldName.equals(Fields.TRIAL_LICENSES)) {
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
if (parser.currentToken().isValue()) {
trialLicenses.add(TrialLicenseUtils.fromEncodedTrialLicense(parser.text()));
}
}
}
}
if (fieldName.equals(Fields.SIGNED_LICENCES)) {
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
License.Builder builder = License.builder().fromXContent(parser);
signedLicenses.add(builder.build());
}
}
}
}
}
}
return new LicensesMetaData(signedLicenses, trialLicenses);
}
@Override
public EnumSet<MetaData.XContentContext> context() {
return EnumSet.of(MetaData.XContentContext.GATEWAY);
}
@Override
public void writeTo(StreamOutput streamOutput) throws IOException {
Licenses.writeTo(signedLicenses, streamOutput);
streamOutput.writeVInt(trialLicenses.size());
for (License trialLicense : trialLicenses) {
streamOutput.writeString(TrialLicenseUtils.toEncodedTrialLicense(trialLicense));
}
}
@Override
public MetaData.Custom readFrom(StreamInput streamInput) throws IOException {
List<License> signedLicenses = Licenses.readFrom(streamInput);
int numTrialLicenses = streamInput.readVInt();
List<License> trialLicenses = new ArrayList<>(numTrialLicenses);
for (int i = 0; i < numTrialLicenses; i++) {
trialLicenses.add(TrialLicenseUtils.fromEncodedTrialLicense(streamInput.readString()));
}
return new LicensesMetaData(signedLicenses, trialLicenses);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startArray(Fields.TRIAL_LICENSES);
for (License trailLicense : trialLicenses) {
builder.value(TrialLicenseUtils.toEncodedTrialLicense(trailLicense));
}
builder.endArray();
builder.startArray(Fields.SIGNED_LICENCES);
for (License license : signedLicenses) {
license.toXContent(builder, params);
}
builder.endArray();
return builder;
}
private final static class Fields {
private static final String SIGNED_LICENCES = "signed_licenses";
private static final String TRIAL_LICENSES = "trial_licenses";
}
}

View File

@ -1,986 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.core;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import org.apache.lucene.util.CollectionUtil;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.*;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.common.util.concurrent.FutureUtils;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.license.core.Licenses.reduceAndMap;
/**
* Service responsible for managing {@link LicensesMetaData}
* Interfaces through which this is exposed are:
* - LicensesManagerService - responsible for managing signed and one-time-trial licenses
* - LicensesClientService - responsible for feature registration and notification to consumer plugin(s)
* <p/>
* <p/>
* Registration Scheme:
* <p/>
* A consumer plugin (feature) is registered with {@link LicensesClientService#register(String, TrialLicenseOptions, java.util.Collection<ExpirationCallback>, LicensesClientService.Listener)}
* This method can be called at any time during the life-cycle of the consumer plugin.
* If the feature can not be registered immediately, it is queued up and registered on the first clusterChanged event with
* no {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK} block
* Upon successful registration, the feature(s) are notified appropriately using the notification scheme
* <p/>
* <p/>
* Notification Scheme:
* <p/>
* All registered feature(s) are notified using {@link #notifyFeatures(LicensesMetaData)} (depends on the current
* {@link #registeredListeners}). It is idempotent with respect to all the feature listeners.
* <p/>
* The notification scheduling is done by {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} which does the following:
* - calls {@link #notifyFeatures(LicensesMetaData)} to notify all registered feature(s)
* - if there is any license(s) with a future expiry date in the current cluster state:
* - schedules a delayed {@link LicensingClientNotificationJob} on the MIN of all the expiry dates of all the registered feature(s)
* <p/>
* The {@link LicensingClientNotificationJob} calls {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} to schedule
* another delayed {@link LicensingClientNotificationJob} as stated above. It is a no-op in case of a global block on
* {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
* <p/>
* Upon successful registration of a new feature:
* - {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} is called
* <p/>
* Upon clusterChanged():
* - {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} is called if:
* - new trial/signed license(s) are found in the cluster state meta data
* - if new feature(s) are added to the registeredListener
* - if the previous cluster state had a global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
* - no-op in case of global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
*/
@Singleton
public class LicensesService extends AbstractLifecycleComponent<LicensesService> implements ClusterStateListener, LicensesManagerService, LicensesClientService {
public static final String REGISTER_TRIAL_LICENSE_ACTION_NAME = "internal:plugin/license/cluster/register_trial_license";
private final ClusterService clusterService;
private final ThreadPool threadPool;
private final TransportService transportService;
/**
* Currently active consumers to notify to
*/
private final List<ListenerHolder> registeredListeners = new CopyOnWriteArrayList<>();
/**
* Currently pending consumers that has to be registered
*/
private final Queue<ListenerHolder> pendingListeners = new ConcurrentLinkedQueue<>();
/**
* Currently active scheduledNotifications
* All finished notifications will be cleared by {@link #scheduleNextNotification(long)}
*/
private final Queue<ScheduledFuture> scheduledNotifications = new ConcurrentLinkedQueue<>();
/**
* Currently active event notifications for every registered feature
*/
private final Map<String, Queue<ScheduledFuture>> eventNotificationsMap = new HashMap<>();
/**
* The last licensesMetaData that has been notified by {@link #notifyFeatures(LicensesMetaData)}
*/
private final AtomicReference<LicensesMetaData> lastObservedLicensesState;
@Inject
public LicensesService(Settings settings, ClusterService clusterService, ThreadPool threadPool, TransportService transportService) {
super(settings);
this.clusterService = clusterService;
this.threadPool = threadPool;
this.transportService = transportService;
this.lastObservedLicensesState = new AtomicReference<>(null);
if (DiscoveryNode.masterNode(settings)) {
transportService.registerRequestHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME, RegisterTrialLicenseRequest.class,
ThreadPool.Names.SAME, new RegisterTrialLicenseRequestHandler());
}
}
/**
* {@inheritDoc}
*/
@Override
public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener<LicensesUpdateResponse> listener) {
final PutLicenseRequest request = requestHolder.request;
final Set<License> newLicenses = Sets.newHashSet(request.licenses());
LicensesStatus status = checkLicenses(newLicenses);
if (status == LicensesStatus.VALID) {
clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask<LicensesUpdateResponse>(request, listener) {
@Override
protected LicensesUpdateResponse newResponse(boolean acknowledged) {
return new LicensesUpdateResponse(acknowledged, LicensesStatus.VALID);
}
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
MetaData metaData = currentState.metaData();
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses);
List<License> updatedSignedLicenses = licensesWrapper.addAndGetSignedLicenses(newLicenses);
if (updatedSignedLicenses.size() != licensesWrapper.signedLicenses.size()) {
LicensesMetaData newLicensesMetaData = new LicensesMetaData(updatedSignedLicenses, licensesWrapper.trialLicenses);
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
}
return currentState;
}
});
} else {
listener.onResponse(new LicensesUpdateResponse(true, status));
}
}
public static class LicensesUpdateResponse extends ClusterStateUpdateResponse {
private final LicensesStatus status;
public LicensesUpdateResponse(boolean acknowledged, LicensesStatus status) {
super(acknowledged);
this.status = status;
}
public LicensesStatus status() {
return status;
}
}
/**
* {@inheritDoc}
*/
@Override
public void removeLicenses(final DeleteLicenseRequestHolder requestHolder, final ActionListener<ClusterStateUpdateResponse> listener) {
final DeleteLicenseRequest request = requestHolder.request;
clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask<ClusterStateUpdateResponse>(request, listener) {
@Override
protected ClusterStateUpdateResponse newResponse(boolean acknowledged) {
return new ClusterStateUpdateResponse(acknowledged);
}
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
MetaData metaData = currentState.metaData();
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses);
List<License> updatedSignedLicenses = licensesWrapper.removeAndGetSignedLicenses(request.features());
if (updatedSignedLicenses.size() != licensesWrapper.signedLicenses.size()) {
LicensesMetaData newLicensesMetaData = new LicensesMetaData(updatedSignedLicenses, licensesWrapper.trialLicenses);
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
} else {
return currentState;
}
}
});
}
/**
* {@inheritDoc}
*/
@Override
public Set<String> enabledFeatures() {
Set<String> enabledFeatures = Sets.newHashSet();
for (ListenerHolder holder : registeredListeners) {
if (holder.enabled) {
enabledFeatures.add(holder.feature);
}
}
if (logger.isDebugEnabled()) {
StringBuilder stringBuilder = new StringBuilder();
for (String feature: enabledFeatures) {
if (stringBuilder.length() != 0) {
stringBuilder.append(", ");
}
stringBuilder.append(feature);
}
logger.debug("LicensesManagerService: Enabled Features: [" + stringBuilder.toString() + "]");
}
return enabledFeatures;
}
/**
* {@inheritDoc}
*/
@Override
public List<License> getLicenses() {
final LicensesMetaData currentMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
if (currentMetaData != null) {
// don't use ESLicenses.reduceAndMap, as it will merge out expired licenses
List<License> currentLicenses = new ArrayList<>();
currentLicenses.addAll(currentMetaData.getSignedLicenses());
currentLicenses.addAll(currentMetaData.getTrialLicenses());
// bucket license for feature with the latest expiry date
Map<String, License> licenseMap = new HashMap<>();
for (License license : currentLicenses) {
if (!licenseMap.containsKey(license.feature())) {
licenseMap.put(license.feature(), license);
} else {
License prevLicense = licenseMap.get(license.feature());
if (license.expiryDate() > prevLicense.expiryDate()) {
licenseMap.put(license.feature(), license);
}
}
}
// sort the licenses by issue date
List<License> reducedLicenses = new ArrayList<>(licenseMap.values());
CollectionUtil.introSort(reducedLicenses, new Comparator<License>() {
@Override
public int compare(License license1, License license2) {
return (int) (license2.issueDate() - license1.issueDate());
}
});
return reducedLicenses;
}
return Collections.emptyList();
}
private LicensesStatus checkLicenses(Set<License> licenses) {
final ImmutableMap<String, License> map = reduceAndMap(licenses);
if (LicenseVerifier.verifyLicenses(map.values())) {
return LicensesStatus.VALID;
} else {
return LicensesStatus.INVALID;
}
}
/**
* Master-only operation to generate a one-time trial license for a feature.
* The trial license is only generated and stored if the current cluster state metaData
* has no signed/one-time-trial license for the feature in question
*/
private void registerTrialLicense(final RegisterTrialLicenseRequest request) {
clusterService.submitStateUpdateTask("register trial license []", new ProcessedClusterStateUpdateTask() {
@Override
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
LicensesMetaData licensesMetaData = newState.metaData().custom(LicensesMetaData.TYPE);
logLicenseMetaDataStats("after trial license registration", licensesMetaData);
}
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
MetaData metaData = currentState.metaData();
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
LicensesMetaData currentLicensesMetaData = metaData.custom(LicensesMetaData.TYPE);
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicensesMetaData);
// do not generate a trial license for a feature that already has a signed/trial license
if (checkTrialLicenseGenerationCondition(request.feature, licensesWrapper)) {
List<License> currentTrailLicenses = new ArrayList<>(licensesWrapper.trialLicenses);
currentTrailLicenses.add(generateEncodedTrialLicense(request.feature, request.duration, request.maxNodes));
final LicensesMetaData newLicensesMetaData = new LicensesMetaData(
licensesWrapper.signedLicenses, ImmutableList.copyOf(currentTrailLicenses));
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
}
return currentState;
}
@Override
public void onFailure(String source, @Nullable Throwable t) {
logger.debug("LicensesService: " + source, t);
}
private boolean checkTrialLicenseGenerationCondition(String feature, LicensesWrapper licensesWrapper) {
final List<License> currentLicenses = new ArrayList<>();
currentLicenses.addAll(licensesWrapper.signedLicenses);
currentLicenses.addAll(licensesWrapper.trialLicenses);
for (License license : currentLicenses) {
if (license.feature().equals(feature)) {
return false;
}
}
return true;
}
private License generateEncodedTrialLicense(String feature, TimeValue duration, int maxNodes) {
return TrialLicenseUtils.builder()
.issuedTo(clusterService.state().getClusterName().value())
.issueDate(System.currentTimeMillis())
.duration(duration)
.feature(feature)
.maxNodes(maxNodes)
.build();
}
});
}
@Override
protected void doStart() throws ElasticsearchException {
clusterService.add(this);
}
@Override
protected void doStop() throws ElasticsearchException {
clusterService.remove(this);
// cancel all notifications
for (ScheduledFuture scheduledNotification : scheduledNotifications) {
FutureUtils.cancel(scheduledNotification);
}
for (Queue<ScheduledFuture> queue : eventNotificationsMap.values()) {
for (ScheduledFuture scheduledFuture : queue) {
FutureUtils.cancel(scheduledFuture);
}
queue.clear();
}
LicensesMetaData currentLicensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
// notify features to be disabled
for (ListenerHolder holder : registeredListeners) {
holder.disableFeatureIfNeeded(effectiveLicenses.get(holder.feature), false);
}
// clear all handlers
registeredListeners.clear();
// empty out notification queue
scheduledNotifications.clear();
lastObservedLicensesState.set(null);
}
@Override
protected void doClose() throws ElasticsearchException {
transportService.removeHandler(REGISTER_TRIAL_LICENSE_ACTION_NAME);
}
/**
* When there is no global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}:
* - tries to register any {@link #pendingListeners} by calling {@link #registeredListeners}
* - if any {@link #pendingListeners} are registered successfully or if previous cluster state had a block on
* {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}, calls
* {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)}
* - else calls {@link #notifyFeaturesAndScheduleNotificationIfNeeded(LicensesMetaData)}
*/
@Override
public void clusterChanged(ClusterChangedEvent event) {
final ClusterState previousClusterState = event.previousState();
final ClusterState currentClusterState = event.state();
if (!currentClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
final LicensesMetaData oldLicensesMetaData = previousClusterState.getMetaData().custom(LicensesMetaData.TYPE);
final LicensesMetaData currentLicensesMetaData = currentClusterState.getMetaData().custom(LicensesMetaData.TYPE);
logLicenseMetaDataStats("old", oldLicensesMetaData);
logLicenseMetaDataStats("new", currentLicensesMetaData);
// register any pending listeners
if (!pendingListeners.isEmpty()) {
ListenerHolder pendingRegistrationListener;
while ((pendingRegistrationListener = pendingListeners.poll()) != null) {
boolean masterAvailable = registerListener(pendingRegistrationListener);
if (logger.isDebugEnabled()) {
logger.debug("trying to register pending listener for " + pendingRegistrationListener.feature + " masterAvailable: " + masterAvailable);
}
if (!masterAvailable) {
// if the master is not available do not, break out of trying pendingListeners
pendingListeners.add(pendingRegistrationListener);
break;
}
}
}
// notify all interested plugins
if (previousClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
} else {
notifyFeaturesAndScheduleNotificationIfNeeded(currentLicensesMetaData);
}
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
for (ListenerHolder listenerHolder : registeredListeners) {
listenerHolder.scheduleNotificationIfNeeded(effectiveLicenses.get(listenerHolder.feature));
}
} else if (logger.isDebugEnabled()) {
logger.debug("clusterChanged: no action [has STATE_NOT_RECOVERED_BLOCK]");
}
}
/**
* Calls {@link #notifyFeaturesAndScheduleNotification(LicensesMetaData)} with <code>currentLicensesMetaData</code>
* if it was not already notified on.
* <p/>
* Upon completion sets <code>currentLicensesMetaData</code> to {@link #lastObservedLicensesState}
* to ensure the same license(s) are not notified on from
* {@link #clusterChanged(org.elasticsearch.cluster.ClusterChangedEvent)}
*/
private void notifyFeaturesAndScheduleNotificationIfNeeded(final LicensesMetaData currentLicensesMetaData) {
final LicensesMetaData lastNotifiedLicensesMetaData = lastObservedLicensesState.get();
if (lastNotifiedLicensesMetaData != null && lastNotifiedLicensesMetaData.equals(currentLicensesMetaData)) {
if (logger.isDebugEnabled()) {
logger.debug("currentLicensesMetaData has been already notified on");
}
return;
}
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
lastObservedLicensesState.set(currentLicensesMetaData);
logLicenseMetaDataStats("Setting last observed metaData", currentLicensesMetaData);
}
/**
* Calls {@link #notifyFeatures(LicensesMetaData)} with <code>currentLicensesMetaData</code>
* and schedules the earliest expiry (if any) notification for registered feature(s)
*/
private void notifyFeaturesAndScheduleNotification(final LicensesMetaData currentLicensesMetaData) {
long nextScheduleFrequency = notifyFeatures(currentLicensesMetaData);
if (nextScheduleFrequency != -1l) {
scheduleNextNotification(nextScheduleFrequency);
}
}
/**
* Checks license expiry for all the registered feature(s)
*
* @return -1 if there are no expiring license(s) for any registered feature(s), else
* returns the minimum of the expiry times of all the registered feature(s) to
* schedule an expiry notification
*/
private long notifyFeatures(final LicensesMetaData currentLicensesMetaData) {
long nextScheduleFrequency = -1l;
for (ListenerHolder listenerHolder : registeredListeners) {
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
License license = effectiveLicenses.get(listenerHolder.feature);
if (license == null) {
continue;
}
long expiryDate = license.expiryDate();
long issueDate = license.issueDate();
long now = System.currentTimeMillis();
long expiryDuration = expiryDate - now;
if (expiryDuration > 0l && (now - issueDate) >= 0l) {
listenerHolder.enableFeatureIfNeeded(license);
if (nextScheduleFrequency == -1l) {
nextScheduleFrequency = expiryDuration;
} else {
nextScheduleFrequency = Math.min(expiryDuration, nextScheduleFrequency);
}
} else {
listenerHolder.disableFeatureIfNeeded(license, true);
}
if (logger.isDebugEnabled()) {
String status;
if (expiryDate != -1l) {
status = " status: license expires in : " + TimeValue.timeValueMillis(expiryDate - System.currentTimeMillis());
} else {
status = " status: no trial/signed license found";
}
if (expiryDuration > 0l) {
status += " action: enableFeatureIfNeeded";
} else {
status += " action: disableFeatureIfNeeded";
}
logger.debug(listenerHolder.toString() + "" + status);
}
}
if (logger.isDebugEnabled()) {
if (nextScheduleFrequency == -1l) {
logger.debug("no need to schedule next notification");
} else {
logger.debug("next notification time: " + TimeValue.timeValueMillis(nextScheduleFrequency).toString());
}
}
return nextScheduleFrequency;
}
private void logLicenseMetaDataStats(String prefix, LicensesMetaData licensesMetaData) {
if (logger.isDebugEnabled()) {
if (licensesMetaData != null) {
StringBuilder signedFeatures = new StringBuilder();
for (License license : licensesMetaData.getSignedLicenses()) {
if (signedFeatures.length() != 0) {
signedFeatures.append(", ");
}
signedFeatures.append(license.feature());
}
StringBuilder trialFeatures = new StringBuilder();
for (License license : licensesMetaData.getTrialLicenses()) {
if (trialFeatures.length() != 0) {
trialFeatures.append(", ");
}
trialFeatures.append(license.feature());
}
logger.debug(prefix + " LicensesMetaData: signedLicenses: [" + signedFeatures.toString() + "] trialLicenses: [" + trialFeatures.toString() + "]");
} else {
logger.debug(prefix + " LicensesMetaData: signedLicenses: [] trialLicenses: []");
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void register(String feature, TrialLicenseOptions trialLicenseOptions, Collection<ExpirationCallback> expirationCallbacks, Listener listener) {
for (final ListenerHolder listenerHolder : Iterables.concat(registeredListeners, pendingListeners)) {
if (listenerHolder.feature.equals(feature)) {
throw new IllegalStateException("feature: [" + feature + "] has been already registered");
}
}
Queue<ScheduledFuture> notificationQueue = new ConcurrentLinkedQueue<>();
eventNotificationsMap.put(feature, notificationQueue);
final ListenerHolder listenerHolder = new ListenerHolder(feature, trialLicenseOptions, expirationCallbacks, listener, notificationQueue);
// don't trust the clusterState for blocks just yet!
final Lifecycle.State clusterServiceState = clusterService.lifecycleState();
if (clusterServiceState != Lifecycle.State.STARTED) {
pendingListeners.add(listenerHolder);
} else {
if (!registerListener(listenerHolder)) {
pendingListeners.add(listenerHolder);
}
}
}
/**
* Notifies new feature listener if it already has a signed license
* if new feature has a non-null trial license option, a master node request is made to generate the trial license
* then notifies features if needed
*
* @param listenerHolder of the feature to register
* @return true if registration has been completed, false otherwise (if masterNode is not available & trail license spec is provided)
* or if there is a global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK}
*/
private boolean registerListener(final ListenerHolder listenerHolder) {
logger.debug("Registering listener for " + listenerHolder.feature);
final ClusterState currentState = clusterService.state();
if (currentState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
return false;
}
final LicensesMetaData currentMetaData = currentState.metaData().custom(LicensesMetaData.TYPE);
if (expiryDateForFeature(listenerHolder.feature, currentMetaData) == -1l) {
// does not have any license so generate a trial license
TrialLicenseOptions options = listenerHolder.trialLicenseOptions;
if (options != null) {
// Trial license option is provided
RegisterTrialLicenseRequest request = new RegisterTrialLicenseRequest(listenerHolder.feature,
options.duration, options.maxNodes);
if (currentState.nodes().localNodeMaster()) {
registerTrialLicense(request);
} else {
DiscoveryNode masterNode = currentState.nodes().masterNode();
if (masterNode != null) {
transportService.sendRequest(masterNode,
REGISTER_TRIAL_LICENSE_ACTION_NAME, request, EmptyTransportResponseHandler.INSTANCE_SAME);
} else {
// could not sent register trial license request to master
logger.debug("Store as pendingRegistration [master not available yet]");
}
// make sure trial license is available before registration
return false;
}
} else if (logger.isDebugEnabled()) {
// notify feature as clusterChangedEvent may not happen
// as no trial or signed license has been found for feature
logger.debug("Calling notifyFeaturesAndScheduleNotification [no trial license spec provided]");
}
} else if (logger.isDebugEnabled()) {
// signed license already found for the new registered
// feature, notify feature on registration
logger.debug("Calling notifyFeaturesAndScheduleNotification [signed/trial license available]");
}
if (currentMetaData == null) {
return false;
}
registeredListeners.add(listenerHolder);
notifyFeaturesAndScheduleNotification(currentMetaData);
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentMetaData);
listenerHolder.scheduleNotificationIfNeeded(effectiveLicenses.get(listenerHolder.feature));
return true;
}
private static long expiryDateForFeature(String feature, final LicensesMetaData currentLicensesMetaData) {
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
License featureLicense;
if ((featureLicense = effectiveLicenses.get(feature)) != null) {
return featureLicense.expiryDate();
}
return -1l;
}
private static Map<String, License> getEffectiveLicenses(final LicensesMetaData metaData) {
Map<String, License> map = new HashMap<>();
if (metaData != null) {
Set<License> licenses = new HashSet<>();
for (License license : metaData.getSignedLicenses()) {
if (LicenseVerifier.verifyLicense(license)) {
licenses.add(license);
}
}
licenses.addAll(metaData.getTrialLicenses());
return reduceAndMap(licenses);
}
return ImmutableMap.copyOf(map);
}
/**
* Clears out any completed notification futures
*/
private static void clearFinishedNotifications(Queue<ScheduledFuture> scheduledNotifications) {
while (!scheduledNotifications.isEmpty()) {
ScheduledFuture notification = scheduledNotifications.peek();
if (notification != null && notification.isDone()) {
// remove the notifications that are done
scheduledNotifications.poll();
} else {
// stop emptying out the queue as soon as the first undone future hits
break;
}
}
}
private String executorName() {
return ThreadPool.Names.GENERIC;
}
/**
* Schedules an expiry notification with a delay of <code>nextScheduleDelay</code>
*/
private void scheduleNextNotification(long nextScheduleDelay) {
clearFinishedNotifications(scheduledNotifications);
try {
final TimeValue delay = TimeValue.timeValueMillis(nextScheduleDelay);
scheduledNotifications.add(threadPool.schedule(delay, executorName(), new LicensingClientNotificationJob()));
if (logger.isDebugEnabled()) {
logger.debug("Scheduling next notification after: " + delay);
}
} catch (EsRejectedExecutionException ex) {
logger.debug("Couldn't re-schedule licensing client notification job", ex);
}
}
/**
* Job for notifying on expired license(s) to registered feature(s)
* In case of a global block on {@link org.elasticsearch.gateway.GatewayService#STATE_NOT_RECOVERED_BLOCK},
* the notification is not run, instead the feature(s) would be notified on the next
* {@link #clusterChanged(org.elasticsearch.cluster.ClusterChangedEvent)} with no global block
*/
private class LicensingClientNotificationJob implements Runnable {
public LicensingClientNotificationJob() {
}
@Override
public void run() {
logger.debug("Performing LicensingClientNotificationJob");
// next clusterChanged event will deal with the missed notifications
final ClusterState currentClusterState = clusterService.state();
if (!currentClusterState.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
final LicensesMetaData currentLicensesMetaData = currentClusterState.metaData().custom(LicensesMetaData.TYPE);
notifyFeaturesAndScheduleNotification(currentLicensesMetaData);
} else if (logger.isDebugEnabled()) {
logger.debug("skip notification [STATE_NOT_RECOVERED_BLOCK]");
}
}
}
public static class PutLicenseRequestHolder {
private final PutLicenseRequest request;
private final String source;
public PutLicenseRequestHolder(PutLicenseRequest request, String source) {
this.request = request;
this.source = source;
}
}
public static class DeleteLicenseRequestHolder {
private final DeleteLicenseRequest request;
private final String source;
public DeleteLicenseRequestHolder(DeleteLicenseRequest request, String source) {
this.request = request;
this.source = source;
}
}
/**
* Stores configuration and listener for a feature
*/
private class ListenerHolder {
final String feature;
final TrialLicenseOptions trialLicenseOptions;
final Collection<ExpirationCallback> expirationCallbacks;
final Listener listener;
final AtomicLong currentExpiryDate = new AtomicLong(-1l);
final Queue<ScheduledFuture> notificationQueue;
volatile boolean initialized = false;
volatile boolean enabled = false; // by default, a consumer plugin should be disabled
private ListenerHolder(String feature, TrialLicenseOptions trialLicenseOptions, Collection<ExpirationCallback> expirationCallbacks, Listener listener, Queue<ScheduledFuture> notificationQueue) {
this.feature = feature;
this.trialLicenseOptions = trialLicenseOptions;
this.expirationCallbacks = expirationCallbacks;
this.listener = listener;
this.notificationQueue = notificationQueue;
}
private synchronized void enableFeatureIfNeeded(License license) {
if (!initialized || !enabled) {
listener.onEnabled(license);
initialized = true;
enabled = true;
logger.info("license for [" + feature + "] - valid");
}
}
private synchronized void disableFeatureIfNeeded(License license, boolean log) {
if (!initialized || enabled) {
listener.onDisabled(license);
initialized = true;
enabled = false;
if (log) {
logger.info("license for [" + feature + "] - expired");
}
}
}
private Runnable triggerJob(final ExpirationCallback callback) {
return new Runnable() {
@Override
public void run() {
LicensesMetaData currentLicensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
triggerEvent(effectiveLicenses.get(feature), System.currentTimeMillis(), callback);
}
};
}
/**
* schedules all {@link ExpirationCallback} associated with <code>license</code>
* If callbacks are already scheduled clear up finished notifications from the queue
*/
private void scheduleNotificationIfNeeded(final License license) {
long expiryDate = ((license != null) ? license.expiryDate() : -1l);
if (currentExpiryDate.get() == expiryDate) {
clearFinishedNotifications(notificationQueue);
return;
}
currentExpiryDate.set(expiryDate);
// clear out notification queue
while (!notificationQueue.isEmpty()) {
ScheduledFuture notification = notificationQueue.peek();
if (notification != null) {
// cancel
FutureUtils.cancel(notification);
notificationQueue.poll();
}
}
long now = System.currentTimeMillis();
// Schedule the first for all the callbacks
long expiryDuration = expiryDate - now;
//schedule first event of callbacks that will be activated in the future
for (ExpirationCallback expirationCallback : expirationCallbacks) {
if (!expirationCallback.matches(expiryDate, now)) {
long delay = -1l;
switch (expirationCallback.orientation()) {
case PRE:
delay = expiryDuration - expirationCallback.max().getMillis();
break;
case POST:
if (expiryDuration >= 0l) {
delay = expiryDuration + expirationCallback.min().getMillis();
} else {
delay = (-1l * expiryDuration) - expirationCallback.min().getMillis();
}
break;
}
if (delay > 0l) {
if (logger.isDebugEnabled()) {
logger.debug("Adding first notification for: orientation: " + expirationCallback.orientation().name()
+ " min: " + expirationCallback.min()
+ " max: " + expirationCallback.max()
+ " with delay: " + TimeValue.timeValueMillis(delay)
+ " license expiry duration: " + TimeValue.timeValueMillis(expiryDuration));
}
notificationQueue.add(threadPool.schedule(TimeValue.timeValueMillis(delay), executorName(), triggerJob(expirationCallback)));
}
}
}
if (license != null) {
// schedule first event of callbacks that match
logger.debug("Calling TRIGGER_EVENTS with license for " + license.feature() + " expiry: " + license.expiryDate());
for (ExpirationCallback expirationCallback : expirationCallbacks) {
triggerEvent(license, now, expirationCallback);
}
}
}
private void triggerEvent(final License license, long now, final ExpirationCallback expirationCallback) {
if (expirationCallback.matches(license.expiryDate(), now)) {
long expiryDuration = license.expiryDate() - now;
boolean expired = expiryDuration <= 0l;
if (logger.isDebugEnabled()) {
logger.debug("Calling notification on: orientation: " + expirationCallback.orientation().name()
+ " min: " + expirationCallback.min()
+ " max: " + expirationCallback.max()
+ " scheduled after: " + expirationCallback.frequency().getMillis()
+ " next interval match: " + expirationCallback.matches(license.expiryDate(), System.currentTimeMillis() + expirationCallback.frequency().getMillis()));
}
expirationCallback.on(license, new ExpirationStatus(expired, TimeValue.timeValueMillis((!expired) ? expiryDuration : (-1l * expiryDuration))));
notificationQueue.add(threadPool.schedule(expirationCallback.frequency(), executorName(), triggerJob(expirationCallback)));
}
}
@Override
public String toString() {
return "(feature: " + feature + ", enabled: " + enabled + ")";
}
}
/**
* Thin wrapper to work with {@link LicensesMetaData}
* Never mutates the wrapped metaData
*/
private static class LicensesWrapper {
public static LicensesWrapper wrap(LicensesMetaData licensesMetaData) {
return new LicensesWrapper(licensesMetaData);
}
private ImmutableList<License> signedLicenses = ImmutableList.of();
private ImmutableList<License> trialLicenses = ImmutableList.of();
private LicensesWrapper(LicensesMetaData licensesMetaData) {
if (licensesMetaData != null) {
this.signedLicenses = ImmutableList.copyOf(licensesMetaData.getSignedLicenses());
this.trialLicenses = ImmutableList.copyOf(licensesMetaData.getTrialLicenses());
}
}
/**
* Returns existingLicenses + newLicenses.
* A new license is added if:
* - there is no current license for the feature
* - current license for feature has a earlier expiry date
*/
private List<License> addAndGetSignedLicenses(Set<License> newLicenses) {
final ImmutableMap<String, License> newLicensesMap = reduceAndMap(newLicenses);
List<License> newSignedLicenses = new ArrayList<>(signedLicenses);
final ImmutableMap<String, License> oldLicenseMap = reduceAndMap(Sets.newHashSet(signedLicenses));
for (String newFeature : newLicensesMap.keySet()) {
final License newFeatureLicense = newLicensesMap.get(newFeature);
if (oldLicenseMap.containsKey(newFeature)) {
final License oldFeatureLicense = oldLicenseMap.get(newFeature);
if (oldFeatureLicense.expiryDate() < newFeatureLicense.expiryDate()) {
newSignedLicenses.add(newFeatureLicense);
}
} else {
newSignedLicenses.add(newFeatureLicense);
}
}
return ImmutableList.copyOf(newSignedLicenses);
}
private List<License> removeAndGetSignedLicenses(Set<String> features) {
List<License> updatedSignedLicenses = new ArrayList<>();
for (License license : signedLicenses) {
if (!features.contains(license.feature())) {
updatedSignedLicenses.add(license);
}
}
return ImmutableList.copyOf(updatedSignedLicenses);
}
}
/**
* Request for trial license generation to master
*/
private static class RegisterTrialLicenseRequest extends TransportRequest {
private int maxNodes;
private String feature;
private TimeValue duration;
private RegisterTrialLicenseRequest() {
}
private RegisterTrialLicenseRequest(String feature, TimeValue duration, int maxNodes) {
this.maxNodes = maxNodes;
this.feature = feature;
this.duration = duration;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
maxNodes = in.readVInt();
feature = in.readString();
duration = new TimeValue(in.readVLong(), TimeUnit.MILLISECONDS);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeVInt(maxNodes);
out.writeString(feature);
out.writeVLong(duration.getMillis());
}
}
/**
* Request handler for trial license generation to master
*/
private class RegisterTrialLicenseRequestHandler implements TransportRequestHandler<RegisterTrialLicenseRequest> {
@Override
public void messageReceived(RegisterTrialLicenseRequest request, TransportChannel channel) throws Exception {
registerTrialLicense(request);
channel.sendResponse(TransportResponse.Empty.INSTANCE);
}
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.core;
public enum LicensesStatus {
VALID((byte) 0),
INVALID((byte) 1),
EXPIRED((byte) 2);
private final byte id;
LicensesStatus(byte id) {
this.id = id;
}
public int id() {
return id;
}
public static LicensesStatus fromId(int id) {
if (id == 0) {
return VALID;
} else if (id == 1) {
return INVALID;
} else if (id == 2) {
return EXPIRED;
} else {
throw new IllegalStateException("no valid LicensesStatus for id=" + id);
}
}
}

View File

@ -1,113 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.core;
import org.elasticsearch.common.Base64;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.util.Collections;
import java.util.UUID;
import static org.elasticsearch.license.core.CryptUtils.decrypt;
import static org.elasticsearch.license.core.CryptUtils.encrypt;
public class TrialLicenseUtils {
public static TrialLicenseBuilder builder() {
return new TrialLicenseBuilder();
}
public static class TrialLicenseBuilder {
private static final String DEFAULT_ISSUER = "elasticsearch";
private static final String DEFAULT_TYPE = "trial";
private static final String DEFAULT_SUBSCRIPTION_TYPE = "none";
private String feature;
private long expiryDate = -1;
private long issueDate = -1;
private TimeValue duration;
private int maxNodes = -1;
private String uid = null;
private String issuedTo;
public TrialLicenseBuilder() {
}
public TrialLicenseBuilder uid(String uid) {
this.uid = uid;
return this;
}
public TrialLicenseBuilder issuedTo(String issuedTo) {
this.issuedTo = issuedTo;
return this;
}
public TrialLicenseBuilder maxNodes(int maxNodes) {
this.maxNodes = maxNodes;
return this;
}
public TrialLicenseBuilder feature(String featureType) {
this.feature = featureType;
return this;
}
public TrialLicenseBuilder issueDate(long issueDate) {
this.issueDate = issueDate;
return this;
}
public TrialLicenseBuilder duration(TimeValue duration) {
this.duration = duration;
return this;
}
public TrialLicenseBuilder expiryDate(long expiryDate) {
this.expiryDate = expiryDate;
return this;
}
public License build() {
if (expiryDate == -1) {
expiryDate = issueDate + duration.millis();
}
if (uid == null) {
uid = UUID.randomUUID().toString();
}
return License.builder()
.type(DEFAULT_TYPE)
.subscriptionType(DEFAULT_SUBSCRIPTION_TYPE)
.issuer(DEFAULT_ISSUER)
.uid(uid)
.issuedTo(issuedTo)
.issueDate(issueDate)
.feature(feature)
.maxNodes(maxNodes)
.expiryDate(expiryDate)
.build();
}
}
public static License fromEncodedTrialLicense(String encodedTrialLicense) throws IOException {
byte[] data = decrypt(Base64.decode(encodedTrialLicense));
XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(data);
parser.nextToken();
return License.builder().fromXContent(parser).build();
}
public static String toEncodedTrialLicense(License trialLicense) throws IOException {
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
// trial license is equivalent to a license spec (no signature)
trialLicense.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(Licenses.LICENSE_SPEC_VIEW_MODE, "true")));
return Base64.encodeBytes(encrypt(contentBuilder.bytes().toBytes()));
}
}

View File

@ -1,41 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.rest;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.support.AcknowledgedRestListener;
import static org.elasticsearch.rest.RestRequest.Method.DELETE;
public class RestDeleteLicenseAction extends BaseRestHandler {
@Inject
public RestDeleteLicenseAction(Settings settings, RestController controller, Client client) {
super(settings, controller, client);
controller.registerHandler(DELETE, "/_licenses/{features}", this);
}
@Override
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
final String[] features = Strings.splitStringByCommaToArray(request.param("features"));
if (features.length == 0) {
throw new IllegalArgumentException("no feature specified for license deletion");
}
DeleteLicenseRequest deleteLicenseRequest = new DeleteLicenseRequest(features);
client.admin().cluster().execute(DeleteLicenseAction.INSTANCE, deleteLicenseRequest, new AcknowledgedRestListener<DeleteLicenseResponse>(channel));
}
}

View File

@ -1,59 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.rest;
import com.google.common.collect.ImmutableMap;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequest;
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
import org.elasticsearch.rest.*;
import org.elasticsearch.rest.action.support.RestBuilderListener;
import java.util.Map;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestStatus.OK;
public class RestGetLicenseAction extends BaseRestHandler {
@Inject
public RestGetLicenseAction(Settings settings, RestController controller, Client client) {
super(settings, controller, client);
controller.registerHandler(GET, "/_licenses", this);
}
/**
* There will be only one license displayed per feature, the selected license will have the latest expiry_date
* out of all other licenses for the feature.
* <p/>
* The licenses are sorted by latest issue_date
*/
@Override
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
final Map<String, String> overrideParams = ImmutableMap.of(Licenses.REST_VIEW_MODE, "true");
final ToXContent.Params params = new ToXContent.DelegatingMapParams(overrideParams, request);
GetLicenseRequest getLicenseRequest = new GetLicenseRequest();
getLicenseRequest.local(request.paramAsBoolean("local", getLicenseRequest.local()));
client.admin().cluster().execute(GetLicenseAction.INSTANCE, getLicenseRequest, new RestBuilderListener<GetLicenseResponse>(channel) {
@Override
public RestResponse buildResponse(GetLicenseResponse response, XContentBuilder builder) throws Exception {
// Default to pretty printing, but allow ?pretty=false to disable
if (!request.hasParam("pretty")) {
builder.prettyPrint().lfAtEnd();
}
Licenses.toXContent(response.licenses(), builder, params);
return new BytesRestResponse(OK, builder);
}
});
}
}

View File

@ -1,69 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.rest;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.support.AcknowledgedRestListener;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.POST;
import static org.elasticsearch.rest.RestRequest.Method.PUT;
public class RestPutLicenseAction extends BaseRestHandler {
@Inject
public RestPutLicenseAction(Settings settings, RestController controller, Client client) {
super(settings, controller, client);
controller.registerHandler(PUT, "/_licenses", this);
controller.registerHandler(POST, "/_licenses", this);
}
@Override
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
PutLicenseRequest putLicenseRequest = new PutLicenseRequest();
putLicenseRequest.licenses(request.content().toUtf8());
client.admin().cluster().execute(PutLicenseAction.INSTANCE, putLicenseRequest, new LicensesAcknowledgedListener(channel));
}
private class LicensesAcknowledgedListener extends AcknowledgedRestListener<PutLicenseResponse> {
public LicensesAcknowledgedListener(RestChannel channel) {
super(channel);
}
@Override
protected void addCustomFields(XContentBuilder builder, PutLicenseResponse response) throws IOException {
LicensesStatus status = response.status();
String statusString = null;
switch (status) {
case VALID:
statusString = "valid";
break;
case INVALID:
statusString = "invalid";
break;
case EXPIRED:
statusString = "expired";
break;
}
builder.field("licenses_status", statusString);
}
}
}

View File

@ -1,2 +0,0 @@
plugin=org.elasticsearch.license.plugin.LicensePlugin
version=${project.version}

View File

@ -1,154 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.base.Predicate;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.plugin.consumer.TestConsumerPluginBase;
import org.elasticsearch.license.plugin.consumer.TestPluginServiceBase;
import org.elasticsearch.test.InternalTestCluster;
import org.junit.After;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
/**
* Framework to test licensing plugin integration for existing/new consumer plugins
* see {@link org.elasticsearch.license.plugin.LicensesEagerConsumerPluginIntegrationTests} and {@link org.elasticsearch.license.plugin.LicensesLazyConsumerPluginIntegrationTests}
* for example usage
*/
@ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0, transportClientRatio = 0.0)
public abstract class AbstractLicensesConsumerPluginIntegrationTests extends AbstractLicensesIntegrationTests {
protected final TestConsumerPluginBase consumerPlugin;
public AbstractLicensesConsumerPluginIntegrationTests(TestConsumerPluginBase consumerPlugin) {
this.consumerPlugin = consumerPlugin;
}
private final int trialLicenseDurationInSeconds = 10;
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put(consumerPlugin.name()
+ ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.putArray("plugin.types", LicensePlugin.class.getName(), consumerPlugin.getClass().getName())
.build();
}
@After
public void afterTest() throws Exception {
wipeAllLicenses();
assertThat(awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
return !clusterService().state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK);
}
}), equalTo(true));
}
@Test
public void testTrialLicenseAndSignedLicenseNotification() throws Exception {
logger.info("using " + consumerPlugin.getClass().getName() + " consumer plugin");
logger.info(" --> trial license generated");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnabledNotification(2);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
logger.info(" --> put signed license");
putLicense(consumerPlugin.featureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnabledNotification(1);
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
}
@Test
public void testTrialLicenseNotification() throws Exception {
logger.info(" --> check onEnabled for trial license");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnabledNotification(1);
logger.info(" --> sleep for rest of trailLicense duration");
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds);
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
}
@Test
public void testOverlappingTrialAndSignedLicenseNotification() throws Exception {
logger.info(" --> check onEnabled for trial license");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnabledNotification(1);
logger.info(" --> put signed license while trial license is in effect");
putLicense(consumerPlugin.featureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnabledNotification(1);
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
logger.info(" --> sleep for rest of trailLicense duration");
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
logger.info(" --> check consumer is still enabled [signed license]");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnabledNotification(1);
assertLicenseManagerEnabledFeatureFor(consumerPlugin.featureName());
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2 * 2);
assertLicenseManagerDisabledFeatureFor(consumerPlugin.featureName());
}
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPluginNotification(consumerPluginServices(), true, timeoutInSec);
}
private void assertConsumerPluginDisabledNotification(int timeoutInSec) throws InterruptedException {
assertConsumerPluginNotification(consumerPluginServices(), false, timeoutInSec);
}
private List<TestPluginServiceBase> consumerPluginServices() {
final InternalTestCluster clients = internalCluster();
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
for (TestPluginServiceBase service : clients.getDataNodeInstances(consumerPlugin.service())) {
consumerPluginServices.add(service);
}
return consumerPluginServices;
}
}

View File

@ -1,205 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ProcessedClusterStateUpdateTask;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.joda.DateMathParser;
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
import org.elasticsearch.license.plugin.consumer.TestPluginServiceBase;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.test.InternalTestCluster;
import org.joda.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
public abstract class AbstractLicensesIntegrationTests extends ElasticsearchIntegrationTest {
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
public static String dateMathString(String time, final long now) {
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
@Override
public Long call() throws Exception {
return now;
}
}));
}
public static long dateMath(String time, final long now) {
return dateMathParser.parse(time, new Callable<Long>() {
@Override
public Long call() throws Exception {
return now;
}
});
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.settingsBuilder()
.put("plugins.load_classpath_plugins", false)
.put("plugin.types", LicensePlugin.class.getName())
.build();
}
@Override
protected Settings transportClientSettings() {
// Plugin should be loaded on the transport client as well
return nodeSettings(0);
}
protected void wipeAllLicenses() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
ClusterService clusterService = internalCluster().getInstance(ClusterService.class, internalCluster().getMasterName());
clusterService.submitStateUpdateTask("delete licensing metadata", new ProcessedClusterStateUpdateTask() {
@Override
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
latch.countDown();
}
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
mdBuilder.removeCustom(LicensesMetaData.TYPE);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
}
@Override
public void onFailure(String source, @Nullable Throwable t) {
logger.error("error on metaData cleanup after test", t);
}
});
latch.await();
}
protected void putLicense(String feature, TimeValue expiryDuration) throws Exception {
License license1 = generateSignedLicense(feature, expiryDuration);
final PutLicenseResponse putLicenseResponse = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE).setLicense(Lists.newArrayList(license1)).get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
}
protected void assertLicenseManagerEnabledFeatureFor(final String feature) throws InterruptedException {
assertLicenseManagerStatusFor(feature, true);
}
protected void assertLicenseManagerDisabledFeatureFor(final String feature) throws InterruptedException {
assertLicenseManagerStatusFor(feature, false);
}
protected void assertLicenseManagerStatusFor(final String feature, final boolean expectedEnabled) throws InterruptedException {
assertThat("LicensesManagerService for feature " + feature + " should have enabled status of " + expectedEnabled, awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
for (LicensesManagerService managerService : licensesManagerServices()) {
if (expectedEnabled != managerService.enabledFeatures().contains(feature)) {
return false;
}
}
return true;
}
}, 5, TimeUnit.SECONDS), equalTo(true));
}
protected void assertEagerConsumerPluginDisableNotification(int timeoutInSec) throws InterruptedException {
assertEagerConsumerPluginNotification(false, timeoutInSec);
}
protected void assertEagerConsumerPluginEnableNotification(int timeoutInSec) throws InterruptedException {
assertEagerConsumerPluginNotification(true, timeoutInSec);
}
protected void assertLazyConsumerPluginDisableNotification(int timeoutInSec) throws InterruptedException {
assertLazyConsumerPluginNotification(false, timeoutInSec);
}
protected void assertLazyConsumerPluginEnableNotification(int timeoutInSec) throws InterruptedException {
assertLazyConsumerPluginNotification(true, timeoutInSec);
}
protected void assertLazyConsumerPluginNotification(final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
final List<TestPluginServiceBase> consumerPluginServices = consumerLazyPluginServices();
assertConsumerPluginNotification(consumerPluginServices, expectedEnabled, timeoutInSec);
}
protected void assertEagerConsumerPluginNotification(final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
final List<TestPluginServiceBase> consumerPluginServices = consumerEagerPluginServices();
assertConsumerPluginNotification(consumerPluginServices, expectedEnabled, timeoutInSec);
}
protected void assertConsumerPluginNotification(final List<TestPluginServiceBase> consumerPluginServices, final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
assertThat("At least one instance has to be present", consumerPluginServices.size(), greaterThan(0));
assertConsumerPluginNotification(consumerPluginServices.get(0).getClass().getName() + " should have license status of: " + expectedEnabled, consumerPluginServices, expectedEnabled, timeoutInSec);
}
private void assertConsumerPluginNotification(String msg, final Iterable<TestPluginServiceBase> consumerPluginServices, final boolean expectedEnabled, int timeoutInSec) throws InterruptedException {
boolean success = awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
for (TestPluginServiceBase pluginService : consumerPluginServices) {
if (expectedEnabled != pluginService.enabled()) {
return false;
}
}
return true;
}
}, timeoutInSec + 1, TimeUnit.SECONDS);
logger.debug("Notification assertion complete");
assertThat(msg, success, equalTo(true));
}
private List<TestPluginServiceBase> consumerLazyPluginServices() {
final InternalTestCluster clients = internalCluster();
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
for (TestPluginServiceBase service : clients.getDataNodeInstances(LazyLicenseRegistrationPluginService.class)) {
consumerPluginServices.add(service);
}
return consumerPluginServices;
}
private List<TestPluginServiceBase> consumerEagerPluginServices() {
final InternalTestCluster clients = internalCluster();
List<TestPluginServiceBase> consumerPluginServices = new ArrayList<>();
for (TestPluginServiceBase service : clients.getDataNodeInstances(EagerLicenseRegistrationPluginService.class)) {
consumerPluginServices.add(service);
}
return consumerPluginServices;
}
private Iterable<LicensesManagerService> licensesManagerServices() {
final InternalTestCluster clients = internalCluster();
return clients.getDataNodeInstances(LicensesManagerService.class);
}
}

View File

@ -1,195 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequest;
import org.elasticsearch.license.plugin.core.LicensesClientService;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.test.InternalTestCluster;
import org.junit.Before;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static org.elasticsearch.license.plugin.core.LicensesService.LicensesUpdateResponse;
import static org.hamcrest.Matchers.equalTo;
public abstract class AbstractLicensesServiceTests extends AbstractLicensesIntegrationTests {
private static String node = null;
private static String[] nodes;
@Before
public void beforeTest() throws Exception {
wipeAllLicenses();
DiscoveryNodes discoveryNodes = masterClusterService().state().getNodes();
Set<String> dataNodeSet = new HashSet<>();
for (DiscoveryNode discoveryNode : discoveryNodes) {
if (discoveryNode.dataNode()) {
dataNodeSet.add(discoveryNode.getName());
}
}
nodes = dataNodeSet.toArray(new String[dataNodeSet.size()]);
node = nodes[randomIntBetween(0, nodes.length - 1)];
}
protected void registerAndAckSignedLicenses(final LicensesManagerService masterLicensesManagerService, final List<License> license, final LicensesStatus expectedStatus) {
PutLicenseRequest putLicenseRequest = new PutLicenseRequest().licenses(license);
LicensesService.PutLicenseRequestHolder requestHolder = new LicensesService.PutLicenseRequestHolder(putLicenseRequest, "test");
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
masterLicensesManagerService.registerLicenses(requestHolder, new ActionListener<LicensesUpdateResponse>() {
@Override
public void onResponse(LicensesUpdateResponse licensesUpdateResponse) {
if (licensesUpdateResponse.isAcknowledged() && licensesUpdateResponse.status() == expectedStatus) {
success.set(true);
}
latch.countDown();
}
@Override
public void onFailure(Throwable e) {
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
fail(e.getMessage());
}
assertThat("register license(s) failed", success.get(), equalTo(true));
}
protected Action registerWithTrialLicense(final LicensesClientService clientService, final LicensesClientService.Listener clientListener, final String feature, final TimeValue expiryDuration) {
return new Action(new Runnable() {
@Override
public void run() {
clientService.register(feature, new LicensesService.TrialLicenseOptions(expiryDuration, 10), Collections.<LicensesService.ExpirationCallback>emptyList(),
clientListener);
// invoke clusterChanged event to flush out pendingRegistration
LicensesService licensesService = (LicensesService) clientService;
ClusterChangedEvent event = new ClusterChangedEvent("", clusterService().state(), clusterService().state());
licensesService.clusterChanged(event);
}
}, 0, 1, "should trigger onEnable for " + feature + " once [trial license]");
}
protected Action registerWithEventNotification(final LicensesClientService clientService, final LicensesClientService.Listener clientListener, final String feature, final TimeValue expiryDuration, final Collection<LicensesService.ExpirationCallback> expirationCallbacks) {
return new Action(new Runnable() {
@Override
public void run() {
clientService.register(feature, new LicensesService.TrialLicenseOptions(expiryDuration, 10), expirationCallbacks,
clientListener);
// invoke clusterChanged event to flush out pendingRegistration
LicensesService licensesService = (LicensesService) clientService;
ClusterChangedEvent event = new ClusterChangedEvent("", clusterService().state(), clusterService().state());
licensesService.clusterChanged(event);
}
}, 0, 1, "should trigger onEnable for " + feature + " once [trial license]");
}
protected class TestTrackingClientListener implements LicensesClientService.Listener {
CountDownLatch enableLatch;
CountDownLatch disableLatch;
AtomicBoolean enabled = new AtomicBoolean(false);
final boolean track;
final String featureName;
public TestTrackingClientListener(String featureName) {
this(featureName, true);
}
public TestTrackingClientListener(String featureName, boolean track) {
this.track = track;
this.featureName = featureName;
}
public synchronized void latch(CountDownLatch enableLatch, CountDownLatch disableLatch) {
this.enableLatch = enableLatch;
this.disableLatch = disableLatch;
}
@Override
public void onEnabled(License license) {
assertNotNull(license);
assertThat(license.feature(), equalTo(featureName));
enabled.set(true);
if (track) {
this.enableLatch.countDown();
}
}
@Override
public void onDisabled(License license) {
assertNotNull(license);
assertThat(license.feature(), equalTo(featureName));
enabled.set(false);
if (track) {
this.disableLatch.countDown();
}
}
}
protected class Action {
final int expectedDisabledCount;
final int expectedEnabledCount;
final TimeValue timeout;
final Runnable action;
final String msg;
protected Action(Runnable action, int expectedEnabledCount, int expectedDisabledCount, String msg) {
this(action, expectedEnabledCount, expectedDisabledCount, TimeValue.timeValueSeconds(1), msg);
}
protected Action(Runnable action, int expectedDisabledCount, int expectedEnabledCount, TimeValue timeout, String msg) {
this.expectedDisabledCount = expectedDisabledCount;
this.expectedEnabledCount = expectedEnabledCount;
this.action = action;
this.timeout = timeout;
this.msg = msg;
}
public void run() {
action.run();
}
}
protected LicensesManagerService masterLicensesManagerService() {
final InternalTestCluster clients = internalCluster();
return clients.getInstance(LicensesManagerService.class, clients.getMasterName());
}
protected LicensesClientService licensesClientService() {
final InternalTestCluster clients = internalCluster();
return internalCluster().getInstance(LicensesClientService.class, clients.getMasterName());
}
protected LicensesService licensesService() {
final InternalTestCluster clients = internalCluster();
return internalCluster().getInstance(LicensesService.class, clients.getMasterName());
}
protected static ClusterService masterClusterService() {
final InternalTestCluster clients = internalCluster();
return clients.getInstance(ClusterService.class, clients.getMasterName());
}
}

View File

@ -1,467 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.base.Predicate;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.*;
import org.junit.Test;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.Matchers.*;
@ClusterScope(scope = TEST, numDataNodes = 1)
public class LicensesClientServiceTests extends AbstractLicensesServiceTests {
@Test
public void testTrialLicenseEnforcement() throws Exception {
// register with trial license and assert onEnable and onDisable notification
final LicensesClientService clientService = licensesClientService();
String feature1 = "feature1";
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature1);
List<Action> actions = new ArrayList<>();
final TimeValue expiryDuration = TimeValue.timeValueSeconds(2);
actions.add(registerWithTrialLicense(clientService, clientListener, feature1, expiryDuration));
actions.add(assertExpiryAction(feature1, "trial", expiryDuration));
assertClientListenerNotificationCount(clientListener, actions);
}
@Test
public void testLicenseWithFutureIssueDate() throws Exception {
final LicensesClientService clientService = licensesClientService();
String feature = "feature";
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
List<Action> actions = new ArrayList<>();
long now = System.currentTimeMillis();
long issueDate = dateMath("now+10d/d", now);
actions.add(registerWithoutTrialLicense(clientService, clientListener, feature));
actions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService(), feature, issueDate, TimeValue.timeValueHours(24 * 20)));
actions.add(assertExpiryAction(feature, "signed", TimeValue.timeValueSeconds(4)));
assertClientListenerNotificationCount(clientListener, actions);
assertThat(clientListener.enabled.get(), equalTo(false));
}
@Test
public void testPostExpiration() throws Exception {
int postExpirySeconds = randomIntBetween(5, 10);
TimeValue postExpiryDuration = TimeValue.timeValueSeconds(postExpirySeconds);
TimeValue min = TimeValue.timeValueSeconds(postExpirySeconds - randomIntBetween(1, 3));
TimeValue max = TimeValue.timeValueSeconds(postExpirySeconds + randomIntBetween(1, 10));
final LicensesService.ExpirationCallback.Post post = new LicensesService.ExpirationCallback.Post(min, max, TimeValue.timeValueMillis(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
}
};
long now = System.currentTimeMillis();
assertThat(post.matches(now - postExpiryDuration.millis(), now), equalTo(true));
assertThat(post.matches(now + postExpiryDuration.getMillis(), now), equalTo(false));
}
@Test
public void testPostExpirationWithNullMax() throws Exception {
int postExpirySeconds = randomIntBetween(5, 10);
TimeValue postExpiryDuration = TimeValue.timeValueSeconds(postExpirySeconds);
TimeValue min = TimeValue.timeValueSeconds(postExpirySeconds - randomIntBetween(1, 3));
final LicensesService.ExpirationCallback.Post post = new LicensesService.ExpirationCallback.Post(min, null, TimeValue.timeValueMillis(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
}
};
long now = System.currentTimeMillis();
assertThat(post.matches(now - postExpiryDuration.millis(), now), equalTo(true));
}
@Test
public void testPreExpirationWithNullMin() throws Exception {
int expirySeconds = randomIntBetween(5, 10);
TimeValue expiryDuration = TimeValue.timeValueSeconds(expirySeconds);
TimeValue max = TimeValue.timeValueSeconds(expirySeconds + randomIntBetween(1, 10));
final LicensesService.ExpirationCallback.Pre pre = new LicensesService.ExpirationCallback.Pre(null, max, TimeValue.timeValueMillis(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
}
};
long now = System.currentTimeMillis();
assertThat(pre.matches(expiryDuration.millis() + now, now), equalTo(true));
}
@Test
public void testPreExpiration() throws Exception {
int expirySeconds = randomIntBetween(5, 10);
TimeValue expiryDuration = TimeValue.timeValueSeconds(expirySeconds);
TimeValue min = TimeValue.timeValueSeconds(expirySeconds - randomIntBetween(0, 3));
TimeValue max = TimeValue.timeValueSeconds(expirySeconds + randomIntBetween(1, 10));
final LicensesService.ExpirationCallback.Pre pre = new LicensesService.ExpirationCallback.Pre(min, max, TimeValue.timeValueMillis(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
}
};
long now = System.currentTimeMillis();
assertThat(pre.matches(expiryDuration.millis() + now, now), equalTo(true));
assertThat(pre.matches(now - expiryDuration.getMillis(), now), equalTo(false));
}
@Test
public void testMultipleEventNotification() throws Exception {
final LicensesManagerService licensesManagerService = masterLicensesManagerService();
final LicensesClientService clientService = licensesClientService();
final String feature = "feature";
TestTrackingClientListener clientListener = new TestTrackingClientListener(feature, true);
List<LicensesService.ExpirationCallback> callbacks = new ArrayList<>();
final AtomicInteger preTriggerCountStartingFromOneSecond = new AtomicInteger(0);
callbacks.add(preCallbackLatch(TimeValue.timeValueMillis(499), TimeValue.timeValueMillis(999), TimeValue.timeValueMillis(100), preTriggerCountStartingFromOneSecond));
final AtomicInteger postTriggerCount = new AtomicInteger(0);
callbacks.add(postCallbackLatch(TimeValue.timeValueMillis(10), null, TimeValue.timeValueMillis(200), postTriggerCount));
final AtomicInteger preTriggerCountStartingFromTwoSeconds = new AtomicInteger(0);
callbacks.add(preCallbackLatch(TimeValue.timeValueSeconds(1), TimeValue.timeValueSeconds(2), TimeValue.timeValueMillis(500), preTriggerCountStartingFromTwoSeconds));
List<Action> actions = new ArrayList<>();
actions.add(registerWithEventNotification(clientService, clientListener, feature, TimeValue.timeValueSeconds(3), callbacks));
actions.add(assertExpiryAction(feature, "trial", TimeValue.timeValueMinutes(1)));
assertClientListenerNotificationCount(clientListener, actions);
assertThat(preTriggerCountStartingFromTwoSeconds.get(), greaterThanOrEqualTo(2));
assertThat(preTriggerCountStartingFromTwoSeconds.get(), lessThan(4));
assertThat(preTriggerCountStartingFromOneSecond.get(), greaterThan(4));
int initialPreTriggerStartingFromTwoSeconds = preTriggerCountStartingFromTwoSeconds.get();
int initialPreTriggerCountStartingFromOneSecond = preTriggerCountStartingFromOneSecond.get();
assertThat(awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
if (postTriggerCount.get() > 2) {
return true;
}
return false;
}
}, 2, TimeUnit.SECONDS), equalTo(true));
int previousTriggerCount = postTriggerCount.get();
// Update license
generateAndPutSignedLicenseAction(licensesManagerService, feature, TimeValue.timeValueSeconds(10)).run();
assertThat(previousTriggerCount, lessThanOrEqualTo(postTriggerCount.get() + 1));
assertThat(initialPreTriggerCountStartingFromOneSecond, equalTo(preTriggerCountStartingFromOneSecond.get()));
assertThat(initialPreTriggerStartingFromTwoSeconds, equalTo(preTriggerCountStartingFromTwoSeconds.get()));
}
@Test
public void testPreEventNotification() throws Exception {
final LicensesClientService clientService = licensesClientService();
final String feature = "feature";
TestTrackingClientListener clientListener = new TestTrackingClientListener(feature, true);
AtomicInteger counter = new AtomicInteger(0);
List<Action> actions = new ArrayList<>();
actions.add(
registerWithEventNotification(clientService, clientListener, feature, TimeValue.timeValueSeconds(3),
Arrays.asList(
preCallbackLatch(TimeValue.timeValueMillis(500), TimeValue.timeValueSeconds(2), TimeValue.timeValueMillis(500), counter)
))
);
actions.add(assertExpiryAction(feature, "trial", TimeValue.timeValueSeconds(3)));
assertClientListenerNotificationCount(clientListener, actions);
assertThat(counter.get(), greaterThanOrEqualTo(3));
assertThat(counter.get(), lessThan(5));
}
@Test
public void testPostEventNotification() throws Exception {
final LicensesClientService clientService = licensesClientService();
final String feature = "feature";
TestTrackingClientListener clientListener = new TestTrackingClientListener(feature, true);
AtomicInteger counter = new AtomicInteger(0);
List<Action> actions = new ArrayList<>();
actions.add(
registerWithEventNotification(clientService, clientListener, feature, TimeValue.timeValueSeconds(1),
Arrays.asList(
postCallbackLatch(TimeValue.timeValueMillis(500), TimeValue.timeValueSeconds(2), TimeValue.timeValueMillis(500), counter)
))
);
actions.add(assertExpiryAction(feature, "trial", TimeValue.timeValueSeconds(1)));
assertClientListenerNotificationCount(clientListener, actions);
Thread.sleep(50 + 2000);
assertThat(counter.get(), greaterThanOrEqualTo(3));
assertThat(counter.get(), lessThan(5));
}
private LicensesService.ExpirationCallback preCallbackLatch(TimeValue min, TimeValue max, TimeValue frequency, final AtomicInteger triggerCount) {
return new LicensesService.ExpirationCallback.Pre(min, max, frequency) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
triggerCount.incrementAndGet();
}
};
}
private LicensesService.ExpirationCallback postCallbackLatch(TimeValue min, TimeValue max, TimeValue frequency, final AtomicInteger triggerCount) {
return new LicensesService.ExpirationCallback.Post(min, max, frequency) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
triggerCount.incrementAndGet();
}
};
}
@Test
public void testMultipleClientSignedLicenseEnforcement() throws Exception {
// multiple client registration with null trial license and then different expiry signed license
final LicensesManagerService masterLicensesManagerService = masterLicensesManagerService();
final LicensesService licensesService = licensesService();
String feature1 = "feature1";
String feature2 = "feature2";
final TestTrackingClientListener clientListener1 = new TestTrackingClientListener(feature1);
final TestTrackingClientListener clientListener2 = new TestTrackingClientListener(feature2);
List<Action> firstClientActions = new ArrayList<>();
List<Action> secondClientActions = new ArrayList<>();
final TimeValue firstExpiryDuration = TimeValue.timeValueSeconds(5);
firstClientActions.add(registerWithoutTrialLicense(licensesService, clientListener1, feature1));
firstClientActions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature1, firstExpiryDuration));
firstClientActions.add(assertExpiryAction(feature1, "signed", firstExpiryDuration));
final TimeValue secondExpiryDuration = TimeValue.timeValueSeconds(4);
secondClientActions.add(registerWithoutTrialLicense(licensesService, clientListener2, feature2));
secondClientActions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature2, secondExpiryDuration));
secondClientActions.add(assertExpiryAction(feature2, "signed", secondExpiryDuration));
if (randomBoolean()) {
assertClientListenerNotificationCount(clientListener1, firstClientActions);
assertClientListenerNotificationCount(clientListener2, secondClientActions);
} else {
assertClientListenerNotificationCount(clientListener2, secondClientActions);
assertClientListenerNotificationCount(clientListener1, firstClientActions);
}
}
@Test
public void testMultipleClientTrialAndSignedLicenseEnforcement() throws Exception {
// multiple client registration: one with trial license and another with signed license (different expiry duration)
final LicensesManagerService masterLicensesManagerService = masterLicensesManagerService();
final LicensesService licensesService = licensesService();
String feature1 = "feature1";
String feature2 = "feature2";
final TestTrackingClientListener clientListener1 = new TestTrackingClientListener(feature1);
final TestTrackingClientListener clientListener2 = new TestTrackingClientListener(feature2);
List<Action> firstClientActions = new ArrayList<>();
List<Action> secondClientActions = new ArrayList<>();
final TimeValue firstExpiryDuration = TimeValue.timeValueSeconds(5);
firstClientActions.add(registerWithoutTrialLicense(licensesService, clientListener1, feature1));
firstClientActions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature1, firstExpiryDuration));
firstClientActions.add(assertExpiryAction(feature1, "signed", firstExpiryDuration));
final TimeValue secondExpiryDuration = TimeValue.timeValueSeconds(4);
secondClientActions.add(registerWithTrialLicense(licensesService, clientListener2, feature2, secondExpiryDuration));
secondClientActions.add(assertExpiryAction(feature2, "trial", secondExpiryDuration));
if (randomBoolean()) {
assertClientListenerNotificationCount(clientListener1, firstClientActions);
assertClientListenerNotificationCount(clientListener2, secondClientActions);
} else {
assertClientListenerNotificationCount(clientListener2, secondClientActions);
assertClientListenerNotificationCount(clientListener1, firstClientActions);
}
}
@Test
public void testMultipleClientTrialLicenseRegistration() throws Exception {
// multiple client registration: both with trail license of different expiryDuration
final LicensesService licensesService = licensesService();
String feature1 = "feature1";
String feature2 = "feature2";
final TestTrackingClientListener clientListener1 = new TestTrackingClientListener(feature1);
final TestTrackingClientListener clientListener2 = new TestTrackingClientListener(feature2);
List<Action> firstClientActions = new ArrayList<>();
List<Action> secondClientActions = new ArrayList<>();
TimeValue firstExpiryDuration = TimeValue.timeValueSeconds(5);
firstClientActions.add(registerWithTrialLicense(licensesService, clientListener1, feature1, firstExpiryDuration));
firstClientActions.add(assertExpiryAction(feature1, "trial", firstExpiryDuration));
TimeValue secondExpiryDuration = TimeValue.timeValueSeconds(4);
secondClientActions.add(registerWithTrialLicense(licensesService, clientListener2, feature2, secondExpiryDuration));
secondClientActions.add(assertExpiryAction(feature2, "trial", secondExpiryDuration));
if (randomBoolean()) {
assertClientListenerNotificationCount(clientListener1, firstClientActions);
assertClientListenerNotificationCount(clientListener2, secondClientActions);
} else {
assertClientListenerNotificationCount(clientListener2, secondClientActions);
assertClientListenerNotificationCount(clientListener1, firstClientActions);
}
}
@Test
public void testFeatureWithoutLicense() throws Exception {
// client registration with no trial license + no signed license
final LicensesClientService clientService = licensesClientService();
String feature = "feature1";
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
List<Action> actions = new ArrayList<>();
actions.add(registerWithoutTrialLicense(clientService, clientListener, feature));
assertClientListenerNotificationCount(clientListener, actions);
}
@Test
public void testLicenseExpiry() throws Exception {
final LicensesClientService clientService = licensesClientService();
String feature = "feature1";
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
List<Action> actions = new ArrayList<>();
TimeValue expiryDuration = TimeValue.timeValueSeconds(2);
actions.add(registerWithTrialLicense(clientService, clientListener, feature, expiryDuration));
actions.add(assertExpiryAction(feature, "trial", expiryDuration));
assertClientListenerNotificationCount(clientListener, actions);
}
@Test
public void testRandomActionSequenceMultipleFeature() throws Exception {
LicensesService licensesService = licensesService();
LicensesManagerService masterLicensesManagerService = masterLicensesManagerService();
Map<TestTrackingClientListener, List<Action>> clientListenersWithActions = new HashMap<>();
TimeValue expiryDuration = TimeValue.timeValueSeconds(0);
for (int i = 0; i < randomIntBetween(5, 10); i++) {
String feature = "feature_" + String.valueOf(i);
final TestTrackingClientListener clientListener = new TestTrackingClientListener(feature);
expiryDuration = TimeValue.timeValueMillis(randomIntBetween(2, 4) * 1000l + expiryDuration.millis());
List<Action> actions = new ArrayList<>();
if (randomBoolean()) {
actions.add(registerWithTrialLicense(licensesService, clientListener, feature, expiryDuration));
actions.add(assertExpiryAction(feature, "trial", expiryDuration));
} else {
actions.add(registerWithoutTrialLicense(licensesService, clientListener, feature));
actions.add(generateAndPutSignedLicenseAction(masterLicensesManagerService, feature, expiryDuration));
actions.add(assertExpiryAction(feature, "signed", expiryDuration));
}
clientListenersWithActions.put(clientListener, actions);
}
for (Map.Entry<TestTrackingClientListener, List<Action>> entry : clientListenersWithActions.entrySet()) {
assertClientListenerNotificationCount(entry.getKey(), entry.getValue());
}
}
private Action generateAndPutSignedLicenseAction(final LicensesManagerService masterLicensesManagerService, final String feature, final TimeValue expiryDuration) throws Exception {
return generateAndPutSignedLicenseAction(masterLicensesManagerService, feature, -1, expiryDuration);
}
private Action generateAndPutSignedLicenseAction(final LicensesManagerService masterLicensesManagerService, final String feature, final long issueDate, final TimeValue expiryDuration) throws Exception {
return new Action(new Runnable() {
@Override
public void run() {
License license;
try {
license = generateSignedLicense(feature, issueDate, expiryDuration);
} catch (Exception e) {
fail(e.getMessage());
return;
}
registerAndAckSignedLicenses(masterLicensesManagerService, Arrays.asList(license), LicensesStatus.VALID);
}
}, 0, (issueDate <= System.currentTimeMillis()) ? 1 : 0, "should trigger onEnable for " + feature + " once [signed license]");
}
private Action registerWithoutTrialLicense(final LicensesClientService clientService, final LicensesClientService.Listener clientListener, final String feature) {
return new Action(new Runnable() {
@Override
public void run() {
clientService.register(feature, null, Collections.<LicensesService.ExpirationCallback>emptyList(), clientListener);
}
}, 0, 0, "should not trigger any notification [disabled by default]");
}
private Action assertExpiryAction(String feature, String licenseType, TimeValue expiryDuration) {
return new Action(new Runnable() {
@Override
public void run() {
}
}, 1, 0, TimeValue.timeValueMillis(expiryDuration.getMillis() * 2),
"should trigger onDisable for " + feature + " once [" + licenseType + " license expiry]");
}
private void assertClientListenerNotificationCount(final TestTrackingClientListener clientListener, List<Action> actions) throws Exception {
final AtomicInteger expectedEnabledCount = new AtomicInteger(0);
final AtomicInteger expectedDisableCount = new AtomicInteger(0);
for (final Action action : actions) {
expectedEnabledCount.addAndGet(action.expectedEnabledCount);
expectedDisableCount.addAndGet(action.expectedDisabledCount);
}
final CountDownLatch enableLatch = new CountDownLatch(expectedEnabledCount.get());
final CountDownLatch disableLatch = new CountDownLatch(expectedDisableCount.get());
final AtomicLong cumulativeTimeoutMillis = new AtomicLong(0);
clientListener.latch(enableLatch, disableLatch);
for (final Action action : actions) {
action.run();
cumulativeTimeoutMillis.addAndGet(action.timeout.getMillis());
}
if (expectedDisableCount.get() > 0) {
assertThat(getActionMsg(true, enableLatch.getCount(), actions), enableLatch.await((cumulativeTimeoutMillis.get() * 2), TimeUnit.MILLISECONDS), equalTo(true));
}
if (expectedDisableCount.get() > 0) {
assertThat(getActionMsg(false, disableLatch.getCount(), actions), disableLatch.await((cumulativeTimeoutMillis.get() * 2), TimeUnit.MILLISECONDS), equalTo(true));
}
}
private static String getActionMsg(final boolean enabledCount, final long latchCount, final List<Action> actions) {
LicensesMetaData licensesMetaData = masterClusterService().state().metaData().custom(LicensesMetaData.TYPE);
Set<String> featureLicenses = new HashSet<>();
if (licensesMetaData != null) {
for (License license : licensesMetaData.getSignedLicenses()) {
featureLicenses.add(license.feature());
}
for (License license : licensesMetaData.getTrialLicenses()) {
featureLicenses.add(license.feature());
}
}
StringBuilder featureLicensesString = new StringBuilder();
for (String featureLicense : featureLicenses) {
if (featureLicensesString.length() != 0) {
featureLicensesString.append(", ");
}
featureLicensesString.append(featureLicense);
}
AtomicLong cumulativeCount = new AtomicLong(0);
for (Action action : actions) {
cumulativeCount.addAndGet((enabledCount) ? action.expectedEnabledCount : action.expectedDisabledCount);
if (latchCount <= cumulativeCount.get()) {
return action.msg + "\n Current licenses in cluster state: [" + featureLicensesString.toString() + "]";
}
}
return "there should be no errors";
}
}

View File

@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
public class LicensesEagerConsumerPluginIntegrationTests extends AbstractLicensesConsumerPluginIntegrationTests {
public LicensesEagerConsumerPluginIntegrationTests() {
super(new EagerLicenseRegistrationConsumerPlugin(Settings.EMPTY));
}
}

View File

@ -1,16 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
public class LicensesLazyConsumerPluginIntegrationTests extends AbstractLicensesConsumerPluginIntegrationTests {
public LicensesLazyConsumerPluginIntegrationTests() {
super(new LazyLicenseRegistrationConsumerPlugin(Settings.EMPTY));
}
}

View File

@ -1,135 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.collect.Sets;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.core.*;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.Matchers.equalTo;
@ClusterScope(scope = TEST, numDataNodes = 10)
public class LicensesManagerServiceTests extends AbstractLicensesServiceTests {
@Test
public void testStoreAndGetLicenses() throws Exception {
LicensesManagerService licensesManagerService = masterLicensesManagerService();
License shieldShortLicense = generateSignedLicense("shield", TimeValue.timeValueHours(1));
License shieldLongLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2));
License marvelShortLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(1));
License marvelLongLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2));
List<License> licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense);
Collections.shuffle(licenses);
registerAndAckSignedLicenses(licensesManagerService, licenses, LicensesStatus.VALID);
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
// all licenses should be stored in the metaData
TestUtils.isSame(licenses, licensesMetaData.getSignedLicenses());
// only the latest expiry date license for each feature should be returned by getLicenses()
final List<License> getLicenses = licensesManagerService.getLicenses();
TestUtils.isSame(getLicenses, Arrays.asList(shieldLongLicense, marvelLongLicense));
}
@Test
public void testInvalidLicenseStorage() throws Exception {
LicensesManagerService licensesManagerService = masterLicensesManagerService();
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
// modify content of signed license
License tamperedLicense = License.builder()
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
.validate()
.build();
registerAndAckSignedLicenses(licensesManagerService, Arrays.asList(tamperedLicense), LicensesStatus.INVALID);
// ensure that the invalid license never made it to cluster state
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
if (licensesMetaData != null) {
assertThat(licensesMetaData.getSignedLicenses().size(), equalTo(0));
}
}
@Test
public void testRemoveLicenses() throws Exception {
LicensesManagerService licensesManagerService = masterLicensesManagerService();
// generate a trial license for one feature
final LicensesClientService clientService = licensesClientService();
final TestTrackingClientListener clientListener = new TestTrackingClientListener("shield", false);
registerWithTrialLicense(clientService, clientListener, "shield", TimeValue.timeValueHours(1)).run();
// generate signed licenses for multiple features
License shieldShortLicense = generateSignedLicense("shield", TimeValue.timeValueHours(1));
License shieldLongLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2));
License marvelShortLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(1));
License marvelLongLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2));
List<License> licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense);
Collections.shuffle(licenses);
registerAndAckSignedLicenses(licensesManagerService, licenses, LicensesStatus.VALID);
// remove license(s) for one feature out of two
removeAndAckSignedLicenses(licensesManagerService, Sets.newHashSet("shield"));
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
TestUtils.isSame(Arrays.asList(marvelLongLicense, marvelShortLicense), licensesMetaData.getSignedLicenses());
// check that trial license is not removed
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(1));
// remove license(s) for all features
removeAndAckSignedLicenses(licensesManagerService, Sets.newHashSet("shield", "marvel"));
licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licensesMetaData.getSignedLicenses().size(), equalTo(0));
// check that trial license is not removed
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(1));
}
private void removeAndAckSignedLicenses(final LicensesManagerService masterLicensesManagerService, final Set<String> featuresToDelete) {
DeleteLicenseRequest deleteLicenseRequest = new DeleteLicenseRequest(featuresToDelete.toArray(new String[featuresToDelete.size()]));
LicensesService.DeleteLicenseRequestHolder requestHolder = new LicensesService.DeleteLicenseRequestHolder(deleteLicenseRequest, "test");
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
masterLicensesManagerService.removeLicenses(requestHolder, new ActionListener<ClusterStateUpdateResponse>() {
@Override
public void onResponse(ClusterStateUpdateResponse clusterStateUpdateResponse) {
if (clusterStateUpdateResponse.isAcknowledged()) {
success.set(true);
}
latch.countDown();
}
@Override
public void onFailure(Throwable throwable) {
latch.countDown();
}
});
try {
latch.await();
} catch (InterruptedException e) {
fail(e.getMessage());
}
assertThat("remove license(s) failed", success.get(), equalTo(true));
}
}

View File

@ -1,152 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.elasticsearch.license.plugin.core.TrialLicenseUtils;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
public class LicensesMetaDataSerializationTests extends ElasticsearchTestCase {
@Test
public void testXContentSerializationOneSignedLicense() throws Exception {
License license = TestUtils.generateSignedLicense("feature", TimeValue.timeValueHours(2));
LicensesMetaData licensesMetaData = new LicensesMetaData(Arrays.asList(license), new ArrayList<License>());
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject("licensesMetaData");
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
byte[] serializedBytes = builder.bytes().toBytes();
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(1));
TestUtils.isSame(licensesMetaDataFromXContent.getSignedLicenses().get(0), license);
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(0));
}
@Test
public void testXContentSerializationManySignedLicense() throws Exception {
List<License> licenses = new ArrayList<>();
int n = randomIntBetween(2, 5);
for (int i = 0; i < n; i++) {
licenses.add(TestUtils.generateSignedLicense("feature__" + String.valueOf(i), TimeValue.timeValueHours(2)));
}
LicensesMetaData licensesMetaData = new LicensesMetaData(licenses, new ArrayList<License>());
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject("licensesMetaData");
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
byte[] serializedBytes = builder.bytes().toBytes();
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(n));
TestUtils.isSame(licensesMetaDataFromXContent.getSignedLicenses(), licenses);
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(0));
}
@Test
public void testXContentSerializationOneTrial() throws Exception {
final License trialLicense = TrialLicenseUtils.builder()
.feature("feature")
.duration(TimeValue.timeValueHours(2))
.maxNodes(5)
.issuedTo("customer")
.issueDate(System.currentTimeMillis())
.build();
LicensesMetaData licensesMetaData = new LicensesMetaData(new ArrayList<License>(), Arrays.asList(trialLicense));
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject("licensesMetaData");
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
byte[] serializedBytes = builder.bytes().toBytes();
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(1));
TestUtils.isSame(licensesMetaDataFromXContent.getTrialLicenses().iterator().next(), trialLicense);
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(0));
}
@Test
public void testXContentSerializationManyTrial() throws Exception {
final TrialLicenseUtils.TrialLicenseBuilder trialLicenseBuilder = TrialLicenseUtils.builder()
.duration(TimeValue.timeValueHours(2))
.maxNodes(5)
.issuedTo("customer")
.issueDate(System.currentTimeMillis());
int n = randomIntBetween(2, 5);
List<License> trialLicenses = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
trialLicenses.add(trialLicenseBuilder.feature("feature__" + String.valueOf(i)).build());
}
LicensesMetaData licensesMetaData = new LicensesMetaData(new ArrayList<License>(), trialLicenses);
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject("licensesMetaData");
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
byte[] serializedBytes = builder.bytes().toBytes();
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(n));
TestUtils.isSame(licensesMetaDataFromXContent.getTrialLicenses(), trialLicenses);
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(0));
}
@Test
public void testXContentSerializationManyTrialAndSignedLicenses() throws Exception {
final TrialLicenseUtils.TrialLicenseBuilder trialLicenseBuilder = TrialLicenseUtils.builder()
.duration(TimeValue.timeValueHours(2))
.maxNodes(5)
.issuedTo("customer")
.issueDate(System.currentTimeMillis());
int n = randomIntBetween(2, 5);
List<License> trialLicenses = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
trialLicenses.add(trialLicenseBuilder.feature("feature__" + String.valueOf(i)).build());
}
List<License> licenses = new ArrayList<>();
for (int i = 0; i < n; i++) {
licenses.add(TestUtils.generateSignedLicense("feature__" + String.valueOf(i), TimeValue.timeValueHours(2)));
}
LicensesMetaData licensesMetaData = new LicensesMetaData(licenses, trialLicenses);
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject("licensesMetaData");
licensesMetaData.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
byte[] serializedBytes = builder.bytes().toBytes();
LicensesMetaData licensesMetaDataFromXContent = getLicensesMetaDataFromXContent(serializedBytes);
assertThat(licensesMetaDataFromXContent.getTrialLicenses().size(), equalTo(n));
assertThat(licensesMetaDataFromXContent.getSignedLicenses().size(), equalTo(n));
TestUtils.isSame(licensesMetaDataFromXContent.getTrialLicenses(), trialLicenses);
TestUtils.isSame(licensesMetaDataFromXContent.getSignedLicenses(), licenses);
}
private static LicensesMetaData getLicensesMetaDataFromXContent(byte[] bytes) throws Exception {
final XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(bytes);
parser.nextToken(); // consume null
parser.nextToken(); // consume "licensesMetaData"
LicensesMetaData licensesMetaDataFromXContent = LicensesMetaData.PROTO.fromXContent(parser);
parser.nextToken(); // consume endObject
assertThat(parser.nextToken(), nullValue());
return licensesMetaDataFromXContent;
}
}

View File

@ -1,150 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.base.Predicate;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
import org.junit.After;
import org.junit.Test;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
@ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0)
public class LicensesPluginIntegrationTests extends AbstractLicensesIntegrationTests {
private final boolean useEagerLicenseRegistrationPlugin = randomBoolean();
private final int trialLicenseDurationInSeconds = 10;
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put(((useEagerLicenseRegistrationPlugin) ? EagerLicenseRegistrationConsumerPlugin.NAME : LazyLicenseRegistrationConsumerPlugin.NAME)
+ ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.putArray("plugin.types", LicensePlugin.class.getName(),
(useEagerLicenseRegistrationPlugin) ? EagerLicenseRegistrationConsumerPlugin.class.getName() : LazyLicenseRegistrationConsumerPlugin.class.getName())
.build();
}
@After
public void afterTest() throws Exception {
wipeAllLicenses();
assertThat(awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
return !clusterService().state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK);
}
}), equalTo(true));
}
@Test
public void testTrialLicenseAndSignedLicenseNotification() throws Exception {
logger.info("using " + ((useEagerLicenseRegistrationPlugin) ? "eager" : "lazy") + " consumer plugin");
logger.info(" --> trial license generated");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnabledNotification(2);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
logger.info(" --> put signed license");
putLicense(getCurrentFeatureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnabledNotification(1);
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
}
@Test
public void testTrialLicenseNotification() throws Exception {
logger.info(" --> check onEnabled for trial license");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnabledNotification(1);
logger.info(" --> sleep for rest of trailLicense duration");
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2);
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
}
@Test
public void testOverlappingTrialAndSignedLicenseNotification() throws Exception {
logger.info(" --> check onEnabled for trial license");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
// consumer plugin service should return enabled on all data nodes
assertConsumerPluginEnabledNotification(1);
logger.info(" --> put signed license while trial license is in effect");
putLicense(getCurrentFeatureName(), TimeValue.timeValueSeconds(trialLicenseDurationInSeconds * 2));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnabledNotification(1);
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
logger.info(" --> sleep for rest of trailLicense duration");
Thread.sleep(trialLicenseDurationInSeconds * 1000l);
logger.info(" --> check consumer is still enabled [signed license]");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertConsumerPluginEnabledNotification(1);
assertLicenseManagerEnabledFeatureFor(getCurrentFeatureName());
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertConsumerPluginDisabledNotification(trialLicenseDurationInSeconds * 2 * 2);
assertLicenseManagerDisabledFeatureFor(getCurrentFeatureName());
}
private String getCurrentFeatureName() {
if (useEagerLicenseRegistrationPlugin) {
return EagerLicenseRegistrationPluginService.FEATURE_NAME;
} else {
return LazyLicenseRegistrationPluginService.FEATURE_NAME;
}
}
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
if (useEagerLicenseRegistrationPlugin) {
assertEagerConsumerPluginEnableNotification(timeoutInSec);
} else {
assertLazyConsumerPluginEnableNotification(timeoutInSec);
}
}
private void assertConsumerPluginDisabledNotification(int timeoutInSec) throws InterruptedException {
if (useEagerLicenseRegistrationPlugin) {
assertEagerConsumerPluginDisableNotification(timeoutInSec);
} else {
assertLazyConsumerPluginDisableNotification(timeoutInSec);
}
}
}

View File

@ -1,176 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
import org.junit.After;
import org.junit.Test;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0)
public class LicensesPluginsIntegrationTests extends AbstractLicensesIntegrationTests {
private final String FEATURE_NAME_1 = EagerLicenseRegistrationPluginService.FEATURE_NAME;
private final String FEATURE_NAME_2 = LazyLicenseRegistrationPluginService.FEATURE_NAME;
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName(), LazyLicenseRegistrationConsumerPlugin.class.getName())
.build();
}
private Settings nodeSettingsWithConsumerPlugin(int consumer1TrialLicenseDuration, int consumer2TrialLicenseDuration) {
return Settings.settingsBuilder()
.put(super.nodeSettings(0))
.put(EagerLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", consumer1TrialLicenseDuration)
.put(LazyLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", consumer2TrialLicenseDuration)
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName(), LazyLicenseRegistrationConsumerPlugin.class.getName())
.build();
}
@After
public void afterTest() throws Exception {
wipeAllLicenses();
}
@Test
public void testWithNoTrialLicense() throws Exception {
int nNodes = randomIntBetween(2, 10);
startNodesWithConsumerPlugins(nNodes, -1, -1);
assertEagerConsumerPluginDisableNotification(1);
assertLazyConsumerPluginDisableNotification(1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
}
@Test
public void testOneTrialAndNonTrialConsumer() throws Exception {
int nNodes = randomIntBetween(2, 10);
int consumer2TrialLicenseDuration = 5;
startNodesWithConsumerPlugins(nNodes, -1, consumer2TrialLicenseDuration);
logger.info(" --> trial license generated for " + FEATURE_NAME_2 + " no trial license for " + FEATURE_NAME_1);
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
// consumer plugin service should return enabled on all data nodes
assertEagerConsumerPluginDisableNotification(1);
assertLazyConsumerPluginEnableNotification(1);
logger.info(" --> put signed license for " + FEATURE_NAME_1);
putLicense(FEATURE_NAME_1, TimeValue.timeValueSeconds(consumer2TrialLicenseDuration));
logger.info(" --> check that both " + FEATURE_NAME_1 + " and " + FEATURE_NAME_2 + " are enabled");
assertEagerConsumerPluginEnableNotification(1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertEagerConsumerPluginDisableNotification(consumer2TrialLicenseDuration * 2);
assertLazyConsumerPluginDisableNotification(consumer2TrialLicenseDuration * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
}
@Test
public void testMultipleConsumerPlugins() throws Exception {
int nNodes = randomIntBetween(2, 10);
int consumer1TrialLicenseExpiry = 5;
int consumer2TrialLicenseExpiry = 5;
startNodesWithConsumerPlugins(nNodes, consumer1TrialLicenseExpiry, consumer2TrialLicenseExpiry);
logger.info(" --> trial license generated");
// managerService should report feature to be enabled on all data nodes
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
// consumer plugin service should return enabled on all data nodes
assertEagerConsumerPluginEnableNotification(1);
assertLazyConsumerPluginEnableNotification(1);
logger.info(" --> check trial license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired trial license)
assertEagerConsumerPluginDisableNotification(consumer1TrialLicenseExpiry * 2);
assertLazyConsumerPluginDisableNotification(consumer2TrialLicenseExpiry * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
logger.info(" --> put signed license");
putLicense(FEATURE_NAME_1, TimeValue.timeValueSeconds(consumer1TrialLicenseExpiry));
putLicense(FEATURE_NAME_2, TimeValue.timeValueSeconds(consumer2TrialLicenseExpiry));
logger.info(" --> check signed license enabled notification");
// consumer plugin should notify onEnabled on all data nodes (signed license)
assertEagerConsumerPluginEnableNotification(1);
assertLazyConsumerPluginEnableNotification(1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
logger.info(" --> check signed license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertEagerConsumerPluginDisableNotification(consumer1TrialLicenseExpiry * 2);
assertLazyConsumerPluginDisableNotification(consumer2TrialLicenseExpiry * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
}
@Test
public void testRandomFeatureLicensesActions() throws Exception {
int nNodes = randomIntBetween(2, 10);
int trialLicenseDuration1 = rarely() ? -1 : randomIntBetween(6, 8);
int trialLicenseDuration2 = rarely() ? -1 : randomIntBetween(6, 8);
startNodesWithConsumerPlugins(nNodes, trialLicenseDuration1, trialLicenseDuration2);
if (trialLicenseDuration1 != -1) {
assertEagerConsumerPluginEnableNotification(trialLicenseDuration1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
} else {
assertEagerConsumerPluginDisableNotification(3 * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
putLicense(FEATURE_NAME_1, TimeValue.timeValueSeconds(5));
}
if (trialLicenseDuration2 != -1) {
assertLazyConsumerPluginEnableNotification(trialLicenseDuration2);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
} else {
assertLazyConsumerPluginDisableNotification(3 * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
putLicense(FEATURE_NAME_2, TimeValue.timeValueSeconds(5));
}
logger.info(" --> check license enabled notification");
assertEagerConsumerPluginEnableNotification(1);
assertLazyConsumerPluginEnableNotification(1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerEnabledFeatureFor(FEATURE_NAME_2);
logger.info(" --> check license expiry notification");
// consumer plugin should notify onDisabled on all data nodes (expired signed license)
assertEagerConsumerPluginDisableNotification(8 * 2);
assertLazyConsumerPluginDisableNotification(8 * 2);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_1);
assertLicenseManagerDisabledFeatureFor(FEATURE_NAME_2);
}
private void startNodesWithConsumerPlugins(int nNodes, int consumer1TrialLicenseDuration, int consumer2TrialLicenseDuration) {
for (int i = 0; i < nNodes; i++) {
internalCluster().startNode(nodeSettingsWithConsumerPlugin(consumer1TrialLicenseDuration, consumer2TrialLicenseDuration));
}
}
}

View File

@ -1,172 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.client.ClusterAdminClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationConsumerPlugin;
import org.elasticsearch.license.plugin.consumer.LazyLicenseRegistrationPluginService;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.node.Node;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
@ClusterScope(scope = TEST, numDataNodes = 0, numClientNodes = 0, maxNumDataNodes = 0, transportClientRatio = 0)
public class LicensesServiceClusterTest extends AbstractLicensesIntegrationTests {
private final String[] FEATURES = {EagerLicenseRegistrationPluginService.FEATURE_NAME, LazyLicenseRegistrationPluginService.FEATURE_NAME};
protected Settings transportClientSettings() {
return super.transportClientSettings();
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return nodeSettingsBuilder(nodeOrdinal).build();
}
private Settings.Builder nodeSettingsBuilder(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put("gateway.type", "local")
.put("plugins.load_classpath_plugins", false)
.put("node.data", true)
.put("format", "json")
.put(EagerLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", 2)
.put(LazyLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", 2)
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName(), LazyLicenseRegistrationConsumerPlugin.class.getName())
.put(Node.HTTP_ENABLED, true);
}
@Test
public void testClusterRestart() throws Exception {
wipeAllLicenses();
int numNodes = randomIntBetween(1, 5);
logger.info("--> starting " + numNodes + " node(s)");
for (int i = 0; i < numNodes; i++) {
internalCluster().startNode();
}
ensureGreen();
logger.info("--> put signed license");
final List<License> licenses = generateAndPutLicenses();
getAndCheckLicense(licenses);
logger.info("--> restart all nodes");
internalCluster().fullRestart();
ensureYellow();
logger.info("--> get and check signed license");
getAndCheckLicense(licenses);
wipeAllLicenses();
}
@Test
public void testClusterNotRecovered() throws Exception {
logger.info("--> start one master out of two [recovery state]");
internalCluster().startNode(nodeSettingsBuilder(0).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true));
// license plugin should not be active when cluster is still recovering
assertLicenseManagerFeatureDisabled();
assertConsumerPluginDisabledNotification(1);
logger.info("--> start second master out of two [recovered state]");
internalCluster().startNode(nodeSettingsBuilder(1).put("discovery.zen.minimum_master_nodes", 2).put("node.master", true));
assertLicenseManagerFeatureEnabled();
assertConsumerPluginEnabledNotification(1);
}
@Test
public void testAtMostOnceTrialLicenseGeneration() throws Exception {
wipeAllLicenses();
logger.info("--> start one node [trial license should be generated & enabled]");
internalCluster().startNode(nodeSettingsBuilder(0));
assertLicenseManagerFeatureEnabled();
assertConsumerPluginEnabledNotification(1);
logger.info("--> start another node [trial license should be propagated from the old master not generated]");
internalCluster().startNode(nodeSettings(1));
assertLicenseManagerFeatureEnabled();
assertConsumerPluginEnabledNotification(1);
logger.info("--> check if multiple trial licenses are found for a feature");
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(FEATURES.length));
wipeAllLicenses();
}
private List<License> generateAndPutLicenses() throws Exception {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
List<License> putLicenses = new ArrayList<>(FEATURES.length);
for (String feature : FEATURES) {
putLicenses.add(generateSignedLicense(feature, TimeValue.timeValueMinutes(1)));
}
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(cluster, PutLicenseAction.INSTANCE);
putLicenseRequestBuilder.setLicense(putLicenses);
ensureGreen();
final PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
return putLicenses;
}
private void getAndCheckLicense(List<License> licenses) {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
final GetLicenseResponse response = new GetLicenseRequestBuilder(cluster, GetLicenseAction.INSTANCE).get();
assertThat(response.licenses().size(), equalTo(licenses.size()));
TestUtils.isSame(licenses, response.licenses());
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licensesMetaData, notNullValue());
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(2));
}
private void assertLicenseManagerFeatureEnabled() throws Exception {
for (String feature : FEATURES) {
assertLicenseManagerEnabledFeatureFor(feature);
}
}
private void assertLicenseManagerFeatureDisabled() throws Exception {
for (String feature : FEATURES) {
assertLicenseManagerDisabledFeatureFor(feature);
}
}
private void assertConsumerPluginEnabledNotification(int timeoutInSec) throws InterruptedException {
assertEagerConsumerPluginEnableNotification(timeoutInSec);
assertLazyConsumerPluginEnableNotification(timeoutInSec);
}
private void assertConsumerPluginDisabledNotification(int timeoutInSec) throws InterruptedException {
assertEagerConsumerPluginDisableNotification(timeoutInSec);
assertLazyConsumerPluginDisableNotification(timeoutInSec);
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.base.Predicate;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationConsumerPlugin;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
import org.elasticsearch.node.Node;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.Matchers.equalTo;
/**
*/
@ElasticsearchIntegrationTest.ClusterScope(scope = TEST, numDataNodes = 10, numClientNodes = 0)
public class LicensesServiceNodeTests extends AbstractLicensesIntegrationTests {
@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put(EagerLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", 60 * 5)
.putArray("plugin.types", LicensePlugin.class.getName(), EagerLicenseRegistrationConsumerPlugin.class.getName())
.put(Node.HTTP_ENABLED, true)
.build();
}
@Test
@TestLogging("_root:DEBUG")
public void testPluginStatus() throws Exception {
final Iterable<EagerLicenseRegistrationPluginService> testPluginServices = internalCluster().getDataNodeInstances(EagerLicenseRegistrationPluginService.class);
assertThat(awaitBusy(new Predicate<Object>() {
@Override
public boolean apply(Object o) {
for (EagerLicenseRegistrationPluginService pluginService : testPluginServices) {
if (!pluginService.enabled()) {
return false;
}
}
return true;
}
}, 10, TimeUnit.SECONDS), equalTo(true));
}
}

View File

@ -1,265 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import com.google.common.collect.Sets;
import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseAction;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse;
import org.elasticsearch.license.plugin.action.get.GetLicenseAction;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
import org.elasticsearch.license.plugin.action.put.PutLicenseAction;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.junit.After;
import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.elasticsearch.license.plugin.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
@ClusterScope(scope = TEST, numDataNodes = 10)
public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@After
public void beforeTest() throws Exception {
wipeAllLicenses();
}
@Test
public void testEmptyGetLicense() throws Exception {
final ActionFuture<GetLicenseResponse> getLicenseFuture = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).execute();
final GetLicenseResponse getLicenseResponse = getLicenseFuture.get();
assertThat("expected 0 licenses; but got: " + getLicenseResponse.licenses().size(), getLicenseResponse.licenses().size(), equalTo(0));
}
@Test
public void testPutLicense() throws Exception {
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
List<License> actualLicenses = Collections.singletonList(signedLicense);
// put license
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
.setLicense(actualLicenses);
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// get license
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
// check license
TestUtils.isSame(signedLicense, getLicenseResponse.licenses().get(0));
}
@Test
public void testPutLicenseFromString() throws Exception {
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
String licenseString = TestUtils.dumpLicense(signedLicense);
// put license source
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
.setLicense(licenseString);
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// get license
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
// check license
TestUtils.isSame(signedLicense, getLicenseResponse.licenses().get(0));
}
@Test
public void testPutInvalidLicense() throws Exception {
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
// modify content of signed license
License tamperedLicense = License.builder()
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
.validate()
.build();
PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE);
builder.setLicense(Collections.singletonList(tamperedLicense));
// try to put license (should be invalid)
final PutLicenseResponse putLicenseResponse = builder.get();
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.INVALID));
// try to get invalid license
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses().size(), equalTo(0));
}
@Test
public void testPutExpiredLicense() throws Exception {
License expiredLicense = generateSignedLicense("feature", dateMath("now-10d/d", System.currentTimeMillis()), TimeValue.timeValueMinutes(2));
License signedLicense = generateSignedLicense("feature", TimeValue.timeValueMinutes(2));
PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE);
builder.setLicense(Arrays.asList(signedLicense, expiredLicense));
// put license should return valid (as there is one valid license)
final PutLicenseResponse putLicenseResponse = builder.get();
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// get license should not return the expired license
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
TestUtils.isSame(getLicenseResponse.licenses().get(0), signedLicense);
}
@Test
public void testPutLicensesForSameFeature() throws Exception {
License shortedSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License longerSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(5));
List<License> actualLicenses = Arrays.asList(longerSignedLicense, shortedSignedLicense);
// put license
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
.setLicense(actualLicenses);
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// get should return only one license (with longer expiry date)
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
// check license
TestUtils.isSame(longerSignedLicense, getLicenseResponse.licenses().get(0));
}
@Test
public void testPutLicensesForMultipleFeatures() throws Exception {
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<License> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
// put license
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
.setLicense(actualLicenses);
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// get should return both the licenses
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
// check license
TestUtils.isSame(actualLicenses, getLicenseResponse.licenses());
}
@Test
public void testPutMultipleLicensesForMultipleFeatures() throws Exception {
License shortedSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License longerSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(5));
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<License> actualLicenses = Arrays.asList(marvelLicense, shortedSignedLicense, longerSignedLicense);
// put license
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
.setLicense(actualLicenses);
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// get should return both the licenses
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(2));
// check license (should get the longest expiry time for all unique features)
TestUtils.isSame(Arrays.asList(marvelLicense, longerSignedLicense), getLicenseResponse.licenses());
}
@Test
public void testRemoveLicenseSimple() throws Exception {
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<License> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
// put two licenses
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
.setLicense(actualLicenses);
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// get and check licenses
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(2));
// delete all licenses
DeleteLicenseRequestBuilder deleteLicenseRequestBuilder = new DeleteLicenseRequestBuilder(client().admin().cluster(), DeleteLicenseAction.INSTANCE)
.setFeatures(Sets.newHashSet("shield", "marvel"));
DeleteLicenseResponse deleteLicenseResponse = deleteLicenseRequestBuilder.get();
assertThat(deleteLicenseResponse.isAcknowledged(), equalTo(true));
// get licenses (expected no licenses)
getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(0));
}
@Test
public void testRemoveLicenses() throws Exception {
License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<License> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
// put two licenses
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster(), PutLicenseAction.INSTANCE)
.setLicense(actualLicenses);
PutLicenseResponse putLicenseResponse = putLicenseRequestBuilder.get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));
// delete one license
DeleteLicenseRequestBuilder deleteLicenseRequestBuilder = new DeleteLicenseRequestBuilder(client().admin().cluster(), DeleteLicenseAction.INSTANCE)
.setFeatures(Sets.newHashSet("shield"));
DeleteLicenseResponse deleteLicenseResponse = deleteLicenseRequestBuilder.get();
assertThat(deleteLicenseResponse.isAcknowledged(), equalTo(true));
// check other license
GetLicenseResponse getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
// delete another license
deleteLicenseRequestBuilder = new DeleteLicenseRequestBuilder(client().admin().cluster(), DeleteLicenseAction.INSTANCE)
.setFeatures(Sets.newHashSet("marvel"));
deleteLicenseResponse = deleteLicenseRequestBuilder.get();
assertThat(deleteLicenseResponse.isAcknowledged(), equalTo(true));
// check no license
getLicenseResponse = new GetLicenseRequestBuilder(client().admin().cluster(), GetLicenseAction.INSTANCE).get();
assertThat(getLicenseResponse.licenses(), notNullValue());
assertThat(getLicenseResponse.licenses().size(), equalTo(0));
}
}

View File

@ -1,99 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.licensor.LicenseSigner;
import java.net.URL;
import java.util.*;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
public class TestUtils {
public static String getTestPriKeyPath() throws Exception {
return getResourcePath("/private.key");
}
public static String getTestPubKeyPath() throws Exception {
return getResourcePath("/public.key");
}
public static void isSame(Collection<License> firstLicenses, Collection<License> secondLicenses) {
isSame(new HashSet<>(firstLicenses), new HashSet<>(secondLicenses));
}
public static void isSame(Set<License> firstLicenses, Set<License> secondLicenses) {
// we do the verifyAndBuild to make sure we weed out any expired licenses
final Map<String, License> licenses1 = Licenses.reduceAndMap(firstLicenses);
final Map<String, License> licenses2 = Licenses.reduceAndMap(secondLicenses);
// check if the effective licenses have the same feature set
assertThat(licenses1.size(), equalTo(licenses2.size()));
// for every feature license, check if all the attributes are the same
for (String featureType : licenses1.keySet()) {
License license1 = licenses1.get(featureType);
License license2 = licenses2.get(featureType);
isSame(license1, license2);
}
}
public static void isSame(License license1, License license2) {
assertThat(license1.uid(), equalTo(license2.uid()));
assertThat(license1.feature(), equalTo(license2.feature()));
assertThat(license1.subscriptionType(), equalTo(license2.subscriptionType()));
assertThat(license1.type(), equalTo(license2.type()));
assertThat(license1.issuedTo(), equalTo(license2.issuedTo()));
assertThat(license1.signature(), equalTo(license2.signature()));
assertThat(license1.expiryDate(), equalTo(license2.expiryDate()));
assertThat(license1.issueDate(), equalTo(license2.issueDate()));
assertThat(license1.maxNodes(), equalTo(license2.maxNodes()));
}
public static String dumpLicense(License license) throws Exception {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
Licenses.toXContent(Collections.singletonList(license), builder, ToXContent.EMPTY_PARAMS);
builder.flush();
return builder.string();
}
public static License generateSignedLicense(String feature, TimeValue expiryDuration) throws Exception {
return generateSignedLicense(feature, -1, expiryDuration);
}
public static License generateSignedLicense(String feature, long issueDate, TimeValue expiryDuration) throws Exception {
long issue = (issueDate != -1l) ? issueDate : System.currentTimeMillis();
final License licenseSpec = License.builder()
.uid(UUID.randomUUID().toString())
.feature(feature)
.expiryDate(issue + expiryDuration.getMillis())
.issueDate(issue)
.type("subscription")
.subscriptionType("gold")
.issuedTo("customer")
.issuer("elasticsearch")
.maxNodes(5)
.build();
LicenseSigner signer = new LicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
return signer.sign(licenseSpec);
}
private static String getResourcePath(String resource) throws Exception {
URL url = TestUtils.class.getResource(resource);
return url.toURI().getPath();
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.TrialLicenseUtils;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class TrailLicenseSerializationTests extends ElasticsearchTestCase {
@Test
public void testSerialization() throws Exception {
final TrialLicenseUtils.TrialLicenseBuilder trialLicenseBuilder = TrialLicenseUtils.builder()
.duration(TimeValue.timeValueHours(2))
.maxNodes(5)
.issuedTo("customer")
.issueDate(System.currentTimeMillis());
int n = randomIntBetween(2, 5);
List<License> trialLicenses = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
trialLicenses.add(trialLicenseBuilder.feature("feature__" + String.valueOf(i)).build());
}
for (License trialLicense : trialLicenses) {
String encodedTrialLicense = TrialLicenseUtils.toEncodedTrialLicense(trialLicense);
final License fromEncodedTrialLicense = TrialLicenseUtils.fromEncodedTrialLicense(encodedTrialLicense);
TestUtils.isSame(fromEncodedTrialLicense, trialLicense);
}
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
/**
* Registers licenses upon the start of the service lifecycle
* see {@link EagerLicenseRegistrationPluginService}
* <p/>
* License registration might happen before clusterService start()
*/
public class EagerLicenseRegistrationConsumerPlugin extends TestConsumerPluginBase {
public final static String NAME = "test_consumer_plugin_1";
@Inject
public EagerLicenseRegistrationConsumerPlugin(Settings settings) {
super(settings);
}
@Override
public Class<? extends TestPluginServiceBase> service() {
return EagerLicenseRegistrationPluginService.class;
}
@Override
protected String pluginName() {
return NAME;
}
@Override
public String featureName() {
return EagerLicenseRegistrationPluginService.FEATURE_NAME;
}
}

View File

@ -1,32 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicensesClientService;
@Singleton
public class EagerLicenseRegistrationPluginService extends TestPluginServiceBase {
public static String FEATURE_NAME = "feature1";
@Inject
public EagerLicenseRegistrationPluginService(Settings settings, LicensesClientService licensesClientService) {
super(true, settings, licensesClientService, null);
}
@Override
public String featureName() {
return FEATURE_NAME;
}
@Override
public String settingPrefix() {
return EagerLicenseRegistrationConsumerPlugin.NAME;
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
/**
* Registers licenses only after cluster has recovered
* see {@link LazyLicenseRegistrationPluginService}
* <p/>
* License registration happens after clusterservice start()
*/
public class LazyLicenseRegistrationConsumerPlugin extends TestConsumerPluginBase {
public static String NAME = "test_consumer_plugin_2";
@Inject
public LazyLicenseRegistrationConsumerPlugin(Settings settings) {
super(settings);
}
@Override
public Class<? extends TestPluginServiceBase> service() {
return LazyLicenseRegistrationPluginService.class;
}
@Override
protected String pluginName() {
return NAME;
}
@Override
public String featureName() {
return LazyLicenseRegistrationPluginService.FEATURE_NAME;
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.plugin.core.LicensesClientService;
@Singleton
public class LazyLicenseRegistrationPluginService extends TestPluginServiceBase {
public static String FEATURE_NAME = "feature2";
@Inject
public LazyLicenseRegistrationPluginService(Settings settings, LicensesClientService licensesClientService, ClusterService clusterService) {
super(false, settings, licensesClientService, clusterService);
}
@Override
public String featureName() {
return FEATURE_NAME;
}
@Override
public String settingPrefix() {
return LazyLicenseRegistrationConsumerPlugin.NAME;
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import com.google.common.collect.Lists;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.plugins.AbstractPlugin;
import java.util.Collection;
public abstract class TestConsumerPluginBase extends AbstractPlugin {
private final boolean isEnabled;
public TestConsumerPluginBase(Settings settings) {
if (DiscoveryNode.clientNode(settings)) {
// Enable plugin only on node clients
this.isEnabled = "node".equals(settings.get(Client.CLIENT_TYPE_SETTING));
} else {
this.isEnabled = true;
}
}
@Override
public String name() {
return pluginName();
}
@Override
public String description() {
return "test licensing consumer plugin";
}
@Override
public Collection<Class<? extends LifecycleComponent>> services() {
Collection<Class<? extends LifecycleComponent>> services = Lists.newArrayList();
if (isEnabled) {
services.add(service());
}
return services;
}
public abstract Class<? extends TestPluginServiceBase> service();
protected abstract String pluginName();
public abstract String featureName();
}

View File

@ -1,147 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.consumer;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterStateListener;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService;
import org.elasticsearch.license.plugin.core.LicensesService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class TestPluginServiceBase extends AbstractLifecycleComponent<TestPluginServiceBase> implements ClusterStateListener {
private LicensesClientService licensesClientService;
private final ClusterService clusterService;
// specify the trial license spec for the feature
// example: 30 day trial on 1000 nodes
final LicensesService.TrialLicenseOptions trialLicenseOptions;
final boolean eagerLicenseRegistration;
final static Collection<LicensesService.ExpirationCallback> expirationCallbacks;
static {
// Callback triggered every 24 hours from 30 days to 7 days of license expiry
final LicensesService.ExpirationCallback.Pre LEVEL_1 = new LicensesService.ExpirationCallback.Pre(TimeValue.timeValueHours(7 * 24), TimeValue.timeValueHours(30 * 24), TimeValue.timeValueHours(24)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
}
};
// Callback triggered every 10 minutes from 7 days to license expiry
final LicensesService.ExpirationCallback.Pre LEVEL_2 = new LicensesService.ExpirationCallback.Pre(null, TimeValue.timeValueHours(7 * 24), TimeValue.timeValueMinutes(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
}
};
// Callback triggered every 10 minutes after license expiry
final LicensesService.ExpirationCallback.Post LEVEL_3 = new LicensesService.ExpirationCallback.Post(TimeValue.timeValueMillis(0), null, TimeValue.timeValueMinutes(10)) {
@Override
public void on(License license, LicensesService.ExpirationStatus status) {
}
};
expirationCallbacks = new ArrayList<>();
expirationCallbacks.add(LEVEL_1);
expirationCallbacks.add(LEVEL_2);
expirationCallbacks.add(LEVEL_3);
}
public final AtomicBoolean registered = new AtomicBoolean(false);
private volatile AtomicBoolean enabled = new AtomicBoolean(false);
public TestPluginServiceBase(boolean eagerLicenseRegistration, Settings settings, LicensesClientService licensesClientService, ClusterService clusterService) {
super(settings);
this.eagerLicenseRegistration = eagerLicenseRegistration;
this.licensesClientService = licensesClientService;
int durationInSec = settings.getAsInt(settingPrefix() + ".trial_license_duration_in_seconds", -1);
if (durationInSec == -1) {
this.trialLicenseOptions = null;
} else {
this.trialLicenseOptions = new LicensesService.TrialLicenseOptions(TimeValue.timeValueSeconds(durationInSec), 1000);
}
if (!eagerLicenseRegistration) {
this.clusterService = clusterService;
clusterService.add(this);
} else {
this.clusterService = null;
}
}
// should be the same string used by the license Manger to generate
// signed license
public abstract String featureName();
public abstract String settingPrefix();
// check if feature is enabled
public boolean enabled() {
return enabled.get();
}
@Override
public void clusterChanged(ClusterChangedEvent event) {
if (!eagerLicenseRegistration && !event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
if (registered.compareAndSet(false, true)) {
logger.info("Registering to licensesService [lazy]");
licensesClientService.register(featureName(),
trialLicenseOptions, expirationCallbacks,
new LicensingClientListener());
}
}
}
protected void doStart() throws ElasticsearchException {
if (eagerLicenseRegistration) {
if (registered.compareAndSet(false, true)) {
logger.info("Registering to licensesService [eager]");
licensesClientService.register(featureName(),
trialLicenseOptions, expirationCallbacks,
new LicensingClientListener());
}
}
}
@Override
protected void doStop() throws ElasticsearchException {
if (clusterService != null) {
clusterService.remove(this);
}
}
@Override
protected void doClose() throws ElasticsearchException {
}
private class LicensingClientListener implements LicensesClientService.Listener {
@Override
public void onEnabled(License license) {
enabled.set(true);
}
@Override
public void onDisabled(License license) {
enabled.set(false);
}
}
}

View File

@ -1,27 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.plugin.rest;
import com.carrotsearch.randomizedtesting.annotations.Name;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.test.rest.ElasticsearchRestTestCase;
import org.elasticsearch.test.rest.RestTestCandidate;
import org.elasticsearch.test.rest.parser.RestTestParseException;
import java.io.IOException;
public class LicensesRestIT extends ElasticsearchRestTestCase {
public LicensesRestIT(@Name("yaml") RestTestCandidate testCandidate) {
super(testCandidate);
}
@ParametersFactory
public static Iterable<Object[]> parameters() throws IOException, RestTestParseException {
return ElasticsearchRestTestCase.createParameters(0, 1);
}
}

View File

@ -1,11 +0,0 @@
es.logger.level=DEBUG
log4j.rootLogger=${es.logger.level}, out
log4j.logger.org.apache.http=INFO, out
log4j.additivity.org.apache.http=false
log4j.logger.org.elasticsearch.license=TRACE
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n

View File

@ -1,3 +0,0 @@
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßÇ<09>ş]d™}o§OčľId®Č
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{Ľ˛Áô­śátť¤7ůřĂę #˛Vó»ktwmŚ]ĎLőŁz"| QlźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5ŻŤĘĘ7%ŘęD
xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|Ţ ÓŞĎÁ*ń§D`<60>Ú?ůxU/3>x­UÓ“+ č

82
pom.xml
View File

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.elasticsearch.plugin</groupId>
<artifactId>elasticsearch-plugin</artifactId>
<version>2.0.0.beta1-SNAPSHOT</version>
</parent>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license</artifactId>
<packaging>pom</packaging>
<version>2.0.0.beta1-SNAPSHOT</version>
<modules>
<module>core</module>
<module>licensor</module>
<module>plugin</module>
<module>plugin-api</module>
</modules>
<properties>
<license.basedir combine.self="override">${project.basedir}</license.basedir>
<elasticsearch.license.header>${license.basedir}/dev-tools/license/elasticsearch_license_header.txt</elasticsearch.license.header>
<elasticsearch.license.headerDefinition>${license.basedir}/dev-tools/license/license_header_definition.xml</elasticsearch.license.headerDefinition>
</properties>
<repositories>
<repository>
<id>oss-snapshots</id>
<name>Sonatype OSS Snapshots</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
<profiles>
<profile>
<id>deploy-internal</id>
<distributionManagement>
<repository>
<id>elasticsearch-internal-releases</id>
<name>Elasticsearch Internal Releases</name>
<url>http://maven.elasticsearch.org/artifactory/internal-releases</url>
</repository>
<snapshotRepository>
<id>elasticsearch-internal-snapshots</id>
<name>Elasticsearch Internal Snapshots</name>
<url>http://maven.elasticsearch.org/artifactory/internal-snapshots</url>
</snapshotRepository>
</distributionManagement>
</profile>
<profile>
<id>deploy-public</id>
<distributionManagement>
<repository>
<id>elasticsearch-public-releases</id>
<name>Elasticsearch Public Releases</name>
<url>http://maven.elasticsearch.org/artifactory/public-releases</url>
</repository>
</distributionManagement>
</profile>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.carrotsearch.randomizedtesting</groupId>
<artifactId>junit4-maven-plugin</artifactId>
<configuration>
<argLine>${tests.jvm.argline}</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>