Skip to content

Commit

Permalink
Enforce transport TLS on Basic with Security (#42150)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tvernum authored and jasontedor committed May 15, 2019
1 parent 6fe747f commit 65b6179
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -772,22 +772,4 @@ public Builder validate() {
}
}

/**
* 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());

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,13 @@ public void registerLicense(final PutLicenseRequest request, final ActionListene
}
}

// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ private static class Status {
}
}

private final Logger logger;
private final DeprecationLogger deprecationLogger;
private static final Logger logger = LogManager.getLogger(XPackLicenseState.class);
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
private final List<LicenseStateListener> listeners;

private final boolean isSecurityEnabled;
Expand All @@ -287,8 +287,6 @@ private static class Status {
private boolean isSecurityEnabledByTrialVersion;

public XPackLicenseState(Settings settings) {
this.logger = LogManager.getLogger(getClass());
this.deprecationLogger = new DeprecationLogger(logger);
this.listeners = new CopyOnWriteArrayList<>();
this.isSecurityEnabled = XPackSettings.SECURITY_ENABLED.get(settings);
this.isSecurityExplicitlyEnabled = checkSecurityExplicitlyEnabled(settings);
Expand All @@ -301,8 +299,8 @@ public XPackLicenseState(Settings settings) {
* setting is not explicitly set.
* This behaviour is deprecated, and will be removed in 7.0
*/
private boolean checkSecurityExplicitlyEnabled(Settings settings) {
if (isSecurityEnabled) {
private static boolean checkSecurityExplicitlyEnabled(Settings settings) {
if (XPackSettings.SECURITY_ENABLED.get(settings)) {
if (settings.hasValue(XPackSettings.SECURITY_ENABLED.getKey())) {
return true;
}
Expand All @@ -324,8 +322,6 @@ private XPackLicenseState(XPackLicenseState xPackLicenseState) {
this.isSecurityExplicitlyEnabled = xPackLicenseState.isSecurityExplicitlyEnabled;
this.status = xPackLicenseState.status;
this.isSecurityEnabledByTrialVersion = xPackLicenseState.isSecurityEnabledByTrialVersion;
this.logger = xPackLicenseState.logger;
this.deprecationLogger = xPackLicenseState.deprecationLogger;
}

/**
Expand Down Expand Up @@ -770,6 +766,25 @@ public synchronized boolean isSecurityDisabledByLicenseDefaults() {
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) && checkSecurityExplicitlyEnabled(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 isSecurityEnabledByTrialVersion, final boolean isSecurityEnabled) {
switch (mode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
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;

/**
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 " +
"[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());
}
, randomBoolean()).build(), MetaData.EMPTY_META_DATA)).isSuccess());
}

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]",
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());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ public Function<String, Predicate<String>> getFieldFilter() {
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)));
Expand All @@ -1191,18 +1191,21 @@ public BiConsumer<DiscoveryNode, ClusterState> getJoinValidator() {
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");
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
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;
Expand Down Expand Up @@ -276,17 +275,45 @@ public void testTLSJoinValidator() throws Exception {
int numIters = randomIntBetween(1,10);
for (int i = 0; i < numIters; i++) {
boolean tlsOn = randomBoolean();
boolean securityExplicitlyEnabled = randomBoolean();
String discoveryType = randomFrom("single-node", "zen", randomAlphaOfLength(4));
Security.ValidateTLSOnJoin validator = new Security.ValidateTLSOnJoin(tlsOn, discoveryType);

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);
}
Expand Down

0 comments on commit 65b6179

Please sign in to comment.