fix(server): configure finite DNS cache TTL (#3126)
1. A Store pod is replaced in Kubernetes and comes back on a new IP behind the same stable DNS name. 2. HugeGraph Server runs with `HugeSecurityManager` installed by default. 3. With a `SecurityManager` present and no explicit policy, Java 11 falls back to `networkaddress.cache.ttl = -1`, so `InetAddressCachePolicy` is `FOREVER`. 4. The Server therefore keeps resolving the Store name to the old pod IP, and HStore writes keep going to an address that no longer exists. 5. Nothing recovers this until the Server process is restarted. The setting is only honoured as a **security** property. The ordinary `-Dnetworkaddress.cache.ttl` system property has no effect here, which is why that form is deliberately not used. --------- Co-authored-by: imbajin <jin@apache.org>
This commit is contained in:
parent
1716c77486
commit
de62d97f34
|
|
@ -87,6 +87,14 @@ jobs:
|
|||
hugegraph-server/hugegraph-dist/src/assembly/static \
|
||||
hugegraph-store/hg-store-dist/src/assembly/static/bin/util.sh
|
||||
|
||||
- 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' }}
|
||||
|
|
@ -111,7 +119,6 @@ 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-start-hugegraph.sh $SERVER_DIR
|
||||
|
|
|
|||
|
|
@ -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' ':')
|
||||
|
|
@ -93,8 +97,18 @@ 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 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]*}"
|
||||
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
|
||||
|
|
@ -142,8 +156,58 @@ case "$GC_OPTION" in
|
|||
esac
|
||||
|
||||
JVM_OPTIONS="-Dlog4j.configurationFile=${CONF}/log4j2.xml"
|
||||
SECURITY_MANAGER_OPTION=""
|
||||
if [[ ${OPEN_SECURITY_CHECK} == "true" ]]; then
|
||||
JVM_OPTIONS="${JVM_OPTIONS} -Djava.security.manager=org.apache.hugegraph.security.HugeSecurityManager"
|
||||
if [[ ${JAVA_VERSION} -gt ${MAX_SECURITY_JAVA_VERSION} ]]; then
|
||||
SECURITY_UNSUPPORTED_MSG=$(cat <<EOF
|
||||
The security check requires Java ${MIN_JAVA_VERSION}-${MAX_SECURITY_JAVA_VERSION}, current is ${JAVA_VERSION}.
|
||||
JDK 24+ removed the Security Manager (JEP 486), so HugeSecurityManager can no longer be installed.
|
||||
Run the server on Java ${MAX_SECURITY_JAVA_VERSION} or lower, or start it with the security check
|
||||
disabled: 'start-hugegraph.sh -s false'.
|
||||
EOF
|
||||
)
|
||||
echo "${SECURITY_UNSUPPORTED_MSG}" >&2
|
||||
echo "${SECURITY_UNSUPPORTED_MSG}" >> "${OUTPUT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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=<file>, 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. 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
|
||||
-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.
|
||||
cat >> "${OUTPUT}" <<EOF
|
||||
ERROR: Missing or unreadable '${SECURITY_PROPERTIES}'.
|
||||
An upgraded deployment that reuses an older conf/ directory must add this file,
|
||||
or supply its own -Djava.security.properties=<file> setting a finite positive
|
||||
networkaddress.cache.ttl.
|
||||
EOF
|
||||
fi
|
||||
JVM_OPTIONS="${JVM_OPTIONS} \
|
||||
-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
|
||||
|
|
@ -184,12 +248,24 @@ 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} -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,623 @@
|
|||
#!/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
|
||||
# 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'"' '/^(java|openjdk) 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"
|
||||
fi
|
||||
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
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'
|
||||
import java.security.Security;
|
||||
|
||||
public class ReadDnsCacheTtl {
|
||||
public static void main(String[] args) {
|
||||
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
|
||||
}
|
||||
|
||||
# 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
|
||||
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_reached_server_startup "$error_file" \
|
||||
"valid Java security properties did not reach server startup"
|
||||
}
|
||||
|
||||
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
|
||||
assert_reached_server_startup "$error_file" \
|
||||
"disabled security check did not reach server startup"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 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" \
|
||||
-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'
|
||||
#!/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
|
||||
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 \
|
||||
-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 \
|
||||
"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
|
||||
|
||||
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"
|
||||
|
||||
# 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"
|
||||
|
||||
# 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" \
|
||||
"${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" \
|
||||
"${CONF}/gremlin-server.yaml" "${CONF}/rest-server.properties" false \
|
||||
"-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"
|
||||
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
|
||||
|
||||
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==${OPERATOR_PROPERTIES}" >/dev/null
|
||||
|
||||
LAST_SECURITY_ARGUMENT=$(grep -E '^-Djava\.security\.properties=' \
|
||||
"$REPLACEMENT_CAPTURE" | tail -n 1)
|
||||
if [[ "$LAST_SECURITY_ARGUMENT" != \
|
||||
"-Djava.security.properties==${OPERATOR_PROPERTIES}" ]]; then
|
||||
fail "operator security properties replacement was overwritten"
|
||||
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"
|
||||
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"
|
||||
|
||||
# ... 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 \
|
||||
"-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"
|
||||
|
||||
echo "PASS: Java security properties and startup wiring"
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* 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.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
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 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;
|
||||
}
|
||||
|
||||
try {
|
||||
System.setSecurityManager(new HugeSecurityManager());
|
||||
if (!(System.getSecurityManager() instanceof
|
||||
HugeSecurityManager)) {
|
||||
throw new IllegalStateException(
|
||||
"Unexpected security manager");
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
reportFatal("ERROR: Failed to install " +
|
||||
"HugeSecurityManager: " + e);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue