Repository navigation
w736: the vault reads systemd's credential file for its key (mode 440 is the ACL mask) - #238
Merged
Merged
Conversation
…t + ACL shows as mode 440) system_status said "key unreadable (... readable by other users (mode 440); chmod 600 it)". For a service that is not root, systemd writes the credential 0400 root-owned with a user ACL entry for the service account; stat shows the ACL mask in the group bits. readKey refused any group bit. LoadCredential has no mode, and a hand chmod does not survive a restart, so readKey now knows the credential: from $CREDENTIALS_DIRECTORY, directly in that folder, regular file, no other bits, no group/other write bit, root or portal-owned, in a folder closed to others. Other key files stay strict. The VM end to end runs the deployed readKey as fff on the live credential after the install and the cold restart. Request: w736 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
bryding
added a commit
that referenced
this pull request
Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Request: w736
TL;DR: the vault key was refused because systemd's credential file reads mode 440. That 440 is an ACL mask, not a grant to a group. Fixed in
readKey: the key that comes from$CREDENTIALS_DIRECTORYis accepted under a stricter, specific rule; every other key file keeps the strict one. Needs a portal deploy.Cause (evidence)
system_status: "Token vault: 0 entries; key unreadable (the vault key /run/credentials/fff-portal.service/fff-vault-key is readable by other users (mode 440); chmod 600 it)". The check isserver/vault.tsreadKey:st.mode & 0o077refuses.src/core/exec-credential.cwrite_credential:fchmod(fd, 0400), then for a service user that is not the manager's:fd_add_uid_acl_permission(fd, uid, ACL_READ)(falling back tofchownonly where the file system has no ACLs).fff-portal.servicerunsUser=fff.setfacl -m u:nobody:rgivesstatmode 440, owner 0:0, andgetfacl:user::r-- user:nobody:r-- group::--- mask::r-- other::---. So 440 is the ACL mask; the group entry is empty. The same machine withsystemd-run -p User=nobody -p LoadCredential=...took thefchownpath (WSL tmpfs): mode 400, owner nobody, which the old check accepts: that is why this only shows on the VM.chmod 600cannot hold (systemd recreates the file at each start) andLoadCredential=has no mode setting.The choice, and why
Two options were on the table: (A)
readKeyunderstands systemd's credential file; (B) the unit or install path gives the credential the mode the code needs. B does not exist: nothing inLoadCredential/SetCredential/LoadCredentialEncryptedsets a mode, and a0600file owned by root would be unreadable forfff(the ACL is what lets it read). AExecStartPrechmod cannot touch the credentials folder, which is read-only for the service. So A.The rule (
credentialProblem, applies only whenkeySourcesays the file is$CREDENTIALS_DIRECTORY/fff-vault-key): directly in that folder afterrealpath(no link out), a regular file, no other bits, no group/other write bit, owned by root or the portal's own uid, in a folder with no other bits owned by the same. Group read bits are not read as a grant there (the mask). Everything else (configvault.keyFile,/etc/fff/vault.key, the same 0440 file anywhere outside the credentials folder) keeps "no group or other bit at all".readKey(file, { credentialsDir, owners }); the signature'splatformargument moved into the options (only caller:Vault.key).Residual: the check cannot see the ACL entries (no ACL API in Node), so it does not prove
group::is empty; it rests on the credentials folder being private to the unit, which systemd arranges (docs/vault.md says this too: same-user shell commands can still read it, the accepted D2 boundary).Tests
server/vault.test.ts(new, POSIX): the credential at 0440 opens throughkeySource+Vault(statusloaded, a Claude token comes back); the same mode viavault.keyFileor as a bare path is refused; 0444/0404/0460, a stranger as owner, an open folder, a symlink out, a file not directly in the folder, are refused; a wrong key is still "not 32 bytes" and its content is not in the message. Run on Linux (WSL, node 23.7): 23 pass, 0 skipped; on Windows it skips.deploy/vm/test/ci-vm-e2e.sh:vault_key_proberuns the deployed release'skeySource/readKeyasfffagainst the live credential of the runningfff-portal.service, prints the file's mode/owner/ACL (MEASURE), and must printKEY-OK 32 bytes, credential=yes. It runs after the install and again after the nightly cold restart (the credential is recreated at that boot).readKey(it would take a CI cycle); the unit test is the red/green proof (the oldreadKeythrows on the 0440 case).After the merge
Needs a portal deploy (
fffctl update), together with #235 (w698) and #234 (w694). Thensystem_statusshould say the vault key is loaded.🤖 Generated with Claude Code