diff --git a/plugin/src/main/java/org/elasticsearch/license/LicenseService.java b/plugin/src/main/java/org/elasticsearch/license/LicenseService.java index 189e7922483..271bd642b76 100644 --- a/plugin/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/plugin/src/main/java/org/elasticsearch/license/LicenseService.java @@ -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) { diff --git a/plugin/src/main/java/org/elasticsearch/xpack/XPackPlugin.java b/plugin/src/main/java/org/elasticsearch/xpack/XPackPlugin.java index 8f6ffa1f78b..caec0053c30 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/XPackPlugin.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/XPackPlugin.java @@ -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> getInitialClusterStateCustomSupplier() { return security.getInitialClusterStateCustomSupplier(); } + + @Override + public BiConsumer getJoinValidator() { + return security.getJoinValidator(); + } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/XPackSettings.java b/plugin/src/main/java/org/elasticsearch/xpack/XPackSettings.java index b700eadaed9..d1b2d39f17f 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/XPackSettings.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/XPackSettings.java @@ -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 TRANSPORT_SSL_ENABLED = + public static final Setting TRANSPORT_SSL_ENABLED = new Setting<>("xpack.security.transport.ssl.enabled", (s) -> Boolean.toString(true), (s) -> { final boolean parsed = Booleans.parseBoolean(s); diff --git a/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java b/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java index b205683b2bc..9bbe89810e6 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -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 getJoinValidator() { + return enabled ? new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings)) : null; + } + + static final class ValidateTLSOnJoin implements BiConsumer { + 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()); + + } + } + } + } + + } diff --git a/plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java b/plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java index 973a36d1927..3d0c7f94de4 100644 --- a/plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java +++ b/plugin/src/test/java/org/elasticsearch/license/LicensesManagerServiceTests.java @@ -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 { diff --git a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index d0c4095a3dd..57c19488702 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -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 joinValidator = security.getJoinValidator(); + assertNull(joinValidator); + } + + public void testTLSJoinValidator() throws Exception { + createComponents(Settings.EMPTY); + BiConsumer 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 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); + } + } }