Merge branch 'master' into jigsaw

Original commit: elastic/x-pack-elasticsearch@c7534cfcf0
This commit is contained in:
Ryan Ernst 2015-12-04 10:50:18 -08:00
commit 2521e567f1
1252 changed files with 10508 additions and 14298 deletions

View File

@ -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)

View File

@ -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'
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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 &amp; 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;
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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

View File

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

View File

@ -0,0 +1,7 @@
subprojects {
project.afterEvaluate {
project.forbiddenPatterns {
exclude '**/*.key'
}
}
}

View File

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

View File

@ -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"

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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 "$@"

View File

@ -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 "$@"

View File

@ -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 "$@"

View File

@ -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

View File

@ -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>

View File

@ -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}]}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

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

View File

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

View File

@ -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'
}

View File

@ -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);
}
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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));
}
}

View File

@ -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')
}

View File

@ -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()}",

View File

@ -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);
}
}

View File

@ -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()}",

View File

@ -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);
}
}

View File

@ -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()}",

View File

@ -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);
}
}

View File

@ -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()}",

View File

@ -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");

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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()}",

View File

@ -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);
}
}

View File

@ -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'
}
}

View File

@ -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;

View File

@ -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()}",

View File

@ -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