]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9712 sonar.auth.jwtBase64Hs256Secret is mandatory on app nodes
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 1 Sep 2017 07:45:19 +0000 (09:45 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 5 Sep 2017 12:24:13 +0000 (14:24 +0200)
server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java
server/sonar-main/src/main/java/org/sonar/application/config/ClusterSettings.java
server/sonar-main/src/test/java/org/sonar/application/config/ClusterSettingsTest.java
server/sonar-process/src/main/java/org/sonar/process/NodeType.java

index 7dbac3b68cd6db37d3f81418c79dd227533e52ed..bfa8135101399c07e6dbcd7f7ae62b8f2edc92c2 100644 (file)
@@ -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;
index 3c502807ce6b1519b297c2d06c63773850895e81..d0fbdbd8ad4456edf97459e4ec1543c5e810d575 100644 (file)
@@ -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;
index ce6c3a42326908751e21e8827f3a9b8517e20800..166a0e0967a7d57cca8524adcf154e94b083db5a 100644 (file)
  */
 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");
   }
 }
index 55f6e5ef86f905065a72e994b5682b8504fc2eb7..63c532b687d08815c7982c1043048802b5df661a 100644 (file)
@@ -19,8 +19,6 @@
  */
 package org.sonar.process;
 
-import javax.annotation.Nullable;
-
 import static java.util.Arrays.stream;
 
 public enum NodeType {
@@ -36,14 +34,11 @@ public enum NodeType {
     return value;
   }
 
-  public static NodeType parse(@Nullable String nodeType) {
-    if (nodeType == null) {
-      throw new IllegalStateException("Setting [" + ProcessProperties.CLUSTER_NODE_TYPE + "] is mandatory");
-    }
+  public static NodeType parse(String nodeType) {
     return stream(values())
       .filter(t -> nodeType.equals(t.value))
       .findFirst()
-      .orElseThrow(() -> new IllegalStateException("Invalid value for [" + ProcessProperties.CLUSTER_NODE_TYPE + "]: [" + nodeType + "]"));
+      .orElseThrow(() -> new IllegalArgumentException("Invalid value for [" + ProcessProperties.CLUSTER_NODE_TYPE + "]: [" + nodeType + "]"));
   }
 
   public static boolean isValid(String nodeType) {