Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bootstrap should implement a denylist of Java versions (ranges) #3164

Merged
merged 2 commits into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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;
Expand Down Expand Up @@ -222,11 +223,32 @@ static List<BootstrapCheck> checks() {
checks.add(new OnErrorCheck());
checks.add(new OnOutOfMemoryErrorCheck());
checks.add(new EarlyAccessCheck());
checks.add(new JvmVersionCheck());
checks.add(new AllPermissionCheck());
checks.add(new DiscoveryConfiguredCheck());
return Collections.unmodifiableList(checks);
}

static class JvmVersionCheck implements BootstrapCheck {
reta marked this conversation as resolved.
Show resolved Hide resolved
@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())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we say where it comes from (JAVA_HOME location)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually publish this information at startup already (at the beginning of startup sequence), at this moment we just saying that it is not recommended

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[2022-05-11T10:16:02,879][INFO ][o.o.n.Node               ] [xxx] version[3.0.0-SNAPSHOT], pid[87008], build[tar/7903ceb845b9f4ed395cce28104c689831ae6379/2022-05-10T18:15:09.508767Z], OS[Linux/5.15.0-27-generic/amd64], JVM[Eclipse Adoptium/OpenJDK 64-Bit Server VM/17.0.3/17.0.3+7]
[2022-05-11T10:16:02,881][INFO ][o.o.n.Node               ] [xxx] JVM home [/OpenSearch/OpenSearch/distribution/archives/linux-tar/build/install/opensearch-3.0.0-SNAPSHOT/jdk], using bundled JDK [true]
[2022-05-11T10:16:02,882][INFO ][o.o.n.Node               ] [xxx] JVM arguments [-Xshare:auto, -Dopensearch.networkaddress.cache.ttl=60, -Dopensearch.networkaddress.cache.negative.ttl=10, -XX:+AlwaysPreTouch, -Xss1m, -Djava.awt.headless=true, -Dfile.encoding=UTF-8, -Djna.nosys=true, -XX:-OmitStackTraceInFastThrow, -XX:+ShowCodeDetailsInExceptionMessages, -Dio.netty.noUnsafe=true, -Dio.netty.noKeySetOptimization=true, -Dio.netty.recycler.maxCapacityPerThread=0, -Dio.netty.allocator.numDirectArenas=0, -Dlog4j.shutdownHookEnabled=false, -Dlog4j2.disable.jmx=true, -Djava.locale.providers=SPI,COMPAT, -Xms1g, -Xmx1g, -XX:+UseG1GC, -XX:G1ReservePercent=25, -XX:InitiatingHeapOccupancyPercent=30, -Djava.io.tmpdir=/tmp/opensearch-18176176448873412724, -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=data, -XX:ErrorFile=logs/hs_err_pid%p.log, -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m, -XX:MaxDirectMemorySize=536870912, -Dopensearch.path.home=/OpenSearch/OpenSearch/distribution/archives/linux-tar/build/install/opensearch-3.0.0-SNAPSHOT, -Dopensearch.path.conf=/OpenSearch/OpenSearch/distribution/archives/linux-tar/build/install/opensearch-3.0.0-SNAPSHOT/config, -Dopensearch.distribution.type=tar, -Dopensearch.bundled_jdk=true]

)
)
.orElseGet(() -> BootstrapCheckResult.success());
}

Runtime.Version getVersion() {
return Runtime.version();
}
}

static class HeapSizeCheck implements BootstrapCheck {

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
reta marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
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;
Expand Down Expand Up @@ -745,4 +746,32 @@ public void testDiscoveryConfiguredCheck() throws NodeValidationException {
// 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.JvmVersionCheck() {
@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));
}
}
Original file line number Diff line number Diff line change
@@ -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"));
}
}