fix(hugegraph-dist): gate init-store on a dedicated init_store.enabled option - #3119
fix(hugegraph-dist): gate init-store on a dedicated init_store.enabled option#3119bitflicker64 wants to merge 20 commits into
Conversation
…g is false Honor the existing ServerOptions flag in InitStore so distributed/HStore can opt out of local backend and admin init. Unset keeps full init for standalone. Docker maps HG_SERVER_LOAD_FROM_LOCAL_CONFIG to rest-server.properties. Fixes apache#3118
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: Disabling local graph loading also bypasses the Docker password-based admin bootstrap, so the requested credential is never installed. Evidence: exact-head static trace through docker-entrypoint.sh, InitStore.java, and GraphManager.java; 3/3 focused tests passed; visible checks are green.
…d option Replace the graph.load_from_local_config gate with a dedicated init_store.enabled option defaulting to true. That property already means whether GraphManager loads graph definitions from the local directory, and existing configs may materialize its declared false default, so reusing it made an explicit false skip backend and admin initialization for standalone RocksDB/HBase installs. The early return also skipped StandardAuthenticator.initAdminUserIfNeeded, so a Docker PASSWORD was piped into init-store.sh and discarded without creating the admin, leaving the server on the auth.admin_pa default. In skip mode the entrypoint now writes PASSWORD to auth.admin_pa instead, and does not create docker/init_complete since nothing was initialized. The gate is read back from rest-server.properties so a config mounted without the env var behaves the same way. Cover the lifecycle with a main-level test whose graphs directory would fail if the early exit were missed, and entrypoint smoke tests for default init, PASSWORD, skip, skip with PASSWORD, mounted property, and false to true restart. Run those in Docker CI and extend its path filter to the dist docker directory.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3119 +/- ##
============================================
- Coverage 39.13% 1.70% -37.43%
+ Complexity 264 56 -208
============================================
Files 770 748 -22
Lines 65772 63335 -2437
Branches 8725 8291 -434
============================================
- Hits 25741 1081 -24660
- Misses 37272 62162 +24890
+ Partials 2759 92 -2667 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The skip path still fails admin bootstrap for the shipped HStore configuration and has inconsistent option/password handling; backend and plugin registration also occurs before the gate. Evidence: six independent review lanes, exact-head static traces, an uppercase FALSE regression test failing four assertions, and all visible latest-head checks passing.
… is true With init_store.enabled=false the built-in admin account is not created by init-store, and the only other component that creates it is GraphManager.initAdminUserIfNeeded(), reached from loadMetaFromPD() and so gated on usePD. That option defaults to false and the shipped HStore compose files do not set it, so enabling auth in skip mode would start a server that enforces authentication with no account to authenticate against. The entrypoint now refuses that combination and exits with an explanation, and InitStore logs the same condition for non-Docker installs. Skipping without auth is unaffected. usePD=true is already the documented pairing for distributed deployments in the cluster-test rest-server.properties template. Also align the shell with the server's boolean parsing, which is case-insensitive, so HG_SERVER_INIT_STORE_ENABLED=FALSE no longer means skip to Java and run to the entrypoint; read the property through the separators a properties file allows; escape the password for properties serialization so a backslash survives; and register backends and plugins only on the enabled path, since plugin registration runs every plugin's register() and propagates its failures. Add entrypoint coverage for these plus a test that disabled mode leaves backend options unregistered. Note a pre-existing arthas config key mismatch found while checking the option table.
|
Unrelated to this change, but noticed while checking the The arthas options are declared camelCase ( It is invisible today because each shipped value equals its default. It bites on any change, e.g. |
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The mounted-properties override path can create duplicate scalar keys and make init-store or server startup fail. Evidence: six independent review lanes, exact-head static tracing through docker-entrypoint.sh and HugeConfig.java, and all visible latest-head checks passing.
get_prop was widened to read the `=`, `:` and whitespace separators a properties file allows, but set_prop still matched only `key=value`. A mounted `init_store.enabled:false` overridden by HG_SERVER_INIT_STORE_ENABLED therefore gained a second definition instead of being replaced, and a key defined twice collects both values into a list that fails the scalar type check while the config is still loading. Both init-store and server startup would fail. The same applied to a colon-form auth.admin_pa overridden by PASSWORD. set_prop now matches the same separators and collapses every existing definition into one canonical `key=value` line, leaving comments alone. It is written in awk, so the key and value are matched and emitted literally and no regex escaping is involved. Cover colon-form and whitespace-form overrides asserting a single remaining definition, a colon-form auth.admin_pa override, and that commented defaults are not treated as definitions. Add a test loading a duplicated key through HugeConfig to pin why the collapse is required.
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The disabled init path can still report success for an unusable auth configuration, and two Docker config rewrite paths can fail or corrupt mounted/protected configs. Evidence: six independent exact-head review lanes; entrypoint smoke tests passed 57 assertions with 2 macOS-only skips; git diff --check passed; codecov/patch and codecov/project are failing.
…onf in place Four fixes to the skip path. InitStore returned zero for a configuration it had just reported as unusable: init_store.enabled=false with an authenticator set and usePD false means no component creates the built-in admin. Tarball and init-job callers see only the exit status, so they continued into an auth-enabled server nobody could reach. It now fails. Remote auth is exempt, since the auth manager is then an RPC client and StandardAuthenticator only bootstraps a local one; the entrypoint guard gains the same exemption, which it was missing. set_prop replaced the config by rename. A single-file bind mount cannot be replaced that way, so startup aborted under set -e, and the rename also discarded the original mode, which matters where auth.admin_pa is written. It now truncates and rewrites in place, and creates its scratch file under umask 077. On the skip path enable-auth.sh ran unconditionally, and it appends its keys without checking, so a mounted config that already enabled auth ended up with auth.authenticator and auth.graph_store defined twice, which the config parser rejects. It now runs only when auth is not configured yet, and both keys are collapsed to single definitions afterwards. The test guard that skipped two cases without GNU sed was stale once set_prop moved to awk. Removed, so those cases run everywhere. Cover the exit status and the remote-auth exemption in the CLI test, and add entrypoint cases for a mounted auth config, the remote-auth exemption, inode and mode preservation, and scratch file cleanup.
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The skip path can leave Gremlin authentication incomplete and rejects supported custom authenticators. Evidence: six independent exact-head review lanes, static traces through enable-auth.sh and HugeAuthenticator.loadAuthenticator(), 74/74 entrypoint assertions passing, git diff --check passing, and all visible latest-head workflows passing.
…n check enable-auth.sh also adds the authentication block to gremlin-server.yaml and switches hugegraph.properties to the auth proxy. Skipping it whenever auth.authenticator already existed left both undone, so a mounted config that set only that key could start with REST auth on and Gremlin open. Apply each of the three changes only where it is missing instead, and point the Gremlin block at whichever authenticator the REST config names. The admin-account requirement now applies only to the built-in authenticator and its subclasses. auth.authenticator takes any implementation class, and a custom LDAP/OIDC/plugin one manages identities elsewhere, so requiring usePD for it rejected a deployment that works. get_prop no longer deletes whitespace inside a value, only around it, as the properties parser does.
41259fb to
0dd5a7a
Compare
- get_prop uses `sed -E` rather than `-r`, which only GNU sed documents - the appended gremlin-server.yaml block no longer glues itself onto a last line that has no trailing newline - the option description, the InitStore comment and docker/README no longer claim every authenticator needs the built-in admin account, which stopped being true when the check was narrowed - InitStoreConfigTest is imported in suite order in UnitTestSuite - a test asserts the entrypoint's inlined auth block still names everything bin/enable-auth.sh does, so the two cannot drift unnoticed
|
Did a self-review pass over the whole diff and pushed 090763a with the follow-ups, no behaviour change beyond the two fixes below:
The rest is text and hygiene: the option description, the Since the entrypoint reapplies what Local run: 106 assertions across 30 entrypoint cases, unit suite 626 tests green on JDK 11, rat and editorconfig clean. |
# Conflicts: # hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The skip path still has inconsistent admin-bootstrap decisions across Docker and Java, and several auth configurations can start unusable or incompletely protected. Evidence: six independent exact-head review lanes, static traces through docker-entrypoint.sh, InitStore, GraphManager, and StandardHugeGraph; 106/106 entrypoint assertions and the focused Java test passed; git diff --check passed; visible Actions passed but Codecov statuses failed.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 14 changed files in this pull request and generated no new comments.
Suppressed comments (1)
hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/auth/StandardAuthenticator.java:240
- StandardAuthenticator.initAdminUserIfNeeded() only checks whether auth.authenticator is empty, but it doesn't verify that the configured authenticator is actually StandardAuthenticator (or a subclass). If this helper is invoked with a config that sets a custom/local authenticator, it can still open the auth graph and potentially create HugeGraph's built-in admin account even though that authenticator doesn't use it. Add a defensive check so the method no-ops unless auth.authenticator resolves to StandardAuthenticator (or subclass).
HugeConfig config = new HugeConfig(confFile);
String authClass = config.get(ServerOptions.AUTHENTICATOR);
if (authClass.isEmpty()) {
return;
}
config.addProperty(INITING_STORE, true);
auth.initAdminUser(config, password, fromConfig);
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The current head still has upgrade and authentication-consistency regressions, plus configuration durability and deployment-hardening gaps. Evidence: six independent exact-head review lanes; static traces through docker-entrypoint.sh, ConfigTool, and init-store.sh; 217/217 entrypoint assertions passed; Actions passed while codecov/patch and codecov/project failed.
- migrate verified legacy auth volumes without old secrets - fail closed on REST and Gremlin auth mismatches - protect config rewrites and reject include flattening - read administrator passwords from standard input - verify read-only mounts in Docker CI
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The latest head still treats aligned authentication configuration as proof that a legacy administrator exists, so a no-auth volume later configured for authentication can skip bootstrap without a usable admin. Evidence: six independent exact-head lanes; test-docker-entrypoint.sh:419-428 creates only init_complete plus enable-auth.sh configuration and asserts init-store is not run; existing discussion_r3687911099 covers the blocker.
…asks for The Docker auth bootstrap and the ConfigTool it depends on grew into a second change sharing this branch, which made the diff hard to review as one idea. They move to a follow-up, leaving the option, the gate, the env mapping and the init-flag guard here. Review fixes carried over: - init-store.sh propagates InitStore's exit status. The trailing "Initialization finished." echo made the script exit 0 no matter what, so a refused configuration let the entrypoint start a server nobody could log in to. - The entrypoint normalizes HG_SERVER_INIT_STORE_ENABLED and rejects values HugeConfig would refuse, instead of recording an initialization that never happened. - The admin bootstrap check no longer requires auth.admin_pa, which nothing in the container writes. usePD and the hstore auth graph stay. - PASSWORD is documented as having no effect once init-store is skipped, and the entrypoint warns when both are set.
|
Rescoped this PR. It had grown to ~2,900 lines doing two unrelated things: the init-store gate #3118 asked for, and a rewrite of the Docker auth bootstrap built on a new What is left here is 496 lines across 7 files — the option, the gate, the env mapping, and the init-flag guard. Your 7 open threads are all on code that moved out. I have replied on each pointing at #3133 instead of resolving them, so they stay findable there. Two things worth flagging from re-reading it:
The admin-bootstrap check no longer requires 604 tests, 0 failures on JDK 11, checkstyle clean. Description updated to match. Not run: Mirrored to hugegraph#171. |
- Name the actual boolean converter in the entrypoint comment. "BooleanUtils" alone is ambiguous: commons-lang3 accepts 0 and 1, the commons-lang 2.x one reached through commons-configuration 1.x PropertyConverter does not, and the accepted spellings follow the latter. - Say that the init_complete guard applies to the environment variable, not to the property set directly in a mounted rest-server.properties. - Move InitStoreConfigTest into its own cmd group in UnitTestSuite instead of the core one. - Trim test comments closer to the density of the surrounding suite.
|
Follow-up push (5268091) after another review pass. No behaviour change — 604 tests, 0 failures, checkstyle clean.
Two things I would rather raise myself than have you find them:
One branch is effectively unreachable and untested. Also for ordering: #3132 (the Helm chart) injects |
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: The disabled-init path can expose a predictable administrator credential and can persist an incorrect initialization marker for mounted configurations. Evidence: exact-head static traces through InitStore, GraphManager, and docker-entrypoint.sh; 12/12 focused tests passed; all visible latest-head workflows passed.
…cord init by result Two findings from review of the disabled init-store path. The check now also requires an explicit non-empty auth.admin_pa. Without it a fresh built-in-auth deployment reaches GraphManager.initAdminUserIfNeeded() on the PD startup path, which creates the admin from that option's public 'pa' default while Docker PASSWORD is discarded on this path. The accepted-HStore test had no admin_pa, so it pinned exactly that case. The docker init marker is now written by init-store itself, and only when it initialized. The entrypoint could only decide from its own environment variable, so a config mounted with init_store.enabled=false was still recorded as initialized, and setting it back to true later skipped initialization for good. The marker path is passed in, so callers that do not want one are unaffected.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (2)
docker/README.md:178
- The README still says the entrypoint writes
docker/init_completeonly whenHG_SERVER_INIT_STORE_ENABLED=falseis set, but the entrypoint no longer writes the marker at all;InitStorewrites it only after a successful enabled run. As written, this incorrectly implies that disabling via a mountedrest-server.propertiesbehaves differently from disabling via the env var.
> With the variable set to `false`, the entrypoint deliberately never writes
> `docker/init_complete`, so a later re-enable is still able to initialize.
> Setting the property directly in a mounted `rest-server.properties` does not
> get that guard.
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:74
HG_SERVER_INIT_STORE_ENABLEDis mapped intorest-server.propertiesviaset_prop, butset_proponly detects existingkey=definitions. If a mountedrest-server.propertiesuses:as the separator (valid for Java properties), this will append a second logical definition, andHugeConfigwill load it as a list and fail (as the unit test documents). Consider teachingset_propto match both=and:separators (and ideally whitespace separators) when deciding whether to replace vs append.
INIT_STORE_ENABLED=$(printf '%s' "${HG_SERVER_INIT_STORE_ENABLED:-}" |
tr -d '[:space:]' | tr '[:upper:]' '[:lower:]')
case "${INIT_STORE_ENABLED}" in
"" | y | t | yes | on | true | n | f | no | off | false) ;;
*) log "ERROR: invalid HG_SERVER_INIT_STORE_ENABLED" \
"'${HG_SERVER_INIT_STORE_ENABLED}'"
exit 1 ;;
esac
[[ -n "${INIT_STORE_ENABLED}" ]] && \
set_prop "init_store.enabled" "${INIT_STORE_ENABLED}" "${REST_SERVER_CONF}"
…moved init-store writes the marker now, so a run disabled by a mounted property records nothing just as one disabled by the environment variable does. The text still described the entrypoint owning it, and still claimed the mounted case was unguarded, which the change removed.
|
Both Copilot points, one taken and one deferred. README (taken, ff1325a). Correct catch, and it was my own stale text. After the marker moved into
Leaving it here for three reasons. It is pre-existing and not specific to this option: the same That is the same reasoning behind moving |
imbajin
left a comment
There was a problem hiding this comment.
Blocking: yes. Summary: Existing initialization markers can bypass the new disabled-path safety checks, and PD administrator creation failures are still swallowed. Evidence: six independent exact-head review lanes, static traces through docker-entrypoint.sh, InitStore, and GraphManager; InitStoreConfigTest passed 13/13; git diff --check passed; visible exact-head checks are green.
An existing docker/init_complete marker skipped bin/init-store.sh entirely, so the disabled path's fail-closed check never ran after an upgrade or a configuration change: built-in auth could start without usePD, an HStore auth graph, or an explicit auth.admin_pa. The entrypoint now runs init-store on every startup and hands it the marker path — absolute, so the in-Java check agrees with the shell guard — and init-store consults the marker only after the gate check, so a marker left by an earlier release or an earlier enabled run skips re-initialization and nothing else. An enabled run with the marker present returns before backend registration and stdin, keeping the restart branch free of wait-storage and PASSWORD. GraphManager.initAdminUserIfNeeded() swallowed every failure from metaManager.createUser() and initDefaultGraphSpace(), so a PD write, permission or validation failure still started a server with no usable administrator — the exact outcome the gate check refuses. Only the already-exists case is benign now, judged by re-reading the user rather than by matching the message (the probe failure is attached as suppressed if the re-read itself fails); everything else aborts startup. Writing the completion marker also tolerates a concurrent container having recorded it first. Regressions: an existing marker with an invalid built-in-auth disabled configuration still fails closed; an existing marker with the gate enabled returns before the graph scan; the admin is created when absent, kept without failing when already present, and a non-duplicate creation failure propagates with its cause.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (3)
hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/cmd/InitStore.java:175
recordInitComplete()treatsFileAlreadyExistsExceptionas success unconditionally. If the marker path already exists but is a directory (or other non-regular file), this will silently “record” completion even though future runs (with the tightened check) won’t consider it a valid marker. It’s safer to accept the already-exists case only when the existing entry is a regular file.
try {
Files.createFile(path);
} catch (FileAlreadyExistsException e) {
// A concurrent container finishing its own successful init has
// already recorded it, which is the same outcome
}
hugegraph-server/hugegraph-dist/docker/docker-entrypoint.sh:74
- The
HG_SERVER_INIT_STORE_ENABLEDmapping relies onset_prop, which only replaces existing definitions written askey=.... Java.propertiesalso allowkey:...and whitespace separators; with a mountedrest-server.propertiesthat usesinit_store.enabled: false, this will append a second logical definition instead of replacing, andHugeConfigwill then load the key as a list and fail the boolean type check. Consider deleting any existinginit_store.enabledline (regardless of separator) before appending the normalized value.
[[ -n "${INIT_STORE_ENABLED}" ]] && \
set_prop "init_store.enabled" "${INIT_STORE_ENABLED}" "${REST_SERVER_CONF}"
hugegraph-server/hugegraph-dist/src/main/java/org/apache/hugegraph/cmd/InitStore.java:151
presentInitCompleteMarker()treats any existing filesystem entry as a completion marker (Files.exists). If the configured path exists but is a directory (or other non-regular file), init-store will incorrectly skip initialization. Consider requiring a regular file viaFiles.isRegularFile(...)so the marker can’t be accidentally satisfied by a directory.
This issue also appears on line 170 of the same file.
private static String presentInitCompleteMarker() {
String marker = configuredInitCompleteMarker();
if (marker != null && Files.exists(Paths.get(marker))) {
return marker;
}
return null;
Fixes #3118
Kept in sync with hugegraph#171
Problem
InitStorealways performs local initialization: it scansconf/graphs,initializes non-HStore backends, and bootstraps the built-in admin account.
That is correct for standalone and tarball deployments, but a PD/HStore
deployment already owns its metadata, and its Kubernetes pods cannot rely on
the container-local
docker/init_completeflag surviving a restart.Solution
A dedicated
init_store.enabledoption, defaulting totrue, so existingstandalone and tarball configurations keep the current path unchanged.
Distributed deployments set it to
false— orHG_SERVER_INIT_STORE_ENABLED=falsein the server container — to skip backend and plugin registration, the graph
initialization scan, and local admin initialization.
#3118 proposed reusing
graph.load_from_local_config. A dedicated option wasused instead: that flag governs whether
GraphManagerloads graphs from./conf/graphs, which is a different question from whether init-store shouldrun, and overloading it would have coupled two unrelated decisions.
Scope
In scope
ServerOptionsinit_store.enabled,Boolean, defaulttrueInitStoreGraphManagerinitAdminUserIfNeededpropagates every failure except already-exists, judged by re-reading the userbin/init-store.shInitStore's exit statusdocker-entrypoint.shHG_SERVER_INIT_STORE_ENABLEDonto the property, validates it, hands the marker path to Java, and runs init-store on every startupdocker/README.mdInitStoreConfigTest, 15 cases;GraphManagerAdminInitTest, 3 casesBehaviour, by deployment shape
trueusePD=true+ HStore auth graph + explicitauth.admin_paauth.admin_paon the PD pathDeliberately out of scope
ConfigTool— split to [Bug] Docker entrypoint auth bootstrap is unsafe for mounted and upgraded configs #3133. Seven reviewthreads on this PR target that code and are answered there.
cannot implement the Java-properties grammar
HugeConfiguses. Solved hereby having
init-storeown the marker instead; a general config-reading CLIbelongs to [Bug] Docker entrypoint auth bootstrap is unsafe for mounted and upgraded configs #3133.
run-native-runtime-smoke-test.sh,start-server.shandtest-start-hugegraph.shall treat any non-zero status frombin/init-store.shas fatal.auth.admin_paapplies only whenthe account is first created.
graph.load_from_local_config,GraphManagerbehaviour, Helm job structure,shipped Compose
usePDdefaults.Downstream
#3132 (Helm chart) injects
HG_SERVER_INIT_STORE_ENABLED=falseand must landafter this PR. It also has to write
auth.admin_pa, which it does not today —commented there.
Fail-closed check
With the gate off and the built-in authenticator selected, the admin is created
on the PD startup path by
GraphManager.initAdminUserIfNeeded(conf.get(ADMIN_PA)).Creation failures there now abort startup; only the already-exists case is
ignored, judged by re-reading the user.
Only an HStore auth graph reads that account back, and
ADMIN_PAdefaults tothe public value
pa. DockerPASSWORDnever reaches this path —init-storereads it from stdin and the disabled path returns first.
So the disabled path refuses unless
usePD=true, the auth graph uses HStore,and
auth.admin_pais explicitly set and non-empty. Remote auth and customauthenticators are exempt. Refusing is what keeps a deployment from coming up
with a publicly known administrator credential.
bin/init-store.shpropagates the exit status for this to mean anything: itstrailing
echo "Initialization finished."previously made the script exit0regardless, so a refusal was swallowed and the entrypoint started the server
anyway.
Completion marker
init-storewritesdocker/init_completeitself, and only after it hasinitialized. The entrypoint supplies the path and no longer decides. Deciding
in shell meant reading the environment variable, which says nothing about a
config mounted with
init_store.enabled=falsealready set — that was recordedas initialized, and setting it back to
truelater skipped initializationpermanently. Nothing is written when no caller supplies a path, so the tarball
path is unaffected. init-store also reads the marker — after the disabled
gate — so an existing one skips re-initialization but never the fail-closed
check, and the entrypoint runs init-store on every startup to make that hold.
Validation
JDK 11:
Tests run: 610, Failures: 0, Errors: 0, Skipped: 1(theskip is pre-existing). Checkstyle clean in all modules.
bash -non both changed scripts.echo "$PASSWORD" | ./bin/init-store.shunder
set -euo pipefail.false/FALSE/" false"/No/OFF/true/t/YES,unset, and the rejected
0/1/disabled. The accepted set is exactly whatcommons-lang 2.x
BooleanUtilstakes, reached through commons-configuration1.x
PropertyConverter— note that is not commons-lang3, which would alsoaccept
0and1.Not covered: a successful enabled run writing the marker needs a live backend.
No end-to-end container build was run — entrypoint changes are verified by
extracted-script simulation and code reading.
shellcheckwas not available.