Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions hugegraph-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ RUN apt-get -q update \
COPY hugegraph-server/hugegraph-dist/docker/scripts/remote-connect.groovy ./scripts
COPY hugegraph-server/hugegraph-dist/docker/scripts/detect-storage.groovy ./scripts
COPY hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh .
COPY hugegraph-server/hugegraph-dist/docker/props.awk .
RUN chmod 755 ./docker-entrypoint.sh

EXPOSE 8080
Expand Down
1 change: 1 addition & 0 deletions hugegraph-server/Dockerfile-hstore
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ RUN apt-get -q update \
COPY hugegraph-server/hugegraph-dist/docker/scripts/remote-connect.groovy ./scripts
#COPY hugegraph-server/hugegraph-dist/docker/scripts/detect-storage.groovy ./scripts
COPY hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh .
COPY hugegraph-server/hugegraph-dist/docker/props.awk .
RUN chmod 755 ./docker-entrypoint.sh

EXPOSE 8080
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ trap 'rm -rf "${TEST_HOME}"' EXIT

mkdir -p "${TEST_HOME}/bin" "${TEST_HOME}/conf/graphs" "${TEST_HOME}/docker"
cp "${SCRIPT_DIR}/docker-entrypoint.sh" "${TEST_HOME}/docker-entrypoint.sh"
cp "${SCRIPT_DIR}/props.awk" "${TEST_HOME}/props.awk"
touch "${TEST_HOME}/docker/init_complete"

cat > "${TEST_HOME}/conf/rest-server.properties" <<'EOF'
Expand Down
144 changes: 128 additions & 16 deletions hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ mkdir -p "${DOCKER_FOLDER}"

log() { echo "[hugegraph-server-entrypoint] $*"; }

# Property reading/writing goes through props.awk, which implements the
# java.util.Properties grammar HugeConfig applies (escapes, `:`/whitespace
# separators, continuations, first-definition-wins duplicates). grep/sed
# rewrites disagree with it on mounted or upgraded configs, silently
# producing two definitions of one key. Values move through environment
# variables rather than argv so a PASSWORD never shows up in `ps` output.
PROPS_AWK="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/props.awk"
if [[ ! -f "${PROPS_AWK}" ]]; then
log "ERROR: props.awk not found next to the entrypoint"
exit 1
fi

encode_prop_value() {
local value="$1" encoded="" char
local i
Expand All @@ -48,18 +60,10 @@ encode_prop_value() {

set_prop_encoded() {
local key="$1" encoded_val="$2" file="$3"
local esc_key esc_val key_re

esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
esc_val=$(printf '%s' "$encoded_val" | sed -e 's/[&|\\~]/\\&/g')
key_re="^[[:space:]]*${esc_key}([[:space:]]*[:=]|[[:space:]]+|[[:space:]]*$)"

if grep -qE "${key_re}" "${file}"; then
sed -ri "0,/${key_re}/!{/${key_re}/d;}" "${file}"
sed -ri "0,/${key_re}/s~${key_re}.*~${key}=${esc_val}~" "${file}"
else
printf '%s=%s\n' "$key" "$encoded_val" >> "${file}"
fi
PROPS_MODE=set PROPS_KEY="${key}" \
PROPS_VALUE_ENCODED="${encoded_val}" PROPS_FILE="${file}" \
awk -f "${PROPS_AWK}" /dev/null
}

set_prop() {
Expand All @@ -70,12 +74,119 @@ set_prop() {

get_prop_encoded() {
local key="$1" file="$2"
local esc_key

esc_key=$(printf '%s' "$key" | sed -e 's/[][(){}.^$*+?|\\/]/\\&/g')
sed -nE \
"s~^[[:space:]]*${esc_key}([[:space:]]*[:=][[:space:]]*|[[:space:]]+)(.*)$~\\2~p" \
"${file}" | head -n 1
PROPS_MODE=get PROPS_KEY="${key}" PROPS_FILE="${file}" \
awk -f "${PROPS_AWK}" /dev/null
}

# Decoded read: unescapes the on-disk value the way java.util.Properties
# does, so it compares equal with the snakeyaml-decoded scalar from
# get_yaml_authenticator. The raw get_prop_encoded mode stays for the
# secret round trip, which must replay backslashes byte-for-byte.
get_prop() {
local key="$1" file="$2"

PROPS_MODE=get-decoded PROPS_KEY="${key}" PROPS_FILE="${file}" \
awk -f "${PROPS_AWK}" /dev/null
}

# First uncommented `authenticator:` inside the gremlin-server.yaml
# authentication block, or on the `authentication:` line itself (a flow
# mapping). snakeyaml resolves duplicate top-level keys to the last one,
# but a mounted file carrying two authentication blocks is pathological;
# report the first and let the mismatch WARN handle it. The scalar is
# cleaned the way snakeyaml reads it — an inline comment (a '#' preceded
# by whitespace), surrounding quotes and padding are stripped — because
# java.util.Properties keeps all of those in the class name.
get_yaml_authenticator() {
local yaml="./conf/gremlin-server.yaml"

[[ -f "${yaml}" ]] || return 0
awk '
function scalar(s, out, i, n, c, q) {
out = ""
q = ""
n = length(s)
for (i = 1; i <= n; i++) {
c = substr(s, i, 1)
if (q != "") {
if (c == q) q = ""
else out = out c
continue
}
if (c == "\"" || c == "\047") { q = c; continue }
if (c == "#" &&
(out == "" || substr(out, length(out), 1) ~ /[ \t]/))
break
if (c == "," || c == "}" || c == "]") break
out = out c
}
sub(/^[ \t\r]+/, "", out)
sub(/[ \t\r]+$/, "", out)
return out
}
/^[ \t]*#/ { next }
/^[ \t]*authentication[ \t]*:/ {
inblk = 1
line = $0
sub(/^[ \t]*authentication[ \t]*:[ \t]*/, "", line)
if (match(line, /authenticator[ \t]*:/)) {
print scalar(substr(line, RSTART + RLENGTH))
exit
}
next
}
inblk && /^[ \t]+authenticator[ \t]*:/ {
line = $0
sub(/^[ \t]*authenticator[ \t]*:[ \t]*/, "", line)
print scalar(line)
exit
}
' "${yaml}"
}

# A mounted yaml can carry an authentication block whose authenticator
# cannot be read (an empty or unparseable one). That is not the
# both-empty case: exporting the default would override an explicit
# choice that snakeyaml does resolve, so callers treat it as a mismatch.
has_yaml_authentication_block() {
local yaml="./conf/gremlin-server.yaml"

[[ -f "${yaml}" ]] || return 1
grep -Eq '^[[:blank:]]*authentication[[:blank:]]*:' "${yaml}"
}

# enable-auth.sh appends definitions to files it did not write. On a
# mounted config those appended definitions are duplicates the two parsers
# resolve in opposite directions — HugeConfig (commons-configuration) takes
# the first, snakeyaml takes the last — so Gremlin and REST can land on
# different authenticators with no error from either. Normalize both sides
# to one definition of the same authenticator here; enable-auth.sh's
# per-file guards then make its appends no-ops on anything already set.
align_auth_config() {
local rest_auth yaml_auth

rest_auth=$(get_prop "auth.authenticator" "${REST_SERVER_CONF}")
yaml_auth=$(get_yaml_authenticator)
if [[ -z "${yaml_auth}" ]] && has_yaml_authentication_block; then
log "WARN: gremlin-server.yaml carries an authentication block" \
"without a readable authenticator; leaving both sides untouched"
return
fi
if [[ -n "${rest_auth}" && -n "${yaml_auth}" && "${rest_auth}" != "${yaml_auth}" ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 These two values are decoded differently, so identical configurations can read as a mismatch. props.awk returns the on-disk escaped form on purpose ("The value of get is intentionally not unescaped"), while get_yaml_authenticator's scalar() returns what snakeyaml decodes:

rest-server.properties:  auth.authenticator=org.apache.hugegraph.auth\.StandardAuthenticator
gremlin-server.yaml:       authenticator: org.apache.hugegraph.auth.StandardAuthenticator

WARN: REST and Gremlin name different authenticators
      ('org.apache.hugegraph.auth\.StandardAuthenticator' vs
       'org.apache.hugegraph.auth.StandardAuthenticator'); leaving both untouched

java.util.Properties reads both spellings as the same class, so the WARN is spurious and the alignment this function exists for is skipped.

Line 173 is the mirror of it: set_prop_encoded "auth.authenticator" "${yaml_auth}" hands a snakeyaml-decoded scalar to the setter that skips encode_prop_value. Harmless for a bare class name, wrong for any scalar carrying a backslash or leading space.

Requested change: unescape before comparing and before the export below, either through a get_decoded mode in props.awk (keeping the raw mode for the secret round trip that needs it) or an unescape here, and use set_prop rather than set_prop_encoded on line 173.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 5f50511. align_auth_config now reads the authenticator via a get-decoded mode (new get_prop wrapper) that unescapes like java.util.Properties before comparing with the snakeyaml scalar, so an escaped properties value and a plain yaml scalar no longer WARN; the yaml-to-properties write on line 173 goes through the encoding setter set_prop. Raw mode kept for the token-secret round trip. Covered by an escaped-authenticator regression case; suite passes.

log "WARN: REST and Gremlin name different authenticators" \
"('${rest_auth}' vs '${yaml_auth}'); leaving both untouched"
return
fi
if [[ -z "${rest_auth}" && -z "${yaml_auth}" ]]; then
export AUTHENTICATOR_CLASS="org.apache.hugegraph.auth.StandardAuthenticator"
elif [[ -n "${yaml_auth}" ]]; then
set_prop "auth.authenticator" "${yaml_auth}" "${REST_SERVER_CONF}"
else
export AUTHENTICATOR_CLASS="${rest_auth}"
fi
# auth.graph_store and the gremlin.graph flip are left to enable-auth.sh,
# which appends/rewrites only what is absent or still the plain default.
}

migrate_env() {
Expand Down Expand Up @@ -147,6 +258,7 @@ elif [[ -n "${AUTH_TOKEN_SECRET_ENCODED}" ]]; then
fi
if [[ -n "${PASSWORD:-}" ]]; then
set_prop "auth.admin_pa" "${PASSWORD}" "${REST_SERVER_CONF}"
align_auth_config
# This script is idempotent and must run outside the initialization guard:
# an upgrade can preserve the marker from an unauthenticated deployment.
./bin/enable-auth.sh
Expand Down
Loading