Skip to content

feat(server): read the initial admin password from a file - #3334

Closed
arshilkxwork wants to merge 5 commits into
apache:masterfrom
arshilkxwork:feat/admin-password-file
Closed

arshilkxwork wants to merge 5 commits into
apache:masterfrom
arshilkxwork:feat/admin-password-file

Conversation

@arshilkxwork

@arshilkxwork arshilkxwork commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3285.

auth.admin_pa is read through commons-configuration's properties reader. It trims the value, processes backslash escapes and decodes the file as ISO-8859-1, so padded , two\\back or pässword can end up as a different admin password from the one the operator set.

This adds auth.admin_pa_file. It points at a file that the Server reads as raw UTF-8, dropping a leading BOM and one trailing \n or \r\n so files written by echo, a Windows editor or a secret mount work as expected. auth.admin_pa stays as it is for compatibility.

When the file option is set it is used on all three bootstrap paths:

  • the PD startup path (GraphManager.loadMetaFromPD), in place of auth.admin_pa
  • non-persistent backends (GraphManager.checkBackendVersionOrExit), in place of auth.admin_pa
  • init-store with a persistent backend, in place of the stdin prompt

The first two read the file on every start, so it has to stay readable while the option is set. The option description and the README say so.

The reader refuses a missing, unreadable, empty or non-UTF-8 file, anything that is not a regular file (symlinks are followed, so mounted Kubernetes Secrets work), a file over 4096 bytes, and a password longer than 72 bytes in UTF-8, which is all that jBCrypt hashes. Each case is a ConfigException that names the option. The file type is checked before the file is opened, so a FIFO cannot block startup. The reader never falls back to auth.admin_pa or the pa default, because that would create an admin whose password nobody chose. The non-UTF-8 case is refused for the same reason: guessing another charset is how pässword became pässword in the first place. When init_store.enabled=false, the existing check in InitStore.checkAdminBootstrapReachable now accepts the file option as an explicit password and reads it right away, so a bad path fails before the Server starts rather than during PD bootstrap.

If init-store refuses the file, it still closes the auth graph. A failure while closing is added to the file error as a suppressed exception. The BackendException thrown when a non-persistent backend can't create the admin now keeps its cause.

initialAdminPassword and readAdminPasswordFile are added to the Gremlin reflection filter next to initAdminUser and inputPassword.

Docs: docker/README.md and the commented example in the shipped rest-server.properties. The Docker entrypoint already encodes PASSWORD safely since #3327, so it is unchanged. The website config-option table also lists auth.admin_pa; the matching entry is in apache/hugegraph-doc#526.

Left for a follow-up: the Helm chart still refuses padded, backslash and non-ASCII admin passwords. Relaxing that means switching its Server wrapper to mount the Secret and set auth.admin_pa_file, and it depends on the minimum Server image the chart supports, so it belongs in its own PR. The TODO in ServerOptions now says that.

Tests: AdminPasswordFileTest covers precedence, the strings from the issue table, line-break and BOM handling, the 4096-byte and 72-byte limits, directories, FIFOs, symlinks and the other failure cases. Its init-store tests check that the file replaces the prompt and that a refused file still closes the auth graph; they run with an empty stdin, so a fallback to the prompt fails instead of hanging. GraphManagerAdminInitTest and InitStoreConfigTest each get two new cases, with a valid file and with a missing one, for the non-persistent backend path and the disabled init-store check.

Conceptual overview

The file option takes precedence, strictly decodes UTF-8 and rejects invalid input without fallback. It feeds PD, non-persistent backend and init-store bootstrap paths.

Conceptual illustration; not test or release-acceptance evidence.

UTF-8 password-file reading and initial admin bootstrap paths

auth.admin_pa goes through the properties grammar, which trims the value,
processes backslash escapes and decodes it as ISO-8859-1. A padded,
backslashed or non-ASCII password can be stored differently from what the
operator wrote.

auth.admin_pa_file names a file that is read as raw UTF-8, with one trailing
line break dropped. When set, it replaces auth.admin_pa on the PD startup
path and the in-memory backend path, and it replaces the stdin prompt in
init-store. A missing, empty or non-UTF-8 file stops startup with an error
naming the option; the server never falls back to the 'pa' default. With
init-store disabled, init-store reads the file up front so a bad path fails
before the server starts.

Fixes apache#3285
@arshilkxwork

Copy link
Copy Markdown
Contributor Author

Paired website docs: apache/hugegraph-doc#526 (apache/hugegraph-doc#526). It adds auth.admin_pa_file to the EN and CN config-option tables, the authentication guide example and the hstore init-store notes.

@codecov

codecov Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.39394% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.91%. Comparing base (c1e7aba) to head (f7f38ac).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
...g/apache/hugegraph/auth/StandardAuthenticator.java 92.00% 2 Missing and 2 partials ⚠️
...n/java/org/apache/hugegraph/core/GraphManager.java 70.00% 1 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3334      +/-   ##
============================================
+ Coverage     54.32%   56.91%   +2.58%     
+ Complexity     2619      130    -2489     
============================================
  Files           770      514     -256     
  Lines         67937    49791   -18146     
  Branches       9210     7215    -1995     
============================================
- Hits          36908    28338    -8570     
+ Misses        27445    18550    -8895     
+ Partials       3584     2903     -681     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

init-store prompts for the admin password on stdin when the backend is
persistent. Pin that a configured auth.admin_pa_file replaces the prompt
and the admin is created with the file's password.
Strip a leading UTF-8 BOM. PowerShell and some Windows editors write
one, and if kept it becomes an invisible first character of the admin
password. Read at most 4096 bytes, so a path that points at a log or a
device fails instead of being loaded whole, and report an invalid path
as a ConfigException.

When the file is refused, init-store now closes the auth graph and
keeps the file error even if the close also fails. The
BackendException raised when a non-persistent backend can't create the
admin now carries its cause.

Document that PD and non-persistent backends read the file on every
start, and that a disabled init-store still needs this option or an
explicit auth.admin_pa.

The init-store tests run with an empty stdin, so falling back to the
prompt fails instead of hanging. New tests cover the BOM, the size cap,
closing the graph on a refused file, and the GraphManager
non-persistent path.

@bitflicker64 bitflicker64 left a comment

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.

Blocking: no. Summary: The file reader, its precedence over auth.admin_pa and the three bootstrap call sites match the description, and I found no correctness or compatibility problem in them; one minor note on password length. Evidence: full diff of c1e7aba..128c0b3 read with the surrounding StandardAuthenticator, GraphManager, InitStore and AuthenticationFilter code; the 72-byte behaviour was reproduced with jbcrypt 0.4 (BCrypt.checkpw). At review time the HugeGraph-Server CI jobs cluster-test, docker-build, docker-bake-check, deployment-contracts and pd_store/struct were still queued, and the PD startup call in GraphManager.loadMetaFromPD has no unit test, so that path rests on reading the code.

@imbajin imbajin left a comment

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.

Blocking: yes. Summary: The password reader opens arbitrary paths before enforcing its size bound; a FIFO with no writer can hang startup. Evidence: Files.newInputStream() opens the configured path before the bounded read. Reject non-regular sources while following mounted-secret symlinks, then apply the size limit.

Check the path's attributes before opening it and refuse anything that
is not a regular file. Opening a FIFO blocks until a writer connects,
so a wrong path could hang startup before the size limit applied.
Symlinks are followed, so a Kubernetes Secret mounted through its
..data links still works.

Refuse a password longer than 72 bytes in UTF-8. jBCrypt hashes only
the first 72 bytes, so a longer secret would also accept any password
sharing that prefix. The 4096-byte limit still bounds how much of the
file is read.

Tests cover a directory, a FIFO (with a timeout so a regression fails
instead of hanging), a symlink to a regular file, and the 72-byte limit
for ASCII and multi-byte passwords.
arshilkxwork added a commit to arshilkxwork/hugegraph-doc that referenced this pull request Oct 13, 2026
apache/hugegraph#3334 now requires a regular file, strips a leading BOM,
and caps the file at 4096 bytes and the password at 72 UTF-8 bytes. Add
those to the option rows and the rest-server.properties comments.

The pages also called the option first-startup only. The password is
applied only when the admin is created, but PD (hstore) and
non-persistent backends read the file on every start, so removing it
after the first boot stops the Server. Say so in the option rows, the
properties comments and the hstore init-store paragraph.

@imbajin imbajin left a comment

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.

Blocking: no. Summary: The non-persistent backend path reads the new password file even when remote authentication disables local admin bootstrap, so an unused missing file can prevent startup. Evidence: GraphManager.checkBackendVersionOrExit resolves the file before StandardAuthenticator.initAdminUser; remote AuthManager makes requireInitAdminUser() return false, while the PD startup path already guards this read.

@bitflicker64 bitflicker64 left a comment

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.

Blocking: no. Summary: The file reader, its precedence over auth.admin_pa and the three bootstrap call sites behave as described at this head, and the two earlier points are fixed (non-regular paths are refused before they are opened, passwords over 72 UTF-8 bytes are refused). One minor gap remains: no test runs the PD call site with the file option set. Evidence: full diff c1e7aba..7ba00bd read with StandardAuthenticator, GraphManager, InitStore, HugeFactoryAuthProxy, HugeSecurityManager, AuthenticationFilter and the Docker entrypoint; on Linux x86_64 with OpenJDK 17.0.20.1, mvn test -pl hugegraph-server/hugegraph-test -am -P unit-test -Dtest=AdminPasswordFileTest,GraphManagerAdminInitTest,InitStoreConfigTest passes the 42 tests of those three classes at 7ba00bd, and the same 42 with the PD call put back to conf.get(ServerOptions.ADMIN_PA); CI run 38446074869 at this head is green (UnitTestSuite: 1146 run, 0 failed in server_memory), and the only red status is codecov/project (54.16%, -0.16% against c1e7aba). The remote-auth comment already open on GraphManager.java line 1883 matches the code and is not repeated here.

Comment on lines +377 to +378
this.initAdminUserIfNeeded(
StandardAuthenticator.initialAdminPassword(conf));

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.

Minor: no test runs this call with auth.admin_pa_file set, and it is the only link between the option and the PD bootstrap, the path it mostly serves (hstore with a mounted Secret). The review of 128c0b3 noted the gap; it still holds at 7ba00bd. loadMetaFromPD() runs only from the constructor when usePD=true, no unit test builds a GraphManager that way, and the hstore API job starts the Server with the option unset.

Evidence (Linux x86_64, OpenJDK 17.0.20.1): with these two lines put back to this.initAdminUserIfNeeded(conf.get(ServerOptions.ADMIN_PA));, AdminPasswordFileTest, GraphManagerAdminInitTest and InitStoreConfigTest still pass all 42 of their tests.

Requested change: move the shouldBootstrapAdmin guard and this call into a small method that takes the config, and cover it in GraphManagerAdminInitTest, which already swaps in an AuthMetaManager over a mock driver: one case with a password file whose content StringEncoding.checkPassword accepts against the stored admin hash, and one with auth.remote_url set and a missing file, which must not be read.

With auth.remote_url set, StandardAuthenticator uses a remote AuthManager
and never creates a local admin, but the non-persistent backend path
still resolved auth.admin_pa_file first. A missing file that would never
be used stopped startup. Skip the lookup in that case, as the PD startup
path already does. Custom authenticators still receive initAdminUser().
@imbajin

imbajin commented Oct 14, 2026

Copy link
Copy Markdown
Member

@arshilkxwork After looking more closely, I don't think adding auth.admin_pa_file is justified by this use case at this stage. We should prioritize common user problems; these uncommon password-encoding cases alone do not justify another configuration mechanism and its lifecycle complexity. The Docker serialization issue has already been addressed by #3327.

For any remaining encoding issues, please prefer a narrowly scoped fix to the existing input path, with round-trip tests and clear documentation. If there is a concrete Kubernetes Secret integration requirement, we can consider a focused adaptation that reuses the existing configuration/authentication flow, rather than introducing a separate general-purpose password-file mechanism. I would defer this feature until that need is demonstrated.

@arshilkxwork

Copy link
Copy Markdown
Contributor Author

Thanks for taking a closer look. Agreed: #3327 already covers the Docker case, and the remaining inputs are uncommon, so I am closing this along with the docs PR, apache/hugegraph-doc#526. If a concrete Kubernetes Secret requirement comes up, it can start from an issue and reuse the existing configuration and authentication flow, as you suggest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Let the Server take the initial admin password without a properties-file round trip

3 participants