Skip to content

Commit 03dc73a

Browse files
committed
feat(security): make composefs verity opt-in via mios.toml [security]
Sealed-image opt-in (gap #1 from the bootc-comparison audit). The existing 40-composefs-verity.sh wrote /usr/lib/ostree/prepare-root.conf unconditionally with composefs in fs-verity mode. fs-verity requires ext4 or btrfs -- on XFS roots (some cloud images, some podman-machine disks) this bricks the deploy. There was no operator-facing escape hatch. Adds [security] table to mios.toml SSOT: composefs_mode = "verity" | "yes" | "off" mask_systemd_remount_fs = true | false - "verity" current behavior (tamper-evident, ext4/btrfs only). Default. - "yes" composefs read-only /usr without verity (works on XFS too; upstream FCOS/bootc default). - "off" skip prepare-root.conf rewrite entirely; honor base image. automation/40-composefs-verity.sh now: - Sources lib/packages.sh for the canonical _resolve_mios_toml chain (so /etc/mios/ + ~/.config/ overrides land here too). - Inlines a minimal _read_mios_scalar awk helper (the same shape as get_packages_from_toml -- top-level scalars, strips quotes + inline comments). - Branches on composefs_mode and renders the matching prepare-root.conf body. The "yes" body keeps the [root] / [etc] transient=false stanzas so the immutable-/usr posture stays intact even without verity. - Only masks systemd-remount-fs.service when verity is selected AND mask_systemd_remount_fs is truthy. The "yes" path uses the upstream mount sequence and doesn't need the mask. - Logs the resolved mode + whether it backed up an existing config. No behavior change for existing deployments: default is "verity", which matches the pre-commit unconditional path. Operators on XFS/cloud substrates can drop a single override into /etc/mios/mios.toml: [security] composefs_mode = "yes" and the next bootc switch flips to the non-verity path without code changes. Verified: bash -n on the rewritten script; tomllib + the awk resolver both round-trip the new [security] table; the resolver also continues to read [hwcaps] cleanly (no regression to the existing toml consumers).
1 parent 6e5d84f commit 03dc73a

2 files changed

Lines changed: 158 additions & 9 deletions

File tree

automation/40-composefs-verity.sh

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,93 @@
11
#!/usr/bin/env bash
2-
# 40-composefs-verity.sh - promote composefs from default (yes) to verity mode
3-
# Tamper-evident root. Requires ext4 or btrfs target FS (NOT xfs).
2+
# 40-composefs-verity.sh -- render /usr/lib/ostree/prepare-root.conf based
3+
# on the operator-tunable [security].composefs_mode knob in mios.toml.
4+
#
5+
# SSOT: usr/share/mios/mios.toml [security].composefs_mode
6+
# (resolved through the documented overlay chain by lib/packages.sh
7+
# + this script's local _read_mios_scalar awk helper).
8+
#
9+
# Modes:
10+
# verity -- composefs in fs-verity mode (tamper-evident root). Default.
11+
# Requires ext4 or btrfs. Also masks systemd-remount-fs.service
12+
# (known-broken on Fedora 42+ with composefs) when
13+
# [security].mask_systemd_remount_fs = true.
14+
# yes -- composefs enabled without verity. Works on XFS too.
15+
# off -- skip prepare-root.conf rewrite entirely; honor base image.
16+
#
17+
# See usr/share/mios/mios.toml [security] prose for the full rationale.
418
set -euo pipefail
5-
source "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh"
19+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20+
# shellcheck source=lib/common.sh
21+
source "${SCRIPT_DIR}/lib/common.sh"
22+
# shellcheck source=lib/packages.sh
23+
# Pulled in for _resolve_mios_toml; the same TOML overlay chain that
24+
# packages.sh uses is what we want for [security] too.
25+
source "${SCRIPT_DIR}/lib/packages.sh"
26+
27+
# _read_mios_scalar <table> <key> -- read a top-level scalar from the
28+
# [<table>] block of the resolved mios.toml. Strips quotes and inline
29+
# comments. Returns empty string when the key is absent.
30+
_read_mios_scalar() {
31+
local table="$1" key="$2" toml_path
32+
toml_path="$(_resolve_mios_toml 2>/dev/null || true)"
33+
[[ -n "$toml_path" && -f "$toml_path" ]] || return 0
34+
awk -v table="$table" -v key="$key" '
35+
/^\[/ {
36+
in_section = 0
37+
line = $0
38+
sub(/^\[/, "", line); sub(/\][[:space:]]*$/, "", line)
39+
gsub(/[[:space:]]/, "", line)
40+
if (line == table) in_section = 1
41+
next
42+
}
43+
in_section {
44+
if (match($0, "^[[:space:]]*" key "[[:space:]]*=")) {
45+
value = $0
46+
sub(/^[^=]*=[[:space:]]*/, "", value)
47+
sub(/[[:space:]]*#.*$/, "", value)
48+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", value)
49+
gsub(/^"|"$/, "", value)
50+
print value
51+
exit 0
52+
}
53+
}
54+
' "$toml_path"
55+
}
56+
57+
MODE="$(_read_mios_scalar security composefs_mode)"
58+
MODE="${MODE:-verity}"
59+
MASK_REMOUNT="$(_read_mios_scalar security mask_systemd_remount_fs)"
60+
MASK_REMOUNT="${MASK_REMOUNT:-true}"
61+
62+
case "$MODE" in
63+
verity|yes|off) ;;
64+
*)
65+
warn "[40-composefs-verity] unknown composefs_mode='${MODE}', falling back to 'verity'"
66+
MODE="verity"
67+
;;
68+
esac
69+
70+
if [[ "$MODE" == "off" ]]; then
71+
log "[40-composefs-verity] composefs_mode=off -- honoring base image's prepare-root.conf"
72+
exit 0
73+
fi
674

775
conf=/usr/lib/ostree/prepare-root.conf
876
if [[ -f "$conf" ]]; then
9-
log "backing up existing $conf -> ${conf}.orig"
77+
log "[40-composefs-verity] backing up existing $conf -> ${conf}.orig"
1078
cp -a "$conf" "${conf}.orig"
1179
fi
1280

13-
cat > "$conf" <<'EOF'
81+
# Render the table according to the requested mode. The [root] / [etc]
82+
# transient = false stanzas are independent of verity vs yes -- they
83+
# enforce immutable / non-tmpfs root and /etc on every composefs path.
84+
log "[40-composefs-verity] writing $conf with composefs mode=${MODE}"
85+
case "$MODE" in
86+
verity)
87+
cat > "$conf" <<'EOF'
1488
# 'MiOS': composefs in verity mode. Tamper-evident root.
1589
# Target filesystems must support fsverity (ext4, btrfs). XFS is NOT supported.
90+
# SSOT: mios.toml [security].composefs_mode = "verity".
1691
[composefs]
1792
enabled = verity
1893
@@ -22,9 +97,32 @@ transient = false
2297
[etc]
2398
transient = false
2499
EOF
100+
;;
101+
yes)
102+
cat > "$conf" <<'EOF'
103+
# 'MiOS': composefs enabled (no verity). Read-only /usr without the
104+
# fs-verity cryptographic chain -- works on every composefs-capable
105+
# filesystem (ext4, btrfs, XFS). Default upstream FCOS / bootc posture.
106+
# SSOT: mios.toml [security].composefs_mode = "yes".
107+
[composefs]
108+
enabled = yes
109+
110+
[root]
111+
transient = false
112+
113+
[etc]
114+
transient = false
115+
EOF
116+
;;
117+
esac
25118

26-
# Mask systemd-remount-fs (known-broken with composefs on F42+)
27-
log "masking systemd-remount-fs.service (composefs interop bug)"
28-
ln -sf /dev/null /etc/systemd/system/systemd-remount-fs.service
119+
# systemd-remount-fs masking: only relevant in verity mode (where the
120+
# composefs/remount-fs interop bug surfaces). The "yes" path uses the
121+
# upstream-default mount sequence and does not need the mask.
122+
if [[ "$MODE" == "verity" && "$MASK_REMOUNT" =~ ^(true|TRUE|1|yes|YES)$ ]]; then
123+
log "[40-composefs-verity] masking systemd-remount-fs.service (composefs/remount interop bug)"
124+
install -d -m 0755 /etc/systemd/system
125+
ln -sf /dev/null /etc/systemd/system/systemd-remount-fs.service
126+
fi
29127

30-
log "composefs verity mode configured"
128+
log "[40-composefs-verity] composefs mode=${MODE} configured"

usr/share/mios/mios.toml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,57 @@ ld_so_hwcaps_autoselect = true
257257
# usr/share/doc/mios/reference/hwcaps.md).
258258
native_rebuild = false
259259

260+
# ----------------------------------------------------------------------------
261+
# [security] -- sealed-image and tamper-evident root knobs.
262+
#
263+
# composefs_mode controls how /usr/lib/ostree/prepare-root.conf is
264+
# rendered by automation/40-composefs-verity.sh:
265+
#
266+
# "verity" composefs in fs-verity mode (default). Tamper-evident
267+
# root: every ostree object is content-addressed and
268+
# cryptographically chained back to a single trusted root
269+
# digest. Requires the target filesystem to support
270+
# fs-verity natively (ext4, btrfs). XFS does NOT support
271+
# fs-verity and will fail to boot if this mode is selected
272+
# on an XFS root.
273+
# "yes" composefs enabled without verity. Same content-addressed
274+
# read-only /usr root, but no cryptographic integrity
275+
# chain on individual objects. Works on every filesystem
276+
# composefs supports (including XFS) and is the upstream
277+
# FCOS / bootc default.
278+
# "off" do not write prepare-root.conf at all. Falls through to
279+
# whatever the base image (ucore-hci / fedora-bootc /
280+
# UBlue) already configured. Use this when running on a
281+
# host that already has a custom prepare-root.conf you
282+
# don't want MiOS to clobber.
283+
#
284+
# Why this is opt-in rather than always-on:
285+
# - On bare-metal hosts with ext4/btrfs, "verity" is the strongest
286+
# anti-tamper posture we can ship without rebuilding userland against
287+
# a signing key (the native-rebuild track in [hwcaps]).
288+
# - Inside WSL2 / podman-machine / cloud images that target XFS,
289+
# "verity" will brick the deploy. "yes" keeps the read-only-/usr
290+
# guarantee without the verity dependency on the underlying FS.
291+
# - Day-N+1: when sealed-image fs-verity userspace + signing-key
292+
# plumbing lands (the "sealed-image-track" referenced in
293+
# usr/share/doc/mios/reference/bootc-comparison.md), this same key
294+
# gates the cryptographic boot path. Operators who already set
295+
# "verity" today inherit it automatically; operators on "off"/"yes"
296+
# keep their existing posture.
297+
#
298+
# Default policy: "verity" matches the existing behavior of the script
299+
# before this knob was introduced (no behavior change for existing
300+
# deployments).
301+
# ----------------------------------------------------------------------------
302+
[security]
303+
composefs_mode = "verity"
304+
# When composefs is in verity mode, automation/40-composefs-verity.sh
305+
# also masks systemd-remount-fs.service (known-broken with composefs on
306+
# Fedora 42+). Set to false to skip the mask if the base image's
307+
# systemd-remount-fs has been patched. No effect when composefs_mode is
308+
# "yes" or "off".
309+
mask_systemd_remount_fs = true
310+
260311
# ----------------------------------------------------------------------------
261312
# [packages] -- runtime SSOT for every dnf install in MiOS. Each
262313
# [packages.<section>] sub-table below carries a `pkgs = [...]` array;

0 commit comments

Comments
 (0)