diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-09-01 09:45:19 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-09-05 14:24:13 +0200 |
commit | 4276d4569c280fe7bffca1aadbbc75a00a7463ee (patch) | |
tree | 1248d527ffe8c2c01b05a7ee6cb592a3312b4f78 /server/sonar-main/src | |
parent | 0f551cb0f2bbbdc9319b49fe495288eed8432fab (diff) | |
download | sonarqube-4276d4569c280fe7bffca1aadbbc75a00a7463ee.tar.gz sonarqube-4276d4569c280fe7bffca1aadbbc75a00a7463ee.zip |
SONAR-9712 sonar.auth.jwtBase64Hs256Secret is mandatory on app nodes
Diffstat (limited to 'server/sonar-main/src')
3 files changed, 129 insertions, 86 deletions
diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java index 7dbac3b68cd..bfa81351013 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java +++ b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java @@ -38,10 +38,10 @@ import org.sonar.process.ProcessProperties; /** * Properties of the cluster configuration */ -public final class ClusterProperties { - static final String DEFAULT_PORT = "9003"; +final class ClusterProperties { + private static final String DEFAULT_PORT = "9003"; private static final Logger LOGGER = LoggerFactory.getLogger(ClusterProperties.class); - public static final String HAZELCAST_CLUSTER_NAME = "sonarqube"; + static final String HAZELCAST_CLUSTER_NAME = "sonarqube"; private final int port; private final List<String> hosts; diff --git a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java index 3c502807ce6..d0fbdbd8ad4 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java +++ b/server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java @@ -29,7 +29,6 @@ import java.net.UnknownHostException; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; -import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; import org.sonar.process.MessageException; import org.sonar.process.NodeType; @@ -42,6 +41,7 @@ import static java.lang.String.format; import static java.util.Arrays.asList; import static java.util.Arrays.stream; import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.joining; import static org.apache.commons.lang.StringUtils.isBlank; import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED; import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS; @@ -56,11 +56,9 @@ public class ClusterSettings implements Consumer<Props> { @Override public void accept(Props props) { - if (!isClusterEnabled(props)) { - return; + if (isClusterEnabled(props)) { + checkProperties(props); } - - checkProperties(props); } private static void checkProperties(Props props) { @@ -71,27 +69,30 @@ public class ClusterSettings implements Consumer<Props> { // Mandatory properties ensureMandatoryProperty(props, CLUSTER_NODE_TYPE); + String nodeTypeValue = props.nonNullValue(ProcessProperties.CLUSTER_NODE_TYPE); + if (!NodeType.isValid(nodeTypeValue)) { + throw new MessageException(format("Invalid value for property [%s]: [%s], only [%s] are allowed", ProcessProperties.CLUSTER_NODE_TYPE, nodeTypeValue, + Arrays.stream(NodeType.values()).map(NodeType::getValue).collect(joining(", ")))); + } ensureMandatoryProperty(props, CLUSTER_HOSTS); ensureMandatoryProperty(props, CLUSTER_SEARCH_HOSTS); - String nodeTypeValue = props.value(ProcessProperties.CLUSTER_NODE_TYPE); - if (!NodeType.isValid(nodeTypeValue)) { - throw new MessageException(format("Invalid nodeTypeValue for [%s]: [%s], only [%s] are allowed", ProcessProperties.CLUSTER_NODE_TYPE, nodeTypeValue, - Arrays.stream(NodeType.values()).map(NodeType::getValue).collect(Collectors.joining(", ")))); - } NodeType nodeType = NodeType.parse(nodeTypeValue); - if (nodeType == NodeType.SEARCH) { - ensureMandatoryProperty(props, SEARCH_HOST); - ensureNotLoopback(props, SEARCH_HOST); - } - - // H2 Database is not allowed in cluster mode - String jdbcUrl = props.value(JDBC_URL); - if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) { - throw new MessageException("Embedded database is not supported in cluster mode"); + switch (nodeType) { + case APPLICATION: + ensureNotH2(props); + ensureMandatoryProperty(props, "sonar.auth.jwtBase64Hs256Secret"); + break; + case SEARCH: + ensureMandatoryProperty(props, SEARCH_HOST); + ensureNotLoopback(props, SEARCH_HOST); + break; + default: + throw new IllegalArgumentException("Unsupported node type: " + nodeType); } - // Loopback interfaces are forbidden for SEARCH_HOST and CLUSTER_NODE_HOST + // Loopback interfaces are forbidden for the ports accessed + // by other nodes of cluster ensureNotLoopback(props, CLUSTER_HOSTS); ensureNotLoopback(props, CLUSTER_NODE_HOST); ensureNotLoopback(props, CLUSTER_SEARCH_HOSTS); @@ -100,6 +101,13 @@ public class ClusterSettings implements Consumer<Props> { ensureLocalAddress(props, CLUSTER_NODE_HOST); } + private static void ensureNotH2(Props props) { + String jdbcUrl = props.value(JDBC_URL); + if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) { + throw new MessageException("Embedded database is not supported in cluster mode"); + } + } + private static void ensureMandatoryProperty(Props props, String key) { if (isBlank(props.value(key))) { throw new MessageException(format("Property [%s] is mandatory", key)); @@ -107,7 +115,7 @@ public class ClusterSettings implements Consumer<Props> { } @VisibleForTesting - protected static void ensureNotLoopback(Props props, String key) { + private static void ensureNotLoopback(Props props, String key) { String ipList = props.value(key); if (ipList == null) { return; @@ -151,7 +159,7 @@ public class ClusterSettings implements Consumer<Props> { HostAndPort hostAndPort = HostAndPort.fromString(text); if (!InetAddresses.isInetAddress(hostAndPort.getHostText())) { try { - inetAddress =InetAddress.getByName(hostAndPort.getHostText()); + inetAddress = InetAddress.getByName(hostAndPort.getHostText()); } catch (UnknownHostException e) { throw new MessageException(format("The interface address [%s] of [%s] cannot be resolved : %s", text, key, e.getMessage())); } @@ -181,12 +189,11 @@ public class ClusterSettings implements Consumer<Props> { case SEARCH: return singletonList(ProcessId.ELASTICSEARCH); default: - throw new IllegalStateException("Unexpected node type "+nodeType); + throw new IllegalArgumentException("Unexpected node type " + nodeType); } } public static boolean isLocalElasticsearchEnabled(AppSettings settings) { - // elasticsearch is enabled on "search" nodes, but disabled on "application" nodes if (isClusterEnabled(settings.getProps())) { return NodeType.parse(settings.getValue(ProcessProperties.CLUSTER_NODE_TYPE).orElse(null)) == NodeType.SEARCH; diff --git a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java index ce6c3a42326..166a0e0967a 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java @@ -19,12 +19,16 @@ */ package org.sonar.application.config; -import org.junit.Before; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Collections; +import java.util.Enumeration; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.process.MessageException; -import org.sonar.process.ProcessProperties; import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; @@ -38,25 +42,17 @@ import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS; import static org.sonar.process.ProcessProperties.JDBC_URL; import static org.sonar.process.ProcessProperties.SEARCH_HOST; - public class ClusterSettingsTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - private TestAppSettings settings; - - @Before - public void resetSettings() { - settings = getClusterSettings(); - } - @Test public void test_isClusterEnabled() { - settings.set(CLUSTER_ENABLED, "true"); + TestAppSettings settings = newSettingsForAppNode().set(CLUSTER_ENABLED, "true"); assertThat(ClusterSettings.isClusterEnabled(settings)).isTrue(); - settings.set(CLUSTER_ENABLED, "false"); + settings = new TestAppSettings().set(CLUSTER_ENABLED, "false"); assertThat(ClusterSettings.isClusterEnabled(settings)).isFalse(); } @@ -66,38 +62,36 @@ public class ClusterSettingsTest { } @Test - public void getEnabledProcesses_returns_all_processes_by_default() { - settings.set(CLUSTER_ENABLED, "false"); + public void getEnabledProcesses_returns_all_processes_in_standalone_mode() { + TestAppSettings settings = new TestAppSettings().set(CLUSTER_ENABLED, "false"); assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, ELASTICSEARCH, WEB_SERVER); } @Test public void getEnabledProcesses_fails_if_no_node_type_is_set_for_a_cluster_node() { + TestAppSettings settings = new TestAppSettings(); settings.set(CLUSTER_ENABLED, "true"); - settings.set(CLUSTER_NODE_TYPE, ""); + settings.set(CLUSTER_NODE_TYPE, "foo"); - expectedException.expect(IllegalStateException.class); - expectedException.expectMessage("Invalid value for [sonar.cluster.node.type]: []"); + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Invalid value for [sonar.cluster.node.type]: [foo]"); ClusterSettings.getEnabledProcesses(settings); } @Test public void getEnabledProcesses_returns_configured_processes_in_cluster_mode() { - settings.set(CLUSTER_ENABLED, "true"); - settings.set(ProcessProperties.CLUSTER_NODE_TYPE, "application"); - + TestAppSettings settings = newSettingsForAppNode(); assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, WEB_SERVER); - settings.set(ProcessProperties.CLUSTER_NODE_TYPE, "search"); - + settings = newSettingsForSearchNode(); assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(ELASTICSEARCH); } @Test public void accept_throws_MessageException_if_no_node_type_is_configured() { + TestAppSettings settings = new TestAppSettings(); settings.set(CLUSTER_ENABLED, "true"); - settings.set(CLUSTER_NODE_TYPE, ""); expectedException.expect(MessageException.class); expectedException.expectMessage("Property [sonar.cluster.node.type] is mandatory"); @@ -106,19 +100,20 @@ public class ClusterSettingsTest { } @Test - public void accept_throws_MessageException_if_no_node_type_is_an_illegal_value() { + public void accept_throws_MessageException_if_node_type_is_not_correct() { + TestAppSettings settings = new TestAppSettings(); settings.set(CLUSTER_ENABLED, "true"); settings.set(CLUSTER_NODE_TYPE, "bla"); expectedException.expect(MessageException.class); - expectedException.expectMessage("Invalid nodeTypeValue for [sonar.cluster.node.type]: [bla], only [application, search] are allowed"); + expectedException.expectMessage("Invalid value for property [sonar.cluster.node.type]: [bla], only [application, search] are allowed"); new ClusterSettings().accept(settings.getProps()); } @Test public void accept_throws_MessageException_if_internal_property_for_web_leader_is_configured() { - settings.set(CLUSTER_ENABLED, "true"); + TestAppSettings settings = newSettingsForAppNode(); settings.set("sonar.cluster.web.startupLeader", "true"); expectedException.expect(MessageException.class); @@ -129,8 +124,7 @@ public class ClusterSettingsTest { @Test public void accept_throws_MessageException_if_search_enabled_with_loopback() { - settings.set(CLUSTER_ENABLED, "true"); - settings.set(ProcessProperties.CLUSTER_NODE_TYPE, "search"); + TestAppSettings settings = newSettingsForSearchNode(); settings.set(CLUSTER_SEARCH_HOSTS, "192.168.1.1,192.168.1.2"); settings.set(SEARCH_HOST, "::1"); @@ -140,18 +134,10 @@ public class ClusterSettingsTest { new ClusterSettings().accept(settings.getProps()); } - @Test - public void accept_not_throwing_MessageException_if_search_disabled_with_loopback() { - settings.set(CLUSTER_ENABLED, "true"); - settings.set(ProcessProperties.CLUSTER_NODE_TYPE, "application"); - settings.set(CLUSTER_SEARCH_HOSTS, "192.168.1.1,192.168.1.2"); - settings.set(SEARCH_HOST, "127.0.0.1"); - - new ClusterSettings().accept(settings.getProps()); - } @Test public void accept_does_nothing_if_cluster_is_disabled() { + TestAppSettings settings = new TestAppSettings(); settings.set(CLUSTER_ENABLED, "false"); // this property is supposed to fail if cluster is enabled settings.set("sonar.cluster.web.startupLeader", "true"); @@ -160,8 +146,8 @@ public class ClusterSettingsTest { } @Test - public void accept_throws_MessageException_if_h2() { - settings.set(CLUSTER_ENABLED, "true"); + public void accept_throws_MessageException_if_h2_on_application_node() { + TestAppSettings settings = newSettingsForAppNode(); settings.set("sonar.jdbc.url", "jdbc:h2:mem"); expectedException.expect(MessageException.class); @@ -171,7 +157,17 @@ public class ClusterSettingsTest { } @Test - public void accept_throws_MessageException_if_default_jdbc_url() { + public void accept_does_not_verify_h2_on_search_node() { + TestAppSettings settings = newSettingsForSearchNode(); + settings.set("sonar.jdbc.url", "jdbc:h2:mem"); + + // do not fail + new ClusterSettings().accept(settings.getProps()); + } + + @Test + public void accept_throws_MessageException_on_application_node_if_default_jdbc_url() { + TestAppSettings settings = newSettingsForAppNode(); settings.clearProperty(JDBC_URL); expectedException.expect(MessageException.class); @@ -181,77 +177,117 @@ public class ClusterSettingsTest { } @Test - public void isLocalElasticsearchEnabled_returns_true_by_default() { + public void isLocalElasticsearchEnabled_returns_true_in_standalone_mode() { + TestAppSettings settings = new TestAppSettings(); assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue(); } @Test - public void isLocalElasticsearchEnabled_returns_true_for_a_search_node() { - settings.set(CLUSTER_ENABLED, "true"); - settings.set(ProcessProperties.CLUSTER_NODE_TYPE, "search"); + public void isLocalElasticsearchEnabled_returns_true_on_search_node() { + TestAppSettings settings = newSettingsForSearchNode(); assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue(); } @Test public void isLocalElasticsearchEnabled_returns_true_for_a_application_node() { - settings.set(CLUSTER_ENABLED, "true"); - settings.set(ProcessProperties.CLUSTER_NODE_TYPE, "application"); + TestAppSettings settings = newSettingsForAppNode(); assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isFalse(); } @Test public void accept_throws_MessageException_if_searchHost_is_missing() { + TestAppSettings settings = newSettingsForSearchNode(); settings.clearProperty(SEARCH_HOST); - checkMandatoryProperty(SEARCH_HOST); + assertThatPropertyIsMandatory(settings, SEARCH_HOST); } @Test public void accept_throws_MessageException_if_searchHost_is_blank() { + TestAppSettings settings = newSettingsForSearchNode(); settings.set(SEARCH_HOST, " "); - checkMandatoryProperty(SEARCH_HOST); + assertThatPropertyIsMandatory(settings, SEARCH_HOST); } @Test public void accept_throws_MessageException_if_clusterHosts_is_missing() { + TestAppSettings settings = newSettingsForSearchNode(); settings.clearProperty(CLUSTER_HOSTS); - checkMandatoryProperty(CLUSTER_HOSTS); + assertThatPropertyIsMandatory(settings, CLUSTER_HOSTS); } @Test public void accept_throws_MessageException_if_clusterHosts_is_blank() { + TestAppSettings settings = newSettingsForSearchNode(); settings.set(CLUSTER_HOSTS, " "); - checkMandatoryProperty(CLUSTER_HOSTS); + assertThatPropertyIsMandatory(settings, CLUSTER_HOSTS); } @Test public void accept_throws_MessageException_if_clusterSearchHosts_is_missing() { + TestAppSettings settings = newSettingsForSearchNode(); settings.clearProperty(CLUSTER_SEARCH_HOSTS); - checkMandatoryProperty(CLUSTER_SEARCH_HOSTS); + assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS); } @Test public void accept_throws_MessageException_if_clusterSearchHosts_is_blank() { + TestAppSettings settings = newSettingsForSearchNode(); settings.set(CLUSTER_SEARCH_HOSTS, " "); - checkMandatoryProperty(CLUSTER_SEARCH_HOSTS); + assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS); } - private void checkMandatoryProperty(String key) { + @Test + public void accept_throws_MessageException_if_jwt_token_is_not_set_on_application_nodes() { + TestAppSettings settings = newSettingsForAppNode(); + settings.clearProperty("sonar.auth.jwtBase64Hs256Secret"); + assertThatPropertyIsMandatory(settings, "sonar.auth.jwtBase64Hs256Secret"); + } + + private void assertThatPropertyIsMandatory(TestAppSettings settings, String key) { expectedException.expect(MessageException.class); expectedException.expectMessage(format("Property [%s] is mandatory", key)); new ClusterSettings().accept(settings.getProps()); } - private static TestAppSettings getClusterSettings() { - TestAppSettings testAppSettings = new TestAppSettings() + private static TestAppSettings newSettingsForAppNode() { + return new TestAppSettings() .set(CLUSTER_ENABLED, "true") - .set(CLUSTER_NODE_TYPE, "search") + .set(CLUSTER_NODE_TYPE, "application") .set(CLUSTER_SEARCH_HOSTS, "localhost") .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3") - .set(SEARCH_HOST, "192.168.233.1") - .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance"); - return testAppSettings; + .set("sonar.auth.jwtBase64Hs256Secret", "abcde") + .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar"); + } + + private static TestAppSettings newSettingsForSearchNode() { + return new TestAppSettings() + .set(CLUSTER_ENABLED, "true") + .set(CLUSTER_NODE_TYPE, "search") + .set(CLUSTER_SEARCH_HOSTS, "192.168.233.1") + .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3") + .set(SEARCH_HOST, getNonLoopbackIpv4Address().getHostName()); + } + + private static InetAddress getNonLoopbackIpv4Address() { + try { + Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface networkInterface : Collections.list(nets)) { + if (!networkInterface.isLoopback() && networkInterface.isUp()) { + Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (inetAddress instanceof Inet4Address) { + return inetAddress; + } + } + } + } + } catch (SocketException se) { + throw new RuntimeException("Cannot find a non loopback card required for tests", se); + } + throw new RuntimeException("Cannot find a non loopback card required for tests"); } } |