From 10ed6d2a42e9142384019098a505ab5e90febfc9 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Wed, 29 Jul 2026 18:06:06 +0530 Subject: [PATCH 1/8] fix(server): configure finite DNS cache TTL Load networkaddress.cache.ttl from a Java security properties file when the HugeGraph security manager is enabled. Add distribution-level coverage for property semantics, startup wiring, disabled mode, and operator overrides. Fixes #3124 --- .github/workflows/server-ci.yml | 1 + .../assembly/static/bin/hugegraph-server.sh | 4 +- .../static/conf/java-security.properties | 18 +++ .../travis/test-java-security-properties.sh | 145 ++++++++++++++++++ 4 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 hugegraph-server/hugegraph-dist/src/assembly/static/conf/java-security.properties create mode 100755 hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index 0d6c54b51c..2068b08303 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -89,6 +89,7 @@ jobs: mvn package -Dmaven.test.skip=true -pl hugegraph-server/hugegraph-dist -am -ntp VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) SERVER_DIR=hugegraph-server/apache-hugegraph-server-$VERSION/ + $TRAVIS_DIR/test-java-security-properties.sh $SERVER_DIR $TRAVIS_DIR/test-start-hugegraph.sh $SERVER_DIR - name: Startup tests skipped (missing prerequisites) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index 3915a5f63e..63ec66f169 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -143,7 +143,9 @@ esac JVM_OPTIONS="-Dlog4j.configurationFile=${CONF}/log4j2.xml" if [[ ${OPEN_SECURITY_CHECK} == "true" ]]; then - JVM_OPTIONS="${JVM_OPTIONS} -Djava.security.manager=org.apache.hugegraph.security.HugeSecurityManager" + JVM_OPTIONS="${JVM_OPTIONS} \ + -Djava.security.properties=${CONF}/java-security.properties \ + -Djava.security.manager=org.apache.hugegraph.security.HugeSecurityManager" fi if [ "${OPEN_TELEMETRY}" == "true" ]; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/conf/java-security.properties b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/java-security.properties new file mode 100644 index 0000000000..243faa3e0e --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/conf/java-security.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF 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. + +# Permit failed network clients to resolve a changed address instead of +# retaining the first successful DNS result for the lifetime of the JVM. +networkaddress.cache.ttl=30 diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh new file mode 100755 index 0000000000..47e1939175 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF 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. + +set -euo pipefail + +SERVER_ROOT_INPUT="${1:?Usage: $0 PATH_TO_SERVER_DIST}" +SERVER_ROOT=$(cd "$SERVER_ROOT_INPUT" && pwd) +SERVER_SCRIPT="${SERVER_ROOT}/bin/hugegraph-server.sh" +CONF="${SERVER_ROOT}/conf" +SECURITY_PROPERTIES="${CONF}/java-security.properties" + +fail() { + echo "FAIL: $1" >&2 + exit 1 +} + +assert_argument() { + local argument="$1" + local capture="$2" + grep -Fxq -- "$argument" "$capture" || \ + fail "missing JVM argument: $argument" +} + +assert_no_argument() { + local pattern="$1" + local capture="$2" + if grep -Eq -- "$pattern" "$capture"; then + fail "unexpected JVM argument matching: $pattern" + fi +} + +if [[ ! -x "$SERVER_SCRIPT" ]]; then + fail "server script is not executable: $SERVER_SCRIPT" +fi +if [[ ! -f "$SECURITY_PROPERTIES" ]]; then + fail "security properties file is missing: $SECURITY_PROPERTIES" +fi + +if [[ -n "${JAVA_HOME:-}" ]]; then + JAVA_BIN="${JAVA_HOME}/bin/java" +else + JAVA_BIN="java" +fi + +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +CHECK_SOURCE="${TEMP_DIR}/ReadDnsCacheTtl.java" +cat > "$CHECK_SOURCE" <<'JAVA' +import java.security.Security; + +public class ReadDnsCacheTtl { + public static void main(String[] args) { + System.out.print(Security.getProperty("networkaddress.cache.ttl")); + } +} +JAVA + +ACTUAL_TTL=$("$JAVA_BIN" \ + -Djava.security.properties="$SECURITY_PROPERTIES" "$CHECK_SOURCE") +if [[ "$ACTUAL_TTL" != "30" ]]; then + fail "expected security property TTL 30, got: $ACTUAL_TTL" +fi + +SYSTEM_PROPERTY_TTL=$("$JAVA_BIN" \ + -Dnetworkaddress.cache.ttl=99 \ + -Djava.security.properties="$SECURITY_PROPERTIES" "$CHECK_SOURCE") +if [[ "$SYSTEM_PROPERTY_TTL" != "30" ]]; then + fail "ordinary -D property unexpectedly changed the security property" +fi + +OPERATOR_PROPERTIES="${TEMP_DIR}/operator-security.properties" +echo "networkaddress.cache.ttl=45" > "$OPERATOR_PROPERTIES" +OPERATOR_TTL=$("$JAVA_BIN" \ + -Djava.security.properties="$SECURITY_PROPERTIES" \ + -Djava.security.properties="$OPERATOR_PROPERTIES" "$CHECK_SOURCE") +if [[ "$OPERATOR_TTL" != "45" ]]; then + fail "operator security properties override was not honored" +fi + +MOCK_JAVA_HOME="${TEMP_DIR}/mock-java-home" +mkdir -p "${MOCK_JAVA_HOME}/bin" +cat > "${MOCK_JAVA_HOME}/bin/java" <<'MOCK' +#!/bin/bash +if [[ " $* " == *" -version "* ]]; then + echo 'openjdk version "11.0.0"' >&2 + exit 0 +fi +printf '%s\n' "$@" > "$CAPTURE_FILE" +MOCK +chmod +x "${MOCK_JAVA_HOME}/bin/java" + +ENABLED_CAPTURE="${TEMP_DIR}/enabled.args" +CAPTURE_FILE="$ENABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Doperator.marker=preserved" >/dev/null + +assert_argument \ + "-Djava.security.properties=${SECURITY_PROPERTIES}" "$ENABLED_CAPTURE" +assert_argument \ + "-Djava.security.manager=org.apache.hugegraph.security.HugeSecurityManager" \ + "$ENABLED_CAPTURE" +assert_argument "-Doperator.marker=preserved" "$ENABLED_CAPTURE" +assert_no_argument '^-D(networkaddress\.cache\.ttl|sun\.net\.inetaddr\.ttl)=' \ + "$ENABLED_CAPTURE" + +DISABLED_CAPTURE="${TEMP_DIR}/disabled.args" +CAPTURE_FILE="$DISABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" false \ + "-Doperator.marker=preserved" >/dev/null + +assert_no_argument '^-Djava\.security\.properties=' "$DISABLED_CAPTURE" +assert_no_argument '^-Djava\.security\.manager=' "$DISABLED_CAPTURE" +assert_argument "-Doperator.marker=preserved" "$DISABLED_CAPTURE" + +OVERRIDE_CAPTURE="${TEMP_DIR}/override.args" +CAPTURE_FILE="$OVERRIDE_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" >/dev/null + +LAST_SECURITY_ARGUMENT=$(grep -E '^-Djava\.security\.properties=' \ + "$OVERRIDE_CAPTURE" | tail -n 1) +if [[ "$LAST_SECURITY_ARGUMENT" != \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" ]]; then + fail "operator security properties argument was overwritten" +fi + +echo "PASS: Java security properties and startup wiring" From 35e1a2404386d561bed9b42a7ed0e3df53fb6164 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Thu, 30 Jul 2026 00:52:02 +0530 Subject: [PATCH 2/8] fix(server): validate DNS security properties --- .github/workflows/server-ci.yml | 10 ++- .../assembly/static/bin/hugegraph-server.sh | 21 ++++- .../travis/test-java-security-properties.sh | 86 ++++++++++++++++++- 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index 2068b08303..4c34e1c257 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -70,6 +70,14 @@ jobs: run: | mvn clean compile -U -Dmaven.javadoc.skip=true -ntp + - name: Run Java security properties tests + if: ${{ env.BACKEND == 'rocksdb' }} + run: | + mvn package -Dmaven.test.skip=true -pl hugegraph-server/hugegraph-dist -am -ntp + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + SERVER_DIR=hugegraph-server/apache-hugegraph-server-$VERSION/ + $TRAVIS_DIR/test-java-security-properties.sh $SERVER_DIR + - name: Check startup test prerequisites id: server-preflight if: ${{ env.BACKEND == 'rocksdb' }} @@ -86,10 +94,8 @@ jobs: - name: Run start-hugegraph.sh foreground mode tests if: ${{ env.BACKEND == 'rocksdb' && steps.server-preflight.outputs.can_run == 'true' }} run: | - mvn package -Dmaven.test.skip=true -pl hugegraph-server/hugegraph-dist -am -ntp VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) SERVER_DIR=hugegraph-server/apache-hugegraph-server-$VERSION/ - $TRAVIS_DIR/test-java-security-properties.sh $SERVER_DIR $TRAVIS_DIR/test-start-hugegraph.sh $SERVER_DIR - name: Startup tests skipped (missing prerequisites) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index 63ec66f169..b798aede98 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -143,8 +143,27 @@ esac JVM_OPTIONS="-Dlog4j.configurationFile=${CONF}/log4j2.xml" if [[ ${OPEN_SECURITY_CHECK} == "true" ]]; then + SECURITY_PROPERTIES="${CONF}/java-security.properties" + HAS_SECURITY_PROPERTIES_OVERRIDE=false + for JAVA_OPTION in ${JAVA_OPTIONS}; do + case "${JAVA_OPTION}" in + -Djava.security.properties=) + HAS_SECURITY_PROPERTIES_OVERRIDE=false + ;; + -Djava.security.properties=*) + HAS_SECURITY_PROPERTIES_OVERRIDE=true + ;; + esac + done + if [[ ! -f "${SECURITY_PROPERTIES}" || ! -r "${SECURITY_PROPERTIES}" ]] && + [[ "${HAS_SECURITY_PROPERTIES_OVERRIDE}" != "true" ]]; then + ERROR="Required Java security properties path is not a readable file: ${SECURITY_PROPERTIES}" + echo "${ERROR}" >&2 + echo "${ERROR}" >> "${OUTPUT}" + exit 1 + fi JVM_OPTIONS="${JVM_OPTIONS} \ - -Djava.security.properties=${CONF}/java-security.properties \ + -Djava.security.properties=${SECURITY_PROPERTIES} \ -Djava.security.manager=org.apache.hugegraph.security.HugeSecurityManager" fi diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index 47e1939175..7a0fb7721a 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -57,7 +57,20 @@ else fi TEMP_DIR=$(mktemp -d) -trap 'rm -rf "$TEMP_DIR"' EXIT +SECURITY_PROPERTIES_BACKUP="${TEMP_DIR}/java-security.properties" + +cleanup() { + if [[ -d "$SECURITY_PROPERTIES" ]]; then + rmdir "$SECURITY_PROPERTIES" + fi + if [[ -f "$SECURITY_PROPERTIES_BACKUP" && + ! -e "$SECURITY_PROPERTIES" ]]; then + mv "$SECURITY_PROPERTIES_BACKUP" "$SECURITY_PROPERTIES" + fi + rm -rf "$TEMP_DIR" +} + +trap cleanup EXIT CHECK_SOURCE="${TEMP_DIR}/ReadDnsCacheTtl.java" cat > "$CHECK_SOURCE" <<'JAVA' @@ -92,6 +105,14 @@ if [[ "$OPERATOR_TTL" != "45" ]]; then fail "operator security properties override was not honored" fi +MISSING_DEFAULT="${TEMP_DIR}/missing-default-security.properties" +MISSING_DEFAULT_TTL=$("$JAVA_BIN" \ + -Djava.security.properties="$MISSING_DEFAULT" \ + -Djava.security.properties="$OPERATOR_PROPERTIES" "$CHECK_SOURCE") +if [[ "$MISSING_DEFAULT_TTL" != "45" ]]; then + fail "operator override did not replace a missing bundled properties file" +fi + MOCK_JAVA_HOME="${TEMP_DIR}/mock-java-home" mkdir -p "${MOCK_JAVA_HOME}/bin" cat > "${MOCK_JAVA_HOME}/bin/java" <<'MOCK' @@ -142,4 +163,67 @@ if [[ "$LAST_SECURITY_ARGUMENT" != \ fail "operator security properties argument was overwritten" fi +mv "$SECURITY_PROPERTIES" "$SECURITY_PROPERTIES_BACKUP" + +MISSING_ERROR="${TEMP_DIR}/missing-security-properties.err" +if CAPTURE_FILE="${TEMP_DIR}/missing.args" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Doperator.marker=preserved" >/dev/null 2>"$MISSING_ERROR"; then + fail "server startup succeeded without security properties" +fi +grep -Fq "Required Java security properties path is not a readable file" \ + "$MISSING_ERROR" || fail "missing security properties error was not reported" + +EMPTY_OVERRIDE_ERROR="${TEMP_DIR}/empty-override.err" +if CAPTURE_FILE="${TEMP_DIR}/empty-override.args" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties=" >/dev/null 2>"$EMPTY_OVERRIDE_ERROR"; then + fail "server startup accepted an empty security properties override" +fi +grep -Fq "Required Java security properties path is not a readable file" \ + "$EMPTY_OVERRIDE_ERROR" || fail "empty override error was not reported" + +EMPTY_LAST_OVERRIDE_ERROR="${TEMP_DIR}/empty-last-override.err" +if CAPTURE_FILE="${TEMP_DIR}/empty-last-override.args" \ + JAVA_HOME="$MOCK_JAVA_HOME" STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties=${OPERATOR_PROPERTIES} \ + -Djava.security.properties=" >/dev/null 2>"$EMPTY_LAST_OVERRIDE_ERROR"; then + fail "server startup accepted an empty final security properties override" +fi +grep -Fq "Required Java security properties path is not a readable file" \ + "$EMPTY_LAST_OVERRIDE_ERROR" || \ + fail "empty final override error was not reported" + +MISSING_OVERRIDE_CAPTURE="${TEMP_DIR}/missing-override.args" +CAPTURE_FILE="$MISSING_OVERRIDE_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties= \ + -Djava.security.properties=${OPERATOR_PROPERTIES}" >/dev/null + +LAST_SECURITY_ARGUMENT=$(grep -E '^-Djava\.security\.properties=' \ + "$MISSING_OVERRIDE_CAPTURE" | tail -n 1) +if [[ "$LAST_SECURITY_ARGUMENT" != \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" ]]; then + fail "operator override did not bypass the missing bundled properties" +fi + +mkdir "$SECURITY_PROPERTIES" + +NON_FILE_ERROR="${TEMP_DIR}/non-file-security-properties.err" +if CAPTURE_FILE="${TEMP_DIR}/non-file.args" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Doperator.marker=preserved" >/dev/null 2>"$NON_FILE_ERROR"; then + fail "server startup accepted a non-file security properties path" +fi +grep -Fq "Required Java security properties path is not a readable file" \ + "$NON_FILE_ERROR" || fail "non-file security properties error was not reported" + +rmdir "$SECURITY_PROPERTIES" +mv "$SECURITY_PROPERTIES_BACKUP" "$SECURITY_PROPERTIES" + echo "PASS: Java security properties and startup wiring" From 0a07cd883cfce35163d74648cf1ef8f5afbf84f6 Mon Sep 17 00:00:00 2001 From: imbajin Date: Fri, 31 Jul 2026 21:39:49 +0800 Subject: [PATCH 3/8] fix(server): validate DNS policy before startup - validate the effective Java security DNS TTL - bootstrap before logging or hostname resolution - install HugeSecurityManager across supported JDKs - cover overrides and malformed security properties --- .../assembly/static/bin/hugegraph-server.sh | 38 +- .../travis/test-java-security-properties.sh | 329 +++++++++++++++--- .../bootstrap/HugeGraphServerBootstrap.java | 81 +++++ .../HugeGraphServerBootstrapTest.java | 50 +++ 4 files changed, 416 insertions(+), 82 deletions(-) create mode 100644 hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java create mode 100644 hugegraph-server/hugegraph-dist/src/test/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrapTest.java diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index b798aede98..1526c54ada 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -142,29 +142,15 @@ case "$GC_OPTION" in esac JVM_OPTIONS="-Dlog4j.configurationFile=${CONF}/log4j2.xml" +SECURITY_MANAGER_OPTION="" if [[ ${OPEN_SECURITY_CHECK} == "true" ]]; then SECURITY_PROPERTIES="${CONF}/java-security.properties" - HAS_SECURITY_PROPERTIES_OVERRIDE=false - for JAVA_OPTION in ${JAVA_OPTIONS}; do - case "${JAVA_OPTION}" in - -Djava.security.properties=) - HAS_SECURITY_PROPERTIES_OVERRIDE=false - ;; - -Djava.security.properties=*) - HAS_SECURITY_PROPERTIES_OVERRIDE=true - ;; - esac - done - if [[ ! -f "${SECURITY_PROPERTIES}" || ! -r "${SECURITY_PROPERTIES}" ]] && - [[ "${HAS_SECURITY_PROPERTIES_OVERRIDE}" != "true" ]]; then - ERROR="Required Java security properties path is not a readable file: ${SECURITY_PROPERTIES}" - echo "${ERROR}" >&2 - echo "${ERROR}" >> "${OUTPUT}" - exit 1 - fi JVM_OPTIONS="${JVM_OPTIONS} \ - -Djava.security.properties=${SECURITY_PROPERTIES} \ - -Djava.security.manager=org.apache.hugegraph.security.HugeSecurityManager" + -Djava.security.properties=${SECURITY_PROPERTIES}" + if [[ ${JAVA_VERSION} -ge 18 ]]; then + # Required to install HugeSecurityManager programmatically on JDK 18+. + SECURITY_MANAGER_OPTION="-Djava.security.manager=allow" + fi fi if [ "${OPEN_TELEMETRY}" == "true" ]; then @@ -207,10 +193,14 @@ fi # Turn on security check if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphServer" ${JVM_OPTIONS} ${JAVA_OPTIONS} -cp ${CLASSPATH}: \ - org.apache.hugegraph.dist.HugeGraphServer ${GREMLIN_SERVER_CONF} ${REST_SERVER_CONF} + exec ${JAVA} -Dname="HugeGraphServer" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + ${SECURITY_MANAGER_OPTION} -cp ${CLASSPATH}: \ + org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap \ + ${OPEN_SECURITY_CHECK} ${GREMLIN_SERVER_CONF} ${REST_SERVER_CONF} else - exec ${JAVA} -Dname="HugeGraphServer" ${JVM_OPTIONS} ${JAVA_OPTIONS} -cp ${CLASSPATH}: \ - org.apache.hugegraph.dist.HugeGraphServer ${GREMLIN_SERVER_CONF} ${REST_SERVER_CONF} \ + exec ${JAVA} -Dname="HugeGraphServer" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + ${SECURITY_MANAGER_OPTION} -cp ${CLASSPATH}: \ + org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap \ + ${OPEN_SECURITY_CHECK} ${GREMLIN_SERVER_CONF} ${REST_SERVER_CONF} \ >> ${LOGS}/hugegraph-server-stdout.log 2>&1 fi diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index 7a0fb7721a..f045049fe2 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -55,6 +55,12 @@ if [[ -n "${JAVA_HOME:-}" ]]; then else JAVA_BIN="java" fi +JAVA_MAJOR=$($JAVA_BIN -version 2>&1 | head -1 | cut -d'"' -f2 | + sed 's/^1\.//' | cut -d'.' -f1) +SECURITY_MANAGER_OPTION="" +if [[ "$JAVA_MAJOR" -ge 18 ]]; then + SECURITY_MANAGER_OPTION="-Djava.security.manager=allow" +fi TEMP_DIR=$(mktemp -d) SECURITY_PROPERTIES_BACKUP="${TEMP_DIR}/java-security.properties" @@ -78,11 +84,154 @@ import java.security.Security; public class ReadDnsCacheTtl { public static void main(String[] args) { - System.out.print(Security.getProperty("networkaddress.cache.ttl")); + String value = Security.getProperty("networkaddress.cache.ttl"); + if (args.length == 0) { + System.out.print(value); + return; + } + try { + if (Integer.parseInt(value) <= 0) { + System.exit(1); + } + } catch (NumberFormatException e) { + System.exit(1); + } } } JAVA +assert_valid_security_properties() { + "$JAVA_BIN" "$@" "$CHECK_SOURCE" --validate >/dev/null || + fail "expected valid Java security properties: $*" +} + +assert_invalid_security_properties() { + if "$JAVA_BIN" "$@" "$CHECK_SOURCE" --validate >/dev/null 2>&1; then + fail "expected invalid Java security properties: $*" + fi +} + +assert_clean_bootstrap_error() { + local error_file="$1" + if grep -Eq 'Log4j|NetUtils|UnknownHost|hostname' "$error_file"; then + fail "bootstrap initialized logging or hostname resolution" + fi +} + +assert_bootstrap_rejects_security_properties() { + local error_file="${TEMP_DIR}/server-validation.err" + if "$JAVA_BIN" "$@" \ + ${SECURITY_MANAGER_OPTION} \ + -cp "${SERVER_ROOT}/lib/*" \ + org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap true \ + >/dev/null 2>"$error_file"; then + fail "server accepted invalid Java security properties: $*" + fi + grep -Fq "networkaddress.cache.ttl must load as a finite positive integer" \ + "$error_file" || fail "server did not report the invalid DNS TTL" + assert_clean_bootstrap_error "$error_file" +} + +assert_bootstrap_accepts_security_properties() { + local error_file="${TEMP_DIR}/server-validation.err" + if "$JAVA_BIN" "$@" \ + ${SECURITY_MANAGER_OPTION} \ + -cp "${SERVER_ROOT}/lib/*" \ + org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap true \ + >/dev/null 2>"$error_file"; then + fail "server unexpectedly started without configuration arguments" + fi + grep -Fq "Expected validation flag and two HugeGraphServer" \ + "$error_file" || fail "valid DNS TTL did not reach argument validation" + assert_clean_bootstrap_error "$error_file" +} + +assert_bootstrap_handles_security_properties_load_failure() { + local error_file="${TEMP_DIR}/server-validation.err" + if "$JAVA_BIN" "$@" \ + ${SECURITY_MANAGER_OPTION} \ + -cp "${SERVER_ROOT}/lib/*" \ + org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap true \ + >/dev/null 2>"$error_file"; then + fail "server accepted unloadable Java security properties: $*" + fi + grep -Fq "networkaddress.cache.ttl must load as a finite positive integer" \ + "$error_file" || fail "server did not report a stable load error" + assert_clean_bootstrap_error "$error_file" +} + +assert_bootstrap_skips_security_validation() { + local error_file="${TEMP_DIR}/server-validation.err" + if "$JAVA_BIN" "$@" -cp "${SERVER_ROOT}/lib/*" \ + org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap false \ + >/dev/null 2>"$error_file"; then + fail "server unexpectedly started without configuration arguments" + fi + grep -Fq "Expected validation flag and two HugeGraphServer" \ + "$error_file" || fail "disabled DNS TTL validation was not skipped" + assert_clean_bootstrap_error "$error_file" +} + +assert_launcher_rejects_marker_bypass() { + local marker_value="$1" + local error_file="${TEMP_DIR}/launcher-marker-${marker_value}.err" + if _JAVA_OPTIONS="-Dhugegraph.security.validate_dns_cache_ttl=${marker_value}" \ + JAVA_OPTIONS="" STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties=${INFINITE_PROPERTIES}" \ + >/dev/null 2>"$error_file"; then + fail "_JAVA_OPTIONS marker bypassed DNS TTL validation" + fi + grep -Fq "networkaddress.cache.ttl must load as a finite positive integer" \ + "$error_file" || fail "launcher did not report invalid DNS TTL" + assert_clean_bootstrap_error "$error_file" +} + +assert_launcher_rejects_security_properties() { + local properties_path="$1" + local error_file="${TEMP_DIR}/launcher-properties.err" + if JAVA_OPTIONS="" STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties=${properties_path}" \ + >/dev/null 2>"$error_file"; then + fail "launcher accepted invalid Java security properties" + fi + grep -Fq "networkaddress.cache.ttl must load as a finite positive integer" \ + "$error_file" || fail "launcher did not report invalid DNS TTL" + assert_clean_bootstrap_error "$error_file" +} + +assert_launcher_accepts_security_properties() { + local properties_path="$1" + local error_file="${TEMP_DIR}/launcher-valid.err" + if JAVA_OPTIONS="" STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${TEMP_DIR}/missing-gremlin.yaml" \ + "${TEMP_DIR}/missing-rest.properties" true \ + "-Djava.security.properties=${properties_path}" \ + >/dev/null 2>"$error_file"; then + fail "server unexpectedly started with missing configuration" + fi + if grep -Eq 'networkaddress.cache.ttl must load|Failed to install' \ + "$error_file"; then + fail "launcher rejected valid Java security properties" + fi +} + +assert_launcher_skips_security_validation() { + local error_file="${TEMP_DIR}/launcher-disabled.err" + if _JAVA_OPTIONS="-Dhugegraph.security.validate_dns_cache_ttl=true \ + -Djava.security.properties=${INFINITE_PROPERTIES}" \ + JAVA_OPTIONS="" STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${TEMP_DIR}/missing-gremlin.yaml" \ + "${TEMP_DIR}/missing-rest.properties" false \ + >/dev/null 2>"$error_file"; then + fail "server unexpectedly started with missing configuration" + fi + if grep -Fq "networkaddress.cache.ttl must load" "$error_file"; then + fail "disabled launcher unexpectedly validated DNS TTL" + fi +} + ACTUAL_TTL=$("$JAVA_BIN" \ -Djava.security.properties="$SECURITY_PROPERTIES" "$CHECK_SOURCE") if [[ "$ACTUAL_TTL" != "30" ]]; then @@ -97,7 +246,7 @@ if [[ "$SYSTEM_PROPERTY_TTL" != "30" ]]; then fi OPERATOR_PROPERTIES="${TEMP_DIR}/operator-security.properties" -echo "networkaddress.cache.ttl=45" > "$OPERATOR_PROPERTIES" +echo "networkaddress.cache.ttl = 45" > "$OPERATOR_PROPERTIES" OPERATOR_TTL=$("$JAVA_BIN" \ -Djava.security.properties="$SECURITY_PROPERTIES" \ -Djava.security.properties="$OPERATOR_PROPERTIES" "$CHECK_SOURCE") @@ -105,6 +254,83 @@ if [[ "$OPERATOR_TTL" != "45" ]]; then fail "operator security properties override was not honored" fi +REPLACEMENT_TTL=$("$JAVA_BIN" \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" \ + "-Djava.security.properties==${OPERATOR_PROPERTIES}" "$CHECK_SOURCE") +if [[ "$REPLACEMENT_TTL" != "45" ]]; then + fail "operator security properties replacement was not honored" +fi + +assert_valid_security_properties \ + "-Djava.security.properties=${SECURITY_PROPERTIES}" \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" +assert_valid_security_properties \ + "-Djava.security.properties==${OPERATOR_PROPERTIES}" + +SPACED_PROPERTIES="${TEMP_DIR}/operator security.properties" +cp "$OPERATOR_PROPERTIES" "$SPACED_PROPERTIES" +SPACED_PROPERTIES_URL="file:${SPACED_PROPERTIES// /%20}" +assert_valid_security_properties \ + "-Djava.security.properties=${SPACED_PROPERTIES_URL}" + +ESCAPED_DUPLICATE="${TEMP_DIR}/escaped-duplicate.properties" +cat > "$ESCAPED_DUPLICATE" <<'PROPERTIES' +networkaddress.cache.ttl=45 +networkaddress.cache.tt\u006c=-1 +PROPERTIES +assert_invalid_security_properties \ + "-Djava.security.properties=${ESCAPED_DUPLICATE}" + +CONTINUED_PROPERTIES="${TEMP_DIR}/continued.properties" +cat > "$CONTINUED_PROPERTIES" <<'PROPERTIES' +unrelated.property=value\ +networkaddress.cache.ttl=45 +PROPERTIES +assert_invalid_security_properties \ + "-Djava.security.properties==${CONTINUED_PROPERTIES}" + +INVALID_UNICODE="${TEMP_DIR}/invalid-unicode.properties" +cat > "$INVALID_UNICODE" <<'PROPERTIES' +networkaddress.cache.ttl=\u00ZZ +PROPERTIES +assert_invalid_security_properties \ + "-Djava.security.properties=${INVALID_UNICODE}" + +INFINITE_PROPERTIES="${TEMP_DIR}/infinite-security.properties" +echo "networkaddress.cache.ttl=-1" > "$INFINITE_PROPERTIES" +assert_invalid_security_properties \ + "-Djava.security.properties=${INFINITE_PROPERTIES}" + +MISSING_OVERRIDE="${TEMP_DIR}/missing-operator-security.properties" +assert_invalid_security_properties \ + "-Djava.security.properties=${MISSING_OVERRIDE}" + +assert_bootstrap_accepts_security_properties \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" +assert_bootstrap_accepts_security_properties \ + "-Djava.security.properties==${OPERATOR_PROPERTIES}" +assert_bootstrap_accepts_security_properties \ + "-Djava.security.properties=${SPACED_PROPERTIES_URL}" +assert_bootstrap_rejects_security_properties \ + "-Djava.security.properties=${ESCAPED_DUPLICATE}" +assert_bootstrap_rejects_security_properties \ + "-Djava.security.properties==${CONTINUED_PROPERTIES}" +assert_bootstrap_rejects_security_properties \ + "-Djava.security.properties=${INFINITE_PROPERTIES}" +assert_bootstrap_rejects_security_properties \ + "-Djava.security.properties=${MISSING_OVERRIDE}" +assert_bootstrap_handles_security_properties_load_failure \ + "-Djava.security.properties=${INVALID_UNICODE}" +assert_bootstrap_skips_security_validation \ + "-Djava.security.properties=${INFINITE_PROPERTIES}" +assert_launcher_rejects_marker_bypass false +assert_launcher_rejects_marker_bypass true +assert_launcher_rejects_security_properties "$MISSING_OVERRIDE" +assert_launcher_rejects_security_properties "$INFINITE_PROPERTIES" +assert_launcher_rejects_security_properties "$INVALID_UNICODE" +assert_launcher_accepts_security_properties "$OPERATOR_PROPERTIES" +assert_launcher_skips_security_validation + MISSING_DEFAULT="${TEMP_DIR}/missing-default-security.properties" MISSING_DEFAULT_TTL=$("$JAVA_BIN" \ -Djava.security.properties="$MISSING_DEFAULT" \ @@ -118,7 +344,7 @@ mkdir -p "${MOCK_JAVA_HOME}/bin" cat > "${MOCK_JAVA_HOME}/bin/java" <<'MOCK' #!/bin/bash if [[ " $* " == *" -version "* ]]; then - echo 'openjdk version "11.0.0"' >&2 + echo "openjdk version \"${MOCK_JAVA_VERSION:-11}.0.0\"" >&2 exit 0 fi printf '%s\n' "$@" > "$CAPTURE_FILE" @@ -129,25 +355,44 @@ ENABLED_CAPTURE="${TEMP_DIR}/enabled.args" CAPTURE_FILE="$ENABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ STDOUT_MODE=true "$SERVER_SCRIPT" \ "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ - "-Doperator.marker=preserved" >/dev/null + "-Doperator.marker=preserved \ + -Dhugegraph.security.validate_dns_cache_ttl=false" >/dev/null assert_argument \ "-Djava.security.properties=${SECURITY_PROPERTIES}" "$ENABLED_CAPTURE" +assert_no_argument '^-Djava\.security\.manager=' "$ENABLED_CAPTURE" assert_argument \ - "-Djava.security.manager=org.apache.hugegraph.security.HugeSecurityManager" \ - "$ENABLED_CAPTURE" + "org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap" "$ENABLED_CAPTURE" +assert_argument "true" "$ENABLED_CAPTURE" assert_argument "-Doperator.marker=preserved" "$ENABLED_CAPTURE" assert_no_argument '^-D(networkaddress\.cache\.ttl|sun\.net\.inetaddr\.ttl)=' \ "$ENABLED_CAPTURE" +JDK21_CAPTURE="${TEMP_DIR}/jdk21.args" +CAPTURE_FILE="$JDK21_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + MOCK_JAVA_VERSION=21 STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.manager=operator.Override" >/dev/null + +LAST_SECURITY_MANAGER_ARGUMENT=$(grep -E '^-Djava\.security\.manager=' \ + "$JDK21_CAPTURE" | tail -n 1) +if [[ "$LAST_SECURITY_MANAGER_ARGUMENT" != \ + "-Djava.security.manager=allow" ]]; then + fail "operator option overrode the JDK 18+ security manager allowance" +fi + DISABLED_CAPTURE="${TEMP_DIR}/disabled.args" CAPTURE_FILE="$DISABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ STDOUT_MODE=true "$SERVER_SCRIPT" \ "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" false \ - "-Doperator.marker=preserved" >/dev/null + "-Doperator.marker=preserved \ + -Dhugegraph.security.validate_dns_cache_ttl=true" >/dev/null assert_no_argument '^-Djava\.security\.properties=' "$DISABLED_CAPTURE" assert_no_argument '^-Djava\.security\.manager=' "$DISABLED_CAPTURE" +assert_argument \ + "org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap" "$DISABLED_CAPTURE" +assert_argument "false" "$DISABLED_CAPTURE" assert_argument "-Doperator.marker=preserved" "$DISABLED_CAPTURE" OVERRIDE_CAPTURE="${TEMP_DIR}/override.args" @@ -163,65 +408,33 @@ if [[ "$LAST_SECURITY_ARGUMENT" != \ fail "operator security properties argument was overwritten" fi -mv "$SECURITY_PROPERTIES" "$SECURITY_PROPERTIES_BACKUP" - -MISSING_ERROR="${TEMP_DIR}/missing-security-properties.err" -if CAPTURE_FILE="${TEMP_DIR}/missing.args" JAVA_HOME="$MOCK_JAVA_HOME" \ - STDOUT_MODE=true "$SERVER_SCRIPT" \ - "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ - "-Doperator.marker=preserved" >/dev/null 2>"$MISSING_ERROR"; then - fail "server startup succeeded without security properties" -fi -grep -Fq "Required Java security properties path is not a readable file" \ - "$MISSING_ERROR" || fail "missing security properties error was not reported" - -EMPTY_OVERRIDE_ERROR="${TEMP_DIR}/empty-override.err" -if CAPTURE_FILE="${TEMP_DIR}/empty-override.args" JAVA_HOME="$MOCK_JAVA_HOME" \ - STDOUT_MODE=true "$SERVER_SCRIPT" \ - "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ - "-Djava.security.properties=" >/dev/null 2>"$EMPTY_OVERRIDE_ERROR"; then - fail "server startup accepted an empty security properties override" -fi -grep -Fq "Required Java security properties path is not a readable file" \ - "$EMPTY_OVERRIDE_ERROR" || fail "empty override error was not reported" - -EMPTY_LAST_OVERRIDE_ERROR="${TEMP_DIR}/empty-last-override.err" -if CAPTURE_FILE="${TEMP_DIR}/empty-last-override.args" \ - JAVA_HOME="$MOCK_JAVA_HOME" STDOUT_MODE=true "$SERVER_SCRIPT" \ - "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ - "-Djava.security.properties=${OPERATOR_PROPERTIES} \ - -Djava.security.properties=" >/dev/null 2>"$EMPTY_LAST_OVERRIDE_ERROR"; then - fail "server startup accepted an empty final security properties override" -fi -grep -Fq "Required Java security properties path is not a readable file" \ - "$EMPTY_LAST_OVERRIDE_ERROR" || \ - fail "empty final override error was not reported" - -MISSING_OVERRIDE_CAPTURE="${TEMP_DIR}/missing-override.args" -CAPTURE_FILE="$MISSING_OVERRIDE_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ +REPLACEMENT_CAPTURE="${TEMP_DIR}/replacement.args" +CAPTURE_FILE="$REPLACEMENT_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ STDOUT_MODE=true "$SERVER_SCRIPT" \ "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ - "-Djava.security.properties= \ - -Djava.security.properties=${OPERATOR_PROPERTIES}" >/dev/null + "-Djava.security.properties==${OPERATOR_PROPERTIES}" >/dev/null LAST_SECURITY_ARGUMENT=$(grep -E '^-Djava\.security\.properties=' \ - "$MISSING_OVERRIDE_CAPTURE" | tail -n 1) + "$REPLACEMENT_CAPTURE" | tail -n 1) if [[ "$LAST_SECURITY_ARGUMENT" != \ - "-Djava.security.properties=${OPERATOR_PROPERTIES}" ]]; then - fail "operator override did not bypass the missing bundled properties" + "-Djava.security.properties==${OPERATOR_PROPERTIES}" ]]; then + fail "operator security properties replacement was overwritten" fi -mkdir "$SECURITY_PROPERTIES" +mv "$SECURITY_PROPERTIES" "$SECURITY_PROPERTIES_BACKUP" -NON_FILE_ERROR="${TEMP_DIR}/non-file-security-properties.err" -if CAPTURE_FILE="${TEMP_DIR}/non-file.args" JAVA_HOME="$MOCK_JAVA_HOME" \ - STDOUT_MODE=true "$SERVER_SCRIPT" \ - "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ - "-Doperator.marker=preserved" >/dev/null 2>"$NON_FILE_ERROR"; then - fail "server startup accepted a non-file security properties path" -fi -grep -Fq "Required Java security properties path is not a readable file" \ - "$NON_FILE_ERROR" || fail "non-file security properties error was not reported" +assert_invalid_security_properties \ + "-Djava.security.properties=${SECURITY_PROPERTIES}" +assert_invalid_security_properties \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" \ + "-Djava.security.properties=" +assert_valid_security_properties \ + "-Djava.security.properties=" \ + "-Djava.security.properties=${OPERATOR_PROPERTIES}" + +mkdir "$SECURITY_PROPERTIES" +assert_invalid_security_properties \ + "-Djava.security.properties=${SECURITY_PROPERTIES}" rmdir "$SECURITY_PROPERTIES" mv "$SECURITY_PROPERTIES_BACKUP" "$SECURITY_PROPERTIES" diff --git a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java new file mode 100644 index 0000000000..74104dbfa1 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.hugegraph.bootstrap; + +import java.security.Security; + +import org.apache.hugegraph.dist.HugeGraphServer; +import org.apache.hugegraph.security.HugeSecurityManager; + +public final class HugeGraphServerBootstrap { + + private HugeGraphServerBootstrap() { + } + + public static void main(String[] args) throws Exception { + if (args.length == 0 || + !("true".equals(args[0]) || "false".equals(args[0]))) { + System.err.println("ERROR: Expected literal validation flag"); + System.exit(1); + return; + } + + if (Boolean.parseBoolean(args[0])) { + try { + validateDnsCacheTtl( + Security.getProperty("networkaddress.cache.ttl")); + } catch (Throwable ignored) { + System.err.println("ERROR: Java security property " + + "networkaddress.cache.ttl must load as a " + + "finite positive integer"); + System.exit(1); + return; + } + + try { + System.setSecurityManager(new HugeSecurityManager()); + if (!(System.getSecurityManager() instanceof + HugeSecurityManager)) { + throw new IllegalStateException( + "Unexpected security manager"); + } + } catch (Throwable ignored) { + System.err.println("ERROR: Failed to install " + + "HugeSecurityManager"); + System.exit(1); + return; + } + } + + if (args.length != 3) { + System.err.println("ERROR: Expected validation flag and two " + + "HugeGraphServer configuration paths"); + System.exit(1); + return; + } + + HugeGraphServer.main(new String[]{args[1], args[2]}); + } + + static void validateDnsCacheTtl(String value) { + int ttl = Integer.parseInt(value); + if (ttl <= 0) { + throw new IllegalArgumentException("DNS cache TTL must be positive"); + } + } +} diff --git a/hugegraph-server/hugegraph-dist/src/test/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrapTest.java b/hugegraph-server/hugegraph-dist/src/test/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrapTest.java new file mode 100644 index 0000000000..24c1badad8 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/test/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrapTest.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.hugegraph.bootstrap; + +import org.junit.Assert; +import org.junit.Test; + +public class HugeGraphServerBootstrapTest { + + @Test + public void testValidateDnsCacheTtlAcceptsFinitePositiveValue() { + HugeGraphServerBootstrap.validateDnsCacheTtl("1"); + HugeGraphServerBootstrap.validateDnsCacheTtl("30"); + HugeGraphServerBootstrap.validateDnsCacheTtl("2147483647"); + } + + @Test + public void testValidateDnsCacheTtlRejectsInvalidValues() { + assertInvalidDnsCacheTtl(null); + assertInvalidDnsCacheTtl(""); + assertInvalidDnsCacheTtl("0"); + assertInvalidDnsCacheTtl("-1"); + assertInvalidDnsCacheTtl("2147483648"); + assertInvalidDnsCacheTtl("invalid"); + } + + private static void assertInvalidDnsCacheTtl(String value) { + try { + HugeGraphServerBootstrap.validateDnsCacheTtl(value); + Assert.fail("Expected invalid DNS cache TTL: " + value); + } catch (RuntimeException ignored) { + // Expected. + } + } +} From 625d3b7532e967e67c0c270b88e931e0f12bebe6 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 2 Aug 2026 00:16:16 +0530 Subject: [PATCH 4/8] fix(server): reject security check on JDK 24+ JDK 24 removed the Security Manager (JEP 486), so '-Djava.security.manager=allow' is a fatal VM initialization error and System.setSecurityManager() always throws. Fail the launcher with an actionable message instead of a cryptic VM error, and keep the security-disabled path unchanged. Also assert a positive downstream signal in the launcher success and security-disabled tests, so an unrelated failure such as a missing bootstrap class can no longer pass, and report the underlying cause when bootstrap validation fails. --- .../assembly/static/bin/hugegraph-server.sh | 17 +++++++ .../travis/test-java-security-properties.sh | 45 +++++++++++++++++++ .../bootstrap/HugeGraphServerBootstrap.java | 8 ++-- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index 1526c54ada..398c974010 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -64,6 +64,10 @@ ensure_path_writable "$PLUGINS" MAX_MEM=$((32 * 1024)) MIN_MEM=$((1 * 512)) MIN_JAVA_VERSION=11 +# JDK 24 removed the Security Manager (JEP 486): "-Djava.security.manager=allow" +# is a fatal VM initialization error there and System.setSecurityManager() always +# throws, so HugeSecurityManager cannot be installed on newer runtimes. +MAX_SECURITY_JAVA_VERSION=23 # Add the slf4j-log4j12 binding CP=$(find -L $LIB -name 'log4j-slf4j-impl*.jar' | sort | tr '\n' ':') @@ -144,6 +148,19 @@ esac JVM_OPTIONS="-Dlog4j.configurationFile=${CONF}/log4j2.xml" SECURITY_MANAGER_OPTION="" if [[ ${OPEN_SECURITY_CHECK} == "true" ]]; then + if [[ ${JAVA_VERSION} -gt ${MAX_SECURITY_JAVA_VERSION} ]]; then + SECURITY_UNSUPPORTED_MSG=$(cat <&2 + echo "${SECURITY_UNSUPPORTED_MSG}" >> "${OUTPUT}" + exit 1 + fi + SECURITY_PROPERTIES="${CONF}/java-security.properties" JVM_OPTIONS="${JVM_OPTIONS} \ -Djava.security.properties=${SECURITY_PROPERTIES}" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index f045049fe2..5d6d4fc89b 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -111,6 +111,19 @@ assert_invalid_security_properties() { fi } +# The bootstrap only hands over to HugeGraphServer once the DNS TTL check and +# the HugeSecurityManager installation have both succeeded, so this downstream +# configuration failure is a positive signal instead of merely a nonzero exit. +assert_reached_server_startup() { + local error_file="$1" + local message="$2" + grep -Fq "Failed to load yaml config file" "$error_file" || fail "$message" + grep -Fq "org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap.main" \ + "$error_file" || fail "$message" + grep -Fq "org.apache.hugegraph.dist.HugeGraphServer.main" \ + "$error_file" || fail "$message" +} + assert_clean_bootstrap_error() { local error_file="$1" if grep -Eq 'Log4j|NetUtils|UnknownHost|hostname' "$error_file"; then @@ -215,6 +228,8 @@ assert_launcher_accepts_security_properties() { "$error_file"; then fail "launcher rejected valid Java security properties" fi + assert_reached_server_startup "$error_file" \ + "valid Java security properties did not reach server startup" } assert_launcher_skips_security_validation() { @@ -230,6 +245,8 @@ assert_launcher_skips_security_validation() { if grep -Fq "networkaddress.cache.ttl must load" "$error_file"; then fail "disabled launcher unexpectedly validated DNS TTL" fi + assert_reached_server_startup "$error_file" \ + "disabled security check did not reach server startup" } ACTUAL_TTL=$("$JAVA_BIN" \ @@ -381,6 +398,34 @@ if [[ "$LAST_SECURITY_MANAGER_ARGUMENT" != \ fail "operator option overrode the JDK 18+ security manager allowance" fi +JDK23_CAPTURE="${TEMP_DIR}/jdk23.args" +CAPTURE_FILE="$JDK23_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + MOCK_JAVA_VERSION=23 STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true >/dev/null + +assert_argument "-Djava.security.manager=allow" "$JDK23_CAPTURE" +assert_argument \ + "-Djava.security.properties=${SECURITY_PROPERTIES}" "$JDK23_CAPTURE" + +JDK24_ERROR="${TEMP_DIR}/jdk24.err" +if JAVA_HOME="$MOCK_JAVA_HOME" MOCK_JAVA_VERSION=24 STDOUT_MODE=true \ + "$SERVER_SCRIPT" "${CONF}/gremlin-server.yaml" \ + "${CONF}/rest-server.properties" true >/dev/null 2>"$JDK24_ERROR"; then + fail "launcher accepted a security-enabled JDK 24 runtime" +fi +grep -Fq "JDK 24+ removed the Security Manager" "$JDK24_ERROR" || + fail "launcher did not explain the JDK 24 security incompatibility" + +JDK24_DISABLED_CAPTURE="${TEMP_DIR}/jdk24-disabled.args" +CAPTURE_FILE="$JDK24_DISABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + MOCK_JAVA_VERSION=24 STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" false \ + >/dev/null + +assert_no_argument '^-Djava\.security\.manager=' "$JDK24_DISABLED_CAPTURE" +assert_no_argument '^-Djava\.security\.properties=' "$JDK24_DISABLED_CAPTURE" +assert_argument "false" "$JDK24_DISABLED_CAPTURE" + DISABLED_CAPTURE="${TEMP_DIR}/disabled.args" CAPTURE_FILE="$DISABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ STDOUT_MODE=true "$SERVER_SCRIPT" \ diff --git a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java index 74104dbfa1..d20a4d3065 100644 --- a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java +++ b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java @@ -39,10 +39,10 @@ public static void main(String[] args) throws Exception { try { validateDnsCacheTtl( Security.getProperty("networkaddress.cache.ttl")); - } catch (Throwable ignored) { + } catch (Throwable e) { System.err.println("ERROR: Java security property " + "networkaddress.cache.ttl must load as a " + - "finite positive integer"); + "finite positive integer: " + e); System.exit(1); return; } @@ -54,9 +54,9 @@ public static void main(String[] args) throws Exception { throw new IllegalStateException( "Unexpected security manager"); } - } catch (Throwable ignored) { + } catch (Throwable e) { System.err.println("ERROR: Failed to install " + - "HugeSecurityManager"); + "HugeSecurityManager: " + e); System.exit(1); return; } From 9438a943b7f83385399d5f14e05258222f0e0dfa Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 2 Aug 2026 10:39:43 +0530 Subject: [PATCH 5/8] fix(server): parse the JDK version line robustly 'java -version' prints a preamble line such as 'Picked up JAVA_TOOL_OPTIONS: ...' whenever JAVA_TOOL_OPTIONS or _JAVA_OPTIONS is set, which is how APM agents are usually installed. Reading 'head -1' then left JAVA_VERSION unusable, so every version-gated branch silently evaluated false: the JDK 24+ guard never fired and '-Djava.security.manager=allow' was dropped on JDK 18-23, which broke the programmatic HugeSecurityManager install there. Select the version line explicitly, strip any pre-release suffix and reject a non-numeric result. Also name the bundled policy file in the server log when it is missing or unreadable, so an upgrade that reuses an older conf/ reports the cause in the log start-hugegraph.sh points at rather than only on the bootstrap's stderr, and include the effective java.security.properties in the bootstrap error. --- .../assembly/static/bin/hugegraph-server.sh | 22 ++++++++++- .../travis/test-java-security-properties.sh | 39 +++++++++++++++++++ .../bootstrap/HugeGraphServerBootstrap.java | 6 ++- 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index 398c974010..155a87c624 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -97,8 +97,15 @@ else JAVA="$JAVA_HOME/bin/java -server" fi -JAVA_VERSION=$($JAVA -version 2>&1 | head -1 | cut -d'"' -f2 | sed 's/^1\.//' | cut -d'.' -f1) -if [[ $? -ne 0 || $JAVA_VERSION -lt $MIN_JAVA_VERSION ]]; then +# Pick the version line explicitly: the JVM prints a preamble such as +# "Picked up JAVA_TOOL_OPTIONS: ..." before it whenever JAVA_TOOL_OPTIONS or +# _JAVA_OPTIONS is set, and reading that line instead would leave JAVA_VERSION +# unusable and silently skip every version-gated option below. +JAVA_VERSION=$($JAVA -version 2>&1 | awk -F'"' '/version "/ {print $2; exit}' | + sed 's/^1\.//' | cut -d'.' -f1) +# Drop any pre-release suffix, e.g. "24-ea" -> "24" +JAVA_VERSION="${JAVA_VERSION%%[!0-9]*}" +if [[ -z $JAVA_VERSION || $JAVA_VERSION -lt $MIN_JAVA_VERSION ]]; then echo "Make sure the JDK is installed and the version >= $MIN_JAVA_VERSION, current is $JAVA_VERSION" \ >> "${OUTPUT}" exit 1 @@ -162,6 +169,17 @@ EOF fi SECURITY_PROPERTIES="${CONF}/java-security.properties" + if [[ ! -r ${SECURITY_PROPERTIES} ]]; then + # The bootstrap validates the effective policy and refuses to start, but + # its stderr goes to the stdout log in daemon mode. Name the cause here + # so it also reaches the log start-hugegraph.sh points operators at. + cat >> "${OUTPUT}" < setting a finite positive +networkaddress.cache.ttl. +EOF + fi JVM_OPTIONS="${JVM_OPTIONS} \ -Djava.security.properties=${SECURITY_PROPERTIES}" if [[ ${JAVA_VERSION} -ge 18 ]]; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index 5d6d4fc89b..7ce837dc28 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -361,6 +361,11 @@ mkdir -p "${MOCK_JAVA_HOME}/bin" cat > "${MOCK_JAVA_HOME}/bin/java" <<'MOCK' #!/bin/bash if [[ " $* " == *" -version "* ]]; then + # Real JVMs print this preamble ahead of the version line whenever + # JAVA_TOOL_OPTIONS or _JAVA_OPTIONS is set. + if [[ -n "${MOCK_JAVA_PREAMBLE:-}" ]]; then + echo "${MOCK_JAVA_PREAMBLE}" >&2 + fi echo "openjdk version \"${MOCK_JAVA_VERSION:-11}.0.0\"" >&2 exit 0 fi @@ -416,6 +421,29 @@ fi grep -Fq "JDK 24+ removed the Security Manager" "$JDK24_ERROR" || fail "launcher did not explain the JDK 24 security incompatibility" +# A version-line preamble must not hide the runtime version from the +# version-gated security options above. +VERSION_PREAMBLE="Picked up JAVA_TOOL_OPTIONS: -XX:+UseSerialGC" + +PREAMBLE_JDK21_CAPTURE="${TEMP_DIR}/preamble-jdk21.args" +CAPTURE_FILE="$PREAMBLE_JDK21_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + MOCK_JAVA_VERSION=21 MOCK_JAVA_PREAMBLE="$VERSION_PREAMBLE" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true >/dev/null + +assert_argument "-Djava.security.manager=allow" "$PREAMBLE_JDK21_CAPTURE" + +PREAMBLE_JDK24_ERROR="${TEMP_DIR}/preamble-jdk24.err" +if JAVA_HOME="$MOCK_JAVA_HOME" MOCK_JAVA_VERSION=24 \ + MOCK_JAVA_PREAMBLE="$VERSION_PREAMBLE" STDOUT_MODE=true \ + "$SERVER_SCRIPT" "${CONF}/gremlin-server.yaml" \ + "${CONF}/rest-server.properties" true \ + >/dev/null 2>"$PREAMBLE_JDK24_ERROR"; then + fail "version preamble hid a security-enabled JDK 24 runtime" +fi +grep -Fq "JDK 24+ removed the Security Manager" "$PREAMBLE_JDK24_ERROR" || + fail "version preamble defeated the JDK 24 guard" + JDK24_DISABLED_CAPTURE="${TEMP_DIR}/jdk24-disabled.args" CAPTURE_FILE="$JDK24_DISABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ MOCK_JAVA_VERSION=24 STDOUT_MODE=true "$SERVER_SCRIPT" \ @@ -468,6 +496,17 @@ fi mv "$SECURITY_PROPERTIES" "$SECURITY_PROPERTIES_BACKUP" +# An upgrade that reuses an older conf/ must say which file is missing, in the +# log that start-hugegraph.sh points operators at rather than only on stderr. +SERVER_LOG="${SERVER_ROOT}/logs/hugegraph-server.log" +: > "$SERVER_LOG" +CAPTURE_FILE="${TEMP_DIR}/missing-bundled.args" JAVA_HOME="$MOCK_JAVA_HOME" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + >/dev/null 2>&1 +grep -Fq "Missing or unreadable '${SECURITY_PROPERTIES}'" "$SERVER_LOG" || + fail "launcher did not name the missing bundled security properties file" + assert_invalid_security_properties \ "-Djava.security.properties=${SECURITY_PROPERTIES}" assert_invalid_security_properties \ diff --git a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java index d20a4d3065..2bb0c96272 100644 --- a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java +++ b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java @@ -42,7 +42,11 @@ public static void main(String[] args) throws Exception { } catch (Throwable e) { System.err.println("ERROR: Java security property " + "networkaddress.cache.ttl must load as a " + - "finite positive integer: " + e); + "finite positive integer " + + "(java.security.properties=" + + System.getProperty( + "java.security.properties") + + "): " + e); System.exit(1); return; } From 4d0ff03e44f22bd9d08de82269b6fbc106c02292 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 2 Aug 2026 11:02:35 +0530 Subject: [PATCH 6/8] fix(server): only report a missing bundled policy when it matters An operator may deliberately replace the packaged policy with their own -Djava.security.properties=, which the JVM applies last, so a missing bundled file is harmless there. Reporting it unconditionally logged a misleading error on a startup that then succeeded. Track the last such option instead, remembering that an empty value clears any earlier override. Also select the version line in the test harness the same way the launcher now does, so a 'Picked up JAVA_TOOL_OPTIONS: ...' preamble cannot silently drop -Djava.security.manager=allow on a JDK 18-23 runner. --- .../assembly/static/bin/hugegraph-server.sh | 16 +++++++++ .../travis/test-java-security-properties.sh | 34 ++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index 155a87c624..ffc7ae2e76 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -170,6 +170,22 @@ EOF SECURITY_PROPERTIES="${CONF}/java-security.properties" if [[ ! -r ${SECURITY_PROPERTIES} ]]; then + # An operator may deliberately replace the bundled policy with their own + # -Djava.security.properties=, which the JVM applies last and which + # makes a missing bundled file harmless. Track the last such option, since + # an empty value clears any earlier override. + SECURITY_PROPERTIES_OVERRIDDEN="false" + for OPTION in ${JAVA_OPTIONS} ${_JAVA_OPTIONS:-}; do + case "${OPTION}" in + -Djava.security.properties=) + SECURITY_PROPERTIES_OVERRIDDEN="false" ;; + -Djava.security.properties=?*) + SECURITY_PROPERTIES_OVERRIDDEN="true" ;; + esac + done + fi + if [[ ! -r ${SECURITY_PROPERTIES} && + ${SECURITY_PROPERTIES_OVERRIDDEN:-false} == "false" ]]; then # The bootstrap validates the effective policy and refuses to start, but # its stderr goes to the stdout log in daemon mode. Name the cause here # so it also reaches the log start-hugegraph.sh points operators at. diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index 7ce837dc28..2b1eaa6086 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -55,8 +55,16 @@ if [[ -n "${JAVA_HOME:-}" ]]; then else JAVA_BIN="java" fi -JAVA_MAJOR=$($JAVA_BIN -version 2>&1 | head -1 | cut -d'"' -f2 | +# Select the version line the same way the launcher does: a preamble such as +# "Picked up JAVA_TOOL_OPTIONS: ..." precedes it whenever JAVA_TOOL_OPTIONS or +# _JAVA_OPTIONS is set, and parsing that would silently drop the option below. +JAVA_MAJOR=$($JAVA_BIN -version 2>&1 | + awk -F'"' '/version "/ {print $2; exit}' | sed 's/^1\.//' | cut -d'.' -f1) +JAVA_MAJOR="${JAVA_MAJOR%%[!0-9]*}" +if [[ -z "$JAVA_MAJOR" ]]; then + fail "could not determine the Java major version of $JAVA_BIN" +fi SECURITY_MANAGER_OPTION="" if [[ "$JAVA_MAJOR" -ge 18 ]]; then SECURITY_MANAGER_OPTION="-Djava.security.manager=allow" @@ -507,6 +515,30 @@ CAPTURE_FILE="${TEMP_DIR}/missing-bundled.args" JAVA_HOME="$MOCK_JAVA_HOME" \ grep -Fq "Missing or unreadable '${SECURITY_PROPERTIES}'" "$SERVER_LOG" || fail "launcher did not name the missing bundled security properties file" +# ... but an operator override legitimately replaces the bundled policy, so the +# same missing file must not be reported as an error in that case. +for OVERRIDE_OPTION in "-Djava.security.properties=${OPERATOR_PROPERTIES}" \ + "-Djava.security.properties==${OPERATOR_PROPERTIES}"; do + : > "$SERVER_LOG" + CAPTURE_FILE="${TEMP_DIR}/missing-bundled-override.args" \ + JAVA_HOME="$MOCK_JAVA_HOME" STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "${OVERRIDE_OPTION}" >/dev/null 2>&1 + if grep -Fq "Missing or unreadable" "$SERVER_LOG"; then + fail "launcher reported a missing bundled file despite ${OVERRIDE_OPTION}" + fi +done + +# An override that clears itself is not an override, so the error must return. +: > "$SERVER_LOG" +CAPTURE_FILE="${TEMP_DIR}/missing-bundled-cleared.args" \ + JAVA_HOME="$MOCK_JAVA_HOME" STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties=${OPERATOR_PROPERTIES} -Djava.security.properties=" \ + >/dev/null 2>&1 +grep -Fq "Missing or unreadable '${SECURITY_PROPERTIES}'" "$SERVER_LOG" || + fail "cleared security properties override suppressed the missing-file error" + assert_invalid_security_properties \ "-Djava.security.properties=${SECURITY_PROPERTIES}" assert_invalid_security_properties \ From 9d53f15f61c6dc3591a89d7e91151ee4f5ae10f1 Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 2 Aug 2026 19:40:54 +0530 Subject: [PATCH 7/8] fix(server): anchor JDK version parsing to the JVM banner line An agent installed through JAVA_TOOL_OPTIONS may print its own banner containing 'version "..."' ahead of the JVM's, so matching the first line with 'version "' can read the agent version instead of the runtime version: a low agent version rejects a supported JDK, and a high one trips the JDK 24+ security guard. Match only lines starting with 'java version' or 'openjdk version', in the launcher and in the test helper, and add agent-banner regressions in both directions. --- .../assembly/static/bin/hugegraph-server.sh | 13 +++--- .../travis/test-java-security-properties.sh | 41 +++++++++++++++++-- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index ffc7ae2e76..d5ea828a82 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -97,11 +97,14 @@ else JAVA="$JAVA_HOME/bin/java -server" fi -# Pick the version line explicitly: the JVM prints a preamble such as -# "Picked up JAVA_TOOL_OPTIONS: ..." before it whenever JAVA_TOOL_OPTIONS or -# _JAVA_OPTIONS is set, and reading that line instead would leave JAVA_VERSION -# unusable and silently skip every version-gated option below. -JAVA_VERSION=$($JAVA -version 2>&1 | awk -F'"' '/version "/ {print $2; exit}' | +# Pick the JVM banner line explicitly, anchored to its "java version"/"openjdk +# version" prefix: whenever JAVA_TOOL_OPTIONS or _JAVA_OPTIONS is set the JVM +# prints a preamble first ("Picked up JAVA_TOOL_OPTIONS: ..."), and an agent +# loaded that way may print its own banner containing 'version "..."' (an APM +# agent, for example), so matching any line with 'version "' can read the +# agent version instead of the runtime version. +JAVA_VERSION=$($JAVA -version 2>&1 | + awk -F'"' '/^(java|openjdk) version "/ {print $2; exit}' | sed 's/^1\.//' | cut -d'.' -f1) # Drop any pre-release suffix, e.g. "24-ea" -> "24" JAVA_VERSION="${JAVA_VERSION%%[!0-9]*}" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index 2b1eaa6086..c1cd76d599 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -55,11 +55,13 @@ if [[ -n "${JAVA_HOME:-}" ]]; then else JAVA_BIN="java" fi -# Select the version line the same way the launcher does: a preamble such as -# "Picked up JAVA_TOOL_OPTIONS: ..." precedes it whenever JAVA_TOOL_OPTIONS or -# _JAVA_OPTIONS is set, and parsing that would silently drop the option below. +# Select the JVM banner line the same way the launcher does, anchored to its +# "java version"/"openjdk version" prefix: a preamble such as "Picked up +# JAVA_TOOL_OPTIONS: ..." precedes it whenever JAVA_TOOL_OPTIONS or +# _JAVA_OPTIONS is set, and an agent loaded that way may print its own +# 'version "..."' banner that an unanchored match would read instead. JAVA_MAJOR=$($JAVA_BIN -version 2>&1 | - awk -F'"' '/version "/ {print $2; exit}' | + awk -F'"' '/^(java|openjdk) version "/ {print $2; exit}' | sed 's/^1\.//' | cut -d'.' -f1) JAVA_MAJOR="${JAVA_MAJOR%%[!0-9]*}" if [[ -z "$JAVA_MAJOR" ]]; then @@ -452,6 +454,37 @@ fi grep -Fq "JDK 24+ removed the Security Manager" "$PREAMBLE_JDK24_ERROR" || fail "version preamble defeated the JDK 24 guard" +# An agent loaded through JAVA_TOOL_OPTIONS may print its own banner containing +# 'version "..."' ahead of the JVM's. Reading the agent's version instead of +# the runtime's would reject a supported JDK when the agent version is low ... +AGENT_PREAMBLE=$'Picked up JAVA_TOOL_OPTIONS: -javaagent:apm-agent.jar\nElastic APM agent version "7.2.0" is starting' + +AGENT_JDK21_CAPTURE="${TEMP_DIR}/agent-preamble-jdk21.args" +CAPTURE_FILE="$AGENT_JDK21_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + MOCK_JAVA_VERSION=21 MOCK_JAVA_PREAMBLE="$AGENT_PREAMBLE" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true >/dev/null + +assert_argument "-Djava.security.manager=allow" "$AGENT_JDK21_CAPTURE" + +# ... and trip the JDK 24+ security guard when the agent version is high. +HIGH_AGENT_PREAMBLE=$'Picked up JAVA_TOOL_OPTIONS: -javaagent:apm-agent.jar\nAPM agent version "24.0.1" is starting' + +HIGH_AGENT_CAPTURE="${TEMP_DIR}/agent-preamble-jdk11.args" +HIGH_AGENT_ERROR="${TEMP_DIR}/agent-preamble-jdk11.err" +CAPTURE_FILE="$HIGH_AGENT_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + MOCK_JAVA_VERSION=11 MOCK_JAVA_PREAMBLE="$HIGH_AGENT_PREAMBLE" \ + STDOUT_MODE=true "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + >/dev/null 2>"$HIGH_AGENT_ERROR" + +if grep -Fq "JDK 24+ removed the Security Manager" "$HIGH_AGENT_ERROR"; then + fail "agent banner version tripped the JDK 24+ guard on a supported JDK" +fi +assert_argument \ + "org.apache.hugegraph.bootstrap.HugeGraphServerBootstrap" "$HIGH_AGENT_CAPTURE" +assert_no_argument '^-Djava\.security\.manager=' "$HIGH_AGENT_CAPTURE" + JDK24_DISABLED_CAPTURE="${TEMP_DIR}/jdk24-disabled.args" CAPTURE_FILE="$JDK24_DISABLED_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ MOCK_JAVA_VERSION=24 STDOUT_MODE=true "$SERVER_SCRIPT" \ From 791947cd63bb8b6dc7ed50d73d54a68c6a6059df Mon Sep 17 00:00:00 2001 From: Himanshu Verma Date: Sun, 2 Aug 2026 19:41:04 +0530 Subject: [PATCH 8/8] fix(server): mirror bootstrap policy rejections into the server log Any non-empty -Djava.security.properties=... token suppressed the missing-bundled-policy diagnostic without checking what the override loads to, and the bootstrap's rejection of a broken override only reached hugegraph-server-stdout.log while start-hugegraph.sh points operators at hugegraph-server.log. Shell-side validation cannot close this gap: only the JVM's own properties parsing decides the effective policy (escaped keys and line continuations included), so a shell check could pass a file the JVM rejects. Keep the bootstrap as the single validator and have it append its fatal errors to the file named by hugegraph.bootstrap.error.log, which the launcher passes in daemon mode only; stderr already reaches the operator in stdout mode. Cover missing, unreadable and infinite-TTL operator overrides in daemon mode, asserting the cause and the override path land in hugegraph-server.log. --- .../assembly/static/bin/hugegraph-server.sh | 13 +++++- .../travis/test-java-security-properties.sh | 34 ++++++++++++++- .../bootstrap/HugeGraphServerBootstrap.java | 41 +++++++++++++++---- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh index d5ea828a82..caffedc482 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/hugegraph-server.sh @@ -176,7 +176,10 @@ EOF # An operator may deliberately replace the bundled policy with their own # -Djava.security.properties=, which the JVM applies last and which # makes a missing bundled file harmless. Track the last such option, since - # an empty value clears any earlier override. + # an empty value clears any earlier override. The override itself is not + # validated here: only the JVM's own properties parsing decides what it + # loads to, so the bootstrap stays the single validator and mirrors its + # rejection into the server log (see hugegraph.bootstrap.error.log below). SECURITY_PROPERTIES_OVERRIDDEN="false" for OPTION in ${JAVA_OPTIONS} ${_JAVA_OPTIONS:-}; do case "${OPTION}" in @@ -245,6 +248,14 @@ if [ "${OPEN_TELEMETRY}" == "true" ]; then export OTEL_RESOURCE_ATTRIBUTES=service.name=server fi +if [[ "${STDOUT_MODE:-false}" != "true" ]]; then + # Daemon stderr only reaches hugegraph-server-stdout.log, so a bootstrap + # rejection of the effective DNS policy (a broken operator override + # included) would be invisible in the log start-hugegraph.sh points + # operators at. Let the bootstrap mirror its fatal errors there. + JVM_OPTIONS="${JVM_OPTIONS} -Dhugegraph.bootstrap.error.log=${OUTPUT}" +fi + # Turn on security check if [[ "${STDOUT_MODE:-false}" == "true" ]]; then exec ${JAVA} -Dname="HugeGraphServer" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index c1cd76d599..796d69c83b 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -358,6 +358,39 @@ assert_launcher_rejects_security_properties "$INVALID_UNICODE" assert_launcher_accepts_security_properties "$OPERATOR_PROPERTIES" assert_launcher_skips_security_validation +# In daemon mode stderr only reaches the stdout log, so when the bootstrap +# rejects a broken operator override, the cause and the override path must be +# mirrored into the server log that start-hugegraph.sh points operators at. +SERVER_LOG="${SERVER_ROOT}/logs/hugegraph-server.log" + +assert_daemon_launcher_rejects_override() { + local properties_path="$1" + local label="$2" + : > "$SERVER_LOG" + if JAVA_OPTIONS="" "$SERVER_SCRIPT" \ + "${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" true \ + "-Djava.security.properties=${properties_path}" >/dev/null 2>&1; then + fail "daemon launcher accepted a ${label} security properties override" + fi + grep -Fq "networkaddress.cache.ttl must load as a finite positive integer" \ + "$SERVER_LOG" || + fail "${label} override rejection did not reach hugegraph-server.log" + grep -Fq -- "${properties_path}" "$SERVER_LOG" || + fail "${label} override path was not named in hugegraph-server.log" +} + +# Invalid content behind the removed read permission keeps this case failing +# even where permission bits do not apply, e.g. when running as root. +UNREADABLE_OVERRIDE="${TEMP_DIR}/unreadable-security.properties" +echo "networkaddress.cache.ttl=-1" > "$UNREADABLE_OVERRIDE" +chmod 000 "$UNREADABLE_OVERRIDE" + +assert_daemon_launcher_rejects_override "$MISSING_OVERRIDE" "missing" +assert_daemon_launcher_rejects_override "$UNREADABLE_OVERRIDE" "unreadable" +assert_daemon_launcher_rejects_override "$INFINITE_PROPERTIES" "infinite-TTL" + +chmod 600 "$UNREADABLE_OVERRIDE" + MISSING_DEFAULT="${TEMP_DIR}/missing-default-security.properties" MISSING_DEFAULT_TTL=$("$JAVA_BIN" \ -Djava.security.properties="$MISSING_DEFAULT" \ @@ -539,7 +572,6 @@ mv "$SECURITY_PROPERTIES" "$SECURITY_PROPERTIES_BACKUP" # An upgrade that reuses an older conf/ must say which file is missing, in the # log that start-hugegraph.sh points operators at rather than only on stderr. -SERVER_LOG="${SERVER_ROOT}/logs/hugegraph-server.log" : > "$SERVER_LOG" CAPTURE_FILE="${TEMP_DIR}/missing-bundled.args" JAVA_HOME="$MOCK_JAVA_HOME" \ STDOUT_MODE=true "$SERVER_SCRIPT" \ diff --git a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java index 2bb0c96272..95c5029954 100644 --- a/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java +++ b/hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/bootstrap/HugeGraphServerBootstrap.java @@ -17,6 +17,8 @@ package org.apache.hugegraph.bootstrap; +import java.io.FileWriter; +import java.io.IOException; import java.security.Security; import org.apache.hugegraph.dist.HugeGraphServer; @@ -40,13 +42,12 @@ public static void main(String[] args) throws Exception { validateDnsCacheTtl( Security.getProperty("networkaddress.cache.ttl")); } catch (Throwable e) { - System.err.println("ERROR: Java security property " + - "networkaddress.cache.ttl must load as a " + - "finite positive integer " + - "(java.security.properties=" + - System.getProperty( - "java.security.properties") + - "): " + e); + reportFatal("ERROR: Java security property " + + "networkaddress.cache.ttl must load as a " + + "finite positive integer " + + "(java.security.properties=" + + System.getProperty("java.security.properties") + + "): " + e); System.exit(1); return; } @@ -59,8 +60,8 @@ public static void main(String[] args) throws Exception { "Unexpected security manager"); } } catch (Throwable e) { - System.err.println("ERROR: Failed to install " + - "HugeSecurityManager: " + e); + reportFatal("ERROR: Failed to install " + + "HugeSecurityManager: " + e); System.exit(1); return; } @@ -82,4 +83,26 @@ static void validateDnsCacheTtl(String value) { throw new IllegalArgumentException("DNS cache TTL must be positive"); } } + + /** + * In daemon mode the launcher redirects stderr to the stdout log, so a + * fatal error printed only there never reaches the server log that + * start-hugegraph.sh points operators at. The launcher passes that log's + * path so the cause can be appended to both. This runs before the + * security manager is installed and must not touch the logging framework: + * the DNS policy being validated here is the very policy logging-time + * hostname resolution would cache. + */ + private static void reportFatal(String message) { + System.err.println(message); + String errorLog = System.getProperty("hugegraph.bootstrap.error.log"); + if (errorLog == null || errorLog.isEmpty()) { + return; + } + try (FileWriter writer = new FileWriter(errorLog, true)) { + writer.write(message + System.lineSeparator()); + } catch (IOException ignored) { + // Best effort only: stderr already carries the message + } + } }