From 32af5a9d9cca1aa3e80bc28f3abce1b942538c1d Mon Sep 17 00:00:00 2001 From: Areek Zillur Date: Tue, 18 Nov 2014 19:31:56 -0500 Subject: [PATCH] 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@0510091d2d9d8ac61ca05ad4f2bed10daf28f1e0 --- pom.xml | 12 - src/main/assemblies/exec.xml | 2 - src/main/assemblies/plugin.xml | 8 - .../license/core/CryptUtils.java | 245 ++++++++++++++++++ .../core/{ESLicense.java => License.java} | 32 +-- .../license/core/LicenseVerifier.java | 118 +++++++++ .../core/{ESLicenses.java => Licenses.java} | 53 ++-- .../core/ResourcePublicKeyDataProvider.java | 33 --- .../license/licensor/ESLicenseSigner.java | 139 ---------- .../license/licensor/LicenseSigner.java | 101 ++++++++ .../licensor/tools/KeyPairGeneratorTool.java | 50 ++-- .../licensor/tools/LicenseGeneratorTool.java | 22 +- .../tools/LicenseVerificationTool.java | 36 ++- .../license/manager/ESLicenseManager.java | 234 ----------------- .../license/plugin/LicenseModule.java | 4 +- .../plugin/action/get/GetLicenseResponse.java | 16 +- .../plugin/action/put/PutLicenseRequest.java | 20 +- .../action/put/PutLicenseRequestBuilder.java | 4 +- .../plugin/core/LicensesManagerService.java | 4 +- .../license/plugin/core/LicensesMetaData.java | 150 +++++------ .../license/plugin/core/LicensesService.java | 173 +++++++------ .../plugin/core/TrialLicenseUtils.java | 94 ++----- .../plugin/rest/RestGetLicenseAction.java | 6 +- .../license/AbstractLicensingTestBase.java | 56 +--- .../LicenseSerializationTests.java | 48 ++-- .../license/LicenseVerificationTests.java | 69 +++++ .../org/elasticsearch/license/TestUtils.java | 60 ++++- .../license/manager/LicenseSignatureTest.java | 48 ---- .../manager/LicenseVerificationTests.java | 93 ------- .../AbstractLicensesIntegrationTests.java | 29 +-- .../plugin/AbstractLicensesServiceTests.java | 5 +- .../plugin/LicensesClientServiceTests.java | 5 +- .../plugin/LicensesManagerServiceTests.java | 54 ++-- .../LicensesMetaDataSerializationTests.java | 156 +++++++++++ .../plugin/LicensesServiceClusterTest.java | 24 +- .../plugin/LicensesTransportTests.java | 69 +++-- .../TrailLicenseSerializationTests.java | 40 +++ .../tools/KeyPairGenerationToolTests.java | 15 +- .../tools/LicenseGenerationToolTests.java | 36 +-- .../tools/LicenseVerificationToolTests.java | 54 ++-- src/test/resources/private.key | Bin 1232 -> 1232 bytes src/test/resources/public.key | Bin 304 -> 304 bytes 42 files changed, 1256 insertions(+), 1161 deletions(-) create mode 100644 src/main/java/org/elasticsearch/license/core/CryptUtils.java rename src/main/java/org/elasticsearch/license/core/{ESLicense.java => License.java} (93%) create mode 100644 src/main/java/org/elasticsearch/license/core/LicenseVerifier.java rename src/main/java/org/elasticsearch/license/core/{ESLicenses.java => Licenses.java} (63%) delete mode 100644 src/main/java/org/elasticsearch/license/core/ResourcePublicKeyDataProvider.java delete mode 100644 src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java create mode 100644 src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java delete mode 100644 src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java rename src/test/java/org/elasticsearch/license/{licensor => }/LicenseSerializationTests.java (63%) create mode 100644 src/test/java/org/elasticsearch/license/LicenseVerificationTests.java delete mode 100644 src/test/java/org/elasticsearch/license/manager/LicenseSignatureTest.java delete mode 100644 src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java create mode 100644 src/test/java/org/elasticsearch/license/plugin/LicensesMetaDataSerializationTests.java create mode 100644 src/test/java/org/elasticsearch/license/plugin/TrailLicenseSerializationTests.java rename src/test/java/org/elasticsearch/license/{licensor => }/tools/KeyPairGenerationToolTests.java (88%) rename src/test/java/org/elasticsearch/license/{licensor => }/tools/LicenseGenerationToolTests.java (84%) rename src/test/java/org/elasticsearch/license/{licensor => }/tools/LicenseVerificationToolTests.java (74%) diff --git a/pom.xml b/pom.xml index 8f7b92737d8..8a76deb0ae9 100644 --- a/pom.xml +++ b/pom.xml @@ -119,18 +119,6 @@ - - net.nicholaswilliams.java.licensing - licensing-core - 1.1.0 - - - - net.nicholaswilliams.java.licensing - licensing-licensor-base - 1.1.0 - - org.elasticsearch elasticsearch diff --git a/src/main/assemblies/exec.xml b/src/main/assemblies/exec.xml index ceff530a3c3..006a1d331a7 100644 --- a/src/main/assemblies/exec.xml +++ b/src/main/assemblies/exec.xml @@ -28,8 +28,6 @@ true org.elasticsearch:elasticsearch-license:*:exec - net.nicholaswilliams.java.licensing:licensing-core - net.nicholaswilliams.java.licensing:licensing-licensor-base org.elasticsearch:elasticsearch diff --git a/src/main/assemblies/plugin.xml b/src/main/assemblies/plugin.xml index ff0f0a39364..8e8e42e7c05 100644 --- a/src/main/assemblies/plugin.xml +++ b/src/main/assemblies/plugin.xml @@ -14,13 +14,5 @@ org.elasticsearch:elasticsearch - - / - true - true - - net.nicholaswilliams.java.licensing:licensing-core - - \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/license/core/CryptUtils.java b/src/main/java/org/elasticsearch/license/core/CryptUtils.java new file mode 100644 index 00000000000..939b5192828 --- /dev/null +++ b/src/main/java/org/elasticsearch/license/core/CryptUtils.java @@ -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 passPhrase + */ + 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 passPhrase + */ + 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 passPhrase + */ + 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 passPhrase + */ + public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey, char[] passPhrase) { + PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded()); + return encrypt(encodedKeySpec.getEncoded(), passPhrase); + } + + /** + * Encrypts provided data with DEFAULT_PASS_PHRASE + */ + public static byte[] encrypt(byte[] data) { + try { + return encrypt(data, hashPassPhrase(DEFAULT_PASS_PHRASE)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + /** + * Decrypts provided encryptedData with DEFAULT_PASS_PHRASE + */ + public static byte[] decrypt(byte[] encryptedData) { + try { + return decrypt(encryptedData, hashPassPhrase(DEFAULT_PASS_PHRASE)); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + /** + * Encrypts provided data with passPhrase + */ + 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 encryptedData with passPhrase + */ + 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(); + } +} diff --git a/src/main/java/org/elasticsearch/license/core/ESLicense.java b/src/main/java/org/elasticsearch/license/core/License.java similarity index 93% rename from src/main/java/org/elasticsearch/license/core/ESLicense.java rename to src/main/java/org/elasticsearch/license/core/License.java index e019e5cb5b7..d3a8874e888 100644 --- a/src/main/java/org/elasticsearch/license/core/ESLicense.java +++ b/src/main/java/org/elasticsearch/license/core/License.java @@ -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) { diff --git a/src/main/java/org/elasticsearch/license/core/LicenseVerifier.java b/src/main/java/org/elasticsearch/license/core/LicenseVerifier.java new file mode 100644 index 00000000000..2a73d86deda --- /dev/null +++ b/src/main/java/org/elasticsearch/license/core/LicenseVerifier.java @@ -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 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); + } + } +} diff --git a/src/main/java/org/elasticsearch/license/core/ESLicenses.java b/src/main/java/org/elasticsearch/license/core/Licenses.java similarity index 63% rename from src/main/java/org/elasticsearch/license/core/ESLicenses.java rename to src/main/java/org/elasticsearch/license/core/Licenses.java index d4bf713caa6..adeedebb4c5 100644 --- a/src/main/java/org/elasticsearch/license/core/ESLicenses.java +++ b/src/main/java/org/elasticsearch/license/core/Licenses.java @@ -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 licenses, XContentBuilder builder, ToXContent.Params params) throws IOException { + private Licenses() {} + + public static void toXContent(Collection 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 fromSource(String content) throws IOException { + public static List fromSource(String content) throws IOException { return fromSource(content.getBytes(StandardCharsets.UTF_8), true); } - public static List fromSource(byte[] bytes) throws IOException { + public static List fromSource(byte[] bytes) throws IOException { return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), true); } - public static List fromSource(byte[] bytes, boolean verify) throws IOException { + public static List fromSource(byte[] bytes, boolean verify) throws IOException { return fromXContent(XContentFactory.xContent(bytes).createParser(bytes), verify); } - public static List fromXContent(XContentParser parser, boolean verify) throws IOException { - List esLicenses = new ArrayList<>(); + public static List fromXContent(XContentParser parser, boolean verify) throws IOException { + List 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 readFrom(StreamInput in) throws IOException { + public static List readFrom(StreamInput in) throws IOException { int size = in.readVInt(); - List esLicenses = new ArrayList<>(size); + List 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 esLicenses, StreamOutput out) throws IOException { - out.writeVInt(esLicenses.size()); - for (ESLicense license : esLicenses) { + public static void writeTo(List licenses, StreamOutput out) throws IOException { + out.writeVInt(licenses.size()); + for (License license : licenses) { license.writeTo(out); } } - public static ImmutableMap reduceAndMap(Set esLicensesSet) { - Map map = new HashMap<>(esLicensesSet.size()); - for (ESLicense license : esLicensesSet) { + public static ImmutableMap reduceAndMap(Set licensesSet) { + Map map = new HashMap<>(licensesSet.size()); + for (License license : licensesSet) { putIfAppropriate(map, license); } return ImmutableMap.copyOf(map); } - private static void putIfAppropriate(Map licenseMap, ESLicense license) { + private static void putIfAppropriate(Map 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); } diff --git a/src/main/java/org/elasticsearch/license/core/ResourcePublicKeyDataProvider.java b/src/main/java/org/elasticsearch/license/core/ResourcePublicKeyDataProvider.java deleted file mode 100644 index 1af7d600d3b..00000000000 --- a/src/main/java/org/elasticsearch/license/core/ResourcePublicKeyDataProvider.java +++ /dev/null @@ -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); - } - } -} diff --git a/src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java b/src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java deleted file mode 100644 index 7350a61798b..00000000000 --- a/src/main/java/org/elasticsearch/license/licensor/ESLicenseSigner.java +++ /dev/null @@ -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 sign(Set licenseSpecs) throws IOException { - final ImmutableSet.Builder builder = ImmutableSet.builder(); - for (ESLicense licenseSpec : licenseSpecs) { - builder.add(sign(licenseSpec)); - } - return builder.build(); - } - - /** - * Generates a signature for the esLicense. - * 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(); - } - -} diff --git a/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java b/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java new file mode 100644 index 00000000000..f97511e7be1 --- /dev/null +++ b/src/main/java/org/elasticsearch/license/licensor/LicenseSigner.java @@ -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 sign(Set licenseSpecs) throws IOException { + final ImmutableSet.Builder builder = ImmutableSet.builder(); + for (License licenseSpec : licenseSpecs) { + builder.add(sign(licenseSpec)); + } + return builder.build(); + } + + /** + * Generates a signature for the licenseSpec. + * 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); + } + } +} diff --git a/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java b/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java index 28b6322682c..92ff1cbf73a 100644 --- a/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java +++ b/src/main/java/org/elasticsearch/license/licensor/tools/KeyPairGeneratorTool.java @@ -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); diff --git a/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java b/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java index a340d81106a..e64f3c473ed 100644 --- a/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java +++ b/src/main/java/org/elasticsearch/license/licensor/tools/LicenseGeneratorTool.java @@ -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 licenseSpecs; + public final Set licenseSpecs; public final String publicKeyFilePath; public final String privateKeyFilePath; - public LicenseGenerator(Terminal terminal, String publicKeyFilePath, String privateKeyFilePath, Set licenseSpecs) { + public LicenseGenerator(Terminal terminal, String publicKeyFilePath, String privateKeyFilePath, Set 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 licenseSpecs = new HashSet<>(); + Set 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 signedLicences = signer.sign(licenseSpecs); + LicenseSigner signer = new LicenseSigner(privateKeyFilePath, publicKeyFilePath); + ImmutableSet 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()); diff --git a/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java b/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java index d8625047f73..8e781966f84 100644 --- a/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java +++ b/src/main/java/org/elasticsearch/license/licensor/tools/LicenseVerificationTool.java @@ -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 licenses; + public final Set licenses; - public LicenseVerifier(Terminal terminal, Set licenses) { + public LicenseVerifier(Terminal terminal, Set 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 esLicenses = new HashSet<>(); + Set 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 effectiveLicenses = ESLicenses.reduceAndMap(licenses); - ESLicenseManager licenseManager = new ESLicenseManager(); - try { - licenseManager.verifyLicenses(effectiveLicenses); - } catch (InvalidLicenseException e) { + Map 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()); diff --git a/src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java b/src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java deleted file mode 100644 index fdb5adb96a5..00000000000 --- a/src/main/java/org/elasticsearch/license/manager/ESLicenseManager.java +++ /dev/null @@ -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 - *

- */ -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 toSignatures(Collection esLicenses) { - Set signatures = new HashSet<>(); - for (ESLicense esLicense : esLicenses) { - signatures.add(esLicense.signature()); - } - return ImmutableSet.copyOf(signatures); - } - - public ImmutableSet fromSignatures(Set signatures) { - Set esLicenses = new HashSet<>(); - for (String signature : signatures) { - esLicenses.add(fromSignature(signature)); - } - return ImmutableSet.copyOf(esLicenses); - } - - public void verifyLicenses(Map 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 - *

- * 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(); - } - } -} diff --git a/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java b/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java index 5a1f3e014a6..dc9c4df64b3 100644 --- a/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java +++ b/src/main/java/org/elasticsearch/license/plugin/LicenseModule.java @@ -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); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java index 4ab91a355e9..14476cd3227 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/get/GetLicenseResponse.java @@ -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 licenses = new ArrayList<>(); + private List licenses = new ArrayList<>(); GetLicenseResponse() { } - GetLicenseResponse(List esLicenses) { - this.licenses = esLicenses; + GetLicenseResponse(List licenses) { + this.licenses = licenses; } - public List licenses() { + public List 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); } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java b/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java index 3da86e932af..27b2fc4bf8c 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequest.java @@ -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 { - private List licenses; + private List licenses; public PutLicenseRequest() { } @@ -30,38 +30,38 @@ public class PutLicenseRequest extends AcknowledgedRequest { } /** - * 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 esLicenses) { - this.licenses = esLicenses; + public PutLicenseRequest licenses(List licenses) { + this.licenses = licenses; return this; } - public List licenses() { + public List 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); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequestBuilder.java b/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequestBuilder.java index 67b7811dfb0..fd9e8642ccd 100644 --- a/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequestBuilder.java +++ b/src/main/java/org/elasticsearch/license/plugin/action/put/PutLicenseRequestBuilder.java @@ -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 licenses) { + public PutLicenseRequestBuilder setLicense(List licenses) { request.licenses(licenses); return this; } diff --git a/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java b/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java index b4ea567cbdd..3645b840b14 100644 --- a/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java +++ b/src/main/java/org/elasticsearch/license/plugin/core/LicensesManagerService.java @@ -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 getLicenses(); + public List getLicenses(); } diff --git a/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java b/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java index 511cc9471bd..2d9f7c8a80a 100644 --- a/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java +++ b/src/main/java/org/elasticsearch/license/plugin/core/LicensesMetaData.java @@ -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 signatures; + private final ImmutableList signedLicenses; - private final Set encodedTrialLicenses; - - public LicensesMetaData(String[] signatures, String[] encodedTrialLicenses) { - this(Sets.newHashSet(signatures), Sets.newHashSet(encodedTrialLicenses)); - } + private final ImmutableList 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 signatures, Set encodedTrialLicenses) { - this.signatures = signatures; - this.encodedTrialLicenses = encodedTrialLicenses; + public LicensesMetaData(List signedLicenses, List trialLicenses) { + this.signedLicenses = ImmutableList.copyOf(signedLicenses); + this.trialLicenses = ImmutableList.copyOf(trialLicenses); } - public Set getSignatures() { - return signatures; + public List getSignedLicenses() { + return signedLicenses; } - public Set getEncodedTrialLicenses() { - return encodedTrialLicenses; + public List 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 signedLicenses = Licenses.readFrom(in); + int numTrialLicenses = in.readVInt(); + List 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 trialLicenses = new ArrayList<>(); + List signedLicenses = new ArrayList<>(); XContentParser.Token token; - String fieldName = null; - Set encodedTrialLicenses = new HashSet<>(); - Set 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"; } - - } } \ No newline at end of file diff --git a/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java b/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java index 15a5f8e70bf..a4a2a1ed293 100644 --- a/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java +++ b/src/main/java/org/elasticsearch/license/plugin/core/LicensesService.java @@ -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 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 private final AtomicReference 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 @Override public void registerLicenses(final PutLicenseRequestHolder requestHolder, final ActionListener listener) { final PutLicenseRequest request = requestHolder.request; - final Set newLicenses = Sets.newHashSet(request.licenses()); + final Set newLicenses = Sets.newHashSet(request.licenses()); LicensesStatus status = checkLicenses(newLicenses); if (status == LicensesStatus.VALID) { clusterService.submitStateUpdateTask(requestHolder.source, new AckedClusterStateUpdateTask(request, listener) { @@ -152,10 +148,9 @@ public class LicensesService extends AbstractLifecycleComponent MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE); final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses); - Set newSignatures = licenseManager.toSignatures(newLicenses); - Set newLicenseSignatures = Sets.union(licensesWrapper.signatures, newSignatures); - if (newLicenseSignatures.size() != licensesWrapper.signatures.size()) { - LicensesMetaData newLicensesMetaData = new LicensesMetaData(newLicenseSignatures, licensesWrapper.encodedTrialLicenses); + List 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 MetaData metaData = currentState.metaData(); LicensesMetaData currentLicenses = metaData.custom(LicensesMetaData.TYPE); final LicensesWrapper licensesWrapper = LicensesWrapper.wrap(currentLicenses); - - Set currentSignedLicenses = licensesWrapper.signedLicenses(licenseManager); - Set licensesToDelete = new HashSet<>(); - for (ESLicense license : currentSignedLicenses) { - if (request.features().contains(license.feature())) { - licensesToDelete.add(license); - } - } - if (!licensesToDelete.isEmpty()) { - Set reducedLicenses = Sets.difference(currentSignedLicenses, licensesToDelete); - Set newSignatures = licenseManager.toSignatures(reducedLicenses); - LicensesMetaData newLicensesMetaData = new LicensesMetaData(newSignatures, licensesWrapper.encodedTrialLicenses); + List 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 * {@inheritDoc} */ @Override - public List getLicenses() { - LicensesMetaData currentMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE); + public List 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 licenses = Sets.union(licenseManager.fromSignatures(currentMetaData.getSignatures()), - TrialLicenseUtils.fromEncodedTrialLicenses(currentMetaData.getEncodedTrialLicenses())); + List currentLicenses = new ArrayList<>(); + currentLicenses.addAll(currentMetaData.getSignedLicenses()); + currentLicenses.addAll(currentMetaData.getTrialLicenses()); // bucket license for feature with the latest expiry date - Map licenseMap = new HashMap<>(); - for (ESLicense license : licenses) { + Map 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 } // sort the licenses by issue date - List reducedLicenses = new ArrayList<>(licenseMap.values()); - Collections.sort(reducedLicenses, new Comparator() { + List reducedLicenses = new ArrayList<>(licenseMap.values()); + Collections.sort(reducedLicenses, new Comparator() { @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 return Collections.emptyList(); } - private LicensesStatus checkLicenses(Set licenses) { - final ImmutableMap map = reduceAndMap(licenses); - return checkLicenses(map); - } - - private LicensesStatus checkLicenses(Map licenseMap) { - LicensesStatus status = LicensesStatus.VALID; - try { - licenseManager.verifyLicenses(licenseMap); - } catch (InvalidLicenseException e) { - status = LicensesStatus.INVALID; + private LicensesStatus checkLicenses(Set licenses) { + final ImmutableMap 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 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 newTrialLicenses = Sets.union(licensesWrapper.encodedTrialLicenses, - Sets.newHashSet(generateEncodedTrialLicense(request.feature, request.duration, request.maxNodes))); + List 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 } private boolean checkTrialLicenseGenerationCondition(String feature, LicensesWrapper licensesWrapper) { - for (ESLicense license : Sets.union(licensesWrapper.signedLicenses(licenseManager), - licensesWrapper.trialLicenses())) { + final List 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 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 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 } private long expiryDateForFeature(String feature, final LicensesMetaData currentLicensesMetaData) { - final Map effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData); - ESLicense featureLicense; + final Map effectiveLicenses = getEffectiveLicenses(currentLicensesMetaData); + License featureLicense; if ((featureLicense = effectiveLicenses.get(feature)) != null) { return featureLicense.expiryDate(); } return -1l; } - private Map getEffectiveLicenses(final LicensesMetaData metaData) { - Map map = new HashMap<>(); + private Map getEffectiveLicenses(final LicensesMetaData metaData) { + Map map = new HashMap<>(); if (metaData != null) { - Set esLicenses = new HashSet<>(); - esLicenses.addAll(licenseManager.fromSignatures(metaData.getSignatures())); - esLicenses.addAll(TrialLicenseUtils.fromEncodedTrialLicenses(metaData.getEncodedTrialLicenses())); - return reduceAndMap(esLicenses); + Set 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 return new LicensesWrapper(licensesMetaData); } - private ImmutableSet signatures = ImmutableSet.of(); - private ImmutableSet encodedTrialLicenses = ImmutableSet.of(); + private ImmutableList signedLicenses = ImmutableList.of(); + private ImmutableList 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 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 addAndGetSignedLicenses(Set newLicenses) { + final ImmutableMap newLicensesMap = reduceAndMap(newLicenses); + List newSignedLicenses = new ArrayList<>(signedLicenses); + final ImmutableMap 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 trialLicenses() { - return TrialLicenseUtils.fromEncodedTrialLicenses(encodedTrialLicenses); + private List removeAndGetSignedLicenses(Set features) { + List updatedSignedLicenses = new ArrayList<>(); + for (License license : signedLicenses) { + if (!features.contains(license.feature())) { + updatedSignedLicenses.add(license); + } + } + return ImmutableList.copyOf(updatedSignedLicenses); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/core/TrialLicenseUtils.java b/src/main/java/org/elasticsearch/license/plugin/core/TrialLicenseUtils.java index 758ca87e1d0..8dd6c53a807 100644 --- a/src/main/java/org/elasticsearch/license/plugin/core/TrialLicenseUtils.java +++ b/src/main/java/org/elasticsearch/license/plugin/core/TrialLicenseUtils.java @@ -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 fromEncodedTrialLicenses(Set encodedTrialLicenses) { - Set 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())); } } diff --git a/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java b/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java index 00f930c73f8..4797250dae3 100644 --- a/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java +++ b/src/main/java/org/elasticsearch/license/plugin/rest/RestGetLicenseAction.java @@ -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 overrideParams = ImmutableMap.of(ESLicenses.REST_VIEW_MODE, "true"); + final Map 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(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); } }); diff --git a/src/test/java/org/elasticsearch/license/AbstractLicensingTestBase.java b/src/test/java/org/elasticsearch/license/AbstractLicensingTestBase.java index bc7ee7a28d7..4e3a8b61216 100644 --- a/src/test/java/org/elasticsearch/license/AbstractLicensingTestBase.java +++ b/src/test/java/org/elasticsearch/license/AbstractLicensingTestBase.java @@ -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 licenseSpecs) throws IOException { + public static String generateLicenseSpecString(List 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 generateSignedLicenses(List licenseSpecs) throws Exception { - ESLicenseSigner signer = new ESLicenseSigner(getTestPriKeyPath(), getTestPubKeyPath()); - Set unSignedLicenses = new HashSet<>(); + public static Set generateSignedLicenses(List licenseSpecs) throws Exception { + LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath); + Set 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)); diff --git a/src/test/java/org/elasticsearch/license/licensor/LicenseSerializationTests.java b/src/test/java/org/elasticsearch/license/LicenseSerializationTests.java similarity index 63% rename from src/test/java/org/elasticsearch/license/licensor/LicenseSerializationTests.java rename to src/test/java/org/elasticsearch/license/LicenseSerializationTests.java index 14fb9b1e4e4..cea6fd7d8b4 100644 --- a/src/test/java/org/elasticsearch/license/licensor/LicenseSerializationTests.java +++ b/src/test/java/org/elasticsearch/license/LicenseSerializationTests.java @@ -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 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 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 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 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 specs = new ArrayList<>(licenseSpecs.values()); - String licenseSpecsSource = generateESLicenseSpecString(specs); - Set esLicensesOutput = new HashSet<>(ESLicenses.fromSource(licenseSpecsSource.getBytes(StandardCharsets.UTF_8), false)); - assertThat(esLicensesOutput.size(), equalTo(licenseSpecs.size())); + String licenseSpecsSource = generateLicenseSpecString(specs); + Set 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 licenses = generateSignedLicenses(Arrays.asList(new LicenseSpec("expired_feature", validLicenseIssueDate, expiredLicenseExpiryDate) + Set 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 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(); diff --git a/src/test/java/org/elasticsearch/license/LicenseVerificationTests.java b/src/test/java/org/elasticsearch/license/LicenseVerificationTests.java new file mode 100644 index 00000000000..8ee528eea79 --- /dev/null +++ b/src/test/java/org/elasticsearch/license/LicenseVerificationTests.java @@ -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 licenseSpecs = new ArrayList<>(); + for (int i = 0; i < n; i++) { + licenseSpecs.add(generateRandomLicenseSpec()); + } + + Set generatedLicenses = generateSignedLicenses(licenseSpecs); + assertThat(generatedLicenses.size(), equalTo(n)); + + for (License generatedLicense: generatedLicenses) { + assertThat(LicenseVerifier.verifyLicense(generatedLicense), equalTo(true)); + } + } +} diff --git a/src/test/java/org/elasticsearch/license/TestUtils.java b/src/test/java/org/elasticsearch/license/TestUtils.java index 46fc956a126..45aad9fe6d0 100644 --- a/src/test/java/org/elasticsearch/license/TestUtils.java +++ b/src/test/java/org/elasticsearch/license/TestUtils.java @@ -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 firstLicenses, Collection 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 firstLicenses, Collection secondLicenses) { isSame(new HashSet<>(firstLicenses), new HashSet<>(secondLicenses)); } - public static void isSame(Set firstLicenses, Set secondLicenses) { + public static void isSame(Set firstLicenses, Set secondLicenses) { // we do the verifyAndBuild to make sure we weed out any expired licenses - final Map licenses1 = ESLicenses.reduceAndMap(firstLicenses); - final Map licenses2 = ESLicenses.reduceAndMap(secondLicenses); + final Map licenses1 = Licenses.reduceAndMap(firstLicenses); + final Map 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(); + } } diff --git a/src/test/java/org/elasticsearch/license/manager/LicenseSignatureTest.java b/src/test/java/org/elasticsearch/license/manager/LicenseSignatureTest.java deleted file mode 100644 index bf76dbf2483..00000000000 --- a/src/test/java/org/elasticsearch/license/manager/LicenseSignatureTest.java +++ /dev/null @@ -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 licenseSpecs = new ArrayList<>(); - for (int i = 0; i < n; i++) { - licenseSpecs.add(generateRandomLicenseSpec()); - } - - Set generatedLicenses = generateSignedLicenses(licenseSpecs); - assertThat(generatedLicenses.size(), equalTo(n)); - - Set signatures = new HashSet<>(); - for (ESLicense license : generatedLicenses) { - signatures.add(license.signature()); - } - Set licenseFromSignatures = esLicenseManager.fromSignatures(signatures); - - TestUtils.isSame(generatedLicenses, licenseFromSignatures); - } -} diff --git a/src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java b/src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java deleted file mode 100644 index 65255e856c1..00000000000 --- a/src/test/java/org/elasticsearch/license/manager/LicenseVerificationTests.java +++ /dev/null @@ -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 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 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 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 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")); - } - } -} diff --git a/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesIntegrationTests.java b/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesIntegrationTests.java index e8548b1618b..475130c497b 100644 --- a/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesIntegrationTests.java +++ b/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesIntegrationTests.java @@ -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)); diff --git a/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesServiceTests.java b/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesServiceTests.java index df8359f667d..5ef0a17598d 100644 --- a/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesServiceTests.java +++ b/src/test/java/org/elasticsearch/license/plugin/AbstractLicensesServiceTests.java @@ -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 license, final LicensesStatus expectedStatus) { + protected void registerAndAckSignedLicenses(final LicensesManagerService masterLicensesManagerService, final List license, final LicensesStatus expectedStatus) { PutLicenseRequest putLicenseRequest = new PutLicenseRequest().licenses(license); LicensesService.PutLicenseRequestHolder requestHolder = new LicensesService.PutLicenseRequestHolder(putLicenseRequest, "test"); final CountDownLatch latch = new CountDownLatch(1); diff --git a/src/test/java/org/elasticsearch/license/plugin/LicensesClientServiceTests.java b/src/test/java/org/elasticsearch/license/plugin/LicensesClientServiceTests.java index 40c695ae697..3d7ee0239f7 100644 --- a/src/test/java/org/elasticsearch/license/plugin/LicensesClientServiceTests.java +++ b/src/test/java/org/elasticsearch/license/plugin/LicensesClientServiceTests.java @@ -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) { diff --git a/src/test/java/org/elasticsearch/license/plugin/LicensesManagerServiceTests.java b/src/test/java/org/elasticsearch/license/plugin/LicensesManagerServiceTests.java index b6192d81c7a..504f38700ef 100644 --- a/src/test/java/org/elasticsearch/license/plugin/LicensesManagerServiceTests.java +++ b/src/test/java/org/elasticsearch/license/plugin/LicensesManagerServiceTests.java @@ -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 licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense); + List licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense); Collections.shuffle(licenses); registerAndAckSignedLicenses(licensesManagerService, licenses, LicensesStatus.VALID); - final ImmutableSet 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 getLicenses = licensesManagerService.getLicenses(); + final List 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 licenses = Arrays.asList(shieldLongLicense, shieldShortLicense, marvelLongLicense, marvelShortLicense); + List 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 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 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()); - } - } diff --git a/src/test/java/org/elasticsearch/license/plugin/LicensesMetaDataSerializationTests.java b/src/test/java/org/elasticsearch/license/plugin/LicensesMetaDataSerializationTests.java new file mode 100644 index 00000000000..e23087880c9 --- /dev/null +++ b/src/test/java/org/elasticsearch/license/plugin/LicensesMetaDataSerializationTests.java @@ -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()); + 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 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()); + 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(), 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 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(), 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 trialLicenses = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + trialLicenses.add(trialLicenseBuilder.feature("feature__" + String.valueOf(i)).build()); + } + List 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; + } +} diff --git a/src/test/java/org/elasticsearch/license/plugin/LicensesServiceClusterTest.java b/src/test/java/org/elasticsearch/license/plugin/LicensesServiceClusterTest.java index ef42a39eb17..edc5fd47e00 100644 --- a/src/test/java/org/elasticsearch/license/plugin/LicensesServiceClusterTest.java +++ b/src/test/java/org/elasticsearch/license/plugin/LicensesServiceClusterTest.java @@ -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 licenses = generateAndPutLicenses(); + final List 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 generateAndPutLicenses() throws Exception { + private List generateAndPutLicenses() throws Exception { ClusterAdminClient cluster = internalCluster().client().admin().cluster(); - List putLicenses = new ArrayList<>(FEATURES.length); + List 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 licenses) { + private void getAndCheckLicense(List 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 { diff --git a/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java b/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java index dfd2414ec49..6bd809dd4c2 100644 --- a/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java +++ b/src/test/java/org/elasticsearch/license/plugin/LicensesTransportTests.java @@ -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 actualLicenses = Collections.singletonList(signedLicense); + License signedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2)); + List 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 actualLicenses = Arrays.asList(longerSignedLicense, shortedSignedLicense); + License shortedSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2)); + License longerSignedLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(5)); + List 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 actualLicenses = Arrays.asList(marvelLicense, shieldLicense); + License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2)); + License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5)); + List 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 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 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 actualLicenses = Arrays.asList(marvelLicense, shieldLicense); + License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2)); + License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5)); + List 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 actualLicenses = Arrays.asList(marvelLicense, shieldLicense); + License shieldLicense = generateSignedLicense("shield", TimeValue.timeValueMinutes(2)); + License marvelLicense = generateSignedLicense("marvel", TimeValue.timeValueMinutes(5)); + List actualLicenses = Arrays.asList(marvelLicense, shieldLicense); // put two licenses PutLicenseRequestBuilder putLicenseRequestBuilder = new PutLicenseRequestBuilder(client().admin().cluster()) diff --git a/src/test/java/org/elasticsearch/license/plugin/TrailLicenseSerializationTests.java b/src/test/java/org/elasticsearch/license/plugin/TrailLicenseSerializationTests.java new file mode 100644 index 00000000000..dd532f75ec4 --- /dev/null +++ b/src/test/java/org/elasticsearch/license/plugin/TrailLicenseSerializationTests.java @@ -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 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); + } + } +} diff --git a/src/test/java/org/elasticsearch/license/licensor/tools/KeyPairGenerationToolTests.java b/src/test/java/org/elasticsearch/license/tools/KeyPairGenerationToolTests.java similarity index 88% rename from src/test/java/org/elasticsearch/license/licensor/tools/KeyPairGenerationToolTests.java rename to src/test/java/org/elasticsearch/license/tools/KeyPairGenerationToolTests.java index 67960801cd7..36069e29931 100644 --- a/src/test/java/org/elasticsearch/license/licensor/tools/KeyPairGenerationToolTests.java +++ b/src/test/java/org/elasticsearch/license/tools/KeyPairGenerationToolTests.java @@ -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)); diff --git a/src/test/java/org/elasticsearch/license/licensor/tools/LicenseGenerationToolTests.java b/src/test/java/org/elasticsearch/license/tools/LicenseGenerationToolTests.java similarity index 84% rename from src/test/java/org/elasticsearch/license/licensor/tools/LicenseGenerationToolTests.java rename to src/test/java/org/elasticsearch/license/tools/LicenseGenerationToolTests.java index 3ad3f65901e..8a49c0b185e 100644 --- a/src/test/java/org/elasticsearch/license/licensor/tools/LicenseGenerationToolTests.java +++ b/src/test/java/org/elasticsearch/license/tools/LicenseGenerationToolTests.java @@ -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 licenseSpecs = ESLicenses.fromSource(generateESLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())).getBytes(StandardCharsets.UTF_8), false); + List licenseSpecs = Licenses.fromSource(generateLicenseSpecString(new ArrayList<>(inputLicenseSpecs.values())).getBytes(StandardCharsets.UTF_8), false); String output = runLicenseGenerationTool(pubKeyPath, priKeyPath, new HashSet<>(licenseSpecs), ExitStatus.OK); - List outputLicenses = ESLicenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true); + List 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 licenseSpecs, ExitStatus expectedExitStatus) throws Exception { + private String runLicenseGenerationTool(String pubKeyPath, String priKeyPath, Set licenseSpecs, ExitStatus expectedExitStatus) throws Exception { CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal(); LicenseGenerator licenseGenerator = new LicenseGenerator(outputTerminal, pubKeyPath, priKeyPath, licenseSpecs); assertThat(execute(licenseGenerator, ImmutableSettings.EMPTY), equalTo(expectedExitStatus)); diff --git a/src/test/java/org/elasticsearch/license/licensor/tools/LicenseVerificationToolTests.java b/src/test/java/org/elasticsearch/license/tools/LicenseVerificationToolTests.java similarity index 74% rename from src/test/java/org/elasticsearch/license/licensor/tools/LicenseVerificationToolTests.java rename to src/test/java/org/elasticsearch/license/tools/LicenseVerificationToolTests.java index ac94bc932d1..dd926256366 100644 --- a/src/test/java/org/elasticsearch/license/licensor/tools/LicenseVerificationToolTests.java +++ b/src/test/java/org/elasticsearch/license/tools/LicenseVerificationToolTests.java @@ -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 inputLicenses = new HashMap<>(); + Map 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 inputLicenses = new HashMap<>(); + Map 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 outputLicenses = ESLicenses.fromSource(output.getBytes(StandardCharsets.UTF_8), true); + List 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 licenses, ExitStatus expectedExitStatus) throws Exception { + private String runLicenseVerificationTool(Set licenses, ExitStatus expectedExitStatus) throws Exception { CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal(); LicenseVerifier licenseVerifier = new LicenseVerifier(outputTerminal, licenses); assertThat(execute(licenseVerifier, ImmutableSettings.EMPTY), equalTo(expectedExitStatus)); diff --git a/src/test/resources/private.key b/src/test/resources/private.key index 2931c592d80686b74b46fcb173f66a42892fe8d1..1f545803d875598d976b206250d5e95667c794ef 100644 GIT binary patch literal 1232 zcmV;>1TXv1%Bc_wbLO>E3}&#D<@-rDJ$(p047kR zkI3mLE>J{X5~_|GE5>Bxd81`fV@~0j8z81fwu78=uPV1?-b_fL$snD`NE}Zsv(l}S z+Dr52K=W~W#&Y(zE)IK9$=-71EtPe#X0XhttX&@1ia@?lvh&>PD=A#>rfdl2Ru)*H zKaKRTp&qI~gmO-)fq;gzrbngk1r$cUKd~25@6;rjPJf6eLtCHTaDU8Rbt#KGKlZ%g zPd`h6iZ4kWgcXo9P=@b~YoN9WQmtytp^dd%)MXVsW%n~=T9#TzSWvr*YCR25X&mo6 zJoJz>&ju@jBz@-J;se7XMiU5&x@g8>FTI*r^O6Xv2(VF_R@Vk3wQBmIE>&XqH^t=M z5_c~f_`fm)R~9ZqEv|Ma3$nQ}LvHLD`h~L2VKXBZwZnP`c6%Qwnwq8Z0!hd2+^*Gk z6R7QdYCwK?kavUU0Vd*Mq5K{M!Vk zzSLrHwA_oZsoml1+m@@Wyh8$ZA^E2_FpxTTRz7~A$x?V+XFW0!aa4^L&A*^Evlz{gN8ae8h9=J=b zC8F19FN`xNpxA!48b`dGRK3GbQMOho1>byaB*psVh{^^yqVL4J0~$j7viqxma>pYOhkH~PwpJT1j6^yHDMWGO{vw8PyI zIrYD&$F3e@0!346r3BY7JkLDD+7E5EE6oiV(gQ62K@eQRE&#pRq81Tut!V+1TCM~srn6-g`!g_`uozhhJEnN6u9$5|& zLC>$@xw*^CD*fb$ngUWU)yZ%TqX*wM!HU2(O2RRFnw&?^e*xOZWZ?n`No9lb?BLpV=@K}hkB uJ-}qCO)pTE(AWQ24*9x_mu179!iEY^J(*;5;=+=*kZC+fDbTdAaQBA1TXu^gvx?ss;j6H)Ij}-CR1}pv;X~@r}CX(uid2=q_ewCwaEi@S$y=bHc0Ex zW81*ae4~mvTA2|5HI?2e7fD=)VRq%ur{*I5liGX}#v-~;{EzvHs;qWez|3Cy@x+RU z)Fr`{Ll%MK?Bxtk+>WaX!b&p+0o?lqzbciMGJ(ph5+a~7ggHMKf{hZMM=Mlfv>`k64`jC}_tGZrr z2O*?|5-E8oO=HX-26Q$T_62nw+hGJ!2X(cv5?Lry>xX4a)cu;FzEvX(nK?17SIdT! zvomX9$zO)`+*`9HcZ8he8ib>FJ9|tN?Wps)2R(&p@7Vu+&MpenZ{AaDHk^bFsF!;BQmDgq=OC184o=y2wE;}=daPIdeiB%zD@9l zE+smsXX+AqO*(i6jo%>3_sW!m;qXLy+DKgOxKv9Ju7y8@=89k)IxmDDCX}>V+T|9D zYWE!I(jTdhTqI^pOC|eGIbi2fu1mhW|LaToT|onqz5K0R#hfC;;Yl(1B#cp(@u(T&YwzUY$d&m1_;&x3#0oIwx^llFIiT_S*?c8L@tDlJU-f|m#t;vAQ-*QhD7#0Ac0h3f*R|3j;v=Lp7@0dTG_MeZ&IYCD$P#>LsGycFsl<9A!KHK03TX^_<|y zHVog??J`m2A6h8m(Sklh@?F46dkxv!{e*^wPbn4FxsA79>YCmzO~-*k8q(Q)HiB=f zL%aa!&F|t8pmAGH4ew{y&RSm84M4&=pPRP1D30v@nl&vKm>!Y=$NDG2)p86~xcYWK zZa7B+^TCiH(tefsKob2PDtb>=uB|dOaN&Nj*ujL)z+wHuenZq9glYMD!ma zmYVdWqhG0f>stR!t3h(4-Xe5w{GnQm7(Z}9F!_b|<+E+rx;%+yjnE|%4c+v1xJ{A8 zAOI^P+Dxxw<3g^urO@D*K z-R|4U#-zD>5LmlVLa^NdVQ)R_3@{F;q6VZV(Z;xydLyICUx1Z6DeV`i|CToKlw$My zA^L&l(B9SbubEm&TpEBR8R`_ot-NYx)+PH_D^ZAS2gt)${#Y!)jh~|~3h81*D?@DF z!%A@P*@u9tu$20dc7*t_V(ffw%qgWIR??WrB2C}WCXcbwh*$N|W}krNcC`RGdTwEE zN9Jy@R8D1GSaF+$m8RsEn+n%ljzwRnz&6puxW_WBe?#}#Ab~cuKmtsSfBW@(D*sf5 ux;B9=9Y8mD`1#arZ%pYe0ikx>%eT^BphgE4yij^?pV%9R)x^ebh;_QdCuh!4Q zW6Uc_((oG8AST4JhGxlr-^T)p35dE~WSJg)5Pxr{Pw2i$WUj~xH9;uswX|N?9rjnL zM2)ISwd)TEefcdmYt+G`S`=?3?m4#RvU5l z8S}dpYjk&Ql8jx?OdR#2dLn!Y6H$vDY@hN`@N?om`8<6Sk6Xbq)*aK}TGk$XHH)u} z%E~t-*cAfm8$=KaS>=&)GpcMxqeCsuRy_$SX1dscK#CIoB=UQ?Nw}7eHB{^RsR*|5jk-UNt1B%|2 zbNucQGpFIUFM6^$i?be?546d`#E-Qz>;--eT$!T&9voV1tW!l+;C8N895 zi9Hb9n8HyISwMsJ3caEc`1m`F0WqxbFJK;=mG%v?>U=cEd8d)20bZS+2TzNTenccd zK6xYxONtjhsk?K|