Bootstrap should implement a denylist of Java versions (ranges) (#3164)
* Bootstrap should implement a denylist of Java versions (ranges) Signed-off-by: Andriy Redko <andriy.redko@aiven.io> * Addressing code review comments Signed-off-by: Andriy Redko <andriy.redko@aiven.io>
This commit is contained in:
parent
ad7ce4cd44
commit
677915dc00
|
@ -36,6 +36,7 @@ import org.apache.logging.log4j.LogManager;
|
|||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||
import org.apache.lucene.util.Constants;
|
||||
import org.opensearch.bootstrap.jvm.DenyJvmVersionsParser;
|
||||
import org.opensearch.cluster.coordination.ClusterBootstrapService;
|
||||
import org.opensearch.common.SuppressForbidden;
|
||||
import org.opensearch.common.io.PathUtils;
|
||||
|
@ -224,11 +225,32 @@ final class BootstrapChecks {
|
|||
checks.add(new OnErrorCheck());
|
||||
checks.add(new OnOutOfMemoryErrorCheck());
|
||||
checks.add(new EarlyAccessCheck());
|
||||
checks.add(new JavaVersionCheck());
|
||||
checks.add(new AllPermissionCheck());
|
||||
checks.add(new DiscoveryConfiguredCheck());
|
||||
return Collections.unmodifiableList(checks);
|
||||
}
|
||||
|
||||
static class JavaVersionCheck implements BootstrapCheck {
|
||||
@Override
|
||||
public BootstrapCheckResult check(BootstrapContext context) {
|
||||
return DenyJvmVersionsParser.getDeniedJvmVersions()
|
||||
.stream()
|
||||
.filter(p -> p.test(getVersion()))
|
||||
.findAny()
|
||||
.map(
|
||||
p -> BootstrapCheckResult.failure(
|
||||
String.format(Locale.ROOT, "The current JVM version %s is not recommended for use: %s", getVersion(), p.getReason())
|
||||
)
|
||||
)
|
||||
.orElseGet(() -> BootstrapCheckResult.success());
|
||||
}
|
||||
|
||||
Runtime.Version getVersion() {
|
||||
return Runtime.version();
|
||||
}
|
||||
}
|
||||
|
||||
static class HeapSizeCheck implements BootstrapCheck {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* The OpenSearch Contributors require contributions made to
|
||||
* this file be licensed under the Apache-2.0 license or a
|
||||
* compatible open source license.
|
||||
*/
|
||||
|
||||
package org.opensearch.bootstrap.jvm;
|
||||
|
||||
import org.opensearch.common.Nullable;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.Runtime.Version;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Parses the list of JVM versions which should be denied to run Opensearch engine with due to discovered
|
||||
* issues or flaws.
|
||||
*/
|
||||
public class DenyJvmVersionsParser {
|
||||
public interface VersionPredicate extends Predicate<Version> {
|
||||
String getReason();
|
||||
}
|
||||
|
||||
private static class SingleVersion implements VersionPredicate {
|
||||
final private Version version;
|
||||
final private String reason;
|
||||
|
||||
public SingleVersion(Version version, String reason) {
|
||||
this.version = version;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Version v) {
|
||||
return version.compareTo(v) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
private static class VersionRange implements VersionPredicate {
|
||||
private Version lower;
|
||||
private boolean lowerIncluded;
|
||||
private Version upper;
|
||||
private boolean upperIncluded;
|
||||
private String reason;
|
||||
|
||||
public VersionRange(@Nullable Version lower, boolean lowerIncluded, @Nullable Version upper, boolean upperIncluded, String reason) {
|
||||
this.lower = lower;
|
||||
this.lowerIncluded = lowerIncluded;
|
||||
this.upper = upper;
|
||||
this.upperIncluded = upperIncluded;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(Version v) {
|
||||
if (lower != null) {
|
||||
int compare = lower.compareTo(v);
|
||||
if (compare > 0 || (compare == 0 && lowerIncluded != true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (upper != null) {
|
||||
int compare = upper.compareTo(v);
|
||||
if (compare < 0 || (compare == 0 && upperIncluded != true)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
|
||||
public static Collection<VersionPredicate> getDeniedJvmVersions() {
|
||||
try (
|
||||
final InputStreamReader in = new InputStreamReader(
|
||||
DenyJvmVersionsParser.class.getResourceAsStream("deny-jvm-versions.txt"),
|
||||
StandardCharsets.UTF_8
|
||||
)
|
||||
) {
|
||||
try (final BufferedReader reader = new BufferedReader(in)) {
|
||||
return reader.lines()
|
||||
.map(String::trim)
|
||||
// filter empty lines
|
||||
.filter(line -> line.isEmpty() == false)
|
||||
// filter out all comments
|
||||
.filter(line -> line.startsWith("//") == false)
|
||||
.map(DenyJvmVersionsParser::parse)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
} catch (final IOException ex) {
|
||||
throw new UncheckedIOException("Unable to read the list of denied JVM versions", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse individual line from the list of denied JVM versions. Some version and version range examples are:
|
||||
* <ul>
|
||||
* <li>11.0.2: "... reason ..." - version 11.0.2</li>
|
||||
* <li>[11.0.2, 11.0.14): "... reason ..." - versions 11.0.2.2 (included) to 11.0.14 (not included)</li>
|
||||
* <li>[11.0.2, 11.0.14]: "... reason ..." - versions 11.0.2 to 11.0.14 (both included)</li>
|
||||
* <li>[11.0.2,): "... reason ..." - versions 11.0.2 and higher</li>
|
||||
* </ul>
|
||||
* @param line line to parse
|
||||
* @return version or version range predicate
|
||||
*/
|
||||
static VersionPredicate parse(String line) {
|
||||
final String[] parts = Arrays.stream(line.split("[:]", 2)).map(String::trim).toArray(String[]::new);
|
||||
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("Unable to parse JVM version or version range: " + line);
|
||||
}
|
||||
|
||||
final String versionOrRange = parts[0];
|
||||
final String reason = parts[1];
|
||||
|
||||
// dealing with version range here
|
||||
if (versionOrRange.startsWith("[") == true || versionOrRange.startsWith("(") == true) {
|
||||
if (versionOrRange.endsWith("]") == false && versionOrRange.endsWith(")") == false) {
|
||||
throw new IllegalArgumentException("Unable to parse JVM version range: " + versionOrRange);
|
||||
}
|
||||
|
||||
final boolean lowerIncluded = versionOrRange.startsWith("[");
|
||||
final boolean upperIncluded = versionOrRange.endsWith("]");
|
||||
|
||||
final String[] range = Arrays.stream(versionOrRange.substring(1, versionOrRange.length() - 1).split("[,]", 2))
|
||||
.map(String::trim)
|
||||
.toArray(String[]::new);
|
||||
|
||||
if (range.length != 2) {
|
||||
throw new IllegalArgumentException("Unable to parse JVM version range: " + versionOrRange);
|
||||
}
|
||||
|
||||
Version lower = null;
|
||||
if (range[0].isEmpty() == false && range[0].equals("*") == false) {
|
||||
lower = Version.parse(range[0]);
|
||||
}
|
||||
|
||||
Version upper = null;
|
||||
if (range[1].isEmpty() == false && range[1].equals("*") == false) {
|
||||
upper = Version.parse(range[1]);
|
||||
}
|
||||
|
||||
return new VersionRange(lower, lowerIncluded, upper, upperIncluded, reason);
|
||||
} else {
|
||||
// this is just a single version
|
||||
return new SingleVersion(Version.parse(versionOrRange), reason);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Some version and version range examples are:
|
||||
// 11.0.2: <... reason ...> version 11.0.2.
|
||||
// [11.0.2, 11.0.14): <... reason ...> versions 11.0.2.2 (included) to 11.0.14 (not included)
|
||||
// [11.0.2, 11.0.14]: <... reason ...> versions 11.0.2 to 11.0.14 (both included)
|
||||
// [11.0.2,): <... reason ...> versions 11.0.2 and higher
|
||||
// [11.0.2,*): <... reason ...> versions 11.0.2 and higher
|
||||
// (, 11.0.14]: <... reason ...> versions up to 11.0.14 (included)
|
||||
// (*, 11.0.14]: <... reason ...> versions up to 11.0.14 (included)
|
||||
|
||||
[11.0.2, 11.0.14): for more details, please check https://github.com/opensearch-project/OpenSearch/issues/2791 and https://bugs.openjdk.java.net/browse/JDK-8259541
|
|
@ -47,6 +47,7 @@ import org.opensearch.node.NodeValidationException;
|
|||
import org.opensearch.test.AbstractBootstrapCheckTestCase;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
import java.lang.Runtime.Version;
|
||||
import java.net.InetAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -745,4 +746,32 @@ public class BootstrapChecksTests extends AbstractBootstrapCheckTestCase {
|
|||
// Validate the deprecated setting is still valid during the node bootstrap.
|
||||
ensureChecksPass.accept(Settings.builder().putList(ClusterBootstrapService.INITIAL_MASTER_NODES_SETTING.getKey()));
|
||||
}
|
||||
|
||||
public void testJvmVersionCheck() throws NodeValidationException {
|
||||
final AtomicReference<Version> version = new AtomicReference<>(Version.parse("11.0.13+8"));
|
||||
final BootstrapCheck check = new BootstrapChecks.JavaVersionCheck() {
|
||||
@Override
|
||||
Version getVersion() {
|
||||
return version.get();
|
||||
}
|
||||
};
|
||||
|
||||
final NodeValidationException e = expectThrows(
|
||||
NodeValidationException.class,
|
||||
() -> BootstrapChecks.check(emptyContext, true, Collections.singletonList(check))
|
||||
);
|
||||
assertThat(
|
||||
e.getMessage(),
|
||||
containsString(
|
||||
"The current JVM version 11.0.13+8 is not recommended for use: "
|
||||
+ "for more details, please check https://github.com/opensearch-project/OpenSearch/issues/2791 and https://bugs.openjdk.java.net/browse/JDK-8259541"
|
||||
)
|
||||
);
|
||||
|
||||
version.set(Version.parse("11.0.14"));
|
||||
BootstrapChecks.check(emptyContext, true, Collections.singletonList(check));
|
||||
|
||||
version.set(Runtime.version());
|
||||
BootstrapChecks.check(emptyContext, true, Collections.singletonList(check));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* The OpenSearch Contributors require contributions made to
|
||||
* this file be licensed under the Apache-2.0 license or a
|
||||
* compatible open source license.
|
||||
*/
|
||||
|
||||
package org.opensearch.bootstrap.jvm;
|
||||
|
||||
import org.opensearch.bootstrap.jvm.DenyJvmVersionsParser.VersionPredicate;
|
||||
import org.opensearch.test.OpenSearchTestCase;
|
||||
|
||||
import java.lang.Runtime.Version;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.collection.IsEmptyCollection.empty;
|
||||
|
||||
public class DenyJvmVersionsParserTests extends OpenSearchTestCase {
|
||||
public void testDefaults() {
|
||||
final Collection<VersionPredicate> versions = DenyJvmVersionsParser.getDeniedJvmVersions();
|
||||
assertThat(versions, not(empty()));
|
||||
}
|
||||
|
||||
public void testSingleVersion() {
|
||||
final VersionPredicate predicate = DenyJvmVersionsParser.parse("11.0.2: know to be flawed version");
|
||||
assertThat(predicate.test(Version.parse("11.0.2")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.1")), is(false));
|
||||
assertThat(predicate.test(Version.parse("11.0.3")), is(false));
|
||||
assertThat(predicate.getReason(), equalTo("know to be flawed version"));
|
||||
}
|
||||
|
||||
public void testVersionRangeLowerIncluded() {
|
||||
final VersionPredicate predicate = DenyJvmVersionsParser.parse("[11.0.2, 11.0.14): know to be flawed version");
|
||||
assertThat(predicate.test(Version.parse("11.0.2")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.1")), is(false));
|
||||
assertThat(predicate.test(Version.parse("11.0.13+1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14")), is(false));
|
||||
assertThat(predicate.getReason(), equalTo("know to be flawed version"));
|
||||
}
|
||||
|
||||
public void testVersionRangeUpperIncluded() {
|
||||
final VersionPredicate predicate = DenyJvmVersionsParser.parse("[11.0.2,): know to be flawed version");
|
||||
assertThat(predicate.test(Version.parse("11.0.2")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.1")), is(false));
|
||||
assertThat(predicate.test(Version.parse("11.0.13+1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14")), is(true));
|
||||
assertThat(predicate.test(Version.parse("17.2.1")), is(true));
|
||||
assertThat(predicate.getReason(), equalTo("know to be flawed version"));
|
||||
}
|
||||
|
||||
public void testVersionRangeLowerAndUpperIncluded() {
|
||||
final VersionPredicate predicate = DenyJvmVersionsParser.parse("[11.0.2, 11.0.14]: know to be flawed version");
|
||||
assertThat(predicate.test(Version.parse("11.0.2")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.1")), is(false));
|
||||
assertThat(predicate.test(Version.parse("11.0.13+1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14.1")), is(false));
|
||||
assertThat(predicate.test(Version.parse("11.0.15")), is(false));
|
||||
assertThat(predicate.getReason(), equalTo("know to be flawed version"));
|
||||
}
|
||||
|
||||
public void testAllVersionsRange() {
|
||||
final VersionPredicate predicate = DenyJvmVersionsParser.parse("(,): know to be flawed version");
|
||||
assertThat(predicate.test(Version.parse("11.0.2")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.13+1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14.1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.15")), is(true));
|
||||
assertThat(predicate.test(Version.parse("17.2.1")), is(true));
|
||||
assertThat(predicate.getReason(), equalTo("know to be flawed version"));
|
||||
}
|
||||
|
||||
public void testAllVersionsRangeIncluded() {
|
||||
final VersionPredicate predicate = DenyJvmVersionsParser.parse("[*, *]: know to be flawed version");
|
||||
assertThat(predicate.test(Version.parse("11.0.2")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.13+1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.14.1")), is(true));
|
||||
assertThat(predicate.test(Version.parse("11.0.15")), is(true));
|
||||
assertThat(predicate.test(Version.parse("17.2.1")), is(true));
|
||||
assertThat(predicate.getReason(), equalTo("know to be flawed version"));
|
||||
}
|
||||
|
||||
public void testIncorrectVersionRanges() {
|
||||
assertThrows(IllegalArgumentException.class, () -> DenyJvmVersionsParser.parse("[*, *: know to be flawed version"));
|
||||
assertThrows(IllegalArgumentException.class, () -> DenyJvmVersionsParser.parse("*, *: know to be flawed version"));
|
||||
assertThrows(IllegalArgumentException.class, () -> DenyJvmVersionsParser.parse("*, *): know to be flawed version"));
|
||||
assertThrows(IllegalArgumentException.class, () -> DenyJvmVersionsParser.parse("(): know to be flawed version"));
|
||||
assertThrows(IllegalArgumentException.class, () -> DenyJvmVersionsParser.parse("[]: know to be flawed version"));
|
||||
assertThrows(IllegalArgumentException.class, () -> DenyJvmVersionsParser.parse("[,]"));
|
||||
assertThrows(IllegalArgumentException.class, () -> DenyJvmVersionsParser.parse("11.0.2"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue