Added keypair generator, license generator and licene verification tools to interface with internal license server

Original commit: elastic/x-pack-elasticsearch@96fc01391e
This commit is contained in:
Areek Zillur 2014-10-01 11:39:01 -04:00
parent 73b3dc5b44
commit 67d776f30a
20 changed files with 2549 additions and 0 deletions

165
pom.xml Normal file
View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch-license</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<elasticsearch.version>1.4.0-SNAPSHOT</elasticsearch.version>
</properties>
<dependencies>
<!-- test deps -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
<scope>test</scope>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- actual deps -->
<dependency>
<groupId>net.nicholaswilliams.java.licensing</groupId>
<artifactId>licensing-core</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>net.nicholaswilliams.java.licensing</groupId>
<artifactId>licensing-licensor-base</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.8.5</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>${elasticsearch.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>enforce-versions</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[1.7,)</version>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<fork>true</fork>
<maxmem>512m</maxmem>
<!-- REMOVE WHEN UPGRADE:
see https://jira.codehaus.org/browse/MCOMPILER-209 it's a bug where
incremental compilation doesn't work unless it's set to false causeing
recompilation of the entire codebase each time without any changes. Should
be fixed in version > 3.1
-->
<useIncrementalCompilation>false</useIncrementalCompilation>
<compilerArgs>
<arg>-XDignore.symbol.file</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- ============================================================= -->
<!-- Optional. This plugin is using surefire plugin to run tests. -->
<!-- ============================================================= -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
<!-- ============================================================= -->
<!-- This plugin assembles plugin .zip package. -->
<!-- ============================================================= -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.3</version>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>${project.build.directory}/releases/</outputDirectory>
<descriptors>
<descriptor>${basedir}/src/main/assemblies/plugin.xml</descriptor>
</descriptors>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>attached</goal>
</goals>
</execution>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>org.elasticsearch.license.licensor.tools.LicenseGeneratorTool</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
-->
</plugins>
</build>
</project>

13
sample/gen_license.json Normal file
View File

@ -0,0 +1,13 @@
{
"licenses" : [ {
"uid" : "d8bcf9e8-bcb0-4f72-81ca-8a7537a436c5",
"type" : "internal",
"subscription_type" : "none",
"issued_to" : "issuedTo",
"issue_date" : "2014-09-29",
"expiry_date" : "2015-08-29",
"feature" : "shield",
"max_nodes" : 1,
"signature" : "naPgicfKM2+IJ0AoYgAAAG0AAAAAVGdIQ01qZUtCeEZNbS8wcTF4RU5mYUpiY01hdFlQNEVkdFJhYitoZndrSTI5eVZrY3ZRZ3lYU0s1QWdYb0Y5d1dBQmRUK01leE1aR0RUOHhoRVVhVUE9PaztAAVzcgAxbmV0Lm5pY2hvbGFzd2lsbGlhbXMuamF2YS5saWNlbnNpbmcuU2lnbmVkTGljZW5zZYqE/59+smqEAgACWwAObGljZW5zZUNvbnRlbnR0AAJbQlsAEHNpZ25hdHVyZUNvbnRlbnRxAH4AAXhwdXIAAltCrPMX+AYIVOACAAB4cAAAAMCsH5r77/8FtWY+JxKd9MiBTYQLcXgmXMm+Y83VaNwmlr1lASJ2yf7rWojiuHTWemtUNtOZcXeSrLfs/oKwBzXIfvEZV8X/vPCWnpi7VtU4Hp+OZUFO4c0NQ1PnVdDk1uns16Dqe99/ota3FSvdFrmlzkz2E+2bbx0fwWbKnGDXFXy6eE7OISRJdCqa8gljMo9PA1+RI7MFQ8bSzs9up0cEkSuPzgtafFW5zfyn2vpoPZTxDpJslTBk7S3mdchE0eJ1cQB+AAMAAAEAdikZHpJVMxWMxNsksYnNOD7F+15SK3MCtUWJnQdhYCuVHdKQUE3YxWv59QQuDmKuLbnvi0DsuPGlq3hEx0AXmbpaBOhkwTv3DKZH7V6C0YmXj7RLZobaDTtGY2pwV6Qf5+teq5dV493a1k6YGFiwUoERuWQxqmA36naLdVo2diCSh8QmZ4ihKnhqxwswh2TlnCVuaNN3E7HuGeE0wYgFEfgISJOFlEOnLOItRlrQOTzCq+mhASKbANxx/Z42eMGrgs+GJsxYQZfnBh8K3NQFQk2SjWR1sEgqUPXC+0Z7ungzkkwoSBbrdJfRPKbqXFDthWI1DY9SSZnTbwpUC2XA6Q=="
} ]
}

14
sample/license_spec.json Normal file
View File

@ -0,0 +1,14 @@
{
"licenses": [
{
"type": "internal",
"subscription_type": "none",
"issued_to": "issuedTo",
"issuer": "issuer",
"issue_date": "2014-09-29",
"expiry_date": "2015-08-29",
"feature": "shield",
"max_nodes": 1
}
]
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<assembly>
<id>plugin</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<useTransitiveFiltering>true</useTransitiveFiltering>
<excludes>
<exclude>org.elasticsearch:elasticsearch</exclude>
</excludes>
</dependencySet>
</dependencySets>
</assembly>

View File

@ -0,0 +1,61 @@
/*
* 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 java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
public class DateUtils {
public static final DateFormat DATE_FORMAT;
public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
static {
DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
DATE_FORMAT.setTimeZone(DateUtils.TIME_ZONE);
DATE_FORMAT.setLenient(false);
}
public static long longExpiryDateFromDate(long date) {
Date dateObj = new Date(date);
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.setTimeZone(TIME_ZONE);
calendar.setTimeInMillis(dateObj.getTime());
calendar.set(Calendar.HOUR, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
return calendar.getTimeInMillis();
}
public static long longFromDateString(String dateStr) throws ParseException {
Date dateObj = DATE_FORMAT.parse(dateStr);
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.setTimeZone(TIME_ZONE);
calendar.setTimeInMillis(dateObj.getTime());
return calendar.getTimeInMillis();
}
public static long longExpiryDateFromString(String dateStr) throws ParseException {
return longExpiryDateFromDate(longFromDateString(dateStr));
}
public static String dateStringFromLongDate(long date) {
Date dateObj = new Date(date);
Calendar calendar = Calendar.getInstance();
calendar.clear();
calendar.setTimeZone(TIME_ZONE);
calendar.setTimeInMillis(dateObj.getTime());
return DATE_FORMAT.format(calendar.getTime());
}
}

View File

@ -0,0 +1,251 @@
/*
* 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 java.util.Collection;
import java.util.Set;
/**
* Interface for ESLicenses, ESLicense
* and enums for Type, SubscriptionType and FeatureType.
* <p/>
* This is the main contract between the licensor and the license manager
*/
public interface ESLicenses extends Iterable<ESLicenses.ESLicense> {
/**
* @return list of licenses contained under this instance
*/
public Collection<ESLicense> licenses();
/**
* @return Set of features for which there exists an underlying license
*/
public Set<FeatureType> features();
/**
* @return a license for a code>featureType<</code>
*/
public ESLicense get(FeatureType featureType);
/**
* Enum for License Type
*/
public enum Type {
TRIAL((byte) 0, "trial"),
SUBSCRIPTION((byte) 1, "subscription"),
INTERNAL((byte) 2, "internal");
private final byte id;
private final String name;
private Type(byte id, String name) {
this.id = id;
this.name = name;
}
public String string() {
return name;
}
public byte id() {
return id;
}
public static Type fromId(byte id) {
switch (id) {
case 0:
return TRIAL;
case 1:
return SUBSCRIPTION;
case 2:
return INTERNAL;
default:
throw new IllegalArgumentException("Invalid Type id=" + id);
}
}
public static Type fromString(String type) {
if (type.equalsIgnoreCase(TRIAL.string())) {
return TRIAL;
} else if (type.equalsIgnoreCase(SUBSCRIPTION.string())) {
return SUBSCRIPTION;
} else if (type.equalsIgnoreCase(INTERNAL.string())) {
return INTERNAL;
} else {
throw new IllegalArgumentException("Invalid Type=" + type);
}
}
}
/**
* Enum for License Subscription Type
*/
public enum SubscriptionType {
NONE((byte) 0, "none"),
DEVELOPMENT((byte) 1, "development"),
SILVER((byte) 2, "silver"),
GOLD((byte) 3, "gold"),
PLATINUM((byte) 4, "platinum");
public static SubscriptionType DEFAULT = NONE;
private final byte id;
private final String name;
private SubscriptionType(byte id, String name) {
this.id = id;
this.name = name;
}
public String string() {
return name;
}
public byte id() {
return id;
}
public static SubscriptionType fromId(byte id) {
switch (id) {
case 0:
return NONE;
case 1:
return DEVELOPMENT;
case 2:
return SILVER;
case 3:
return GOLD;
case 4:
return PLATINUM;
default:
throw new IllegalArgumentException("Invalid SubscriptionType id=" + id);
}
}
public static SubscriptionType fromString(String subscriptionType) {
if (subscriptionType.equalsIgnoreCase(NONE.string())) {
return NONE;
} else if (subscriptionType.equalsIgnoreCase(DEVELOPMENT.string())) {
return DEVELOPMENT;
} else if (subscriptionType.equalsIgnoreCase(SILVER.string())) {
return SILVER;
} else if (subscriptionType.equalsIgnoreCase(GOLD.string())) {
return GOLD;
} else if (subscriptionType.equalsIgnoreCase(PLATINUM.string())) {
return PLATINUM;
} else {
throw new IllegalArgumentException("Invalid SubscriptionType=" + subscriptionType);
}
}
}
/**
* Enum for License FeatureType
*/
public enum FeatureType {
SHIELD((byte) 0, "shield"),
MARVEL((byte) 1, "marvel");
private final byte id;
private final String name;
private FeatureType(byte id, String name) {
this.id = id;
this.name = name;
}
public String string() {
return name;
}
public byte id() {
return id;
}
public static FeatureType fromId(byte id) {
switch (id) {
case 0:
return SHIELD;
case 1:
return MARVEL;
default:
throw new IllegalArgumentException("Invalid FeatureType id=" + id);
}
}
public static FeatureType fromString(String featureType) {
if (featureType.equalsIgnoreCase(SHIELD.string())) {
return SHIELD;
} else if (featureType.equalsIgnoreCase(MARVEL.string())) {
return MARVEL;
} else {
throw new IllegalArgumentException("Invalid FeatureType=" + featureType);
}
}
}
/**
* Interface representing all the license fields
*/
public interface ESLicense {
/**
* @return a unique identifier for a license (currently just a UUID)
*/
public String uid();
/**
* @return type of the license [trial, subscription, internal]
*/
public Type type();
/**
* @return subscription type of the license [none, silver, gold, platinum]
*/
public SubscriptionType subscriptionType();
/**
* @return the issueDate in milliseconds
*/
public long issueDate();
/**
* @return the featureType for the license [shield, marvel]
*/
public FeatureType feature();
/**
* @return the expiry date in milliseconds
*/
public long expiryDate();
/**
* @return the maximum number of nodes this license has been issued for
*/
public int maxNodes();
/**
* @return a string representing the entity this licenses has been issued to
*/
public String issuedTo();
/**
* @return a string representing the entity responsible for issuing this license (internal)
*/
public String issuer();
/**
* @return a string representing the signature of the license used for license verification
*/
public String signature();
}
}

View File

@ -0,0 +1,308 @@
/*
* 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 java.util.*;
import static org.elasticsearch.license.core.ESLicenses.*;
public class LicenseBuilders {
/**
* @return a licenses builder instance to build a {@link org.elasticsearch.license.core.ESLicenses}
*/
public static LicensesBuilder licensesBuilder() {
return new LicensesBuilder();
}
/**
* @return a license builder instance to build a {@link org.elasticsearch.license.core.ESLicenses.ESLicense}
* if internal is set to true, then license fields (which are internal) are required to be set
*/
public static LicenseBuilder licenseBuilder(boolean internal) {
return new LicenseBuilder(internal);
}
/**
* Merges all the sub-licenses of the provided licenses parameters by
* longest expiry date for each license feature and merges out any
* sub-licenses that have already expired
*
* @return a merged <code>ESLicenses</code> instance from <code>licenses</code>
* and <code>mergedLicenses</code>
*/
public static ESLicenses merge(ESLicenses licenses, ESLicenses mergeLicenses) {
if (licenses == null && mergeLicenses == null) {
throw new IllegalArgumentException("both licenses can not be null");
} else if (licenses == null) {
return mergeLicenses;
} else if (mergeLicenses == null) {
return licenses;
} else {
return licensesBuilder()
.licenses(licenses)
.licenses(mergeLicenses)
.build();
}
}
public static class LicensesBuilder {
private Map<FeatureType, ESLicense> licenseMap;
public LicensesBuilder() {
}
public LicensesBuilder license(LicenseBuilder builder) {
return license(builder.build());
}
public LicensesBuilder license(ESLicense license) {
initLicenses();
putIfAppropriate(license);
return this;
}
public LicensesBuilder licenses(Collection<ESLicense> licenses) {
for (ESLicense esLicense : licenses) {
license(esLicense);
}
return this;
}
public LicensesBuilder licenses(ESLicenses licenses) {
return licenses(licenses.licenses());
}
public ESLicenses build() {
return new ESLicenses() {
@Override
public Collection<ESLicense> licenses() {
return licenseMap.values();
}
@Override
public Set<FeatureType> features() {
return licenseMap.keySet();
}
@Override
public ESLicense get(FeatureType featureType) {
return licenseMap.get(featureType);
}
@Override
public Iterator<ESLicense> iterator() {
return licenseMap.values().iterator();
}
};
}
private void initLicenses() {
if (licenseMap == null) {
licenseMap = new HashMap<>();
}
}
/**
* Add a {@link org.elasticsearch.license.core.ESLicenses.ESLicense} to
* {@link org.elasticsearch.license.core.ESLicenses} only if
* there exists no License for the feature that has a longer expiry date
* and if the license in question has an <code>expiryDate</code> that has
* not expired yet
*
* @param license license in question
*/
private void putIfAppropriate(ESLicense license) {
final FeatureType featureType = license.feature();
if (licenseMap.containsKey(featureType)) {
final ESLicense previousLicense = licenseMap.get(featureType);
if (license.expiryDate() > previousLicense.expiryDate()) {
licenseMap.put(featureType, license);
}
} else if (license.expiryDate() > System.currentTimeMillis()) {
licenseMap.put(featureType, license);
}
}
}
public static class LicenseBuilder {
private String uid;
private String issuer;
private String issuedTo;
private long issueDate = -1;
private Type type;
private SubscriptionType subscriptionType = SubscriptionType.DEFAULT;
private FeatureType feature;
private String signature;
private long expiryDate = -1;
private int maxNodes;
private final boolean internal;
public LicenseBuilder(boolean internal) {
this.internal = internal;
}
public LicenseBuilder uid(String uid) {
this.uid = uid;
return this;
}
public LicenseBuilder issuer(String issuer) {
this.issuer = issuer;
return this;
}
public LicenseBuilder issuedTo(String issuedTo) {
this.issuedTo = issuedTo;
return this;
}
public LicenseBuilder issueDate(long issueDate) {
this.issueDate = issueDate;
return this;
}
public LicenseBuilder type(Type type) {
this.type = type;
return this;
}
public LicenseBuilder subscriptionType(SubscriptionType subscriptionType) {
this.subscriptionType = subscriptionType;
return this;
}
public LicenseBuilder feature(FeatureType feature) {
this.feature = feature;
return this;
}
public LicenseBuilder expiryDate(long expiryDate) {
this.expiryDate = expiryDate;
return this;
}
public LicenseBuilder maxNodes(int maxNodes) {
this.maxNodes = maxNodes;
return this;
}
public LicenseBuilder signature(String signature) {
if (signature != null) {
this.signature = signature;
}
return this;
}
public LicenseBuilder fromLicense(ESLicense license) {
LicenseBuilder builder = this.uid(license.uid())
.issuedTo(license.issuedTo())
.issueDate(license.issueDate())
.type(license.type())
.subscriptionType(license.subscriptionType())
.feature(license.feature())
.maxNodes(license.maxNodes())
.expiryDate(license.expiryDate());
return (internal)
? builder.issuer(license.issuer()).signature(license.signature())
: builder;
}
public ESLicense build() {
if (uid == null) {
uid = UUID.randomUUID().toString();
}
verify();
return new ESLicense() {
@Override
public String uid() {
return uid;
}
@Override
public Type type() {
return type;
}
@Override
public SubscriptionType subscriptionType() {
return subscriptionType;
}
@Override
public long issueDate() {
return issueDate;
}
@Override
public FeatureType feature() {
return feature;
}
@Override
public long expiryDate() {
return expiryDate;
}
@Override
public int maxNodes() {
return maxNodes;
}
@Override
public String issuer() {
return issuer;
}
@Override
public String issuedTo() {
return issuedTo;
}
@Override
public String signature() {
return signature;
}
};
}
private void verify() {
String msg = null;
if (internal && issuer == null) {
msg = "issuer can not be null";
} else if (issuedTo == null) {
msg = "issuedTo can not be null";
} else if (issueDate == -1) {
msg = "issueDate has to be set";
} else if (type == null) {
msg = "type can not be null";
} else if (subscriptionType == null) {
msg = "subscriptionType can not be null";
} else if (uid == null) {
msg = "uid can not be null";
} else if (feature == null) {
msg = "at least one feature has to be enabled";
} else if (internal && signature == null) {
msg = "signature can not be null";
} else if (maxNodes == -1) {
msg = "maxNodes has to be set";
} else if (expiryDate == -1) {
msg = "expiryDate has to be set";
}
if (msg != null) {
throw new IllegalStateException(msg);
}
}
}
}

View File

@ -0,0 +1,182 @@
/*
* 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.commons.io.FileUtils;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.HashSet;
import java.util.Set;
public class LicenseUtils {
public static void dumpLicenseAsJson(ESLicenses esLicenses, OutputStream out) throws IOException {
JsonGenerator generator = new JsonFactory().createJsonGenerator(out);
generator.useDefaultPrettyPrinter();
generator.writeStartObject();
{
generator.writeArrayFieldStart("licenses");
{
for (ESLicenses.ESLicense esLicense : esLicenses) {
generator.writeStartObject();
{
generator.writeStringField("uid", esLicense.uid());
generator.writeStringField("type", esLicense.type().string());
generator.writeStringField("subscription_type", esLicense.subscriptionType().string());
generator.writeStringField("issued_to", esLicense.issuedTo());
generator.writeStringField("issue_date", DateUtils.dateStringFromLongDate(esLicense.issueDate()));
generator.writeStringField("expiry_date", DateUtils.dateStringFromLongDate(esLicense.expiryDate()));
generator.writeStringField("feature", esLicense.feature().string());
generator.writeNumberField("max_nodes", esLicense.maxNodes());
generator.writeStringField("signature", esLicense.signature());
}
generator.writeEndObject();
}
}
generator.writeEndArray();
}
generator.writeEndObject();
generator.flush();
}
public static Set<ESLicenses> readLicensesFromFiles(Set<File> licenseFiles) throws IOException {
Set<ESLicenses> esLicensesSet = new HashSet<>();
for (File licenseFile : licenseFiles) {
esLicensesSet.add(LicenseUtils.readLicenseFile(licenseFile));
}
return esLicensesSet;
}
public static Set<ESLicenses> readLicensesFromDirectory(File licenseDirectory) throws IOException {
Set<ESLicenses> esLicensesSet = new HashSet<>();
if (!licenseDirectory.exists()) {
throw new IllegalArgumentException(licenseDirectory.getAbsolutePath() + " does not exist!");
}
if (licenseDirectory.isDirectory()) {
for (File licenseFile : FileUtils.listFiles(licenseDirectory, new String[]{"json"}, false)) {
esLicensesSet.add(readLicenseFile(licenseFile));
}
} else if (licenseDirectory.isFile()) {
esLicensesSet.add(readLicenseFile(licenseDirectory));
} else {
throw new IllegalArgumentException(licenseDirectory.getAbsolutePath() + "is not a file or a directory");
}
return esLicensesSet;
}
public static ESLicenses readLicenseFile(File licenseFile) throws IOException {
try (FileInputStream fileInputStream = new FileInputStream(licenseFile)) {
JsonNode jsonNode = new ObjectMapper().readTree(fileInputStream);
return extractLicenseFromJson(jsonNode);
}
}
public static ESLicenses readLicensesFromString(String licensesString) throws IOException {
JsonNode jsonNode = new ObjectMapper().readTree(licensesString);
return extractLicenseFromJson(jsonNode);
}
private static ESLicenses extractLicenseFromJson(final JsonNode jsonNode) {
final LicenseBuilders.LicensesBuilder licensesBuilder = LicenseBuilders.licensesBuilder();
JsonNode licensesNode = jsonNode.get("licenses");
if (licensesNode.isArray()) {
for (JsonNode licenseNode : licensesNode) {
licensesBuilder.license(LicenseBuilders.licenseBuilder(false)
.uid(getValueAsString(licenseNode, "uid", true))
.issuedTo(getValueAsString(licenseNode, "issued_to"))
.issuer(getValueAsString(licenseNode, "issuer", true))
.issueDate(getValueAsDate(licenseNode, "issue_date"))
.type(ESLicenses.Type.fromString(getValueAsString(licenseNode, "type")))
.subscriptionType(ESLicenses.SubscriptionType.fromString(getValueAsString(licenseNode, "subscription_type")))
.feature(ESLicenses.FeatureType.fromString(getValueAsString(licenseNode, "feature")))
.expiryDate(getValueAsExpiryDate(licenseNode, "expiry_date"))
.maxNodes(getValueAsInt(licenseNode, "max_nodes"))
.signature(getValueAsString(licenseNode, "signature", true))
.build());
}
} else {
throw new IllegalStateException("'licenses' field is not an array");
}
return licensesBuilder.build();
}
private static int getValueAsInt(final JsonNode jsonNode, String field) {
JsonNode node = getFieldNode(jsonNode, field, false);
assert node.isNumber();
return node.getValueAsInt();
}
private static String getValueAsString(final JsonNode jsonNode, String field) {
return getValueAsString(jsonNode, field, false);
}
private static String getValueAsString(final JsonNode jsonNode, String field, boolean optional) {
JsonNode node = getFieldNode(jsonNode, field, optional);
assert node != null || optional;
if (node == null) {
return null;
}
assert !node.isObject();
return node.getTextValue();
}
private static long getValueAsDate(final JsonNode jsonNode, String field) {
JsonNode node = getFieldNode(jsonNode, field, false);
assert !node.isObject();
final String value = node.getTextValue();
try {
return DateUtils.longFromDateString(value);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
private static long getValueAsExpiryDate(final JsonNode jsonNode, String field) {
long actualDate = getValueAsDate(jsonNode, field);
return DateUtils.longExpiryDateFromDate(actualDate);
}
private static JsonNode getFieldNode(final JsonNode jsonNode, String field, boolean optional) {
JsonNode node = jsonNode.get(field);
if (node == null && !optional) {
throw new IllegalArgumentException("field ['" + field + "'] is missing");
}
return node;
}
public static void printLicense(ESLicenses licenses) {
for (ESLicenses.ESLicense license : licenses) {
System.out.println("===");
printValue(" uid", license.uid());
printValue(" type", license.type().string());
printValue(" subscription_type", license.subscriptionType().string());
printValue(" issueDate", DateUtils.dateStringFromLongDate(license.issueDate()));
printValue(" issuedTo", license.issuedTo());
printValue(" feature", license.feature().string());
printValue(" maxNodes", license.maxNodes());
printValue(" expiryDate", DateUtils.dateStringFromLongDate(license.expiryDate()));
printValue(" signature", license.signature());
System.out.println("===");
}
}
private static void printValue(String name, Object value) {
System.out.println(name + " : " + value);
}
}

View File

@ -0,0 +1,125 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor;
import net.nicholaswilliams.java.licensing.License;
import net.nicholaswilliams.java.licensing.encryption.Hasher;
import net.nicholaswilliams.java.licensing.encryption.PasswordProvider;
import net.nicholaswilliams.java.licensing.encryption.PrivateKeyDataProvider;
import net.nicholaswilliams.java.licensing.exception.KeyNotFoundException;
import net.nicholaswilliams.java.licensing.licensor.LicenseCreator;
import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseBuilders;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Random;
import static org.elasticsearch.license.core.ESLicenses.ESLicense;
public class ESLicenseSigner {
private final static int VERSION_START = 0;
private final static int VERSION = VERSION_START;
private final static int MAGIC_LENGTH = 13;
private final LicenseCreator licenseCreator;
private final SignerOptions options;
public static class SignerOptions {
final String privateKeyPath;
final String publicKeyPath;
final String password;
public SignerOptions(String privateKeyPath, String publicKeyPath, String password) {
this.privateKeyPath = privateKeyPath;
this.publicKeyPath = publicKeyPath;
this.password = password;
}
}
public ESLicenseSigner(final SignerOptions options) {
LicenseCreatorProperties.setPrivateKeyDataProvider(new PrivateKeyDataProvider() {
@Override
public byte[] getEncryptedPrivateKeyData() throws KeyNotFoundException {
File privateKeyFile = new File(options.privateKeyPath);
assert privateKeyFile.exists();
try {
return FileUtils.readFileToByteArray(privateKeyFile);
} catch (IOException e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
}
});
LicenseCreatorProperties.setPrivateKeyPasswordProvider(new PasswordProvider() {
@Override
public char[] getPassword() {
return options.password.toCharArray();
}
});
this.licenseCreator = LicenseCreator.getInstance();
this.options = options;
}
public ESLicenses sign(ESLicenses esLicenses) throws IOException {
final LicenseBuilders.LicensesBuilder licensesBuilder = LicenseBuilders.licensesBuilder();
for (ESLicense license : esLicenses) {
licensesBuilder.license(sign(license));
}
return licensesBuilder.build();
}
/**
* Generates a signature for the <code>esLicense</code>.
* Signature structure:
* | MAGIC | HEADER_LENGTH | VERSION | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
*
* @return a signed ESLicense (with signature)
* @throws IOException
*/
public ESLicense sign(ESLicense esLicense) throws IOException {
License.Builder licenseBuilder = new License.Builder()
.withGoodBeforeDate(esLicense.expiryDate())
.withIssueDate(esLicense.issueDate())
.withProductKey(esLicense.uid())
.withHolder(esLicense.issuedTo())
.withIssuer(esLicense.issuer())
.addFeature(esLicense.feature().string(), esLicense.expiryDate())
.addFeature("maxNodes:" + String.valueOf(esLicense.maxNodes()))
.addFeature("type:" + esLicense.type().string())
.addFeature("subscription_type:" + esLicense.subscriptionType().string());
final License license = licenseBuilder.build();
final byte[] magic = new byte[MAGIC_LENGTH];
Random random = new Random();
random.nextBytes(magic);
final byte[] licenseSignature = licenseCreator.signAndSerializeLicense(license);
final byte[] hash = Hasher.hash(Base64.encodeBase64String(
FileUtils.readFileToByteArray(new File(options.publicKeyPath)))
).getBytes(Charset.forName("UTF-8"));
int headerLength = MAGIC_LENGTH + hash.length + 4 + 4;
byte[] bytes = new byte[headerLength + licenseSignature.length];
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
byteBuffer.put(magic)
.putInt(headerLength)
.putInt(VERSION)
.put(hash)
.put(licenseSignature);
String signature = Base64.encodeBase64String(bytes);
return LicenseBuilders.licenseBuilder(true).fromLicense(esLicense).signature(signature).build();
}
}

View File

@ -0,0 +1,110 @@
/*
* 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 net.nicholaswilliams.java.licensing.encryption.RSAKeyPairGenerator;
import net.nicholaswilliams.java.licensing.exception.AlgorithmNotSupportedException;
import net.nicholaswilliams.java.licensing.exception.InappropriateKeyException;
import net.nicholaswilliams.java.licensing.exception.InappropriateKeySpecificationException;
import net.nicholaswilliams.java.licensing.exception.RSA2048NotSupportedException;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.security.KeyPair;
public class KeyPairGeneratorTool {
static class Options {
private final String publicKeyFilePath;
private final String privateKeyFilePath;
private final String keyPass;
Options(String publicKeyFilePath, String privateKeyFilePath, String keyPass) {
this.publicKeyFilePath = publicKeyFilePath;
this.privateKeyFilePath = privateKeyFilePath;
this.keyPass = keyPass;
}
}
private static Options parse(String[] args) {
String privateKeyPath = null;
String publicKeyPath = null;
String keyPass = null;
for (int i = 0; i < args.length; i++) {
String command = args[i];
switch (command) {
case "--publicKeyPath":
publicKeyPath = args[++i];
break;
case "--privateKeyPath":
privateKeyPath = args[++i];
break;
case "--keyPass":
keyPass = args[++i];
break;
}
}
if (publicKeyPath == null) {
throw new IllegalArgumentException("mandatory option '--publicKeyPath' is missing");
}
if (privateKeyPath == null) {
throw new IllegalArgumentException("mandatory option '--privateKeyPath' is missing");
}
if (keyPass == null) {
throw new IllegalArgumentException("mandatory option '--keyPass' is missing");
}
return new Options(publicKeyPath, privateKeyPath, keyPass);
}
public static void main(String[] args) throws IOException {
run(args, System.out);
}
public static void run(String[] args, OutputStream out) throws IOException {
PrintWriter printWriter = new PrintWriter(out);
Options options = parse(args);
if (exists(options.privateKeyFilePath)) {
throw new IllegalArgumentException("private key already exists in " + options.privateKeyFilePath);
} else if (exists(options.publicKeyFilePath)) {
throw new IllegalArgumentException("public key already exists in " + options.publicKeyFilePath);
}
KeyPair keyPair = generateKeyPair(options.privateKeyFilePath, options.publicKeyFilePath, options.keyPass);
if (keyPair != null) {
printWriter.println("Successfully generated new keyPair [publicKey: " + options.publicKeyFilePath + ", privateKey: " + options.privateKeyFilePath + "]");
}
}
private static boolean exists(String filePath) {
return new File(filePath).exists();
}
private static KeyPair generateKeyPair(String privateKeyFileName, String publicKeyFileName, String password) {
RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
KeyPair keyPair;
try {
keyPair = generator.generateKeyPair();
} catch (RSA2048NotSupportedException e) {
return null;
}
try {
generator.saveKeyPairToFiles(keyPair, privateKeyFileName, publicKeyFileName, password.toCharArray());
} catch (IOException | AlgorithmNotSupportedException | InappropriateKeyException | InappropriateKeySpecificationException e) {
throw new IllegalStateException(e);
}
return keyPair;
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.io.FileUtils;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseUtils;
import org.elasticsearch.license.licensor.ESLicenseSigner;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
public class LicenseGeneratorTool {
static class Options {
private final String licensesInput;
private final String publicKeyFilePath;
private final String privateKeyFilePath;
private final String keyPass;
Options(String licensesInput, String publicKeyFilePath, String privateKeyFilePath, String keyPass) {
this.licensesInput = licensesInput;
this.publicKeyFilePath = publicKeyFilePath;
this.privateKeyFilePath = privateKeyFilePath;
this.keyPass = keyPass;
}
}
private static Options parse(String[] args) throws IOException {
String licenseInput = null;
String licenseFilePath = null;
String privateKeyPath = null;
String publicKeyPath = null;
String keyPass = null;
for (int i = 0; i < args.length; i++) {
String command = args[i].trim();
switch (command) {
case "--license":
licenseInput = args[++i];
break;
case "--licenseFile":
licenseFilePath = args[++i];
break;
case "--publicKeyPath":
publicKeyPath = args[++i];
break;
case "--privateKeyPath":
privateKeyPath = args[++i];
break;
case "--keyPass":
keyPass = args[++i];
break;
}
}
if ((licenseInput == null && licenseFilePath == null) || (licenseInput != null && licenseFilePath != null)) {
throw new IllegalArgumentException("only one of '--license' or '--licenseFile' option should be set");
} else if (licenseFilePath != null) {
File licenseFile = new File(licenseFilePath);
if (licenseFile.exists()) {
licenseInput = FileUtils.readFileToString(licenseFile, Charset.forName("UTF-8"));
} else {
throw new IllegalArgumentException("provided --licenseFile " + licenseFile.getAbsolutePath() + " does not exist!");
}
}
if (publicKeyPath == null) {
throw new IllegalArgumentException("mandatory option '--publicKeyPath' is missing");
}
if (privateKeyPath == null) {
throw new IllegalArgumentException("mandatory option '--privateKeyPath' is missing");
}
if (keyPass == null) {
throw new IllegalArgumentException("mandatory option '--keyPass' is missing");
}
return new Options(licenseInput, publicKeyPath, privateKeyPath, keyPass);
}
public static void main(String[] args) throws IOException {
run(args, System.out);
}
public static void run(String[] args, OutputStream out) throws IOException {
Options options = parse(args);
ESLicenses esLicenses = LicenseUtils.readLicensesFromString(options.licensesInput);
ESLicenseSigner signer = new ESLicenseSigner(new ESLicenseSigner.SignerOptions(options.privateKeyFilePath, options.publicKeyFilePath, options.keyPass));
ESLicenses signedLicences = signer.sign(esLicenses);
LicenseUtils.dumpLicenseAsJson(signedLicences, out);
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseUtils;
import org.elasticsearch.license.manager.ESLicenseManager;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class LicenseVerificationTool {
static class Options {
private final Set<File> licensesFiles;
private final String publicKeyFilePath;
private final String keyPass;
Options(Set<File> licensesFiles, String publicKeyFilePath, String keyPass) {
this.licensesFiles = licensesFiles;
this.publicKeyFilePath = publicKeyFilePath;
this.keyPass = keyPass;
}
static Set<File> asFiles(Set<String> filePaths) {
Set<File> files = new HashSet<>(filePaths.size());
for (String filePath : filePaths) {
final File file = new File(filePath);
if (file.exists()) {
files.add(file);
} else {
throw new IllegalArgumentException(file.getAbsolutePath() + " does not exist!");
}
}
return files;
}
}
private static Options parse(String[] args) {
Set<String> licenseFilePaths = null;
Set<File> licenseFiles = null;
String publicKeyPath = null;
String keyPass = null;
for (int i = 0; i < args.length; i++) {
String command = args[i];
switch (command) {
case "--licensesFiles":
licenseFilePaths = new HashSet<>();
licenseFilePaths.addAll(Arrays.asList(args[++i].split(":")));
break;
case "--publicKeyPath":
publicKeyPath = args[++i];
break;
case "--keyPass":
keyPass = args[++i];
break;
}
}
if (licenseFilePaths == null) {
throw new IllegalArgumentException("mandatory option '--licensesFiles' is missing");
} else {
licenseFiles = Options.asFiles(licenseFilePaths);
if (licenseFiles.size() == 0) {
throw new IllegalArgumentException("no license file found for provided license files");
}
}
if (publicKeyPath == null) {
throw new IllegalArgumentException("mandatory option '--publicKeyPath' is missing");
}
if (keyPass == null) {
throw new IllegalArgumentException("mandatory option '--keyPass' is missing");
}
return new Options(licenseFiles, publicKeyPath, keyPass);
}
public static void main(String[] args) throws IOException {
run(args, System.out);
}
public static void run(String[] args, OutputStream out) throws IOException {
Options options = parse(args);
// read licenses
Set<ESLicenses> esLicensesSet = LicenseUtils.readLicensesFromFiles(options.licensesFiles);
// verify licenses
ESLicenseManager licenseManager = new ESLicenseManager(esLicensesSet, options.publicKeyFilePath, options.keyPass);
licenseManager.verifyLicenses();
// dump effective licences
LicenseUtils.dumpLicenseAsJson(licenseManager.getEffectiveLicenses(), out);
}
}

View File

@ -0,0 +1,269 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.manager;
import net.nicholaswilliams.java.licensing.*;
import net.nicholaswilliams.java.licensing.encryption.FilePublicKeyDataProvider;
import net.nicholaswilliams.java.licensing.encryption.Hasher;
import net.nicholaswilliams.java.licensing.encryption.PasswordProvider;
import net.nicholaswilliams.java.licensing.exception.ExpiredLicenseException;
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseBuilders;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import static org.elasticsearch.license.core.ESLicenses.*;
/**
* Class responsible for reading signed licenses, maintaining an effective esLicenses instance, verification of licenses
* and querying against licenses on a feature basis
*
* TODO:
* - integration with cluster state
* - use ESLicenseProvider to query license from cluster state
*/
public class ESLicenseManager {
private final LicenseManager licenseManager;
private final ESLicenses esLicenses;
private final FilePublicKeyDataProvider publicKeyDataProvider;
public ESLicenseManager(Set<ESLicenses> esLicensesSet, String publicKeyFile, String password) throws IOException {
this.publicKeyDataProvider = new FilePublicKeyDataProvider(publicKeyFile);
this.esLicenses = merge(esLicensesSet);
LicenseManagerProperties.setLicenseProvider(new ESLicenseProvider());
LicenseManagerProperties.setPublicKeyDataProvider(publicKeyDataProvider);
LicenseManagerProperties.setLicenseValidator(new DefaultLicenseValidator());
LicenseManagerProperties.setPublicKeyPasswordProvider(new ESPublicKeyPasswordProvider(password));
this.licenseManager = LicenseManager.getInstance();
}
public ESLicenseManager(ESLicenses esLicenses, String publicKeyFile, String password) throws IOException {
this(Collections.singleton(esLicenses), publicKeyFile, password);
}
private static ESLicenses merge(Set<ESLicenses> esLicensesSet) {
ESLicenses mergedLicenses = null;
for (ESLicenses licenses : esLicensesSet) {
mergedLicenses = LicenseBuilders.merge(mergedLicenses, licenses);
}
return mergedLicenses;
}
public ESLicenses getEffectiveLicenses() {
return esLicenses;
}
private License getLicense(FeatureType featureType) {
ESLicense esLicense = esLicenses.get(featureType);
if (esLicense != null) {
String signature = esLicense.signature();
try {
License license = this.licenseManager.decryptAndVerifyLicense(extractSignedLicence(signature));
this.licenseManager.validateLicense(license);
return license;
} catch (IOException e) {
throw new IllegalStateException("bogus");
}
}
return null;
}
/**
* Extract a signedLicense (SIGNED_LICENSE_CONTENT) from the signature.
* Validates the public key used to decrypt the license by comparing their hashes
* <p/>
* Signature structure:
* | MAGIC | HEADER_LENGTH | VERSION | PUB_KEY_DIGEST | SIGNED_LICENSE_CONTENT |
*
* @param signature of a single license
* @return signed license content for the license
* @throws IOException
*/
private SignedLicense extractSignedLicence(String signature) throws IOException {
byte[] signatureBytes = Base64.decodeBase64(signature);
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
byteBuffer = (ByteBuffer) byteBuffer.position(13);
int start = byteBuffer.getInt();
int version = byteBuffer.getInt();
byte[] hash = new byte[start - 13 - 4 - 4];
byteBuffer.get(hash);
final byte[] computedHash = Hasher.hash(Base64.encodeBase64String(
FileUtils.readFileToByteArray(publicKeyDataProvider.getPublicKeyFile()))
).getBytes(Charset.forName("UTF-8"));
if (!Arrays.equals(hash, computedHash)) {
throw new InvalidLicenseException("Invalid License");
}
return new ObjectSerializer().readObject(SignedLicense.class, Arrays.copyOfRange(signatureBytes, start, signatureBytes.length));
}
public void verifyLicenses() {
for (FeatureType featureType : esLicenses.features()) {
final License license = getLicense(featureType);
assert license != null : "license should not be null for feature: " + featureType.string();
verifyLicenseFields(license, esLicenses.get(featureType));
}
}
private static void verifyLicenseFields(License license, ESLicense eslicense) {
boolean licenseValid = license.getProductKey().equals(eslicense.uid())
&& license.getHolder().equals(eslicense.issuedTo())
&& license.getIssueDate() == eslicense.issueDate()
&& license.getGoodBeforeDate() == eslicense.expiryDate();
assert license.getFeatures().size() == 4 : "one license should have only four feature";
String maxNodesPrefix = "maxNodes:";
String typePrefix = "type:";
String subscriptionTypePrefix = "subscription_type:";
boolean maxNodesValid = false;
boolean featureValid = false;
boolean typeValid = false;
boolean subscriptionTypeValid = false;
for (License.Feature feature : license.getFeatures()) {
String featureName = feature.getName();
if (featureName.startsWith(maxNodesPrefix)) {
maxNodesValid = eslicense.maxNodes() == Integer.parseInt(featureName.substring(maxNodesPrefix.length()));
} else if (featureName.startsWith(typePrefix)) {
typeValid = eslicense.type() == Type.fromString(featureName.substring(typePrefix.length()));
} else if (featureName.startsWith(subscriptionTypePrefix)) {
subscriptionTypeValid = eslicense.subscriptionType() == SubscriptionType.fromString(featureName.substring(subscriptionTypePrefix.length()));
} else {
featureValid = feature.getName().equals(eslicense.feature().string())
&& feature.getGoodBeforeDate() == eslicense.expiryDate();
}
}
if (!licenseValid || !featureValid || !maxNodesValid || !typeValid || !subscriptionTypeValid) {
String msg = "licenseValid: " + licenseValid + "\n" +
"featureValid: " + featureValid + "\n" +
"maxNodeValide: " + maxNodesValid + "\n" +
"typeValid: " + typeValid + "\n" +
"subscriptionTypeValid: " + subscriptionTypeValid + "\n";
throw new InvalidLicenseException("Invalid License");
}
}
public boolean hasLicenseForFeature(FeatureType featureType) {
try {
final License license = getLicense(featureType);
if (license == null) {
return false;
}
return license.hasLicenseForFeature(featureType.string());
} catch (ExpiredLicenseException e) {
return false;
} catch (InvalidLicenseException e) {
return false;
}
}
public boolean hasLicenseForNodes(FeatureType featureType, int nodes) {
ESLicense esLicense = generateESLicense(featureType);
return esLicense.maxNodes() >= nodes;
}
public String getIssuerForLicense(FeatureType featureType) {
final License license = getLicense(featureType);
return license.getIssuer();
}
public long getIssueDateForLicense(FeatureType featureType) {
final License license = getLicense(featureType);
return license.getIssueDate();
}
public long getExpiryDateForLicense(FeatureType featureType) {
final License license = getLicense(featureType);
return license.getGoodBeforeDate();
}
public String getIssuedToForLicense(FeatureType featureType) {
final License license = getLicense(featureType);
return license.getHolder();
}
public Type getTypeForLicense(FeatureType featureType) {
ESLicense esLicense = generateESLicense(featureType);
return esLicense.type();
}
public SubscriptionType getSubscriptionTypeForLicense(FeatureType featureType) {
ESLicense esLicense = generateESLicense(featureType);
return esLicense.subscriptionType();
}
private ESLicense generateESLicense(FeatureType featureType) {
final License license = getLicense(featureType);
return convertToESLicense(license);
}
static ESLicense convertToESLicense(License license) {
final LicenseBuilders.LicenseBuilder licenseBuilder = LicenseBuilders.licenseBuilder(false);
licenseBuilder
.expiryDate(license.getGoodBeforeDate())
.issueDate(license.getIssueDate())
.uid(license.getProductKey())
.issuedTo(license.getHolder())
.issuer(license.getIssuer());
assert license.getFeatures().size() == 4 : "one license should have only four feature";
String maxNodesPrefix = "maxNodes:";
String typePrefix = "type:";
String subscriptionTypePrefix = "subscription_type:";
for (License.Feature feature : license.getFeatures()) {
String featureName = feature.getName();
if (featureName.startsWith(maxNodesPrefix)) {
licenseBuilder.maxNodes(Integer.parseInt(featureName.substring(maxNodesPrefix.length())));
} else if (featureName.startsWith(typePrefix)) {
licenseBuilder.type(Type.fromString(featureName.substring(typePrefix.length())));
} else if (featureName.startsWith(subscriptionTypePrefix)) {
licenseBuilder.subscriptionType(SubscriptionType.fromString(featureName.substring(subscriptionTypePrefix.length())));
} else {
licenseBuilder.feature(FeatureType.fromString(featureName));
}
}
return licenseBuilder.build();
}
/**
* Used by the underlying license manager (make sure it is never called for now)
* This should be retrieving licenses from the custom metadata in the cluster state
*/
public class ESLicenseProvider implements LicenseProvider {
@Override
public SignedLicense getLicense(Object context) {
throw new NotImplementedException();
}
}
private class ESPublicKeyPasswordProvider implements PasswordProvider {
private final String pass;
private ESPublicKeyPasswordProvider(String pass) {
this.pass = pass;
}
@Override
public char[] getPassword() {
return pass.toCharArray();
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.manager;
import net.nicholaswilliams.java.licensing.LicenseManager;
import net.nicholaswilliams.java.licensing.ObjectSerializer;
import net.nicholaswilliams.java.licensing.SignedLicense;
import org.apache.commons.codec.binary.Base64;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseBuilders;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Set;
import static org.elasticsearch.license.core.ESLicenses.ESLicense;
public class Utils {
private Utils() {
}
static ESLicenses getESLicensesFromSignatures(final LicenseManager licenseManager, Set<String> signatures) {
final LicenseBuilders.LicensesBuilder licensesBuilder = LicenseBuilders.licensesBuilder();
for (String signature : signatures) {
licensesBuilder.license(getESLicenseFromSignature(licenseManager, signature));
}
return licensesBuilder.build();
}
private static ESLicense getESLicenseFromSignature(LicenseManager licenseManager, String signature) {
byte[] signatureBytes = Base64.decodeBase64(signature);
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
byteBuffer = (ByteBuffer) byteBuffer.position(13);
int start = byteBuffer.getInt();
SignedLicense signedLicense = new ObjectSerializer()
.readObject(SignedLicense.class, Arrays.copyOfRange(signatureBytes, start, signatureBytes.length));
return ESLicenseManager.convertToESLicense(licenseManager.decryptAndVerifyLicense(signedLicense));
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.plugins.AbstractPlugin;
//TODO: plugin hooks
public class LicensePlugin extends AbstractPlugin {
@Override
public String name() {
return "license";
}
@Override
public String description() {
return "Internal Elasticsearch Licensing Plugin";
}
}

View File

@ -0,0 +1 @@
plugin=org.elasticsearch.license.plugin.LicensePlugin

View File

@ -0,0 +1,102 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license;
import org.apache.commons.io.FileUtils;
import org.elasticsearch.license.core.DateUtils;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.licensor.tools.LicenseGeneratorTool;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.util.Map;
import static org.junit.Assert.assertTrue;
public class TestUtils {
public static String generateESLicenses(Map<ESLicenses.FeatureType, FeatureAttributes> featureAttributes) {
StringBuilder licenseBuilder = new StringBuilder();
int size = featureAttributes.values().size();
int i = 0;
for (FeatureAttributes attributes : featureAttributes.values()) {
licenseBuilder.append("{\n" +
" \"type\" : \"" + attributes.type + "\",\n" +
" \"subscription_type\" : \"" + attributes.subscriptionType + "\",\n" +
" \"issued_to\" : \"" + attributes.issuedTo + "\",\n" +
" \"issuer\" : \"" + attributes.issuer + "\",\n" +
" \"issue_date\" : \"" + attributes.issueDate + "\",\n" +
" \"expiry_date\" : \"" + attributes.expiryDate + "\",\n" +
" \"feature\" : \"" + attributes.featureType + "\",\n" +
" \"max_nodes\" : " + attributes.maxNodes +
"}");
if (++i < size) {
licenseBuilder.append(",\n");
}
}
return "{\n" +
" \"licenses\" : [" +
licenseBuilder.toString() +
"]\n" +
"}";
}
public static String runLicenseGenerationTool(String[] args) throws IOException {
File temp = File.createTempFile("temp", ".out");
temp.deleteOnExit();
try (FileOutputStream outputStream = new FileOutputStream(temp)) {
LicenseGeneratorTool.run(args, outputStream);
}
return FileUtils.readFileToString(temp);
}
public static void verifyESLicenses(ESLicenses esLicenses, Map<ESLicenses.FeatureType, FeatureAttributes> featureAttributes) throws ParseException {
assertTrue("Number of feature licenses should be " + featureAttributes.size(), esLicenses.features().size() == featureAttributes.size());
for (Map.Entry<ESLicenses.FeatureType, FeatureAttributes> featureAttrTuple : featureAttributes.entrySet()) {
ESLicenses.FeatureType featureType = featureAttrTuple.getKey();
FeatureAttributes attributes = featureAttrTuple.getValue();
final ESLicenses.ESLicense esLicense = esLicenses.get(featureType);
assertTrue("license for " + featureType.string() + " should be present", esLicense != null);
assertTrue("expected value for issuedTo was: " + attributes.issuedTo + " but got: " + esLicense.issuedTo(), esLicense.issuedTo().equals(attributes.issuedTo));
assertTrue("expected value for type was: " + attributes.type + " but got: " + esLicense.type().string(), esLicense.type().string().equals(attributes.type));
assertTrue("expected value for subscriptionType was: " + attributes.subscriptionType + " but got: " + esLicense.subscriptionType().string(), esLicense.subscriptionType().string().equals(attributes.subscriptionType));
assertTrue("expected value for feature was: " + attributes.featureType + " but got: " + esLicense.feature().string(), esLicense.feature().string().equals(attributes.featureType));
assertTrue("expected value for issueDate was: " + DateUtils.longFromDateString(attributes.issueDate) + " but got: " + esLicense.issueDate(), esLicense.issueDate() == DateUtils.longFromDateString(attributes.issueDate));
assertTrue("expected value for expiryDate: " + DateUtils.longExpiryDateFromString(attributes.expiryDate) + " but got: " + esLicense.expiryDate(), esLicense.expiryDate() == DateUtils.longExpiryDateFromString(attributes.expiryDate));
assertTrue("expected value for maxNodes: " + attributes.maxNodes + " but got: " + esLicense.maxNodes(), esLicense.maxNodes() == attributes.maxNodes);
assertTrue("generated licenses should have non-null uid field", esLicense.uid() != null);
assertTrue("generated licenses should have non-null signature field", esLicense.signature() != null);
}
}
public static class FeatureAttributes {
public final String featureType;
public final String type;
public final String subscriptionType;
public final String issuedTo;
public final int maxNodes;
public final String issueDate;
public final String expiryDate;
public final String issuer;
public FeatureAttributes(String featureType, String type, String subscriptionType, String issuedTo, String issuer, int maxNodes, String issueDateStr, String expiryDateStr) throws ParseException {
this.featureType = featureType;
this.type = type;
this.subscriptionType = subscriptionType;
this.issuedTo = issuedTo;
this.issuer = issuer;
this.maxNodes = maxNodes;
this.issueDate = issueDateStr;
this.expiryDate = expiryDateStr;
}
}
}

View File

@ -0,0 +1,221 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.licensor;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseUtils;
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.license.core.ESLicenses.FeatureType;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class LicenseGenerationTests {
private static String pubKeyPath = null;
private static String priKeyPath = null;
private static String keyPass = null;
@BeforeClass
public static void setup() throws IOException {
// Generate temp KeyPair spec
File privateKeyFile = File.createTempFile("privateKey", ".key");
File publicKeyFile = File.createTempFile("publicKey", ".key");
LicenseGenerationTests.pubKeyPath = publicKeyFile.getAbsolutePath();
LicenseGenerationTests.priKeyPath = privateKeyFile.getAbsolutePath();
assert privateKeyFile.delete();
assert publicKeyFile.delete();
String keyPass = "password";
LicenseGenerationTests.keyPass = keyPass;
// Generate keyPair
String[] args = new String[6];
args[0] = "--publicKeyPath";
args[1] = LicenseGenerationTests.pubKeyPath;
args[2] = "--privateKeyPath";
args[3] = LicenseGenerationTests.priKeyPath;
args[4] = "--keyPass";
args[5] = LicenseGenerationTests.keyPass;
KeyPairGeneratorTool.main(args);
}
@Test
public void testSimpleLicenseGeneration() throws ParseException, IOException {
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureAttributes =
new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 2, "2014-12-13", "2015-12-13");
map.put(FeatureType.SHIELD, featureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
ESLicenses esLicensesOutput = LicenseUtils.readLicensesFromString(licenseOutput);
TestUtils.verifyESLicenses(esLicensesOutput, map);
}
@Test
public void testMultipleFeatureTypes() throws ParseException, IOException {
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes shildFeatureAttributes =
new TestUtils.FeatureAttributes("shield", "trial", "none", "foo bar Inc.", "elasticsearch", 2, "2014-12-13", "2015-12-13");
TestUtils.FeatureAttributes marvelFeatureAttributes =
new TestUtils.FeatureAttributes("marvel", "subscription", "silver", "foo1 bar Inc.", "elasticsearc3h", 10, "2014-01-13", "2014-12-13");
map.put(FeatureType.SHIELD, shildFeatureAttributes);
map.put(FeatureType.MARVEL, marvelFeatureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
ESLicenses esLicensesOutput = LicenseUtils.readLicensesFromString(licenseOutput);
TestUtils.verifyESLicenses(esLicensesOutput, map);
}
@Test
public void testMissingCLTArgs() throws ParseException, IOException {
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureAttributes =
new TestUtils.FeatureAttributes("shiedgdsld", "internal", "none", "foo bar Inc.", "elasticsearch", 23, "2014-12-13", "2015-12-13");
map.put(FeatureType.SHIELD, featureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--linse";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
try {
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
fail();
} catch (IllegalArgumentException e) {
assertTrue("Exception should indicate mandatory param --license, got: " + e.getMessage(), e.getMessage().contains("license"));
}
}
@Test
public void testInvalidFeatureType() throws ParseException, IOException {
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureAttributes =
new TestUtils.FeatureAttributes("shiedgdsld", "internal", "none", "foo bar Inc.", "elasticsearch", 23, "2014-12-13", "2015-12-13");
map.put(FeatureType.SHIELD, featureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
try {
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
fail();
} catch (IllegalArgumentException e) {
assertTrue("Exception should indicate invalid FeatureType, got: " + e.getMessage(), e.getMessage().contains("Invalid FeatureType"));
}
}
@Test
public void testInvalidSubscriptionType() throws ParseException, IOException {
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureAttributes =
new TestUtils.FeatureAttributes("shield", "trial", "nodavne", "foo bar Inc.", "elasticsearch", 25, "2014-12-13", "2015-12-13");
map.put(FeatureType.SHIELD, featureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
try {
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
fail();
} catch (IllegalArgumentException e) {
assertTrue("Exception should indicate invalid SubscriptionType, got: " + e.getMessage(), e.getMessage().contains("Invalid SubscriptionType"));
}
}
@Test
public void testInvalidType() throws ParseException, IOException {
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureAttributes =
new TestUtils.FeatureAttributes("shield", "inininternal", "gold", "foo bar Inc.", "elasticsearch", 12, "2014-12-13", "2015-12-13");
map.put(FeatureType.SHIELD, featureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
try {
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
fail();
} catch (IllegalArgumentException e) {
assertTrue("Exception should indicate invalid Type, got: " + e.getMessage(), e.getMessage().contains("Invalid Type"));
}
}
}

View File

@ -0,0 +1,200 @@
/*
* 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.apache.commons.io.FileUtils;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseUtils;
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool;
import org.elasticsearch.license.licensor.tools.LicenseVerificationTool;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Ignore("Enable once maven is setup properly; now it throws invalid signature error for all the tests when the tests always pass in intellij")
public class LicenseVerificationToolTests {
private static String pubKeyPath = null;
private static String priKeyPath = null;
private static String keyPass = null;
@BeforeClass
public static void setup() throws IOException {
// Generate temp KeyPair spec
File privateKeyFile = File.createTempFile("privateKey", ".key");
File publicKeyFile = File.createTempFile("publicKey", ".key");
LicenseVerificationToolTests.pubKeyPath = publicKeyFile.getAbsolutePath();
LicenseVerificationToolTests.priKeyPath = privateKeyFile.getAbsolutePath();
assert privateKeyFile.delete();
assert publicKeyFile.delete();
LicenseVerificationToolTests.keyPass = "password";
// Generate keyPair
String[] args = new String[6];
args[0] = "--publicKeyPath";
args[1] = LicenseVerificationToolTests.pubKeyPath;
args[2] = "--privateKeyPath";
args[3] = LicenseVerificationToolTests.priKeyPath;
args[4] = "--keyPass";
args[5] = LicenseVerificationToolTests.keyPass;
KeyPairGeneratorTool.main(args);
}
@Test
public void testEffectiveLicenseGeneration() throws Exception {
Map<ESLicenses.FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureWithLongerExpiryDate =
new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 10, "2014-12-13", "2015-12-13");
map.put(ESLicenses.FeatureType.SHIELD, featureWithLongerExpiryDate);
String signedLicense = runLicenseGenerationTool(TestUtils.generateESLicenses(map));
String firstLicenseFile = getAsFilePath(signedLicense);
TestUtils.FeatureAttributes featureWithShorterExpiryDate =
new TestUtils.FeatureAttributes("shield", "trial", "none", "foo bar Inc.", "elasticsearch", 2, "2014-12-13", "2015-01-13");
map.put(ESLicenses.FeatureType.SHIELD, featureWithShorterExpiryDate);
signedLicense = runLicenseGenerationTool(TestUtils.generateESLicenses(map));
String secondLicenseFile = getAsFilePath(signedLicense);
String[] args = new String[6];
args[0] = "--licensesFiles";
args[1] = firstLicenseFile + ":" + secondLicenseFile;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--keyPass";
args[5] = keyPass;
String effectiveLicenseStr = runLicenseVerificationTool(args);
ESLicenses effectiveLicense = LicenseUtils.readLicensesFromString(effectiveLicenseStr);
map.put(ESLicenses.FeatureType.SHIELD, featureWithLongerExpiryDate);
// verify that the effective license strips out license for the same feature with earlier expiry dates
TestUtils.verifyESLicenses(effectiveLicense, map);
}
@Test
public void testEffectiveLicenseForMultiFeatures() throws Exception {
Map<ESLicenses.FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes shieldFeatureWithLongerExpiryDate =
new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 10, "2014-12-13", "2015-12-13");
map.put(ESLicenses.FeatureType.SHIELD, shieldFeatureWithLongerExpiryDate);
String signedLicense = runLicenseGenerationTool(TestUtils.generateESLicenses(map));
String firstLicenseFile = getAsFilePath(signedLicense);
TestUtils.FeatureAttributes marvelFeatureWithShorterExpiryDate =
new TestUtils.FeatureAttributes("marvel", "trial", "none", "foo bar Inc.", "elasticsearch", 2, "2014-12-13", "2015-01-13");
map.put(ESLicenses.FeatureType.MARVEL, marvelFeatureWithShorterExpiryDate);
signedLicense = runLicenseGenerationTool(TestUtils.generateESLicenses(map));
String secondLicenseFile = getAsFilePath(signedLicense);
String[] args = new String[6];
args[0] = "--licensesFiles";
args[1] = firstLicenseFile + ":" + secondLicenseFile;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--keyPass";
args[5] = keyPass;
String effectiveLicenseStr = runLicenseVerificationTool(args);
ESLicenses effectiveLicense = LicenseUtils.readLicensesFromString(effectiveLicenseStr);
// verify that the effective license contains both feature licenses
TestUtils.verifyESLicenses(effectiveLicense, map);
}
@Test
public void testEffectiveLicenseForMultiFeatures2() throws Exception {
Map<ESLicenses.FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes shieldFeatureWithLongerExpiryDate =
new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 10, "2014-12-13", "2015-12-13");
TestUtils.FeatureAttributes marvelFeatureWithShorterExpiryDate =
new TestUtils.FeatureAttributes("marvel", "trial", "none", "foo bar Inc.", "elasticsearch", 2, "2014-12-13", "2015-01-13");
map.put(ESLicenses.FeatureType.SHIELD, shieldFeatureWithLongerExpiryDate);
map.put(ESLicenses.FeatureType.MARVEL, marvelFeatureWithShorterExpiryDate);
String signedLicense = runLicenseGenerationTool(TestUtils.generateESLicenses(map));
String firstLicenseFile = getAsFilePath(signedLicense);
TestUtils.FeatureAttributes shieldFeatureWithShorterExpiryDate =
new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 10, "2014-12-13", "2015-11-13");
TestUtils.FeatureAttributes marvelFeatureWithLongerExpiryDate =
new TestUtils.FeatureAttributes("marvel", "trial", "none", "foo bar Inc.", "elasticsearch", 2, "2014-12-13", "2015-11-13");
map.put(ESLicenses.FeatureType.SHIELD, shieldFeatureWithShorterExpiryDate);
map.put(ESLicenses.FeatureType.MARVEL, marvelFeatureWithLongerExpiryDate);
signedLicense = runLicenseGenerationTool(TestUtils.generateESLicenses(map));
String secondLicenseFile = getAsFilePath(signedLicense);
String[] args = new String[6];
args[0] = "--licensesFiles";
args[1] = firstLicenseFile + ":" + secondLicenseFile;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--keyPass";
args[5] = keyPass;
String effectiveLicenseStr = runLicenseVerificationTool(args);
ESLicenses effectiveLicense = LicenseUtils.readLicensesFromString(effectiveLicenseStr);
map.put(ESLicenses.FeatureType.SHIELD, shieldFeatureWithLongerExpiryDate);
map.put(ESLicenses.FeatureType.MARVEL, marvelFeatureWithLongerExpiryDate);
// verify that the generated effective license is generated from choosing individual licences from multiple files
TestUtils.verifyESLicenses(effectiveLicense, map);
}
public static String runLicenseVerificationTool(String[] args) throws IOException {
File temp = File.createTempFile("temp", ".out");
temp.deleteOnExit();
try (FileOutputStream outputStream = new FileOutputStream(temp)) {
LicenseVerificationTool.run(args, outputStream);
}
return FileUtils.readFileToString(temp);
}
public static String runLicenseGenerationTool(String licenseInput) throws IOException {
String args[] = new String[8];
args[0] = "--license";
args[1] = licenseInput;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
return TestUtils.runLicenseGenerationTool(args);
}
private static String getAsFilePath(String content) throws IOException {
File temp = File.createTempFile("license", ".out");
temp.deleteOnExit();
FileUtils.write(temp, content);
String tempFilePath = temp.getAbsolutePath();
while (tempFilePath.contains(":")) {
assert temp.delete();
tempFilePath = getAsFilePath(content);
}
return tempFilePath;
}
}

View File

@ -0,0 +1,240 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.license.manager;
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.core.DateUtils;
import org.elasticsearch.license.core.ESLicenses;
import org.elasticsearch.license.core.LicenseBuilders;
import org.elasticsearch.license.licensor.tools.KeyPairGeneratorTool;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.license.core.ESLicenses.FeatureType;
import static org.elasticsearch.license.core.LicenseUtils.readLicensesFromString;
import static org.junit.Assert.*;
@Ignore("Enable once maven is setup properly; now it throws invalid signature error for all the tests when the tests always pass in intellij")
public class LicenseVerificationTests {
private static String pubKeyPath = null;
private static String priKeyPath = null;
private static String keyPass = null;
@BeforeClass
public static void setup() throws IOException {
// Generate temp KeyPair spec
File privateKeyFile = File.createTempFile("privateKey", ".key");
File publicKeyFile = File.createTempFile("publicKey", ".key");
LicenseVerificationTests.pubKeyPath = publicKeyFile.getAbsolutePath();
LicenseVerificationTests.priKeyPath = privateKeyFile.getAbsolutePath();
assert privateKeyFile.delete();
assert publicKeyFile.delete();
LicenseVerificationTests.keyPass = "password";
// Generate keyPair
String[] args = new String[6];
args[0] = "--publicKeyPath";
args[1] = LicenseVerificationTests.pubKeyPath;
args[2] = "--privateKeyPath";
args[3] = LicenseVerificationTests.priKeyPath;
args[4] = "--keyPass";
args[5] = LicenseVerificationTests.keyPass;
KeyPairGeneratorTool.main(args);
}
@Test
public void testGeneratedLicenses() throws Exception {
Date issueDate = new Date();
String issueDateStr = DateUtils.dateStringFromLongDate(issueDate.getTime());
String expiryDateStr = DateUtils.dateStringFromLongDate(DateUtils.longExpiryDateFromDate(issueDate.getTime() + 24 * 60 * 60l));
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureAttributes =
new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 2, issueDateStr, expiryDateStr);
map.put(FeatureType.SHIELD, featureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
ESLicenses esLicensesOutput = readLicensesFromString(licenseOutput);
ESLicenseManager esLicenseManager = new ESLicenseManager(esLicensesOutput, pubKeyPath, keyPass);
esLicenseManager.verifyLicenses();
verifyLicenseManager(esLicenseManager, map);
}
@Test
public void testMultipleFeatureLicenses() throws Exception {
Date issueDate = new Date();
String issueDateStr = DateUtils.dateStringFromLongDate(issueDate.getTime());
String expiryDateStr = DateUtils.dateStringFromLongDate(DateUtils.longExpiryDateFromDate(issueDate.getTime() + 24 * 60 * 60 * 1000l));
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes shildFeatureAttributes =
new TestUtils.FeatureAttributes("shield", "trial", "none", "foo bar Inc.", "elasticsearch", 2, issueDateStr, expiryDateStr);
TestUtils.FeatureAttributes marvelFeatureAttributes =
new TestUtils.FeatureAttributes("marvel", "subscription", "silver", "foo1 bar Inc.", "elasticsearc3h", 10, issueDateStr, expiryDateStr);
map.put(FeatureType.SHIELD, shildFeatureAttributes);
map.put(FeatureType.MARVEL, marvelFeatureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
ESLicenses esLicensesOutput = readLicensesFromString(licenseOutput);
ESLicenseManager esLicenseManager = new ESLicenseManager(esLicensesOutput, pubKeyPath, keyPass);
esLicenseManager.verifyLicenses();
verifyLicenseManager(esLicenseManager, map);
}
@Test
public void testLicenseExpiry() throws Exception {
Date issueDate = new Date();
String issueDateStr = DateUtils.dateStringFromLongDate(issueDate.getTime());
String expiryDateStr = DateUtils.dateStringFromLongDate(DateUtils.longExpiryDateFromDate(issueDate.getTime() + 24 * 60 * 60l));
String expiredExpiryDateStr = DateUtils.dateStringFromLongDate(DateUtils.longExpiryDateFromDate(issueDate.getTime() - 5 * 24 * 60 * 60 * 1000l));
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes shildFeatureAttributes =
new TestUtils.FeatureAttributes("shield", "trial", "none", "foo bar Inc.", "elasticsearch", 2, issueDateStr, expiryDateStr);
TestUtils.FeatureAttributes marvelFeatureAttributes =
new TestUtils.FeatureAttributes("marvel", "subscription", "silver", "foo1 bar Inc.", "elasticsearc3h", 10, issueDateStr, expiredExpiryDateStr);
map.put(FeatureType.SHIELD, shildFeatureAttributes);
map.put(FeatureType.MARVEL, marvelFeatureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
ESLicenses esLicensesOutput = readLicensesFromString(licenseOutput);
ESLicenseManager esLicenseManager = new ESLicenseManager(esLicensesOutput, pubKeyPath, keyPass);
// All validation for shield license should be normal as expected
verifyLicenseManager(esLicenseManager, Collections.singletonMap(FeatureType.SHIELD, shildFeatureAttributes));
assertFalse("license for marvel should not be valid due to expired expiry date", esLicenseManager.hasLicenseForFeature(FeatureType.MARVEL));
}
@Test
public void testLicenseTampering() throws Exception {
Date issueDate = new Date();
String issueDateStr = DateUtils.dateStringFromLongDate(issueDate.getTime());
String expiryDateStr = DateUtils.dateStringFromLongDate(DateUtils.longExpiryDateFromDate(issueDate.getTime() + 24 * 60 * 60l));
Map<FeatureType, TestUtils.FeatureAttributes> map = new HashMap<>();
TestUtils.FeatureAttributes featureAttributes =
new TestUtils.FeatureAttributes("shield", "subscription", "platinum", "foo bar Inc.", "elasticsearch", 2, issueDateStr, expiryDateStr);
map.put(FeatureType.SHIELD, featureAttributes);
String licenseString = TestUtils.generateESLicenses(map);
String[] args = new String[8];
args[0] = "--license";
args[1] = licenseString;
args[2] = "--publicKeyPath";
args[3] = pubKeyPath;
args[4] = "--privateKeyPath";
args[5] = priKeyPath;
args[6] = "--keyPass";
args[7] = keyPass;
String licenseOutput = TestUtils.runLicenseGenerationTool(args);
ESLicenses esLicensesOutput = readLicensesFromString(licenseOutput);
ESLicenses.ESLicense esLicense = esLicensesOutput.get(FeatureType.SHIELD);
long originalExpiryDate = esLicense.expiryDate();
final ESLicenses.ESLicense tamperedLicense = LicenseBuilders.licenseBuilder(true)
.fromLicense(esLicense)
.expiryDate(esLicense.expiryDate() + 10 * 24 * 60 * 60 * 1000l)
.feature(FeatureType.SHIELD)
.issuer("elasticsqearch")
.build();
ESLicenses tamperedLicenses = LicenseBuilders.licensesBuilder().license(tamperedLicense).build();
ESLicenseManager esLicenseManager = null;
try {
esLicenseManager = new ESLicenseManager(tamperedLicenses, pubKeyPath, keyPass);
assertTrue("License manager should always report the original (signed) expiry date", esLicenseManager.getExpiryDateForLicense(FeatureType.SHIELD) == originalExpiryDate);
esLicenseManager.verifyLicenses();
fail();
} catch (InvalidLicenseException e) {
assertTrue("Exception should contain 'Invalid License' ", e.getMessage().contains("Invalid License"));
}
}
public static void verifyLicenseManager(ESLicenseManager esLicenseManager, Map<ESLicenses.FeatureType, TestUtils.FeatureAttributes> featureAttributeMap) throws ParseException {
for (Map.Entry<FeatureType, TestUtils.FeatureAttributes> entry : featureAttributeMap.entrySet()) {
TestUtils.FeatureAttributes featureAttributes = entry.getValue();
FeatureType featureType = entry.getKey();
assertTrue("License should have issuedTo of " + featureAttributes.issuedTo, esLicenseManager.getIssuedToForLicense(featureType).equals(featureAttributes.issuedTo));
assertTrue("License should have issuer of " + featureAttributes.issuer, esLicenseManager.getIssuerForLicense(featureType).equals(featureAttributes.issuer));
assertTrue("License should have issue date of " + DateUtils.longFromDateString(featureAttributes.issueDate), esLicenseManager.getIssueDateForLicense(featureType) == DateUtils.longFromDateString(featureAttributes.issueDate));
assertTrue("License should have expiry date of " + DateUtils.longExpiryDateFromString(featureAttributes.expiryDate), esLicenseManager.getExpiryDateForLicense(featureType) == DateUtils.longExpiryDateFromString(featureAttributes.expiryDate));
assertTrue("License should have type of " + featureAttributes.featureType, esLicenseManager.getTypeForLicense(featureType) == ESLicenses.Type.fromString(featureAttributes.type));
assertTrue("License should have subscription type of " + featureAttributes.subscriptionType, esLicenseManager.getSubscriptionTypeForLicense(featureType) == ESLicenses.SubscriptionType.fromString(featureAttributes.subscriptionType));
assertTrue("License should be valid for shield", esLicenseManager.hasLicenseForFeature(featureType));
assertTrue("License should be valid for maxNodes = " + (featureAttributes.maxNodes - 1), esLicenseManager.hasLicenseForNodes(featureType, featureAttributes.maxNodes - 1));
assertTrue("License should be valid for maxNodes = " + (featureAttributes.maxNodes), esLicenseManager.hasLicenseForNodes(featureType, featureAttributes.maxNodes));
assertFalse("License should not be valid for maxNodes = " + (featureAttributes.maxNodes + 1), esLicenseManager.hasLicenseForNodes(featureType, featureAttributes.maxNodes + 1));
}
}
}