pin the base image to a digest, with TTL refresh and audit labels
A tag is a moving pointer, and three things conspired to make the base immutable-in-practice while mutable-in-theory: image_hash() hashed the string "ubuntu:24.04" rather than what it resolves to, ensure_image() returns early when the tag exists, and podman build's pull policy is "missing" -- so even 'build --rebuild' rebuilt on the cached base. A Canonical patch simply never arrived, and nothing said so. Now the tag is resolved to a registry digest (skopeo, falling back to a pull unless AISB_BASE_PULL_FALLBACK=0), recorded per-ref under $AISB_CACHE_DIR/base/ at 0600, and used in FROM *and* in image_hash(): refreshing the base produces a new image instead of mutating one a sandbox may be running. Freshness is a TTL (AISB_BASE_TTL_DAYS=7, 0 every build, -1 never) plus an explicit 'update' / 'update --check'. Two rules keep it usable rather than merely strict. A stale pin beats a mutable tag: offline or with AISB_BASE_AUTO_UPDATE=0 the recorded digest is still used, and only a build with no digest at all is allowed -- announced as UNPINNED, labelled that way, warned about by doctor. And preview modes stay deterministic: --print-* never reach the network, inspect is the surface that asks the machine now. Both images carry aisb.base-digest / .node / .pi-package / .pi-version labels plus /etc/aisb/manifest.txt of resolved apt versions, which answers "which squid was enforcing this?" after the build log is gone. Tags are named after the base instead of the hardcoded u24- prefix. Bug found on the way: POL[base_digest]="$(base_pin ...)" silently produced unpinned labels, because an assignment inside a command substitution is discarded with the subshell. Helpers return text; callers assign. Also fixed: the manifest step used jq on npm's dependency objects as if they were strings, and a missing Node checksum used to skip verification quietly -- it now warns. 311 assertions pass (unit + e2e), shellcheck clean. LLM-Generated: true
This commit is contained in:
@@ -31,6 +31,13 @@ allowlist that the sandboxed process cannot lift.
|
||||
5. **Measure, don't assume, about podman.** Rootless networking varies by version
|
||||
and distro. Run `tests/spike_network.sh` on a new host and update
|
||||
`docs/networking.md` when reality disagrees with the design.
|
||||
6. **Nothing about a build may be silently mutable.** The base enters the
|
||||
Containerfile as a digest, and that digest is part of `image_hash()`; Node is
|
||||
version-pinned and checksum-verified; `pi` is version-pinned. A refresh is an
|
||||
explicit, dated event (`update`); a build with no digest is announced as `UNPINNED`
|
||||
and labelled that way; preview modes must never reach the network. If you add a
|
||||
dependency, decide which of those three it is and cover it in
|
||||
`tests/unit/07_base.sh` or `tests/e2e/10_image.sh`.
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -68,9 +75,15 @@ networking or the image, `make e2e` green too.
|
||||
variables that have no config-file counterpart. Breaking this is the single
|
||||
easiest way to introduce a precedence bug.
|
||||
* `POL[...]` holds resolved policy, `P_*` arrays resolved lists, `OPT_*` raw flags,
|
||||
`CFG_*` config-file values. Do not read raw env vars deep in the code.
|
||||
`CFG_*` config-file values. Do not read raw env vars deep in the code. A helper
|
||||
called as `X="$(helper)"` runs in a subshell: it can return text, but any `POL[..]`
|
||||
it assigns is discarded — return a value and assign it in the caller (see
|
||||
`base_pin_digest()` + `base_ref_of()`).
|
||||
* Preview paths (`--dry-run`, `--print-policy`, `--print-entrypoint`,
|
||||
`--print-containerfile`) must not build images, start containers or write state.
|
||||
`--print-containerfile`) must not build images, start containers, write state or
|
||||
reach the network. That is why digest resolution has a `cache_only` mode, and why
|
||||
`inspect` (asks the machine now) is a separate surface from `--print-*` (which stays
|
||||
deterministic and scriptable).
|
||||
* Tests use `t_name` + `assert_*` from `tests/lib.sh`; each file gets its own
|
||||
`AISB_STATE_DIR`/`AISB_CACHE_DIR`/`AISB_CONFIG_DIR`. Never let a test touch the
|
||||
real `~/.config`, `~/.local/state` or `~/.cache`, and never override `HOME`
|
||||
|
||||
Reference in New Issue
Block a user