Merge branch 'master' into shield-kibana-auth

Original commit: elastic/x-pack-elasticsearch@fa6658c1e4
This commit is contained in:
Lukas Olson 2015-12-07 08:36:16 -07:00
commit a4670211e5
1285 changed files with 13069 additions and 16442 deletions

9
GRADLE.CHEATSHEET Normal file
View File

@ -0,0 +1,9 @@
As a quick helper, below are the equivalent commands from maven to gradle. You can also run `gradle tasks` to see all tasks that are available to run.
| Maven | Gradle | Description |
| ----------------------------| ------------|---------------------|
| `clean` | `clean` | |
| `verify` | `check` | |
| `verify -Dskip.unit.tests` | `integTest` | |
| `package -DskipTests` | `assemble` | |
| `install -DskipTests` | `install` | |

View File

@ -7,12 +7,6 @@ A set of Elastic's commercial plugins:
- Watcher
- Marvel
= Testing with Elasticsearch
Sometimes it is useful to use your local elasticsearch checkout with x-plugins. To do this, run the following commands:
cd buildSrc
gradle attach --name elasticsearch --path /path/to/elasticsearch/buildSrc
cd ..
gradle attach --name elasticsearch --path /path/to/elasticsearch
This will cause building x-plugins to reflect any changes in your elasticsearch repo. For example, if you make a change to elasticsearch core, building x-plugins will first re-build elasticsearch core, and use that when building x-plugins.
= Setup
You must checkout x-plugins within an elasticsearch checkout. It must be
called x-plugins, and must be inside the extra-plugins directory.

View File

@ -1,37 +1,8 @@
import org.elasticsearch.gradle.ElasticsearchProperties
allprojects {
apply plugin: 'idea'
apply plugin: 'eclipse'
}
subprojects {
project.group = 'org.elasticsearch'
project.version = ElasticsearchProperties.version
project.ext.luceneVersion = ElasticsearchProperties.luceneVersion
repositories {
mavenCentral()
maven {
name 'sonatype-snapshots'
url 'http://oss.sonatype.org/content/repositories/snapshots/'
}
if (luceneVersion.contains('-snapshot')) {
String revision = (luceneVersion =~ /\d\.\d\.\d-snapshot-(\d+)/)[0][1]
maven {
name 'lucene-snapshots'
url "http://s3.amazonaws.com/download.elasticsearch.org/lucenesnapshots/${revision}"
}
}
}
}
if (hasProperty('projectsPrefix') == false) {
allprojects {
project.ext['projectsPrefix'] = ''
}
if (project.projectDir.name != 'x-plugins') {
throw new GradleException('You must checkout x-plugins in a directory named x-plugins next to elasticsearch')
}
/*
<repository>
<id>elasticsearch-releases</id>
<url>http://maven.elasticsearch.org/releases</url>
@ -71,22 +42,3 @@ subprojects {
}
}
*/
// ================= Local Elasticsearch attachment ===============
if (hasProperty('attachments') && 'elasticsearch' in attachments) {
subprojects {
configurations {
all {
resolutionStrategy {
dependencySubstitution {
substitute module("org.elasticsearch:rest-api-spec:${version}") with project(":elasticsearch:rest-api-spec")
substitute module("org.elasticsearch:elasticsearch:${version}") with project(":elasticsearch:core")
substitute module("org.elasticsearch:test-framework:${version}") with project(":elasticsearch:test-framework")
substitute module("org.elasticsearch.distribution.zip:elasticsearch:${version}") with project(":elasticsearch:distribution:zip")
substitute module("org.elasticsearch.distribution.tar:elasticsearch:${version}") with project(":elasticsearch:distribution:tar")
}
}
}
}
}
}

View File

@ -1,41 +0,0 @@
apply plugin: 'groovy'
buildscript {
repositories {
maven {
name 'sonatype-snapshots'
url 'http://oss.sonatype.org/content/repositories/snapshots/'
}
}
dependencies {
classpath 'org.elasticsearch.gradle:project-attachment-plugin:1.0.0-SNAPSHOT'
}
}
apply plugin: 'elasticsearch.project-attachment'
repositories {
mavenCentral()
maven {
name 'sonatype-snapshots'
url "https://oss.sonatype.org/content/repositories/snapshots/"
}
jcenter()
}
dependencies {
runtime 'org.elasticsearch.gradle:project-attachment-plugin:1.0.0-SNAPSHOT'
runtime 'org.elasticsearch.gradle:build-tools:3.0.0-SNAPSHOT'
}
if ('elasticsearch' in attachments) {
configurations {
all {
resolutionStrategy {
dependencySubstitution {
substitute module('org.elasticsearch.gradle:build-tools') with project(':elasticsearch')
}
}
}
}
}

View File

@ -1,13 +1,6 @@
buildscript {
repositories {
maven {
name 'sonatype-snapshots'
url 'http://oss.sonatype.org/content/repositories/snapshots/'
}
}
dependencies {
classpath 'org.elasticsearch.gradle:project-attachment-plugin:1.0.0-SNAPSHOT'
}
File elasticsearchDir = new File(settingsDir, '../../elasticsearch')
if (elasticsearchDir.exists() == false) {
throw new GradleException('Elasticsearch must be checked out as a sibling directory of x-plugins')
}
apply plugin: 'elasticsearch.project-settings-attachment'
project(':').projectDir = new File(elasticsearchDir, 'buildSrc')

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

@ -0,0 +1,18 @@
/*
* Messy tests that depend on groovy directly. Fix these!
* https://github.com/elastic/x-plugins/issues/724
*/
apply plugin: 'elasticsearch.standalone-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'testArtifacts')
testCompile project(path: ':modules:lang-groovy', configuration: 'runtime')
}
// TODO: remove this, its because gradle does not bring in plugin-metadata for lang-groovy
// into the test classpath: if it did, then things will work
test {
systemProperty 'tests.security.manager', 'false'
}

View File

@ -0,0 +1,176 @@
/*
* 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.messy.tests;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.util.Callback;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.watcher.client.WatcherClient;
import org.elasticsearch.watcher.support.xcontent.ObjectPath;
import org.elasticsearch.watcher.support.xcontent.XContentSource;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchResponse;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.watcher.actions.ActionBuilders.loggingAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.condition.ConditionBuilders.scriptCondition;
import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.transform.TransformBuilders.scriptTransform;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.cron;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
*/
public class ExecutionVarsIntegrationTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
return types;
}
@Override
protected boolean timeWarped() {
return true;
}
public void testVars() throws Exception {
WatcherClient watcherClient = watcherClient();
PutWatchResponse putWatchResponse = watcherClient.preparePutWatch("_id").setSource(watchBuilder()
.trigger(schedule(cron("0/1 * * * * ?")))
.input(simpleInput("value", 5))
.condition(scriptCondition("ctx.vars.condition_value = ctx.payload.value + 5; return ctx.vars.condition_value > 5;"))
.transform(scriptTransform("ctx.vars.watch_transform_value = ctx.vars.condition_value + 5; return ctx.payload;"))
.addAction(
"a1",
scriptTransform("ctx.vars.a1_transform_value = ctx.vars.watch_transform_value + 10; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a1_transform_value={{ctx.vars.a1_transform_value}}"))
.addAction(
"a2",
scriptTransform("ctx.vars.a2_transform_value = ctx.vars.watch_transform_value + 20; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a2_transform_value={{ctx.vars.a2_transform_value}}")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
timeWarp().scheduler().trigger("_id");
flush();
refresh();
SearchResponse searchResponse = searchWatchRecords(new Callback<SearchRequestBuilder>() {
@Override
public void handle(SearchRequestBuilder builder) {
// defaults to match all;
}
});
assertThat(searchResponse.getHits().getTotalHits(), is(1L));
Map<String, Object> source = searchResponse.getHits().getAt(0).getSource();
assertValue(source, "watch_id", is("_id"));
assertValue(source, "state", is("executed"));
// we don't store the computed vars in history
assertValue(source, "vars", nullValue());
assertValue(source, "result.condition.status", is("success"));
assertValue(source, "result.transform.status", is("success"));
List<Map<String, Object>> actions = ObjectPath.eval("result.actions", source);
for (Map<String, Object> action : actions) {
String id = (String) action.get("id");
switch (id) {
case "a1":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a1_transform_value=25"));
break;
case "a2":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a2_transform_value=35"));
break;
default:
fail("there should not be an action result for action with an id other than a1 or a2");
}
}
}
public void testVarsManual() throws Exception {
WatcherClient watcherClient = watcherClient();
PutWatchResponse putWatchResponse = watcherClient.preparePutWatch("_id").setSource(watchBuilder()
.trigger(schedule(cron("0/1 * * * * ? 2020")))
.input(simpleInput("value", 5))
.condition(scriptCondition("ctx.vars.condition_value = ctx.payload.value + 5; return ctx.vars.condition_value > 5;"))
.transform(scriptTransform("ctx.vars.watch_transform_value = ctx.vars.condition_value + 5; return ctx.payload;"))
.addAction(
"a1",
scriptTransform("ctx.vars.a1_transform_value = ctx.vars.watch_transform_value + 10; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a1_transform_value={{ctx.vars.a1_transform_value}}"))
.addAction(
"a2",
scriptTransform("ctx.vars.a2_transform_value = ctx.vars.watch_transform_value + 20; return ctx.payload;"),
loggingAction("condition_value={{ctx.vars.condition_value}}, watch_transform_value={{ctx.vars.watch_transform_value}}, a2_transform_value={{ctx.vars.a2_transform_value}}")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
boolean debug = randomBoolean();
ExecuteWatchResponse executeWatchResponse = watcherClient
.prepareExecuteWatch("_id")
.setDebug(debug)
.get();
assertThat(executeWatchResponse.getRecordId(), notNullValue());
XContentSource source = executeWatchResponse.getRecordSource();
assertValue(source, "watch_id", is("_id"));
assertValue(source, "state", is("executed"));
if (debug) {
assertValue(source, "vars.condition_value", is(10));
assertValue(source, "vars.watch_transform_value", is(15));
assertValue(source, "vars.a1_transform_value", is(25));
assertValue(source, "vars.a2_transform_value", is(35));
}
assertValue(source, "result.condition.status", is("success"));
assertValue(source, "result.transform.status", is("success"));
List<Map<String, Object>> actions = source.getValue("result.actions");
for (Map<String, Object> action : actions) {
String id = (String) action.get("id");
switch (id) {
case "a1":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a1_transform_value=25"));
break;
case "a2":
assertValue(action, "status", is("success"));
assertValue(action, "transform.status", is("success"));
assertValue(action, "logging.logged_text", is("condition_value=10, watch_transform_value=15, a2_transform_value=35"));
break;
default:
fail("there should not be an action result for action with an id other than a1 or a2");
}
}
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.messy.tests;
import static org.elasticsearch.watcher.actions.ActionBuilders.loggingAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.cron;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.watcher.client.WatchSourceBuilder;
import org.elasticsearch.watcher.condition.script.ScriptCondition;
import org.elasticsearch.watcher.execution.ManualExecutionContext;
import org.elasticsearch.watcher.execution.ManualExecutionTests.ExecutionRunner;
import org.elasticsearch.watcher.history.WatchRecord;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.transport.actions.delete.DeleteWatchResponse;
import org.elasticsearch.watcher.transport.actions.get.GetWatchRequest;
import org.elasticsearch.watcher.transport.actions.put.PutWatchRequest;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import org.elasticsearch.watcher.trigger.manual.ManualTriggerEvent;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Watch;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
/**
* Two groovy-using methods from ManualExecutionTests.
* They appear to be using groovy as a way to sleep.
*/
public class GroovyManualExecutionTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
return types;
}
@Override
protected boolean enableShield() {
return false;
}
public void testWatchExecutionDuration() throws Exception {
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(new ScriptCondition((new Script.Builder.Inline("sleep 100; return true")).build()))
.addAction("log", loggingAction("foobar"));
Watch watch = watchParser().parse("_id", false, watchBuilder.buildAsBytes(XContentType.JSON));
ManualExecutionContext.Builder ctxBuilder = ManualExecutionContext.builder(watch, false, new ManualTriggerEvent("_id", new ScheduleTriggerEvent(new DateTime(DateTimeZone.UTC), new DateTime(DateTimeZone.UTC))), new TimeValue(1, TimeUnit.HOURS));
WatchRecord record = executionService().execute(ctxBuilder.build());
assertThat(record.result().executionDurationMs(), greaterThanOrEqualTo(100L));
}
public void testForceDeletionOfLongRunningWatch() throws Exception {
WatchSourceBuilder watchBuilder = watchBuilder()
.trigger(schedule(cron("0 0 0 1 * ? 2099")))
.input(simpleInput("foo", "bar"))
.condition(new ScriptCondition((new Script.Builder.Inline("sleep 10000; return true")).build()))
.defaultThrottlePeriod(new TimeValue(1, TimeUnit.HOURS))
.addAction("log", loggingAction("foobar"));
int numberOfThreads = scaledRandomIntBetween(1, 5);
PutWatchResponse putWatchResponse = watcherClient().putWatch(new PutWatchRequest("_id", watchBuilder)).actionGet();
assertThat(putWatchResponse.getVersion(), greaterThan(0L));
refresh();
assertThat(watcherClient().getWatch(new GetWatchRequest("_id")).actionGet().isFound(), equalTo(true));
CountDownLatch startLatch = new CountDownLatch(1);
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < numberOfThreads; ++i) {
threads.add(new Thread(new ExecutionRunner(watchService(), executionService(), "_id", startLatch)));
}
for (Thread thread : threads) {
thread.start();
}
DeleteWatchResponse deleteWatchResponse = watcherClient().prepareDeleteWatch("_id").setForce(true).get();
assertThat(deleteWatchResponse.isFound(), is(true));
deleteWatchResponse = watcherClient().prepareDeleteWatch("_id").get();
assertThat(deleteWatchResponse.isFound(), is(false));
startLatch.countDown();
long startJoin = System.currentTimeMillis();
for (Thread thread : threads) {
thread.join();
}
long endJoin = System.currentTimeMillis();
TimeValue tv = new TimeValue(10 * (numberOfThreads+1), TimeUnit.SECONDS);
assertThat("Shouldn't take longer than [" + tv.getSeconds() + "] seconds for all the threads to stop", (endJoin - startJoin), lessThan(tv.getMillis()));
}
}

View File

@ -0,0 +1,119 @@
/*
* 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.messy.tests;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.watcher.execution.ExecutionState;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.common.xcontent.support.XContentMapValues.extractValue;
import static org.elasticsearch.watcher.actions.ActionBuilders.loggingAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.condition.ConditionBuilders.alwaysCondition;
import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.transform.TransformBuilders.scriptTransform;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.interval;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
/**
* This test makes sure that the http host and path fields in the watch_record action result are
* not analyzed so they can be used in aggregations
*/
public class HistoryTemplateTransformMappingsTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
return types;
}
@Override
protected boolean timeWarped() {
return true; // just to have better control over the triggers
}
@Override
protected boolean enableShield() {
return false; // remove shield noise from this test
}
public void testTransformFields() throws Exception {
String index = "the-index";
String type = "the-type";
createIndex(index);
index(index, type, "{}");
flush();
refresh();
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id1").setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(simpleInput())
.condition(alwaysCondition())
.transform(scriptTransform("return [ 'key' : 'value1' ];"))
.addAction("logger", scriptTransform("return [ 'key' : 'value2' ];"), loggingAction("indexed")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
timeWarp().scheduler().trigger("_id1");
// adding another watch which with a transform that should conflict with the preview watch. Since the
// mapping for the transform construct is disabled, there should be nor problems.
putWatchResponse = watcherClient().preparePutWatch("_id2").setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(simpleInput())
.condition(alwaysCondition())
.transform(scriptTransform("return [ 'key' : [ 'key1' : 'value1' ] ];"))
.addAction("logger", scriptTransform("return [ 'key' : [ 'key1' : 'value2' ] ];"), loggingAction("indexed")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
timeWarp().scheduler().trigger("_id2");
flush();
refresh();
assertWatchWithMinimumActionsCount("_id1", ExecutionState.EXECUTED, 1);
assertWatchWithMinimumActionsCount("_id2", ExecutionState.EXECUTED, 1);
refresh();
assertBusy(new Runnable() {
@Override
public void run() {
GetMappingsResponse mappingsResponse = client().admin().indices().prepareGetMappings().get();
assertThat(mappingsResponse, notNullValue());
assertThat(mappingsResponse.getMappings().isEmpty(), is(false));
for (ObjectObjectCursor<String, ImmutableOpenMap<String, MappingMetaData>> metadatas : mappingsResponse.getMappings()) {
if (!metadatas.key.startsWith(".watch_history")) {
continue;
}
MappingMetaData metadata = metadatas.value.get("watch_record");
assertThat(metadata, notNullValue());
try {
Map<String, Object> source = metadata.getSourceAsMap();
logger.info("checking index [{}] with metadata:\n[{}]", metadatas.key, metadata.source().toString());
assertThat(extractValue("properties.result.properties.transform.properties.payload.enabled", source), is((Object) false));
assertThat(extractValue("properties.result.properties.actions.properties.transform.properties.payload.enabled", source), is((Object) false));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
});
}
}

View File

@ -0,0 +1,215 @@
/*
* 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.messy.tests;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.watcher.history.HistoryStore;
import org.elasticsearch.watcher.support.WatcherDateTimeUtils;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.transport.actions.execute.ExecuteWatchResponse;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import java.util.List;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.search.aggregations.AggregationBuilders.dateHistogram;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.watcher.actions.ActionBuilders.indexAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.input.InputBuilders.searchInput;
import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.transform.TransformBuilders.scriptTransform;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.cron;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.is;
/**
*
*/
public class IndexActionIntegrationTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
return types;
}
public void testSimple() throws Exception {
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder()
.trigger(schedule(cron("0/1 * * * * ? 2020")))
.input(simpleInput("foo", "bar"))
.addAction("index-buckets", indexAction("idx", "type").setExecutionTimeField("@timestamp")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC);
ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id")
.setTriggerEvent(new ScheduleTriggerEvent(now, now))
.get();
assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed"));
flush("idx");
refresh();
SearchResponse searchResponse = client().prepareSearch("idx").setTypes("type").get();
assertThat(searchResponse.getHits().totalHits(), is(1L));
SearchHit hit = searchResponse.getHits().getAt(0);
if (timeWarped()) {
assertThat(hit.getSource(), hasEntry("@timestamp", (Object) WatcherDateTimeUtils.formatDate(now)));
} else {
assertThat(hit.getSource(), hasKey("@timestamp"));
DateTime timestamp = WatcherDateTimeUtils.parseDate((String) hit.getSource().get("@timestamp"));
assertThat(timestamp.isEqual(now) || timestamp.isAfter(now), is(true));
}
assertThat(hit.getSource(), hasEntry("foo", (Object) "bar"));
}
public void testSimpleWithDocField() throws Exception {
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder()
.trigger(schedule(cron("0/1 * * * * ? 2020")))
.input(simpleInput("foo", "bar"))
.addAction("index-buckets",
scriptTransform("return [ '_doc' : ctx.payload ]"),
indexAction("idx", "type").setExecutionTimeField("@timestamp")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC);
ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id")
.setTriggerEvent(new ScheduleTriggerEvent(now, now))
.get();
assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed"));
flush("idx");
refresh();
SearchResponse searchResponse = client().prepareSearch("idx").setTypes("type").get();
assertThat(searchResponse.getHits().totalHits(), is(1L));
SearchHit hit = searchResponse.getHits().getAt(0);
if (timeWarped()) {
assertThat(hit.getSource(), hasEntry("@timestamp", (Object) WatcherDateTimeUtils.formatDate(now)));
} else {
assertThat(hit.getSource(), hasKey("@timestamp"));
DateTime timestamp = WatcherDateTimeUtils.parseDate((String) hit.getSource().get("@timestamp"));
assertThat(timestamp.isEqual(now) || timestamp.isAfter(now), is(true));
}
assertThat(hit.getSource(), hasEntry("foo", (Object) "bar"));
}
public void testSimpleWithDocFieldWrongFieldType() throws Exception {
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder()
.trigger(schedule(cron("0/1 * * * * ? 2020")))
.input(simpleInput("foo", "bar"))
.addAction("index-buckets",
scriptTransform("return [ '_doc' : 1 ]"),
indexAction("idx", "type").setExecutionTimeField("@timestamp")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC);
ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id")
.setTriggerEvent(new ScheduleTriggerEvent(now, now))
.setRecordExecution(true)
.get();
assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed"));
flush();
refresh();
assertThat(client().admin().indices().prepareExists("idx").get().isExists(), is(false));
assertThat(docCount(HistoryStore.INDEX_PREFIX + "*", HistoryStore.DOC_TYPE, searchSource()
.query(matchQuery("result.actions.status", "failure"))), is(1L));
}
public void testIndexAggsBucketsAsDocuments() throws Exception {
DateTime now = timeWarped() ? timeWarp().clock().now(DateTimeZone.UTC) : DateTime.now(DateTimeZone.UTC);
long bucketCount = randomIntBetween(2, 5);
for (int i = 0; i < bucketCount; i++) {
index("idx", "type", jsonBuilder().startObject()
.field("timestamp", now.minusDays(i))
.endObject());
}
flush("idx");
refresh();
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id").setSource(watchBuilder()
.trigger(schedule(cron("0/1 * * * * ? 2020")))
.input(searchInput(new SearchRequest("idx")
.types("type")
.searchType(SearchType.QUERY_THEN_FETCH)
.source(searchSource()
.aggregation(dateHistogram("trend")
.field("timestamp")
.interval(DateHistogramInterval.DAY)))))
.addAction("index-buckets",
// this transform takes the bucket list and assigns it to `_doc`
// this means each bucket will be indexed as a separate doc,
// so we expect to have the same number of documents as the number
// of buckets.
scriptTransform("return [ '_doc' : ctx.payload.aggregations.trend.buckets]"),
indexAction("idx", "bucket").setExecutionTimeField("@timestamp")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
ExecuteWatchResponse executeWatchResponse = watcherClient().prepareExecuteWatch("_id")
.setTriggerEvent(new ScheduleTriggerEvent(now, now))
.get();
assertThat(executeWatchResponse.getRecordSource().getValue("state"), is((Object) "executed"));
flush("idx");
refresh();
SearchResponse searchResponse = client().prepareSearch("idx").setTypes("bucket")
.addSort("key", SortOrder.DESC)
.get();
assertThat(searchResponse.getHits().getTotalHits(), is(bucketCount));
DateTime key = now.withMillisOfDay(0);
int i = 0;
for (SearchHit hit : searchResponse.getHits()) {
if (timeWarped()) {
assertThat(hit.getSource(), hasEntry("@timestamp", (Object) WatcherDateTimeUtils.formatDate(now)));
} else {
assertThat(hit.getSource(), hasKey("@timestamp"));
DateTime timestamp = WatcherDateTimeUtils.parseDate((String) hit.getSource().get("@timestamp"));
assertThat(timestamp.isEqual(now) || timestamp.isAfter(now), is(true));
}
assertThat(hit.getSource(), hasEntry("key", (Object) key.getMillis()));
key = key.minusDays(1);
}
}
}

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.messy.tests;
import org.apache.lucene.util.LuceneTestCase;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.script.ScriptContextRegistry;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.groovy.GroovyScriptEngineService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.text.xmustache.XMustacheScriptEngineService;
import org.junit.Ignore;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Ignore // not a test.
@SuppressForbidden(reason = "gradle is broken and tries to run me as a test")
public final class MessyTestUtils {
public static ScriptServiceProxy getScriptServiceProxy(ThreadPool tp) throws Exception {
Settings settings = Settings.settingsBuilder()
.put("script.inline", "on")
.put("script.indexed", "on")
.put("path.home", LuceneTestCase.createTempDir())
.build();
XMustacheScriptEngineService mustacheScriptEngineService = new XMustacheScriptEngineService(settings);
GroovyScriptEngineService groovyScriptEngineService = new GroovyScriptEngineService(settings);
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
engineServiceSet.add(mustacheScriptEngineService);
engineServiceSet.add(groovyScriptEngineService);
ScriptContextRegistry registry = new ScriptContextRegistry(Arrays.asList(ScriptServiceProxy.INSTANCE));
return ScriptServiceProxy.of(new ScriptService(settings, new Environment(settings), engineServiceSet, new ResourceWatcherService(settings, tp), registry));
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.messy.tests;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.common.text.StringText;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.search.SearchShardTarget;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.internal.InternalSearchHit;
import org.elasticsearch.search.internal.InternalSearchHits;
import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.condition.script.ExecutableScriptCondition;
import org.elasticsearch.watcher.condition.script.ScriptCondition;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.watch.Payload;
import org.junit.After;
import org.junit.Before;
import java.util.List;
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.when;
/**
*/
public class ScriptConditionSearchTests extends AbstractWatcherIntegrationTestCase {
private ThreadPool tp = null;
private ScriptServiceProxy scriptService;
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
return types;
}
@Before
public void init() throws Exception {
tp = new ThreadPool(ThreadPool.Names.SAME);
scriptService = MessyTestUtils.getScriptServiceProxy(tp);
}
@After
public void cleanup() {
tp.shutdownNow();
}
public void testExecuteWithAggs() throws Exception {
client().admin().indices().prepareCreate("my-index")
.addMapping("my-type", "_timestamp", "enabled=true")
.get();
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:00").setSource("{}").get();
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:10").setSource("{}").get();
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:20").setSource("{}").get();
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:30").setSource("{}").get();
refresh();
SearchResponse response = client().prepareSearch("my-index")
.addAggregation(AggregationBuilders.dateHistogram("rate").field("_timestamp").interval(DateHistogramInterval.HOUR).order(Histogram.Order.COUNT_DESC))
.get();
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("ctx.payload.aggregations.rate.buckets[0]?.doc_count >= 5").build()), logger, scriptService);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
assertFalse(condition.execute(ctx).met());
client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:40").setSource("{}").get();
refresh();
response = client().prepareSearch("my-index")
.addAggregation(AggregationBuilders.dateHistogram("rate").field("_timestamp").interval(DateHistogramInterval.HOUR).order(Histogram.Order.COUNT_DESC))
.get();
ctx = mockExecutionContext("_name", new Payload.XContent(response));
assertThat(condition.execute(ctx).met(), is(true));
}
public void testExecuteAccessHits() throws Exception {
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("ctx.payload.hits?.hits[0]?._score == 1.0").build()), logger, scriptService);
InternalSearchHit hit = new InternalSearchHit(0, "1", new StringText("type"), null);
hit.score(1f);
hit.shard(new SearchShardTarget("a", "a", 0));
InternalSearchResponse internalSearchResponse = new InternalSearchResponse(new InternalSearchHits(new InternalSearchHit[]{hit}, 1l, 1f), null, null, false, false);
SearchResponse response = new SearchResponse(internalSearchResponse, "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_watch_name", new Payload.XContent(response));
assertThat(condition.execute(ctx).met(), is(true));
hit.score(2f);
when(ctx.payload()).thenReturn(new Payload.XContent(response));
assertThat(condition.execute(ctx).met(), is(false));
}
}

View File

@ -0,0 +1,221 @@
/*
* 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.messy.tests;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.ScriptService.ScriptType;
import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.condition.Condition;
import org.elasticsearch.watcher.condition.script.ExecutableScriptCondition;
import org.elasticsearch.watcher.condition.script.ScriptCondition;
import org.elasticsearch.watcher.condition.script.ScriptConditionFactory;
import org.elasticsearch.watcher.execution.WatchExecutionContext;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.watch.Payload;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.After;
import org.junit.Before;
import java.io.IOException;
import static java.util.Collections.singletonMap;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.watcher.support.Exceptions.illegalArgument;
import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.elasticsearch.messy.tests.MessyTestUtils.getScriptServiceProxy;
/**
*/
public class ScriptConditionTests extends ESTestCase {
ThreadPool tp = null;
@Before
public void init() {
tp = new ThreadPool(ThreadPool.Names.SAME);
}
@After
public void cleanup() {
tp.shutdownNow();
}
public void testExecute() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("ctx.payload.hits.total > 1").build()), logger, scriptService);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
assertFalse(condition.execute(ctx).met());
}
public void testExecuteMergedParams() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
Script script = Script.inline("ctx.payload.hits.total > threshold").lang(ScriptService.DEFAULT_LANG).params(singletonMap("threshold", 1)).build();
ExecutableScriptCondition executable = new ExecutableScriptCondition(new ScriptCondition(script), logger, scriptService);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
assertFalse(executable.execute(ctx).met());
}
public void testParserValid() throws Exception {
ScriptConditionFactory factory = new ScriptConditionFactory(Settings.settingsBuilder().build(), getScriptServiceProxy(tp));
XContentBuilder builder = createConditionContent("ctx.payload.hits.total > 1", null, ScriptType.INLINE);
XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
parser.nextToken();
ScriptCondition condition = factory.parseCondition("_watch", parser);
ExecutableScriptCondition executable = factory.createExecutable(condition);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
assertFalse(executable.execute(ctx).met());
builder = createConditionContent("return true", null, ScriptType.INLINE);
parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
parser.nextToken();
condition = factory.parseCondition("_watch", parser);
executable = factory.createExecutable(condition);
ctx = mockExecutionContext("_name", new Payload.XContent(response));
assertTrue(executable.execute(ctx).met());
}
public void testParserInvalid() throws Exception {
ScriptConditionFactory factory = new ScriptConditionFactory(Settings.settingsBuilder().build(), getScriptServiceProxy(tp));
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.startObject().endObject();
XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
parser.nextToken();
try {
factory.parseCondition("_id", parser);
fail("expected a condition exception trying to parse an invalid condition XContent");
} catch (ElasticsearchParseException e) {
// TODO add these when the test if fixed
// assertThat(e.getMessage(), is("ASDF"));
}
}
public void testScriptConditionParserBadScript() throws Exception {
ScriptConditionFactory conditionParser = new ScriptConditionFactory(Settings.settingsBuilder().build(), getScriptServiceProxy(tp));
ScriptType scriptType = randomFrom(ScriptType.values());
String script;
switch (scriptType) {
case INDEXED:
case FILE:
script = "nonExisting_script";
break;
case INLINE:
default:
script = "foo = = 1";
}
XContentBuilder builder = createConditionContent(script, "groovy", scriptType);
XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
parser.nextToken();
ScriptCondition scriptCondition = conditionParser.parseCondition("_watch", parser);
try {
conditionParser.createExecutable(scriptCondition);
fail("expected a condition validation exception trying to create an executable with a bad or missing script");
} catch (ScriptException e) {
// TODO add these when the test if fixed
// assertThat(e.getMessage(), is("ASDF"));
}
}
public void testScriptConditionParser_badLang() throws Exception {
ScriptConditionFactory conditionParser = new ScriptConditionFactory(Settings.settingsBuilder().build(), getScriptServiceProxy(tp));
ScriptType scriptType = ScriptType.INLINE;
String script = "return true";
XContentBuilder builder = createConditionContent(script, "not_a_valid_lang", scriptType);
XContentParser parser = XContentFactory.xContent(builder.bytes()).createParser(builder.bytes());
parser.nextToken();
ScriptCondition scriptCondition = conditionParser.parseCondition("_watch", parser);
try {
conditionParser.createExecutable(scriptCondition);
fail("expected a condition validation exception trying to create an executable with an invalid language");
} catch (ScriptException e) {
// TODO add these when the test if fixed
// assertThat(e.getMessage(), is("ASDF"));
}
}
public void testScriptConditionThrowException() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("assert false").build()), logger, scriptService);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
ScriptCondition.Result result = condition.execute(ctx);
assertThat(result, notNullValue());
assertThat(result.status(), is(Condition.Result.Status.FAILURE));
assertThat(result.reason(), notNullValue());
assertThat(result.reason(), containsString("Assertion"));
}
public void testScriptConditionReturnObject() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("return new Object()").build()), logger, scriptService);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response));
ScriptCondition.Result result = condition.execute(ctx);
assertThat(result, notNullValue());
assertThat(result.status(), is(Condition.Result.Status.FAILURE));
assertThat(result.reason(), notNullValue());
assertThat(result.reason(), containsString("ScriptException"));
}
public void testScriptConditionAccessCtx() throws Exception {
ScriptServiceProxy scriptService = getScriptServiceProxy(tp);
ExecutableScriptCondition condition = new ExecutableScriptCondition(new ScriptCondition(Script.inline("ctx.trigger.scheduled_time.getMillis() < new Date().time ").build()), logger, scriptService);
SearchResponse response = new SearchResponse(InternalSearchResponse.empty(), "", 3, 3, 500l, new ShardSearchFailure[0]);
WatchExecutionContext ctx = mockExecutionContext("_name", new DateTime(DateTimeZone.UTC), new Payload.XContent(response));
Thread.sleep(10);
assertThat(condition.execute(ctx).met(), is(true));
}
private static XContentBuilder createConditionContent(String script, String scriptLang, ScriptType scriptType) throws IOException {
XContentBuilder builder = jsonBuilder();
if (scriptType == null) {
return builder.value(script);
}
builder.startObject();
switch (scriptType) {
case INLINE:
builder.field("inline", script);
break;
case FILE:
builder.field("file", script);
break;
case INDEXED:
builder.field("id", script);
break;
default:
throw illegalArgument("unsupported script type [{}]", scriptType);
}
if (scriptLang != null) {
builder.field("lang", scriptLang);
}
return builder.endObject();
}
}

View File

@ -0,0 +1,224 @@
/*
* 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.messy.tests;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.script.groovy.GroovyPlugin;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTestCase;
import org.elasticsearch.watcher.test.WatcherTestUtils;
import org.elasticsearch.watcher.transport.actions.put.PutWatchResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.elasticsearch.watcher.actions.ActionBuilders.indexAction;
import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder;
import static org.elasticsearch.watcher.condition.ConditionBuilders.alwaysCondition;
import static org.elasticsearch.watcher.input.InputBuilders.searchInput;
import static org.elasticsearch.watcher.input.InputBuilders.simpleInput;
import static org.elasticsearch.watcher.transform.TransformBuilders.chainTransform;
import static org.elasticsearch.watcher.transform.TransformBuilders.scriptTransform;
import static org.elasticsearch.watcher.transform.TransformBuilders.searchTransform;
import static org.elasticsearch.watcher.trigger.TriggerBuilders.schedule;
import static org.elasticsearch.watcher.trigger.schedule.Schedules.interval;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
/**
*/
public class TransformIntegrationTests extends AbstractWatcherIntegrationTestCase {
@Override
protected List<Class<? extends Plugin>> pluginTypes() {
List<Class<? extends Plugin>> types = super.pluginTypes();
types.add(GroovyPlugin.class);
return types;
}
@Override
public Settings nodeSettings(int nodeOrdinal) {
Path configDir = createTempDir();
Path scripts = configDir.resolve("scripts");
try {
Files.createDirectories(scripts);
try (InputStream stream = TransformIntegrationTests.class.getResourceAsStream("/config/scripts/my-script.groovy");
OutputStream output = Files.newOutputStream(scripts.resolve("my-script.groovy"))) {
Streams.copy(stream, output);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
//Set path so ScriptService will pick up the test scripts
return settingsBuilder().put(super.nodeSettings(nodeOrdinal)).put("path.conf", configDir.toString()).build();
}
public void testScriptTransform() throws Exception {
final Script script;
if (randomBoolean()) {
logger.info("testing script transform with an inline script");
script = Script.inline("return [key3 : ctx.payload.key1 + ctx.payload.key2]").lang("groovy").build();
} else if (randomBoolean()) {
logger.info("testing script transform with an indexed script");
client().preparePutIndexedScript("groovy", "_id", "{\"script\" : \"return [key3 : ctx.payload.key1 + ctx.payload.key2]\"}").get();
script = Script.indexed("_id").lang("groovy").build();
} else {
logger.info("testing script transform with a file script");
script = Script.file("my-script").lang("groovy").build();
}
// put a watch that has watch level transform:
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id1")
.setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(simpleInput(MapBuilder.<String, Object>newMapBuilder().put("key1", 10).put("key2", 10)))
.condition(alwaysCondition())
.transform(scriptTransform(script))
.addAction("_id", indexAction("output1", "type")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
// put a watch that has a action level transform:
putWatchResponse = watcherClient().preparePutWatch("_id2")
.setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(simpleInput(MapBuilder.<String, Object>newMapBuilder().put("key1", 10).put("key2", 10)))
.condition(alwaysCondition())
.addAction("_id", scriptTransform(script), indexAction("output2", "type")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
if (timeWarped()) {
timeWarp().scheduler().trigger("_id1");
timeWarp().scheduler().trigger("_id2");
refresh();
}
assertWatchWithMinimumPerformedActionsCount("_id1", 1, false);
assertWatchWithMinimumPerformedActionsCount("_id2", 1, false);
refresh();
SearchResponse response = client().prepareSearch("output1").get();
assertNoFailures(response);
assertThat(response.getHits().getTotalHits(), greaterThanOrEqualTo(1l));
assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(1));
assertThat(response.getHits().getAt(0).sourceAsMap().get("key3").toString(), equalTo("20"));
response = client().prepareSearch("output2").get();
assertNoFailures(response);
assertThat(response.getHits().getTotalHits(), greaterThanOrEqualTo(1l));
assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(1));
assertThat(response.getHits().getAt(0).sourceAsMap().get("key3").toString(), equalTo("20"));
}
public void testSearchTransform() throws Exception {
createIndex("my-condition-index", "my-payload-index");
ensureGreen("my-condition-index", "my-payload-index");
index("my-payload-index", "payload", "mytestresult");
refresh();
SearchRequest inputRequest = WatcherTestUtils.newInputSearchRequest("my-condition-index").source(searchSource().query(matchAllQuery()));
SearchRequest transformRequest = WatcherTestUtils.newInputSearchRequest("my-payload-index").source(searchSource().query(matchAllQuery()));
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id1")
.setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(searchInput(inputRequest))
.transform(searchTransform(transformRequest))
.addAction("_id", indexAction("output1", "result"))
).get();
assertThat(putWatchResponse.isCreated(), is(true));
putWatchResponse = watcherClient().preparePutWatch("_id2")
.setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(searchInput(inputRequest))
.addAction("_id", searchTransform(transformRequest), indexAction("output2", "result"))
).get();
assertThat(putWatchResponse.isCreated(), is(true));
if (timeWarped()) {
timeWarp().scheduler().trigger("_id1");
timeWarp().scheduler().trigger("_id2");
refresh();
}
assertWatchWithMinimumPerformedActionsCount("_id1", 1, false);
assertWatchWithMinimumPerformedActionsCount("_id2", 1, false);
refresh();
SearchResponse response = client().prepareSearch("output1").get();
assertNoFailures(response);
assertThat(response.getHits().getTotalHits(), greaterThanOrEqualTo(1l));
assertThat(response.getHits().getAt(0).sourceAsString(), containsString("mytestresult"));
response = client().prepareSearch("output2").get();
assertNoFailures(response);
assertThat(response.getHits().getTotalHits(), greaterThanOrEqualTo(1l));
assertThat(response.getHits().getAt(0).sourceAsString(), containsString("mytestresult"));
}
public void testChainTransform() throws Exception {
final Script script1 = Script.inline("return [key3 : ctx.payload.key1 + ctx.payload.key2]").lang("groovy").build();
final Script script2 = Script.inline("return [key4 : ctx.payload.key3 + 10]").lang("groovy").build();
// put a watch that has watch level transform:
PutWatchResponse putWatchResponse = watcherClient().preparePutWatch("_id1")
.setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(simpleInput(MapBuilder.<String, Object>newMapBuilder().put("key1", 10).put("key2", 10)))
.condition(alwaysCondition())
.transform(chainTransform(scriptTransform(script1), scriptTransform(script2)))
.addAction("_id", indexAction("output1", "type")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
// put a watch that has a action level transform:
putWatchResponse = watcherClient().preparePutWatch("_id2")
.setSource(watchBuilder()
.trigger(schedule(interval("5s")))
.input(simpleInput(MapBuilder.<String, Object>newMapBuilder().put("key1", 10).put("key2", 10)))
.condition(alwaysCondition())
.addAction("_id", chainTransform(scriptTransform(script1), scriptTransform(script2)), indexAction("output2", "type")))
.get();
assertThat(putWatchResponse.isCreated(), is(true));
if (timeWarped()) {
timeWarp().scheduler().trigger("_id1");
timeWarp().scheduler().trigger("_id2");
refresh();
}
assertWatchWithMinimumPerformedActionsCount("_id1", 1, false);
assertWatchWithMinimumPerformedActionsCount("_id2", 1, false);
refresh();
SearchResponse response = client().prepareSearch("output1").get();
assertNoFailures(response);
assertThat(response.getHits().getTotalHits(), greaterThanOrEqualTo(1l));
assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(1));
assertThat(response.getHits().getAt(0).sourceAsMap().get("key4").toString(), equalTo("30"));
response = client().prepareSearch("output2").get();
assertNoFailures(response);
assertThat(response.getHits().getTotalHits(), greaterThanOrEqualTo(1l));
assertThat(response.getHits().getAt(0).sourceAsMap().size(), equalTo(1));
assertThat(response.getHits().getAt(0).sourceAsMap().get("key4").toString(), equalTo("30"));
}
}

View File

@ -0,0 +1,26 @@
apply plugin: 'elasticsearch.rest-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
}
integTest {
cluster {
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
systemProperty 'es.shield.audit.enabled', 'true'
systemProperty 'es.shield.audit.outputs', 'index'
setupCommand 'setupDummyUser',
'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()}",
dest: tmpFile.toString(),
username: 'test_user',
password: 'changeme',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
}

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;
@ -42,10 +42,9 @@ public class IndexAuditIT extends ESIntegTestCase {
assertThat(found, is(true));
SearchResponse searchResponse = client().prepareSearch(".shield_audit_log*").setQuery(QueryBuilders.matchQuery("principal", USER)).addField("principal").get();
SearchResponse searchResponse = client().prepareSearch(".shield_audit_log*").setQuery(QueryBuilders.matchQuery("principal", USER)).get();
assertThat(searchResponse.getHits().getHits().length, greaterThan(0));
assertThat((String) searchResponse.getHits().getAt(0).field("principal").getValue(), is(USER));
assertThat((String) searchResponse.getHits().getAt(0).sourceAsMap().get("principal"), is(USER));
}
@Override
@ -57,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

@ -0,0 +1,26 @@
apply plugin: 'elasticsearch.rest-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
}
integTest {
cluster {
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
setupCommand 'setupDummyUser',
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
setupCommand 'setupTransportClientUser',
'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()}",
dest: tmpFile.toString(),
username: 'test_user',
password: 'changeme',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
}

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

@ -0,0 +1,50 @@
apply plugin: 'elasticsearch.rest-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
}
integTest {
includePackaged true
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',
'indices.segments/10_basic/no segments test',
'indices.clear_cache/10_basic/clear_cache test',
'indices.status/10_basic/Indices status test',
'cat.indices/10_basic/Test cat indices output',
'cat.recovery/10_basic/Test cat recovery output',
'cat.shards/10_basic/Test cat shards output',
'termvector/20_issue7121/*',
'index/10_with_id/Index with ID',
'indices.get_alias/20_emtpy/*',
'cat.segments/10_basic/Test cat segments output',
'indices.put_settings/10_basic/Test indices settings allow_no_indices',
'indices.put_settings/10_basic/Test indices settings ignore_unavailable',
'indices.refresh/10_basic/Indices refresh test no-match wildcard',
'indices.stats/10_index/Index - star*',
'indices.recovery/10_basic/Indices recovery test*',
'indices.shard_stores/10_basic/no indices test',
'cat.nodeattrs/10_basic/Test cat nodes attrs output',
'bulk/40_fields/Fields'].join(',')
cluster {
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
systemProperty 'es.watcher.enabled', 'false'
systemProperty 'es.marvel.enabled', 'false'
setupCommand 'setupDummyUser',
'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()}",
dest: tmpFile.toString(),
username: 'test_user',
password: 'changeme',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
}

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

@ -0,0 +1,39 @@
apply plugin: 'elasticsearch.esplugin'
esplugin {
description 'a very basic implementation of a custom realm to validate it works'
classname 'org.elasticsearch.example.ExampleRealmPlugin'
isolated false
}
dependencies {
provided project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
}
compileJava.options.compilerArgs << "-Xlint:-rawtypes"
//compileTestJava.options.compilerArgs << "-Xlint:-rawtypes"
integTest {
cluster {
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'
systemProperty 'es.shield.authc.realms.esusers.order', '1'
systemProperty 'es.shield.authc.realms.esusers.type', 'esusers'
setupCommand 'setupDummyUser',
'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()}",
dest: tmpFile.toString(),
username: 'test_user',
password: 'changeme',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
}

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

@ -0,0 +1,79 @@
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:elasticsearch:x-pack', configuration: 'runtime')
}
// location of keystore and files to generate it
File keystore = new File(project.buildDir, 'keystore/test-node.jks')
// generate the keystore
task createKey(type: LoggedExec) {
doFirst {
project.delete(keystore.parentFile)
keystore.parentFile.mkdirs()
}
executable = 'keytool'
standardInput = new ByteArrayInputStream('FirstName LastName\nUnit\nOrganization\nCity\nState\nNL\nyes\n\n'.getBytes('UTF-8'))
args '-genkey',
'-alias', 'test-node',
'-keystore', keystore,
'-keyalg', 'RSA',
'-keysize', '2048',
'-validity', '712',
'-ext', 'san=dns:localhost,ip:127.0.0.1',
'-storepass', 'keypass'
}
// add keystore to test classpath: it expects it there
sourceSets.test.resources.srcDir(keystore.parentFile)
processTestResources.dependsOn(createKey)
ext.pluginsCount = 1 // we install xpack explicitly
project.rootProject.subprojects.findAll { it.path.startsWith(':plugins:') }.each { subproj ->
// need to get a non-decorated project object, so must re-lookup the project by path
integTest.cluster.plugin(subproj.name, project(subproj.path))
pluginsCount += 1
}
integTest {
cluster {
// TODO: use some variable here for port number
systemProperty 'es.marvel.agent.exporter.es.hosts', 'https://marvel_export:changeme@localhost:9400'
systemProperty 'es.marvel.agent.exporter.es.ssl.truststore.path', keystore.name
systemProperty 'es.marvel.agent.exporter.es.ssl.truststore.password', 'keypass'
systemProperty 'es.shield.transport.ssl', 'true'
systemProperty 'es.shield.http.ssl', 'true'
systemProperty 'es.shield.ssl.keystore.path', keystore.name
systemProperty 'es.shield.ssl.keystore.password', 'keypass'
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
// copy keystore into config/
extraConfigFile keystore.name, keystore
setupCommand 'setupTestUser',
'bin/x-pack/esusers', 'useradd', 'test_user', '-p', 'changeme', '-r', 'admin'
setupCommand 'setupMarvelUser',
'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
}
}
}
ext.expansions = [
'expected.plugins.count': pluginsCount
]
processTestResources {
from(sourceSets.test.resources.srcDirs) {
include '**/*.yaml'
inputs.properties(expansions)
MavenFilteringHack.filter(it, expansions)
}
}

View File

@ -11,12 +11,15 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.client.support.Headers;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldPlugin;
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;
@ -85,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

@ -0,0 +1,13 @@
# Integration tests for smoke testing plugins
#
"Plugins are actually installed":
- do:
cluster.state: {}
# Get master node id
- set: { master_node: master }
- do:
nodes.info: {}
- length: { nodes.$master.plugins: ${expected.plugins.count} }

View File

@ -0,0 +1,43 @@
import org.elasticsearch.gradle.MavenFilteringHack
apply plugin: 'elasticsearch.rest-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
}
ext.pluginsCount = 1 // we install xpack explicitly
project.rootProject.subprojects.findAll { it.path.startsWith(':plugins:') }.each { subproj ->
// need to get a non-decorated project object, so must re-lookup the project by path
integTest.cluster.plugin(subproj.name, project(subproj.path))
pluginsCount += 1
}
integTest {
cluster {
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
setupCommand 'setupDummyUser',
'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()}",
dest: tmpFile.toString(),
username: 'test_user',
password: 'changeme',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
}
ext.expansions = [
'expected.plugins.count': pluginsCount
]
processTestResources {
inputs.properties(expansions)
MavenFilteringHack.filter(it, expansions)
}

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

@ -0,0 +1,14 @@
# Integration tests for smoke testing plugins
#
"Plugins are actually installed":
- do:
cluster.state: {}
# Get master node id
- set: { master_node: master }
- do:
nodes.info: {}
- length: { nodes.$master.plugins: ${expected.plugins.count} }
# TODO: check that every plugin is installed

View File

@ -0,0 +1,15 @@
apply plugin: 'elasticsearch.rest-test'
dependencies {
testCompile project(path: ':x-plugins:elasticsearch:x-pack', configuration: 'runtime')
testCompile project(path: ':modules:lang-groovy', configuration: 'runtime')
}
integTest {
cluster {
plugin 'x-pack', project(':x-plugins:elasticsearch:x-pack')
systemProperty 'es.script.inline', 'on'
systemProperty 'es.shield.enabled', 'false'
systemProperty 'es.marvel.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;

Some files were not shown because too many files have changed in this diff Show More