feat(runtime): var.encrypt opts a runtime into an encrypted /var - #216
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an opt-in configuration flag to enable encrypted /var per runtime, and wires the CLI to include the required packages and emit an initramfs marker that the initrd uses to activate the feature.
Changes:
- Extend
VarConfigwithruntimes.<name>.var.encryptand add helpers/tests for parsing and package-set unioning. - Update initramfs/rootfs package selection to union in
cryptsetup-var/cryptsetup-var-udevwhen any runtime opts in. - Update
runtime buildscript generation to write/etc/avocado/var-encryptinto the runtime’s initramfs work copy; add docs and changelog entry.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/utils/config.rs |
Adds var.encrypt, package-set union logic, and unit tests. |
src/commands/runtime/build.rs |
Writes the per-runtime initramfs marker and adds script-generation tests. |
docs/features/encrypted-var.md |
Documents how to enable and what the CLI/device do. |
CHANGELOG.md |
Notes the new runtimes.<name>.var.encrypt feature. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
2cf9521 to
56c2fc0
Compare
| }; | ||
| let mut names: Vec<String> = runtimes | ||
| .iter() | ||
| .filter(|(_, r)| r.target.is_none() || r.target.as_deref() == Some(target)) |
There was a problem hiding this comment.
problem: r.target.is_none() matches every target, not default_target as the doc comment above states. So an omitted target: leaks the opt-in across targets: with default_target: jetson-orin-nx, an untargeted opted-in prod and a qemu-targeted sibling dev, this returns ["prod"] for both targets — confirmed on this head, jetson=["prod"] qemu=["prod"], and get_initramfs_packages(Some(&parsed), "qemux86-64") then contains cryptsetup-var.
avocado install --target qemux86-64 (or building dev) therefore asks a feed that does not publish it — the failure the target filter exists to prevent, reached by omitting target: rather than declaring a different one.
.or(self.default_target.as_deref()) before falling through to match-any makes the code do what the comment says.
There was a problem hiding this comment.
Fixed in b04aba1: the filter is now r.target.as_deref().or(self.default_target.as_deref()).is_none_or(|t| t == target), so an omitted target: means default_target; match-any only when neither is set. The foreign-build-target guard in create_build_script uses the same fallback, so an untargeted opted-in runtime built with --target ≠ default_target refuses instead of silently dropping marker and packages. test_var_encrypt_union_is_scoped_to_the_sysroot_target now has your exact case (default_target: jetson-orin-nx, untargeted prod, qemu dev → jetson=[prod], qemu=[]) plus the neither-set case.
| initramfs_filesystem: &str, | ||
| post_install: Option<&str>, | ||
| permissions_section: &str, | ||
| var_encrypt: bool, |
There was a problem hiding this comment.
CI's red is base drift, not this change. main's 7ccaec1 (#219) added test_ownership_is_hashed_iff_the_image_records_it, which calls this function with the old four arguments; the branch's merge base predates it. Different lines, so no textual conflict — GitHub still says MERGEABLE while the merge ref fails to compile, which takes out Run Tests (at the Clippy step, before Build) and Windows compile check with one E0061 at image.rs:705.
Merging origin/main here is a clean 3-file merge and yields exactly that one error. Passing false at that call site — the standalone image path, no runtime to opt in — clears it: cargo clippy --all-targets --all-features -- -D warnings clean, suite 1692 passing. Verified locally.
Adds `encrypt: Option<bool>` to VarConfig. When any runtime sets it, get_initramfs_packages() gains `cryptsetup-var` and get_rootfs_packages() gains `cryptsetup-var-udev` (the sysroots are target-scoped and shared, so the package set is the union; siblings carry the package dormant). For the opted-in runtime, create_build_script() writes /etc/avocado/var-encrypt into its initramfs work copy - the marker the initrd keys on. The var image and provisioning are untouched: the flashed btrfs is encrypted in place on first boot by meta-avocado's cryptsetup-var, so seeded content survives. /etc/avocado-security-capabilities is deliberately not written by the cli: it declares what the feed's image supports and is owned by the feed. runtime.<r>.var already feeds the runtime build stamp, so no stamp change. Tests: VarConfig parse + package-set union (config.rs), marker present / absent in the generated build script (build.rs). Claude-Session: https://claude.ai/code/session_01AqE5abpz1hSdeM9ZCRLLrf
…ker from typed config, write it after post_install
- get_{rootfs,initramfs}_packages take the sysroot target and union in
cryptsetup-var{,-udev} only for runtimes on that target, so a Jetson
opt-in no longer asks a qemu feed for packages it does not publish.
- The initramfs marker reads the typed runtimes.<r>.var.encrypt, the same
field the union reads, so a target-override-only encrypt is a uniform
no-op instead of a marker with no cryptsetup-var behind it.
- The marker is emitted by generate_initramfs_build_script after the
post_install hook and before the build id, so a hook that rebuilds /etc
cannot drop it.
…lves; refuse a foreign build target Config::var_encrypt_runtimes(parsed, target) replaces runtime_var_encrypt / any_runtime_var_encrypt. It applies the declared-target filter and resolves target-<x>: overrides from the composed YAML via resolve_overrides_in_value, so encrypt is read exactly like var.compression / var.subvolumes / var_files. The sysroot package union and the initramfs marker both read it. runtime build now fails when a runtime opted into var.encrypt for its declared target is built for another one (runtimes.<r>.target does not drive the build target), instead of writing a marker no cryptsetup-var honours.
…t any target var_encrypt_runtimes matched every target for a runtime with no target:, leaking a default_target opt-in into a sibling target's sysroot package set. Fall back to default_target first; match-any only when neither is set. The foreign-build-target guard in runtime build uses the same fallback.
b04aba1 to
282c14e
Compare
Adds
runtimes.<name>.var.encrypt: true. When any runtime sets it, the initramfs package set gainscryptsetup-varand the rootfs set gainscryptsetup-var-udev; for the opted-in runtime,runtime buildwrites/etc/avocado/var-encryptinto that runtime's initramfs work copy — the marker the initrd keys on. The var image and provisioning are untouched: the flashed btrfs is encrypted in place on first boot by meta-avocado'scryptsetup-var(avocado-linux/meta-avocado#309, #310), so seeded content (subvolumes,var_files, primed images) survives. Unset is byte-identical to today./etc/avocado-security-capabilitiesis deliberately not written by the cli — it declares what the feed's image supports and is owned by the feed (shipped as a package by #310).Tests:
VarConfigparse + package-set union (config.rs), marker present/absent in the generated build script (build.rs). Docs:docs/features/encrypted-var.md, CHANGELOG.Verified on a Jetson Orin Nano devkit with the meta-avocado PRs: first boot encrypts
/varin place and enrolls a TPM2 PCR-7 keyslot on the OP-TEE fTPM.https://claude.ai/code/session_01AqE5abpz1hSdeM9ZCRLLrf