-
Notifications
You must be signed in to change notification settings - Fork 639
fix(docker): make auth bootstrap safe for mounted and upgraded configs #3192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
698b0c3
f5e368c
5f50511
bedc21e
bf2718f
b93b52e
bf83032
b8801a6
5afbb4a
53a5edb
2c9ebaf
d5ccb94
cc9cc8c
5940fa5
c1dde5e
b42b1ba
a5de9f6
be7f5e2
664dcac
364d42f
9ae26c8
f61a0f0
0be7fde
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same for hstore: at merge base |
||
| RUN chmod 755 ./docker-entrypoint.sh | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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' | ||
|
|
@@ -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' | ||
|
|
@@ -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}" | ||
|
|
@@ -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" | ||
|
|
@@ -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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At merge base
83ef9f3this Dockerfile already hadWORKDIR /hugegraph-server/(48),COPY .../docker-entrypoint.sh .(72),VOLUME /hugegraph-server(76) andCMD ["./docker-entrypoint.sh"](82) - so the shadow predates this PR and already covers the entrypoint itself; my only new copy isyamlscan.awk(75). Relocating the payload outside the volume is a wider image-layout change; happy to do it here if maintainers want that.