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