Skip to content

fix: make Zallet config readable by the container uid (1000)#46

Merged
gustavovalverde merged 4 commits into
mainfrom
fix/zallet-config-perms-uid
Jun 19, 2026
Merged

fix: make Zallet config readable by the container uid (1000)#46
gustavovalverde merged 4 commits into
mainfrom
fix/zallet-config-perms-uid

Conversation

@alchemydc

Copy link
Copy Markdown
Collaborator

Problem

On a bare-metal regtest bring-up where the host user's uid ≠ 1000, Zallet dies at startup:

error: zallet fatal error: config error: path error: /etc/zallet/zallet.toml:
I/O operation failed: failed to open file `/etc/zallet/zallet.toml`: Permission denied (os error 13)

Zallet bind-mounts two operator-authored host files read-only (config/<network>/zallet.toml and config/<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.txtsetup-network.sh runs rage-keygen then chmod 600.
  • zallet.toml (regtest) — regtest-init.sh's update_zallet_rpc_pwhash() rewrites the file via mktemp + mv; mktemp creates the temp file mode 0600 and mv carries 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-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.

  • scripts/setup-network.shchmod 644 the copied non-secret TOMLs; grant the age key read to uid 1000 only via setfacl -m u:1000:r (stays 0600 for everyone else), with a warning + chmod 644 fallback when setfacl/acl is unavailable.
  • scripts/regtest-init.shchmod 644 zallet.toml after 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 the acl/setfacl soft prerequisite and fallback.
  • .github/workflows/ci.yaml — reproduce the scripts' permission end-state in regtest staging and assert a --user 1000:1000 container 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:

chmod 644 config/regtest/zallet.toml
setfacl -m u:1000:r config/regtest/zallet_identity.txt

Verification

  • bash -n passes on both scripts; ci.yaml parses as valid YAML.
  • New CI step exercises uid-1000 readability of both config files through a bind mount.

🤖 Generated with Claude Code

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.sh and regtest-init.sh to adjust file permissions after creating/updating Zallet config material.
  • Document the uid-1000 readability contract and the (soft) acl/setfacl prerequisite 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.

Comment thread scripts/setup-network.sh
Comment thread scripts/setup-network.sh Outdated
Comment thread scripts/regtest-init.sh Outdated
Comment thread .github/workflows/ci.yaml Outdated
alchemydc and others added 3 commits June 17, 2026 15:09
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>
@gustavovalverde gustavovalverde merged commit 7c5a4bc into main Jun 19, 2026
7 checks passed
@gustavovalverde gustavovalverde deleted the fix/zallet-config-perms-uid branch June 19, 2026 14:46
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.

3 participants