From 1e14e14571509d0525e5e93563c71d53364a0172 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Thu, 14 Sep 2017 20:14:27 +0200 Subject: [PATCH] Prevent licenses to be upgraded to production unless TLS is configured (elastic/x-pack-elasticsearch#2502) if a user tries to upgrade a license to a production license and has security enabled we prevent the upgrade unless TLS is setup. This is a requirement now if a cluster with security is running in prodcution. Relates to elastic/x-pack-elasticsearch#2463 Original commit: elastic/x-pack-elasticsearch@d61ef3bcb1a6056b139782f0772b565847bd834f --- .../elasticsearch/license/LicenseService.java | 38 ++++++++----- .../AbstractLicenseServiceTestCase.java | 4 +- .../license/LicenseClusterChangeTests.java | 3 +- .../license/LicenseRegistrationTests.java | 3 +- .../license/LicensesAcknowledgementTests.java | 55 ++++++++++++++++++- 5 files changed, 85 insertions(+), 18 deletions(-) diff --git a/plugin/src/main/java/org/elasticsearch/license/LicenseService.java b/plugin/src/main/java/org/elasticsearch/license/LicenseService.java index 84fa4003e3f..227293886a9 100644 --- a/plugin/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/plugin/src/main/java/org/elasticsearch/license/LicenseService.java @@ -30,6 +30,7 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.XPackPlugin; +import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.scheduler.SchedulerEngine; import java.time.Clock; @@ -207,20 +208,31 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste } } } - clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new - AckedClusterStateUpdateTask(request, listener) { - @Override - protected PutLicenseResponse newResponse(boolean acknowledged) { - return new PutLicenseResponse(acknowledged, LicensesStatus.VALID); - } - @Override - public ClusterState execute(ClusterState currentState) throws Exception { - MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); - mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense)); - return ClusterState.builder(currentState).metaData(mdBuilder).build(); - } - }); + if (newLicense.isProductionLicense() + && XPackSettings.SECURITY_ENABLED.get(settings) + && XPackSettings.TRANSPORT_SSL_ENABLED.get(settings) == false) { + // security is on but TLS is not configured we gonna fail the entire request and throw an exception + throw new IllegalStateException("Can not upgrade to a production license unless TLS is configured or " + + "security is disabled"); + // TODO we should really validate that all nodes have xpack in stalled and are consistently configured but this + // should happen on a different level and not in this code + } else { + clusterService.submitStateUpdateTask("register license [" + newLicense.uid() + "]", new + AckedClusterStateUpdateTask(request, listener) { + @Override + protected PutLicenseResponse newResponse(boolean acknowledged) { + return new PutLicenseResponse(acknowledged, LicensesStatus.VALID); + } + + @Override + public ClusterState execute(ClusterState currentState) throws Exception { + MetaData.Builder mdBuilder = MetaData.builder(currentState.metaData()); + mdBuilder.putCustom(LicensesMetaData.TYPE, new LicensesMetaData(newLicense)); + return ClusterState.builder(currentState).metaData(mdBuilder).build(); + } + }); + } } } diff --git a/plugin/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java b/plugin/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java index 1acd3012276..dcead1236f2 100644 --- a/plugin/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java +++ b/plugin/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java @@ -48,11 +48,11 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase { environment = mock(Environment.class); } - protected void setInitialState(License license, XPackLicenseState licenseState) { + protected void setInitialState(License license, XPackLicenseState licenseState, Settings settings) { Path tempDir = createTempDir(); when(environment.configFile()).thenReturn(tempDir); licenseType = randomBoolean() ? "trial" : "basic"; - Settings settings = Settings.builder().put(LicenseService.SELF_GENERATED_LICENSE_TYPE.getKey(), licenseType).build(); + settings = Settings.builder().put(settings).put(LicenseService.SELF_GENERATED_LICENSE_TYPE.getKey(), licenseType).build(); licenseService = new LicenseService(settings, clusterService, clock, environment, resourceWatcherService, licenseState); ClusterState state = mock(ClusterState.class); final ClusterBlocks noBlock = ClusterBlocks.builder().build(); diff --git a/plugin/src/test/java/org/elasticsearch/license/LicenseClusterChangeTests.java b/plugin/src/test/java/org/elasticsearch/license/LicenseClusterChangeTests.java index f258e1d70aa..903cfa4f5fe 100644 --- a/plugin/src/test/java/org/elasticsearch/license/LicenseClusterChangeTests.java +++ b/plugin/src/test/java/org/elasticsearch/license/LicenseClusterChangeTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.cluster.ClusterStateUpdateTask; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import org.junit.After; import org.junit.Before; @@ -33,7 +34,7 @@ public class LicenseClusterChangeTests extends AbstractLicenseServiceTestCase { @Before public void setup() { licenseState = new TestUtils.AssertingLicenseState(); - setInitialState(null, licenseState); + setInitialState(null, licenseState, Settings.EMPTY); licenseService.start(); } diff --git a/plugin/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java b/plugin/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java index f96298e3087..8c76a009e40 100644 --- a/plugin/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java +++ b/plugin/src/test/java/org/elasticsearch/license/LicenseRegistrationTests.java @@ -8,6 +8,7 @@ package org.elasticsearch.license; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateUpdateTask; +import org.elasticsearch.common.settings.Settings; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -19,7 +20,7 @@ public class LicenseRegistrationTests extends AbstractLicenseServiceTestCase { public void testTrialLicenseRequestOnEmptyLicenseState() throws Exception { XPackLicenseState licenseState = new XPackLicenseState(); - setInitialState(null, licenseState); + setInitialState(null, licenseState, Settings.EMPTY); when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(true); licenseService.start(); diff --git a/plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java b/plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java index b439fa4a133..eed19785b1b 100644 --- a/plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java +++ b/plugin/src/test/java/org/elasticsearch/license/LicensesAcknowledgementTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.license; import org.elasticsearch.action.ActionListener; import org.elasticsearch.cluster.ClusterStateUpdateTask; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; import static org.hamcrest.Matchers.equalTo; @@ -20,7 +21,7 @@ public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase public void testAcknowledgment() throws Exception { XPackLicenseState licenseState = new XPackLicenseState(); - setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)), licenseState); + setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)), licenseState, Settings.EMPTY); licenseService.start(); // try installing a signed license License signedLicense = TestUtils.generateSignedLicense("basic", TimeValue.timeValueHours(10)); @@ -37,6 +38,58 @@ public class LicensesAcknowledgementTests extends AbstractLicenseServiceTestCase verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); } + public void testRejectUpgradeToProductionWithoutTLS() throws Exception { + XPackLicenseState licenseState = new XPackLicenseState(); + setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)), licenseState, Settings.EMPTY); + licenseService.start(); + // try installing a signed license + License signedLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueHours(10)); + PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense); + // ensure acknowledgement message was part of the response + IllegalStateException ise = expectThrows(IllegalStateException.class, () -> + licenseService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID, true))); + assertEquals("Can not upgrade to a production license unless TLS is configured or security is disabled", ise.getMessage()); + } + + public void testUpgradeToProductionWithoutTLSAndSecurityDisabled() throws Exception { + XPackLicenseState licenseState = new XPackLicenseState(); + setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)), licenseState, Settings.builder() + .put("xpack.security.enabled", false).build()); + licenseService.start(); + // try installing a signed license + License signedLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueHours(10)); + PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense); + licenseService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID, true)); + assertThat(licenseService.getLicense(), not(signedLicense)); + verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + + // try installing a signed license with acknowledgement + putLicenseRequest = new PutLicenseRequest().license(signedLicense).acknowledge(true); + // ensure license was installed and no acknowledgment message was returned + licenseService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID, false)); + verify(clusterService, times(2)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + } + + public void testUpgradeToProductionWithTLSAndSecurity() throws Exception { + XPackLicenseState licenseState = new XPackLicenseState(); + setInitialState(TestUtils.generateSignedLicense("trial", TimeValue.timeValueHours(2)), licenseState, Settings.builder() + .put("xpack.security.enabled", true) + .put("xpack.security.transport.ssl.enabled", true).build()); + licenseService.start(); + // try installing a signed license + License signedLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueHours(10)); + PutLicenseRequest putLicenseRequest = new PutLicenseRequest().license(signedLicense); + licenseService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(false, LicensesStatus.VALID, true)); + assertThat(licenseService.getLicense(), not(signedLicense)); + verify(clusterService, times(1)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + + // try installing a signed license with acknowledgement + putLicenseRequest = new PutLicenseRequest().license(signedLicense).acknowledge(true); + // ensure license was installed and no acknowledgment message was returned + licenseService.registerLicense(putLicenseRequest, new AssertingLicensesUpdateResponse(true, LicensesStatus.VALID, false)); + verify(clusterService, times(2)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + } + private static class AssertingLicensesUpdateResponse implements ActionListener { private final boolean expectedAcknowledgement; private final LicensesStatus expectedStatus;