fix: make Zallet config readable by the container uid (1000)#46
Merged
Conversation
Zallet runs on a distroless image pinned to user "1000:1000" (no shell,
entrypoint, or capabilities to chown/override at runtime). It reads two
bind-mounted host files, zallet.toml and zallet_identity.txt, whose host
ownership/mode pass straight through. Operators whose host uid \!= 1000 hit:
zallet fatal error: config error: path error: /etc/zallet/zallet.toml:
Permission denied (os error 13)
because both files end up mode 0600 owned by the host uid: setup-network.sh
chmod 600s the age key, and regtest-init.sh rewrites zallet.toml via
mktemp+mv, leaking mktemp's 0600 onto the config.
Decouple host uid from container uid the same way the cookie-permissions
sidecar already does for Zebra's RPC cookie, rather than parametrizing the
pinned uid (which would force operators to also solve data-volume ownership
that distroless Zallet cannot self-heal):
- setup-network.sh: chmod 644 the copied non-secret TOMLs; grant the age key
read to uid 1000 only via `setfacl -m u:1000:r` (stays 0600 otherwise),
with a warning + chmod 644 fallback when setfacl/acl is unavailable.
- regtest-init.sh: chmod 644 zallet.toml after the pwhash rewrite.
- docs + README: document that uid \!= 1000 operators need no uid coordination
for Zallet config; note the acl/setfacl soft prerequisite.
- ci.yaml: reproduce the scripts' permission end-state in regtest staging and
assert a --user 1000:1000 container can read both config files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a startup failure for the distroless Zallet container (pinned to uid/gid 1000:1000) when operator-authored bind-mounted config files are created with restrictive host ownership/modes, by making the generated/copied config readable by uid 1000 and adding CI coverage to prevent regressions.
Changes:
- Update
setup-network.shandregtest-init.shto adjust file permissions after creating/updating Zallet config material. - Document the uid-1000 readability contract and the (soft)
acl/setfaclprerequisite for identity file access. - Extend CI regtest staging to mirror the expected permission end-state and assert uid-1000 readability through a bind mount.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/setup-network.sh | Adjusts permissions for copied TOMLs and adds ACL-based readability for the Zallet identity file. |
| scripts/regtest-init.sh | Restores readable permissions after rewriting zallet.toml via mktemp + mv. |
| README.md | Documents the “no uid coordination required” behavior and ACL fallback guidance for Zallet config. |
| docs/docker-architecture.md | Explains why Zallet needs readable bind-mounted config and how scripts achieve it (incl. ACLs). |
| .github/workflows/ci.yaml | Stages regtest files with the intended perms/ACL and asserts uid-1000 container readability. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address Copilot review feedback on the Zallet config-readability change: - setup-network.sh: skip setfacl when the host uid is already 1000 (the 0600 age key is readable as-is) and fail fast with actionable instructions when uid != 1000 and the ACL cannot be applied (setfacl missing or failing), instead of warning and letting Zallet crash at startup. The age key is never auto-widened to 0644. - ci.yaml: install the acl package before applying the ACL so the uid-1000 readability assertion does not depend on the runner image. - README.md / docs/docker-architecture.md: document the uid-1000 skip and the new fail-fast behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Problem
On a bare-metal regtest bring-up where the host user's uid ≠ 1000, Zallet dies at startup:
Zallet bind-mounts two operator-authored host files read-only (
config/<network>/zallet.tomlandconfig/<network>/zallet_identity.txt). Bind mounts pass host ownership/mode straight through, both files end up mode 0600 owned by the host uid, and the Zallet container runs as uid 1000 — so it can't read them.Why the files are 0600:
zallet_identity.txt—setup-network.shrunsrage-keygenthenchmod 600.zallet.toml(regtest) —regtest-init.sh'supdate_zallet_rpc_pwhash()rewrites the file viamktemp+mv;mktempcreates the temp file mode 0600 andmvcarries that onto the config.Why only Zallet hits this: it's the only core service on a distroless image (no shell, entrypoint, or capabilities), so it's pinned with
user: "1000:1000"and runs as that uid from PID 1. Zebra (root entrypoint →setpriv) and Zaino (root +cap_add: [DAC_OVERRIDE, ...]) read their own bind-mounted config regardless of host mode, then drop privileges. Zallet can't.Fix
Decouple host uid from container uid the same way the
cookie-permissionssidecar already does for Zebra's RPC cookie — rather than parametrizing the pinned uid, which would force operators to also solve data-volume ownership that distroless Zallet cannot self-heal.scripts/setup-network.sh—chmod 644the copied non-secret TOMLs; grant the age key read to uid 1000 only viasetfacl -m u:1000:r(stays0600for everyone else), with a warning +chmod 644fallback whensetfacl/aclis unavailable.scripts/regtest-init.sh—chmod 644zallet.tomlafter the pwhash rewrite (fixes the mktemp-0600 leak).docs/docker-architecture.md/README.md— document that uid ≠ 1000 operators need no uid coordination for Zallet config; note theacl/setfaclsoft prerequisite and fallback..github/workflows/ci.yaml— reproduce the scripts' permission end-state in regtest staging and assert a--user 1000:1000container can read both config files (guards the mktemp-0600 regression and validates the ACL-through-bind-mount mechanism end-to-end).Note for existing deployments
Re-running the setup scripts won't retro-fix already-present config files (both scripts skip when the live files exist). On an existing box, run:
Verification
bash -npasses on both scripts;ci.yamlparses as valid YAML.🤖 Generated with Claude Code