Merge branch 'master' into jigsaw
Original commit: elastic/x-pack-elasticsearch@c7534cfcf0
This commit is contained in:
commit
2521e567f1
|
@ -0,0 +1,36 @@
|
|||
elasticsearch-license
|
||||
=====================
|
||||
|
||||
Elasticsearch Licensing core, tools and plugin
|
||||
|
||||
## Core
|
||||
|
||||
Contains core data structures, utilities used by **Licensor** and **Plugin**.
|
||||
|
||||
See `core/` and `core-shaded/`
|
||||
|
||||
## Licensor
|
||||
|
||||
Contains a collection of tools to generate key-pairs, licenses and validate licenses.
|
||||
|
||||
See `licensor/`
|
||||
|
||||
see [wiki] (https://github.com/elasticsearch/elasticsearch-license/wiki) for documentation on
|
||||
[Licensing Tools Usage & Reference] (https://github.com/elasticsearch/elasticsearch-license/wiki/License-Tools-Usage-&-Reference)
|
||||
|
||||
## Plugin
|
||||
|
||||
**NOTE**: The license plugin has to be packaged with the right public key when being deployed to public repositories in maven
|
||||
or uploaded to s3. Use `-Dkeys.path=<PATH_TO_KEY_DIR>` with maven command to package the plugin with a specified key.
|
||||
|
||||
See `plugin/`
|
||||
|
||||
see [Getting Started] (https://github.com/elasticsearch/elasticsearch-license/blob/master/docs/getting-started.asciidoc) to install license plugin.
|
||||
|
||||
see [Licensing REST APIs] (https://github.com/elasticsearch/elasticsearch-license/blob/master/docs/license.asciidoc)
|
||||
to use the license plugin from an elasticsearch deployment.
|
||||
|
||||
see [wiki] (https://github.com/elasticsearch/elasticsearch-license/wiki) for documentation on
|
||||
- [License Plugin Consumer Interface] (https://github.com/elasticsearch/elasticsearch-license/wiki/License---Consumer-Interface)
|
||||
- [License Plugin Release Process] (https://github.com/elasticsearch/elasticsearch-license/wiki/Plugin-Release-Process)
|
||||
- [License Plugin Design] (https://github.com/elasticsearch/elasticsearch-license/wiki/License-Plugin--Design)
|
|
@ -0,0 +1,13 @@
|
|||
apply plugin: 'elasticsearch.build'
|
||||
|
||||
dependencies {
|
||||
compile "org.elasticsearch:elasticsearch:${version}"
|
||||
testCompile "org.elasticsearch:test-framework:${version}"
|
||||
}
|
||||
|
||||
dependencyLicenses.enabled = false
|
||||
|
||||
jar {
|
||||
baseName = 'license-core'
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.core;
|
||||
|
||||
|
||||
import org.elasticsearch.common.Base64;
|
||||
|
||||
import javax.crypto.*;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.*;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
public class CryptUtils {
|
||||
private static final int minimumPadding = 20;
|
||||
private static final byte[] salt = {
|
||||
(byte) 0xA9, (byte) 0xA2, (byte) 0xB5, (byte) 0xDE,
|
||||
(byte) 0x2A, (byte) 0x8A, (byte) 0x9A, (byte) 0xE6
|
||||
};
|
||||
private static final int iterationCount = 1024;
|
||||
private static final int aesKeyLength = 128;
|
||||
private static final String keyAlgorithm = "RSA";
|
||||
private static final String passHashAlgorithm = "SHA-512";
|
||||
private static final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
|
||||
|
||||
private static final SecureRandom random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Read encrypted private key file content with default pass phrase
|
||||
*/
|
||||
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents) {
|
||||
try {
|
||||
return readEncryptedPrivateKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read encrypted public key file content with default pass phrase
|
||||
*/
|
||||
public static PublicKey readEncryptedPublicKey(byte[] fileContents) {
|
||||
try {
|
||||
return readEncryptedPublicKey(fileContents, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encrypted public key file content with default pass phrase
|
||||
*/
|
||||
public static byte[] writeEncryptedPublicKey(PublicKey publicKey) {
|
||||
try {
|
||||
return writeEncryptedPublicKey(publicKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encrypted private key file content with default pass phrase
|
||||
*/
|
||||
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey) {
|
||||
try {
|
||||
return writeEncryptedPrivateKey(privateKey, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read encrypted private key file content with provided <code>passPhrase</code>
|
||||
*/
|
||||
public static PrivateKey readEncryptedPrivateKey(byte[] fileContents, char[] passPhrase) {
|
||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decrypt(fileContents, passPhrase));
|
||||
try {
|
||||
return KeyFactory.getInstance(keyAlgorithm).generatePrivate(privateKeySpec);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read encrypted public key file content with provided <code>passPhrase</code>
|
||||
*/
|
||||
public static PublicKey readEncryptedPublicKey(byte[] fileContents, char[] passPhrase) {
|
||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(decrypt(fileContents, passPhrase));
|
||||
try {
|
||||
return KeyFactory.getInstance(CryptUtils.keyAlgorithm).generatePublic(publicKeySpec);
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encrypted public key file content with provided <code>passPhrase</code>
|
||||
*/
|
||||
public static byte[] writeEncryptedPublicKey(PublicKey publicKey, char[] passPhrase) {
|
||||
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(publicKey.getEncoded());
|
||||
return encrypt(encodedKeySpec.getEncoded(), passPhrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns encrypted private key file content with provided <code>passPhrase</code>
|
||||
*/
|
||||
public static byte[] writeEncryptedPrivateKey(PrivateKey privateKey, char[] passPhrase) {
|
||||
PKCS8EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(privateKey.getEncoded());
|
||||
return encrypt(encodedKeySpec.getEncoded(), passPhrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts provided <code>data</code> with <code>DEFAULT_PASS_PHRASE</code>
|
||||
*/
|
||||
public static byte[] encrypt(byte[] data) {
|
||||
try {
|
||||
return encrypt(data, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts provided <code>encryptedData</code> with <code>DEFAULT_PASS_PHRASE</code>
|
||||
*/
|
||||
public static byte[] decrypt(byte[] encryptedData) {
|
||||
try {
|
||||
return decrypt(encryptedData, hashPassPhrase(DEFAULT_PASS_PHRASE));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts provided <code>data</code> with <code>passPhrase</code>
|
||||
*/
|
||||
public static byte[] encrypt(byte[] data, char[] passPhrase) {
|
||||
try {
|
||||
final Cipher encryptionCipher = getEncryptionCipher(getSecretKey(passPhrase));
|
||||
return encryptionCipher.doFinal(pad(data, minimumPadding));
|
||||
} catch (InvalidKeySpecException | IllegalBlockSizeException | BadPaddingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts provided <code>encryptedData</code> with <code>passPhrase</code>
|
||||
*/
|
||||
private static byte[] decrypt(byte[] encryptedData, char[] passPhrase) {
|
||||
try {
|
||||
final Cipher cipher = getDecryptionCipher(getSecretKey(passPhrase));
|
||||
return unPad(cipher.doFinal(encryptedData));
|
||||
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeySpecException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static SecretKey getSecretKey(char[] passPhrase) throws InvalidKeySpecException {
|
||||
try {
|
||||
PBEKeySpec keySpec = new PBEKeySpec(passPhrase, salt, iterationCount, aesKeyLength);
|
||||
|
||||
byte[] shortKey = SecretKeyFactory.getInstance("PBEWithSHA1AndDESede").
|
||||
generateSecret(keySpec).getEncoded();
|
||||
|
||||
byte[] intermediaryKey = new byte[aesKeyLength / 8];
|
||||
for (int i = 0, j = 0; i < aesKeyLength / 8; i++) {
|
||||
intermediaryKey[i] = shortKey[j];
|
||||
if (++j == shortKey.length)
|
||||
j = 0;
|
||||
}
|
||||
|
||||
return new SecretKeySpec(intermediaryKey, "AES");
|
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Cipher getEncryptionCipher(SecretKey secretKey) {
|
||||
return getCipher(Cipher.ENCRYPT_MODE, secretKey);
|
||||
}
|
||||
|
||||
private static Cipher getDecryptionCipher(SecretKey secretKey) {
|
||||
return getCipher(Cipher.DECRYPT_MODE, secretKey);
|
||||
}
|
||||
|
||||
private static Cipher getCipher(int mode, SecretKey secretKey) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(secretKey.getAlgorithm());
|
||||
cipher.init(mode, secretKey, random);
|
||||
return cipher;
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] pad(byte[] bytes, int length) {
|
||||
if (bytes.length >= length) {
|
||||
byte[] out = new byte[bytes.length + 1];
|
||||
System.arraycopy(bytes, 0, out, 0, bytes.length);
|
||||
out[bytes.length] = (byte) 1;
|
||||
return out;
|
||||
}
|
||||
|
||||
byte[] out = new byte[length + 1];
|
||||
|
||||
int i = 0;
|
||||
for (; i < bytes.length; i++)
|
||||
out[i] = bytes[i];
|
||||
|
||||
int padded = length - i;
|
||||
|
||||
// fill the rest with random bytes
|
||||
byte[] fill = new byte[padded - 1];
|
||||
random.nextBytes(fill);
|
||||
System.arraycopy(fill, 0, out, i, padded - 1);
|
||||
|
||||
out[length] = (byte) (padded + 1);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static byte[] unPad(byte[] bytes) {
|
||||
int padded = (int) bytes[bytes.length - 1];
|
||||
int targetLength = bytes.length - padded;
|
||||
|
||||
byte[] out = new byte[targetLength];
|
||||
|
||||
System.arraycopy(bytes, 0, out, 0, targetLength);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static char[] hashPassPhrase(String passPhrase) throws NoSuchAlgorithmException {
|
||||
final byte[] passBytes = passPhrase.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] digest = MessageDigest.getInstance(passHashAlgorithm).digest(passBytes);
|
||||
return new String(Base64.encodeBytesToBytes(digest), StandardCharsets.UTF_8).toCharArray();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.core;
|
||||
|
||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
||||
import org.elasticsearch.common.joda.Joda;
|
||||
import org.joda.time.MutableDateTime;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
public class DateUtils {
|
||||
|
||||
private final static FormatDateTimeFormatter formatDateOnlyFormatter = Joda.forPattern("yyyy-MM-dd");
|
||||
|
||||
private final static DateTimeFormatter dateOnlyFormatter = formatDateOnlyFormatter.parser().withZoneUTC();
|
||||
|
||||
private final static DateTimeFormatter dateTimeFormatter = ISODateTimeFormat.dateTime().withZoneUTC();
|
||||
|
||||
public static long endOfTheDay(String date) {
|
||||
try {
|
||||
// Try parsing using complete date/time format
|
||||
return dateTimeFormatter.parseDateTime(date).getMillis();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Fall back to the date only format
|
||||
MutableDateTime dateTime = dateOnlyFormatter.parseMutableDateTime(date);
|
||||
dateTime.millisOfDay().set(dateTime.millisOfDay().getMaximumValue());
|
||||
return dateTime.getMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public static long beginningOfTheDay(String date) {
|
||||
try {
|
||||
// Try parsing using complete date/time format
|
||||
return dateTimeFormatter.parseDateTime(date).getMillis();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Fall back to the date only format
|
||||
return dateOnlyFormatter.parseDateTime(date).getMillis();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,693 @@
|
|||
/*
|
||||
* 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.apache.lucene.util.CollectionUtil;
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchParseException;
|
||||
import org.elasticsearch.common.Base64;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.xcontent.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Data structure for license. Use {@link Builder} to build a license.
|
||||
* Provides serialization/deserialization & validation methods for license object
|
||||
*/
|
||||
public class License implements ToXContent {
|
||||
public final static int VERSION_START = 1;
|
||||
public final static int VERSION_NO_FEATURE_TYPE = 2;
|
||||
public final static int VERSION_CURRENT = VERSION_NO_FEATURE_TYPE;
|
||||
|
||||
/**
|
||||
* XContent param name to deserialize license(s) with
|
||||
* an additional <code>status</code> field, indicating whether a
|
||||
* particular license is 'active' or 'expired' and no signature
|
||||
* and in a human readable format
|
||||
*/
|
||||
public static final String REST_VIEW_MODE = "rest_view";
|
||||
/**
|
||||
* XContent param name to deserialize license(s) with
|
||||
* no signature
|
||||
*/
|
||||
public static final String LICENSE_SPEC_VIEW_MODE = "license_spec_view";
|
||||
/**
|
||||
* XContent param name to deserialize licenses according
|
||||
* to a specific license version
|
||||
*/
|
||||
public static final String LICENSE_VERSION_MODE = "license_version";
|
||||
|
||||
public final static Comparator<License> LATEST_ISSUE_DATE_FIRST = new Comparator<License>() {
|
||||
@Override
|
||||
public int compare(License right, License left) {
|
||||
return Long.compare(left.issueDate(), right.issueDate());
|
||||
}
|
||||
};
|
||||
|
||||
private final int version;
|
||||
private final String uid;
|
||||
private final String issuer;
|
||||
private final String issuedTo;
|
||||
private final long issueDate;
|
||||
private final String type;
|
||||
private final String subscriptionType;
|
||||
private final String feature;
|
||||
private final String signature;
|
||||
private final long expiryDate;
|
||||
private final int maxNodes;
|
||||
private final OperationMode operationMode;
|
||||
|
||||
/**
|
||||
* Decouples operation mode of a license
|
||||
* from the license type value
|
||||
*/
|
||||
public enum OperationMode {
|
||||
NONE,
|
||||
TRIAL,
|
||||
BASIC,
|
||||
GOLD,
|
||||
PLATINUM;
|
||||
|
||||
public static OperationMode resolve(String type) {
|
||||
switch (type.toLowerCase(Locale.ROOT)) {
|
||||
case "trial":
|
||||
case "none": // bwc for 1.x subscription_type field
|
||||
case "dev": // bwc for 1.x subscription_type field
|
||||
case "development": // bwc for 1.x subscription_type field
|
||||
return TRIAL;
|
||||
case "basic":
|
||||
return BASIC;
|
||||
case "silver":
|
||||
case "gold":
|
||||
return GOLD;
|
||||
case "platinum":
|
||||
case "internal": // bwc for 1.x subscription_type field
|
||||
return PLATINUM;
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown type [" + type + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private License(int version, String uid, String issuer, String issuedTo, long issueDate, String type,
|
||||
String subscriptionType, String feature, String signature, long expiryDate, int maxNodes) {
|
||||
this.version = version;
|
||||
this.uid = uid;
|
||||
this.issuer = issuer;
|
||||
this.issuedTo = issuedTo;
|
||||
this.issueDate = issueDate;
|
||||
this.type = type;
|
||||
this.subscriptionType = subscriptionType;
|
||||
this.feature = feature;
|
||||
this.signature = signature;
|
||||
this.expiryDate = expiryDate;
|
||||
this.maxNodes = maxNodes;
|
||||
if (version == VERSION_START) {
|
||||
// in 1.x: the acceptable values for 'subscription_type': none | dev | silver | gold | platinum
|
||||
this.operationMode = OperationMode.resolve(subscriptionType);
|
||||
} else {
|
||||
// in 2.x: the acceptable values for 'type': trial | basic | silver | dev | gold | platinum
|
||||
this.operationMode = OperationMode.resolve(type);
|
||||
}
|
||||
validate();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return version of the license
|
||||
*/
|
||||
public int version() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a unique identifier for a license
|
||||
*/
|
||||
public String uid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return type of the license [trial, subscription, internal]
|
||||
*/
|
||||
public String type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the issueDate in milliseconds
|
||||
*/
|
||||
public long issueDate() {
|
||||
return issueDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the expiry date in milliseconds
|
||||
*/
|
||||
public long expiryDate() {
|
||||
return expiryDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the maximum number of nodes this license has been issued for
|
||||
*/
|
||||
public int maxNodes() {
|
||||
return maxNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representing the entity this licenses has been issued to
|
||||
*/
|
||||
public String issuedTo() {
|
||||
return issuedTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representing the entity responsible for issuing this license (internal)
|
||||
*/
|
||||
public String issuer() {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representing the signature of the license used for license verification
|
||||
*/
|
||||
public String signature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the operation mode of the license as computed from the license type
|
||||
*/
|
||||
public OperationMode operationMode() {
|
||||
return operationMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current license's status
|
||||
*/
|
||||
public Status status() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (issueDate > now) {
|
||||
return Status.INVALID;
|
||||
} else if (expiryDate < now) {
|
||||
return Status.EXPIRED;
|
||||
}
|
||||
return Status.ACTIVE;
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
if (issuer == null) {
|
||||
throw new IllegalStateException("issuer can not be null");
|
||||
} else if (issuedTo == null) {
|
||||
throw new IllegalStateException("issuedTo can not be null");
|
||||
} else if (issueDate == -1) {
|
||||
throw new IllegalStateException("issueDate has to be set");
|
||||
} else if (type == null) {
|
||||
throw new IllegalStateException("type can not be null");
|
||||
} else if (subscriptionType == null && version == VERSION_START) {
|
||||
throw new IllegalStateException("subscriptionType can not be null");
|
||||
} else if (uid == null) {
|
||||
throw new IllegalStateException("uid can not be null");
|
||||
} else if (feature == null && version == VERSION_START) {
|
||||
throw new IllegalStateException("feature can not be null");
|
||||
} else if (maxNodes == -1) {
|
||||
throw new IllegalStateException("maxNodes has to be set");
|
||||
} else if (expiryDate == -1) {
|
||||
throw new IllegalStateException("expiryDate has to be set");
|
||||
}
|
||||
}
|
||||
|
||||
public static License readLicense(StreamInput in) throws IOException {
|
||||
int version = in.readVInt(); // Version for future extensibility
|
||||
if (version > VERSION_CURRENT) {
|
||||
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch-license plugin");
|
||||
}
|
||||
Builder builder = builder();
|
||||
builder.version(version);
|
||||
builder.uid(in.readString());
|
||||
builder.type(in.readString());
|
||||
if (version == VERSION_START) {
|
||||
builder.subscriptionType(in.readString());
|
||||
}
|
||||
builder.issueDate(in.readLong());
|
||||
if (version == VERSION_START) {
|
||||
builder.feature(in.readString());
|
||||
}
|
||||
builder.expiryDate(in.readLong());
|
||||
builder.maxNodes(in.readInt());
|
||||
builder.issuedTo(in.readString());
|
||||
builder.issuer(in.readString());
|
||||
builder.signature(in.readOptionalString());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeVInt(version);
|
||||
out.writeString(uid);
|
||||
out.writeString(type);
|
||||
if (version == VERSION_START) {
|
||||
out.writeString(subscriptionType);
|
||||
}
|
||||
out.writeLong(issueDate);
|
||||
if (version == VERSION_START) {
|
||||
out.writeString(feature);
|
||||
}
|
||||
out.writeLong(expiryDate);
|
||||
out.writeInt(maxNodes);
|
||||
out.writeString(issuedTo);
|
||||
out.writeString(issuer);
|
||||
out.writeOptionalString(signature);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
final XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
return builder.string();
|
||||
} catch (IOException e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject();
|
||||
toInnerXContent(builder, params);
|
||||
builder.endObject();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public XContentBuilder toInnerXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
boolean licenseSpecMode = params.paramAsBoolean(LICENSE_SPEC_VIEW_MODE, false);
|
||||
boolean restViewMode = params.paramAsBoolean(REST_VIEW_MODE, false);
|
||||
boolean previouslyHumanReadable = builder.humanReadable();
|
||||
if (licenseSpecMode && restViewMode) {
|
||||
throw new IllegalArgumentException("can have either " + REST_VIEW_MODE + " or " + LICENSE_SPEC_VIEW_MODE);
|
||||
} else if (restViewMode) {
|
||||
if (!previouslyHumanReadable) {
|
||||
builder.humanReadable(true);
|
||||
}
|
||||
}
|
||||
final int version;
|
||||
if (params.param(LICENSE_VERSION_MODE) != null && restViewMode) {
|
||||
version = Integer.parseInt(params.param(LICENSE_VERSION_MODE));
|
||||
} else {
|
||||
version = this.version;
|
||||
}
|
||||
if (restViewMode) {
|
||||
builder.field(XFields.STATUS, status().label());
|
||||
}
|
||||
builder.field(XFields.UID, uid);
|
||||
builder.field(XFields.TYPE, type);
|
||||
if (version == VERSION_START) {
|
||||
builder.field(XFields.SUBSCRIPTION_TYPE, subscriptionType);
|
||||
}
|
||||
builder.dateValueField(XFields.ISSUE_DATE_IN_MILLIS, XFields.ISSUE_DATE, issueDate);
|
||||
if (version == VERSION_START) {
|
||||
builder.field(XFields.FEATURE, feature);
|
||||
}
|
||||
builder.dateValueField(XFields.EXPIRY_DATE_IN_MILLIS, XFields.EXPIRY_DATE, expiryDate);
|
||||
builder.field(XFields.MAX_NODES, maxNodes);
|
||||
builder.field(XFields.ISSUED_TO, issuedTo);
|
||||
builder.field(XFields.ISSUER, issuer);
|
||||
if (!licenseSpecMode && !restViewMode && signature != null) {
|
||||
builder.field(XFields.SIGNATURE, signature);
|
||||
}
|
||||
if (restViewMode) {
|
||||
builder.humanReadable(previouslyHumanReadable);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static License fromXContent(XContentParser parser) throws IOException {
|
||||
Builder builder = new Builder();
|
||||
XContentParser.Token token;
|
||||
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||
if (token == XContentParser.Token.FIELD_NAME) {
|
||||
String currentFieldName = parser.currentName();
|
||||
token = parser.nextToken();
|
||||
if (token.isValue()) {
|
||||
if (Fields.UID.equals(currentFieldName)) {
|
||||
builder.uid(parser.text());
|
||||
} else if (Fields.TYPE.equals(currentFieldName)) {
|
||||
builder.type(parser.text());
|
||||
} else if (Fields.SUBSCRIPTION_TYPE.equals(currentFieldName)) {
|
||||
builder.subscriptionType(parser.text());
|
||||
} else if (Fields.ISSUE_DATE.equals(currentFieldName)) {
|
||||
builder.issueDate(parseDate(parser, "issue", false));
|
||||
} else if (Fields.ISSUE_DATE_IN_MILLIS.equals(currentFieldName)) {
|
||||
builder.issueDate(parser.longValue());
|
||||
} else if (Fields.FEATURE.equals(currentFieldName)) {
|
||||
builder.feature(parser.text());
|
||||
} else if (Fields.EXPIRY_DATE.equals(currentFieldName)) {
|
||||
builder.expiryDate(parseDate(parser, "expiration", true));
|
||||
} else if (Fields.EXPIRY_DATE_IN_MILLIS.equals(currentFieldName)) {
|
||||
builder.expiryDate(parser.longValue());
|
||||
} else if (Fields.MAX_NODES.equals(currentFieldName)) {
|
||||
builder.maxNodes(parser.intValue());
|
||||
} else if (Fields.ISSUED_TO.equals(currentFieldName)) {
|
||||
builder.issuedTo(parser.text());
|
||||
} else if (Fields.ISSUER.equals(currentFieldName)) {
|
||||
builder.issuer(parser.text());
|
||||
} else if (Fields.SIGNATURE.equals(currentFieldName)) {
|
||||
builder.signature(parser.text());
|
||||
} else if (Fields.VERSION.equals(currentFieldName)) {
|
||||
builder.version(parser.intValue());
|
||||
}
|
||||
// Ignore unknown elements - might be new version of license
|
||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||
// It was probably created by newer version - ignoring
|
||||
parser.skipChildren();
|
||||
} else if (token == XContentParser.Token.START_OBJECT) {
|
||||
// It was probably created by newer version - ignoring
|
||||
parser.skipChildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
// not a license spec
|
||||
if (builder.signature != null) {
|
||||
byte[] signatureBytes = Base64.decode(builder.signature);
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
|
||||
int version = byteBuffer.getInt();
|
||||
// we take the absolute version, because negative versions
|
||||
// mean that the license was generated by the cluster (see TrialLicense)
|
||||
// and positive version means that the license was signed
|
||||
if (version < 0) {
|
||||
version *= -1;
|
||||
}
|
||||
if (version == 0) {
|
||||
throw new ElasticsearchException("malformed signature for license [" + builder.uid + "]");
|
||||
} else if (version > VERSION_CURRENT) {
|
||||
throw new ElasticsearchException("Unknown license version found, please upgrade all nodes to the latest elasticsearch-license plugin");
|
||||
}
|
||||
// signature version is the source of truth
|
||||
builder.version(version);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the license was auto-generated (by license plugin),
|
||||
* false otherwise
|
||||
*/
|
||||
public static boolean isAutoGeneratedLicense(String signature) {
|
||||
try {
|
||||
byte[] signatureBytes = Base64.decode(signature);
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
|
||||
return byteBuffer.getInt() < 0;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static License fromSource(String content) throws IOException {
|
||||
return fromSource(content.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static License fromSource(byte[] bytes) throws IOException {
|
||||
final XContentParser parser = XContentFactory.xContent(bytes).createParser(bytes);
|
||||
License license = null;
|
||||
if (parser.nextToken() == XContentParser.Token.START_OBJECT) {
|
||||
if (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
|
||||
String currentFieldName = parser.currentName();
|
||||
if (Fields.LICENSES.equals(currentFieldName)) {
|
||||
final List<License> pre20Licenses = new ArrayList<>();
|
||||
if (parser.nextToken() == XContentParser.Token.START_ARRAY) {
|
||||
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
|
||||
pre20Licenses.add(License.fromXContent(parser));
|
||||
}
|
||||
// take the latest issued unexpired license
|
||||
CollectionUtil.timSort(pre20Licenses, LATEST_ISSUE_DATE_FIRST);
|
||||
long now = System.currentTimeMillis();
|
||||
for (License oldLicense : pre20Licenses) {
|
||||
if (oldLicense.expiryDate() > now) {
|
||||
license = oldLicense;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (license == null && !pre20Licenses.isEmpty()) {
|
||||
license = pre20Licenses.get(0);
|
||||
}
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse licenses expected an array of licenses");
|
||||
}
|
||||
} else if (Fields.LICENSE.equals(currentFieldName)) {
|
||||
license = License.fromXContent(parser);
|
||||
}
|
||||
// Ignore all other fields - might be created with new version
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse licenses expected field");
|
||||
}
|
||||
} else {
|
||||
throw new ElasticsearchParseException("failed to parse licenses expected start object");
|
||||
}
|
||||
return license;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
License license = (License) o;
|
||||
|
||||
if (issueDate != license.issueDate) return false;
|
||||
if (expiryDate != license.expiryDate) return false;
|
||||
if (maxNodes != license.maxNodes) return false;
|
||||
if (version != license.version) return false;
|
||||
if (uid != null ? !uid.equals(license.uid) : license.uid != null) return false;
|
||||
if (issuer != null ? !issuer.equals(license.issuer) : license.issuer != null) return false;
|
||||
if (issuedTo != null ? !issuedTo.equals(license.issuedTo) : license.issuedTo != null) return false;
|
||||
if (type != null ? !type.equals(license.type) : license.type != null) return false;
|
||||
if (subscriptionType != null ? !subscriptionType.equals(license.subscriptionType) : license.subscriptionType != null)
|
||||
return false;
|
||||
if (feature != null ? !feature.equals(license.feature) : license.feature != null) return false;
|
||||
return !(signature != null ? !signature.equals(license.signature) : license.signature != null);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = uid != null ? uid.hashCode() : 0;
|
||||
result = 31 * result + (issuer != null ? issuer.hashCode() : 0);
|
||||
result = 31 * result + (issuedTo != null ? issuedTo.hashCode() : 0);
|
||||
result = 31 * result + (int) (issueDate ^ (issueDate >>> 32));
|
||||
result = 31 * result + (type != null ? type.hashCode() : 0);
|
||||
result = 31 * result + (subscriptionType != null ? subscriptionType.hashCode() : 0);
|
||||
result = 31 * result + (feature != null ? feature.hashCode() : 0);
|
||||
result = 31 * result + (signature != null ? signature.hashCode() : 0);
|
||||
result = 31 * result + (int) (expiryDate ^ (expiryDate >>> 32));
|
||||
result = 31 * result + maxNodes;
|
||||
result = 31 * result + version;
|
||||
return result;
|
||||
}
|
||||
|
||||
final static class Fields {
|
||||
static final String STATUS = "status";
|
||||
static final String UID = "uid";
|
||||
static final String TYPE = "type";
|
||||
static final String SUBSCRIPTION_TYPE = "subscription_type";
|
||||
static final String ISSUE_DATE_IN_MILLIS = "issue_date_in_millis";
|
||||
static final String ISSUE_DATE = "issue_date";
|
||||
static final String FEATURE = "feature";
|
||||
static final String EXPIRY_DATE_IN_MILLIS = "expiry_date_in_millis";
|
||||
static final String EXPIRY_DATE = "expiry_date";
|
||||
static final String MAX_NODES = "max_nodes";
|
||||
static final String ISSUED_TO = "issued_to";
|
||||
static final String ISSUER = "issuer";
|
||||
static final String VERSION = "version";
|
||||
static final String SIGNATURE = "signature";
|
||||
|
||||
static final String LICENSES = "licenses";
|
||||
static final String LICENSE = "license";
|
||||
|
||||
}
|
||||
|
||||
public interface XFields {
|
||||
XContentBuilderString STATUS = new XContentBuilderString(Fields.STATUS);
|
||||
XContentBuilderString UID = new XContentBuilderString(Fields.UID);
|
||||
XContentBuilderString TYPE = new XContentBuilderString(Fields.TYPE);
|
||||
XContentBuilderString SUBSCRIPTION_TYPE = new XContentBuilderString(Fields.SUBSCRIPTION_TYPE);
|
||||
XContentBuilderString ISSUE_DATE_IN_MILLIS = new XContentBuilderString(Fields.ISSUE_DATE_IN_MILLIS);
|
||||
XContentBuilderString ISSUE_DATE = new XContentBuilderString(Fields.ISSUE_DATE);
|
||||
XContentBuilderString FEATURE = new XContentBuilderString(Fields.FEATURE);
|
||||
XContentBuilderString EXPIRY_DATE_IN_MILLIS = new XContentBuilderString(Fields.EXPIRY_DATE_IN_MILLIS);
|
||||
XContentBuilderString EXPIRY_DATE = new XContentBuilderString(Fields.EXPIRY_DATE);
|
||||
XContentBuilderString MAX_NODES = new XContentBuilderString(Fields.MAX_NODES);
|
||||
XContentBuilderString ISSUED_TO = new XContentBuilderString(Fields.ISSUED_TO);
|
||||
XContentBuilderString ISSUER = new XContentBuilderString(Fields.ISSUER);
|
||||
XContentBuilderString SIGNATURE = new XContentBuilderString(Fields.SIGNATURE);
|
||||
}
|
||||
|
||||
private static long parseDate(XContentParser parser, String description, boolean endOfTheDay) throws IOException {
|
||||
if (parser.currentToken() == XContentParser.Token.VALUE_NUMBER) {
|
||||
return parser.longValue();
|
||||
} else {
|
||||
try {
|
||||
if (endOfTheDay) {
|
||||
return DateUtils.endOfTheDay(parser.text());
|
||||
} else {
|
||||
return DateUtils.beginningOfTheDay(parser.text());
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw new ElasticsearchParseException("invalid " + description + " date format " + parser.text());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private int version = License.VERSION_CURRENT;
|
||||
private String uid;
|
||||
private String issuer;
|
||||
private String issuedTo;
|
||||
private long issueDate = -1;
|
||||
private String type;
|
||||
private String subscriptionType;
|
||||
private String feature;
|
||||
private String signature;
|
||||
private long expiryDate = -1;
|
||||
private int maxNodes = -1;
|
||||
|
||||
public Builder uid(String uid) {
|
||||
this.uid = uid;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder version(int version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder issuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder issuedTo(String issuedTo) {
|
||||
this.issuedTo = issuedTo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder issueDate(long issueDate) {
|
||||
this.issueDate = issueDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder type(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder subscriptionType(String subscriptionType) {
|
||||
this.subscriptionType = subscriptionType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder feature(String feature) {
|
||||
this.feature = feature;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder expiryDate(long expiryDate) {
|
||||
this.expiryDate = expiryDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxNodes(int maxNodes) {
|
||||
this.maxNodes = maxNodes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder signature(String signature) {
|
||||
if (signature != null) {
|
||||
this.signature = signature;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder fromLicenseSpec(License license, String signature) {
|
||||
return uid(license.uid())
|
||||
.version(license.version())
|
||||
.issuedTo(license.issuedTo())
|
||||
.issueDate(license.issueDate())
|
||||
.type(license.type())
|
||||
.subscriptionType(license.subscriptionType)
|
||||
.feature(license.feature)
|
||||
.maxNodes(license.maxNodes())
|
||||
.expiryDate(license.expiryDate())
|
||||
.issuer(license.issuer())
|
||||
.signature(signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a builder that converts pre 2.0 licenses
|
||||
* to the new license format
|
||||
*/
|
||||
public Builder fromPre20LicenseSpec(License pre20License) {
|
||||
return uid(pre20License.uid())
|
||||
.issuedTo(pre20License.issuedTo())
|
||||
.issueDate(pre20License.issueDate())
|
||||
.maxNodes(pre20License.maxNodes())
|
||||
.expiryDate(pre20License.expiryDate());
|
||||
}
|
||||
|
||||
public License build() {
|
||||
return new License(version, uid, issuer, issuedTo, issueDate, type,
|
||||
subscriptionType, feature, signature, expiryDate, maxNodes);
|
||||
}
|
||||
|
||||
public Builder validate() {
|
||||
if (issuer == null) {
|
||||
throw new IllegalStateException("issuer can not be null");
|
||||
} else if (issuedTo == null) {
|
||||
throw new IllegalStateException("issuedTo can not be null");
|
||||
} else if (issueDate == -1) {
|
||||
throw new IllegalStateException("issueDate has to be set");
|
||||
} else if (type == null) {
|
||||
throw new IllegalStateException("type can not be null");
|
||||
} else if (uid == null) {
|
||||
throw new IllegalStateException("uid can not be null");
|
||||
} else if (signature == null) {
|
||||
throw new IllegalStateException("signature can not be null");
|
||||
} else if (maxNodes == -1) {
|
||||
throw new IllegalStateException("maxNodes has to be set");
|
||||
} else if (expiryDate == -1) {
|
||||
throw new IllegalStateException("expiryDate has to be set");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
ACTIVE("active"),
|
||||
INVALID("invalid"),
|
||||
EXPIRED("expired");
|
||||
|
||||
private final String label;
|
||||
|
||||
Status(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String label() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.core;
|
||||
|
||||
import org.elasticsearch.common.Base64;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Responsible for verifying signed licenses
|
||||
*/
|
||||
public class LicenseVerifier {
|
||||
|
||||
/**
|
||||
* verifies the license content with the signature using the packaged
|
||||
* public key
|
||||
* @param license to verify
|
||||
* @return true if valid, false otherwise
|
||||
*/
|
||||
public static boolean verifyLicense(final License license, byte[] encryptedPublicKeyData) {
|
||||
byte[] signedContent = null;
|
||||
byte[] signatureHash = null;
|
||||
try {
|
||||
byte[] signatureBytes = Base64.decode(license.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();
|
||||
signatureHash = new byte[hashLen];
|
||||
byteBuffer.get(signatureHash);
|
||||
int signedContentLen = byteBuffer.getInt();
|
||||
signedContent = new byte[signedContentLen];
|
||||
byteBuffer.get(signedContent);
|
||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
license.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true")));
|
||||
Signature rsa = Signature.getInstance("SHA512withRSA");
|
||||
rsa.initVerify(CryptUtils.readEncryptedPublicKey(encryptedPublicKeyData));
|
||||
rsa.update(contentBuilder.bytes().toBytes());
|
||||
return rsa.verify(signedContent)
|
||||
&& Arrays.equals(Base64.encodeBytesToBytes(encryptedPublicKeyData), signatureHash);
|
||||
} catch (IOException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} finally {
|
||||
Arrays.fill(encryptedPublicKeyData, (byte) 0);
|
||||
if (signedContent != null) {
|
||||
Arrays.fill(signedContent, (byte) 0);
|
||||
}
|
||||
if (signatureHash != null) {
|
||||
Arrays.fill(signatureHash, (byte) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* 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.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.hamcrest.core.IsNull.notNullValue;
|
||||
import static org.hamcrest.core.IsNull.nullValue;
|
||||
|
||||
public class LicenseSerializationTests extends ESTestCase {
|
||||
public void testSimpleIssueExpiryDate() throws Exception {
|
||||
long now = System.currentTimeMillis();
|
||||
String issueDate = TestUtils.dateMathString("now", now);
|
||||
String expiryDate = TestUtils.dateMathString("now+10d/d", now);
|
||||
String licenseSpecs = TestUtils.generateLicenseSpecString(new TestUtils.LicenseSpec(issueDate, expiryDate));
|
||||
License generatedLicense = License.fromSource(licenseSpecs.getBytes(StandardCharsets.UTF_8));
|
||||
assertThat(generatedLicense.issueDate(), equalTo(DateUtils.beginningOfTheDay(issueDate)));
|
||||
assertThat(generatedLicense.expiryDate(), equalTo(DateUtils.endOfTheDay(expiryDate)));
|
||||
}
|
||||
|
||||
public void testLicensesFields() throws Exception {
|
||||
TestUtils.LicenseSpec randomLicenseSpec = TestUtils.generateRandomLicenseSpec(License.VERSION_START);//randomIntBetween(License.VERSION_START, License.VERSION_CURRENT));
|
||||
String licenseSpecsSource = TestUtils.generateLicenseSpecString(randomLicenseSpec);
|
||||
final License fromSource = License.fromSource(licenseSpecsSource.getBytes(StandardCharsets.UTF_8));
|
||||
TestUtils.assertLicenseSpec(randomLicenseSpec, fromSource);
|
||||
}
|
||||
|
||||
public void testLicenseRestView() throws Exception {
|
||||
long now = System.currentTimeMillis();
|
||||
String expiredLicenseExpiryDate = TestUtils.dateMathString("now-1d/d", now);
|
||||
String validLicenseIssueDate = TestUtils.dateMathString("now-10d/d", now);
|
||||
String invalidLicenseIssueDate = TestUtils.dateMathString("now+1d/d", now);
|
||||
String validLicenseExpiryDate = TestUtils.dateMathString("now+2d/d", now);
|
||||
|
||||
License license = TestUtils.generateLicenses(new TestUtils.LicenseSpec(validLicenseIssueDate, expiredLicenseExpiryDate));
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
license.toXContent(builder, new ToXContent.MapParams(Collections.singletonMap(License.REST_VIEW_MODE, "true")));
|
||||
builder.flush();
|
||||
Map<String, Object> map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
||||
|
||||
// should have an extra status field, human readable issue_data and expiry_date
|
||||
assertThat(map.get("status"), notNullValue());
|
||||
assertThat(map.get("issue_date"), notNullValue());
|
||||
assertThat(map.get("expiry_date"), notNullValue());
|
||||
assertThat(map.get("status"), equalTo("expired"));
|
||||
builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
license.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.flush();
|
||||
map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
||||
assertThat(map.get("status"), nullValue());
|
||||
|
||||
license = TestUtils.generateLicenses(new TestUtils.LicenseSpec(validLicenseIssueDate, validLicenseExpiryDate));
|
||||
builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
license.toXContent(builder, new ToXContent.MapParams(Collections.singletonMap(License.REST_VIEW_MODE, "true")));
|
||||
builder.flush();
|
||||
map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
||||
|
||||
// should have an extra status field, human readable issue_data and expiry_date
|
||||
assertThat(map.get("status"), notNullValue());
|
||||
assertThat(map.get("issue_date"), notNullValue());
|
||||
assertThat(map.get("expiry_date"), notNullValue());
|
||||
assertThat(map.get("status"), equalTo("active"));
|
||||
builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
license.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.flush();
|
||||
map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
||||
assertThat(map.get("status"), nullValue());
|
||||
|
||||
license = TestUtils.generateLicenses(new TestUtils.LicenseSpec(invalidLicenseIssueDate, validLicenseExpiryDate));
|
||||
builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
license.toXContent(builder, new ToXContent.MapParams(Collections.singletonMap(License.REST_VIEW_MODE, "true")));
|
||||
builder.flush();
|
||||
map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
||||
|
||||
// should have an extra status field, human readable issue_data and expiry_date
|
||||
assertThat(map.get("status"), notNullValue());
|
||||
assertThat(map.get("issue_date"), notNullValue());
|
||||
assertThat(map.get("expiry_date"), notNullValue());
|
||||
assertThat(map.get("status"), equalTo("invalid"));
|
||||
builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
license.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.flush();
|
||||
map = XContentHelper.convertToMap(builder.bytesStream().bytes(), false).v2();
|
||||
assertThat(map.get("status"), nullValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.core;
|
||||
|
||||
import org.elasticsearch.common.joda.DateMathParser;
|
||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
||||
import org.elasticsearch.common.joda.Joda;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.*;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.test.ESTestCase.randomFrom;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
|
||||
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
|
||||
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
|
||||
|
||||
public static String dateMathString(String time, final long now) {
|
||||
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
|
||||
@Override
|
||||
public Long call() throws Exception {
|
||||
return now;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static long dateMath(String time, final long now) {
|
||||
return dateMathParser.parse(time, new Callable<Long>() {
|
||||
@Override
|
||||
public Long call() throws Exception {
|
||||
return now;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static LicenseSpec generateRandomLicenseSpec(int version) {
|
||||
boolean datesInMillis = randomBoolean();
|
||||
long now = System.currentTimeMillis();
|
||||
String uid = UUID.randomUUID().toString();
|
||||
String feature = "feature__" + randomInt();
|
||||
String issuer = "issuer__" + randomInt();
|
||||
String issuedTo = "issuedTo__" + randomInt();
|
||||
final String type;
|
||||
final String subscriptionType;
|
||||
if (version < License.VERSION_NO_FEATURE_TYPE) {
|
||||
subscriptionType = randomFrom("gold", "silver", "platinum");
|
||||
type = "subscription";//randomFrom("subscription", "internal", "development");
|
||||
} else {
|
||||
subscriptionType = null;
|
||||
type = randomFrom("basic", "dev", "gold", "silver", "platinum");
|
||||
}
|
||||
int maxNodes = randomIntBetween(5, 100);
|
||||
if (datesInMillis) {
|
||||
long issueDateInMillis = dateMath("now", now);
|
||||
long expiryDateInMillis = dateMath("now+10d/d", now);
|
||||
return new LicenseSpec(version, uid, feature, issueDateInMillis, expiryDateInMillis, type, subscriptionType, issuedTo, issuer, maxNodes);
|
||||
} else {
|
||||
String issueDate = dateMathString("now", now);
|
||||
String expiryDate = dateMathString("now+10d/d", now);
|
||||
return new LicenseSpec(version, uid, feature, issueDate, expiryDate, type, subscriptionType, issuedTo, issuer, maxNodes);
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateLicenseSpecString(LicenseSpec licenseSpec) throws IOException {
|
||||
XContentBuilder licenses = jsonBuilder();
|
||||
licenses.startObject();
|
||||
licenses.startArray("licenses");
|
||||
licenses.startObject()
|
||||
.field("uid", licenseSpec.uid)
|
||||
.field("type", licenseSpec.type)
|
||||
.field("subscription_type", licenseSpec.subscriptionType)
|
||||
.field("issued_to", licenseSpec.issuedTo)
|
||||
.field("issuer", licenseSpec.issuer)
|
||||
.field("feature", licenseSpec.feature)
|
||||
.field("max_nodes", licenseSpec.maxNodes);
|
||||
|
||||
if (licenseSpec.issueDate != null) {
|
||||
licenses.field("issue_date", licenseSpec.issueDate);
|
||||
} else {
|
||||
licenses.field("issue_date_in_millis", licenseSpec.issueDateInMillis);
|
||||
}
|
||||
if (licenseSpec.expiryDate != null) {
|
||||
licenses.field("expiry_date", licenseSpec.expiryDate);
|
||||
} else {
|
||||
licenses.field("expiry_date_in_millis", licenseSpec.expiryDateInMillis);
|
||||
}
|
||||
licenses.field("version", licenseSpec.version);
|
||||
licenses.endObject();
|
||||
licenses.endArray();
|
||||
licenses.endObject();
|
||||
return licenses.string();
|
||||
}
|
||||
|
||||
public static License generateLicenses(LicenseSpec spec) {
|
||||
License.Builder builder = License.builder()
|
||||
.uid(spec.uid)
|
||||
.feature(spec.feature)
|
||||
.type(spec.type)
|
||||
.subscriptionType(spec.subscriptionType)
|
||||
.issuedTo(spec.issuedTo)
|
||||
.issuer(spec.issuer)
|
||||
.maxNodes(spec.maxNodes);
|
||||
|
||||
if (spec.expiryDate != null) {
|
||||
builder.expiryDate(DateUtils.endOfTheDay(spec.expiryDate));
|
||||
} else {
|
||||
builder.expiryDate(spec.expiryDateInMillis);
|
||||
}
|
||||
if (spec.issueDate != null) {
|
||||
builder.issueDate(DateUtils.beginningOfTheDay(spec.issueDate));
|
||||
} else {
|
||||
builder.issueDate(spec.issueDateInMillis);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static void assertLicenseSpec(LicenseSpec spec, License license) {
|
||||
MatcherAssert.assertThat(license.uid(), equalTo(spec.uid));
|
||||
MatcherAssert.assertThat(license.issuedTo(), equalTo(spec.issuedTo));
|
||||
MatcherAssert.assertThat(license.issuer(), equalTo(spec.issuer));
|
||||
MatcherAssert.assertThat(license.type(), equalTo(spec.type));
|
||||
MatcherAssert.assertThat(license.maxNodes(), equalTo(spec.maxNodes));
|
||||
if (spec.issueDate != null) {
|
||||
MatcherAssert.assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay(spec.issueDate)));
|
||||
} else {
|
||||
MatcherAssert.assertThat(license.issueDate(), equalTo(spec.issueDateInMillis));
|
||||
}
|
||||
if (spec.expiryDate != null) {
|
||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay(spec.expiryDate)));
|
||||
} else {
|
||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(spec.expiryDateInMillis));
|
||||
}
|
||||
}
|
||||
|
||||
public static class LicenseSpec {
|
||||
public final int version;
|
||||
public final String feature;
|
||||
public final String issueDate;
|
||||
public final long issueDateInMillis;
|
||||
public final String expiryDate;
|
||||
public final long expiryDateInMillis;
|
||||
public final String uid;
|
||||
public final String type;
|
||||
public final String subscriptionType;
|
||||
public final String issuedTo;
|
||||
public final String issuer;
|
||||
public final int maxNodes;
|
||||
|
||||
public LicenseSpec(String issueDate, String expiryDate) {
|
||||
this(License.VERSION_CURRENT, UUID.randomUUID().toString(), "feature", issueDate, expiryDate, "trial", "none", "customer", "elasticsearch", 5);
|
||||
}
|
||||
|
||||
public LicenseSpec(int version, String uid, String feature, long issueDateInMillis, long expiryDateInMillis, String type,
|
||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
||||
this.version = version;
|
||||
this.feature = feature;
|
||||
this.issueDateInMillis = issueDateInMillis;
|
||||
this.issueDate = null;
|
||||
this.expiryDateInMillis = expiryDateInMillis;
|
||||
this.expiryDate = null;
|
||||
this.uid = uid;
|
||||
this.type = type;
|
||||
this.subscriptionType = subscriptionType;
|
||||
this.issuedTo = issuedTo;
|
||||
this.issuer = issuer;
|
||||
this.maxNodes = maxNodes;
|
||||
}
|
||||
|
||||
public LicenseSpec(int version, String uid, String feature, String issueDate, String expiryDate, String type,
|
||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
||||
this.version = version;
|
||||
this.feature = feature;
|
||||
this.issueDate = issueDate;
|
||||
this.issueDateInMillis = -1;
|
||||
this.expiryDate = expiryDate;
|
||||
this.expiryDateInMillis = -1;
|
||||
this.uid = uid;
|
||||
this.type = type;
|
||||
this.subscriptionType = subscriptionType;
|
||||
this.issuedTo = issuedTo;
|
||||
this.issuer = issuer;
|
||||
this.maxNodes = maxNodes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
es.logger.level=INFO
|
||||
log4j.rootLogger=${es.logger.level}, out
|
||||
|
||||
log4j.logger.org.apache.http=INFO, out
|
||||
log4j.additivity.org.apache.http=false
|
||||
|
||||
log4j.logger.org.elasticsearch.license=TRACE
|
||||
|
||||
log4j.appender.out=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.out.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
|
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßlj <09>ş]d™}o§OčľId®Č
|
||||
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{’Ľ˛Áôśát–ť¤7ůřĂę#˛Vqöó»ktwm’Ś]ĎLőŁz"| Q‹lźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5‹ŻŤĘĘ7%ŘęD
|
||||
Yĺ‘xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|ŢÓŞĎÁ*ń§D`<60>Ú?‚ůxU/3>xUÓ“+ č
|
|
@ -0,0 +1,7 @@
|
|||
subprojects {
|
||||
project.afterEvaluate {
|
||||
project.forbiddenPatterns {
|
||||
exclude '**/*.key'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/eclipse-build/
|
|
@ -0,0 +1,19 @@
|
|||
apply plugin: 'elasticsearch.esplugin'
|
||||
esplugin {
|
||||
name 'found-plugin'
|
||||
description 'Internal Elasticsearch Licensing Plugin for Found'
|
||||
classname 'org.elasticsearch.license.plugin.LicensePlugin'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':x-plugins:elasticsearch:license:plugin-api')
|
||||
testCompile project(':x-plugins:elasticsearch:license:licensor')
|
||||
}
|
||||
|
||||
// no tests
|
||||
test.enabled = false
|
||||
integTest.enabled = false
|
||||
|
||||
dependencyLicenses.enabled = false
|
||||
|
||||
compileJava.options.compilerArgs << "-Xlint:-rawtypes,-unchecked"
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin;
|
||||
|
||||
import org.elasticsearch.common.inject.AbstractModule;
|
||||
import org.elasticsearch.license.core.LicenseVerifier;
|
||||
import org.elasticsearch.license.plugin.core.FoundLicensesService;
|
||||
import org.elasticsearch.license.plugin.core.LicenseeRegistry;
|
||||
import org.elasticsearch.license.plugin.core.LicensesManagerService;
|
||||
|
||||
public class LicenseModule extends AbstractModule {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
bind(LicenseVerifier.class).asEagerSingleton();
|
||||
bind(LicenseeRegistry.class).to(FoundLicensesService.class);
|
||||
bind(LicensesManagerService.class).to(FoundLicensesService.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.component.LifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.inject.Module;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.plugin.core.FoundLicensesService;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class LicensePlugin extends Plugin {
|
||||
|
||||
public static final String NAME = "license";
|
||||
|
||||
@Inject
|
||||
public LicensePlugin(Settings settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Internal Elasticsearch Licensing Plugin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends LifecycleComponent>> nodeServices() {
|
||||
Collection<Class<? extends LifecycleComponent>> services = new ArrayList<>();
|
||||
services.add(FoundLicensesService.class);
|
||||
return services;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Module> nodeModules() {
|
||||
Collection<Module> modules = new ArrayList<Module>();
|
||||
modules.add(new LicenseModule());
|
||||
return modules;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.inject.Singleton;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.core.License;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
@Singleton
|
||||
public class FoundLicensesService extends AbstractLifecycleComponent<FoundLicensesService> implements LicenseeRegistry, LicensesManagerService {
|
||||
|
||||
@Inject
|
||||
public FoundLicensesService(Settings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void register(Licensee licensee) {
|
||||
licensee.onChange(new Licensee.Status(License.OperationMode.PLATINUM, LicenseState.ENABLED));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> licenseesWithState(LicenseState state) {
|
||||
return Collections.<String>emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public License getLicense() {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License;
|
||||
# you may not use this file except in compliance with the Elastic License.
|
||||
|
||||
CDPATH=""
|
||||
SCRIPT="$0"
|
||||
|
||||
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
||||
while [ -h "$SCRIPT" ] ; do
|
||||
ls=`ls -ld "$SCRIPT"`
|
||||
# Drop everything prior to ->
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
SCRIPT="$link"
|
||||
else
|
||||
SCRIPT=`dirname "$SCRIPT"`/"$link"
|
||||
fi
|
||||
done
|
||||
|
||||
# determine license home
|
||||
LICENSE_HOME=`dirname "$SCRIPT"`/..
|
||||
|
||||
# make LICENSE_HOME absolute
|
||||
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
|
||||
|
||||
# setup classpath
|
||||
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
|
||||
|
||||
if [ -x "$JAVA_HOME/bin/java" ]; then
|
||||
JAVA=$JAVA_HOME/bin/java
|
||||
else
|
||||
JAVA=`which java`
|
||||
fi
|
||||
|
||||
|
||||
# Parse any long getopt options and put them into properties before calling getopt below
|
||||
# Be dash compatible to make sure running under ubuntu works
|
||||
ARGCOUNT=$#
|
||||
COUNT=0
|
||||
while [ $COUNT -lt $ARGCOUNT ]
|
||||
do
|
||||
case $1 in
|
||||
-D*=*)
|
||||
properties="$properties $1"
|
||||
shift 1; COUNT=$(($COUNT+1))
|
||||
;;
|
||||
-D*)
|
||||
properties="$properties $1=$2"
|
||||
shift ; shift; COUNT=$(($COUNT+2))
|
||||
;;
|
||||
*) set -- "$@" "$1"; shift; COUNT=$(($COUNT+1))
|
||||
esac
|
||||
done
|
||||
|
||||
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" -Des.path.home="`pwd`" org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool "$@"
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License;
|
||||
# you may not use this file except in compliance with the Elastic License.
|
||||
|
||||
CDPATH=""
|
||||
SCRIPT="$0"
|
||||
|
||||
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
||||
while [ -h "$SCRIPT" ] ; do
|
||||
ls=`ls -ld "$SCRIPT"`
|
||||
# Drop everything prior to ->
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
SCRIPT="$link"
|
||||
else
|
||||
SCRIPT=`dirname "$SCRIPT"`/"$link"
|
||||
fi
|
||||
done
|
||||
|
||||
# determine license home
|
||||
LICENSE_HOME=`dirname "$SCRIPT"`/..
|
||||
|
||||
# make LICENSE_HOME absolute
|
||||
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
|
||||
|
||||
# setup classpath
|
||||
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
|
||||
|
||||
if [ -x "$JAVA_HOME/bin/java" ]; then
|
||||
JAVA=$JAVA_HOME/bin/java
|
||||
else
|
||||
JAVA=`which java`
|
||||
fi
|
||||
|
||||
# Parse any long getopt options and put them into properties before calling getopt below
|
||||
# Be dash compatible to make sure running under ubuntu works
|
||||
ARGCOUNT=$#
|
||||
COUNT=0
|
||||
while [ $COUNT -lt $ARGCOUNT ]
|
||||
do
|
||||
case $1 in
|
||||
-D*=*)
|
||||
properties="$properties $1"
|
||||
shift 1; COUNT=$(($COUNT+1))
|
||||
;;
|
||||
-D*)
|
||||
properties="$properties $1=$2"
|
||||
shift ; shift; COUNT=$(($COUNT+2))
|
||||
;;
|
||||
*) set -- "$@" "$1"; shift; COUNT=$(($COUNT+1))
|
||||
esac
|
||||
done
|
||||
|
||||
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" -Des.path.home="`pwd`" org.elasticsearch.license.licensor.tools.LicenseGeneratorTool "$@"
|
|
@ -0,0 +1,57 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
# or more contributor license agreements. Licensed under the Elastic License;
|
||||
# you may not use this file except in compliance with the Elastic License.
|
||||
|
||||
CDPATH=""
|
||||
SCRIPT="$0"
|
||||
|
||||
# SCRIPT may be an arbitrarily deep series of symlinks. Loop until we have the concrete path.
|
||||
while [ -h "$SCRIPT" ] ; do
|
||||
ls=`ls -ld "$SCRIPT"`
|
||||
# Drop everything prior to ->
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
SCRIPT="$link"
|
||||
else
|
||||
SCRIPT=`dirname "$SCRIPT"`/"$link"
|
||||
fi
|
||||
done
|
||||
|
||||
# determine license home
|
||||
LICENSE_HOME=`dirname "$SCRIPT"`/..
|
||||
|
||||
# make LICENSE_HOME absolute
|
||||
LICENSE_HOME=`cd "$LICENSE_HOME"; pwd`
|
||||
|
||||
# setup classpath
|
||||
LICENSE_CLASSPATH=$LICENSE_CLASSPATH:$LICENSE_HOME/lib/${project.artifactId}-${project.version}-exec.jar:$LICENSE_HOME/lib/*
|
||||
|
||||
if [ -x "$JAVA_HOME/bin/java" ]; then
|
||||
JAVA=$JAVA_HOME/bin/java
|
||||
else
|
||||
JAVA=`which java`
|
||||
fi
|
||||
|
||||
# Parse any long getopt options and put them into properties before calling getopt below
|
||||
# Be dash compatible to make sure running under ubuntu works
|
||||
ARGCOUNT=$#
|
||||
COUNT=0
|
||||
while [ $COUNT -lt $ARGCOUNT ]
|
||||
do
|
||||
case $1 in
|
||||
-D*=*)
|
||||
properties="$properties $1"
|
||||
shift 1; COUNT=$(($COUNT+1))
|
||||
;;
|
||||
-D*)
|
||||
properties="$properties $1=$2"
|
||||
shift ; shift; COUNT=$(($COUNT+2))
|
||||
;;
|
||||
*) set -- "$@" "$1"; shift; COUNT=$(($COUNT+1))
|
||||
esac
|
||||
done
|
||||
|
||||
exec "$JAVA" $JAVA_OPTS -Xmx64m -Xms16m $properties -cp "$LICENSE_CLASSPATH" -Des.path.home="`pwd`" org.elasticsearch.license.licensor.tools.LicenseVerificationTool "$@"
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
apply plugin: 'elasticsearch.build'
|
||||
|
||||
dependencies {
|
||||
compile project(':x-plugins:elasticsearch:license:base')
|
||||
compile "org.elasticsearch:elasticsearch:${version}"
|
||||
testCompile "org.elasticsearch:test-framework:${version}"
|
||||
}
|
||||
|
||||
dependencyLicenses.enabled = false
|
|
@ -0,0 +1,122 @@
|
|||
<?xml version="1.0"?>
|
||||
<project name="commercial-integration-tests">
|
||||
|
||||
<condition property="is_windows">
|
||||
<os family="windows"/>
|
||||
</condition>
|
||||
|
||||
<!-- runs a script (only on unix) -->
|
||||
<macrodef name="run-script">
|
||||
<attribute name="script"/>
|
||||
<attribute name="output"/>
|
||||
<element name="nested" optional="true"/>
|
||||
<sequential>
|
||||
<!-- create a temp CWD, to enforce that commands don't rely on CWD -->
|
||||
<local name="temp.cwd"/>
|
||||
<tempfile property="temp.cwd" destDir="${integ.temp}"/>
|
||||
<mkdir dir="${temp.cwd}"/>
|
||||
|
||||
<!-- print commands we run -->
|
||||
<local name="script.base"/>
|
||||
<basename file="@{script}" property="script.base"/>
|
||||
<!-- crappy way to output, but we need it. make it nice later -->
|
||||
<echoxml><exec script="${script.base}"><nested/></exec></echoxml>
|
||||
|
||||
<exec executable="sh" osfamily="unix" dir="${temp.cwd}" failonerror="false" resultproperty="status" output="@{output}" taskname="${script.base}">
|
||||
<arg value="@{script}"/>
|
||||
<nested/>
|
||||
</exec>
|
||||
<fail message="Failed to execute @{script}">
|
||||
<condition>
|
||||
<not>
|
||||
<equals arg1="${status}" arg2="0"/>
|
||||
</not>
|
||||
</condition>
|
||||
</fail>
|
||||
</sequential>
|
||||
</macrodef>
|
||||
|
||||
<target name="verify-tools" unless="is_windows">
|
||||
<sequential>
|
||||
<delete dir="${integ.scratch}"/>
|
||||
<unzip src="${project.build.directory}/releases/${project.artifactId}-${project.version}-exec.zip" dest="${integ.scratch}"/>
|
||||
<local name="home"/>
|
||||
<property name="home" location="${integ.scratch}/${project.artifactId}-${project.version}"/>
|
||||
|
||||
<!-- verify key pair generator tool -->
|
||||
<run-script script="${home}/bin/key-pair-generator" output="${integ.scratch}/key-pair-gen-help.txt">
|
||||
<nested>
|
||||
<arg value="-h"/>
|
||||
</nested>
|
||||
</run-script>
|
||||
<run-script script="${home}/bin/key-pair-generator" output="${integ.scratch}/key-pair-gen-out.txt">
|
||||
<nested>
|
||||
<arg value="-pub"/>
|
||||
<arg value="${integ.scratch}/public.key"/>
|
||||
<arg value="-pri"/>
|
||||
<arg value="${integ.scratch}/private.key"/>
|
||||
</nested>
|
||||
</run-script>
|
||||
<fail message="private key not created by bin/key-pair-generator">
|
||||
<condition>
|
||||
<not>
|
||||
<resourceexists>
|
||||
<file file="${integ.scratch}/private.key"/>
|
||||
</resourceexists>
|
||||
</not>
|
||||
</condition>
|
||||
</fail>
|
||||
<fail message="public key not created by bin/key-pair-generator">
|
||||
<condition>
|
||||
<not>
|
||||
<resourceexists>
|
||||
<file file="${integ.scratch}/public.key"/>
|
||||
</resourceexists>
|
||||
</not>
|
||||
</condition>
|
||||
</fail>
|
||||
|
||||
<!-- verify license generator tool -->
|
||||
<run-script script="${home}/bin/license-generator" output="${integ.scratch}/license-gen-help.txt">
|
||||
<nested>
|
||||
<arg value="-h"/>
|
||||
</nested>
|
||||
</run-script>
|
||||
<run-script script="${home}/bin/license-generator" output="${integ.scratch}/signed_license.json">
|
||||
<nested>
|
||||
<arg value="-lf"/>
|
||||
<arg value="${basedir}/sample/license_spec.json"/>
|
||||
<arg value="-pub"/>
|
||||
<arg value="${integ.scratch}/public.key"/>
|
||||
<arg value="-pri"/>
|
||||
<arg value="${integ.scratch}/private.key"/>
|
||||
</nested>
|
||||
</run-script>
|
||||
<fail message="signed license was not created by license-generator">
|
||||
<condition>
|
||||
<not>
|
||||
<resourceexists>
|
||||
<file file="${integ.scratch}/signed_license.json"/>
|
||||
</resourceexists>
|
||||
</not>
|
||||
</condition>
|
||||
</fail>
|
||||
|
||||
<!-- verify license verification tool -->
|
||||
<run-script script="${home}/bin/verify-license" output="${integ.scratch}/verify-license-help.txt">
|
||||
<nested>
|
||||
<arg value="-h"/>
|
||||
</nested>
|
||||
</run-script>
|
||||
<run-script script="${home}/bin/verify-license" output="${integ.scratch}/verify-license-license.txt">
|
||||
<nested>
|
||||
<arg value="-lf"/>
|
||||
<arg value="${integ.scratch}/signed_license.json"/>
|
||||
<arg value="-pub"/>
|
||||
<arg value="${integ.scratch}/public.key"/>
|
||||
</nested>
|
||||
</run-script>
|
||||
<delete dir="${integ.scratch}"/>
|
||||
</sequential>
|
||||
</target>
|
||||
</project>
|
|
@ -0,0 +1 @@
|
|||
{"licenses":[{"uid": "893361dc-9749-4997-93cb-802e3d7fa4a8", "type":"basic","issued_to":"issuedTo","issuer":"issuer","issue_date":"2014-09-29","expiry_date":"2030-08-29","max_nodes":1}]}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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.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.CryptUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.*;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Responsible for generating a license signature according to
|
||||
* the signature spec and sign it with the provided encrypted private key
|
||||
*/
|
||||
public class LicenseSigner {
|
||||
|
||||
private final static int MAGIC_LENGTH = 13;
|
||||
|
||||
private final Path publicKeyPath;
|
||||
|
||||
private final Path privateKeyPath;
|
||||
|
||||
public LicenseSigner(final Path privateKeyPath, final Path publicKeyPath) {
|
||||
this.publicKeyPath = publicKeyPath;
|
||||
this.privateKeyPath = privateKeyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a signature for the <code>licenseSpec</code>.
|
||||
* Signature structure:
|
||||
* | VERSION | MAGIC | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
|
||||
*
|
||||
* @return a signed License
|
||||
*/
|
||||
public License sign(License licenseSpec) throws IOException {
|
||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
licenseSpec.toXContent(contentBuilder, new ToXContent.MapParams(Collections.singletonMap(License.LICENSE_SPEC_VIEW_MODE, "true")));
|
||||
final byte[] signedContent;
|
||||
try {
|
||||
final Signature rsa = Signature.getInstance("SHA512withRSA");
|
||||
rsa.initSign(CryptUtils.readEncryptedPrivateKey(Files.readAllBytes(privateKeyPath)));
|
||||
rsa.update(contentBuilder.bytes().toBytes());
|
||||
signedContent = rsa.sign();
|
||||
} catch (InvalidKeyException | IOException | NoSuchAlgorithmException | SignatureException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
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(licenseSpec.version())
|
||||
.putInt(magic.length)
|
||||
.put(magic)
|
||||
.putInt(hash.length)
|
||||
.put(hash)
|
||||
.putInt(signedContent.length)
|
||||
.put(signedContent);
|
||||
|
||||
return License.builder()
|
||||
.fromLicenseSpec(licenseSpec, Base64.encodeBytes(bytes))
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor.tools;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.cli.CliTool;
|
||||
import org.elasticsearch.common.cli.CliToolConfig;
|
||||
import org.elasticsearch.common.cli.Terminal;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.config;
|
||||
import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPrivateKey;
|
||||
import static org.elasticsearch.license.core.CryptUtils.writeEncryptedPublicKey;
|
||||
|
||||
public class KeyPairGeneratorTool extends CliTool {
|
||||
|
||||
public static final String NAME = "key-pair-generator";
|
||||
private static final CliToolConfig CONFIG = config("licensor", KeyPairGeneratorTool.class)
|
||||
.cmds(KeyGenerator.CMD)
|
||||
.build();
|
||||
|
||||
public KeyPairGeneratorTool() {
|
||||
super(CONFIG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String s, CommandLine commandLine) throws Exception {
|
||||
return KeyGenerator.parse(terminal, commandLine, env);
|
||||
}
|
||||
|
||||
public static class KeyGenerator extends Command {
|
||||
|
||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, KeyGenerator.class)
|
||||
.options(
|
||||
option("pub", "publicKeyPath").required(true).hasArg(true),
|
||||
option("pri", "privateKeyPath").required(true).hasArg(true)
|
||||
).build();
|
||||
|
||||
public final Path publicKeyPath;
|
||||
public final Path privateKeyPath;
|
||||
|
||||
protected KeyGenerator(Terminal terminal, Path publicKeyPath, Path privateKeyPath) {
|
||||
super(terminal);
|
||||
this.privateKeyPath = privateKeyPath;
|
||||
this.publicKeyPath = publicKeyPath;
|
||||
}
|
||||
|
||||
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) {
|
||||
Path publicKeyPath = environment.binFile().getParent().resolve(commandLine.getOptionValue("publicKeyPath"));
|
||||
Path privateKeyPath = environment.binFile().getParent().resolve(commandLine.getOptionValue("privateKeyPath"));
|
||||
|
||||
if (Files.exists(privateKeyPath)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, privateKeyPath + " already exists");
|
||||
} else if (Files.exists(publicKeyPath)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " already exists");
|
||||
}
|
||||
return new KeyGenerator(terminal, publicKeyPath, privateKeyPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
KeyPair keyPair = generateKeyPair(privateKeyPath, publicKeyPath);
|
||||
terminal.println(Terminal.Verbosity.VERBOSE, "generating key pair [public key: " + publicKeyPath + ", private key: " + privateKeyPath + "]");
|
||||
return (keyPair != null) ? ExitStatus.OK : ExitStatus.CANT_CREATE;
|
||||
}
|
||||
|
||||
private static KeyPair generateKeyPair(Path privateKeyPath, Path publicKeyPath) throws IOException, NoSuchAlgorithmException {
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048, random);
|
||||
KeyPair keyPair = keyGen.generateKeyPair();
|
||||
|
||||
saveKeyPairToFiles(keyPair, privateKeyPath, publicKeyPath);
|
||||
return keyPair;
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveKeyPairToFiles(KeyPair keyPair, Path privateKeyPath, Path publicKeyPath) throws IOException {
|
||||
Files.write(privateKeyPath, writeEncryptedPrivateKey(keyPair.getPrivate()));
|
||||
Files.write(publicKeyPath, writeEncryptedPublicKey(keyPair.getPublic()));
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ExitStatus exitStatus = new KeyPairGeneratorTool().execute(args);
|
||||
exit(exitStatus.status());
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "Allowed to exit explicitly from #main()")
|
||||
private static void exit(int status) {
|
||||
System.exit(status);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor.tools;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.cli.CliTool;
|
||||
import org.elasticsearch.common.cli.CliToolConfig;
|
||||
import org.elasticsearch.common.cli.Terminal;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.licensor.LicenseSigner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.config;
|
||||
|
||||
public class LicenseGeneratorTool extends CliTool {
|
||||
public static final String NAME = "license-generator";
|
||||
|
||||
private static final CliToolConfig CONFIG = config("licensor", LicenseGeneratorTool.class)
|
||||
.cmds(LicenseGenerator.CMD)
|
||||
.build();
|
||||
|
||||
public LicenseGeneratorTool() {
|
||||
super(CONFIG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String s, CommandLine commandLine) throws Exception {
|
||||
return LicenseGenerator.parse(terminal, commandLine, env);
|
||||
}
|
||||
|
||||
public static class LicenseGenerator extends Command {
|
||||
|
||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, LicenseGenerator.class)
|
||||
.options(
|
||||
option("pub", "publicKeyPath").required(true).hasArg(true),
|
||||
option("pri", "privateKeyPath").required(true).hasArg(true),
|
||||
option("l", "license").required(false).hasArg(true),
|
||||
option("lf", "licenseFile").required(false).hasArg(true)
|
||||
).build();
|
||||
|
||||
public final License licenseSpec;
|
||||
public final Path publicKeyFilePath;
|
||||
public final Path privateKeyFilePath;
|
||||
|
||||
public LicenseGenerator(Terminal terminal, Path publicKeyFilePath, Path privateKeyFilePath, License licenseSpec) {
|
||||
super(terminal);
|
||||
this.licenseSpec = licenseSpec;
|
||||
this.privateKeyFilePath = privateKeyFilePath;
|
||||
this.publicKeyFilePath = publicKeyFilePath;
|
||||
}
|
||||
|
||||
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) throws IOException {
|
||||
Path publicKeyPath = environment.binFile().getParent().resolve(commandLine.getOptionValue("publicKeyPath"));
|
||||
Path privateKeyPath = environment.binFile().getParent().resolve(commandLine.getOptionValue("privateKeyPath"));
|
||||
String licenseSpecSource = commandLine.getOptionValue("license");
|
||||
String licenseSpecSourceFile = commandLine.getOptionValue("licenseFile");
|
||||
|
||||
if (!Files.exists(privateKeyPath)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, privateKeyPath + " does not exist");
|
||||
} else if (!Files.exists(publicKeyPath)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
|
||||
}
|
||||
|
||||
License license = null;
|
||||
if (licenseSpecSource != null) {
|
||||
license = License.fromSource(licenseSpecSource);
|
||||
} else if (licenseSpecSourceFile != null) {
|
||||
Path licenseSpecPath = environment.binFile().getParent().resolve(licenseSpecSourceFile);
|
||||
if (!Files.exists(licenseSpecPath)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, licenseSpecSourceFile + " does not exist");
|
||||
}
|
||||
license = License.fromSource(Files.readAllBytes(licenseSpecPath));
|
||||
}
|
||||
|
||||
if (license == null) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, "no license spec provided");
|
||||
}
|
||||
return new LicenseGenerator(terminal, publicKeyPath, privateKeyPath, license);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
|
||||
// sign
|
||||
License license = new LicenseSigner(privateKeyFilePath, publicKeyFilePath).sign(licenseSpec);
|
||||
|
||||
// dump
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
builder.startObject();
|
||||
builder.startObject("license");
|
||||
license.toInnerXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.endObject();
|
||||
builder.endObject();
|
||||
builder.flush();
|
||||
terminal.print(builder.string());
|
||||
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ExitStatus exitStatus = new LicenseGeneratorTool().execute(args);
|
||||
exit(exitStatus.status());
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "Allowed to exit explicitly from #main()")
|
||||
private static void exit(int status) {
|
||||
System.exit(status);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor.tools;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
import org.elasticsearch.common.SuppressForbidden;
|
||||
import org.elasticsearch.common.cli.CliTool;
|
||||
import org.elasticsearch.common.cli.CliToolConfig;
|
||||
import org.elasticsearch.common.cli.Terminal;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.license.core.License;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
|
||||
import static org.elasticsearch.common.cli.CliToolConfig.config;
|
||||
|
||||
public class LicenseVerificationTool extends CliTool {
|
||||
public static final String NAME = "verify-license";
|
||||
|
||||
private static final CliToolConfig CONFIG = config("licensor", LicenseVerificationTool.class)
|
||||
.cmds(LicenseVerifier.CMD)
|
||||
.build();
|
||||
|
||||
public LicenseVerificationTool() {
|
||||
super(CONFIG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Command parse(String s, CommandLine commandLine) throws Exception {
|
||||
return LicenseVerifier.parse(terminal, commandLine, env);
|
||||
}
|
||||
|
||||
public static class LicenseVerifier extends Command {
|
||||
|
||||
private static final CliToolConfig.Cmd CMD = cmd(NAME, LicenseVerifier.class)
|
||||
.options(
|
||||
option("pub", "publicKeyPath").required(true).hasArg(true),
|
||||
option("l", "license").required(false).hasArg(true),
|
||||
option("lf", "licenseFile").required(false).hasArg(true)
|
||||
).build();
|
||||
|
||||
public final License license;
|
||||
public final Path publicKeyPath;
|
||||
|
||||
public LicenseVerifier(Terminal terminal, License license, Path publicKeyPath) {
|
||||
super(terminal);
|
||||
this.license = license;
|
||||
this.publicKeyPath = publicKeyPath;
|
||||
}
|
||||
|
||||
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) throws IOException {
|
||||
String publicKeyPathString = commandLine.getOptionValue("publicKeyPath");
|
||||
String licenseSource = commandLine.getOptionValue("license");
|
||||
String licenseSourceFile = commandLine.getOptionValue("licenseFile");
|
||||
|
||||
License license = null;
|
||||
if (licenseSource != null) {
|
||||
license = License.fromSource(licenseSource);
|
||||
} else if (licenseSourceFile != null) {
|
||||
Path licenseSpecPath = environment.binFile().getParent().resolve(licenseSourceFile);
|
||||
if (!Files.exists(licenseSpecPath)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, licenseSourceFile + " does not exist");
|
||||
}
|
||||
license = License.fromSource(Files.readAllBytes(licenseSpecPath));
|
||||
}
|
||||
if (license == null) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, "no license spec provided");
|
||||
}
|
||||
Path publicKeyPath = environment.binFile().getParent().resolve(publicKeyPathString);
|
||||
if (!Files.exists(publicKeyPath)) {
|
||||
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
|
||||
}
|
||||
return new LicenseVerifier(terminal, license, publicKeyPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExitStatus execute(Settings settings, Environment env) throws Exception {
|
||||
|
||||
// verify
|
||||
if (!org.elasticsearch.license.core.LicenseVerifier.verifyLicense(license, Files.readAllBytes(publicKeyPath))) {
|
||||
terminal.println("Invalid License!");
|
||||
return ExitStatus.DATA_ERROR;
|
||||
}
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
builder.startObject();
|
||||
builder.startObject("license");
|
||||
license.toInnerXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.endObject();
|
||||
builder.endObject();
|
||||
builder.flush();
|
||||
terminal.print(builder.string());
|
||||
return ExitStatus.OK;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ExitStatus exitStatus = new LicenseVerificationTool().execute(args);
|
||||
exit(exitStatus.status());
|
||||
}
|
||||
|
||||
@SuppressForbidden(reason = "Allowed to exit explicitly from #main()")
|
||||
private static void exit(int status) {
|
||||
System.exit(status);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
NAME
|
||||
|
||||
key-pair-generator - generates a key pair with RSA 2048-bit security
|
||||
|
||||
SYNOPSIS
|
||||
|
||||
key-pair-generator -pub publicKeyPath -pri privateKeyPath
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
This tool generates and saves a key pair to the provided publicKeyPath
|
||||
and privateKeyPath. The tool checks the existence of the provided key paths
|
||||
and will not override if any existing keys are found.
|
||||
|
||||
OPTIONS
|
||||
|
||||
-h,--help Shows this message
|
||||
|
||||
-pub,--publicKeyPath <path> Save the generated public key to path
|
||||
|
||||
-pri,--privateKeyPath <path> Save the generated private key to path
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
NAME
|
||||
|
||||
license-generator - generates signed elasticsearch license(s) for a given license spec(s)
|
||||
|
||||
SYNOPSIS
|
||||
|
||||
license-generator -l licenseSpec -pub publicKeyPath -pri privateKeyPath
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
This tool generate elasticsearch license(s) for the provided license spec(s). The tool
|
||||
can take arbitrary number of `--license` and/or `--licenseFile` to generate corrosponding
|
||||
signed license(s).
|
||||
|
||||
OPTIONS
|
||||
|
||||
-h,--help Shows this message
|
||||
|
||||
-l,--license <license spec> License spec to generate a signed license from
|
||||
|
||||
-lf,--licenseFile <path> Path to a license spec file
|
||||
|
||||
-pub,--publicKeyPath <path> Path to public key to be used
|
||||
|
||||
-pri,--privateKeyPath <path> Path to private key to be used
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
NAME
|
||||
|
||||
verify-license - verifies the integrity of elasticsearch signed license(s)
|
||||
|
||||
SYNOPSIS
|
||||
|
||||
verify-license -l signedLicense -pub publicKeyPath
|
||||
|
||||
DESCRIPTION
|
||||
|
||||
This tool assumes the configured public key to be the same as that of the production license plugin public key.
|
||||
The tool can take arbitrary number of `--license` and/or `--licenseFile` for verifying signed license(s). If any
|
||||
of the provided license(s) are invalid, the tool will error out, otherwise it will output a effective licenses file.
|
||||
|
||||
Effective Licenses:
|
||||
A set of licenses that only has one effective sub-license for every feature provided through the input license file.
|
||||
Where effective sub-licenses are identified as the sub-licenses with the latest `expiry_date` for a `feature`
|
||||
and the sub-license has not already expired.
|
||||
|
||||
OPTIONS
|
||||
|
||||
-h,--help Shows this message
|
||||
|
||||
-l,--license <signed license> signed license(s) string
|
||||
|
||||
-lf,--licenseFile <path> Path to signed license(s) file
|
||||
|
||||
-pub,--publicKeyPath <path> Path to public key to verify against
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor;
|
||||
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.license.core.DateUtils;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.core.LicenseVerifier;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class LicenseVerificationTests extends ESTestCase {
|
||||
protected Path pubKeyPath = null;
|
||||
protected Path priKeyPath = null;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
pubKeyPath = getDataPath("/public.key");
|
||||
priKeyPath = getDataPath("/private.key");
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
pubKeyPath = null;
|
||||
priKeyPath = null;
|
||||
}
|
||||
|
||||
public void testGeneratedLicenses() throws Exception {
|
||||
assertThat(LicenseVerifier.verifyLicense(TestUtils.generateSignedLicense(TimeValue.timeValueHours(2 * 24), pubKeyPath, priKeyPath), Files.readAllBytes(pubKeyPath)), equalTo(true));
|
||||
}
|
||||
|
||||
public void testLicenseTampering() throws Exception {
|
||||
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(2), pubKeyPath, priKeyPath);
|
||||
|
||||
final License tamperedLicense = License.builder()
|
||||
.fromLicenseSpec(license, license.signature())
|
||||
.expiryDate(license.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
|
||||
.validate()
|
||||
.build();
|
||||
|
||||
assertThat(LicenseVerifier.verifyLicense(tamperedLicense, Files.readAllBytes(pubKeyPath)), equalTo(false));
|
||||
}
|
||||
|
||||
public void testRandomLicenseVerification() throws Exception {
|
||||
TestUtils.LicenseSpec licenseSpec = TestUtils.generateRandomLicenseSpec(randomIntBetween(License.VERSION_START, License.VERSION_CURRENT));
|
||||
License generatedLicense = generateSignedLicense(licenseSpec, pubKeyPath, priKeyPath);
|
||||
assertThat(LicenseVerifier.verifyLicense(generatedLicense, Files.readAllBytes(pubKeyPath)), equalTo(true));
|
||||
}
|
||||
|
||||
private static License generateSignedLicense(TestUtils.LicenseSpec spec, Path pubKeyPath, Path priKeyPath) throws Exception {
|
||||
LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath);
|
||||
License.Builder builder = License.builder()
|
||||
.uid(spec.uid)
|
||||
.feature(spec.feature)
|
||||
.type(spec.type)
|
||||
.subscriptionType(spec.subscriptionType)
|
||||
.issuedTo(spec.issuedTo)
|
||||
.issuer(spec.issuer)
|
||||
.maxNodes(spec.maxNodes);
|
||||
|
||||
if (spec.expiryDate != null) {
|
||||
builder.expiryDate(DateUtils.endOfTheDay(spec.expiryDate));
|
||||
} else {
|
||||
builder.expiryDate(spec.expiryDateInMillis);
|
||||
}
|
||||
if (spec.issueDate != null) {
|
||||
builder.issueDate(DateUtils.beginningOfTheDay(spec.issueDate));
|
||||
} else {
|
||||
builder.issueDate(spec.issueDateInMillis);
|
||||
}
|
||||
builder.version(spec.version);
|
||||
return signer.sign(builder.build());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor;
|
||||
|
||||
import org.elasticsearch.common.joda.DateMathParser;
|
||||
import org.elasticsearch.common.joda.FormatDateTimeFormatter;
|
||||
import org.elasticsearch.common.joda.Joda;
|
||||
import org.elasticsearch.common.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.DateUtils;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.*;
|
||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||
import static org.elasticsearch.test.ESTestCase.randomFrom;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
public class TestUtils {
|
||||
|
||||
public static final String PUBLIC_KEY_RESOURCE = "/public.key";
|
||||
public static final String PRIVATE_KEY_RESOURCE = "/private.key";
|
||||
|
||||
private final static FormatDateTimeFormatter formatDateTimeFormatter = Joda.forPattern("yyyy-MM-dd");
|
||||
private final static DateMathParser dateMathParser = new DateMathParser(formatDateTimeFormatter);
|
||||
private final static DateTimeFormatter dateTimeFormatter = formatDateTimeFormatter.printer();
|
||||
|
||||
public static String dumpLicense(License license) throws Exception {
|
||||
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
builder.startObject();
|
||||
builder.startObject("license");
|
||||
license.toInnerXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
builder.endObject();
|
||||
builder.endObject();
|
||||
return builder.string();
|
||||
}
|
||||
|
||||
public static String dateMathString(String time, final long now) {
|
||||
return dateTimeFormatter.print(dateMathParser.parse(time, new Callable<Long>() {
|
||||
@Override
|
||||
public Long call() throws Exception {
|
||||
return now;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static long dateMath(String time, final long now) {
|
||||
return dateMathParser.parse(time, new Callable<Long>() {
|
||||
@Override
|
||||
public Long call() throws Exception {
|
||||
return now;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static LicenseSpec generateRandomLicenseSpec(int version) {
|
||||
boolean datesInMillis = randomBoolean();
|
||||
long now = System.currentTimeMillis();
|
||||
String uid = UUID.randomUUID().toString();
|
||||
String issuer = "issuer__" + randomInt();
|
||||
String issuedTo = "issuedTo__" + randomInt();
|
||||
String type = version < License.VERSION_NO_FEATURE_TYPE ?
|
||||
randomFrom("subscription", "internal", "development") :
|
||||
randomFrom("basic", "silver", "dev", "gold", "platinum");
|
||||
final String subscriptionType;
|
||||
final String feature;
|
||||
if (version < License.VERSION_NO_FEATURE_TYPE) {
|
||||
subscriptionType = randomFrom("gold", "silver", "platinum");
|
||||
feature = "feature__" + randomInt();
|
||||
} else {
|
||||
subscriptionType = null;
|
||||
feature = null;
|
||||
}
|
||||
int maxNodes = randomIntBetween(5, 100);
|
||||
if (datesInMillis) {
|
||||
long issueDateInMillis = dateMath("now", now);
|
||||
long expiryDateInMillis = dateMath("now+10d/d", now);
|
||||
return new LicenseSpec(version, uid, feature, issueDateInMillis, expiryDateInMillis, type, subscriptionType, issuedTo, issuer, maxNodes);
|
||||
} else {
|
||||
String issueDate = dateMathString("now", now);
|
||||
String expiryDate = dateMathString("now+10d/d", now);
|
||||
return new LicenseSpec(version, uid, feature, issueDate, expiryDate, type, subscriptionType, issuedTo, issuer, maxNodes);
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateLicenseSpecString(LicenseSpec licenseSpec) throws IOException {
|
||||
XContentBuilder licenses = jsonBuilder();
|
||||
licenses.startObject();
|
||||
licenses.startObject("license")
|
||||
.field("uid", licenseSpec.uid)
|
||||
.field("type", licenseSpec.type)
|
||||
.field("subscription_type", licenseSpec.subscriptionType)
|
||||
.field("issued_to", licenseSpec.issuedTo)
|
||||
.field("issuer", licenseSpec.issuer)
|
||||
.field("feature", licenseSpec.feature)
|
||||
.field("max_nodes", licenseSpec.maxNodes);
|
||||
|
||||
if (licenseSpec.issueDate != null) {
|
||||
licenses.field("issue_date", licenseSpec.issueDate);
|
||||
} else {
|
||||
licenses.field("issue_date_in_millis", licenseSpec.issueDateInMillis);
|
||||
}
|
||||
if (licenseSpec.expiryDate != null) {
|
||||
licenses.field("expiry_date", licenseSpec.expiryDate);
|
||||
} else {
|
||||
licenses.field("expiry_date_in_millis", licenseSpec.expiryDateInMillis);
|
||||
}
|
||||
licenses.field("version", licenseSpec.version);
|
||||
licenses.endObject();
|
||||
licenses.endObject();
|
||||
return licenses.string();
|
||||
}
|
||||
|
||||
public static void assertLicenseSpec(LicenseSpec spec, License license) {
|
||||
MatcherAssert.assertThat(license.uid(), equalTo(spec.uid));
|
||||
MatcherAssert.assertThat(license.issuedTo(), equalTo(spec.issuedTo));
|
||||
MatcherAssert.assertThat(license.issuer(), equalTo(spec.issuer));
|
||||
MatcherAssert.assertThat(license.type(), equalTo(spec.type));
|
||||
MatcherAssert.assertThat(license.maxNodes(), equalTo(spec.maxNodes));
|
||||
if (spec.issueDate != null) {
|
||||
MatcherAssert.assertThat(license.issueDate(), equalTo(DateUtils.beginningOfTheDay(spec.issueDate)));
|
||||
} else {
|
||||
MatcherAssert.assertThat(license.issueDate(), equalTo(spec.issueDateInMillis));
|
||||
}
|
||||
if (spec.expiryDate != null) {
|
||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(DateUtils.endOfTheDay(spec.expiryDate)));
|
||||
} else {
|
||||
MatcherAssert.assertThat(license.expiryDate(), equalTo(spec.expiryDateInMillis));
|
||||
}
|
||||
}
|
||||
|
||||
public static License generateSignedLicense(TimeValue expiryDuration, Path pubKeyPath, Path priKeyPath) throws Exception {
|
||||
long issue = System.currentTimeMillis();
|
||||
int version = ESTestCase.randomIntBetween(License.VERSION_START, License.VERSION_CURRENT);
|
||||
String type = version < License.VERSION_NO_FEATURE_TYPE ?
|
||||
randomFrom("subscription", "internal", "development") :
|
||||
randomFrom("trial", "basic", "silver", "dev", "gold", "platinum");
|
||||
final License.Builder builder = License.builder()
|
||||
.uid(UUID.randomUUID().toString())
|
||||
.expiryDate(issue + expiryDuration.getMillis())
|
||||
.issueDate(issue)
|
||||
.version(version)
|
||||
.type(type)
|
||||
.issuedTo("customer")
|
||||
.issuer("elasticsearch")
|
||||
.maxNodes(5);
|
||||
if (version == License.VERSION_START) {
|
||||
builder.subscriptionType(randomFrom("dev", "gold", "platinum", "silver"));
|
||||
builder.feature(ESTestCase.randomAsciiOfLength(10));
|
||||
}
|
||||
LicenseSigner signer = new LicenseSigner(priKeyPath, pubKeyPath);
|
||||
return signer.sign(builder.build());
|
||||
}
|
||||
|
||||
public static class LicenseSpec {
|
||||
public final int version;
|
||||
public final String feature;
|
||||
public final String issueDate;
|
||||
public final long issueDateInMillis;
|
||||
public final String expiryDate;
|
||||
public final long expiryDateInMillis;
|
||||
public final String uid;
|
||||
public final String type;
|
||||
public final String subscriptionType;
|
||||
public final String issuedTo;
|
||||
public final String issuer;
|
||||
public final int maxNodes;
|
||||
|
||||
public LicenseSpec(int version, String uid, String feature, long issueDateInMillis, long expiryDateInMillis, String type,
|
||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
||||
this.version = version;
|
||||
this.feature = feature;
|
||||
this.issueDateInMillis = issueDateInMillis;
|
||||
this.issueDate = null;
|
||||
this.expiryDateInMillis = expiryDateInMillis;
|
||||
this.expiryDate = null;
|
||||
this.uid = uid;
|
||||
this.type = type;
|
||||
this.subscriptionType = subscriptionType;
|
||||
this.issuedTo = issuedTo;
|
||||
this.issuer = issuer;
|
||||
this.maxNodes = maxNodes;
|
||||
}
|
||||
|
||||
public LicenseSpec(int version, String uid, String feature, String issueDate, String expiryDate, String type,
|
||||
String subscriptionType, String issuedTo, String issuer, int maxNodes) {
|
||||
this.version = version;
|
||||
this.feature = feature;
|
||||
this.issueDate = issueDate;
|
||||
this.issueDateInMillis = -1;
|
||||
this.expiryDate = expiryDate;
|
||||
this.expiryDateInMillis = -1;
|
||||
this.uid = uid;
|
||||
this.type = type;
|
||||
this.subscriptionType = subscriptionType;
|
||||
this.issuedTo = issuedTo;
|
||||
this.issuer = issuer;
|
||||
this.maxNodes = maxNodes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor.tools;
|
||||
|
||||
import org.apache.commons.cli.MissingOptionException;
|
||||
import org.elasticsearch.common.cli.CliTool.Command;
|
||||
import org.elasticsearch.common.cli.CliTool.ExitStatus;
|
||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool.KeyGenerator;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
public class KeyPairGenerationToolTests extends CliToolTestCase {
|
||||
public void testParsingMissingPath() throws Exception {
|
||||
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
|
||||
Path tempFile = createTempFile();
|
||||
try {
|
||||
keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME,
|
||||
new String[] { "--privateKeyPath", tempFile.toAbsolutePath().toString() });
|
||||
fail("no public key path provided");
|
||||
} catch (MissingOptionException e) {
|
||||
assertThat(e.getMessage(), containsString("pub"));
|
||||
}
|
||||
try {
|
||||
keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME,
|
||||
new String[] { "--publicKeyPath", tempFile.toAbsolutePath().toString() });
|
||||
fail("no private key path provided");
|
||||
} catch (MissingOptionException e) {
|
||||
assertThat(e.getMessage(), containsString("pri"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testParsingNeverOverrideKey() throws Exception {
|
||||
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
|
||||
Path tempFile = createTempFile();
|
||||
Path tempFile2 = createTempFile();
|
||||
String nonExistentFilePath = tempFile2.toAbsolutePath().toString();
|
||||
Files.delete(tempFile2);
|
||||
assertThat(Files.exists(tempFile2), equalTo(false));
|
||||
|
||||
Command command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] {"--privateKeyPath", tempFile.toAbsolutePath().toString(),
|
||||
"--publicKeyPath", nonExistentFilePath });
|
||||
|
||||
assertThat(command, instanceOf(Command.Exit.class));
|
||||
Command.Exit exitCommand = (Command.Exit) command;
|
||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
||||
|
||||
command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] {"--publicKeyPath", tempFile.toAbsolutePath().toString(),
|
||||
"--privateKeyPath", nonExistentFilePath });
|
||||
|
||||
assertThat(command, instanceOf(Command.Exit.class));
|
||||
exitCommand = (Command.Exit) command;
|
||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
public void testToolSimple() throws Exception {
|
||||
KeyPairGeneratorTool keyPairGeneratorTool = new KeyPairGeneratorTool();
|
||||
Path publicKeyFilePath = createTempFile().toAbsolutePath();
|
||||
Path privateKeyFilePath = createTempFile().toAbsolutePath();
|
||||
Settings settings = Settings.builder().put("path.home", createTempDir("KeyPairGenerationToolTests")).build();
|
||||
|
||||
Files.delete(publicKeyFilePath);
|
||||
Files.delete(privateKeyFilePath);
|
||||
assertThat(Files.exists(publicKeyFilePath), equalTo(false));
|
||||
assertThat(Files.exists(privateKeyFilePath), equalTo(false));
|
||||
|
||||
Command command = keyPairGeneratorTool.parse(KeyPairGeneratorTool.NAME, new String[] { "--privateKeyPath", privateKeyFilePath.toString(),
|
||||
"--publicKeyPath", publicKeyFilePath.toString() });
|
||||
|
||||
assertThat(command, instanceOf(KeyGenerator.class));
|
||||
KeyGenerator keyGenerator = (KeyGenerator) command;
|
||||
assertThat(keyGenerator.privateKeyPath, equalTo(privateKeyFilePath));
|
||||
assertThat(keyGenerator.publicKeyPath, equalTo(publicKeyFilePath));
|
||||
|
||||
assertThat(Files.exists(publicKeyFilePath), equalTo(false));
|
||||
assertThat(Files.exists(privateKeyFilePath), equalTo(false));
|
||||
|
||||
assertThat(keyGenerator.execute(settings, new Environment(settings)), equalTo(ExitStatus.OK));
|
||||
assertThat(Files.exists(publicKeyFilePath), equalTo(true));
|
||||
assertThat(Files.exists(privateKeyFilePath), equalTo(true));
|
||||
|
||||
Files.delete(publicKeyFilePath);
|
||||
Files.delete(privateKeyFilePath);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor.tools;
|
||||
|
||||
import org.apache.commons.cli.MissingOptionException;
|
||||
import org.elasticsearch.common.cli.CliTool.Command;
|
||||
import org.elasticsearch.common.cli.CliTool.ExitStatus;
|
||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.licensor.TestUtils;
|
||||
import org.elasticsearch.license.licensor.tools.LicenseGeneratorTool.LicenseGenerator;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
public class LicenseGenerationToolTests extends CliToolTestCase {
|
||||
protected Path pubKeyPath = null;
|
||||
protected Path priKeyPath = null;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
logger.error("project.basedir [{}]", System.getProperty("project.basedir"));
|
||||
pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
||||
priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
||||
}
|
||||
|
||||
public void testParsingNonExistentKeyFile() throws Exception {
|
||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec(License.VERSION_CURRENT);
|
||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
||||
new String[] {"--license", TestUtils.generateLicenseSpecString(inputLicenseSpec),
|
||||
"--publicKeyPath", pubKeyPath.toString().concat("invalid"),
|
||||
"--privateKeyPath", priKeyPath.toString() });
|
||||
|
||||
assertThat(command, instanceOf(Command.Exit.class));
|
||||
Command.Exit exitCommand = (Command.Exit) command;
|
||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
||||
|
||||
command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
||||
new String[] {"--license", TestUtils.generateLicenseSpecString(inputLicenseSpec),
|
||||
"--privateKeyPath", priKeyPath.toString().concat("invalid"),
|
||||
"--publicKeyPath", pubKeyPath.toString() });
|
||||
|
||||
assertThat(command, instanceOf(Command.Exit.class));
|
||||
exitCommand = (Command.Exit) command;
|
||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
public void testParsingMissingLicenseSpec() throws Exception {
|
||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
||||
new String[] { "--publicKeyPath", pubKeyPath.toString(),
|
||||
"--privateKeyPath", priKeyPath.toString() });
|
||||
|
||||
assertThat(command, instanceOf(Command.Exit.class));
|
||||
Command.Exit exitCommand = (Command.Exit) command;
|
||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
public void testParsingMissingArgs() throws Exception {
|
||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec(License.VERSION_CURRENT);
|
||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
||||
boolean pubKeyMissing = randomBoolean();
|
||||
try {
|
||||
licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
||||
new String[] { "--license", TestUtils.generateLicenseSpecString(inputLicenseSpec),
|
||||
((pubKeyMissing) ? "--privateKeyPath" : "--publicKeyPath"),
|
||||
((pubKeyMissing) ? priKeyPath.toString() : pubKeyPath.toString()) });
|
||||
fail("missing argument: " + ((pubKeyMissing) ? "publicKeyPath" : "privateKeyPath") + " should throw an exception");
|
||||
} catch (MissingOptionException e) {
|
||||
assertThat(e.getMessage(), containsString((pubKeyMissing) ? "pub" : "pri"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testParsingSimple() throws Exception {
|
||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec(License.VERSION_CURRENT);
|
||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
||||
new String[]{"--license", TestUtils.generateLicenseSpecString(inputLicenseSpec),
|
||||
"--publicKeyPath", pubKeyPath.toString(),
|
||||
"--privateKeyPath", priKeyPath.toString() });
|
||||
|
||||
assertThat(command, instanceOf(LicenseGenerator.class));
|
||||
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
|
||||
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
|
||||
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
|
||||
TestUtils.assertLicenseSpec(inputLicenseSpec, licenseGenerator.licenseSpec);
|
||||
}
|
||||
|
||||
public void testParsingLicenseFile() throws Exception {
|
||||
TestUtils.LicenseSpec inputLicenseSpec = TestUtils.generateRandomLicenseSpec(License.VERSION_CURRENT);
|
||||
Path tempFile = createTempFile();
|
||||
Files.write(tempFile, TestUtils.generateLicenseSpecString(inputLicenseSpec).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
|
||||
Command command = licenseGeneratorTool.parse(LicenseGeneratorTool.NAME,
|
||||
new String[] { "--licenseFile", tempFile.toAbsolutePath().toString(),
|
||||
"--publicKeyPath", pubKeyPath.toString(),
|
||||
"--privateKeyPath", priKeyPath.toString() });
|
||||
|
||||
assertThat(command, instanceOf(LicenseGenerator.class));
|
||||
LicenseGenerator licenseGenerator = (LicenseGenerator) command;
|
||||
assertThat(licenseGenerator.publicKeyFilePath, equalTo(pubKeyPath));
|
||||
assertThat(licenseGenerator.privateKeyFilePath, equalTo(priKeyPath));
|
||||
TestUtils.assertLicenseSpec(inputLicenseSpec, licenseGenerator.licenseSpec);
|
||||
}
|
||||
|
||||
public void testTool() throws Exception {
|
||||
TestUtils.LicenseSpec licenseSpec = TestUtils.generateRandomLicenseSpec(License.VERSION_CURRENT);
|
||||
License license = License.fromSource(TestUtils.generateLicenseSpecString(licenseSpec).getBytes(StandardCharsets.UTF_8));
|
||||
String output = runLicenseGenerationTool(pubKeyPath, priKeyPath, license, ExitStatus.OK);
|
||||
License outputLicense = License.fromSource(output.getBytes(StandardCharsets.UTF_8));
|
||||
TestUtils.assertLicenseSpec(licenseSpec, outputLicense);
|
||||
}
|
||||
|
||||
private String runLicenseGenerationTool(Path pubKeyPath, Path priKeyPath, License licenseSpec, ExitStatus expectedExitStatus) throws Exception {
|
||||
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
|
||||
Settings settings = Settings.builder().put("path.home", createTempDir("LicenseGenerationToolTests")).build();
|
||||
LicenseGenerator licenseGenerator = new LicenseGenerator(outputTerminal, pubKeyPath, priKeyPath, licenseSpec);
|
||||
assertThat(execute(licenseGenerator, settings), equalTo(expectedExitStatus));
|
||||
assertThat(outputTerminal.getTerminalOutput().size(), equalTo(1));
|
||||
return outputTerminal.getTerminalOutput().get(0);
|
||||
}
|
||||
|
||||
private ExitStatus execute(Command cmd, Settings settings) throws Exception {
|
||||
Environment env = new Environment(settings);
|
||||
return cmd.execute(settings, env);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.licensor.tools;
|
||||
|
||||
import org.apache.commons.cli.MissingOptionException;
|
||||
import org.elasticsearch.common.cli.CliTool.Command;
|
||||
import org.elasticsearch.common.cli.CliTool.ExitStatus;
|
||||
import org.elasticsearch.common.cli.CliToolTestCase;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.licensor.TestUtils;
|
||||
import org.elasticsearch.license.licensor.tools.LicenseVerificationTool.LicenseVerifier;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
||||
public class LicenseVerificationToolTests extends CliToolTestCase {
|
||||
protected Path pubKeyPath = null;
|
||||
protected Path priKeyPath = null;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
logger.error("project.basedir [{}]", System.getProperty("project.basedir"));
|
||||
pubKeyPath = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
||||
priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
|
||||
}
|
||||
|
||||
public void testParsingMissingLicense() throws Exception {
|
||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
||||
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, new String[] { "--publicKeyPath", path.toString() });
|
||||
|
||||
assertThat(command, instanceOf(Command.Exit.class));
|
||||
Command.Exit exitCommand = (Command.Exit) command;
|
||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
public void testParsingMissingPublicKeyPath() throws Exception {
|
||||
License inputLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
|
||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
||||
try {
|
||||
licenseVerificationTool.parse(LicenseVerificationTool.NAME,
|
||||
new String[] { "--license", TestUtils.dumpLicense(inputLicense) });
|
||||
} catch (MissingOptionException e) {
|
||||
assertThat(e.getMessage(), containsString("pub"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testParsingNonExistentPublicKeyPath() throws Exception {
|
||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
||||
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
|
||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
|
||||
new String[] { "--publicKeyPath", path.toString().concat(".invalid") });
|
||||
|
||||
assertThat(command, instanceOf(Command.Exit.class));
|
||||
Command.Exit exitCommand = (Command.Exit) command;
|
||||
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
|
||||
}
|
||||
|
||||
public void testParsingSimple() throws Exception {
|
||||
License inputLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
|
||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
|
||||
new String[] { "--license", TestUtils.dumpLicense(inputLicense),
|
||||
"--publicKeyPath", getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString() });
|
||||
assertThat(command, instanceOf(LicenseVerifier.class));
|
||||
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
|
||||
assertThat(inputLicense, equalTo(licenseVerifier.license));
|
||||
}
|
||||
|
||||
public void testParsingLicenseFile() throws Exception {
|
||||
License inputLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
|
||||
|
||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
|
||||
new String[]{"--licenseFile", dumpLicenseAsFile(inputLicense),
|
||||
"--publicKeyPath", getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString()});
|
||||
assertThat(command, instanceOf(LicenseVerifier.class));
|
||||
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
|
||||
assertThat(inputLicense, equalTo(licenseVerifier.license));
|
||||
|
||||
}
|
||||
|
||||
public void testParsingMultipleLicense() throws Exception {
|
||||
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
|
||||
List<String> arguments = new ArrayList<>();
|
||||
arguments.add("--license");
|
||||
arguments.add(TestUtils.dumpLicense(license));
|
||||
arguments.add("--publicKeyPath");
|
||||
arguments.add(getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString());
|
||||
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
|
||||
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, arguments.toArray(new String[arguments.size()]));
|
||||
|
||||
assertThat(command, instanceOf(LicenseVerifier.class));
|
||||
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
|
||||
assertThat(licenseVerifier.license, equalTo(license));
|
||||
}
|
||||
|
||||
public void testToolSimple() throws Exception {
|
||||
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
|
||||
String output = runLicenseVerificationTool(license, getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.OK);
|
||||
License outputLicense = License.fromSource(output.getBytes(StandardCharsets.UTF_8));
|
||||
assertThat(outputLicense, CoreMatchers.equalTo(license));
|
||||
}
|
||||
|
||||
public void testToolInvalidLicense() throws Exception {
|
||||
License signedLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
|
||||
|
||||
License tamperedLicense = License.builder()
|
||||
.fromLicenseSpec(signedLicense, signedLicense.signature())
|
||||
.expiryDate(signedLicense.expiryDate() + randomIntBetween(1, 1000)).build();
|
||||
|
||||
runLicenseVerificationTool(tamperedLicense, getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.DATA_ERROR);
|
||||
}
|
||||
|
||||
private String dumpLicenseAsFile(License license) throws Exception {
|
||||
Path tempFile = createTempFile();
|
||||
Files.write(tempFile, TestUtils.dumpLicense(license).getBytes(StandardCharsets.UTF_8));
|
||||
return tempFile.toAbsolutePath().toString();
|
||||
}
|
||||
|
||||
private String runLicenseVerificationTool(License license, Path publicKeyPath, ExitStatus expectedExitStatus) throws Exception {
|
||||
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
|
||||
Settings settings = Settings.builder().put("path.home", createTempDir("LicenseVerificationToolTests")).build();
|
||||
LicenseVerifier licenseVerifier = new LicenseVerifier(outputTerminal, license, publicKeyPath);
|
||||
assertThat(execute(licenseVerifier, settings), equalTo(expectedExitStatus));
|
||||
if (expectedExitStatus == ExitStatus.OK) {
|
||||
assertThat(outputTerminal.getTerminalOutput().size(), equalTo(1));
|
||||
|
||||
return outputTerminal.getTerminalOutput().get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ExitStatus execute(Command cmd, Settings settings) throws Exception {
|
||||
Environment env = new Environment(settings);
|
||||
return cmd.execute(settings, env);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
es.logger.level=INFO
|
||||
log4j.rootLogger=${es.logger.level}, out
|
||||
|
||||
log4j.logger.org.apache.http=INFO, out
|
||||
log4j.additivity.org.apache.http=false
|
||||
|
||||
log4j.logger.org.elasticsearch.license=TRACE
|
||||
|
||||
log4j.appender.out=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.out.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n
|
Binary file not shown.
|
@ -0,0 +1,3 @@
|
|||
ýŽÇqÝnęÄĚgŠśwM}Ťą‡UiKŠ•0âbÖ2Řşqö]â쇴ŻÖĎĂcĚ+IŇđÔ &IJ†fÉ~ßlj <09>ş]d™}o§OčľId®Č
|
||||
5A(ěµ´^ŘöW©DŤŞJµë}ů-Oîë?u N5ľŰvpŰ{’Ľ˛Áôśát–ť¤7ůřĂę#˛Vqöó»ktwm’Ś]ĎLőŁz"| Q‹lźňQđsâ>ů<}Ź[Á2ÖÓŕZÖ|5‹ŻŤĘĘ7%ŘęD
|
||||
Yĺ‘xn:ĽlúLČćHň˘«Ë2<C38B>źHvEEWÇ\¦H:“6Žh9 [!š…Űć©Š¤+;Ö.w7Cě©_|ŢÓŞĎÁ*ń§D`<60>Ú?‚ůxU/3>xUÓ“+ č
|
|
@ -0,0 +1 @@
|
|||
/eclipse-build/
|
|
@ -0,0 +1,15 @@
|
|||
apply plugin: 'elasticsearch.build'
|
||||
|
||||
dependencies {
|
||||
compile project(':x-plugins:elasticsearch:license:base')
|
||||
compile "org.elasticsearch:elasticsearch:${version}"
|
||||
testCompile project(':x-plugins:elasticsearch:license:licensor')
|
||||
testCompile "org.elasticsearch:test-framework:${version}"
|
||||
}
|
||||
|
||||
dependencyLicenses.enabled = false
|
||||
|
||||
jar {
|
||||
baseName = 'license-plugin-api'
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* A supporting base class for injectable Licensee components.
|
||||
*/
|
||||
public abstract class AbstractLicenseeComponent<T extends AbstractLicenseeComponent<T>> extends AbstractLifecycleComponent<T> implements Licensee {
|
||||
|
||||
private final String id;
|
||||
private final LicenseeRegistry clientService;
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
// we initialize the licensee state to enabled with trial operation mode
|
||||
protected volatile Status status = Status.ENABLED;
|
||||
|
||||
protected AbstractLicenseeComponent(Settings settings, String id, LicenseeRegistry clientService) {
|
||||
super(settings);
|
||||
this.id = id;
|
||||
this.clientService = clientService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
clientService.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current status of this licensee (can never be null)
|
||||
*/
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void add(Listener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(Status status) {
|
||||
this.status = status;
|
||||
logger.trace("[{}] is running in [{}] mode", id(), status);
|
||||
for (Listener listener : listeners) {
|
||||
listener.onChange(status);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onChange(Status status);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
/**
|
||||
* States of a registered licensee
|
||||
* based on the current license
|
||||
*/
|
||||
public enum LicenseState {
|
||||
|
||||
/**
|
||||
* Active license is valid.
|
||||
*
|
||||
* When license expires
|
||||
* changes to {@link #GRACE_PERIOD}
|
||||
*/
|
||||
ENABLED,
|
||||
|
||||
/**
|
||||
* Active license expired
|
||||
* but grace period has not.
|
||||
*
|
||||
* When grace period expires
|
||||
* changes to {@link #DISABLED}.
|
||||
* When valid license is installed
|
||||
* changes back to {@link #ENABLED}
|
||||
*/
|
||||
GRACE_PERIOD,
|
||||
|
||||
/**
|
||||
* Grace period for active license
|
||||
* expired.
|
||||
*
|
||||
* When a valid license is installed
|
||||
* changes to {@link #ENABLED}, otherwise
|
||||
* remains unchanged
|
||||
*/
|
||||
DISABLED
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
||||
public class LicenseUtils {
|
||||
|
||||
public final static String EXPIRED_FEATURE_HEADER = "es.license.expired.feature";
|
||||
|
||||
/**
|
||||
* Exception to be thrown when a feature action requires a valid license, but license
|
||||
* has expired
|
||||
*
|
||||
* <code>feature</code> accessible through {@link #EXPIRED_FEATURE_HEADER} in the
|
||||
* exception's rest header
|
||||
*/
|
||||
public static ElasticsearchSecurityException newComplianceException(String feature) {
|
||||
ElasticsearchSecurityException e = new ElasticsearchSecurityException("current license is non-compliant for [{}]", RestStatus.UNAUTHORIZED, feature);
|
||||
e.addHeader(EXPIRED_FEATURE_HEADER, feature);
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given {@link ElasticsearchSecurityException} refers to a feature that
|
||||
* requires a valid license, but the license has expired.
|
||||
*/
|
||||
public static boolean isLicenseExpiredException(ElasticsearchSecurityException exception) {
|
||||
return (exception != null) && (exception.getHeader(EXPIRED_FEATURE_HEADER) != null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.license.core.License;
|
||||
import org.elasticsearch.license.core.License.OperationMode;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public interface Licensee {
|
||||
|
||||
/**
|
||||
* Unique id used to log expiry and
|
||||
* acknowledgment messages
|
||||
*/
|
||||
String id();
|
||||
|
||||
/**
|
||||
* Messages to be printed when
|
||||
* logging license expiry warnings
|
||||
*/
|
||||
String[] expirationMessages();
|
||||
|
||||
/**
|
||||
* Messages to be returned when
|
||||
* installing <code>newLicense</code>
|
||||
* when <code>oldLicense</code> is
|
||||
* active
|
||||
*/
|
||||
String[] acknowledgmentMessages(License currentLicense, License newLicense);
|
||||
|
||||
/**
|
||||
* Notifies when a new license is activated
|
||||
* or when a license state change has occurred
|
||||
*/
|
||||
void onChange(Status status);
|
||||
|
||||
class Status {
|
||||
|
||||
public static Status ENABLED = new Status(OperationMode.TRIAL, LicenseState.ENABLED);
|
||||
|
||||
private final OperationMode mode;
|
||||
private final LicenseState licenseState;
|
||||
|
||||
public Status(OperationMode mode, LicenseState licenseState) {
|
||||
this.mode = mode;
|
||||
this.licenseState = licenseState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operation mode of the license
|
||||
* responsible for the current <code>licenseState</code>
|
||||
*/
|
||||
public OperationMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a license is active, the state is
|
||||
* {@link LicenseState#ENABLED}, upon license expiry
|
||||
* the state changes to {@link LicenseState#GRACE_PERIOD}
|
||||
* and after the grace period has ended the state changes
|
||||
* to {@link LicenseState#DISABLED}
|
||||
*/
|
||||
public LicenseState getLicenseState() {
|
||||
return licenseState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mode == OperationMode.NONE) {
|
||||
return "disabled";
|
||||
}
|
||||
switch (licenseState) {
|
||||
case DISABLED:
|
||||
return "disabled " + mode.name().toLowerCase(Locale.ROOT);
|
||||
case GRACE_PERIOD:
|
||||
return mode.name().toLowerCase(Locale.ROOT) + " grace period";
|
||||
default:
|
||||
return mode.name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
public interface LicenseeRegistry {
|
||||
|
||||
/**
|
||||
* Registers a licensee for license notifications
|
||||
*/
|
||||
void register(Licensee licensee);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.license.core.License;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface LicensesManagerService {
|
||||
|
||||
/**
|
||||
* @return the id of registered licensees currently in <code>state</code>
|
||||
*/
|
||||
List<String> licenseesWithState(LicenseState state);
|
||||
|
||||
/**
|
||||
* @return the currently active license, or {@code null} if no license is currently installed
|
||||
*/
|
||||
License getLicense();
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.license.plugin.core;
|
||||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
public class LicenseUtilsTests extends ESTestCase {
|
||||
|
||||
public void testNewExpirationException() {
|
||||
for (String feature : Arrays.asList("feature", randomAsciiOfLength(5), null, "")) {
|
||||
ElasticsearchSecurityException exception = LicenseUtils.newComplianceException(feature);
|
||||
assertNotNull(exception);
|
||||
assertThat(exception.getHeaderKeys(), contains(LicenseUtils.EXPIRED_FEATURE_HEADER));
|
||||
assertThat(exception.getHeader(LicenseUtils.EXPIRED_FEATURE_HEADER), hasSize(1));
|
||||
assertThat(exception.getHeader(LicenseUtils.EXPIRED_FEATURE_HEADER).iterator().next(), equalTo(feature));
|
||||
}
|
||||
}
|
||||
|
||||
public void testIsLicenseExpiredException() {
|
||||
ElasticsearchSecurityException exception = LicenseUtils.newComplianceException("feature");
|
||||
assertTrue(LicenseUtils.isLicenseExpiredException(exception));
|
||||
|
||||
exception = new ElasticsearchSecurityException("msg");
|
||||
assertFalse(LicenseUtils.isLicenseExpiredException(exception));
|
||||
}
|
||||
}
|
|
@ -7,9 +7,7 @@
|
|||
apply plugin: 'elasticsearch.standalone-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:license:plugin', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:watcher', configuration: 'testArtifacts')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'testArtifacts')
|
||||
testCompile project(path: ':modules:lang-groovy', configuration: 'runtime')
|
||||
}
|
||||
|
|
@ -1,18 +1,16 @@
|
|||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
}
|
||||
|
||||
integTest {
|
||||
cluster {
|
||||
plugin 'license', project(':x-plugins:license:plugin')
|
||||
plugin 'shield', project(':x-plugins:shield')
|
||||
// TODO: these should be settings?
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
systemProperty 'es.shield.audit.enabled', 'true'
|
||||
systemProperty 'es.shield.audit.outputs', 'index'
|
||||
setupCommand 'setupDummyUser',
|
||||
'bin/shield/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
waitCondition = { node, ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
ant.get(src: "http://localhost:${node.httpPort()}",
|
|
@ -9,7 +9,7 @@ import org.elasticsearch.action.search.SearchResponse;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
@ -56,6 +56,6 @@ public class IndexAuditIT extends ESIntegTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.<Class<? extends Plugin>>singleton(ShieldPlugin.class);
|
||||
return Collections.<Class<? extends Plugin>>singleton(XPackPlugin.class);
|
||||
}
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
}
|
||||
|
||||
integTest {
|
||||
cluster {
|
||||
plugin 'license', project(':x-plugins:license:plugin')
|
||||
plugin 'shield', project(':x-plugins:shield')
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
setupCommand 'setupDummyUser',
|
||||
'bin/shield/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
setupCommand 'setupTransportClientUser',
|
||||
'bin/shield/esusers', 'useradd', 'transport', '-p', 'changeme', '-r', 'transport_client'
|
||||
'bin/x-pack/esusers', 'useradd', 'transport', '-p', 'changeme', '-r', 'transport_client'
|
||||
waitCondition = { node, ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
ant.get(src: "http://localhost:${node.httpPort()}",
|
|
@ -13,7 +13,7 @@ import org.elasticsearch.client.transport.TransportClient;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
|
||||
|
@ -41,7 +41,7 @@ public class ShieldTransportClientIT extends ESIntegTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.singletonList(ShieldPlugin.class);
|
||||
return Collections.singletonList(XPackPlugin.class);
|
||||
}
|
||||
|
||||
public void testThatTransportClientWithoutAuthenticationDoesNotWork() throws Exception {
|
||||
|
@ -111,6 +111,6 @@ public class ShieldTransportClientIT extends ESIntegTestCase {
|
|||
.put("cluster.name", clusterName)
|
||||
.build();
|
||||
|
||||
return TransportClient.builder().settings(settings).addPlugin(ShieldPlugin.class).build().addTransportAddress(publishAddress);
|
||||
return TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build().addTransportAddress(publishAddress);
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
}
|
||||
|
||||
integTest {
|
||||
includePackaged true
|
||||
systemProperty 'tests.rest.blacklist',
|
||||
systemProperty 'tests.rest.blacklist',
|
||||
['indices.get/10_basic/*allow_no_indices*',
|
||||
'cat.count/10_basic/Test cat count output',
|
||||
'cat.aliases/10_basic/Empty cluster',
|
||||
|
@ -30,10 +30,11 @@ integTest {
|
|||
'bulk/40_fields/Fields'].join(',')
|
||||
|
||||
cluster {
|
||||
plugin 'license', project(':x-plugins:license:plugin')
|
||||
plugin 'shield', project(':x-plugins:shield')
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
systemProperty 'es.watcher.enabled', 'false'
|
||||
systemProperty 'es.marvel.enabled', 'false'
|
||||
setupCommand 'setupDummyUser',
|
||||
'bin/shield/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
waitCondition = { node, ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
ant.get(src: "http://localhost:${node.httpPort()}",
|
|
@ -11,6 +11,7 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
|||
import org.elasticsearch.client.support.Headers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.test.rest.RestTestCandidate;
|
||||
|
@ -53,7 +54,7 @@ public class RestIT extends ESRestTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.<Class<? extends Plugin>>singleton(ShieldPlugin.class);
|
||||
return Collections.<Class<? extends Plugin>>singleton(XPackPlugin.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,8 +7,7 @@ esplugin {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
provided project(path: ':x-plugins:license:plugin', configuration: 'runtime')
|
||||
provided project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
provided project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
}
|
||||
|
||||
compileJava.options.compilerArgs << "-Xlint:-rawtypes"
|
||||
|
@ -16,8 +15,7 @@ compileJava.options.compilerArgs << "-Xlint:-rawtypes"
|
|||
|
||||
integTest {
|
||||
cluster {
|
||||
plugin 'license', project(':x-plugins:license:plugin')
|
||||
plugin 'shield', project(':x-plugins:shield')
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
// TODO: these should be settings?
|
||||
systemProperty 'es.shield.authc.realms.custom.order', '0'
|
||||
systemProperty 'es.shield.authc.realms.custom.type', 'custom'
|
||||
|
@ -25,7 +23,7 @@ integTest {
|
|||
systemProperty 'es.shield.authc.realms.esusers.type', 'esusers'
|
||||
|
||||
setupCommand 'setupDummyUser',
|
||||
'bin/shield/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
waitCondition = { node, ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
ant.get(src: "http://localhost:${node.httpPort()}",
|
|
@ -14,7 +14,7 @@ import org.elasticsearch.client.transport.TransportClient;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.transport.TransportAddress;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
import org.elasticsearch.test.rest.client.http.HttpResponse;
|
||||
|
||||
|
@ -37,7 +37,7 @@ public class CustomRealmIT extends ESIntegTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.<Class<? extends Plugin>>singleton(ShieldPlugin.class);
|
||||
return Collections.<Class<? extends Plugin>>singleton(XPackPlugin.class);
|
||||
}
|
||||
|
||||
public void testHttpConnectionWithNoAuthentication() throws Exception {
|
||||
|
@ -67,7 +67,7 @@ public class CustomRealmIT extends ESIntegTestCase {
|
|||
.put(Headers.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER)
|
||||
.put(Headers.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW)
|
||||
.build();
|
||||
try (TransportClient client = TransportClient.builder().settings(settings).addPlugin(ShieldPlugin.class).build()) {
|
||||
try (TransportClient client = TransportClient.builder().settings(settings).addPlugin(XPackPlugin.class).build()) {
|
||||
client.addTransportAddress(publishAddress);
|
||||
ClusterHealthResponse response = client.admin().cluster().prepareHealth().execute().actionGet();
|
||||
assertThat(response.isTimedOut(), is(false));
|
||||
|
@ -86,7 +86,7 @@ public class CustomRealmIT extends ESIntegTestCase {
|
|||
.put(Headers.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER + randomAsciiOfLength(1))
|
||||
.put(Headers.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW)
|
||||
.build();
|
||||
try (TransportClient client = TransportClient.builder().addPlugin(ShieldPlugin.class).settings(settings).build()) {
|
||||
try (TransportClient client = TransportClient.builder().addPlugin(XPackPlugin.class).settings(settings).build()) {
|
||||
client.addTransportAddress(publishAddress);
|
||||
client.admin().cluster().prepareHealth().execute().actionGet();
|
||||
fail("authentication failure should have resulted in a NoNodesAvailableException");
|
|
@ -11,7 +11,7 @@ import org.elasticsearch.common.Strings;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.index.IndexNotFoundException;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.test.ESIntegTestCase;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
|
@ -37,7 +37,7 @@ public class MarvelClusterInfoIT extends ESIntegTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.singletonList(ShieldPlugin.class);
|
||||
return Collections.singletonList(XPackPlugin.class);
|
||||
}
|
||||
|
||||
public void testMarvelClusterInfoCollectorWorks() throws Exception {
|
|
@ -15,7 +15,7 @@ import org.elasticsearch.client.support.Headers;
|
|||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
|
@ -90,7 +90,7 @@ public class WatcherWithShieldIT extends ESRestTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.<Class<? extends Plugin>>singleton(ShieldPlugin.class);
|
||||
return Collections.<Class<? extends Plugin>>singleton(XPackPlugin.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
import org.elasticsearch.gradle.MavenFilteringHack
|
||||
|
||||
import org.elasticsearch.gradle.LoggedExec
|
||||
import org.elasticsearch.gradle.MavenFilteringHack
|
||||
|
||||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
}
|
||||
|
||||
// location of keystore and files to generate it
|
||||
|
@ -49,17 +51,14 @@ integTest {
|
|||
systemProperty 'es.shield.http.ssl', 'true'
|
||||
systemProperty 'es.shield.ssl.keystore.path', keystore.name
|
||||
systemProperty 'es.shield.ssl.keystore.password', 'keypass'
|
||||
plugin 'licence', project(':x-plugins:license:plugin')
|
||||
plugin 'shield', project(':x-plugins:shield')
|
||||
plugin 'watcher', project(':x-plugins:watcher')
|
||||
plugin 'marvel-agent', project(':x-plugins:marvel')
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
|
||||
// copy keystore into config/
|
||||
extraConfigFile keystore.name, keystore
|
||||
setupCommand 'setupTestUser',
|
||||
'bin/shield/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
setupCommand 'setupMarvelUser',
|
||||
'bin/shield/esusers', 'useradd', 'marvel_export', '-p', 'changeme', '-r', 'marvel_agent'
|
||||
'bin/x-pack/esusers', 'useradd', 'marvel_export', '-p', 'changeme', '-r', 'marvel_agent'
|
||||
waitCondition = { node, ant ->
|
||||
// we just return true, doing an https check is tricky here
|
||||
return true
|
|
@ -19,7 +19,7 @@ import org.elasticsearch.client.support.Headers;
|
|||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.test.rest.RestTestCandidate;
|
||||
|
@ -88,7 +88,7 @@ public class SmokeTestPluginsSslIT extends ESRestTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.<Class<? extends Plugin>>singleton(ShieldPlugin.class);
|
||||
return Collections.<Class<? extends Plugin>>singleton(XPackPlugin.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,7 +3,7 @@ import org.elasticsearch.gradle.MavenFilteringHack
|
|||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
}
|
||||
|
||||
ext.pluginsCount = 4 // we install xplugins explicitly
|
||||
|
@ -15,13 +15,10 @@ project.rootProject.subprojects.findAll { it.path.startsWith(':plugins:') }.each
|
|||
|
||||
integTest {
|
||||
cluster {
|
||||
plugin 'licence', project(':x-plugins:license:plugin')
|
||||
plugin 'shield', project(':x-plugins:shield')
|
||||
plugin 'watcher', project(':x-plugins:watcher')
|
||||
plugin 'marvel-agent', project(':x-plugins:marvel')
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
|
||||
setupCommand 'setupDummyUser',
|
||||
'bin/shield/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
|
||||
waitCondition = { node, ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
ant.get(src: "http://localhost:${node.httpPort()}",
|
|
@ -11,7 +11,7 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
|||
import org.elasticsearch.client.support.Headers;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.test.rest.RestTestCandidate;
|
||||
|
@ -54,7 +54,7 @@ public class SmokeTestPluginsIT extends ESRestTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.<Class<? extends Plugin>>singleton(ShieldPlugin.class);
|
||||
return Collections.<Class<? extends Plugin>>singleton(XPackPlugin.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:watcher', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
testCompile project(path: ':modules:lang-groovy', configuration: 'runtime')
|
||||
}
|
||||
|
||||
integTest {
|
||||
cluster {
|
||||
plugin 'license', project(':x-plugins:license:plugin')
|
||||
plugin 'watcher', project(':x-plugins:watcher')
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
systemProperty 'es.script.inline', 'on'
|
||||
systemProperty 'es.shield.enabled', 'false'
|
||||
}
|
||||
}
|
|
@ -13,14 +13,14 @@ import org.apache.http.impl.client.HttpClients;
|
|||
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
|
||||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.plugin.LicensePlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.node.Node;
|
||||
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
|
||||
import org.elasticsearch.test.junit.annotations.TestLogging;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.test.rest.RestTestCandidate;
|
||||
import org.elasticsearch.test.rest.parser.RestTestParseException;
|
||||
import org.elasticsearch.watcher.WatcherPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
dependencies {
|
||||
testCompile project(path: ':x-plugins:shield', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:watcher', configuration: 'runtime')
|
||||
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
|
||||
}
|
||||
|
||||
// bring in watcher rest test suite
|
||||
task copyWatcherRestTests(type: Copy) {
|
||||
into project.sourceSets.test.output.resourcesDir
|
||||
from project(':x-plugins:watcher').sourceSets.test.resources.srcDirs
|
||||
include 'rest-api-spec/test/**'
|
||||
from project(':x-plugins:elasticsearch:x-pack').sourceSets.test.resources.srcDirs
|
||||
include 'rest-api-spec/test/watcher/**'
|
||||
}
|
||||
|
||||
integTest {
|
||||
|
@ -19,16 +18,14 @@ integTest {
|
|||
'array_compare_watch/10_basic/Basic array_compare watch'].join(',')
|
||||
|
||||
cluster {
|
||||
plugin 'license', project(':x-plugins:license:plugin')
|
||||
plugin 'shield', project(':x-plugins:shield')
|
||||
plugin 'watcher', project(':x-plugins:watcher')
|
||||
extraConfigFile 'shield/roles.yml', 'roles.yml'
|
||||
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
|
||||
extraConfigFile 'x-pack/roles.yml', 'roles.yml'
|
||||
setupCommand 'setupTestAdminUser',
|
||||
'bin/shield/esusers', 'useradd', 'test_admin', '-p', 'changeme', '-r', 'admin'
|
||||
'bin/x-pack/esusers', 'useradd', 'test_admin', '-p', 'changeme', '-r', 'admin'
|
||||
setupCommand 'setupWatcherManagerUser',
|
||||
'bin/shield/esusers', 'useradd', 'watcher_manager', '-p', 'changeme', '-r', 'watcher_manager'
|
||||
'bin/x-pack/esusers', 'useradd', 'watcher_manager', '-p', 'changeme', '-r', 'watcher_manager'
|
||||
setupCommand 'setupPowerlessUser',
|
||||
'bin/shield/esusers', 'useradd', 'powerless_user', '-p', 'changeme', '-r', 'crapy_role'
|
||||
'bin/x-pack/esusers', 'useradd', 'powerless_user', '-p', 'changeme', '-r', 'crapy_role'
|
||||
waitCondition = { node, ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
ant.get(src: "http://localhost:${node.httpPort()}",
|
|
@ -15,7 +15,7 @@ import org.elasticsearch.client.support.Headers;
|
|||
import org.elasticsearch.common.network.NetworkAddress;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.shield.ShieldPlugin;
|
||||
import org.elasticsearch.xpack.XPackPlugin;
|
||||
import org.elasticsearch.shield.authc.support.SecuredString;
|
||||
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
|
@ -90,7 +90,7 @@ public class WatcherWithShieldIT extends ESRestTestCase {
|
|||
|
||||
@Override
|
||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
||||
return Collections.<Class<? extends Plugin>>singleton(ShieldPlugin.class);
|
||||
return Collections.<Class<? extends Plugin>>singleton(XPackPlugin.class);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue