bitflicker64 commented on code in PR #3119:
URL: https://github.com/apache/hugegraph/pull/3119#discussion_r3676024845
##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -54,28 +199,109 @@ migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
# ── Map env → properties file ─────────────────────────────────────────
[[ -n "${HG_SERVER_BACKEND:-}" ]] && set_prop "backend"
"${HG_SERVER_BACKEND}" "${GRAPH_CONF}"
[[ -n "${HG_SERVER_PD_PEERS:-}" ]] && set_prop "pd.peers"
"${HG_SERVER_PD_PEERS}" "${GRAPH_CONF}"
+if [[ -n "${HG_SERVER_INIT_STORE_ENABLED:-}" ]]; then
+ # Canonicalize before writing, so the property file only ever holds `true`
+ # or `false` and cannot be read differently by the shell and the server
+ if ! HG_SERVER_INIT_STORE_ENABLED=$(to_bool
"${HG_SERVER_INIT_STORE_ENABLED}"); then
+ log "ERROR: HG_SERVER_INIT_STORE_ENABLED must be a boolean, got
'${HG_SERVER_INIT_STORE_ENABLED}'"
+ exit 1
+ fi
+ set_prop "init_store.enabled" "${HG_SERVER_INIT_STORE_ENABLED}"
"${REST_SERVER_CONF}"
+fi
# ── Build wait-storage env ─────────────────────────────────────────────
WAIT_ENV=()
[[ -n "${HG_SERVER_BACKEND:-}" ]] &&
WAIT_ENV+=("hugegraph.backend=${HG_SERVER_BACKEND}")
[[ -n "${HG_SERVER_PD_PEERS:-}" ]] &&
WAIT_ENV+=("hugegraph.pd.peers=${HG_SERVER_PD_PEERS}")
-# ── Init store (once) ─────────────────────────────────────────────────
-if [[ ! -f "${DOCKER_FOLDER}/${INIT_FLAG_FILE}" ]]; then
+wait_storage() {
if (( ${#WAIT_ENV[@]} > 0 )); then
env "${WAIT_ENV[@]}" ./bin/wait-storage.sh
else
./bin/wait-storage.sh
fi
+}
+
+# ── Init store (once) ─────────────────────────────────────────────────
+# With `init_store.enabled=false` (distributed PD/HStore) init-store is a
no-op:
+# storage owns the metadata and the admin account is created on server startup
+# from `auth.admin_pa`. A requested PASSWORD is therefore written to that
+# property rather than piped into init-store.sh, where it would be read and
+# discarded without creating the account.
+#
+# The value is read back from the config file rather than from the env var, so
+# that a rest-server.properties mounted with the property already set behaves
+# the same as `HG_SERVER_INIT_STORE_ENABLED` (the env mapping above has already
+# been applied, so env still wins).
+INIT_STORE_ENABLED=$(get_prop "init_store.enabled" "${REST_SERVER_CONF}")
+if [[ -n "${INIT_STORE_ENABLED}" ]]; then
+ if ! INIT_STORE_ENABLED=$(to_bool "${INIT_STORE_ENABLED}"); then
+ log "ERROR: init_store.enabled in ${REST_SERVER_CONF} must be a
boolean," \
+ "got '${INIT_STORE_ENABLED}'"
+ exit 1
+ fi
+fi
+if [[ "${INIT_STORE_ENABLED:-true}" == "false" ]]; then
+ log "init-store disabled, skipping local backend/admin init"
+
+ # With init-store skipped, nothing creates the built-in admin account
+ # unless the server takes the PD metadata path, which it only does when
+ # `usePD=true`. Enabling auth without that combination starts a server
+ # that enforces authentication while no account exists, so refuse it here
+ # rather than fail every request later.
+ # Only the built-in authenticator relies on that account: any other
+ # implementation class keeps its identities outside HugeGraph, so requiring
+ # usePD for it would reject a deployment that works
+ AUTH_REQUESTED=""
+ [[ -n "${PASSWORD:-}" ]] && AUTH_REQUESTED=1
Review Comment:
Final update in `95bfe451`: `InitStore` now owns this decision through
`StandardAuthenticator.class.isAssignableFrom(...)`. Custom or unresolved
authenticators are exempt, while subclasses still require the built-in
bootstrap. The entrypoint delegates validation to that Java path even when
`PASSWORD` is present. Direct coverage includes both a custom authenticator and
a derived standard authenticator.
##########
hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/cmd/InitStore.java:
##########
@@ -83,6 +121,61 @@ public static void main(String[] args) throws Exception {
}
}
+ /**
+ * Skipping means init-store does not create the built-in admin account,
+ * and the only other component that creates it is
+ * GraphManager.initAdminUserIfNeeded(), reached from loadMetaFromPD() and
+ * so gated on 'usePD'. Failing here rather than returning zero keeps
+ * tarball and init-job callers, which see only the exit status, from
+ * continuing into a server that enforces authentication with no account
+ * to authenticate against.
+ * <p>
+ * Remote auth is exempt: the auth manager is then an RPC client, and
+ * StandardAuthenticator only bootstraps an admin for a local one. So is
+ * any authenticator other than the built-in one, which keeps its
+ * identities outside HugeGraph and needs no such account.
+ */
+ private static void checkAdminBootstrapReachable(HugeConfig conf,
+ String restConf) {
+ if (!requiresBuiltinAdmin(conf.get(ServerOptions.AUTHENTICATOR)) ||
+ conf.get(ServerOptions.USE_PD) ||
Review Comment:
Fixed in `95bfe451`. Disabled local built-in auth now requires both
`usePD=true` and the graph selected by `auth.graph_store` to use
`backend=hstore`. A memory-backed auth graph is rejected and the HStore case is
accepted by direct CLI tests; remote auth remains exempt.
##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -54,28 +199,109 @@ migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
# ── Map env → properties file ─────────────────────────────────────────
[[ -n "${HG_SERVER_BACKEND:-}" ]] && set_prop "backend"
"${HG_SERVER_BACKEND}" "${GRAPH_CONF}"
[[ -n "${HG_SERVER_PD_PEERS:-}" ]] && set_prop "pd.peers"
"${HG_SERVER_PD_PEERS}" "${GRAPH_CONF}"
+if [[ -n "${HG_SERVER_INIT_STORE_ENABLED:-}" ]]; then
+ # Canonicalize before writing, so the property file only ever holds `true`
+ # or `false` and cannot be read differently by the shell and the server
+ if ! HG_SERVER_INIT_STORE_ENABLED=$(to_bool
"${HG_SERVER_INIT_STORE_ENABLED}"); then
+ log "ERROR: HG_SERVER_INIT_STORE_ENABLED must be a boolean, got
'${HG_SERVER_INIT_STORE_ENABLED}'"
+ exit 1
+ fi
+ set_prop "init_store.enabled" "${HG_SERVER_INIT_STORE_ENABLED}"
"${REST_SERVER_CONF}"
+fi
# ── Build wait-storage env ─────────────────────────────────────────────
WAIT_ENV=()
[[ -n "${HG_SERVER_BACKEND:-}" ]] &&
WAIT_ENV+=("hugegraph.backend=${HG_SERVER_BACKEND}")
[[ -n "${HG_SERVER_PD_PEERS:-}" ]] &&
WAIT_ENV+=("hugegraph.pd.peers=${HG_SERVER_PD_PEERS}")
-# ── Init store (once) ─────────────────────────────────────────────────
-if [[ ! -f "${DOCKER_FOLDER}/${INIT_FLAG_FILE}" ]]; then
+wait_storage() {
if (( ${#WAIT_ENV[@]} > 0 )); then
env "${WAIT_ENV[@]}" ./bin/wait-storage.sh
else
./bin/wait-storage.sh
fi
+}
+
+# ── Init store (once) ─────────────────────────────────────────────────
+# With `init_store.enabled=false` (distributed PD/HStore) init-store is a
no-op:
+# storage owns the metadata and the admin account is created on server startup
+# from `auth.admin_pa`. A requested PASSWORD is therefore written to that
+# property rather than piped into init-store.sh, where it would be read and
+# discarded without creating the account.
+#
+# The value is read back from the config file rather than from the env var, so
+# that a rest-server.properties mounted with the property already set behaves
+# the same as `HG_SERVER_INIT_STORE_ENABLED` (the env mapping above has already
+# been applied, so env still wins).
+INIT_STORE_ENABLED=$(get_prop "init_store.enabled" "${REST_SERVER_CONF}")
+if [[ -n "${INIT_STORE_ENABLED}" ]]; then
+ if ! INIT_STORE_ENABLED=$(to_bool "${INIT_STORE_ENABLED}"); then
+ log "ERROR: init_store.enabled in ${REST_SERVER_CONF} must be a
boolean," \
+ "got '${INIT_STORE_ENABLED}'"
+ exit 1
+ fi
+fi
+if [[ "${INIT_STORE_ENABLED:-true}" == "false" ]]; then
+ log "init-store disabled, skipping local backend/admin init"
+
+ # With init-store skipped, nothing creates the built-in admin account
+ # unless the server takes the PD metadata path, which it only does when
+ # `usePD=true`. Enabling auth without that combination starts a server
+ # that enforces authentication while no account exists, so refuse it here
+ # rather than fail every request later.
+ # Only the built-in authenticator relies on that account: any other
+ # implementation class keeps its identities outside HugeGraph, so requiring
+ # usePD for it would reject a deployment that works
+ AUTH_REQUESTED=""
+ [[ -n "${PASSWORD:-}" ]] && AUTH_REQUESTED=1
+ [[ "$(get_prop "auth.authenticator" "${REST_SERVER_CONF}")" == \
+ "${BUILTIN_AUTHENTICATOR}" ]] && AUTH_REQUESTED=1
+ # Remote auth delegates to another service and has no local admin to
+ # create, so it is exempt from the requirement below
+ [[ -n "$(get_prop "auth.remote_url" "${REST_SERVER_CONF}")" ]] &&
AUTH_REQUESTED=""
+ if [[ -n "${AUTH_REQUESTED}" ]]; then
+ USE_PD=$(to_bool "$(get_prop "usePD" "${REST_SERVER_CONF}")"
2>/dev/null || echo "false")
+ if [[ "${USE_PD}" != "true" ]]; then
+ log "ERROR: auth is enabled and init_store.enabled=false, but
usePD is not true."
+ log "ERROR: With init-store skipped the admin account is only
created on the PD"
+ log "ERROR: metadata path, so this combination would start a
server that nobody"
+ log "ERROR: can authenticate against."
+ log "ERROR: Set usePD=true in ${REST_SERVER_CONF}, or leave
init-store enabled"
+ log "ERROR: so that it can create the admin account locally."
+ exit 1
+ fi
+ fi
+
+ # Still wait: the server needs the storage side reachable at startup even
+ # though nothing is initialized here
+ wait_storage
+
+ if [[ -n "${PASSWORD:-}" ]]; then
Review Comment:
Fixed in `95bfe451`. `ensure_auth_enabled` now runs when either `PASSWORD`
or an effective authenticator is present, and idempotently completes the REST
key, Gremlin authentication block, and `HugeFactoryAuthProxy` switch.
`auth.admin_pa` remains conditional on `PASSWORD`. The
mounted-auth-without-password regression is covered in the entrypoint suite.
##########
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:
##########
@@ -54,28 +199,109 @@ migrate_env "PD_PEERS" "HG_SERVER_PD_PEERS"
# ── Map env → properties file ─────────────────────────────────────────
[[ -n "${HG_SERVER_BACKEND:-}" ]] && set_prop "backend"
"${HG_SERVER_BACKEND}" "${GRAPH_CONF}"
[[ -n "${HG_SERVER_PD_PEERS:-}" ]] && set_prop "pd.peers"
"${HG_SERVER_PD_PEERS}" "${GRAPH_CONF}"
+if [[ -n "${HG_SERVER_INIT_STORE_ENABLED:-}" ]]; then
+ # Canonicalize before writing, so the property file only ever holds `true`
+ # or `false` and cannot be read differently by the shell and the server
+ if ! HG_SERVER_INIT_STORE_ENABLED=$(to_bool
"${HG_SERVER_INIT_STORE_ENABLED}"); then
+ log "ERROR: HG_SERVER_INIT_STORE_ENABLED must be a boolean, got
'${HG_SERVER_INIT_STORE_ENABLED}'"
+ exit 1
+ fi
+ set_prop "init_store.enabled" "${HG_SERVER_INIT_STORE_ENABLED}"
"${REST_SERVER_CONF}"
+fi
# ── Build wait-storage env ─────────────────────────────────────────────
WAIT_ENV=()
[[ -n "${HG_SERVER_BACKEND:-}" ]] &&
WAIT_ENV+=("hugegraph.backend=${HG_SERVER_BACKEND}")
[[ -n "${HG_SERVER_PD_PEERS:-}" ]] &&
WAIT_ENV+=("hugegraph.pd.peers=${HG_SERVER_PD_PEERS}")
-# ── Init store (once) ─────────────────────────────────────────────────
-if [[ ! -f "${DOCKER_FOLDER}/${INIT_FLAG_FILE}" ]]; then
+wait_storage() {
if (( ${#WAIT_ENV[@]} > 0 )); then
env "${WAIT_ENV[@]}" ./bin/wait-storage.sh
else
./bin/wait-storage.sh
fi
+}
+
+# ── Init store (once) ─────────────────────────────────────────────────
+# With `init_store.enabled=false` (distributed PD/HStore) init-store is a
no-op:
+# storage owns the metadata and the admin account is created on server startup
+# from `auth.admin_pa`. A requested PASSWORD is therefore written to that
+# property rather than piped into init-store.sh, where it would be read and
+# discarded without creating the account.
+#
+# The value is read back from the config file rather than from the env var, so
+# that a rest-server.properties mounted with the property already set behaves
+# the same as `HG_SERVER_INIT_STORE_ENABLED` (the env mapping above has already
+# been applied, so env still wins).
+INIT_STORE_ENABLED=$(get_prop "init_store.enabled" "${REST_SERVER_CONF}")
+if [[ -n "${INIT_STORE_ENABLED}" ]]; then
+ if ! INIT_STORE_ENABLED=$(to_bool "${INIT_STORE_ENABLED}"); then
+ log "ERROR: init_store.enabled in ${REST_SERVER_CONF} must be a
boolean," \
+ "got '${INIT_STORE_ENABLED}'"
+ exit 1
+ fi
+fi
+if [[ "${INIT_STORE_ENABLED:-true}" == "false" ]]; then
+ log "init-store disabled, skipping local backend/admin init"
+
+ # With init-store skipped, nothing creates the built-in admin account
+ # unless the server takes the PD metadata path, which it only does when
+ # `usePD=true`. Enabling auth without that combination starts a server
+ # that enforces authentication while no account exists, so refuse it here
+ # rather than fail every request later.
+ # Only the built-in authenticator relies on that account: any other
+ # implementation class keeps its identities outside HugeGraph, so requiring
+ # usePD for it would reject a deployment that works
+ AUTH_REQUESTED=""
+ [[ -n "${PASSWORD:-}" ]] && AUTH_REQUESTED=1
+ [[ "$(get_prop "auth.authenticator" "${REST_SERVER_CONF}")" == \
+ "${BUILTIN_AUTHENTICATOR}" ]] && AUTH_REQUESTED=1
+ # Remote auth delegates to another service and has no local admin to
+ # create, so it is exempt from the requirement below
+ [[ -n "$(get_prop "auth.remote_url" "${REST_SERVER_CONF}")" ]] &&
AUTH_REQUESTED=""
+ if [[ -n "${AUTH_REQUESTED}" ]]; then
+ USE_PD=$(to_bool "$(get_prop "usePD" "${REST_SERVER_CONF}")"
2>/dev/null || echo "false")
+ if [[ "${USE_PD}" != "true" ]]; then
+ log "ERROR: auth is enabled and init_store.enabled=false, but
usePD is not true."
+ log "ERROR: With init-store skipped the admin account is only
created on the PD"
+ log "ERROR: metadata path, so this combination would start a
server that nobody"
+ log "ERROR: can authenticate against."
+ log "ERROR: Set usePD=true in ${REST_SERVER_CONF}, or leave
init-store enabled"
+ log "ERROR: so that it can create the admin account locally."
+ exit 1
+ fi
+ fi
+
+ # Still wait: the server needs the storage side reachable at startup even
+ # though nothing is initialized here
+ wait_storage
+
+ if [[ -n "${PASSWORD:-}" ]]; then
+ log "enabling auth mode, admin password applied via auth.admin_pa"
+ ensure_auth_enabled
+ # TODO: auth.admin_pa only applies when the admin account is first
+ # created, so changing PASSWORD on a later restart silently keeps the
+ # old one. It also leaves the password at rest in
rest-server.properties,
+ # unlike the enabled path where it only travels over stdin.
+ set_prop "auth.admin_pa" "$(props_escape "${PASSWORD}")"
"${REST_SERVER_CONF}"
Review Comment:
Fixed in `95bfe451`. Before persisting `auth.admin_pa`, the entrypoint
changes `rest-server.properties` to mode `0600`; the content rewrite remains in
place so a mounted-file inode is preserved. The regression starts from the
shipped `0644` mode and checks the same inode, final `0600` mode, and no
leftover scratch file.
##########
.github/workflows/docker-build-ci.yml:
##########
@@ -26,8 +26,19 @@ on:
paths:
- '**/Dockerfile*'
- '.dockerignore'
+ - 'hugegraph-server/hugegraph-dist/docker/**'
Review Comment:
Fixed in `95bfe451`. The Docker workflow path filter now includes the
shipped `enable-auth.sh` and `init-store.sh` paths as well as the Docker
entrypoint and tests, so changes to either source script trigger this suite.
##########
hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh:
##########
@@ -0,0 +1,545 @@
+#!/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.
+#
+# Smoke tests for docker-entrypoint.sh init-store lifecycle.
+#
+# The entrypoint is run against a throwaway install tree whose ./bin scripts
are
+# stubs recording their own invocation, so the tests assert on which scripts
ran
+# and on the resulting config, without needing a JVM, a backend or Docker.
+#
+# Usage: hugegraph-server/hugegraph-dist/docker/test/test-docker-entrypoint.sh
+
+set -uo pipefail
+
+SELF_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ENTRYPOINT="${SELF_DIR}/../docker-entrypoint.sh"
+
+PASS=0
+FAIL=0
+
+fail() {
+ echo " FAIL: $*"
+ FAIL=$((FAIL + 1))
+}
+
+ok() {
+ PASS=$((PASS + 1))
+}
+
+assert_ran() {
+ if grep -qxF "$1" "${INSTALL}/calls.log" 2>/dev/null; then
+ ok
+ else
+ fail "expected '$1' to run; calls were: $(tr '\n' ' ' <
"${INSTALL}/calls.log")"
+ fi
+}
+
+assert_not_ran() {
+ if grep -qxF "$1" "${INSTALL}/calls.log" 2>/dev/null; then
+ fail "expected '$1' NOT to run"
+ else
+ ok
+ fi
+}
+
+assert_file() {
+ if [[ -f "${INSTALL}/$1" ]]; then ok; else fail "expected file '$1' to
exist"; fi
+}
+
+assert_no_file() {
+ if [[ -f "${INSTALL}/$1" ]]; then fail "expected file '$1' NOT to exist";
else ok; fi
+}
+
+assert_prop() {
+ local expected="$1=$2"
+ if grep -qxF "${expected}" "${INSTALL}/conf/rest-server.properties"
2>/dev/null; then
+ ok
+ else
+ fail "expected property '${expected}' in rest-server.properties"
+ fi
+}
+
+# A scalar option must end up defined exactly once, on any separator, or the
+# properties parser exposes it as a list and a scalar read of it fails
+assert_prop_defined_once() {
+ local n
+ n=$(grep -cE "^[[:space:]]*$1([[:space:]]*[=:]|[[:space:]])" \
+ "${INSTALL}/conf/rest-server.properties" 2>/dev/null || true)
+ if [[ "${n}" == "1" ]]; then ok; else fail "expected '$1' defined once,
found ${n}"; fi
+}
+
+# Matches the separator set assert_prop_defined_once uses, so a `key:value` or
+# `key value` definition cannot pass as absent
+assert_no_prop_key() {
+ if grep -qE "^[[:space:]]*$1([[:space:]]*[=:]|[[:space:]])" \
+ "${INSTALL}/conf/rest-server.properties" 2>/dev/null; then
+ fail "expected no '$1' property"
+ else
+ ok
+ fi
+}
+
+# Auth is only fully enabled when all three configs agree: the REST properties,
+# the gremlin-server.yaml authentication block and the graph's auth proxy
+assert_auth_fully_enabled() {
+ local n
+ n=$(grep -cE '^[[:space:]]*authentication:' \
+ "${INSTALL}/conf/gremlin-server.yaml" 2>/dev/null || true)
+ if [[ "${n}" == "1" ]]; then
+ ok
+ else
+ fail "expected one gremlin-server.yaml authentication block, found
${n}"
+ fi
+ if grep -qxF
"gremlin.graph=org.apache.hugegraph.auth.HugeFactoryAuthProxy" \
+ "${INSTALL}/conf/graphs/hugegraph.properties" 2>/dev/null; then
+ ok
+ else
+ fail "expected hugegraph.properties to use HugeFactoryAuthProxy"
+ fi
+ assert_prop_defined_once "auth.authenticator"
+ assert_prop_defined_once "auth.graph_store"
+}
+
+# Build a throwaway install tree with stubbed bin scripts
+new_install() {
+ INSTALL=$(mktemp -d "${TMPDIR:-/tmp}/hg-entrypoint-test.XXXXXX")
+ mkdir -p "${INSTALL}/bin" "${INSTALL}/conf/graphs"
+
+ # Mirrors the shipped conf: the auth properties are present but commented
+ cat > "${INSTALL}/conf/rest-server.properties" <<'EOF'
+restserver.url=http://0.0.0.0:8080
+graphs=./conf/graphs
+#auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator
+#auth.admin_pa=pa
+EOF
+ cat > "${INSTALL}/conf/graphs/hugegraph.properties" <<'EOF'
+backend=rocksdb
+gremlin.graph=org.apache.hugegraph.HugeFactory
+EOF
+ # Shipped without an authentication block, which is what enable-auth.sh
adds
+ echo "host: 0.0.0.0" > "${INSTALL}/conf/gremlin-server.yaml"
+
+ local script
+ for script in wait-storage start-hugegraph wait-partition; do
+ cat > "${INSTALL}/bin/${script}.sh" <<EOF
+#!/bin/bash
+echo "${script}.sh" >> "${INSTALL}/calls.log"
+EOF
+ chmod +x "${INSTALL}/bin/${script}.sh"
+ done
+
+ # Records whether a password was piped in, which is how the entrypoint
+ # passes a Docker PASSWORD to the admin bootstrap
+ cat > "${INSTALL}/bin/init-store.sh" <<EOF
+#!/bin/bash
+echo "init-store.sh" >> "${INSTALL}/calls.log"
+if [[ ! -t 0 ]]; then
+ stdin=\$(cat)
+ [[ -n "\${stdin}" ]] && echo "init-store.sh:stdin=\${stdin}" >>
"${INSTALL}/calls.log"
+fi
+exit 0
+EOF
+ chmod +x "${INSTALL}/bin/init-store.sh"
+
+ # Mirrors bin/enable-auth.sh: appends the REST keys and the YAML
+ # authentication block, and switches the graph to the auth proxy
+ cat > "${INSTALL}/bin/enable-auth.sh" <<EOF
+#!/bin/bash
+echo "enable-auth.sh" >> "${INSTALL}/calls.log"
+{
+ echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator"
+ echo "auth.graph_store=hugegraph"
+} >> "${INSTALL}/conf/rest-server.properties"
+cat >> "${INSTALL}/conf/gremlin-server.yaml" <<'YAML'
+authentication: {
+ authenticator: org.apache.hugegraph.auth.StandardAuthenticator,
+ authenticationHandler: org.apache.hugegraph.auth.WsAndHttpBasicAuthHandler,
+ config: {tokens: conf/rest-server.properties}
+}
+YAML
+sed -i.bak
's/gremlin.graph=org.apache.hugegraph.HugeFactory/gremlin.graph=org.apache.hugegraph.auth.HugeFactoryAuthProxy/g'
\
+ "${INSTALL}/conf/graphs/hugegraph.properties"
+rm -f "${INSTALL}/conf/graphs/hugegraph.properties.bak"
+EOF
+ chmod +x "${INSTALL}/bin/enable-auth.sh"
+
+ : > "${INSTALL}/calls.log"
+}
+
+# Auth plus skipped init-store is only supported alongside the PD metadata
+# path, which is what actually creates the admin account in that mode
+enable_pd() {
+ echo "usePD=true" >> "${INSTALL}/conf/rest-server.properties"
+}
+
+# Run the entrypoint inside the throwaway tree. No ./bin/pid is ever written by
+# the stubs, so the entrypoint's tail-on-pid block is skipped and it returns.
+run_entrypoint() {
+ ( cd "${INSTALL}" && env "$@" bash "${ENTRYPOINT}" ) >
"${INSTALL}/out.log" 2>&1
+ local rc=$?
+ if [[ ${rc} -ne 0 ]]; then
+ fail "entrypoint exited ${rc}; output: $(cat "${INSTALL}/out.log")"
+ fi
+ return 0
+}
+
+cleanup() { [[ -n "${INSTALL:-}" ]] && rm -rf "${INSTALL}"; }
+trap cleanup EXIT
+
+echo "==> default: no flag set, full init runs"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_ran "wait-storage.sh"
+assert_ran "init-store.sh"
+assert_not_ran "enable-auth.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> default + PASSWORD: auth enabled, password piped to init-store"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret
+assert_ran "enable-auth.sh"
+assert_ran "init-store.sh"
+assert_ran "init-store.sh:stdin=s3cret"
+assert_file "docker/init_complete"
+assert_auth_fully_enabled
+cleanup
+
+echo "==> skip via env: init-store never runs and no init flag is written"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "wait-storage.sh"
+assert_not_ran "init-store.sh"
+assert_prop "init_store.enabled" "false"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> skip + PASSWORD: password reaches auth.admin_pa, not init-store
stdin"
+new_install
+enable_pd
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_ran "enable-auth.sh"
+assert_not_ran "init-store.sh"
+assert_not_ran "init-store.sh:stdin=s3cret"
+assert_prop "auth.admin_pa" "s3cret"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> skip via mounted property only: env var absent behaves the same"
+new_install
+enable_pd
+echo "init_store.enabled=false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret
+assert_not_ran "init-store.sh"
+assert_not_ran "init-store.sh:stdin=s3cret"
+assert_prop "auth.admin_pa" "s3cret"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> env wins over a conflicting mounted property"
+new_install
+echo "init_store.enabled=false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_ran "init-store.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> false then true: a restart with init enabled still initializes"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+: > "${INSTALL}/calls.log"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_ran "init-store.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> restart with init enabled: the flag file suppresses re-init"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_ran "init-store.sh"
+: > "${INSTALL}/calls.log"
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_not_ran "init-store.sh"
+assert_file "docker/init_complete"
+cleanup
+
+echo "==> uppercase FALSE is honoured, matching the server's boolean parsing"
+new_install
+enable_pd
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=FALSE PASSWORD=s3cret
+assert_not_ran "init-store.sh"
+assert_not_ran "init-store.sh:stdin=s3cret"
+assert_prop "init_store.enabled" "false"
+assert_prop "auth.admin_pa" "s3cret"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> 'off' and 'no' are honoured too"
+for value in off no; do
+ new_install
+ run_entrypoint -u PASSWORD "HG_SERVER_INIT_STORE_ENABLED=${value}"
+ assert_not_ran "init-store.sh"
+ assert_no_file "docker/init_complete"
+ cleanup
+done
+
+echo "==> a non-boolean value fails fast instead of diverging"
+new_install
+if ( cd "${INSTALL}" && env -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=maybe \
+ bash "${ENTRYPOINT}" ) >/dev/null 2>&1; then
+ fail "expected a non-boolean HG_SERVER_INIT_STORE_ENABLED to fail"
+else
+ ok
+fi
+assert_not_ran "start-hugegraph.sh"
+cleanup
+
+echo "==> mounted property with a ':' separator is honoured"
+new_install
+echo "init_store.enabled:false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED -u PASSWORD
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> password with backslashes survives the properties round trip"
+new_install
+enable_pd
+run_entrypoint 'PASSWORD=abc\def' HG_SERVER_INIT_STORE_ENABLED=false
+assert_not_ran "init-store.sh"
+# Written escaped, so the properties parser reads back the original `abc\def`
+assert_prop "auth.admin_pa" 'abc\\def'
+cleanup
+
+echo "==> skip + PASSWORD without usePD is refused, not silently started"
+new_install
+if ( cd "${INSTALL}" && env -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret \
+ HG_SERVER_INIT_STORE_ENABLED=false bash "${ENTRYPOINT}" ) >/dev/null
2>&1; then
+ fail "expected auth + skip without usePD to be refused"
+else
+ ok
+fi
+# Refused before starting the server, and without leaving auth half-enabled
+assert_not_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> skip with auth already in a mounted config, no usePD, is refused too"
+new_install
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+ >> "${INSTALL}/conf/rest-server.properties"
+if ( cd "${INSTALL}" && env -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false \
+ bash "${ENTRYPOINT}" ) >/dev/null 2>&1; then
+ fail "expected mounted auth + skip without usePD to be refused"
+else
+ ok
+fi
+assert_not_ran "start-hugegraph.sh"
+cleanup
+
+echo "==> skip without auth is unaffected by the usePD requirement"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+assert_no_file "docker/init_complete"
+cleanup
+
+echo "==> env override of a colon-form property leaves one canonical key"
+new_install
+echo "init_store.enabled:false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_prop_defined_once "init_store.enabled"
+assert_prop "init_store.enabled" "true"
+assert_ran "init-store.sh"
+cleanup
+
+echo "==> env override of a whitespace-form property leaves one canonical key"
+new_install
+echo "init_store.enabled false" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=true
+assert_prop_defined_once "init_store.enabled"
+assert_prop "init_store.enabled" "true"
+assert_ran "init-store.sh"
+cleanup
+
+echo "==> PASSWORD override of a colon-form auth.admin_pa leaves one key"
+new_install
+enable_pd
+echo "auth.admin_pa:old" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_prop_defined_once "auth.admin_pa"
+assert_prop "auth.admin_pa" "s3cret"
+cleanup
+
+echo "==> commented-out defaults are not treated as definitions"
+new_install
+run_entrypoint -u HG_SERVER_INIT_STORE_ENABLED PASSWORD=s3cret
+# The shipped file ships '#auth.admin_pa=pa' commented; it must stay commented
+# and must not count as an existing definition
+if grep -qxF "#auth.admin_pa=pa" "${INSTALL}/conf/rest-server.properties"; then
+ ok
+else
+ fail "expected the commented '#auth.admin_pa=pa' line to be preserved"
+fi
+cleanup
+
+echo "==> mounted config that already enables auth is not duplicated"
+new_install
+enable_pd
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+ >> "${INSTALL}/conf/rest-server.properties"
+echo "auth.graph_store=hugegraph" >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_not_ran "enable-auth.sh"
+assert_prop_defined_once "auth.admin_pa"
+assert_prop "auth.admin_pa" "s3cret"
+# The mounted config carried only the REST keys, so the YAML block and the auth
+# proxy still have to be applied, or Gremlin would stay unauthenticated
+assert_auth_fully_enabled
+cleanup
+
+echo "==> mounted config with only auth.authenticator still authenticates
Gremlin"
+new_install
+enable_pd
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+ >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_not_ran "enable-auth.sh"
+assert_auth_fully_enabled
+assert_prop "auth.graph_store" "hugegraph"
+cleanup
+
+echo "==> a gremlin-server.yaml without a trailing newline is still valid"
+new_install
+enable_pd
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+ >> "${INSTALL}/conf/rest-server.properties"
+printf 'host: 0.0.0.0' > "${INSTALL}/conf/gremlin-server.yaml"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_auth_fully_enabled
+# The appended block must start on its own line, not glued onto 'host: 0.0.0.0'
+if grep -qxF "host: 0.0.0.0" "${INSTALL}/conf/gremlin-server.yaml"; then
+ ok
+else
+ fail "the last pre-existing line was absorbed by the appended block"
+fi
+cleanup
+
+echo "==> a pre-authenticated mount is completed, not duplicated"
+new_install
+enable_pd
+# Everything already in place, as after a restart with the conf dir mounted
+"${INSTALL}/bin/enable-auth.sh"
+: > "${INSTALL}/calls.log"
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false PASSWORD=s3cret
+assert_not_ran "enable-auth.sh"
+assert_auth_fully_enabled
+cleanup
+
+echo "==> a custom authenticator is not held to the usePD requirement"
+new_install
+echo "auth.authenticator=org.example.auth.LdapAuthenticator" \
+ >> "${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+cleanup
+
+echo "==> surrounding whitespace is trimmed, inner whitespace is kept"
+new_install
+printf 'auth.authenticator = %s \n' \
+ "org.apache.hugegraph.auth.StandardAuthenticator" \
+ >> "${INSTALL}/conf/rest-server.properties"
+# Trailing spaces must not stop the value matching the built-in class, or the
+# admin-account requirement below would be skipped for a config that needs it
+if ( cd "${INSTALL}" && env -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false \
+ bash "${ENTRYPOINT}" ) >/dev/null 2>&1; then
+ fail "expected a padded auth.authenticator to still be recognized"
+else
+ ok
+fi
+cleanup
+
+echo "==> a value containing spaces survives the round trip"
+new_install
+enable_pd
+run_entrypoint HG_SERVER_INIT_STORE_ENABLED=false 'PASSWORD=two words'
+assert_prop "auth.admin_pa" "two words"
+assert_prop_defined_once "auth.admin_pa"
+cleanup
+
+echo "==> remote auth is exempt from the usePD requirement"
+new_install
+echo "auth.authenticator=org.apache.hugegraph.auth.StandardAuthenticator" \
+ >> "${INSTALL}/conf/rest-server.properties"
+echo "auth.remote_url=127.0.0.1:8899" >>
"${INSTALL}/conf/rest-server.properties"
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+assert_ran "start-hugegraph.sh"
+assert_not_ran "init-store.sh"
+cleanup
+
+echo "==> set_prop preserves the config file's inode and mode"
+new_install
+chmod 600 "${INSTALL}/conf/rest-server.properties"
+before_inode=$(ls -i "${INSTALL}/conf/rest-server.properties" | awk '{print
$1}')
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+after_inode=$(ls -i "${INSTALL}/conf/rest-server.properties" | awk '{print
$1}')
+after_mode=$(stat -c '%a' "${INSTALL}/conf/rest-server.properties" 2>/dev/null
\
+ || stat -f '%Lp' "${INSTALL}/conf/rest-server.properties")
+# Rewriting in place matters: a single-file bind mount cannot be replaced by
+# rename, and a rename would drop the mode protecting auth.admin_pa
+if [[ "${before_inode}" == "${after_inode}" ]]; then ok; else fail "inode
changed"; fi
+if [[ "${after_mode}" == "600" ]]; then ok; else fail "mode became
${after_mode}, expected 600"; fi
+assert_prop "init_store.enabled" "false"
+cleanup
+
+echo "==> no scratch file is left behind"
+new_install
+run_entrypoint -u PASSWORD HG_SERVER_INIT_STORE_ENABLED=false
+if compgen -G "${INSTALL}/conf/rest-server.properties.tmp*" >/dev/null; then
+ fail "a set_prop scratch file was left behind"
+else
+ ok
+fi
+cleanup
+
+echo "==> the entrypoint's auth block has not drifted from enable-auth.sh"
+# The entrypoint reapplies what bin/enable-auth.sh would have done when it
+# cannot run the script itself, so the two must name the same classes and keys
+ENABLE_AUTH="${SELF_DIR}/../../src/assembly/static/bin/enable-auth.sh"
+if [[ -f "${ENABLE_AUTH}" ]]; then
+ for token in "authentication: {" "WsAndHttpBasicAuthHandler" \
+ "tokens: conf/rest-server.properties" "auth.graph_store" \
+ "HugeFactoryAuthProxy"; do
+ if grep -qF "${token}" "${ENABLE_AUTH}" && \
Review Comment:
Fixed in `95bfe451`. The drift test now checks every required invariant
independently in both `enable-auth.sh` and `docker-entrypoint.sh`; removing a
token from either side fails the suite.
##########
PR_DESCRIPTION.md:
##########
@@ -0,0 +1,152 @@
+<!-- Body below is shared by both PRs. Prepend per-repo before posting:
Review Comment:
Removed in `95bfe451`. `PR_DESCRIPTION.md` is absent from the final branch,
and both live PR descriptions now carry the synchronized problem, solution,
scope, and validation details.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]