REFACTOR: convert internal license feature to json blob
Original commit: elastic/x-pack-elasticsearch@21d99b2219
This commit is contained in:
parent
7f11de275f
commit
a57164ec67
|
@ -15,6 +15,7 @@ import java.util.TimeZone;
|
||||||
public class DateUtils {
|
public class DateUtils {
|
||||||
public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
|
public static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
|
||||||
|
|
||||||
|
//MutableDateTime.
|
||||||
private static DateFormat getDateFormat() {
|
private static DateFormat getDateFormat() {
|
||||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
dateFormat.setTimeZone(TIME_ZONE);
|
dateFormat.setTimeZone(TIME_ZONE);
|
||||||
|
|
|
@ -14,6 +14,9 @@ import net.nicholaswilliams.java.licensing.licensor.LicenseCreator;
|
||||||
import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties;
|
import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.elasticsearch.common.collect.ImmutableSet;
|
import org.elasticsearch.common.collect.ImmutableSet;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.license.core.ESLicense;
|
import org.elasticsearch.license.core.ESLicense;
|
||||||
import org.elasticsearch.license.core.LicensesCharset;
|
import org.elasticsearch.license.core.LicensesCharset;
|
||||||
|
|
||||||
|
@ -90,11 +93,11 @@ public class ESLicenseSigner {
|
||||||
.withIssueDate(licenseSpec.issueDate())
|
.withIssueDate(licenseSpec.issueDate())
|
||||||
.withProductKey(licenseSpec.uid())
|
.withProductKey(licenseSpec.uid())
|
||||||
.withHolder(licenseSpec.issuedTo())
|
.withHolder(licenseSpec.issuedTo())
|
||||||
.withIssuer(licenseSpec.issuer())
|
.withIssuer(licenseSpec.issuer());
|
||||||
.addFeature("feature:" + licenseSpec.feature(), licenseSpec.expiryDate())
|
|
||||||
.addFeature("maxNodes:" + String.valueOf(licenseSpec.maxNodes()))
|
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||||
.addFeature("type:" + licenseSpec.type().string())
|
featureToXContent(licenseSpec, contentBuilder);
|
||||||
.addFeature("subscription_type:" + licenseSpec.subscriptionType().string());
|
licenseBuilder.addFeature(contentBuilder.string());
|
||||||
|
|
||||||
final License license = licenseBuilder.build();
|
final License license = licenseBuilder.build();
|
||||||
|
|
||||||
|
@ -121,4 +124,13 @@ public class ESLicenseSigner {
|
||||||
.verifyAndBuild();
|
.verifyAndBuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void featureToXContent(ESLicense license, XContentBuilder builder) throws IOException {
|
||||||
|
builder.startObject();
|
||||||
|
builder.field("feature", license.feature());
|
||||||
|
builder.field("type", license.type().string());
|
||||||
|
builder.field("subscription_type", license.subscriptionType().string());
|
||||||
|
builder.field("max_nodes", license.maxNodes());
|
||||||
|
builder.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,19 +10,20 @@ import net.nicholaswilliams.java.licensing.encryption.Hasher;
|
||||||
import net.nicholaswilliams.java.licensing.encryption.PasswordProvider;
|
import net.nicholaswilliams.java.licensing.encryption.PasswordProvider;
|
||||||
import net.nicholaswilliams.java.licensing.exception.ExpiredLicenseException;
|
import net.nicholaswilliams.java.licensing.exception.ExpiredLicenseException;
|
||||||
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
|
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.elasticsearch.common.collect.ImmutableSet;
|
import org.elasticsearch.common.collect.ImmutableSet;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.license.core.ESLicense;
|
import org.elasticsearch.license.core.ESLicense;
|
||||||
import org.elasticsearch.license.core.ResourcePublicKeyDataProvider;
|
import org.elasticsearch.license.core.ResourcePublicKeyDataProvider;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.io.IOException;
|
||||||
import java.util.HashSet;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.elasticsearch.license.core.ESLicense.SubscriptionType;
|
import static org.elasticsearch.license.core.ESLicense.SubscriptionType;
|
||||||
import static org.elasticsearch.license.core.ESLicense.Type;
|
import static org.elasticsearch.license.core.ESLicense.Type;
|
||||||
import static org.elasticsearch.license.manager.Utils.extractSignedLicence;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class responsible for reading signed licenses, maintaining an effective esLicenses instance, verification of licenses
|
* Class responsible for reading signed licenses, maintaining an effective esLicenses instance, verification of licenses
|
||||||
|
@ -33,11 +34,11 @@ public class ESLicenseManager {
|
||||||
|
|
||||||
private final LicenseManager licenseManager;
|
private final LicenseManager licenseManager;
|
||||||
|
|
||||||
private static class Prefix {
|
private static class FeatureFields {
|
||||||
static final String MAX_NODES = "maxNodes:";
|
static final String MAX_NODES = "max_nodes";
|
||||||
static final String TYPE = "type:";
|
static final String TYPE = "type";
|
||||||
static final String SUBSCRIPTION_TYPE = "subscription_type:";
|
static final String SUBSCRIPTION_TYPE = "subscription_type";
|
||||||
static final String FEATURE = "feature:";
|
static final String FEATURE = "feature";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize LicenseManager
|
// Initialize LicenseManager
|
||||||
|
@ -89,27 +90,32 @@ public class ESLicenseManager {
|
||||||
}
|
}
|
||||||
} catch (ExpiredLicenseException e) {
|
} catch (ExpiredLicenseException e) {
|
||||||
throw new InvalidLicenseException("Expired License");
|
throw new InvalidLicenseException("Expired License");
|
||||||
} catch (InvalidLicenseException e) {
|
} catch (InvalidLicenseException | IOException e) {
|
||||||
throw new InvalidLicenseException("Invalid License");
|
throw new InvalidLicenseException("Invalid License");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ESLicense fromSignature(String signature) {
|
private ESLicense fromSignature(String signature) {
|
||||||
final SignedLicense signedLicense = Utils.extractSignedLicence(signature);
|
final SignedLicense signedLicense = extractSignedLicence(signature);
|
||||||
License license = licenseManager.decryptAndVerifyLicense(signedLicense);
|
License license = licenseManager.decryptAndVerifyLicense(signedLicense);
|
||||||
ESLicense.Builder builder = ESLicense.builder();
|
ESLicense.Builder builder = ESLicense.builder();
|
||||||
|
|
||||||
for (License.Feature feature : license.getFeatures()) {
|
for (License.Feature feature : license.getFeatures()) {
|
||||||
String featureName = feature.getName();
|
String featureName = feature.getName();
|
||||||
if (featureName.startsWith(Prefix.MAX_NODES)) {
|
LicenseFeatures licenseFeatures;
|
||||||
builder.maxNodes(Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length())));
|
try {
|
||||||
} else if (featureName.startsWith(Prefix.TYPE)) {
|
licenseFeatures = licenseFeaturesFromSource(featureName);
|
||||||
builder.type(Type.fromString(featureName.substring(Prefix.TYPE.length())));
|
if (licenseFeatures.maxNodes != -1
|
||||||
} else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) {
|
&& licenseFeatures.feature != null
|
||||||
builder.subscriptionType(SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length())));
|
&& licenseFeatures.type != null
|
||||||
} else if (featureName.startsWith(Prefix.FEATURE)) {
|
&& licenseFeatures.subscriptionType != null) {
|
||||||
builder.feature(featureName.substring(Prefix.FEATURE.length()));
|
builder.maxNodes(licenseFeatures.maxNodes)
|
||||||
|
.feature(licenseFeatures.feature)
|
||||||
|
.type(Type.fromString(licenseFeatures.type))
|
||||||
|
.subscriptionType(SubscriptionType.fromString(licenseFeatures.subscriptionType));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder
|
return builder
|
||||||
|
@ -122,12 +128,11 @@ public class ESLicenseManager {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void verifyLicenseFields(License license, ESLicense eslicense) {
|
private static void verifyLicenseFields(License license, ESLicense eslicense) throws IOException {
|
||||||
boolean licenseValid = license.getProductKey().equals(eslicense.uid())
|
boolean licenseValid = license.getProductKey().equals(eslicense.uid())
|
||||||
&& license.getHolder().equals(eslicense.issuedTo())
|
&& license.getHolder().equals(eslicense.issuedTo())
|
||||||
&& license.getIssueDate() == eslicense.issueDate()
|
&& license.getIssueDate() == eslicense.issueDate()
|
||||||
&& license.getGoodBeforeDate() == eslicense.expiryDate();
|
&& license.getGoodBeforeDate() == eslicense.expiryDate();
|
||||||
assert license.getFeatures().size() == 4 : "one license should have only four features";
|
|
||||||
boolean maxNodesValid = false;
|
boolean maxNodesValid = false;
|
||||||
boolean featureValid = false;
|
boolean featureValid = false;
|
||||||
boolean typeValid = false;
|
boolean typeValid = false;
|
||||||
|
@ -135,16 +140,14 @@ public class ESLicenseManager {
|
||||||
|
|
||||||
for (License.Feature feature : license.getFeatures()) {
|
for (License.Feature feature : license.getFeatures()) {
|
||||||
String featureName = feature.getName();
|
String featureName = feature.getName();
|
||||||
if (featureName.startsWith(Prefix.MAX_NODES)) {
|
LicenseFeatures licenseFeatures = licenseFeaturesFromSource(featureName);
|
||||||
maxNodesValid = eslicense.maxNodes() == Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length()));
|
maxNodesValid = eslicense.maxNodes() == licenseFeatures.maxNodes;
|
||||||
} else if (featureName.startsWith(Prefix.TYPE)) {
|
typeValid = eslicense.type().string().equals(licenseFeatures.type);
|
||||||
typeValid = eslicense.type() == Type.fromString(featureName.substring(Prefix.TYPE.length()));
|
subscriptionTypeValid = eslicense.subscriptionType().string().equals(licenseFeatures.subscriptionType);
|
||||||
} else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) {
|
featureValid = eslicense.feature().equals(licenseFeatures.feature);
|
||||||
subscriptionTypeValid = eslicense.subscriptionType() == SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length()));
|
|
||||||
} else if (featureName.startsWith(Prefix.FEATURE)) {
|
if (maxNodesValid && typeValid && subscriptionTypeValid && featureValid) {
|
||||||
String featureValue = featureName.substring(Prefix.FEATURE.length());
|
break;
|
||||||
featureValid = featureValue.equals(eslicense.feature())
|
|
||||||
&& feature.getGoodBeforeDate() == eslicense.expiryDate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!licenseValid || !featureValid || !maxNodesValid || !typeValid || !subscriptionTypeValid) {
|
if (!licenseValid || !featureValid || !maxNodesValid || !typeValid || !subscriptionTypeValid) {
|
||||||
|
@ -152,6 +155,73 @@ public class ESLicenseManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
private static SignedLicense extractSignedLicence(String signature) {
|
||||||
|
byte[] signatureBytes = Base64.decodeBase64(signature);
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
|
||||||
|
byteBuffer = (ByteBuffer) byteBuffer.position(13);
|
||||||
|
int start = byteBuffer.getInt();
|
||||||
|
int version = byteBuffer.getInt();
|
||||||
|
return new ObjectSerializer().readObject(SignedLicense.class, Arrays.copyOfRange(signatureBytes, start, signatureBytes.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LicenseFeatures licenseFeaturesFromSource(String source) throws IOException {
|
||||||
|
XContentParser parser = XContentFactory.xContent(source).createParser(source);
|
||||||
|
XContentParser.Token token;
|
||||||
|
|
||||||
|
String feature = null;
|
||||||
|
String type = null;
|
||||||
|
String subscriptionType = null;
|
||||||
|
int maxNodes = -1;
|
||||||
|
|
||||||
|
String currentName = null;
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
|
||||||
|
if (token == XContentParser.Token.FIELD_NAME) {
|
||||||
|
currentName = parser.currentName();
|
||||||
|
} else if (token == XContentParser.Token.VALUE_STRING) {
|
||||||
|
switch (currentName) {
|
||||||
|
case FeatureFields.FEATURE:
|
||||||
|
feature = parser.text();
|
||||||
|
break;
|
||||||
|
case FeatureFields.TYPE:
|
||||||
|
type = parser.text();
|
||||||
|
break;
|
||||||
|
case FeatureFields.SUBSCRIPTION_TYPE:
|
||||||
|
subscriptionType = parser.text();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (token == XContentParser.Token.VALUE_NUMBER) {
|
||||||
|
if (FeatureFields.MAX_NODES.equals(currentName)) {
|
||||||
|
maxNodes = parser.intValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new LicenseFeatures(feature, type, subscriptionType, maxNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LicenseFeatures {
|
||||||
|
private final String feature;
|
||||||
|
private final String type;
|
||||||
|
private final String subscriptionType;
|
||||||
|
private final int maxNodes;
|
||||||
|
|
||||||
|
private LicenseFeatures(String feature, String type, String subscriptionType, int maxNodes) {
|
||||||
|
this.feature = feature;
|
||||||
|
this.type = type;
|
||||||
|
this.subscriptionType = subscriptionType;
|
||||||
|
this.maxNodes = maxNodes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Need a better password management
|
// TODO: Need a better password management
|
||||||
private static class ESPublicKeyPasswordProvider implements PasswordProvider {
|
private static class ESPublicKeyPasswordProvider implements PasswordProvider {
|
||||||
private final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
|
private final String DEFAULT_PASS_PHRASE = "elasticsearch-license";
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.ObjectSerializer;
|
|
||||||
import net.nicholaswilliams.java.licensing.SignedLicense;
|
|
||||||
import org.apache.commons.codec.binary.Base64;
|
|
||||||
import org.elasticsearch.common.collect.ImmutableMap;
|
|
||||||
import org.elasticsearch.license.core.ESLicense;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public final class Utils {
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
public static SignedLicense extractSignedLicence(String signature) {
|
|
||||||
byte[] signatureBytes = Base64.decodeBase64(signature);
|
|
||||||
ByteBuffer byteBuffer = ByteBuffer.wrap(signatureBytes);
|
|
||||||
byteBuffer = (ByteBuffer) byteBuffer.position(13);
|
|
||||||
int start = byteBuffer.getInt();
|
|
||||||
int version = byteBuffer.getInt();
|
|
||||||
return new ObjectSerializer().readObject(SignedLicense.class, Arrays.copyOfRange(signatureBytes, start, signatureBytes.length));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -152,6 +152,7 @@ public class LicenseVerificationTests extends AbstractLicensingTestBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to TestUtils
|
||||||
public static void verifyLicenses(FileBasedESLicenseProvider licenseProvider, Map<String, TestUtils.FeatureAttributes> featureAttributeMap) throws ParseException {
|
public static void verifyLicenses(FileBasedESLicenseProvider licenseProvider, Map<String, TestUtils.FeatureAttributes> featureAttributeMap) throws ParseException {
|
||||||
|
|
||||||
for (Map.Entry<String, TestUtils.FeatureAttributes> entry : featureAttributeMap.entrySet()) {
|
for (Map.Entry<String, TestUtils.FeatureAttributes> entry : featureAttributeMap.entrySet()) {
|
||||||
|
|
Loading…
Reference in New Issue