Browse Source

Enable explicitly enforcing bootstrap checks

This commit adds a system property that enables end-users to explicitly
enforce the bootstrap checks, independently of the binding of the
transport protocol. This can be useful for single-node production
systems that do not bind the transport protocol (and thus the bootstrap
checks would not be enforced).

Relates #23585
Jason Tedor 8 years ago
parent
commit
f7b8128f92

+ 36 - 10
core/src/main/java/org/elasticsearch/bootstrap/BootstrapChecks.java

@@ -48,16 +48,21 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
- * We enforce bootstrap checks once a node has the transport protocol bound to a non-loopback interface. In this case we assume the node is
- * running in production and all bootstrap checks must pass.
+ * We enforce bootstrap checks once a node has the transport protocol bound to a non-loopback interface or if the system property {@code
+ * es.enforce.bootstrap.checks} is set to {@true}. In this case we assume the node is running in production and all bootstrap checks must
+ * pass.
  */
 final class BootstrapChecks {
 
     private BootstrapChecks() {
     }
 
+    static final String ES_ENFORCE_BOOTSTRAP_CHECKS = "es.enforce.bootstrap.checks";
+
     /**
-     * Executes the bootstrap checks if the node has the transport protocol bound to a non-loopback interface.
+     * Executes the bootstrap checks if the node has the transport protocol bound to a non-loopback interface. If the system property
+     * {@code es.enforce.bootstrap.checks} is set to {@code true} then the bootstrap checks will be enforced regardless of whether or not
+     * the transport protocol is bound to a non-loopback interface.
      *
      * @param settings              the current node settings
      * @param boundTransportAddress the node network bindings
@@ -74,7 +79,9 @@ final class BootstrapChecks {
     }
 
     /**
-     * Executes the provided checks and fails the node if {@code enforceLimits} is {@code true}, otherwise logs warnings.
+     * Executes the provided checks and fails the node if {@code enforceLimits} is {@code true}, otherwise logs warnings. If the system
+     * property {@code es.enforce.bootstrap.checks} is set to {@code true} then the bootstrap checks will be enforced regardless of whether
+     * or not the transport protocol is bound to a non-loopback interface.
      *
      * @param enforceLimits {@code true} if the checks should be enforced or otherwise warned
      * @param checks        the checks to execute
@@ -88,7 +95,9 @@ final class BootstrapChecks {
     }
 
     /**
-     * Executes the provided checks and fails the node if {@code enforceLimits} is {@code true}, otherwise logs warnings.
+     * Executes the provided checks and fails the node if {@code enforceLimits} is {@code true}, otherwise logs warnings. If the system
+     * property {@code es.enforce.bootstrap.checks }is set to {@code true} then the bootstrap checks will be enforced regardless of whether
+     * or not the transport protocol is bound to a non-loopback interface.
      *
      * @param enforceLimits {@code true} if the checks should be enforced or otherwise warned
      * @param checks        the checks to execute
@@ -101,13 +110,31 @@ final class BootstrapChecks {
         final List<String> errors = new ArrayList<>();
         final List<String> ignoredErrors = new ArrayList<>();
 
+        final String esEnforceBootstrapChecks = System.getProperty(ES_ENFORCE_BOOTSTRAP_CHECKS);
+        final boolean enforceBootstrapChecks;
+        if (esEnforceBootstrapChecks == null) {
+            enforceBootstrapChecks = false;
+        } else if (Boolean.TRUE.toString().equals(esEnforceBootstrapChecks)) {
+            enforceBootstrapChecks = true;
+        } else {
+            final String message =
+                    String.format(
+                            Locale.ROOT,
+                            "[%s] must be [true] but was [%s]",
+                            ES_ENFORCE_BOOTSTRAP_CHECKS,
+                            esEnforceBootstrapChecks);
+            throw new IllegalArgumentException(message);
+        }
+
         if (enforceLimits) {
             logger.info("bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks");
+        } else if (enforceBootstrapChecks) {
+            logger.info("explicitly enforcing bootstrap checks");
         }
 
         for (final BootstrapCheck check : checks) {
             if (check.check()) {
-                if (!enforceLimits && !check.alwaysEnforce()) {
+                if (!(enforceLimits || enforceBootstrapChecks) && !check.alwaysEnforce()) {
                     ignoredErrors.add(check.errorMessage());
                 } else {
                     errors.add(check.errorMessage());
@@ -127,7 +154,6 @@ final class BootstrapChecks {
             errors.stream().map(IllegalStateException::new).forEach(ne::addSuppressed);
             throw ne;
         }
-
     }
 
     static void log(final Logger logger, final String error) {
@@ -140,9 +166,9 @@ final class BootstrapChecks {
      * @param boundTransportAddress the node network bindings
      * @return {@code true} if the checks should be enforced
      */
-    static boolean enforceLimits(BoundTransportAddress boundTransportAddress) {
-        Predicate<TransportAddress> isLoopbackOrLinkLocalAddress = t -> t.address().getAddress().isLinkLocalAddress()
-            || t.address().getAddress().isLoopbackAddress();
+    static boolean enforceLimits(final BoundTransportAddress boundTransportAddress) {
+        Predicate<TransportAddress> isLoopbackOrLinkLocalAddress =
+                t -> t.address().getAddress().isLinkLocalAddress() || t.address().getAddress().isLoopbackAddress();
         return !(Arrays.stream(boundTransportAddress.boundAddresses()).allMatch(isLoopbackOrLinkLocalAddress) &&
                 isLoopbackOrLinkLocalAddress.test(boundTransportAddress.publishAddress()));
     }

+ 1 - 1
core/src/test/java/org/elasticsearch/bootstrap/BootstrapCheckTests.java → core/src/test/java/org/elasticsearch/bootstrap/BootstrapChecksTests.java

@@ -49,7 +49,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
-public class BootstrapCheckTests extends ESTestCase {
+public class BootstrapChecksTests extends ESTestCase {
 
     public void testNonProductionMode() throws NodeValidationException {
         // nothing should happen since we are in non-production mode

+ 6 - 1
docs/reference/setup/bootstrap-checks.asciidoc

@@ -35,7 +35,12 @@ production mode if it does bind transport to an external interface. Note
 that HTTP can be configured independently of transport via
 <<modules-http,`http.host`>> and <<modules-transport,`transport.host`>>;
 this can be useful for configuring a single instance to be reachable via
-HTTP for testing purposes without triggering production mode.
+HTTP for testing purposes without triggering production mode. If you do
+want to force enforcement of the bootstrap checks independent of the
+binding of the transport protocal, you can set the system property
+`es.enforce.bootstrap.checks` to `true` (this can be useful on a
+single-node production system that does not bind transport to an external
+interface).
 
 === Heap size check
 

+ 116 - 0
qa/evil-tests/src/test/java/org/elasticsearch/bootstrap/EvilBootstrapChecksTests.java

@@ -0,0 +1,116 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.elasticsearch.bootstrap;
+
+import org.apache.logging.log4j.Logger;
+import org.elasticsearch.common.SuppressForbidden;
+import org.elasticsearch.node.NodeValidationException;
+import org.elasticsearch.test.ESTestCase;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.Collections;
+import java.util.List;
+
+import static java.util.Collections.emptyList;
+import static org.elasticsearch.bootstrap.BootstrapChecks.ES_ENFORCE_BOOTSTRAP_CHECKS;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.hasToString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class EvilBootstrapChecksTests extends ESTestCase {
+
+    private String esEnforceBootstrapChecks = System.getProperty(ES_ENFORCE_BOOTSTRAP_CHECKS);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        setEsEnforceBootstrapChecks(esEnforceBootstrapChecks);
+        super.tearDown();
+    }
+
+    public void testEnforceBootstrapChecks() throws NodeValidationException {
+        setEsEnforceBootstrapChecks("true");
+        final List<BootstrapCheck> checks = Collections.singletonList(
+                new BootstrapCheck() {
+                    @Override
+                    public boolean check() {
+                        return true;
+                    }
+
+                    @Override
+                    public String errorMessage() {
+                        return "error";
+                    }
+                }
+        );
+        final Logger logger = mock(Logger.class);
+
+        final NodeValidationException e = expectThrows(
+                NodeValidationException.class,
+                () -> BootstrapChecks.check(false, checks, logger));
+        final Matcher<String> allOf =
+                allOf(containsString("bootstrap checks failed"), containsString("error"));
+        assertThat(e, hasToString(allOf));
+        verify(logger).info("explicitly enforcing bootstrap checks");
+        verifyNoMoreInteractions(logger);
+    }
+
+    public void testNonEnforcedBootstrapChecks() throws NodeValidationException {
+        setEsEnforceBootstrapChecks(null);
+        final Logger logger = mock(Logger.class);
+        // nothing should happen
+        BootstrapChecks.check(false, emptyList(), logger);
+        verifyNoMoreInteractions(logger);
+    }
+
+    public void testInvalidValue() {
+        final String value = randomAsciiOfLength(8);
+        setEsEnforceBootstrapChecks(value);
+        final boolean enforceLimits = randomBoolean();
+        final IllegalArgumentException e = expectThrows(
+                IllegalArgumentException.class,
+                () -> BootstrapChecks.check(enforceLimits, emptyList(), "testInvalidValue"));
+        final Matcher<String> matcher = containsString(
+                "[es.enforce.bootstrap.checks] must be [true] but was [" + value + "]");
+        assertThat(e, hasToString(matcher));
+    }
+
+    @SuppressForbidden(reason = "set or clear system property es.enforce.bootstrap.checks")
+    public void setEsEnforceBootstrapChecks(final String value) {
+        if (value == null) {
+            System.clearProperty(ES_ENFORCE_BOOTSTRAP_CHECKS);
+        } else {
+            System.setProperty(ES_ENFORCE_BOOTSTRAP_CHECKS, value);
+        }
+    }
+
+}