Repository navigation
feat(verify): Refuse a boot asset whose digest differs from the bundle - #392
Conversation
b49655d to
2326737
Compare
|
Ready to merge from the review side: three nits, all fixed. Findings:
Checks run (at 8e86624):
Real runtime (macOS, hull v0.1.0-rc29, brig built at 8e86624):
|
8e86624 to
8fdcf3b
Compare
8fdcf3b to
8025458
Compare
8025458 to
8b9df61
Compare
brig checked the signature on the boot bundle, but the kernel and initrd that booted were whatever sat in the asset directory. A kernel swapped there, or left from an older bundle, booted under "boot assets verified". brig now keeps the digest whose signature verified and reads the per-file sha256 from the registry manifest at that digest. Every registry request refuses a redirect to plain http or a redirect loop, as it refuses a plain-http token realm. The two files brig hands the runtime are hashed and compared before the boot. With BRIG_BOOT_ASSETS unset a difference refuses under warn and require. A named directory states it under warn and refuses under require, and on nerdctl the refusal says why the Linux runtime bundle's directory lands there. Digests brig cannot read warn or refuse by mode. The summary names the boot assets only after both files match. When the manifest cannot be read, hull's provenance.json is read instead, and only a record of that digest counts. A registry that answers with a manifest brig refuses gets no fallback. The record sits beside the files it describes, so files that differ from it refuse as a difference, and files that match it are "cannot check", never verified. A relative BRIG_BOOT_ASSETS is made absolute: brig hashes the files from its own working directory, and the runtime opens them from another. Every republish of a bundle tag now refuses installs holding the older bundle, until the two files are deleted and fetched again. This needs a release note. Fixes: #234 Fixes: #168 Signed-off-by: Panagiotis Moustafellos <pmoust@nofire.ai> Signed-off-by: Anastassios Nanos <ananos@nofire.ai>
8b9df61 to
4d258a4
Compare
|
thanks @pmoust! #392 is rebased onto main and approved at 4d258a4. I folded the three fixups and the review points into your commit, so it is one commit now, with your authorship and both sign-offs. What changed:
A manifest brig refuses stays "cannot check": The window between the check and the boot is tracked in #407. #403 sits on top: the Linux runtime bundle's signed record, the fetch by the verified digest, the re-fetch of an older bundle, and the rc13 pin. @pmoust care to review it? The e2e canaries in #360 now run the boot-asset checks of both PRs on real hosts, and they pass over the stack. |
Summary
brig checked the signature on the boot bundle, but the kernel and initrd that booted were whatever sat in the asset directory, so a swapped or stale kernel booted under "boot assets verified". brig now reads the per-file sha256 from the registry manifest at the digest that verified (hull's
provenance.jsonstands in when the registry does not answer) and compares both files before the boot. Stacked on #386.Related issues
Fixes: #234
Refs: #168
Changes
internal/verify/bundle.go: fetch the bundle manifest by digest with net/http, hash it against that digest, read the layer digests. A registry that answers with other bytes, an index, or a plain-http token realm or redirect comes back asManifestRefusedErrorand gets no provenance fallback.internal/wrap/verify.go: hash the kernel and initrd and compare. BRIG_BOOT_ASSETS unset: a difference refuses under warn and require. Named directory: warn states it and boots, require refuses. The Linux runtime bundle note shows only for a named directory on nerdctl.internal/runtime/boot.go:BootAssets.Namedrecords that BRIG_BOOT_ASSETS chose the directory, which decides whether a difference refuses in every mode or only under require.script/smoke.sh: five end-to-end cases for the digest check.Checklist
make allpasses (vet, test, build)script/smoke.shpassesgo test ./... -race, if the change touches concurrency, subprocesses or the daemonbrig run <agent>), if it touches the run, exec or credential pathAlso ran
gofmt -l .(empty),go vet ./...,script/check-tests-kept.sh origin/mainandscript/check-retired-spellings.sh.Real runtime
macOS, hull v0.1.0-rc29, brig built from this branch,
claudeprofile.A named directory (copy of
~/.hull/store/assetswith one byte changed inImage),BRIG_BOOT_ASSETS=<copy> BRIG_VERIFY=require brig sh claude echo booted-A, exit 5 before hull:No Linux runtime bundle text on hull. Then
BRIG_VERIFY=require brig sh claude echo booted-Bwith BRIG_BOOT_ASSETS unset, exit 0:brig rm clauderemoved the sandbox.Rerun at 8e86624 with the same named copy and its
provenance.jsonremoved, so only the registry answer can supply the digests: exit 5 with the same refusal (Image is sha256:fd1a3fb2..., not the sha256:c5dd2578... it lists). The default directory under require printedbrig: image and boot assets verifiedandbooted-B, exit 0.brig rm claude-coderemoved the sandbox. The refused-registry-answer path cannot be triggered against ghcr and is covered by unit tests only. The nerdctl note was not run on Linux.Credentials and the sandbox boundary
This moves promise 1: the kernel a sandbox boots is now bound to the digest whose signature verified, not only checked for existence. docs/security.md is updated in this change. Negative tests: a modified kernel refuses where brig chose the directory, a provenance record for another digest refuses, a refused registry answer is not replaced by a matching record, and a hull run does not name the Linux bundle. Credentials are untouched.