Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
698b0c3
fix(docker): make auth bootstrap safe for mounted and upgraded configs
Adarsh-Me Sep 3, 2026
f5e368c
fix(docker): close review gaps in auth bootstrap alignment
Adarsh-Me Sep 11, 2026
5f50511
fix(docker): close review gaps in auth bootstrap parsing and rewrite
Adarsh0087 Sep 12, 2026
bedc21e
fix(docker): keep the config inode when rewriting properties
Adarsh-Me Sep 13, 2026
bf2718f
fix(docker): refuse one-sided auth bootstrap and pre-create tmp 0600
Sep 16, 2026
b93b52e
fix(docker): close the remaining auth bootstrap review gaps
Adarsh-Me Sep 22, 2026
bf83032
test(docker): cover the rollback branch where restoring also fails
Adarsh-Me Sep 22, 2026
b8801a6
fix(docker): decide authentication by side, not by class
Adarsh-Me Sep 23, 2026
5afbb4a
fix(docker): read properties and yaml the way the server does
Adarsh-Me Sep 23, 2026
53a5edb
Merge master into fix-entrypoint-auth-bootstrap
Adarsh-Me Sep 23, 2026
2c9ebaf
fix(docker): let enable-auth.sh answer what the entrypoint asks
Adarsh-Me Sep 23, 2026
d5ccb94
fix(docker): read the authenticator value the server actually reads
Adarsh-Me Sep 24, 2026
cc9cc8c
fix(docker): answer the config questions the guards cannot resolve
Adarsh-Me Sep 24, 2026
5940fa5
fix(docker): fail closed on layouts the guards resolved too optimisti…
Adarsh-Me Sep 24, 2026
c1dde5e
fix(docker): read the yaml shapes the scanner still answered wrongly
Adarsh-Me Sep 25, 2026
b42b1ba
fix(docker): refuse to arm REST when the yaml names no authenticator
Adarsh-Me Sep 25, 2026
a5de9f6
fix(docker): reject an unusable AUTHENTICATOR_CLASS before writing it
Adarsh-Me Sep 26, 2026
be7f5e2
fix(docker): read a flow collection that a child value leaves open
Adarsh-Me Sep 27, 2026
664dcac
fix(docker): answer yamlscan with what Settings.read() actually loads
Adarsh-Me Sep 27, 2026
364d42f
fix(docker): read a BOM, a node property and a CR as SnakeYAML does
Adarsh-Me Sep 28, 2026
9ae26c8
fix(docker): state get's real exit status, and drop the has mode noth…
Adarsh-Me Sep 28, 2026
f61a0f0
fix(docker): read every value the script decides on before the first …
Adarsh-Me Sep 29, 2026
0be7fde
fix(docker): refuse a config the run cannot write, and do not call it…
Adarsh-Me Sep 29, 2026
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
3 changes: 3 additions & 0 deletions hugegraph-server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ RUN sed -i "s/^restserver.url.*$/restserver.url=http:\/\/0.0.0.0:8080/g" ./conf/
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 .
# props.awk needs no COPY: it ships in the assembly bin/ above, which is also
# where bin/enable-auth.sh finds it. yamlscan.awk serves only the entrypoint.
COPY hugegraph-server/hugegraph-dist/docker/yamlscan.awk .
Comment on lines +73 to +75

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.

At merge base 83ef9f3 this Dockerfile already had WORKDIR /hugegraph-server/ (48), COPY .../docker-entrypoint.sh . (72), VOLUME /hugegraph-server (76) and CMD ["./docker-entrypoint.sh"] (82) - so the shadow predates this PR and already covers the entrypoint itself; my only new copy is yamlscan.awk (75). Relocating the payload outside the volume is a wider image-layout change; happy to do it here if maintainers want that.

RUN chmod 755 ./docker-entrypoint.sh

EXPOSE 8080
Expand Down
3 changes: 3 additions & 0 deletions hugegraph-server/Dockerfile-hstore
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ RUN cd /hugegraph-server/conf/graphs \
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 .
# props.awk needs no COPY: it ships in the assembly bin/ above, which is also
# where bin/enable-auth.sh finds it. yamlscan.awk serves only the entrypoint.
COPY hugegraph-server/hugegraph-dist/docker/yamlscan.awk .
Comment on lines +75 to +77

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.

Same for hstore: at merge base 83ef9f3 this file already had WORKDIR /hugegraph-server/ (46), COPY .../docker-entrypoint.sh . (74), VOLUME /hugegraph-server (78) and CMD ["./docker-entrypoint.sh"] (84), so the entrypoint was already inside the declared volume before this PR added COPY yamlscan.awk (77). I can move these helpers under /opt in a follow-up if that is the preferred direction.

RUN chmod 755 ./docker-entrypoint.sh

EXPOSE 8080
Expand Down
144 changes: 141 additions & 3 deletions hugegraph-server/hugegraph-dist/docker/docker-entrypoint-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,28 @@
#
set -euo pipefail

# CI runs this harness under a backend matrix: server-ci.yml exports BACKEND for
# the rocksdb leg, which is the only leg that reaches these tests. The
# entrypoint maps BACKEND to HG_SERVER_BACKEND and then overwrites whatever a
# fixture writes into hugegraph.properties, so a case that decides on the
# on-disk backend -- the escaped-hstore assertion below -- would be answered by
# the matrix value rather than by the file it is checking, and would fail in CI
# while passing locally. Clear the inherited backend/pd environment so the
# harness is hermetic; cases that mean to drive the entrypoint from the
# environment set it on their own invocation (see the hstore mapping test). The
# production precedence (environment beats file) is left exactly as it is.
unset BACKEND HG_SERVER_BACKEND PD_PEERS HG_SERVER_PD_PEERS

SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
TEST_HOME=$(mktemp -d "${TMPDIR:-/tmp}/hugegraph-entrypoint-test.XXXXXX")
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"
# props.awk is packaged in the release bin/; the image gets it from there, and
# the entrypoint accepts it beside itself so this harness can stage either.
cp "${SCRIPT_DIR}/../src/assembly/static/bin/props.awk" "${TEST_HOME}/props.awk"
cp "${SCRIPT_DIR}/yamlscan.awk" "${TEST_HOME}/yamlscan.awk"
touch "${TEST_HOME}/docker/init_complete"

cat > "${TEST_HOME}/conf/rest-server.properties" <<'EOF'
Expand Down Expand Up @@ -54,6 +70,7 @@ printf 'called\n' >> ./docker/enable-auth-calls
EOF
cat > "${TEST_HOME}/bin/wait-partition.sh" <<'EOF'
#!/usr/bin/env bash
printf 'called\n' >> ./docker/wait-partition-calls
exit 0
EOF
cat > "${TEST_HOME}/bin/wait-storage.sh" <<'EOF'
Expand Down Expand Up @@ -200,7 +217,7 @@ reused_complex_secret=$(sed -n 's/^auth\.token_secret=//p' \
"${TEST_HOME}/conf/rest-server.properties")
[[ "${reused_complex_secret}" == "${complex_secret}" ]]
[[ "${reused_complex_secret}" == \
'Strong\\Secret\ 9!0123456789abcdef' ]]
'Strong\\Secret\u00209!0123456789abcdef' ]]

(
cd "${TEST_HOME}"
Expand All @@ -217,16 +234,46 @@ trailing_space_secret=$(sed -n 's/^auth\.token_secret=//p' \
reused_trailing_space_secret=$(sed -n 's/^auth\.token_secret=//p' \
"${TEST_HOME}/conf/rest-server.properties")
[[ "${trailing_space_secret}" == \
'SecretEnds\ 0123456789abcdefABCDE\ ' ]]
'SecretEnds\u00200123456789abcdefABCDE\u0020' ]]
[[ "${reused_trailing_space_secret}" == "${trailing_space_secret}" ]]
grep -Fqx 'auth.admin_pa=pa' \
"${TEST_HOME}/conf/rest-server.properties"
# The secret above ends in a space, and commons-configuration trims a physical
# line before it asks whether that line continues: the `\ ` the encoder used to
# write survives the trim as a lone trailing backslash, which pulls the property
# under it into the password -- that is how a file that plainly carried
# auth.admin_pa next to it would reach the server as one long secret. \u0020
# leaves the trimmer nothing to take.
grep -Fqx 'auth.token_secret=SecretEnds\u00200123456789abcdefABCDE\u0020' \
"${TEST_HOME}/conf/rest-server.properties" || {
echo "a trailing space must not be written as a backslash-space" >&2
sed -n 's/^auth\.token_secret=/written: [&]/p' \
"${TEST_HOME}/conf/rest-server.properties" >&2
exit 1
}
# Round trip: the secret comes back with both spaces it started with, and the
# property written under it is still its own property.
props_read() {
PROPS_MODE=get PROPS_DECODED=1 PROPS_KEY="$1" \
PROPS_FILE="${TEST_HOME}/conf/rest-server.properties" \
awk -f "${TEST_HOME}/props.awk" /dev/null
}
[[ "$(props_read auth.token_secret)" == 'SecretEnds 0123456789abcdefABCDE ' ]] || {
echo "auth.token_secret lost its spaces: [$(props_read auth.token_secret)]" >&2
exit 1
}
[[ "$(props_read auth.admin_pa)" == "pa" ]] || {
echo "auth.admin_pa is not its own property any more: [$(props_read auth.admin_pa)]" >&2
exit 1
}
grep -Fqx 'auth.admin_pa=pa' \
"${TEST_HOME}/conf/rest-server.properties"

(
cd "${TEST_HOME}"
PASSWORD='Strong\Pass 9!' bash ./docker-entrypoint.sh
)
grep -Fqx 'auth.admin_pa=Strong\\Pass\ 9!' \
grep -Fqx 'auth.admin_pa=Strong\\Pass\u00209!' \
"${TEST_HOME}/conf/rest-server.properties"

rm -f "${TEST_HOME}/docker/init_complete"
Expand Down Expand Up @@ -324,4 +371,95 @@ done
)
assert_start_timeout 120

# A mounted rest-server.properties that already carries auth.authenticator,
# with no matching yaml mapping and no PASSWORD given, used to start without a
# word: the parity check ran only inside the PASSWORD branch, so nothing ever
# compared the two sides and the server came up with REST enforcing and Gremlin
# on AllowAllAuthenticator. The check now runs on every start, and a refusal
# has to come before anything touches the backend.
printf '%s\n' 'host: 8182' > "${TEST_HOME}/conf/gremlin-server.yaml"
grep -qx 'auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator' \
"${TEST_HOME}/conf/rest-server.properties" ||
printf '%s\n' \
'auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator' \
>> "${TEST_HOME}/conf/rest-server.properties"
rm -f "${TEST_HOME}/docker/init_complete"
before_calls="$(wc -l < "${TEST_HOME}/docker/init-store-calls")"
before_auth="$(wc -l < "${TEST_HOME}/docker/enable-auth-calls")"
status=0
(
cd "${TEST_HOME}"
bash ./docker-entrypoint.sh
) || status=$?
if (( status == 0 )); then
echo "entrypoint must refuse a mounted REST-only authenticator with no PASSWORD" >&2
exit 1
fi
if [[ "$(wc -l < "${TEST_HOME}/docker/init-store-calls")" != "${before_calls}" ]]; then
echo "the refusal must happen before init-store runs" >&2
exit 1
fi
if [[ "$(wc -l < "${TEST_HOME}/docker/enable-auth-calls")" != "${before_auth}" ]]; then
echo "a refused start must not run enable-auth.sh" >&2
exit 1
fi

# The same start is accepted once both sides agree, so the check above is a
# parity decision and not a blanket refusal to run without PASSWORD.
printf '%s\n' \
'authentication: {' \
' authenticator: org.apache.hugegraph.auth.StandardAuthenticator,' \
' config: {tokens: conf/rest-server.properties}' \
'}' > "${TEST_HOME}/conf/gremlin-server.yaml"
rm -f "${TEST_HOME}/docker/init_complete"
(
cd "${TEST_HOME}"
bash ./docker-entrypoint.sh
)

# ── The stabilization check follows the backend the JVM actually loaded ──
# ACTUAL_BACKEND is compared against a literal, so it has to be the decoded
# value. A mounted hugegraph.properties may spell the word with a unicode
# escape for the s, which java.util.Properties hands the server as hstore;
# reading the on-disk escaping instead compared something else to hstore,
# skipped wait-partition.sh, and let startup continue before the partitions
# were assigned. bs is the backslash, taken from its code point rather than
# written here: printf '%c' 92 hands back the digit 9, which would have built a
# fixture holding a different word than the one being decoded.
bs=$(awk 'BEGIN { printf "%c", 92 }')
if [[ "${#bs}" != 1 || "$(printf '%d' "'${bs}")" != 92 ]]; then
echo "this host did not yield a backslash for code point 92" >&2
exit 1
fi
touch "${TEST_HOME}/docker/init_complete"
rm -f "${TEST_HOME}/docker/wait-partition-calls"
printf '%s\n' "backend=h${bs}u0073tore" 'pd.peers=pd:8686' \
> "${TEST_HOME}/conf/graphs/hugegraph.properties"
if [[ "$(head -n 1 "${TEST_HOME}/conf/graphs/hugegraph.properties")" != \
"backend=h${bs}u0073tore" ]]; then
echo "the fixture has to hold the escaped bytes, not the decoded word" >&2
exit 1
fi
(
cd "${TEST_HOME}"
bash ./docker-entrypoint.sh

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

⚠️ [P2] Isolate this config-driven invocation from the CI backend environment. Server CI exports BACKEND=rocksdb; the entrypoint maps it to HG_SERVER_BACKEND and overwrites this fixture's escaped hstore, so the assertion at line 436 fails. This reproduces with BACKEND=rocksdb and passes when BACKEND and HG_SERVER_BACKEND are unset. Clear inherited backend variables for these config-driven cases rather than changing production environment precedence.

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 5940fa5. The harness now unsets inherited BACKEND/HG_SERVER_BACKEND/PD_PEERS/HG_SERVER_PD_PEERS at the top, so the escaped hstore fixture decides the stabilization check instead of the CI matrix value. Production env precedence is unchanged; env-driven cases set it per invocation. Reproduced: the test failed under BACKEND=rocksdb before, passes after.

)
if [[ ! -s "${TEST_HOME}/docker/wait-partition-calls" ]]; then
echo "an escaped hstore backend must still reach wait-partition.sh" >&2
exit 1
fi
# The other half: this is a read that follows the server, not a switch that
# simply always waits.
rm -f "${TEST_HOME}/docker/wait-partition-calls"
printf '%s\n' 'backend=rocksdb' 'pd.peers=pd:8686' \
> "${TEST_HOME}/conf/graphs/hugegraph.properties"
(
cd "${TEST_HOME}"
bash ./docker-entrypoint.sh
)
if [[ -e "${TEST_HOME}/docker/wait-partition-calls" ]]; then
echo "wait-partition.sh ran for a rocksdb backend" >&2
exit 1
fi

echo "PASS: Docker entrypoint configures HStore discovery and authentication"
Loading
Loading