Prevent nodes from joining a non-TLS enabled cluster with a production license (elastic/x-pack-elasticsearch#2484)

This change prevents a node from joining a cluster with a production license (gold, platinum, standard) iff the cluster doesn't have TLS setup. This is mainly a BWC oriented change that prevents joining old 5.x clusters without a TLS setup.

Relates to elastic/x-pack-elasticsearch#2463

Original commit: elastic/x-pack-elasticsearch@21f5a58472
This commit is contained in:
Simon Willnauer 2017-09-13 20:40:35 +02:00 committed by GitHub
parent 2e3aca414b
commit 0680e41f36
6 changed files with 109 additions and 9 deletions

View File

@ -271,7 +271,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
}
public License getLicense() {
final License license = getLicense(clusterService.state().metaData().custom(LicensesMetaData.TYPE));
final License license = getLicense(clusterService.state());
return license == LicensesMetaData.LICENSE_TOMBSTONE ? null : license;
}
@ -469,7 +469,12 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
};
}
License getLicense(final LicensesMetaData metaData) {
public static License getLicense(final ClusterState state) {
final LicensesMetaData licensesMetaData = state.metaData().custom(LicensesMetaData.TYPE);
return getLicense(licensesMetaData);
}
static License getLicense(final LicensesMetaData metaData) {
if (metaData != null) {
License license = metaData.getLicense();
if (license == LicensesMetaData.LICENSE_TOMBSTONE) {

View File

@ -16,6 +16,7 @@ import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Binder;
@ -43,6 +44,7 @@ import org.elasticsearch.license.Licensing;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.ClusterPlugin;
import org.elasticsearch.plugins.DiscoveryPlugin;
import org.elasticsearch.plugins.IngestPlugin;
import org.elasticsearch.plugins.NetworkPlugin;
import org.elasticsearch.plugins.Plugin;
@ -124,6 +126,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@ -131,7 +134,7 @@ import java.util.stream.Stream;
import static org.elasticsearch.xpack.watcher.Watcher.ENCRYPT_SENSITIVE_DATA_SETTING;
public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin {
public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin, DiscoveryPlugin {
public static final String NAME = "x-pack";
@ -605,4 +608,9 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
public Map<String, Supplier<ClusterState.Custom>> getInitialClusterStateCustomSupplier() {
return security.getInitialClusterStateCustomSupplier();
}
@Override
public BiConsumer<DiscoveryNode, ClusterState> getJoinValidator() {
return security.getJoinValidator();
}
}

View File

@ -60,7 +60,7 @@ public class XPackSettings {
* Legacy setting for enabling or disabling transport ssl. Defaults to true. This is just here to make upgrading easier since the
* user needs to set this setting in 5.x to upgrade
*/
private static final Setting<Boolean> TRANSPORT_SSL_ENABLED =
public static final Setting<Boolean> TRANSPORT_SSL_ENABLED =
new Setting<>("xpack.security.transport.ssl.enabled", (s) -> Boolean.toString(true),
(s) -> {
final boolean parsed = Booleans.parseBoolean(s);

View File

@ -15,10 +15,10 @@ import org.elasticsearch.action.support.ActionFilter;
import org.elasticsearch.action.support.DestructiveOperations;
import org.elasticsearch.bootstrap.BootstrapCheck;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.LocalNodeMasterListener;
import org.elasticsearch.cluster.NamedDiff;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.metadata.IndexTemplateMetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Booleans;
@ -51,9 +51,12 @@ import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.index.IndexModule;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
import org.elasticsearch.ingest.Processor;
import org.elasticsearch.license.License;
import org.elasticsearch.license.LicenseService;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.ClusterPlugin;
import org.elasticsearch.plugins.DiscoveryPlugin;
import org.elasticsearch.plugins.IngestPlugin;
import org.elasticsearch.plugins.NetworkPlugin;
import org.elasticsearch.rest.RestController;
@ -194,7 +197,7 @@ import static java.util.Collections.singletonList;
import static org.elasticsearch.xpack.XPackSettings.HTTP_SSL_ENABLED;
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_TEMPLATE_NAME;
public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin {
public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin, DiscoveryPlugin {
private static final Logger logger = Loggers.getLogger(XPackPlugin.class);
@ -900,4 +903,47 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
return Collections.emptyMap();
}
}
@Override
public BiConsumer<DiscoveryNode, ClusterState> getJoinValidator() {
return enabled ? new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings)) : null;
}
static final class ValidateTLSOnJoin implements BiConsumer<DiscoveryNode, ClusterState> {
private final boolean isTLSEnabled;
ValidateTLSOnJoin(boolean isTLSEnabled) {
this.isTLSEnabled = isTLSEnabled;
}
@Override
public void accept(DiscoveryNode node, ClusterState state) {
License license = LicenseService.getLicense(state);
validateLicense(license);
}
void validateLicense(License license) {
if (license != null) {
switch (license.operationMode()) {
case MISSING:
case TRIAL:
case BASIC:
break;
case STANDARD:
case GOLD:
case PLATINUM:
if (isTLSEnabled == false) {
throw new IllegalStateException("TLS setup is required for license type [" + license.operationMode().name()
+ "]");
}
break;
default:
throw new AssertionError("unknown operation mode: " + license.operationMode());
}
}
}
}
}

View File

@ -77,19 +77,19 @@ public class LicensesManagerServiceTests extends XPackSingleNodeTestCase {
// put gold license
TestUtils.registerAndAckSignedLicenses(licenseService, goldLicense, LicensesStatus.VALID);
LicensesMetaData licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licenseService.getLicense(licensesMetaData), equalTo(goldLicense));
assertThat(LicenseService.getLicense(licensesMetaData), equalTo(goldLicense));
License platinumLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueSeconds(3));
// put platinum license
TestUtils.registerAndAckSignedLicenses(licenseService, platinumLicense, LicensesStatus.VALID);
licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licenseService.getLicense(licensesMetaData), equalTo(platinumLicense));
assertThat(LicenseService.getLicense(licensesMetaData), equalTo(platinumLicense));
License basicLicense = TestUtils.generateSignedLicense("basic", TimeValue.timeValueSeconds(3));
// put basic license
TestUtils.registerAndAckSignedLicenses(licenseService, basicLicense, LicensesStatus.VALID);
licensesMetaData = clusterService.state().metaData().custom(LicensesMetaData.TYPE);
assertThat(licenseService.getLicense(licensesMetaData), equalTo(basicLicense));
assertThat(LicenseService.getLicense(licensesMetaData), equalTo(basicLicense));
}
public void testInvalidLicenseStorage() throws Exception {

View File

@ -8,18 +8,28 @@ package org.elasticsearch.xpack.security;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import org.elasticsearch.Version;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.network.NetworkModule;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.License;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
@ -191,4 +201,35 @@ public class SecurityTests extends ESTestCase {
assertThat(filter, hasItem(Security.setting("authc.realms.*.ssl.truststore.path")));
assertThat(filter, hasItem(Security.setting("authc.realms.*.ssl.truststore.algorithm")));
}
public void testTLSJoinValidatorOnDisabledSecurity() throws Exception {
Settings disabledSettings = Settings.builder().put("xpack.security.enabled", false).build();
createComponents(disabledSettings);
BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
assertNull(joinValidator);
}
public void testTLSJoinValidator() throws Exception {
createComponents(Settings.EMPTY);
BiConsumer<DiscoveryNode, ClusterState> joinValidator = security.getJoinValidator();
assertNotNull(joinValidator);
ClusterState state = ClusterState.builder(ClusterName.DEFAULT).build();
joinValidator.accept(new DiscoveryNode("foo", buildNewFakeTransportAddress(), Version.CURRENT), state);
assertTrue(joinValidator instanceof Security.ValidateTLSOnJoin);
int numIters = randomIntBetween(1,10);
for (int i = 0; i < numIters; i++) {
boolean tlsOn = randomBoolean();
Security.ValidateTLSOnJoin validator = new Security.ValidateTLSOnJoin(tlsOn);
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
EnumSet<License.OperationMode> productionModes = EnumSet.of(License.OperationMode.GOLD, License.OperationMode.PLATINUM,
License.OperationMode.STANDARD);
if (productionModes.contains(license.operationMode()) && tlsOn == false) {
IllegalStateException ise = expectThrows(IllegalStateException.class, () -> validator.validateLicense(license));
assertEquals("TLS setup is required for license type [" + license.operationMode().name() + "]", ise.getMessage());
} else {
validator.validateLicense(license);
}
validator.validateLicense(null);
}
}
}