Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting to restrict license types #49418

Merged
merged 6 commits into from
Dec 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,23 @@ public static LicenseType parse(String type) throws IllegalArgumentException {
/**
* Backward compatible license type parsing for older license models
*/
public static LicenseType resolve(String name) {
public static LicenseType resolve(License license) {
if (license.version == VERSION_START) {
// in 1.x: the acceptable values for 'subscription_type': none | dev | silver | gold | platinum
return resolve(license.subscriptionType);
} else {
// in 2.x: the acceptable values for 'type': trial | basic | silver | dev | gold | platinum
// in 5.x: the acceptable values for 'type': trial | basic | standard | dev | gold | platinum
// in 6.x: the acceptable values for 'type': trial | basic | standard | dev | gold | platinum
// in 7.x: the acceptable values for 'type': trial | basic | standard | dev | gold | platinum | enterprise
return resolve(license.type);
}
}

/**
* Backward compatible license type parsing for older license models
*/
static LicenseType resolve(String name) {
switch (name.toLowerCase(Locale.ROOT)) {
case "missing":
return null;
Expand Down Expand Up @@ -165,8 +181,12 @@ public static int compare(OperationMode opMode1, OperationMode opMode2) {
return Integer.compare(opMode1.id, opMode2.id);
}

public static OperationMode resolve(String typeName) {
LicenseType type = LicenseType.resolve(typeName);
/**
* Determine the operating mode for a license type
* @see LicenseType#resolve(License)
* @see #parse(String)
*/
public static OperationMode resolve(LicenseType type) {
if (type == null) {
return MISSING;
}
Expand All @@ -187,6 +207,21 @@ public static OperationMode resolve(String typeName) {
}
}

/**
* Parses an {@code OperatingMode} from a String.
* The string must name an operating mode, and not a licensing level (that is, it cannot parse old style license levels
* such as "dev" or "silver").
* @see #description()
*/
public static OperationMode parse(String mode) {
try {
return OperationMode.valueOf(mode.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("unrecognised license operating mode [ " + mode + "], supported modes are ["
+ Stream.of(values()).map(OperationMode::description).collect(Collectors.joining(",")) + "]");
}
}

public String description() {
return name().toLowerCase(Locale.ROOT);
}
Expand All @@ -212,13 +247,7 @@ private License(int version, String uid, String issuer, String issuedTo, long is
}
this.maxNodes = maxNodes;
this.startDate = startDate;
if (version == VERSION_START) {
// in 1.x: the acceptable values for 'subscription_type': none | dev | silver | gold | platinum
this.operationMode = OperationMode.resolve(subscriptionType);
} else {
// in 2.x: the acceptable values for 'type': trial | basic | silver | dev | gold | platinum
this.operationMode = OperationMode.resolve(type);
}
this.operationMode = OperationMode.resolve(LicenseType.resolve(this));
validate();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* Service responsible for managing {@link LicensesMetaData}.
Expand All @@ -64,6 +65,12 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
return SelfGeneratedLicense.validateSelfGeneratedType(type);
}, Setting.Property.NodeScope);

static final List<License.LicenseType> ALLOWABLE_UPLOAD_TYPES = getAllowableUploadTypes();

public static final Setting<List<License.LicenseType>> ALLOWED_LICENSE_TYPES_SETTING = Setting.listSetting("xpack.license.upload.types",
ALLOWABLE_UPLOAD_TYPES.stream().map(License.LicenseType::getTypeName).collect(Collectors.toUnmodifiableList()),
License.LicenseType::parse, LicenseService::validateUploadTypesSetting, Setting.Property.NodeScope);

// pkg private for tests
static final TimeValue NON_BASIC_SELF_GENERATED_LICENSE_DURATION = TimeValue.timeValueHours(30 * 24);

Expand Down Expand Up @@ -104,6 +111,12 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
*/
private List<ExpirationCallback> expirationCallbacks = new ArrayList<>();

/**
* Which license types are permitted to be uploaded to the cluster
* @see #ALLOWED_LICENSE_TYPES_SETTING
*/
private final List<License.LicenseType> allowedLicenseTypes;

/**
* Max number of nodes licensed by generated trial license
*/
Expand All @@ -123,6 +136,7 @@ public LicenseService(Settings settings, ClusterService clusterService, Clock cl
this.clock = clock;
this.scheduler = new SchedulerEngine(settings, clock);
this.licenseState = licenseState;
this.allowedLicenseTypes = ALLOWED_LICENSE_TYPES_SETTING.get(settings);
this.operationModeFileWatcher = new OperationModeFileWatcher(resourceWatcherService,
XPackPlugin.resolveConfigFile(env, "license_mode"), logger,
() -> updateLicenseState(getLicensesMetaData()));
Expand Down Expand Up @@ -196,8 +210,20 @@ public void registerLicense(final PutLicenseRequest request, final ActionListene
final long now = clock.millis();
if (!LicenseVerifier.verifyLicense(newLicense) || newLicense.issueDate() > now || newLicense.startDate() > now) {
listener.onResponse(new PutLicenseResponse(true, LicensesStatus.INVALID));
} else if (newLicense.type().equals(License.LicenseType.BASIC.getTypeName())) {
return;
}
final License.LicenseType licenseType;
try {
licenseType = License.LicenseType.resolve(newLicense);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not consume any part of the license before we validate its signature first

} catch (Exception e) {
listener.onFailure(e);
return;
}
if (licenseType == License.LicenseType.BASIC) {
listener.onFailure(new IllegalArgumentException("Registering basic licenses is not allowed."));
} else if (isAllowedLicenseType(licenseType) == false) {
listener.onFailure(new IllegalArgumentException(
"Registering [" + licenseType.getTypeName() + "] licenses is not allowed on this cluster"));
} else if (newLicense.expiryDate() < now) {
listener.onResponse(new PutLicenseResponse(true, LicensesStatus.EXPIRED));
} else {
Expand Down Expand Up @@ -272,6 +298,11 @@ private static boolean licenseIsCompatible(License license, Version version) {
}
}

private boolean isAllowedLicenseType(License.LicenseType type) {
logger.debug("Checking license [{}] against allowed license types: {}", type, allowedLicenseTypes);
return allowedLicenseTypes.contains(type);
}

public static Map<String, String[]> getAckMessages(License newLicense, License currentLicense) {
Map<String, String[]> acknowledgeMessages = new HashMap<>();
if (!License.isAutoGeneratedLicense(currentLicense.signature()) // current license is not auto-generated
Expand Down Expand Up @@ -574,4 +605,20 @@ private static boolean isProductionMode(Settings settings, DiscoveryNode localNo
private static boolean isBoundToLoopback(DiscoveryNode localNode) {
return localNode.getAddress().address().getAddress().isLoopbackAddress();
}

private static List<License.LicenseType> getAllowableUploadTypes() {
return Stream.of(License.LicenseType.values())
.filter(t -> t != License.LicenseType.BASIC)
.collect(Collectors.toUnmodifiableList());
}

private static void validateUploadTypesSetting(List<License.LicenseType> value) {
if (ALLOWABLE_UPLOAD_TYPES.containsAll(value) == false) {
throw new IllegalArgumentException("Invalid value [" +
value.stream().map(License.LicenseType::getTypeName).collect(Collectors.joining(",")) +
"] for " + ALLOWED_LICENSE_TYPES_SETTING.getKey() + ", allowed values are [" +
ALLOWABLE_UPLOAD_TYPES.stream().map(License.LicenseType::getTypeName).collect(Collectors.joining(",")) +
"]");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private synchronized void onChange(Path file) {
// this UTF-8 conversion is much pickier than java String
final String operationMode = new BytesRef(content).utf8ToString();
try {
newOperationMode = OperationMode.resolve(operationMode);
newOperationMode = OperationMode.parse(operationMode);
} catch (IllegalArgumentException e) {
logger.error(
(Supplier<?>) () -> new ParameterizedMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public RemoteClusterLicenseChecker(final Client client, final Predicate<License.
}

public static boolean isLicensePlatinumOrTrial(final XPackInfoResponse.LicenseInfo licenseInfo) {
final License.OperationMode mode = License.OperationMode.resolve(licenseInfo.getMode());
final License.OperationMode mode = License.OperationMode.parse(licenseInfo.getMode());
return mode == License.OperationMode.PLATINUM || mode == License.OperationMode.TRIAL;
}

Expand Down Expand Up @@ -168,7 +168,7 @@ public void onResponse(final XPackInfoResponse xPackInfoResponse) {
return;
}
if ((licenseInfo.getStatus() == LicenseStatus.ACTIVE) == false
|| predicate.test(License.OperationMode.resolve(licenseInfo.getMode())) == false) {
|| predicate.test(License.OperationMode.parse(licenseInfo.getMode())) == false) {
listener.onResponse(LicenseCheck.failure(new RemoteClusterLicenseInfo(clusterAlias.get(), licenseInfo)));
return;
}
Expand Down Expand Up @@ -282,7 +282,7 @@ public static String buildErrorMessage(
final String message = String.format(
Locale.ROOT,
"the license mode [%s] on cluster [%s] does not enable [%s]",
License.OperationMode.resolve(remoteClusterLicenseInfo.licenseInfo().getMode()),
License.OperationMode.parse(remoteClusterLicenseInfo.licenseInfo().getMode()),
remoteClusterLicenseInfo.clusterAlias(),
feature);
error.append(message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ public List<Setting<?>> getSettings() {
settings.addAll(XPackSettings.getAllSettings());

settings.add(LicenseService.SELF_GENERATED_LICENSE_TYPE);
settings.add(LicenseService.ALLOWED_LICENSE_TYPES_SETTING);

// we add the `xpack.version` setting to all internal indices
settings.add(Setting.simpleString("index.xpack.version", Setting.Property.IndexScope));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static TrainedModelConfig.Builder fromXContent(XContentParser parser, boo
throw new IllegalArgumentException("[" + ESTIMATED_OPERATIONS.getPreferredName() + "] must be greater than or equal to 0");
}
this.estimatedOperations = estimatedOperations;
this.licenseLevel = License.OperationMode.resolve(ExceptionsHelper.requireNonNull(licenseLevel, LICENSE_LEVEL));
this.licenseLevel = License.OperationMode.parse(ExceptionsHelper.requireNonNull(licenseLevel, LICENSE_LEVEL));
}

public TrainedModelConfig(StreamInput in) throws IOException {
Expand All @@ -153,7 +153,7 @@ public TrainedModelConfig(StreamInput in) throws IOException {
input = new TrainedModelInput(in);
estimatedHeapMemory = in.readVLong();
estimatedOperations = in.readVLong();
licenseLevel = License.OperationMode.resolve(in.readString());
licenseLevel = License.OperationMode.parse(in.readString());
}

public String getModelId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public void testFIPSCheckWithAllowedLicense() throws Exception {
licenseService.start();
PlainActionFuture<PutLicenseResponse> responseFuture = new PlainActionFuture<>();
licenseService.registerLicense(request, responseFuture);
if (responseFuture.isDone()) {
// If the future is done, it means request/license validation failed.
// In which case, this `actionGet` should throw a more useful exception than the verify below.
responseFuture.actionGet();
}
verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
}

Expand Down Expand Up @@ -67,6 +72,11 @@ public void testFIPSCheckWithoutAllowedLicense() throws Exception {
setInitialState(null, licenseState, settings);
licenseService.start();
licenseService.registerLicense(request, responseFuture);
if (responseFuture.isDone()) {
// If the future is done, it means request/license validation failed.
// In which case, this `actionGet` should throw a more useful exception than the verify below.
responseFuture.actionGet();
}
verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public void testResolveUnknown() {

for (String type : types) {
try {
OperationMode.resolve(type);
final License.LicenseType licenseType = License.LicenseType.resolve(type);
OperationMode.resolve(licenseType);

fail(String.format(Locale.ROOT, "[%s] should not be recognized as an operation mode", type));
}
Expand All @@ -69,7 +70,8 @@ public void testResolveUnknown() {

private static void assertResolve(OperationMode expected, String... types) {
for (String type : types) {
assertThat(OperationMode.resolve(type), equalTo(expected));
License.LicenseType licenseType = License.LicenseType.resolve(type);
assertThat(OperationMode.resolve(licenseType), equalTo(expected));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public void init() throws Exception {
}

public void testLicenseOperationModeUpdate() throws Exception {
String type = randomFrom("trial", "basic", "standard", "gold", "platinum");
License.LicenseType type = randomFrom(License.LicenseType.values());
License license = License.builder()
.uid("id")
.expiryDate(0)
Expand Down
Loading