Enforce transport TLS on Basic with Security (#42150)

If a basic license enables security, then we should also enforce TLS
on the transport interface.

This was already the case for Standard/Gold/Platinum licenses.

For Basic, security defaults to disabled, so some of the process
around checking whether security is actuallY enabled is more complex
now that we need to account for basic licenses.
This commit is contained in:
Tim Vernum 2019-05-16 03:53:34 +10:00 committed by Jason Tedor
parent 2f8c5ac6f8
commit 9191b02213
No known key found for this signature in database
GPG Key ID: FA89F05560F16BC5
7 changed files with 178 additions and 64 deletions

View File

@ -780,22 +780,4 @@ public class License implements ToXContentObject {
}
}
/**
* Returns <code>true</code> iff the license is a production licnese
*/
public boolean isProductionLicense() {
switch (operationMode()) {
case MISSING:
case TRIAL:
case BASIC:
return false;
case STANDARD:
case GOLD:
case PLATINUM:
return true;
default:
throw new AssertionError("unknown operation mode: " + operationMode());
}
}
}

View File

@ -218,10 +218,13 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste
}
}
// This check would be incorrect if "basic" licenses were allowed here
// because the defaults there mean that security can be "off", even if the setting is "on"
// BUT basic licenses are explicitly excluded earlier in this method, so we don't need to worry
if (XPackSettings.SECURITY_ENABLED.get(settings)) {
// TODO we should really validate that all nodes have xpack installed and are consistently configured but this
// should happen on a different level and not in this code
if (newLicense.isProductionLicense()
if (XPackLicenseState.isTransportTlsRequired(newLicense, settings)
&& XPackSettings.TRANSPORT_SSL_ENABLED.get(settings) == false
&& isProductionMode(settings, clusterService.localNode())) {
// security is on but TLS is not configured we gonna fail the entire request and throw an exception

View File

@ -282,7 +282,7 @@ public class XPackLicenseState {
public XPackLicenseState(Settings settings) {
this.listeners = new CopyOnWriteArrayList<>();
this.isSecurityEnabled = XPackSettings.SECURITY_ENABLED.get(settings);
this.isSecurityExplicitlyEnabled = isSecurityEnabled && settings.hasValue(XPackSettings.SECURITY_ENABLED.getKey());
this.isSecurityExplicitlyEnabled = isSecurityEnabled && isSecurityExplicitlyEnabled(settings);
}
private XPackLicenseState(XPackLicenseState xPackLicenseState) {
@ -292,6 +292,10 @@ public class XPackLicenseState {
this.status = xPackLicenseState.status;
}
private static boolean isSecurityExplicitlyEnabled(Settings settings) {
return settings.hasValue(XPackSettings.SECURITY_ENABLED.getKey());
}
/**
* Updates the current state of the license, which will change what features are available.
*
@ -727,6 +731,25 @@ public class XPackLicenseState {
return false;
}
public static boolean isTransportTlsRequired(License license, Settings settings) {
if (license == null) {
return false;
}
switch (license.operationMode()) {
case STANDARD:
case GOLD:
case PLATINUM:
return XPackSettings.SECURITY_ENABLED.get(settings);
case BASIC:
return XPackSettings.SECURITY_ENABLED.get(settings) && isSecurityExplicitlyEnabled(settings);
case MISSING:
case TRIAL:
return false;
default:
throw new AssertionError("unknown operation mode [" + license.operationMode() + "]");
}
}
private static boolean isSecurityEnabled(final OperationMode mode, final boolean isSecurityExplicitlyEnabled,
final boolean isSecurityEnabled) {
switch (mode) {

View File

@ -9,6 +9,7 @@ import org.elasticsearch.bootstrap.BootstrapCheck;
import org.elasticsearch.bootstrap.BootstrapContext;
import org.elasticsearch.license.License;
import org.elasticsearch.license.LicenseService;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.core.XPackSettings;
/**
@ -19,10 +20,11 @@ public final class TLSLicenseBootstrapCheck implements BootstrapCheck {
public BootstrapCheckResult check(BootstrapContext context) {
if (XPackSettings.TRANSPORT_SSL_ENABLED.get(context.settings()) == false) {
License license = LicenseService.getLicense(context.metaData());
if (license != null && license.isProductionLicense()) {
return BootstrapCheckResult.failure("Transport SSL must be enabled for setups with production licenses. Please set " +
"[xpack.security.transport.ssl.enabled] to [true] or disable security by setting [xpack.security.enabled] " +
"to [false]");
if (XPackLicenseState.isTransportTlsRequired(license, context.settings())) {
return BootstrapCheckResult.failure("Transport SSL must be enabled if security is enabled on a [" +
license.operationMode().description() + "] license. " +
"Please set [xpack.security.transport.ssl.enabled] to [true] or disable security by setting " +
"[xpack.security.enabled] to [false]");
}
}
return BootstrapCheckResult.success();

View File

@ -5,40 +5,115 @@
*/
package org.elasticsearch.xpack.core.ssl;
import org.elasticsearch.bootstrap.BootstrapCheck;
import org.elasticsearch.bootstrap.BootstrapContext;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.license.License;
import org.elasticsearch.license.License.OperationMode;
import org.elasticsearch.license.TestUtils;
import org.elasticsearch.test.AbstractBootstrapCheckTestCase;
import java.util.EnumSet;
public class TLSLicenseBootstrapCheckTests extends AbstractBootstrapCheckTestCase {
public void testBootstrapCheck() throws Exception {
public void testBootstrapCheckOnEmptyMetadata() {
assertTrue(new TLSLicenseBootstrapCheck().check(emptyContext).isSuccess());
assertTrue(new TLSLicenseBootstrapCheck().check(createTestContext(Settings.builder().put("xpack.security.transport.ssl.enabled"
, randomBoolean()).build(), MetaData.EMPTY_META_DATA)).isSuccess());
int numIters = randomIntBetween(1,10);
for (int i = 0; i < numIters; i++) {
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
EnumSet<License.OperationMode> productionModes = EnumSet.of(License.OperationMode.GOLD, License.OperationMode.PLATINUM,
License.OperationMode.STANDARD);
MetaData.Builder builder = MetaData.builder();
TestUtils.putLicense(builder, license);
MetaData build = builder.build();
if (productionModes.contains(license.operationMode()) == false) {
assertTrue(new TLSLicenseBootstrapCheck().check(createTestContext(
Settings.builder().put("xpack.security.transport.ssl.enabled", true).build(), build)).isSuccess());
} else {
assertTrue(new TLSLicenseBootstrapCheck().check(createTestContext(
Settings.builder().put("xpack.security.transport.ssl.enabled", false).build(), build)).isFailure());
assertEquals("Transport SSL must be enabled for setups with production licenses. Please set " +
}
public void testBootstrapCheckFailureOnPremiumLicense() throws Exception {
final OperationMode mode = randomFrom(OperationMode.PLATINUM, OperationMode.GOLD, OperationMode.STANDARD);
final Settings.Builder settings = Settings.builder();
if (randomBoolean()) {
// randomise between default-false & explicit-false
settings.put("xpack.security.transport.ssl.enabled", false);
}
if (randomBoolean()) {
// randomise between default-true & explicit-true
settings.put("xpack.security.enabled", true);
}
final BootstrapCheck.BootstrapCheckResult result = runBootstrapCheck(mode, settings);
assertTrue("Expected bootstrap failure", result.isFailure());
assertEquals("Transport SSL must be enabled if security is enabled on a [" + mode.description() + "] license. Please set " +
"[xpack.security.transport.ssl.enabled] to [true] or disable security by setting " +
"[xpack.security.enabled] to [false]",
new TLSLicenseBootstrapCheck().check(createTestContext(
Settings.builder().put("xpack.security.transport.ssl.enabled", false).build(), build)).getMessage());
}
result.getMessage());
}
public void testBootstrapCheckSucceedsWithTlsEnabledOnPremiumLicense() throws Exception {
final OperationMode mode = randomFrom(OperationMode.PLATINUM, OperationMode.GOLD, OperationMode.STANDARD);
final Settings.Builder settings = Settings.builder().put("xpack.security.transport.ssl.enabled", true);
final BootstrapCheck.BootstrapCheckResult result = runBootstrapCheck(mode, settings);
assertSuccess(result);
}
public void testBootstrapCheckFailureOnBasicLicense() throws Exception {
final Settings.Builder settings = Settings.builder().put("xpack.security.enabled", true);
if (randomBoolean()) {
// randomise between default-false & explicit-false
settings.put("xpack.security.transport.ssl.enabled", false);
}
final BootstrapCheck.BootstrapCheckResult result = runBootstrapCheck(OperationMode.BASIC, settings);
assertTrue("Expected bootstrap failure", result.isFailure());
assertEquals("Transport SSL must be enabled if security is enabled on a [basic] license. Please set " +
"[xpack.security.transport.ssl.enabled] to [true] or disable security by setting " +
"[xpack.security.enabled] to [false]",
result.getMessage());
}
public void testBootstrapSucceedsIfSecurityIsNotEnabledOnBasicLicense() throws Exception {
final Settings.Builder settings = Settings.builder();
if (randomBoolean()) {
// randomise between default-false & explicit-false
settings.put("xpack.security.enabled", false);
}
if (randomBoolean()) {
// it does not matter whether or not this is set, as security is not enabled.
settings.put("xpack.security.transport.ssl.enabled", randomBoolean());
}
final BootstrapCheck.BootstrapCheckResult result = runBootstrapCheck(OperationMode.BASIC, settings);
assertSuccess(result);
}
public void testBootstrapSucceedsIfTlsIsEnabledOnBasicLicense() throws Exception {
final Settings.Builder settings = Settings.builder().put("xpack.security.transport.ssl.enabled", true);
if (randomBoolean()) {
// it does not matter whether or not this is set, as TLS is enabled.
settings.put("xpack.security.enabled", randomBoolean());
}
final BootstrapCheck.BootstrapCheckResult result = runBootstrapCheck(OperationMode.BASIC, settings);
assertSuccess(result);
}
public void testBootstrapCheckAlwaysSucceedsOnTrialLicense() throws Exception {
final Settings.Builder settings = Settings.builder();
if (randomBoolean()) {
// it does not matter whether this is set, or to which value.
settings.put("xpack.security.enabled", randomBoolean());
}
if (randomBoolean()) {
// it does not matter whether this is set, or to which value.
settings.put("xpack.security.transport.ssl.enabled", randomBoolean());
}
final BootstrapCheck.BootstrapCheckResult result = runBootstrapCheck(OperationMode.TRIAL, settings);
assertSuccess(result);
}
public BootstrapCheck.BootstrapCheckResult runBootstrapCheck(OperationMode mode, Settings.Builder settings) throws Exception {
final License license = TestUtils.generateSignedLicense(mode.description(), TimeValue.timeValueHours(24));
MetaData.Builder builder = MetaData.builder();
TestUtils.putLicense(builder, license);
MetaData metaData = builder.build();
final BootstrapContext context = createTestContext(settings.build(), metaData);
return new TLSLicenseBootstrapCheck().check(context);
}
public void assertSuccess(BootstrapCheck.BootstrapCheckResult result) {
if (result.isFailure()) {
fail("Bootstrap check failed unexpectedly: " + result.getMessage());
}
}
}

View File

@ -258,8 +258,8 @@ import static java.util.Collections.singletonList;
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING;
import static org.elasticsearch.xpack.core.XPackSettings.API_KEY_SERVICE_ENABLED_SETTING;
import static org.elasticsearch.xpack.core.XPackSettings.HTTP_SSL_ENABLED;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT;
import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.SECURITY_MAIN_TEMPLATE_7;
public class Security extends Plugin implements ActionPlugin, IngestPlugin, NetworkPlugin, ClusterPlugin,
@ -985,7 +985,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
public BiConsumer<DiscoveryNode, ClusterState> getJoinValidator() {
if (enabled) {
return new ValidateTLSOnJoin(XPackSettings.TRANSPORT_SSL_ENABLED.get(settings),
DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings))
DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings), settings)
.andThen(new ValidateUpgradedSecurityIndex())
.andThen(new ValidateLicenseCanBeDeserialized())
.andThen(new ValidateLicenseForFIPS(XPackSettings.FIPS_MODE_ENABLED.get(settings)));
@ -996,18 +996,21 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
static final class ValidateTLSOnJoin implements BiConsumer<DiscoveryNode, ClusterState> {
private final boolean isTLSEnabled;
private final String discoveryType;
private final Settings settings;
ValidateTLSOnJoin(boolean isTLSEnabled, String discoveryType) {
ValidateTLSOnJoin(boolean isTLSEnabled, String discoveryType, Settings settings) {
this.isTLSEnabled = isTLSEnabled;
this.discoveryType = discoveryType;
this.settings = settings;
}
@Override
public void accept(DiscoveryNode node, ClusterState state) {
License license = LicenseService.getLicense(state.metaData());
if (license != null && license.isProductionLicense() &&
isTLSEnabled == false && "single-node".equals(discoveryType) == false) {
throw new IllegalStateException("TLS setup is required for license type [" + license.operationMode().name() + "]");
if (isTLSEnabled == false && "single-node".equals(discoveryType) == false
&& XPackLicenseState.isTransportTlsRequired(license, settings)) {
throw new IllegalStateException("Transport TLS ([" + XPackSettings.TRANSPORT_SSL_ENABLED.getKey() +
"]) is required for license type [" + license.operationMode().description() + "] when security is enabled");
}
}
}

View File

@ -54,7 +54,6 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -67,9 +66,8 @@ import java.util.stream.Collectors;
import static org.elasticsearch.cluster.metadata.IndexMetaData.INDEX_FORMAT_SETTING;
import static org.elasticsearch.discovery.DiscoveryModule.ZEN2_DISCOVERY_TYPE;
import static org.elasticsearch.discovery.DiscoveryModule.ZEN_DISCOVERY_TYPE;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT;
import static org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames.SECURITY_MAIN_ALIAS;
import static org.elasticsearch.xpack.security.support.SecurityIndexManager.INTERNAL_MAIN_INDEX_FORMAT;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
@ -254,17 +252,45 @@ public class SecurityTests extends ESTestCase {
int numIters = randomIntBetween(1, 10);
for (int i = 0; i < numIters; i++) {
boolean tlsOn = randomBoolean();
String discoveryType = randomFrom("single-node", ZEN_DISCOVERY_TYPE, ZEN2_DISCOVERY_TYPE, randomAlphaOfLength(4));
Security.ValidateTLSOnJoin validator = new Security.ValidateTLSOnJoin(tlsOn, discoveryType);
boolean securityExplicitlyEnabled = randomBoolean();
String discoveryType = randomFrom("single-node", ZEN2_DISCOVERY_TYPE, ZEN2_DISCOVERY_TYPE, randomAlphaOfLength(4));
final Settings settings;
if (securityExplicitlyEnabled) {
settings = Settings.builder().put("xpack.security.enabled", true).build();
} else {
settings = Settings.EMPTY;
}
Security.ValidateTLSOnJoin validator = new Security.ValidateTLSOnJoin(tlsOn, discoveryType, settings);
MetaData.Builder builder = MetaData.builder();
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(24));
License.OperationMode licenseMode = randomFrom(License.OperationMode.values());
License license = TestUtils.generateSignedLicense(licenseMode.description(), TimeValue.timeValueHours(24));
TestUtils.putLicense(builder, license);
ClusterState state = ClusterState.builder(ClusterName.DEFAULT).metaData(builder.build()).build();
EnumSet<License.OperationMode> productionModes = EnumSet.of(License.OperationMode.GOLD, License.OperationMode.PLATINUM,
License.OperationMode.STANDARD);
if (productionModes.contains(license.operationMode()) && tlsOn == false && "single-node".equals(discoveryType) == false) {
final boolean expectFailure;
switch (licenseMode) {
case PLATINUM:
case GOLD:
case STANDARD:
expectFailure = tlsOn == false && "single-node".equals(discoveryType) == false;
break;
case BASIC:
expectFailure = tlsOn == false && "single-node".equals(discoveryType) == false && securityExplicitlyEnabled;
break;
case MISSING:
case TRIAL:
expectFailure = false;
break;
default:
throw new AssertionError("unknown operation mode [" + license.operationMode() + "]");
}
logger.info("Test TLS join; Lic:{} TLS:{} Disco:{} Settings:{} ; Expect Failure: {}",
licenseMode, tlsOn, discoveryType, settings.toDelimitedString(','), expectFailure);
if (expectFailure) {
IllegalStateException ise = expectThrows(IllegalStateException.class, () -> validator.accept(node, state));
assertEquals("TLS setup is required for license type [" + license.operationMode().name() + "]", ise.getMessage());
assertEquals("Transport TLS ([xpack.security.transport.ssl.enabled]) is required for license type ["
+ license.operationMode().description() + "] when security is enabled", ise.getMessage());
} else {
validator.accept(node, state);
}