Repository navigation
feat: support kubernetes init containers and least privilege - #34
Merged
Merged
Conversation
Add IGNITY_MODE (full | init | services) to split the init phase and the services phase across two containers sharing files through a volume. No env is handed over, so secrets of the init container stay there. - env: /etc/ignity/envs become non-secret defaults applied before s6-svscan, container env wins and is never written to disk - with-env: passthrough, -k narrows the env, -d loads an envdir - stage2-cmd: the CMD drops to USERMAP_UID/USERMAP_GID - gen-accounts: IGNITY_ACCOUNTS_DIR for read-only root filesystems - fix-perms: never follow symlinks and fail when chown/chmod fail - stage1: s6-rmrf takes no option, fails on a read-only root - stage3: wait for services (-d, not -f), skip the wait at grace time 0 - tests: shared image fixture, default Containerfile, tuple constants, mode and per-capability tests - docs: Kubernetes, least privilege and read-only sections
amannocci
added a commit
that referenced
this pull request
Oct 22, 2026
## What is the change being made? - `stage3` globs the services with `elglob -0`, so a scandir without services expands to nothing instead of the pattern itself. - Each service is waited for in parallel (`forx -p`, one `s6-svwait -D` per service), still bounded by `IGNITY_SERVICES_GRACETIME`. Without services there is nothing to wait for. - Tests: shutdown without services (`test_boot.py`) and with services (`test_services.py`) leaves no `s6-svwait` error. - CI: `astral-sh/setup-uv` and `peter-evans/create-pull-request` are replaced by their `techcode-io/github-actions` proxies (`setup-uv/v1`, `create-pull-request/v1`), as already done for `setup-github-bot`. The `setup-uv` proxy pins uv to 0.13.5 by default. ## Why is the change being made? Downstream images without user services print on every shutdown: ``` s6-svwait: fatal: unable to subscribe to events for /run/ignity/services-state/*: No such file or directory ``` Without `-0`, `elglob` keeps a pattern that matches nothing (`.s6-svscan` is skipped as a dotfile), and `s6-svwait` got the literal `*`. The bug was hidden until #34: the old `eltest -f` check never ran the wait. `astral-sh/setup-uv` and `peter-evans/create-pull-request` are restricted actions: the proxies are the allowed way to use them. ## How has this been tested? - By hand with podman, on an image built from source (`debian:bookworm-slim`): - no services: the error is gone, and the CMD exit code is kept (`sh -c 'exit 3'` gives 3); - with the `services` fixture: clean shutdown, exit code kept. - Not run yet: `uv run poe test`, including the two new tests. The proxies are covered by this PR's CI run, but `dependabot-plus-plus.yml` only runs weekly or by hand (`workflow_dispatch`). ## How has this been documented? No documentation change: the behaviour matches what is already documented. ## Related Issues None.
amannocci
added a commit
that referenced
this pull request
Oct 23, 2026
## What is the change being made? - `stage2-cmd` runs the CMD as PID 1 (root) by default, even when `USERMAP_UID` / `USERMAP_GID` are set. Dropping to `USERMAP_UID:USERMAP_GID` is now opt-in with `IGNITY_CMD_DROP_PRIVILEGES=1` (default `0`). When opted in, the behaviour is unchanged: no drop for `0:0` or a non root PID 1, supplementary groups set to the gid. - `gen-accounts` checks that `USERMAP_UID` and `USERMAP_GID` are digits (`grep -Eqzx "[0-9]+"`, like the `USER` check) instead of `eltest -ge`, which let values like `1000:0` through. - Tests: new `test_cmd_stays_root_with_usermap_by_default`. The tests expecting the CMD to drop (`test_boot.py`, `test_least_privilege.py`) set `IGNITY_CMD_DROP_PRIVILEGES=1`. `test_accounts_reject_non_numeric_ids` also asserts the error message. ## Why is the change being made? The CMD is mostly used to debug or test in the ignity ecosystem, where root is expected, while `USERMAP_*` is mostly set for `perms`. This goes back to the behaviour before 0.1.0 (#34). Users of 0.1.0 relying on the CMD drop have to set `IGNITY_CMD_DROP_PRIVILEGES=1`: worth a line in the release notes. The `gen-accounts` bug was hidden by the drop: with `USERMAP_UID=1000:0`, it wrote `exploit:x:1000:0:1000::...` (gid 0) in the generated passwd, and `test_accounts_reject_non_numeric_ids` only passed because `s6-applyuidgid` then failed on the CMD. ## How has this been tested? - `uv run poe test`: 72 passed, run on top of `feat/split-packages` before moving the commit onto `main`. Not rerun on this branch, CI covers it. - By hand with podman on an image built from source (`debian:bookworm-slim`): `USERMAP_UID=1000:0` and `USERMAP_GID=0:0` are rejected ("must be numbers", exit 1), `1000:1000` still writes the account. ## How has this been documented? - `README.md`: `IGNITY_CMD_DROP_PRIVILEGES` in the parameters table and "How to customize `ignity` behaviour", "How to run the CMD as a non root user", the least-privilege section (`SETUID` row, capabilities note) and the Kubernetes examples. - `.claude/CLAUDE.md`: `stage2-cmd` and the runtime variables. ## Related Issues None.
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.
What is the change being made?
IGNITY_MODE(fulldefault,init,services) splits the init phase and the services phase across two containers (a Kubernetes init container and the main container). They share files through a user-mounted volume, and no env is handed over./etc/ignity/envs/*become non-secret defaults applied befores6-svscan. The container env wins and is never written to disk.with-envis a passthrough by default:-knarrows the inherited env and-dloads an envdir for one process.USERMAP_UID/USERMAP_GIDwhen PID 1 is root.CHOWN,FOWNER,DAC_OVERRIDE,SETUID,SETGID,KILL) and a read-only root filesystem.IGNITY_ACCOUNTS_DIR(gen-accounts) lets an init container generate passwd/group for read-only roots.fix-permsnever follows symlinks and exits non-zero whenchown/chmodfail.stage1no longer passes--tos6-rmrf, which takes no option.stage3waits for services (eltest -dinstead of-f), and a grace time of 0 skips the wait.imagefixture andimage_fixture()factory, defaultContainerfiletemplate,env_args/READ_ONLYhelpers, tuple constants.Behaviour changes:
chown/chmodinfix-permsnow aborts the boot.with-envno longer restores env from a dump. It is POSIXshand needsawkfor-k./run/ignity/envsno longer exists.IGNITY_MODEfails the container.CAP_KILL, shutdown never ends (stage3waits for all children).Why is the change being made?
ignity was designed for Docker Swarm, with everything in one container. On Kubernetes the init phase can run in an init container and the services in the main container, so that secrets only given to the init container never reach the main one. The same work lets ignity run with minimal capabilities and a read-only root filesystem, which Pod Security policies ask for.
How has this been tested?
uv run poe test: 67 passed.tests/test_modes.py(two-container flow with a shared volume, failing init, invalid mode) andtests/test_least_privilege.py(minimal set, plus one test per capability showing what breaks without it).with-env -k/-d, CMD privilege drop, account generation, symlink handling).How has this been documented?
IGNITY_SERVICES_GRACETIMEdefault and a/run/ingitytypo..claude/CLAUDE.md: architecture, environment variables and test helpers.Related Issues
None.