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 static final TimeZone TIME_ZONE = TimeZone.getTimeZone("UTC");
|
||||
|
||||
//MutableDateTime.
|
||||
private static DateFormat getDateFormat() {
|
||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
dateFormat.setTimeZone(TIME_ZONE);
|
||||
|
|
|
@ -14,6 +14,9 @@ import net.nicholaswilliams.java.licensing.licensor.LicenseCreator;
|
|||
import net.nicholaswilliams.java.licensing.licensor.LicenseCreatorProperties;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
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.LicensesCharset;
|
||||
|
||||
|
@ -90,11 +93,11 @@ public class ESLicenseSigner {
|
|||
.withIssueDate(licenseSpec.issueDate())
|
||||
.withProductKey(licenseSpec.uid())
|
||||
.withHolder(licenseSpec.issuedTo())
|
||||
.withIssuer(licenseSpec.issuer())
|
||||
.addFeature("feature:" + licenseSpec.feature(), licenseSpec.expiryDate())
|
||||
.addFeature("maxNodes:" + String.valueOf(licenseSpec.maxNodes()))
|
||||
.addFeature("type:" + licenseSpec.type().string())
|
||||
.addFeature("subscription_type:" + licenseSpec.subscriptionType().string());
|
||||
.withIssuer(licenseSpec.issuer());
|
||||
|
||||
XContentBuilder contentBuilder = XContentFactory.contentBuilder(XContentType.JSON);
|
||||
featureToXContent(licenseSpec, contentBuilder);
|
||||
licenseBuilder.addFeature(contentBuilder.string());
|
||||
|
||||
final License license = licenseBuilder.build();
|
||||
|
||||
|
@ -121,4 +124,13 @@ public class ESLicenseSigner {
|
|||
.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.exception.ExpiredLicenseException;
|
||||
import net.nicholaswilliams.java.licensing.exception.InvalidLicenseException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.elasticsearch.common.collect.ImmutableSet;
|
||||
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.ResourcePublicKeyDataProvider;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
import static org.elasticsearch.license.core.ESLicense.SubscriptionType;
|
||||
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
|
||||
|
@ -33,11 +34,11 @@ public class ESLicenseManager {
|
|||
|
||||
private final LicenseManager licenseManager;
|
||||
|
||||
private static class Prefix {
|
||||
static final String MAX_NODES = "maxNodes:";
|
||||
static final String TYPE = "type:";
|
||||
static final String SUBSCRIPTION_TYPE = "subscription_type:";
|
||||
static final String FEATURE = "feature:";
|
||||
private static class FeatureFields {
|
||||
static final String MAX_NODES = "max_nodes";
|
||||
static final String TYPE = "type";
|
||||
static final String SUBSCRIPTION_TYPE = "subscription_type";
|
||||
static final String FEATURE = "feature";
|
||||
}
|
||||
|
||||
// Initialize LicenseManager
|
||||
|
@ -89,27 +90,32 @@ public class ESLicenseManager {
|
|||
}
|
||||
} catch (ExpiredLicenseException e) {
|
||||
throw new InvalidLicenseException("Expired License");
|
||||
} catch (InvalidLicenseException e) {
|
||||
} catch (InvalidLicenseException | IOException e) {
|
||||
throw new InvalidLicenseException("Invalid License");
|
||||
}
|
||||
}
|
||||
|
||||
private ESLicense fromSignature(String signature) {
|
||||
final SignedLicense signedLicense = Utils.extractSignedLicence(signature);
|
||||
final SignedLicense signedLicense = extractSignedLicence(signature);
|
||||
License license = licenseManager.decryptAndVerifyLicense(signedLicense);
|
||||
ESLicense.Builder builder = ESLicense.builder();
|
||||
|
||||
for (License.Feature feature : license.getFeatures()) {
|
||||
String featureName = feature.getName();
|
||||
if (featureName.startsWith(Prefix.MAX_NODES)) {
|
||||
builder.maxNodes(Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length())));
|
||||
} else if (featureName.startsWith(Prefix.TYPE)) {
|
||||
builder.type(Type.fromString(featureName.substring(Prefix.TYPE.length())));
|
||||
} else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) {
|
||||
builder.subscriptionType(SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length())));
|
||||
} else if (featureName.startsWith(Prefix.FEATURE)) {
|
||||
builder.feature(featureName.substring(Prefix.FEATURE.length()));
|
||||
}
|
||||
LicenseFeatures licenseFeatures;
|
||||
try {
|
||||
licenseFeatures = licenseFeaturesFromSource(featureName);
|
||||
if (licenseFeatures.maxNodes != -1
|
||||
&& licenseFeatures.feature != null
|
||||
&& licenseFeatures.type != null
|
||||
&& licenseFeatures.subscriptionType != null) {
|
||||
builder.maxNodes(licenseFeatures.maxNodes)
|
||||
.feature(licenseFeatures.feature)
|
||||
.type(Type.fromString(licenseFeatures.type))
|
||||
.subscriptionType(SubscriptionType.fromString(licenseFeatures.subscriptionType));
|
||||
break;
|
||||
}
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
return builder
|
||||
|
@ -122,12 +128,11 @@ public class ESLicenseManager {
|
|||
.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())
|
||||
&& license.getHolder().equals(eslicense.issuedTo())
|
||||
&& license.getIssueDate() == eslicense.issueDate()
|
||||
&& license.getGoodBeforeDate() == eslicense.expiryDate();
|
||||
assert license.getFeatures().size() == 4 : "one license should have only four features";
|
||||
boolean maxNodesValid = false;
|
||||
boolean featureValid = false;
|
||||
boolean typeValid = false;
|
||||
|
@ -135,16 +140,14 @@ public class ESLicenseManager {
|
|||
|
||||
for (License.Feature feature : license.getFeatures()) {
|
||||
String featureName = feature.getName();
|
||||
if (featureName.startsWith(Prefix.MAX_NODES)) {
|
||||
maxNodesValid = eslicense.maxNodes() == Integer.parseInt(featureName.substring(Prefix.MAX_NODES.length()));
|
||||
} else if (featureName.startsWith(Prefix.TYPE)) {
|
||||
typeValid = eslicense.type() == Type.fromString(featureName.substring(Prefix.TYPE.length()));
|
||||
} else if (featureName.startsWith(Prefix.SUBSCRIPTION_TYPE)) {
|
||||
subscriptionTypeValid = eslicense.subscriptionType() == SubscriptionType.fromString(featureName.substring(Prefix.SUBSCRIPTION_TYPE.length()));
|
||||
} else if (featureName.startsWith(Prefix.FEATURE)) {
|
||||
String featureValue = featureName.substring(Prefix.FEATURE.length());
|
||||
featureValid = featureValue.equals(eslicense.feature())
|
||||
&& feature.getGoodBeforeDate() == eslicense.expiryDate();
|
||||
LicenseFeatures licenseFeatures = licenseFeaturesFromSource(featureName);
|
||||
maxNodesValid = eslicense.maxNodes() == licenseFeatures.maxNodes;
|
||||
typeValid = eslicense.type().string().equals(licenseFeatures.type);
|
||||
subscriptionTypeValid = eslicense.subscriptionType().string().equals(licenseFeatures.subscriptionType);
|
||||
featureValid = eslicense.feature().equals(licenseFeatures.feature);
|
||||
|
||||
if (maxNodesValid && typeValid && subscriptionTypeValid && featureValid) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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
|
||||
private static class ESPublicKeyPasswordProvider implements PasswordProvider {
|
||||
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 {
|
||||
|
||||
for (Map.Entry<String, TestUtils.FeatureAttributes> entry : featureAttributeMap.entrySet()) {
|
||||
|
|
Loading…
Reference in New Issue