-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Bootstrap should implement a denylist of Java versions (ranges) (#3164)
* Bootstrap should implement a denylist of Java versions (ranges) Signed-off-by: Andriy Redko <[email protected]> * Addressing code review comments Signed-off-by: Andriy Redko <[email protected]>
- Loading branch information
Showing
5 changed files
with
329 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
169 changes: 169 additions & 0 deletions
169
server/src/main/java/org/opensearch/bootstrap/jvm/DenyJvmVersionsParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
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); | ||
} | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
server/src/main/resources/org/opensearch/bootstrap/jvm/deny-jvm-versions.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
server/src/test/java/org/opensearch/bootstrap/jvm/DenyJvmVersionsParserTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | ||
} | ||
} |