diff --git a/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java b/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java index 1170764052c..14e72142715 100644 --- a/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java +++ b/plugin/core/src/main/java/org/elasticsearch/license/LicenseService.java @@ -14,6 +14,7 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.cluster.ack.ClusterStateUpdateResponse; import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.component.Lifecycle; @@ -206,7 +207,7 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste if (newLicense.isProductionLicense() && XPackSettings.SECURITY_ENABLED.get(settings) && XPackSettings.TRANSPORT_SSL_ENABLED.get(settings) == false - && "single-node".equals(DiscoveryModule.DISCOVERY_TYPE_SETTING.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 throw new IllegalStateException("Cannot install a [" + newLicense.operationMode() + "] license unless TLS is configured or security is disabled"); @@ -512,4 +513,13 @@ public class LicenseService extends AbstractLifecycleComponent implements Cluste } return null; } -} \ No newline at end of file + + private static boolean isProductionMode(Settings settings, DiscoveryNode localNode) { + final boolean singleNodeDisco = "single-node".equals(DiscoveryModule.DISCOVERY_TYPE_SETTING.get(settings)); + return singleNodeDisco == false && isBoundToLoopback(localNode) == false; + } + + private static boolean isBoundToLoopback(DiscoveryNode localNode) { + return localNode.getAddress().address().getAddress().isLoopbackAddress(); + } +} diff --git a/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java b/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java index 925ba64e578..2f110f4f8a9 100644 --- a/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java +++ b/plugin/core/src/test/java/org/elasticsearch/license/AbstractLicenseServiceTestCase.java @@ -64,7 +64,7 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase { MetaData metaData = mock(MetaData.class); when(metaData.custom(LicensesMetaData.TYPE)).thenReturn(new LicensesMetaData(license, null)); when(state.metaData()).thenReturn(metaData); - final DiscoveryNode mockNode = new DiscoveryNode("b", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT); + final DiscoveryNode mockNode = getLocalNode(); when(discoveryNodes.getMasterNode()).thenReturn(mockNode); when(discoveryNodes.isLocalNodeElectedMaster()).thenReturn(false); when(state.nodes()).thenReturn(discoveryNodes); @@ -72,6 +72,11 @@ public abstract class AbstractLicenseServiceTestCase extends ESTestCase { when(clusterService.state()).thenReturn(state); when(clusterService.lifecycleState()).thenReturn(Lifecycle.State.STARTED); when(clusterService.getClusterName()).thenReturn(new ClusterName("a")); + when(clusterService.localNode()).thenReturn(mockNode); + } + + protected DiscoveryNode getLocalNode() { + return new DiscoveryNode("b", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT); } @After diff --git a/plugin/core/src/test/java/org/elasticsearch/license/LicenseTLSTests.java b/plugin/core/src/test/java/org/elasticsearch/license/LicenseTLSTests.java new file mode 100644 index 00000000000..588dbabb9db --- /dev/null +++ b/plugin/core/src/test/java/org/elasticsearch/license/LicenseTLSTests.java @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.license; + +import org.elasticsearch.Version; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.cluster.ClusterStateUpdateTask; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.unit.TimeValue; + +import java.net.InetAddress; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static org.hamcrest.Matchers.containsString; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class LicenseTLSTests extends AbstractLicenseServiceTestCase { + + private InetAddress inetAddress; + + public void testApplyLicenseInDevMode() throws Exception { + License newLicense = TestUtils.generateSignedLicense(randomFrom("gold", "platinum"), TimeValue.timeValueHours(24L)); + PutLicenseRequest request = new PutLicenseRequest(); + request.acknowledge(true); + request.license(newLicense); + Settings settings = Settings.builder().put("xpack.security.enabled", true).build(); + XPackLicenseState licenseState = new XPackLicenseState(settings); + inetAddress = InetAddress.getLoopbackAddress(); + + setInitialState(null, licenseState, settings); + licenseService.start(); + PlainActionFuture responseFuture = new PlainActionFuture<>(); + licenseService.registerLicense(request, responseFuture); + verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + + inetAddress = TransportAddress.META_ADDRESS; + settings = Settings.builder() + .put("xpack.security.enabled", true) + .put("discovery.type", "single-node") + .build(); + licenseService.stop(); + licenseState = new XPackLicenseState(settings); + setInitialState(null, licenseState, settings); + licenseService.start(); + licenseService.registerLicense(request, responseFuture); + verify(clusterService, times(2)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + } + + public void testApplyLicenseInProdMode() throws Exception { + final String licenseType = randomFrom("GOLD", "PLATINUM"); + License newLicense = TestUtils.generateSignedLicense(licenseType, TimeValue.timeValueHours(24L)); + PutLicenseRequest request = new PutLicenseRequest(); + request.acknowledge(true); + request.license(newLicense); + Settings settings = Settings.builder().put("xpack.security.enabled", true).build(); + XPackLicenseState licenseState = new XPackLicenseState(settings); + inetAddress = TransportAddress.META_ADDRESS; + + setInitialState(null, licenseState, settings); + licenseService.start(); + PlainActionFuture responseFuture = new PlainActionFuture<>(); + IllegalStateException e = expectThrows(IllegalStateException.class, () -> licenseService.registerLicense(request, responseFuture)); + assertThat(e.getMessage(), + containsString("Cannot install a [" + licenseType + "] license unless TLS is configured or security is disabled")); + + settings = Settings.builder().put("xpack.security.enabled", false).build(); + licenseService.stop(); + licenseState = new XPackLicenseState(settings); + setInitialState(null, licenseState, settings); + licenseService.start(); + licenseService.registerLicense(request, responseFuture); + verify(clusterService).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + + settings = Settings.builder() + .put("xpack.security.enabled", true) + .put("xpack.security.transport.ssl.enabled", true) + .build(); + licenseService.stop(); + licenseState = new XPackLicenseState(settings); + setInitialState(null, licenseState, settings); + licenseService.start(); + licenseService.registerLicense(request, responseFuture); + verify(clusterService, times(2)).submitStateUpdateTask(any(String.class), any(ClusterStateUpdateTask.class)); + } + + @Override + protected DiscoveryNode getLocalNode() { + return new DiscoveryNode("localnode", new TransportAddress(inetAddress, randomIntBetween(9300, 9399)), + emptyMap(), emptySet(), Version.CURRENT); + } +} diff --git a/plugin/security/src/test/java/org/elasticsearch/license/LicenseServiceSingleNodeSecurityTests.java b/plugin/security/src/test/java/org/elasticsearch/license/LicenseServiceSingleNodeSecurityTests.java deleted file mode 100644 index e7c1db0cf98..00000000000 --- a/plugin/security/src/test/java/org/elasticsearch/license/LicenseServiceSingleNodeSecurityTests.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license; - -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.test.ESIntegTestCase; -import org.elasticsearch.test.SecurityIntegTestCase; - -import static org.hamcrest.CoreMatchers.equalTo; - -@ESIntegTestCase.ClusterScope( - scope = ESIntegTestCase.Scope.TEST, - numDataNodes = 1, - numClientNodes = 0, - supportsDedicatedMasters = false, - autoMinMasterNodes = false) -public class LicenseServiceSingleNodeSecurityTests extends SecurityIntegTestCase { - - @Override - protected Settings nodeSettings(int nodeOrdinal) { - return Settings - .builder() - .put(super.nodeSettings(nodeOrdinal)) - .put("discovery.type", "single-node") - .put("transport.tcp.port", "0") - .build(); - } - - public void testLicenseUpgradeSucceeds() throws Exception { - LicensingClient licensingClient = new LicensingClient(client()); - License prodLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueHours(24)); - PutLicenseResponse putLicenseResponse = licensingClient.preparePutLicense(prodLicense).get(); - assertEquals(putLicenseResponse.status(), LicensesStatus.VALID); - assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(prodLicense)); - } -} diff --git a/plugin/security/src/test/java/org/elasticsearch/license/LicenseServiceWithSecurityTests.java b/plugin/security/src/test/java/org/elasticsearch/license/LicenseServiceWithSecurityTests.java deleted file mode 100644 index 42bfd2df1d5..00000000000 --- a/plugin/security/src/test/java/org/elasticsearch/license/LicenseServiceWithSecurityTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -package org.elasticsearch.license; - -import org.elasticsearch.analysis.common.CommonAnalysisPlugin; -import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.plugins.Plugin; -import org.elasticsearch.test.SecurityIntegTestCase; -import org.elasticsearch.transport.Netty4Plugin; -import org.elasticsearch.xpack.security.LocalStateSecurity; - -import java.util.Arrays; -import java.util.Collection; - -import static org.hamcrest.CoreMatchers.equalTo; - -/** - * Basic integration test that checks if license can be upgraded to a production license if TLS is enabled and vice versa. - */ -public class LicenseServiceWithSecurityTests extends SecurityIntegTestCase { - - @Override - protected Collection> nodePlugins() { - return Arrays.asList(LocalStateSecurity.class, CommonAnalysisPlugin.class, Netty4Plugin.class); - } - - @Override - protected Collection> transportClientPlugins() { - return nodePlugins(); - } - - public void testLicenseUpgradeFailsWithoutTLS() throws Exception { - assumeFalse("transport ssl is enabled", isTransportSSLEnabled()); - LicensingClient licensingClient = new LicensingClient(client()); - License license = licensingClient.prepareGetLicense().get().license(); - License prodLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueHours(24)); - IllegalStateException ise = expectThrows(IllegalStateException.class, () -> licensingClient.preparePutLicense(prodLicense).get()); - assertEquals("Cannot install a [PLATINUM] license unless TLS is configured or security is disabled", ise.getMessage()); - assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(license)); - } - - public void testLicenseUpgradeSucceedsWithTLS() throws Exception { - assumeTrue("transport ssl is disabled", isTransportSSLEnabled()); - LicensingClient licensingClient = new LicensingClient(client()); - License prodLicense = TestUtils.generateSignedLicense("platinum", TimeValue.timeValueHours(24)); - PutLicenseResponse putLicenseResponse = licensingClient.preparePutLicense(prodLicense).get(); - assertEquals(putLicenseResponse.status(), LicensesStatus.VALID); - assertThat(licensingClient.prepareGetLicense().get().license(), equalTo(prodLicense)); - } -}