Remove third-party licensing library

Incorporate Feedback:
 - verify signature for signed licenses whenever it is read from cluster state
 - encrypt trial licenses with default pass phrase when storing it
 - moved toSignature & fromSignature to License

Make LicenseManager a Utility class

Refactor:
 - renamed LicenseManager to LicenseVerifier
 - LicensesMetaData now holds a list of license objects (for signed licenses) and a set of encoded strings (trial licenses)
 - minor test cleanup

incorporate feedback

incorporated feedback

switch to a stronger secret key gen algo; clean up build files & LicensesMetaData

cosmetic changes to LicenseSigner

incorporate LicnesesMetaData feedback

Original commit: elastic/x-pack-elasticsearch@0510091d2d
This commit is contained in:
Areek Zillur 2014-11-18 19:31:56 -05:00
parent eb5a06a1f9
commit 32af5a9d9c
42 changed files with 1256 additions and 1161 deletions

12
pom.xml

@ -119,18 +119,6 @@
</dependency>
<!-- actual deps -->
<dependency>
<groupId>net.nicholaswilliams.java.licensing</groupId>
<artifactId>licensing-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>net.nicholaswilliams.java.licensing</groupId>
<artifactId>licensing-licensor-base</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>

@ -28,8 +28,6 @@
<useTransitiveFiltering>true</useTransitiveFiltering>
<includes>
<include>org.elasticsearch:elasticsearch-license:*:exec</include>
<include>net.nicholaswilliams.java.licensing:licensing-core</include>
<include>net.nicholaswilliams.java.licensing:licensing-licensor-base</include>
<include>org.elasticsearch:elasticsearch</include>
</includes>
<excludes>

@ -14,13 +14,5 @@
<exclude>org.elasticsearch:elasticsearch</exclude>
</excludes>
</dependencySet>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<useTransitiveFiltering>true</useTransitiveFiltering>
<includes>
<include>net.nicholaswilliams.java.licensing:licensing-core</include>
</includes>
</dependencySet>
</dependencySets>
</assembly>

@ -0,0 +1,245 @@
/*
* 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();
}
}

@ -8,14 +8,11 @@ package org.elasticsearch.license.core;
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 org.elasticsearch.common.xcontent.*;
import java.io.IOException;
public class ESLicense implements ToXContent {
public class License implements ToXContent {
private final String uid;
private final String issuer;
@ -28,8 +25,8 @@ public class ESLicense implements ToXContent {
private final long expiryDate;
private final int maxNodes;
private ESLicense(String uid, String issuer, String issuedTo, long issueDate, String type,
String subscriptionType, String feature, String signature, long expiryDate, 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;
@ -113,7 +110,7 @@ public class ESLicense implements ToXContent {
return signature;
}
public void verify() {
public void validate() {
if (issuer == null) {
throw new IllegalStateException("issuer can not be null");
} else if (issuedTo == null) {
@ -137,8 +134,7 @@ public class ESLicense implements ToXContent {
}
}
static ESLicense readESLicense(StreamInput in) throws IOException {
static License readLicense(StreamInput in) throws IOException {
in.readVInt(); // Version for future extensibility
Builder builder = builder();
builder.uid(in.readString());
@ -170,7 +166,11 @@ public class ESLicense implements ToXContent {
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
boolean restViewMode = params.paramAsBoolean(ESLicenses.REST_VIEW_MODE, false);
boolean licenseSpecMode = params.paramAsBoolean(Licenses.LICENSE_SPEC_VIEW_MODE, false);
boolean restViewMode = params.paramAsBoolean(Licenses.REST_VIEW_MODE, false);
if (licenseSpecMode && restViewMode) {
throw new IllegalArgumentException("can have either " + Licenses.REST_VIEW_MODE + " or " + Licenses.LICENSE_SPEC_VIEW_MODE);
}
builder.startObject();
if (restViewMode) {
builder.field(XFields.STATUS, ((expiryDate - System.currentTimeMillis()) > 0l) ? "active" : "expired");
@ -184,7 +184,7 @@ public class ESLicense implements ToXContent {
builder.field(XFields.MAX_NODES, maxNodes);
builder.field(XFields.ISSUED_TO, issuedTo);
builder.field(XFields.ISSUER, issuer);
if (signature != null && !restViewMode) {
if (!licenseSpecMode && !restViewMode && signature != null) {
builder.field(XFields.SIGNATURE, signature);
}
builder.endObject();
@ -310,7 +310,7 @@ public class ESLicense implements ToXContent {
return this;
}
public Builder fromLicenseSpec(ESLicense license, String signature) {
public Builder fromLicenseSpec(License license, String signature) {
return uid(license.uid())
.issuedTo(license.issuedTo())
.issueDate(license.issueDate())
@ -372,12 +372,12 @@ public class ESLicense implements ToXContent {
return this;
}
public ESLicense build() {
return new ESLicense(uid, issuer, issuedTo, issueDate, type,
public License build() {
return new License(uid, issuer, issuedTo, issueDate, type,
subscriptionType, feature, signature, expiryDate, maxNodes);
}
public Builder verify() {
public Builder validate() {
if (issuer == null) {
throw new IllegalStateException("issuer can not be null");
} else if (issuedTo == null) {

@ -0,0 +1,118 @@
/*
* 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.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.*;
/**
* Responsible for verifying signed licenses
*/
public class LicenseVerifier {
/**
* Verifies Licenses using {@link #verifyLicense(License)}
*/
public static boolean verifyLicenses(final Collection<License> licenses) {
for (License license : licenses) {
if (!verifyLicense(license)) {
return false;
}
}
return true;
}
/**
* 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) {
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(contentBuilder.bytes().toBytes(), licenseSignature.contentSignature)) {
return false;
}
final byte[] hash = Base64.encodeBytesToBytes(getPublicKeyContent("/public.key"));
return Arrays.equals(hash, licenseSignature.hash);
} catch (IOException e) {
throw new IllegalStateException(e);
} finally {
if (licenseSignature != null) {
licenseSignature.clear();
}
}
}
private static boolean verifyContent(byte[] data, byte[] contentSignature) {
try {
Signature rsa = Signature.getInstance("SHA512withRSA");
rsa.initVerify(CryptUtils.readEncryptedPublicKey(getPublicKeyContent("/public.key")));
rsa.update(data);
return rsa.verify(contentSignature);
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
throw new IllegalStateException(e);
}
}
private static byte[] getPublicKeyContent(String resource) {
try (InputStream inputStream = LicenseVerifier.class.getResourceAsStream(resource)) {
return Streams.copyToByteArray(inputStream);
} 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);
}
}
}

@ -15,9 +15,10 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class ESLicenses {
public final class Licenses {
public static final String REST_VIEW_MODE = "rest_api";
public static final String REST_VIEW_MODE = "rest_view";
public static final String LICENSE_SPEC_VIEW_MODE = "license_spec_view";
private final static class Fields {
static final String LICENSES = "licenses";
@ -27,41 +28,43 @@ public class ESLicenses {
static final XContentBuilderString LICENSES = new XContentBuilderString(Fields.LICENSES);
}
public static void toXContent(Collection<ESLicense> licenses, XContentBuilder builder, ToXContent.Params params) throws IOException {
private Licenses() {}
public static void toXContent(Collection<License> licenses, XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject();
builder.startArray(XFields.LICENSES);
for (ESLicense license : licenses) {
for (License license : licenses) {
license.toXContent(builder, params);
}
builder.endArray();
builder.endObject();
}
public static List<ESLicense> fromSource(String content) throws IOException {
public static List<License> fromSource(String content) throws IOException {
return fromSource(content.getBytes(StandardCharsets.UTF_8), true);
}
public static List<ESLicense> fromSource(byte[] bytes) throws IOException {
public static List<License> fromSource(byte[] bytes) throws IOException {
return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), true);
}
public static List<ESLicense> fromSource(byte[] bytes, boolean verify) throws IOException {
public static List<License> fromSource(byte[] bytes, boolean verify) throws IOException {
return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), verify);
}
public static List<ESLicense> fromXContent(XContentParser parser, boolean verify) throws IOException {
List<ESLicense> esLicenses = new ArrayList<>();
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) {
ESLicense.Builder builder = ESLicense.builder().fromXContent(parser);
License.Builder builder = License.builder().fromXContent(parser);
if (verify) {
builder.verify();
builder.validate();
}
esLicenses.add(builder.build());
licenses.add(builder.build());
}
} else {
throw new ElasticsearchParseException("failed to parse licenses expected an array of licenses");
@ -74,37 +77,37 @@ public class ESLicenses {
} else {
throw new ElasticsearchParseException("failed to parse licenses expected start object");
}
return esLicenses;
return licenses;
}
public static List<ESLicense> readFrom(StreamInput in) throws IOException {
public static List<License> readFrom(StreamInput in) throws IOException {
int size = in.readVInt();
List<ESLicense> esLicenses = new ArrayList<>(size);
List<License> licenses = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
esLicenses.add(ESLicense.readESLicense(in));
licenses.add(License.readLicense(in));
}
return esLicenses;
return licenses;
}
public static void writeTo(List<ESLicense> esLicenses, StreamOutput out) throws IOException {
out.writeVInt(esLicenses.size());
for (ESLicense license : esLicenses) {
public static void writeTo(List<License> licenses, StreamOutput out) throws IOException {
out.writeVInt(licenses.size());
for (License license : licenses) {
license.writeTo(out);
}
}
public static ImmutableMap<String, ESLicense> reduceAndMap(Set<ESLicense> esLicensesSet) {
Map<String, ESLicense> map = new HashMap<>(esLicensesSet.size());
for (ESLicense license : esLicensesSet) {
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);
}
private static void putIfAppropriate(Map<String, ESLicense> licenseMap, ESLicense license) {
private static void putIfAppropriate(Map<String, License> licenseMap, License license) {
final String featureType = license.feature();
if (licenseMap.containsKey(featureType)) {
final ESLicense previousLicense = licenseMap.get(featureType);
final License previousLicense = licenseMap.get(featureType);
if (license.expiryDate() > previousLicense.expiryDate()) {
licenseMap.put(featureType, license);
}

@ -1,33 +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 net.nicholaswilliams.java.licensing.encryption.PublicKeyDataProvider;
import net.nicholaswilliams.java.licensing.exception.KeyNotFoundException;
import org.elasticsearch.common.io.Streams;
import java.io.IOException;
import java.io.InputStream;
/**
*/
public class ResourcePublicKeyDataProvider implements PublicKeyDataProvider {
private final String resource;
public ResourcePublicKeyDataProvider(String resource) {
this.resource = resource;
}
@Override
public byte[] getEncryptedPublicKeyData() throws KeyNotFoundException {
try (InputStream inputStream = this.getClass().getResourceAsStream(resource)) {
return Streams.copyToByteArray(inputStream);
} catch (IOException ex) {
throw new KeyNotFoundException(ex);
}
}
}

@ -1,139 +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 net.nicholaswilliams.java.licensing.License;
import net.nicholaswilliams.java.licensing.encryption.Hasher;
import net.nicholaswilliams.java.licensing.encryption.PasswordProvider;
import net.nicholaswilliams.java.licensing.encryption.PrivateKeyDataProvider;
import net.nicholaswilliams.java.licensing.exception.KeyNotFoundException;
import net.nicholaswilliams.java.licensing.licensor.LicenseCreator;
import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.license.core.ESLicense;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Random;
import java.util.Set;
public class ESLicenseSigner {
public final static String DEFAULT_PASS_PHRASE = "elasticsearch-license";
private final static int VERSION_START = 0;
private final static int VERSION = VERSION_START;
private final static int MAGIC_LENGTH = 13;
private final LicenseCreator licenseCreator;
private final Path publicKeyPath;
public ESLicenseSigner(final String privateKeyPath, final String publicKeyPath) {
this(Paths.get(privateKeyPath), Paths.get(publicKeyPath));
}
public ESLicenseSigner(final Path privateKeyPath, final Path publicKeyPath) {
LicenseCreatorProperties.setPrivateKeyDataProvider(new PrivateKeyDataProvider() {
@Override
public byte[] getEncryptedPrivateKeyData() throws KeyNotFoundException {
assert privateKeyPath.toFile().exists();
try {
return Files.readAllBytes(privateKeyPath);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
});
LicenseCreatorProperties.setPrivateKeyPasswordProvider(new PasswordProvider() {
@Override
public char[] getPassword() {
return Hasher.hash(DEFAULT_PASS_PHRASE).toCharArray();
}
});
this.licenseCreator = LicenseCreator.getInstance();
this.publicKeyPath = publicKeyPath;
}
public ImmutableSet<ESLicense> sign(Set<ESLicense> licenseSpecs) throws IOException {
final ImmutableSet.Builder<ESLicense> builder = ImmutableSet.builder();
for (ESLicense licenseSpec : licenseSpecs) {
builder.add(sign(licenseSpec));
}
return builder.build();
}
/**
* Generates a signature for the <code>esLicense</code>.
* Signature structure:
* | MAGIC | HEADER_LENGTH | VERSION | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
*
* @return a signed ESLicense (with signature)
* @throws IOException
*/
public ESLicense sign(ESLicense licenseSpec) throws IOException {
License.Builder licenseBuilder = new License.Builder()
.withGoodBeforeDate(licenseSpec.expiryDate())
.withIssueDate(licenseSpec.issueDate())
.withProductKey(licenseSpec.uid())
.withHolder(licenseSpec.issuedTo())
.withIssuer(licenseSpec.issuer());
// NOTE: to add additional feature(s) to the internal license
// encode the new feature(s) in featureToXContent rather
// than doing licenseBuilder.addFeature(..)
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
featureToXContent(licenseSpec, contentBuilder);
licenseBuilder.addFeature(contentBuilder.string());
final License license = licenseBuilder.build();
final byte[] magic = new byte[MAGIC_LENGTH];
Random random = new Random();
random.nextBytes(magic);
final byte[] licenseSignature = licenseCreator.signAndSerializeLicense(license);
final byte[] hash = Hasher.hash(Base64.encodeBase64String(
Files.readAllBytes(publicKeyPath))
).getBytes(StandardCharsets.UTF_8);
int headerLength = MAGIC_LENGTH + hash.length + 4 + 4;
byte[] bytes = new byte[headerLength + licenseSignature.length];
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.put(magic)
.putInt(headerLength)
.putInt(VERSION)
.put(hash)
.put(licenseSignature);
String signature = Base64.encodeBase64String(bytes);
return ESLicense.builder()
.fromLicenseSpec(licenseSpec, signature)
.verify()
.build();
}
private void featureToXContent(ESLicense license, XContentBuilder builder) throws IOException {
builder.startObject();
builder.field("feature", license.feature());
builder.field("type", license.type());
builder.field("subscription_type", license.subscriptionType());
builder.field("max_nodes", license.maxNodes());
builder.endObject();
}
}

@ -0,0 +1,101 @@
/*
* 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.Base64;
import org.elasticsearch.common.collect.ImmutableSet;
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.nio.file.Paths;
import java.security.*;
import java.util.Collections;
import java.util.Set;
public class LicenseSigner {
private final static int VERSION_START = 0;
private final static int VERSION = VERSION_START;
private final static int MAGIC_LENGTH = 13;
private final Path publicKeyPath;
private final Path privateKeyPath;
public LicenseSigner(final String privateKeyPath, final String publicKeyPath) {
this(Paths.get(privateKeyPath), Paths.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 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(VERSION)
.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);
}
}
}

@ -5,12 +5,6 @@
*/
package org.elasticsearch.license.licensor.tools;
import net.nicholaswilliams.java.licensing.encryption.Hasher;
import net.nicholaswilliams.java.licensing.encryption.RSAKeyPairGenerator;
import net.nicholaswilliams.java.licensing.exception.AlgorithmNotSupportedException;
import net.nicholaswilliams.java.licensing.exception.InappropriateKeyException;
import net.nicholaswilliams.java.licensing.exception.InappropriateKeySpecificationException;
import net.nicholaswilliams.java.licensing.exception.RSA2048NotSupportedException;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
@ -20,17 +14,21 @@ import org.elasticsearch.env.Environment;
import java.io.File;
import java.io.IOException;
import java.security.KeyPair;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
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(NAME, KeyPairGeneratorTool.class)
.cmds(KeyPairGenerator.CMD)
.cmds(KeyGenerator.CMD)
.build();
public KeyPairGeneratorTool() {
@ -39,14 +37,12 @@ public class KeyPairGeneratorTool extends CliTool {
@Override
protected Command parse(String s, CommandLine commandLine) throws Exception {
return KeyPairGenerator.parse(terminal, commandLine);
return KeyGenerator.parse(terminal, commandLine);
}
public static class KeyPairGenerator extends Command {
public static class KeyGenerator extends Command {
public static final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
private static final CliToolConfig.Cmd CMD = cmd(NAME, KeyPairGenerator.class)
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)
@ -55,7 +51,7 @@ public class KeyPairGeneratorTool extends CliTool {
public final String publicKeyPath;
public final String privateKeyPath;
protected KeyPairGenerator(Terminal terminal, String publicKeyPath, String privateKeyPath) {
protected KeyGenerator(Terminal terminal, String publicKeyPath, String privateKeyPath) {
super(terminal);
this.privateKeyPath = privateKeyPath;
this.publicKeyPath = publicKeyPath;
@ -70,7 +66,7 @@ public class KeyPairGeneratorTool extends CliTool {
} else if (exists(publicKeyPath)) {
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " already exists");
}
return new KeyPairGenerator(terminal, publicKeyPath, privateKeyPath);
return new KeyGenerator(terminal, publicKeyPath, privateKeyPath);
}
@Override
@ -84,25 +80,23 @@ public class KeyPairGeneratorTool extends CliTool {
return new File(filePath).exists();
}
private static KeyPair generateKeyPair(String privateKeyFileName, String publicKeyFileName) {
RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
private static KeyPair generateKeyPair(String privateKeyFileName, String publicKeyFileName) throws IOException, NoSuchAlgorithmException {
SecureRandom random = new SecureRandom();
KeyPair keyPair;
try {
keyPair = generator.generateKeyPair();
} catch (RSA2048NotSupportedException e) {
throw new IllegalStateException(e);
}
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048, random);
KeyPair keyPair = keyGen.generateKeyPair();
try {
generator.saveKeyPairToFiles(keyPair, privateKeyFileName, publicKeyFileName, Hasher.hash(DEFAULT_PASS_PHRASE).toCharArray());
} catch (IOException | AlgorithmNotSupportedException | InappropriateKeyException | InappropriateKeySpecificationException e) {
throw new IllegalStateException(e);
}
saveKeyPairToFiles(keyPair, privateKeyFileName, publicKeyFileName);
return keyPair;
}
}
private static void saveKeyPairToFiles(KeyPair keyPair, String privateKeyFileName, String publicKeyFileName) throws IOException {
Files.write(Paths.get(privateKeyFileName), writeEncryptedPrivateKey(keyPair.getPrivate()));
Files.write(Paths.get(publicKeyFileName), writeEncryptedPublicKey(keyPair.getPublic()));
}
public static void main(String[] args) throws Exception {
int status = new KeyPairGeneratorTool().execute(args);
System.exit(status);

@ -16,9 +16,9 @@ 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.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.licensor.ESLicenseSigner;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.licensor.LicenseSigner;
import java.io.File;
import java.io.IOException;
@ -59,11 +59,11 @@ public class LicenseGeneratorTool extends CliTool {
option("lf", "licenseFile").required(false).hasArg(true)
).build();
public final Set<ESLicense> licenseSpecs;
public final Set<License> licenseSpecs;
public final String publicKeyFilePath;
public final String privateKeyFilePath;
public LicenseGenerator(Terminal terminal, String publicKeyFilePath, String privateKeyFilePath, Set<ESLicense> licenseSpecs) {
public LicenseGenerator(Terminal terminal, String publicKeyFilePath, String privateKeyFilePath, Set<License> licenseSpecs) {
super(terminal);
this.licenseSpecs = licenseSpecs;
this.privateKeyFilePath = privateKeyFilePath;
@ -82,10 +82,10 @@ public class LicenseGeneratorTool extends CliTool {
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
}
Set<ESLicense> licenseSpecs = new HashSet<>();
Set<License> licenseSpecs = new HashSet<>();
if (licenseSpecSources != null) {
for (String licenseSpec : licenseSpecSources) {
licenseSpecs.addAll(ESLicenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8), false));
licenseSpecs.addAll(Licenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8), false));
}
}
@ -95,7 +95,7 @@ public class LicenseGeneratorTool extends CliTool {
if (doesNotExist(licenseSpecFilePath)) {
return exitCmd(ExitStatus.USAGE, terminal, licenseSpecFilePath + " does not exist");
}
licenseSpecs.addAll(ESLicenses.fromSource(Files.readAllBytes(licenseSpecPath), false));
licenseSpecs.addAll(Licenses.fromSource(Files.readAllBytes(licenseSpecPath), false));
}
}
@ -109,12 +109,12 @@ public class LicenseGeneratorTool extends CliTool {
public ExitStatus execute(Settings settings, Environment env) throws Exception {
// sign
ESLicenseSigner signer = new ESLicenseSigner(privateKeyFilePath, publicKeyFilePath);
ImmutableSet<ESLicense> signedLicences = signer.sign(licenseSpecs);
LicenseSigner signer = new LicenseSigner(privateKeyFilePath, publicKeyFilePath);
ImmutableSet<License> signedLicences = signer.sign(licenseSpecs);
// dump
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
ESLicenses.toXContent(signedLicences, builder, ToXContent.EMPTY_PARAMS);
Licenses.toXContent(signedLicences, builder, ToXContent.EMPTY_PARAMS);
builder.flush();
terminal.print(builder.string());

@ -5,7 +5,6 @@
*/
package org.elasticsearch.license.licensor.tools;
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
@ -16,9 +15,9 @@ 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.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.manager.ESLicenseManager;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.core.Licenses;
import java.io.File;
import java.io.IOException;
@ -38,7 +37,7 @@ public class LicenseVerificationTool extends CliTool {
public static final String NAME = "verify-license";
private static final CliToolConfig CONFIG = config(NAME, LicenseVerificationTool.class)
.cmds(LicenseVerifier.CMD)
.cmds(LicenseVerificationTool.LicenseVerifier.CMD)
.build();
public LicenseVerificationTool() {
@ -47,7 +46,7 @@ public class LicenseVerificationTool extends CliTool {
@Override
protected Command parse(String s, CommandLine commandLine) throws Exception {
return LicenseVerifier.parse(terminal, commandLine);
return LicenseVerificationTool.LicenseVerifier.parse(terminal, commandLine);
}
public static class LicenseVerifier extends Command {
@ -58,9 +57,9 @@ public class LicenseVerificationTool extends CliTool {
option("lf", "licenseFile").required(false).hasArg(true)
).build();
public final Set<ESLicense> licenses;
public final Set<License> licenses;
public LicenseVerifier(Terminal terminal, Set<ESLicense> licenses) {
public LicenseVerifier(Terminal terminal, Set<License> licenses) {
super(terminal);
this.licenses = licenses;
}
@ -69,10 +68,10 @@ public class LicenseVerificationTool extends CliTool {
String[] licenseSources = commandLine.getOptionValues("license");
String[] licenseSourceFiles = commandLine.getOptionValues("licenseFile");
Set<ESLicense> esLicenses = new HashSet<>();
Set<License> licenses = new HashSet<>();
if (licenseSources != null) {
for (String licenseSpec : licenseSources) {
esLicenses.addAll(ESLicenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8)));
licenses.addAll(Licenses.fromSource(licenseSpec.getBytes(StandardCharsets.UTF_8)));
}
}
@ -82,31 +81,30 @@ public class LicenseVerificationTool extends CliTool {
if (!exists(licenseFilePath)) {
return exitCmd(ExitStatus.USAGE, terminal, licenseFilePath + " does not exist");
}
esLicenses.addAll(ESLicenses.fromSource(Files.readAllBytes(licensePath)));
licenses.addAll(Licenses.fromSource(Files.readAllBytes(licensePath)));
}
}
if (esLicenses.size() == 0) {
if (licenses.size() == 0) {
return exitCmd(ExitStatus.USAGE, terminal, "no license provided");
}
return new LicenseVerifier(terminal, esLicenses);
return new LicenseVerifier(terminal, licenses);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
// verify
Map<String, ESLicense> effectiveLicenses = ESLicenses.reduceAndMap(licenses);
ESLicenseManager licenseManager = new ESLicenseManager();
try {
licenseManager.verifyLicenses(effectiveLicenses);
} catch (InvalidLicenseException e) {
Map<String, License> effectiveLicenses = Licenses.reduceAndMap(licenses);
org.elasticsearch.license.core.LicenseVerifier licenseVerifier = new org.elasticsearch.license.core.LicenseVerifier();
if (!org.elasticsearch.license.core.LicenseVerifier.verifyLicenses(effectiveLicenses.values())) {
return ExitStatus.DATA_ERROR;
}
// dump effective licences
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
ESLicenses.toXContent(effectiveLicenses.values(), builder, ToXContent.EMPTY_PARAMS);
Licenses.toXContent(effectiveLicenses.values(), builder, ToXContent.EMPTY_PARAMS);
builder.flush();
terminal.print(builder.string());

@ -1,234 +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.manager;
import net.nicholaswilliams.java.licensing.*;
import net.nicholaswilliams.java.licensing.encryption.Hasher;
import net.nicholaswilliams.java.licensing.encryption.PasswordProvider;
import net.nicholaswilliams.java.licensing.exception.ExpiredLicenseException;
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.ResourcePublicKeyDataProvider;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.*;
/**
* Class responsible for reading signed licenses, maintaining an effective esLicenses instance, verification of licenses
* and querying against licenses on a feature basis
* <p/>
*/
public class ESLicenseManager {
private final LicenseManager licenseManager;
private static class FeatureFields {
static final String MAX_NODES = "max_nodes";
static final String TYPE = "type";
static final String SUBSCRIPTION_TYPE = "subscription_type";
static final String FEATURE = "feature";
}
// Initialize LicenseManager
static {
LicenseManagerProperties.setPublicKeyDataProvider(new ResourcePublicKeyDataProvider("/public.key"));
LicenseManagerProperties.setPublicKeyPasswordProvider(new ESPublicKeyPasswordProvider());
LicenseManagerProperties.setLicenseValidator(new DefaultLicenseValidator());
LicenseManagerProperties.setLicenseProvider(new LicenseProvider() {
@Override
public SignedLicense getLicense(Object context) {
throw new UnsupportedOperationException("This singleton license provider shouldn't be used");
}
});
}
@Inject
public ESLicenseManager() {
this.licenseManager = LicenseManager.getInstance();
}
public ImmutableSet<String> toSignatures(Collection<ESLicense> esLicenses) {
Set<String> signatures = new HashSet<>();
for (ESLicense esLicense : esLicenses) {
signatures.add(esLicense.signature());
}
return ImmutableSet.copyOf(signatures);
}
public ImmutableSet<ESLicense> fromSignatures(Set<String> signatures) {
Set<ESLicense> esLicenses = new HashSet<>();
for (String signature : signatures) {
esLicenses.add(fromSignature(signature));
}
return ImmutableSet.copyOf(esLicenses);
}
public void verifyLicenses(Map<String, ESLicense> esLicenses) {
try {
for (String feature : esLicenses.keySet()) {
ESLicense esLicense = esLicenses.get(feature);
// verify signature
final License license = this.licenseManager.decryptAndVerifyLicense(
extractSignedLicence(esLicense.signature()));
// validate license
this.licenseManager.validateLicense(license);
// verify all readable license fields
verifyLicenseFields(license, esLicense);
}
} catch (InvalidLicenseException e) {
throw new InvalidLicenseException("Invalid License");
}
}
private ESLicense fromSignature(String signature) {
final SignedLicense signedLicense = extractSignedLicence(signature);
License license = licenseManager.decryptAndVerifyLicense(signedLicense);
ESLicense.Builder builder = ESLicense.builder();
if (license.getFeatures().size() == 1) {
try {
String featureName = license.getFeatures().get(0).getName();
LicenseFeatures licenseFeatures = licenseFeaturesFromSource(featureName);
builder.maxNodes(licenseFeatures.maxNodes)
.feature(licenseFeatures.feature)
.type(licenseFeatures.type)
.subscriptionType(licenseFeatures.subscriptionType);
} catch (IOException ignored) {
}
}
return builder
.uid(license.getProductKey())
.issuer(license.getIssuer())
.issuedTo(license.getHolder())
.issueDate(license.getIssueDate())
.expiryDate(license.getGoodBeforeDate())
.signature(signature)
.build();
}
private static void verifyLicenseFields(License license, ESLicense eslicense) {
boolean licenseValid = license.getProductKey().equals(eslicense.uid())
&& license.getHolder().equals(eslicense.issuedTo())
&& license.getIssueDate() == eslicense.issueDate()
&& license.getGoodBeforeDate() == eslicense.expiryDate();
boolean maxNodesValid = false;
boolean featureValid = false;
boolean typeValid = false;
boolean subscriptionTypeValid = false;
if (license.getFeatures().size() == 1) {
try {
String featureName = license.getFeatures().get(0).getName();
LicenseFeatures licenseFeatures = licenseFeaturesFromSource(featureName);
maxNodesValid = eslicense.maxNodes() == licenseFeatures.maxNodes;
typeValid = eslicense.type().equals(licenseFeatures.type);
subscriptionTypeValid = eslicense.subscriptionType().equals(licenseFeatures.subscriptionType);
featureValid = eslicense.feature().equals(licenseFeatures.feature);
} catch (IOException ignored) {
}
}
if (!licenseValid || !featureValid || !maxNodesValid || !typeValid || !subscriptionTypeValid) {
throw new InvalidLicenseException("Invalid License");
}
}
/**
* Extract a signedLicense (SIGNED_LICENSE_CONTENT) from the signature.
* Validates the public key used to decrypt the license by comparing their hashes
* <p/>
* Signature structure:
* | MAGIC | HEADER_LENGTH | VERSION | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
*
* @param signature of a single license
* @return signed license content for the license
*/
private static SignedLicense extractSignedLicence(String signature) {
byte[] signatureBytes = Base64.decodeBase64(signature);
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
byteBuffer = (ByteBuffer) byteBuffer.position(13);
int start = byteBuffer.getInt();
int version = byteBuffer.getInt();
return new ObjectSerializer().readObject(SignedLicense.class, Arrays.copyOfRange(signatureBytes, start, signatureBytes.length));
}
private static LicenseFeatures licenseFeaturesFromSource(String source) throws IOException {
XContentParser parser = XContentFactory.xContent(source).createParser(source);
String feature = null;
String type = null;
String subscriptionType = null;
int maxNodes = -1;
XContentParser.Token token = parser.nextToken();
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 (token == XContentParser.Token.VALUE_STRING) {
switch (currentFieldName) {
case FeatureFields.FEATURE:
feature = parser.text();
break;
case FeatureFields.TYPE:
type = parser.text();
break;
case FeatureFields.SUBSCRIPTION_TYPE:
subscriptionType = parser.text();
break;
}
} else if (token == XContentParser.Token.VALUE_NUMBER) {
if (FeatureFields.MAX_NODES.equals(currentFieldName)) {
maxNodes = parser.intValue();
}
}
} 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();
}
}
}
}
// Should we throw a ElasticsearchParseException here?
return new LicenseFeatures(feature, type, subscriptionType, maxNodes);
}
private static class LicenseFeatures {
private final String feature;
private final String type;
private final String subscriptionType;
private final int maxNodes;
private LicenseFeatures(String feature, String type, String subscriptionType, int maxNodes) {
this.feature = feature;
this.type = type;
this.subscriptionType = subscriptionType;
this.maxNodes = maxNodes;
}
}
// TODO: Need a better password management
private static class ESPublicKeyPasswordProvider implements PasswordProvider {
private final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
@Override
public char[] getPassword() {
return Hasher.hash(DEFAULT_PASS_PHRASE).toCharArray();
}
}
}

@ -7,13 +7,13 @@ package org.elasticsearch.license.plugin;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.Scopes;
import org.elasticsearch.license.manager.ESLicenseManager;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.plugin.core.LicensesService;
public class LicenseModule extends AbstractModule {
@Override
protected void configure() {
bind(ESLicenseManager.class).in(Scopes.SINGLETON);
bind(LicenseVerifier.class).in(Scopes.SINGLETON);
bind(LicensesService.class).in(Scopes.SINGLETON);
}
}

@ -8,8 +8,8 @@ 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.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.util.ArrayList;
@ -17,29 +17,29 @@ import java.util.List;
public class GetLicenseResponse extends ActionResponse {
private List<ESLicense> licenses = new ArrayList<>();
private List<License> licenses = new ArrayList<>();
GetLicenseResponse() {
}
GetLicenseResponse(List<ESLicense> esLicenses) {
this.licenses = esLicenses;
GetLicenseResponse(List<License> licenses) {
this.licenses = licenses;
}
public List<ESLicense> licenses() {
public List<License> licenses() {
return licenses;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
licenses = ESLicenses.readFrom(in);
licenses = Licenses.readFrom(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
ESLicenses.writeTo(licenses, out);
Licenses.writeTo(licenses, out);
}
}

@ -10,8 +10,8 @@ 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.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.util.List;
@ -19,7 +19,7 @@ import java.util.List;
public class PutLicenseRequest extends AcknowledgedRequest<PutLicenseRequest> {
private List<ESLicense> licenses;
private List<License> licenses;
public PutLicenseRequest() {
}
@ -30,38 +30,38 @@ public class PutLicenseRequest extends AcknowledgedRequest<PutLicenseRequest> {
}
/**
* Parses licenses from json format to an instance of {@link org.elasticsearch.license.core.ESLicenses}
* 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(ESLicenses.fromSource(licenseDefinition));
return licenses(Licenses.fromSource(licenseDefinition));
} catch (IOException e) {
throw new ElasticsearchIllegalArgumentException("failed to parse licenses source", e);
}
}
public PutLicenseRequest licenses(List<ESLicense> esLicenses) {
this.licenses = esLicenses;
public PutLicenseRequest licenses(List<License> licenses) {
this.licenses = licenses;
return this;
}
public List<ESLicense> licenses() {
public List<License> licenses() {
return licenses;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
licenses = ESLicenses.readFrom(in);
licenses = Licenses.readFrom(in);
readTimeout(in);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
ESLicenses.writeTo(licenses, out);
Licenses.writeTo(licenses, out);
writeTimeout(out);
}
}

@ -8,7 +8,7 @@ package org.elasticsearch.license.plugin.action.put;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder;
import org.elasticsearch.client.ClusterAdminClient;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.License;
import java.util.List;
@ -32,7 +32,7 @@ public class PutLicenseRequestBuilder extends AcknowledgedRequestBuilder<PutLice
* @param licenses license
* @return this builder
*/
public PutLicenseRequestBuilder setLicense(List<ESLicense> licenses) {
public PutLicenseRequestBuilder setLicense(List<License> licenses) {
request.licenses(licenses);
return this;
}

@ -8,7 +8,7 @@ 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.ESLicense;
import org.elasticsearch.license.core.License;
import java.util.List;
import java.util.Set;
@ -39,5 +39,5 @@ public interface LicensesManagerService {
/**
* @return a list of licenses, contains one license (with the latest expiryDate) per registered features sorted by latest issueDate
*/
public List<ESLicense> getLicenses();
public List<License> getLicenses();
}

@ -6,17 +6,18 @@
package org.elasticsearch.license.plugin.core;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.collect.Sets;
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.XContentParser;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.io.IOException;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
/**
* Contains metadata about registered licenses
@ -27,69 +28,43 @@ public class LicensesMetaData implements MetaData.Custom {
public static final Factory FACTORY = new Factory();
private final Set<String> signatures;
private final ImmutableList<License> signedLicenses;
private final Set<String> encodedTrialLicenses;
public LicensesMetaData(String[] signatures, String[] encodedTrialLicenses) {
this(Sets.newHashSet(signatures), Sets.newHashSet(encodedTrialLicenses));
}
private final ImmutableList<License> trialLicenses;
/**
* Constructs new licenses metadata
*
* @param signatures set of esLicense signatures
* @param encodedTrialLicenses set of encoded trial licenses
* @param signedLicenses list of signed Licenses
* @param trialLicenses set of encoded trial licenses
*/
public LicensesMetaData(Set<String> signatures, Set<String> encodedTrialLicenses) {
this.signatures = signatures;
this.encodedTrialLicenses = encodedTrialLicenses;
public LicensesMetaData(List<License> signedLicenses, List<License> trialLicenses) {
this.signedLicenses = ImmutableList.copyOf(signedLicenses);
this.trialLicenses = ImmutableList.copyOf(trialLicenses);
}
public Set<String> getSignatures() {
return signatures;
public List<License> getSignedLicenses() {
return signedLicenses;
}
public Set<String> getEncodedTrialLicenses() {
return encodedTrialLicenses;
public List<License> getTrialLicenses() {
return trialLicenses;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) {
return false;
}
if (obj == this) {
return true;
}
if (obj instanceof LicensesMetaData) {
LicensesMetaData other = (LicensesMetaData) obj;
boolean signaturesEqual;
boolean trialLicensesEqual;
LicensesMetaData that = (LicensesMetaData) obj;
return signedLicenses.equals(that.signedLicenses)
&& trialLicenses.equals(that.trialLicenses);
}
if (other.getSignatures() != null) {
if (this.getSignatures() != null) {
signaturesEqual = other.getSignatures().equals(this.getSignatures());
} else {
return false;
}
} else {
signaturesEqual = this.getSignatures() == null;
}
if (other.getEncodedTrialLicenses() != null) {
if (this.getEncodedTrialLicenses() != null) {
trialLicensesEqual = other.getEncodedTrialLicenses().equals(this.getEncodedTrialLicenses());
} else {
return false;
}
} else {
trialLicensesEqual = this.getEncodedTrialLicenses() == null;
}
return signaturesEqual && trialLicensesEqual;
}
return false;
@Override
public int hashCode() {
return signedLicenses.hashCode() + 31 * trialLicenses.hashCode();
}
/**
@ -110,13 +85,13 @@ public class LicensesMetaData implements MetaData.Custom {
*/
@Override
public LicensesMetaData readFrom(StreamInput in) throws IOException {
String[] signatures = new String[0];
String[] encodedTrialLicenses = new String[0];
if (in.readBoolean()) {
signatures = in.readStringArray();
encodedTrialLicenses = in.readStringArray();
List<License> signedLicenses = Licenses.readFrom(in);
int numTrialLicenses = in.readVInt();
List<License> trialLicenses = new ArrayList<>(numTrialLicenses);
for (int i = 0; i < numTrialLicenses; i++) {
trialLicenses.add(TrialLicenseUtils.fromEncodedTrialLicense(in.readString()));
}
return new LicensesMetaData(signatures, encodedTrialLicenses);
return new LicensesMetaData(signedLicenses, trialLicenses);
}
/**
@ -124,12 +99,10 @@ public class LicensesMetaData implements MetaData.Custom {
*/
@Override
public void writeTo(LicensesMetaData licensesMetaData, StreamOutput out) throws IOException {
if (licensesMetaData == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
out.writeStringArray(licensesMetaData.signatures.toArray(new String[licensesMetaData.signatures.size()]));
out.writeStringArray(licensesMetaData.encodedTrialLicenses.toArray(new String[licensesMetaData.encodedTrialLicenses.size()]));
Licenses.writeTo(licensesMetaData.signedLicenses, out);
out.writeVInt(licensesMetaData.trialLicenses.size());
for (License trialLicense : licensesMetaData.trialLicenses) {
out.writeString(TrialLicenseUtils.toEncodedTrialLicense(trialLicense));
}
}
@ -138,36 +111,35 @@ public class LicensesMetaData implements MetaData.Custom {
*/
@Override
public LicensesMetaData fromXContent(XContentParser parser) throws IOException {
List<License> trialLicenses = new ArrayList<>();
List<License> signedLicenses = new ArrayList<>();
XContentParser.Token token;
String fieldName = null;
Set<String> encodedTrialLicenses = new HashSet<>();
Set<String> signatures = new HashSet<>();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
while (parser.currentToken() != XContentParser.Token.END_OBJECT) {
token = parser.nextToken();
if (token == XContentParser.Token.FIELD_NAME) {
fieldName = parser.currentName();
}
if (fieldName != null) {
if (fieldName.equals(Fields.LICENSES)) {
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
if (parser.currentToken().isValue()) {
signatures.add(parser.text());
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()));
}
}
}
}
} else if (fieldName.equals(Fields.TRIAL_LICENSES)) {
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
if (parser.currentToken().isValue()) {
encodedTrialLicenses.add(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(signatures, encodedTrialLicenses);
return new LicensesMetaData(signedLicenses, trialLicenses);
}
/**
@ -175,8 +147,17 @@ public class LicensesMetaData implements MetaData.Custom {
*/
@Override
public void toXContent(LicensesMetaData licensesMetaData, XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.array(Fields.LICENSES, licensesMetaData.signatures.toArray(new String[licensesMetaData.signatures.size()]));
builder.array(Fields.TRIAL_LICENSES, licensesMetaData.encodedTrialLicenses.toArray(new String[licensesMetaData.encodedTrialLicenses.size()]));
builder.startArray(Fields.TRIAL_LICENSES);
for (License trailLicense : licensesMetaData.trialLicenses) {
builder.value(TrialLicenseUtils.toEncodedTrialLicense(trailLicense));
}
builder.endArray();
builder.startArray(Fields.SIGNED_LICENCES);
for (License license : licensesMetaData.signedLicenses) {
license.toXContent(builder, params);
}
builder.endArray();
}
@Override
@ -184,12 +165,9 @@ public class LicensesMetaData implements MetaData.Custom {
return EnumSet.of(MetaData.XContentContext.GATEWAY);
}
private final static class Fields {
private static final String LICENSES = "licenses";
private static final String SIGNED_LICENCES = "signed_licenses";
private static final String TRIAL_LICENSES = "trial_licenses";
}
}
}

@ -5,7 +5,6 @@
*/
package org.elasticsearch.license.plugin.core;
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.*;
@ -13,8 +12,8 @@ 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.collect.ImmutableList;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
@ -26,8 +25,8 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
import org.elasticsearch.gateway.GatewayService;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.manager.ESLicenseManager;
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;
@ -42,7 +41,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.license.core.ESLicenses.reduceAndMap;
import static org.elasticsearch.license.core.Licenses.reduceAndMap;
/**
* Service responsible for managing {@link LicensesMetaData}
@ -89,8 +88,6 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
public static final String REGISTER_TRIAL_LICENSE_ACTION_NAME = "internal:plugin/licenses/cluster/register_trial_license";
private final ESLicenseManager licenseManager;
private final ClusterService clusterService;
private final ThreadPool threadPool;
@ -119,10 +116,9 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
private final AtomicReference<LicensesMetaData> lastObservedLicensesState;
@Inject
public LicensesService(Settings settings, ClusterService clusterService, ThreadPool threadPool, TransportService transportService, ESLicenseManager licenseManager) {
public LicensesService(Settings settings, ClusterService clusterService, ThreadPool threadPool, TransportService transportService) {
super(settings);
this.clusterService = clusterService;
this.licenseManager = licenseManager;
this.threadPool = threadPool;
this.transportService = transportService;
this.lastObservedLicensesState = new AtomicReference<>(null);
@ -137,7 +133,7 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
@Override
public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener<LicensesUpdateResponse> listener) {
final PutLicenseRequest request = requestHolder.request;
final Set<ESLicense> newLicenses = Sets.newHashSet(request.licenses());
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) {
@ -152,10 +148,9 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses);
Set<String> newSignatures = licenseManager.toSignatures(newLicenses);
Set<String> newLicenseSignatures = Sets.union(licensesWrapper.signatures, newSignatures);
if (newLicenseSignatures.size() != licensesWrapper.signatures.size()) {
LicensesMetaData newLicensesMetaData = new LicensesMetaData(newLicenseSignatures, licensesWrapper.encodedTrialLicenses);
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();
}
@ -197,18 +192,9 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
MetaData metaData = currentState.metaData();
LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE);
final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses);
Set<ESLicense> currentSignedLicenses = licensesWrapper.signedLicenses(licenseManager);
Set<ESLicense> licensesToDelete = new HashSet<>();
for (ESLicense license : currentSignedLicenses) {
if (request.features().contains(license.feature())) {
licensesToDelete.add(license);
}
}
if (!licensesToDelete.isEmpty()) {
Set<ESLicense> reducedLicenses = Sets.difference(currentSignedLicenses, licensesToDelete);
Set<String> newSignatures = licenseManager.toSignatures(reducedLicenses);
LicensesMetaData newLicensesMetaData = new LicensesMetaData(newSignatures, licensesWrapper.encodedTrialLicenses);
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();
@ -237,20 +223,21 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
* {@inheritDoc}
*/
@Override
public List<ESLicense> getLicenses() {
LicensesMetaData currentMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
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
Set<ESLicense> licenses = Sets.union(licenseManager.fromSignatures(currentMetaData.getSignatures()),
TrialLicenseUtils.fromEncodedTrialLicenses(currentMetaData.getEncodedTrialLicenses()));
List<License> currentLicenses = new ArrayList<>();
currentLicenses.addAll(currentMetaData.getSignedLicenses());
currentLicenses.addAll(currentMetaData.getTrialLicenses());
// bucket license for feature with the latest expiry date
Map<String, ESLicense> licenseMap = new HashMap<>();
for (ESLicense license : licenses) {
Map<String, License> licenseMap = new HashMap<>();
for (License license : currentLicenses) {
if (!licenseMap.containsKey(license.feature())) {
licenseMap.put(license.feature(), license);
} else {
ESLicense prevLicense = licenseMap.get(license.feature());
License prevLicense = licenseMap.get(license.feature());
if (license.expiryDate() > prevLicense.expiryDate()) {
licenseMap.put(license.feature(), license);
}
@ -258,10 +245,10 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
}
// sort the licenses by issue date
List<ESLicense> reducedLicenses = new ArrayList<>(licenseMap.values());
Collections.sort(reducedLicenses, new Comparator<ESLicense>() {
List<License> reducedLicenses = new ArrayList<>(licenseMap.values());
Collections.sort(reducedLicenses, new Comparator<License>() {
@Override
public int compare(ESLicense license1, ESLicense license2) {
public int compare(License license1, License license2) {
return (int) (license2.issueDate() - license1.issueDate());
}
});
@ -270,19 +257,13 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
return Collections.emptyList();
}
private LicensesStatus checkLicenses(Set<ESLicense> licenses) {
final ImmutableMap<String, ESLicense> map = reduceAndMap(licenses);
return checkLicenses(map);
}
private LicensesStatus checkLicenses(Map<String, ESLicense> licenseMap) {
LicensesStatus status = LicensesStatus.VALID;
try {
licenseManager.verifyLicenses(licenseMap);
} catch (InvalidLicenseException e) {
status = LicensesStatus.INVALID;
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;
}
return status;
}
/**
@ -306,10 +287,10 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
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)) {
Set<String> newTrialLicenses = Sets.union(licensesWrapper.encodedTrialLicenses,
Sets.newHashSet(generateEncodedTrialLicense(request.feature, request.duration, request.maxNodes)));
List<License> currentTrailLicenses = new ArrayList<>(licensesWrapper.trialLicenses);
currentTrailLicenses.add(generateEncodedTrialLicense(request.feature, request.duration, request.maxNodes));
final LicensesMetaData newLicensesMetaData = new LicensesMetaData(
licensesWrapper.signatures, newTrialLicenses);
licensesWrapper.signedLicenses, ImmutableList.copyOf(currentTrailLicenses));
mdBuilder.putCustom(LicensesMetaData.TYPE, newLicensesMetaData);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
}
@ -322,8 +303,10 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
}
private boolean checkTrialLicenseGenerationCondition(String feature, LicensesWrapper licensesWrapper) {
for (ESLicense license : Sets.union(licensesWrapper.signedLicenses(licenseManager),
licensesWrapper.trialLicenses())) {
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;
}
@ -331,16 +314,14 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
return true;
}
private String generateEncodedTrialLicense(String feature, TimeValue duration, int maxNodes) {
return TrialLicenseUtils.toEncodedTrialLicense(
TrialLicenseUtils.builder()
.issuedTo(clusterService.state().getClusterName().value())
.issueDate(System.currentTimeMillis())
.duration(duration)
.feature(feature)
.maxNodes(maxNodes)
.build()
);
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();
}
});
}
@ -510,12 +491,12 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
if (logger.isDebugEnabled()) {
if (licensesMetaData != null) {
StringBuilder signedFeatures = new StringBuilder();
for (ESLicense license : licenseManager.fromSignatures(licensesMetaData.getSignatures())) {
for (License license : licensesMetaData.getSignedLicenses()) {
signedFeatures.append(license.feature());
signedFeatures.append(", ");
}
StringBuilder trialFeatures = new StringBuilder();
for (ESLicense license : TrialLicenseUtils.fromEncodedTrialLicenses(licensesMetaData.getEncodedTrialLicenses())) {
for (License license : licensesMetaData.getTrialLicenses()) {
trialFeatures.append(license.feature());
trialFeatures.append(", ");
}
@ -596,21 +577,25 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
}
private long expiryDateForFeature(String feature, final LicensesMetaData currentLicensesMetaData) {
final Map<String, ESLicense> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
ESLicense featureLicense;
final Map<String, License> effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData);
License featureLicense;
if ((featureLicense = effectiveLicenses.get(feature)) != null) {
return featureLicense.expiryDate();
}
return -1l;
}
private Map<String, ESLicense> getEffectiveLicenses(final LicensesMetaData metaData) {
Map<String, ESLicense> map = new HashMap<>();
private Map<String, License> getEffectiveLicenses(final LicensesMetaData metaData) {
Map<String, License> map = new HashMap<>();
if (metaData != null) {
Set<ESLicense> esLicenses = new HashSet<>();
esLicenses.addAll(licenseManager.fromSignatures(metaData.getSignatures()));
esLicenses.addAll(TrialLicenseUtils.fromEncodedTrialLicenses(metaData.getEncodedTrialLicenses()));
return reduceAndMap(esLicenses);
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);
@ -756,22 +741,48 @@ public class LicensesService extends AbstractLifecycleComponent<LicensesService>
return new LicensesWrapper(licensesMetaData);
}
private ImmutableSet<String> signatures = ImmutableSet.of();
private ImmutableSet<String> encodedTrialLicenses = ImmutableSet.of();
private ImmutableList<License> signedLicenses = ImmutableList.of();
private ImmutableList<License> trialLicenses = ImmutableList.of();
private LicensesWrapper(LicensesMetaData licensesMetaData) {
if (licensesMetaData != null) {
this.signatures = ImmutableSet.copyOf(licensesMetaData.getSignatures());
this.encodedTrialLicenses = ImmutableSet.copyOf(licensesMetaData.getEncodedTrialLicenses());
this.signedLicenses = ImmutableList.copyOf(licensesMetaData.getSignedLicenses());
this.trialLicenses = ImmutableList.copyOf(licensesMetaData.getTrialLicenses());
}
}
public Set<ESLicense> signedLicenses(ESLicenseManager licenseManager) {
return licenseManager.fromSignatures(signatures);
/**
* 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);
}
public Set<ESLicense> trialLicenses() {
return TrialLicenseUtils.fromEncodedTrialLicenses(encodedTrialLicenses);
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);
}
}

@ -5,16 +5,17 @@
*/
package org.elasticsearch.license.plugin.core;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.Base64;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.io.IOException;
import java.util.*;
import static org.elasticsearch.license.core.CryptUtils.decrypt;
import static org.elasticsearch.license.core.CryptUtils.encrypt;
public class TrialLicenseUtils {
@ -73,14 +74,14 @@ public class TrialLicenseUtils {
return this;
}
public ESLicense build() {
public License build() {
if (expiryDate == -1) {
expiryDate = issueDate + duration.millis();
}
if (uid == null) {
uid = UUID.randomUUID().toString();
}
return ESLicense.builder()
return License.builder()
.type(DEFAULT_TYPE)
.subscriptionType(DEFAULT_SUBSCRIPTION_TYPE)
.issuer(DEFAULT_ISSUER)
@ -95,70 +96,17 @@ public class TrialLicenseUtils {
}
public static Set<ESLicense> fromEncodedTrialLicenses(Set<String> encodedTrialLicenses) {
Set<ESLicense> licenses = new HashSet<>(encodedTrialLicenses.size());
for (String encodedTrialLicense : encodedTrialLicenses) {
licenses.add(fromEncodedTrialLicense(encodedTrialLicense));
}
return ImmutableSet.copyOf(licenses);
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 ESLicense fromEncodedTrialLicense(String encodedTrialLicense) {
byte[] encodedBytes = Base64.decodeBase64(encodedTrialLicense);
ByteBuffer byteBuffer = ByteBuffer.wrap(encodedBytes);
int uidLen = byteBuffer.getInt();
byte[] uidBytes = new byte[uidLen];
byteBuffer.get(uidBytes);
String uid = new String(uidBytes, StandardCharsets.UTF_8);
int issuedToLen = byteBuffer.getInt();
byte[] issuedToBytes = new byte[issuedToLen];
byteBuffer.get(issuedToBytes);
String issuedTo = new String(issuedToBytes, StandardCharsets.UTF_8);
int featureLen = byteBuffer.getInt();
byte[] featureBytes = new byte[featureLen];
byteBuffer.get(featureBytes);
String feature = new String(featureBytes, StandardCharsets.UTF_8);
int maxNodes = byteBuffer.getInt();
long issueDate = byteBuffer.getLong();
long expiryDate = byteBuffer.getLong();
return builder()
.uid(uid)
.issuedTo(issuedTo)
.feature(feature)
.maxNodes(maxNodes)
.issueDate(issueDate)
.expiryDate(expiryDate)
.build();
}
public static String toEncodedTrialLicense(ESLicense trialLicense) {
byte[] uidBytes = trialLicense.uid().getBytes(StandardCharsets.UTF_8);
byte[] featureBytes = trialLicense.feature().getBytes(StandardCharsets.UTF_8);
byte[] issuedToBytes = trialLicense.issuedTo().getBytes(StandardCharsets.UTF_8);
// uid len + uid bytes + issuedTo len + issuedTo bytes + feature bytes length + feature bytes + maxNodes + issueDate + expiryDate
int len = 4 + uidBytes.length + 4 + issuedToBytes.length + 4 + featureBytes.length + 4 + 8 + 8;
final byte[] encodedLicense = new byte[len];
ByteBuffer byteBuffer = ByteBuffer.wrap(encodedLicense);
byteBuffer.putInt(uidBytes.length);
byteBuffer.put(uidBytes);
byteBuffer.putInt(issuedToBytes.length);
byteBuffer.put(issuedToBytes);
byteBuffer.putInt(featureBytes.length);
byteBuffer.put(featureBytes);
byteBuffer.putInt(trialLicense.maxNodes());
byteBuffer.putLong(trialLicense.issueDate());
byteBuffer.putLong(trialLicense.expiryDate());
return Base64.encodeBase64String(encodedLicense);
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()));
}
}

@ -11,7 +11,7 @@ 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.ESLicenses;
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;
@ -39,14 +39,14 @@ public class RestGetLicenseAction extends BaseRestHandler {
*/
@Override
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
final Map<String, String> overrideParams = ImmutableMap.of(ESLicenses.REST_VIEW_MODE, "true");
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 {
ESLicenses.toXContent(response.licenses(), builder, params);
Licenses.toXContent(response.licenses(), builder, params);
return new BytesRestResponse(OK, builder);
}
});

@ -11,11 +11,10 @@ import org.elasticsearch.common.joda.Joda;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.core.DateUtils;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.licensor.ESLicenseSigner;
import org.elasticsearch.license.manager.ESLicenseManager;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.licensor.LicenseSigner;
import org.elasticsearch.license.core.LicenseVerifier;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import java.io.IOException;
@ -50,28 +49,19 @@ public abstract class AbstractLicensingTestBase {
priKeyPath = getResourcePath("/private.key");
}
protected static String dateMathString(String time, long now) {
public static String dateMathString(String time, long now) {
return dateTimeFormatter.print(dateMathParser.parse(time, now));
}
protected static long dateMath(String time, long now) {
public static long dateMath(String time, long now) {
return dateMathParser.parse(time, now);
}
public static String getTestPriKeyPath() throws Exception {
return getResourcePath("/private.key");
}
public static String getTestPubKeyPath() throws Exception {
return getResourcePath("/public.key");
}
public static String getResourcePath(String resource) throws Exception {
URL url = ESLicenseManager.class.getResource(resource);
URL url = LicenseVerifier.class.getResource(resource);
return url.toURI().getPath();
}
public static LicenseSpec generateRandomLicenseSpec() {
boolean datesInMillis = randomBoolean();
long now = System.currentTimeMillis();
@ -93,7 +83,7 @@ public abstract class AbstractLicensingTestBase {
}
}
public static String generateESLicenseSpecString(List<LicenseSpec> licenseSpecs) throws IOException {
public static String generateLicenseSpecString(List<LicenseSpec> licenseSpecs) throws IOException {
XContentBuilder licenses = jsonBuilder();
licenses.startObject();
licenses.startArray("licenses");
@ -124,11 +114,11 @@ public abstract class AbstractLicensingTestBase {
return licenses.string();
}
public static Set<ESLicense> generateSignedLicenses(List<LicenseSpec> licenseSpecs) throws Exception {
ESLicenseSigner signer = new ESLicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
Set<ESLicense> unSignedLicenses = new HashSet<>();
public static Set<License> generateSignedLicenses(List<LicenseSpec> licenseSpecs) throws Exception {
LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath);
Set<License> unSignedLicenses = new HashSet<>();
for (LicenseSpec spec : licenseSpecs) {
ESLicense.Builder builder = ESLicense.builder()
License.Builder builder = License.builder()
.uid(spec.uid)
.feature(spec.feature)
.type(spec.type)
@ -152,28 +142,6 @@ public abstract class AbstractLicensingTestBase {
return signer.sign(unSignedLicenses);
}
public static ESLicense generateSignedLicense(String feature, TimeValue expiryDuration) throws Exception {
return generateSignedLicense(feature, -1, expiryDuration);
}
public static ESLicense generateSignedLicense(String feature, long issueDate, TimeValue expiryDuration) throws Exception {
long issue = (issueDate != -1l) ? issueDate : System.currentTimeMillis();
final ESLicense licenseSpec = ESLicense.builder()
.uid(UUID.randomUUID().toString())
.feature(feature)
.expiryDate(issue + expiryDuration.getMillis())
.issueDate(issue)
.type("subscription")
.subscriptionType("gold")
.issuedTo("customer")
.issuer("elasticsearch")
.maxNodes(5)
.build();
ESLicenseSigner signer = new ESLicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
return signer.sign(licenseSpec);
}
public static class LicenseSpec {
public final String feature;
public final String issueDate;
@ -222,7 +190,7 @@ public abstract class AbstractLicensingTestBase {
}
}
public static void assertLicenseSpec(LicenseSpec spec, ESLicense license) {
public static void assertLicenseSpec(LicenseSpec spec, License license) {
assertThat(license.uid(), equalTo(spec.uid));
assertThat(license.feature(), equalTo(spec.feature));
assertThat(license.issuedTo(), equalTo(spec.issuedTo));

@ -3,14 +3,14 @@
* 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;
package org.elasticsearch.license;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.license.AbstractLicensingTestBase;
import org.elasticsearch.license.core.DateUtils;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
@ -29,11 +29,11 @@ public class LicenseSerializationTests extends AbstractLicensingTestBase {
long now = System.currentTimeMillis();
String issueDate = dateMathString("now", now);
String expiryDate = dateMathString("now+10d/d", now);
String licenseSpecs = generateESLicenseSpecString(Arrays.asList(new LicenseSpec("shield", issueDate, expiryDate)));
Set<ESLicense> esLicensesOutput = new HashSet<>(ESLicenses.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8), false));
ESLicense generatedLicense = esLicensesOutput.iterator().next();
String licenseSpecs = generateLicenseSpecString(Arrays.asList(new LicenseSpec("shield", issueDate, expiryDate)));
Set<License> licensesOutput = new HashSet<>(Licenses.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8), false));
License generatedLicense = licensesOutput.iterator().next();
assertThat(esLicensesOutput.size(), equalTo(1));
assertThat(licensesOutput.size(), equalTo(1));
assertThat(generatedLicense.issueDate(), equalTo(DateUtils.beginningOfTheDay(issueDate)));
assertThat(generatedLicense.expiryDate(), equalTo(DateUtils.endOfTheDay(expiryDate)));
}
@ -45,15 +45,15 @@ public class LicenseSerializationTests extends AbstractLicensingTestBase {
String shieldExpiryDate = dateMathString("now+30d/d", now);
String marvelIssueDate = dateMathString("now", now);
String marvelExpiryDate = dateMathString("now+60d/d", now);
String licenseSpecs = generateESLicenseSpecString(Arrays.asList(new LicenseSpec("shield", shieldIssueDate, shieldExpiryDate)));
String licenseSpecs1 = generateESLicenseSpecString(Arrays.asList(new LicenseSpec("marvel", marvelIssueDate, marvelExpiryDate)));
Set<ESLicense> esLicensesOutput = new HashSet<>();
esLicensesOutput.addAll(ESLicenses.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8), false));
esLicensesOutput.addAll(ESLicenses.fromSource(licenseSpecs1.getBytes(StandardCharsets.UTF_8), false));
assertThat(esLicensesOutput.size(), equalTo(2));
for (ESLicense esLicense : esLicensesOutput) {
assertThat(esLicense.issueDate(), equalTo(DateUtils.beginningOfTheDay((esLicense.feature().equals("shield")) ? shieldIssueDate : marvelIssueDate)));
assertThat(esLicense.expiryDate(), equalTo(DateUtils.endOfTheDay((esLicense.feature().equals("shield")) ? shieldExpiryDate : marvelExpiryDate)));
String licenseSpecs = generateLicenseSpecString(Arrays.asList(new LicenseSpec("shield", shieldIssueDate, shieldExpiryDate)));
String licenseSpecs1 = generateLicenseSpecString(Arrays.asList(new 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)));
}
}
@ -66,11 +66,11 @@ public class LicenseSerializationTests extends AbstractLicensingTestBase {
}
ArrayList<LicenseSpec> specs = new ArrayList<>(licenseSpecs.values());
String licenseSpecsSource = generateESLicenseSpecString(specs);
Set<ESLicense> esLicensesOutput = new HashSet<>(ESLicenses.fromSource(licenseSpecsSource.getBytes(StandardCharsets.UTF_8), false));
assertThat(esLicensesOutput.size(), equalTo(licenseSpecs.size()));
String licenseSpecsSource = generateLicenseSpecString(specs);
Set<License> licensesOutput = new HashSet<>(Licenses.fromSource(licenseSpecsSource.getBytes(StandardCharsets.UTF_8), false));
assertThat(licensesOutput.size(), equalTo(licenseSpecs.size()));
for (ESLicense license : esLicensesOutput) {
for (License license : licensesOutput) {
LicenseSpec spec = licenseSpecs.get(license.feature());
assertThat(spec, notNullValue());
assertLicenseSpec(spec, license);
@ -83,13 +83,13 @@ public class LicenseSerializationTests extends AbstractLicensingTestBase {
String expiredLicenseExpiryDate = dateMathString("now-1d/d", now);
String validLicenseIssueDate = dateMathString("now-10d/d", now);
String validLicenseExpiryDate = dateMathString("now+1d/d", now);
Set<ESLicense> licenses = generateSignedLicenses(Arrays.asList(new LicenseSpec("expired_feature", validLicenseIssueDate, expiredLicenseExpiryDate)
Set<License> licenses = generateSignedLicenses(Arrays.asList(new LicenseSpec("expired_feature", validLicenseIssueDate, expiredLicenseExpiryDate)
, new LicenseSpec("valid_feature", validLicenseIssueDate, validLicenseExpiryDate)));
assertThat(licenses.size(), equalTo(2));
for (ESLicense license : licenses) {
for (License license : licenses) {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
license.toXContent(builder, new ToXContent.MapParams(ImmutableMap.of(ESLicenses.REST_VIEW_MODE, "true")));
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();
assertThat(map.get("status"), notNullValue());
@ -100,7 +100,7 @@ public class LicenseSerializationTests extends AbstractLicensingTestBase {
}
}
for (ESLicense license : licenses) {
for (License license : licenses) {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
license.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.flush();

@ -0,0 +1,69 @@
/*
* 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;
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.XContentParser;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.junit.Test;
import java.util.*;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
public class LicenseVerificationTests extends AbstractLicensingTestBase {
@Test
public void testGeneratedLicenses() throws Exception {
License shieldLicense = TestUtils.generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24));
assertThat(LicenseVerifier.verifyLicense(shieldLicense), equalTo(true));
}
@Test
public void testMultipleFeatureLicenses() throws Exception {
License shieldLicense = TestUtils.generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24));
License marvelLicense = TestUtils.generateSignedLicense("marvel", TimeValue.timeValueHours(2 * 24));
assertThat(LicenseVerifier.verifyLicenses(Arrays.asList(shieldLicense, marvelLicense)), equalTo(true));
}
@Test
public void testLicenseTampering() throws Exception {
License license = TestUtils.generateSignedLicense("shield", TimeValue.timeValueHours(2));
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<LicenseSpec> licenseSpecs = new ArrayList<>();
for (int i = 0; i < n; i++) {
licenseSpecs.add(generateRandomLicenseSpec());
}
Set<License> generatedLicenses = generateSignedLicenses(licenseSpecs);
assertThat(generatedLicenses.size(), equalTo(n));
for (License generatedLicense: generatedLicenses) {
assertThat(LicenseVerifier.verifyLicense(generatedLicense), equalTo(true));
}
}
}

@ -5,13 +5,16 @@
*/
package org.elasticsearch.license;
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.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
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;
@ -19,28 +22,36 @@ import static org.junit.Assert.assertThat;
public class TestUtils {
public static void isSame(Collection<ESLicense> firstLicenses, Collection<ESLicense> secondLicenses) {
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<ESLicense> firstLicenses, Set<ESLicense> 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, ESLicense> licenses1 = ESLicenses.reduceAndMap(firstLicenses);
final Map<String, ESLicense> licenses2 = ESLicenses.reduceAndMap(secondLicenses);
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()) {
ESLicense license1 = licenses1.get(featureType);
ESLicense license2 = licenses2.get(featureType);
License license1 = licenses1.get(featureType);
License license2 = licenses2.get(featureType);
isSame(license1, license2);
}
}
public static void isSame(ESLicense license1, ESLicense 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()));
@ -52,10 +63,37 @@ public class TestUtils {
assertThat(license1.maxNodes(), equalTo(license2.maxNodes()));
}
public static String dumpLicense(ESLicense license) throws Exception {
public static String dumpLicense(License license) throws Exception {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
ESLicenses.toXContent(Collections.singletonList(license), builder, ToXContent.EMPTY_PARAMS);
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();
}
}

@ -1,48 +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.manager;
import org.elasticsearch.license.AbstractLicensingTestBase;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.ESLicense;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.*;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
public class LicenseSignatureTest extends AbstractLicensingTestBase {
private static ESLicenseManager esLicenseManager;
@BeforeClass
public static void setupManager() {
esLicenseManager = new ESLicenseManager();
}
@Test
public void testLicenseGeneration() throws Exception {
int n = randomIntBetween(5, 15);
List<LicenseSpec> licenseSpecs = new ArrayList<>();
for (int i = 0; i < n; i++) {
licenseSpecs.add(generateRandomLicenseSpec());
}
Set<ESLicense> generatedLicenses = generateSignedLicenses(licenseSpecs);
assertThat(generatedLicenses.size(), equalTo(n));
Set<String> signatures = new HashSet<>();
for (ESLicense license : generatedLicenses) {
signatures.add(license.signature());
}
Set<ESLicense> licenseFromSignatures = esLicenseManager.fromSignatures(signatures);
TestUtils.isSame(generatedLicenses, licenseFromSignatures);
}
}

@ -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.manager;
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.AbstractLicensingTestBase;
import org.elasticsearch.license.core.ESLicense;
import org.junit.BeforeClass;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
public class LicenseVerificationTests extends AbstractLicensingTestBase {
private static ESLicenseManager esLicenseManager;
@BeforeClass
public static void setupManager() {
esLicenseManager = new ESLicenseManager();
}
@Test
public void testGeneratedLicenses() throws Exception {
ESLicense shieldLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24));
Map<String, ESLicense> shieldLicenseMap = new HashMap<>();
shieldLicenseMap.put("shield", shieldLicense);
esLicenseManager.verifyLicenses(shieldLicenseMap);
}
@Test
public void testMultipleFeatureLicenses() throws Exception {
ESLicense shieldLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24));
ESLicense marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2 * 24));
Map<String, ESLicense> licenseMap = new HashMap<>();
licenseMap.put("shield", shieldLicense);
licenseMap.put("marvel", marvelLicense);
esLicenseManager.verifyLicenses(licenseMap);
}
@Test
public void testLicenseExpiry() throws Exception {
long now = System.currentTimeMillis();
long marvelIssueDate = dateMath("now-10d/d", now);
ESLicense shieldLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2 * 24));
ESLicense marvelLicense = generateSignedLicense("marvel", marvelIssueDate, TimeValue.timeValueHours(2 * 24));
Map<String, ESLicense> licenseMap = new HashMap<>();
licenseMap.put("shield", shieldLicense);
licenseMap.put("marvel", marvelLicense);
try {
esLicenseManager.verifyLicenses(licenseMap);
fail("verifyLicenses should throw InvalidLicenseException [expired license]");
} catch (InvalidLicenseException e) {
assertThat(e.getMessage(), containsString("Invalid License"));
}
licenseMap.clear();
licenseMap.put("shield", shieldLicense);
esLicenseManager.verifyLicenses(licenseMap);
}
@Test
public void testLicenseTampering() throws Exception {
ESLicense esLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2));
final ESLicense tamperedLicense = ESLicense.builder()
.fromLicenseSpec(esLicense, esLicense.signature())
.expiryDate(esLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
.verify()
.build();
Map<String, ESLicense> licenseMap = new HashMap<>();
licenseMap.put("shield", tamperedLicense);
try {
esLicenseManager.verifyLicenses(licenseMap);
fail("Tampered license should throw exception");
} catch (InvalidLicenseException e) {
assertThat(e.getMessage(), containsString("Invalid License"));
}
}
}

@ -15,8 +15,7 @@ import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.licensor.ESLicenseSigner;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.put.PutLicenseResponse;
import org.elasticsearch.license.plugin.consumer.EagerLicenseRegistrationPluginService;
@ -27,16 +26,13 @@ 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.junit.Ignore;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.license.AbstractLicensingTestBase.getTestPriKeyPath;
import static org.elasticsearch.license.AbstractLicensingTestBase.getTestPubKeyPath;
import static org.elasticsearch.license.TestUtils.generateSignedLicense;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
@ -68,7 +64,7 @@ public abstract class AbstractLicensesIntegrationTests extends ElasticsearchInte
@Override
public ClusterState execute(ClusterState currentState) throws Exception {
MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData());
mdBuilder.putCustom(LicensesMetaData.TYPE, null);
mdBuilder.removeCustom(LicensesMetaData.TYPE);
return ClusterState.builder(currentState).metaData(mdBuilder).build();
}
@ -80,25 +76,8 @@ public abstract class AbstractLicensesIntegrationTests extends ElasticsearchInte
latch.await();
}
public static ESLicense generateSignedLicense(String feature, TimeValue expiryDate) throws Exception {
final ESLicense licenseSpec = ESLicense.builder()
.uid(UUID.randomUUID().toString())
.feature(feature)
.expiryDate(System.currentTimeMillis() + expiryDate.getMillis())
.issueDate(System.currentTimeMillis())
.type("subscription")
.subscriptionType("gold")
.issuedTo("customer")
.issuer("elasticsearch")
.maxNodes(randomIntBetween(5, 100))
.build();
ESLicenseSigner signer = new ESLicenseSigner(getTestPriKeyPath(), getTestPubKeyPath());
return signer.sign(licenseSpec);
}
protected void putLicense(String feature, TimeValue expiryDuration) throws Exception {
ESLicense license1 = generateSignedLicense(feature, expiryDuration);
License license1 = generateSignedLicense(feature, expiryDuration);
final PutLicenseResponse putLicenseResponse = new PutLicenseRequestBuilder(client().admin().cluster()).setLicense(Lists.newArrayList(license1)).get();
assertThat(putLicenseResponse.isAcknowledged(), equalTo(true));
assertThat(putLicenseResponse.status(), equalTo(LicensesStatus.VALID));

@ -11,7 +11,7 @@ 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.ESLicense;
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;
@ -19,7 +19,6 @@ import org.elasticsearch.license.plugin.core.LicensesService;
import org.elasticsearch.license.plugin.core.LicensesStatus;
import org.elasticsearch.test.InternalTestCluster;
import org.junit.Before;
import org.junit.Ignore;
import java.util.HashSet;
import java.util.List;
@ -50,7 +49,7 @@ public abstract class AbstractLicensesServiceTests extends AbstractLicensesInteg
node = nodes[randomIntBetween(0, nodes.length - 1)];
}
protected void registerAndAckSignedLicenses(final LicensesManagerService masterLicensesManagerService, final List<ESLicense> license, final LicensesStatus expectedStatus) {
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);

@ -6,7 +6,7 @@
package org.elasticsearch.license.plugin;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesClientService;
import org.elasticsearch.license.plugin.core.LicensesManagerService;
import org.elasticsearch.license.plugin.core.LicensesService;
@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static org.elasticsearch.license.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.Matchers.equalTo;
@ -192,7 +193,7 @@ public class LicensesClientServiceTests extends AbstractLicensesServiceTests {
return new Action(new Runnable() {
@Override
public void run() {
ESLicense license;
License license;
try {
license = generateSignedLicense(feature, expiryDuration);
} catch (Exception e) {

@ -7,15 +7,12 @@ package org.elasticsearch.license.plugin;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse;
import org.elasticsearch.common.collect.ImmutableSet;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.manager.ESLicenseManager;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.plugin.core.*;
import org.elasticsearch.test.InternalTestCluster;
import org.junit.Test;
import java.util.Arrays;
@ -25,6 +22,7 @@ import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.elasticsearch.license.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.Matchers.equalTo;
@ -35,36 +33,35 @@ public class LicensesManagerServiceTests extends AbstractLicensesServiceTests {
@Test
public void testStoreAndGetLicenses() throws Exception {
LicensesManagerService licensesManagerService = masterLicensesManagerService();
ESLicense shieldShortLicense = generateSignedLicense("shield", TimeValue.timeValueHours(1));
ESLicense shieldLongLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2));
ESLicense marvelShortLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(1));
ESLicense marvelLongLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2));
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<ESLicense> licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense);
List<License> licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense);
Collections.shuffle(licenses);
registerAndAckSignedLicenses(licensesManagerService, licenses, LicensesStatus.VALID);
final ImmutableSet<String> licenseSignatures = masterLicenseManager().toSignatures(licenses);
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
// all licenses should be stored in the metaData
assertThat(licenseSignatures, equalTo(licensesMetaData.getSignatures()));
TestUtils.isSame(licenses, licensesMetaData.getSignedLicenses());
// only the latest expiry date license for each feature should be returned by getLicenses()
final List<ESLicense> getLicenses = licensesManagerService.getLicenses();
final List<License> getLicenses = licensesManagerService.getLicenses();
TestUtils.isSame(getLicenses, Arrays.asList(shieldLongLicense, marvelLongLicense));
}
@Test
public void testInvalidLicenseStorage() throws Exception {
LicensesManagerService licensesManagerService = masterLicensesManagerService();
ESLicense signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
// modify content of signed license
ESLicense tamperedLicense = ESLicense.builder()
License tamperedLicense = License.builder()
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
.verify()
.validate()
.build();
registerAndAckSignedLicenses(licensesManagerService, Arrays.asList(tamperedLicense), LicensesStatus.INVALID);
@ -72,7 +69,7 @@ public class LicensesManagerServiceTests extends AbstractLicensesServiceTests {
// ensure that the invalid license never made it to cluster state
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
if (licensesMetaData != null) {
assertThat(licensesMetaData.getSignatures().size(), equalTo(0));
assertThat(licensesMetaData.getSignedLicenses().size(), equalTo(0));
}
}
@ -86,29 +83,28 @@ public class LicensesManagerServiceTests extends AbstractLicensesServiceTests {
registerWithTrialLicense(clientService, clientListener, "shield", TimeValue.timeValueHours(1)).run();
// generate signed licenses for multiple features
ESLicense shieldShortLicense = generateSignedLicense("shield", TimeValue.timeValueHours(1));
ESLicense shieldLongLicense = generateSignedLicense("shield", TimeValue.timeValueHours(2));
ESLicense marvelShortLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(1));
ESLicense marvelLongLicense = generateSignedLicense("marvel", TimeValue.timeValueHours(2));
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<ESLicense> licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense);
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"));
final ImmutableSet<String> licenseSignatures = masterLicenseManager().toSignatures(Arrays.asList(marvelLongLicense, marvelShortLicense));
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licenseSignatures, equalTo(licensesMetaData.getSignatures()));
TestUtils.isSame(Arrays.asList(marvelLongLicense, marvelShortLicense), licensesMetaData.getSignedLicenses());
// check that trial license is not removed
assertThat(licensesMetaData.getEncodedTrialLicenses().size(), equalTo(1));
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.getSignatures().size(), equalTo(0));
assertThat(licensesMetaData.getSignedLicenses().size(), equalTo(0));
// check that trial license is not removed
assertThat(licensesMetaData.getEncodedTrialLicenses().size(), equalTo(1));
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(1));
}
private void removeAndAckSignedLicenses(final LicensesManagerService masterLicensesManagerService, final Set<String> featuresToDelete) {
@ -137,10 +133,4 @@ public class LicensesManagerServiceTests extends AbstractLicensesServiceTests {
}
assertThat("remove license(s) failed", success.get(), equalTo(true));
}
private ESLicenseManager masterLicenseManager() {
InternalTestCluster clients = internalCluster();
return clients.getInstance(ESLicenseManager.class, clients.getMasterName());
}
}

@ -0,0 +1,156 @@
/*
* 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.XContentParser;
import org.elasticsearch.license.AbstractLicensingTestBase;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.LicensesMetaData;
import org.elasticsearch.license.plugin.core.TrialLicenseUtils;
import org.junit.Test;
import java.util.*;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
public class LicensesMetaDataSerializationTests extends AbstractLicensingTestBase {
@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.FACTORY.toXContent(licensesMetaData, 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.FACTORY.toXContent(licensesMetaData, 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.FACTORY.toXContent(licensesMetaData, 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.FACTORY.toXContent(licensesMetaData, 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.FACTORY.toXContent(licensesMetaData, 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(bytes).createParser(bytes);
parser.nextToken(); // consume null
parser.nextToken(); // consume "licensesMetaData"
LicensesMetaData licensesMetaDataFromXContent = LicensesMetaData.FACTORY.fromXContent(parser);
parser.nextToken(); // consume endObject
assertThat(parser.nextToken(), nullValue());
return licensesMetaDataFromXContent;
}
}

@ -10,7 +10,7 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.get.GetLicenseResponse;
import org.elasticsearch.license.plugin.action.put.PutLicenseRequestBuilder;
@ -28,17 +28,17 @@ import java.util.ArrayList;
import java.util.List;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.license.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};
private final int trialLicenseDurationInSeconds = 2;
protected Settings transportClientSettings() {
return super.transportClientSettings();
}
@ -55,8 +55,8 @@ public class LicensesServiceClusterTest extends AbstractLicensesIntegrationTests
.put("plugins.load_classpath_plugins", false)
.put("node.data", true)
.put("format", "json")
.put(EagerLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.put(LazyLicenseRegistrationConsumerPlugin.NAME + ".trial_license_duration_in_seconds", trialLicenseDurationInSeconds)
.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(InternalNode.HTTP_ENABLED, true);
}
@ -73,7 +73,7 @@ public class LicensesServiceClusterTest extends AbstractLicensesIntegrationTests
ensureGreen();
logger.info("--> put signed license");
final List<ESLicense> licenses = generateAndPutLicenses();
final List<License> licenses = generateAndPutLicenses();
getAndCheckLicense(licenses);
logger.info("--> restart all nodes");
internalCluster().fullRestart();
@ -114,14 +114,14 @@ public class LicensesServiceClusterTest extends AbstractLicensesIntegrationTests
logger.info("--> check if multiple trial licenses are found for a feature");
LicensesMetaData licensesMetaData = clusterService().state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licensesMetaData.getEncodedTrialLicenses().size(), equalTo(FEATURES.length));
assertThat(licensesMetaData.getTrialLicenses().size(), equalTo(FEATURES.length));
wipeAllLicenses();
}
private List<ESLicense> generateAndPutLicenses() throws Exception {
private List<License> generateAndPutLicenses() throws Exception {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
List<ESLicense> putLicenses = new ArrayList<>(FEATURES.length);
List<License> putLicenses = new ArrayList<>(FEATURES.length);
for (String feature : FEATURES) {
putLicenses.add(generateSignedLicense(feature, TimeValue.timeValueMinutes(1)));
}
@ -137,11 +137,15 @@ public class LicensesServiceClusterTest extends AbstractLicensesIntegrationTests
return putLicenses;
}
private void getAndCheckLicense(List<ESLicense> licenses) {
private void getAndCheckLicense(List<License> licenses) {
ClusterAdminClient cluster = internalCluster().client().admin().cluster();
final GetLicenseResponse response = new GetLicenseRequestBuilder(cluster).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 {

@ -9,9 +9,7 @@ import org.elasticsearch.action.ActionFuture;
import org.elasticsearch.common.collect.Sets;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequest;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseRequestBuilder;
import org.elasticsearch.license.plugin.action.delete.DeleteLicenseResponse;
import org.elasticsearch.license.plugin.action.get.GetLicenseRequestBuilder;
@ -26,6 +24,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.elasticsearch.license.AbstractLicensingTestBase.dateMath;
import static org.elasticsearch.license.TestUtils.generateSignedLicense;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.hamcrest.CoreMatchers.equalTo;
@ -48,8 +48,8 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@Test
public void testPutLicense() throws Exception {
ESLicense signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
List<ESLicense> actualLicenses = Collections.singletonList(signedLicense);
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
List<License> actualLicenses = Collections.singletonList(signedLicense);
// put license
PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster())
@ -69,7 +69,7 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@Test
public void testPutLicenseFromString() throws Exception {
ESLicense signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
String licenseString = TestUtils.dumpLicense(signedLicense);
// put license source
@ -90,13 +90,13 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@Test
public void testPutInvalidLicense() throws Exception {
ESLicense signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
// modify content of signed license
ESLicense tamperedLicense = ESLicense.builder()
License tamperedLicense = License.builder()
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
.verify()
.validate()
.build();
PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster());
@ -111,11 +111,30 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
assertThat(getLicenseResponse.licenses().size(), equalTo(0));
}
@Test
public void testPutExpiredLicense() throws Exception {
License expiredLicense = generateSignedLicense("expiredFeature", dateMath("now-10d/d", System.currentTimeMillis()), TimeValue.timeValueMinutes(2));
License signedLicense = generateSignedLicense("feature", TimeValue.timeValueMinutes(2));
PutLicenseRequestBuilder builder = new PutLicenseRequestBuilder(client().admin().cluster());
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()).get();
assertThat(getLicenseResponse.licenses().size(), equalTo(1));
TestUtils.isSame(getLicenseResponse.licenses().get(0), signedLicense);
}
@Test
public void testPutLicensesForSameFeature() throws Exception {
ESLicense shortedSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
ESLicense longerSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(5));
List<ESLicense> actualLicenses = Arrays.asList(longerSignedLicense, shortedSignedLicense);
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())
@ -135,9 +154,9 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@Test
public void testPutLicensesForMultipleFeatures() throws Exception {
ESLicense shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
ESLicense marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<ESLicense> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
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())
@ -156,10 +175,10 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@Test
public void testPutMultipleLicensesForMultipleFeatures() throws Exception {
ESLicense shortedSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
ESLicense longerSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(5));
ESLicense marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<ESLicense> actualLicenses = Arrays.asList(marvelLicense, shortedSignedLicense, longerSignedLicense);
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())
@ -179,9 +198,9 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@Test
public void testRemoveLicenseSimple() throws Exception {
ESLicense shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
ESLicense marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<ESLicense> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
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())
@ -209,9 +228,9 @@ public class LicensesTransportTests extends AbstractLicensesIntegrationTests {
@Test
public void testRemoveLicenses() throws Exception {
ESLicense shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2));
ESLicense marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5));
List<ESLicense> actualLicenses = Arrays.asList(marvelLicense, shieldLicense);
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())

@ -0,0 +1,40 @@
/*
* 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.AbstractLicensingTestBase;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.plugin.core.TrialLicenseUtils;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;
public class TrailLicenseSerializationTests extends AbstractLicensingTestBase {
@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);
}
}
}

@ -3,13 +3,14 @@
* 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;
package org.elasticsearch.license.tools;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.commons.MissingOptionException;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool.KeyPairGenerator;
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool;
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool.KeyGenerator;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@ -85,15 +86,15 @@ public class KeyPairGenerationToolTests extends CliToolTestCase {
Command command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, args("--privateKeyPath " + privateKeyPath
+ " --publicKeyPath " + publicKeyPath));
assertThat(command, instanceOf(KeyPairGenerator.class));
KeyPairGenerator keyPairGenerator = (KeyPairGenerator) command;
assertThat(keyPairGenerator.privateKeyPath, equalTo(privateKeyPath));
assertThat(keyPairGenerator.publicKeyPath, equalTo(publicKeyPath));
assertThat(command, instanceOf(KeyGenerator.class));
KeyGenerator keyGenerator = (KeyGenerator) command;
assertThat(keyGenerator.privateKeyPath, equalTo(privateKeyPath));
assertThat(keyGenerator.publicKeyPath, equalTo(publicKeyPath));
assertThat(Paths.get(publicKeyPath).toFile().exists(), equalTo(false));
assertThat(Paths.get(privateKeyPath).toFile().exists(), equalTo(false));
assertThat(keyPairGenerator.execute(ImmutableSettings.EMPTY, new Environment(ImmutableSettings.EMPTY)), equalTo(ExitStatus.OK));
assertThat(keyGenerator.execute(ImmutableSettings.EMPTY, new Environment(ImmutableSettings.EMPTY)), equalTo(ExitStatus.OK));
assertThat(Paths.get(publicKeyPath).toFile().exists(), equalTo(true));
assertThat(Paths.get(privateKeyPath).toFile().exists(), equalTo(true));

@ -3,17 +3,17 @@
* 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;
package org.elasticsearch.license.tools;
import org.apache.commons.io.FileUtils;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.cli.commons.MissingOptionException;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.licensor.tools.LicenseGeneratorTool;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@ -21,6 +21,8 @@ import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
@ -52,7 +54,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
LicenseSpec inputLicenseSpec = generateRandomLicenseSpec();
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
args("--license " + generateESLicenseSpecString(Arrays.asList(inputLicenseSpec))
args("--license " + generateLicenseSpecString(Arrays.asList(inputLicenseSpec))
+ " --publicKeyPath " + pubKeyPath.concat("invalid")
+ " --privateKeyPath " + priKeyPath));
@ -61,7 +63,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
args("--license " + generateESLicenseSpecString(Arrays.asList(inputLicenseSpec))
args("--license " + generateLicenseSpecString(Arrays.asList(inputLicenseSpec))
+ " --privateKeyPath " + priKeyPath.concat("invalid")
+ " --publicKeyPath " + pubKeyPath));
@ -89,7 +91,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
boolean pubKeyMissing = randomBoolean();
try {
licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
args("--license " + generateESLicenseSpecString(Arrays.asList(inputLicenseSpec))
args("--license " + generateLicenseSpecString(Arrays.asList(inputLicenseSpec))
+ ((!pubKeyMissing) ? " --publicKeyPath " + pubKeyPath : "")
+ ((pubKeyMissing) ? " --privateKeyPath " + priKeyPath : "")));
fail("missing argument: " + ((pubKeyMissing) ? "publicKeyPath" : "privateKeyPath") + " should throw an exception");
@ -103,7 +105,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
LicenseSpec inputLicenseSpec = generateRandomLicenseSpec();
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
args("--license " + generateESLicenseSpecString(Arrays.asList(inputLicenseSpec))
args("--license " + generateLicenseSpecString(Arrays.asList(inputLicenseSpec))
+ " --publicKeyPath " + pubKeyPath
+ " --privateKeyPath " + priKeyPath));
@ -112,7 +114,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(1));
ESLicense outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
License outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
}
@ -121,7 +123,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
public void testParsingLicenseFile() throws Exception {
LicenseSpec inputLicenseSpec = generateRandomLicenseSpec();
File tempFile = temporaryFolder.newFile("license_spec.json");
FileUtils.write(tempFile, generateESLicenseSpecString(Arrays.asList(inputLicenseSpec)));
Files.write(Paths.get(tempFile.getAbsolutePath()), generateLicenseSpecString(Arrays.asList(inputLicenseSpec)).getBytes(StandardCharsets.UTF_8));
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
@ -134,7 +136,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(1));
ESLicense outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
License outputLicenseSpec = licenseGenerator.licenseSpecs.iterator().next();
assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
}
@ -149,7 +151,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
}
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
args("--license " + generateESLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values()))
args("--license " + generateLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values()))
+ " --publicKeyPath " + pubKeyPath
+ " --privateKeyPath " + priKeyPath));
@ -159,7 +161,7 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
assertThat(licenseGenerator.licenseSpecs.size(), equalTo(inputLicenseSpecs.size()));
for (ESLicense outputLicenseSpec : licenseGenerator.licenseSpecs) {
for (License outputLicenseSpec : licenseGenerator.licenseSpecs) {
LicenseSpec inputLicenseSpec = inputLicenseSpecs.get(outputLicenseSpec.feature());
assertThat(inputLicenseSpec, notNullValue());
assertLicenseSpec(inputLicenseSpec, outputLicenseSpec);
@ -174,20 +176,20 @@ public class LicenseGenerationToolTests extends CliToolTestCase {
LicenseSpec licenseSpec = generateRandomLicenseSpec();
inputLicenseSpecs.put(licenseSpec.feature, licenseSpec);
}
List<ESLicense> licenseSpecs = ESLicenses.fromSource(generateESLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())).getBytes(StandardCharsets.UTF_8), false);
List<License> licenseSpecs = Licenses.fromSource(generateLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())).getBytes(StandardCharsets.UTF_8), false);
String output = runLicenseGenerationTool(pubKeyPath, priKeyPath, new HashSet<>(licenseSpecs), ExitStatus.OK);
List<ESLicense> outputLicenses = ESLicenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
List<License> outputLicenses = Licenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
assertThat(outputLicenses.size(), equalTo(inputLicenseSpecs.size()));
for (ESLicense outputLicense : outputLicenses) {
for (License outputLicense : outputLicenses) {
LicenseSpec inputLicenseSpec = inputLicenseSpecs.get(outputLicense.feature());
assertThat(inputLicenseSpec, notNullValue());
assertLicenseSpec(inputLicenseSpec, outputLicense);
}
}
private String runLicenseGenerationTool(String pubKeyPath, String priKeyPath, Set<ESLicense> licenseSpecs, ExitStatus expectedExitStatus) throws Exception {
private String runLicenseGenerationTool(String pubKeyPath, String priKeyPath, Set<License> licenseSpecs, ExitStatus expectedExitStatus) throws Exception {
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
LicenseGenerator licenseGenerator = new LicenseGenerator(outputTerminal, pubKeyPath, priKeyPath, licenseSpecs);
assertThat(execute(licenseGenerator, ImmutableSettings.EMPTY), equalTo(expectedExitStatus));

@ -3,28 +3,30 @@
* 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;
package org.elasticsearch.license.tools;
import org.apache.commons.io.FileUtils;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.ESLicense;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.Licenses;
import org.elasticsearch.license.licensor.tools.LicenseVerificationTool;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import static org.elasticsearch.common.cli.CliTool.Command;
import static org.elasticsearch.common.cli.CliTool.ExitStatus;
import static org.elasticsearch.license.AbstractLicensingTestBase.generateSignedLicense;
import static org.elasticsearch.license.TestUtils.generateSignedLicense;
import static org.elasticsearch.license.licensor.tools.LicenseVerificationTool.LicenseVerifier;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
@ -47,7 +49,7 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
@Test
public void testParsingSimple() throws Exception {
ESLicense inputLicense = generateSignedLicense("feature__1",
License inputLicense = TestUtils.generateSignedLicense("feature__1",
TimeValue.timeValueHours(1));
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
@ -55,13 +57,13 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(licenseVerifier.licenses.size(), equalTo(1));
ESLicense outputLicense = licenseVerifier.licenses.iterator().next();
License outputLicense = licenseVerifier.licenses.iterator().next();
TestUtils.isSame(inputLicense, outputLicense);
}
@Test
public void testParsingLicenseFile() throws Exception {
ESLicense inputLicense = generateSignedLicense("feature__1",
License inputLicense = TestUtils.generateSignedLicense("feature__1",
TimeValue.timeValueHours(1));
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
@ -70,7 +72,7 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(licenseVerifier.licenses.size(), equalTo(1));
ESLicense outputLicense = licenseVerifier.licenses.iterator().next();
License outputLicense = licenseVerifier.licenses.iterator().next();
TestUtils.isSame(inputLicense, outputLicense);
}
@ -78,15 +80,15 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
@Test
public void testParsingMultipleLicense() throws Exception {
int n = randomIntBetween(2, 5);
Map<String, ESLicense> inputLicenses = new HashMap<>();
Map<String, License> inputLicenses = new HashMap<>();
for (int i = 0; i < n; i++) {
ESLicense esLicense = generateSignedLicense("feature__" + i,
License license = TestUtils.generateSignedLicense("feature__" + i,
TimeValue.timeValueHours(1));
inputLicenses.put(esLicense.feature(), esLicense);
inputLicenses.put(license.feature(), license);
}
StringBuilder argsBuilder = new StringBuilder();
for (ESLicense inputLicense : inputLicenses.values()) {
for (License inputLicense : inputLicenses.values()) {
argsBuilder.append(" --license ")
.append(TestUtils.dumpLicense(inputLicense));
}
@ -97,8 +99,8 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(licenseVerifier.licenses.size(), equalTo(inputLicenses.size()));
for (ESLicense outputLicense : licenseVerifier.licenses) {
ESLicense inputLicense = inputLicenses.get(outputLicense.feature());
for (License outputLicense : licenseVerifier.licenses) {
License inputLicense = inputLicenses.get(outputLicense.feature());
assertThat(inputLicense, notNullValue());
TestUtils.isSame(inputLicense, outputLicense);
}
@ -107,19 +109,19 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
@Test
public void testToolSimple() throws Exception {
int n = randomIntBetween(2, 5);
Map<String, ESLicense> inputLicenses = new HashMap<>();
Map<String, License> inputLicenses = new HashMap<>();
for (int i = 0; i < n; i++) {
ESLicense esLicense = generateSignedLicense("feature__" + i,
License license = TestUtils.generateSignedLicense("feature__" + i,
TimeValue.timeValueHours(1));
inputLicenses.put(esLicense.feature(), esLicense);
inputLicenses.put(license.feature(), license);
}
String output = runLicenseVerificationTool(new HashSet<>(inputLicenses.values()), ExitStatus.OK);
List<ESLicense> outputLicenses = ESLicenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
List<License> outputLicenses = Licenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true);
assertThat(outputLicenses.size(), equalTo(inputLicenses.size()));
for (ESLicense outputLicense : outputLicenses) {
ESLicense inputLicense = inputLicenses.get(outputLicense.feature());
for (License outputLicense : outputLicenses) {
License inputLicense = inputLicenses.get(outputLicense.feature());
assertThat(inputLicense, notNullValue());
TestUtils.isSame(inputLicense, outputLicense);
}
@ -127,23 +129,23 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
@Test
public void testToolInvalidLicense() throws Exception {
ESLicense signedLicense = generateSignedLicense("feature__1"
License signedLicense = TestUtils.generateSignedLicense("feature__1"
, TimeValue.timeValueHours(1));
ESLicense tamperedLicense = ESLicense.builder()
License tamperedLicense = License.builder()
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + randomIntBetween(1, 1000)).build();
runLicenseVerificationTool(Collections.singleton(tamperedLicense), ExitStatus.DATA_ERROR);
}
private String dumpLicenseAsFile(ESLicense license) throws Exception {
private String dumpLicenseAsFile(License license) throws Exception {
File tempFile = temporaryFolder.newFile();
FileUtils.write(tempFile, TestUtils.dumpLicense(license));
Files.write(Paths.get(tempFile.getAbsolutePath()), TestUtils.dumpLicense(license).getBytes(StandardCharsets.UTF_8));
return tempFile.getAbsolutePath();
}
private String runLicenseVerificationTool(Set<ESLicense> licenses, ExitStatus expectedExitStatus) throws Exception {
private String runLicenseVerificationTool(Set<License> licenses, ExitStatus expectedExitStatus) throws Exception {
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
LicenseVerifier licenseVerifier = new LicenseVerifier(outputTerminal, licenses);
assertThat(execute(licenseVerifier, ImmutableSettings.EMPTY), equalTo(expectedExitStatus));

Binary file not shown.

Binary file not shown.