Skip to content

Commit dcb5c67

Browse files
authored
Merge pull request #680 from jpculp/repack-variant-improvements
repack-variant improvements
2 parents 160d689 + 130810d commit dcb5c67

2 files changed

Lines changed: 99 additions & 49 deletions

File tree

docs/design/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,40 @@ After installing packages, the result of `dnf list installed` will be output to
540540
This will allow maintainers to audit which kit each package was taken from.
541541
The rest of the variant image creation steps proceed the same way that they do now.
542542

543+
## Twoliter Repack
544+
545+
In the event that a maintainer wants to replace specific artifacts on an already-built variant image, such as the CA certificate bundle, TUF `root.json`, or Secure Boot keys, they can use the `repack-variant` target rather than rebuilding from source.
546+
547+
The `repack-variant` target operates on whatever images exist in the local build artifacts.
548+
There are two ways to get them there:
549+
550+
- `build-variant`: builds images from source.
551+
Use this when the images haven't been published yet or you have local source changes.
552+
- `fetch-variant`: downloads previously published images from a TUF repository.
553+
Use this when repacking a released set of images without rebuilding all packages.
554+
555+
```sh
556+
# Repack published images:
557+
twoliter make fetch-variant
558+
twoliter make repack-variant
559+
560+
# Or repack locally built images:
561+
twoliter make build-variant
562+
twoliter make repack-variant
563+
```
564+
565+
The `repack-variant` target takes the input image, replaces the configured artifacts, re-signs where necessary, regenerates dm-verity, and produces the final image.
566+
567+
For example, a maintainer behind a corporate proxy can add their internal CA to an existing image by pointing `BUILDSYS_CACERTS_BUNDLE_OVERRIDE` at a custom bundle:
568+
569+
```sh
570+
twoliter make \
571+
-e BUILDSYS_CACERTS_BUNDLE_OVERRIDE=/path/to/corporate-ca-bundle.crt \
572+
repack-variant
573+
```
574+
575+
Because `repack-variant` skips package builds entirely, it is significantly faster than a full `build-variant`.
576+
543577
## Publishing
544578

545579
Common publishing steps such as `cargo make repo` and `cargo make ami` will be hoisted to Twoliter.

twoliter/embedded/img2img

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,61 @@ cleanup() {
4141
}
4242
trap 'cleanup' EXIT
4343

44-
# Since debugfs doesn't pass return codes, extrapolate failures from STDERR.
45-
check_debugfs_errors() {
46-
local stderr
47-
stderr="$1"
48-
49-
# debugfs prints it's version info to STDERR, so we need to filter it out.
50-
# shellcheck disable=SC2312 # grep returns an error code if no match.
51-
if [[ "$(grep -vc '^debugfs ' "${stderr}")" -ne 0 ]]; then
52-
grep -v '^debugfs ' "${stderr}" >&2
44+
# Replace a file in an ext4 image and verify the result.
45+
# Usage: debugfs_replace <image> <source> <target>
46+
debugfs_replace() {
47+
local image="$1" source="$2" target="$3"
48+
local errlog
49+
errlog="$(mktemp -p "${WORKDIR}" debugfs-err.XXXXXXXXXX)"
50+
51+
cat <<EOF | debugfs -w -f - "${image}" 2>"${errlog}"
52+
rm ${target}
53+
write ${source} ${target}
54+
ea_set ${target} security.selinux system_u:object_r:os_t:s0
55+
EOF
56+
57+
local unexpected
58+
unexpected="$(grep -Ev '^debugfs [0-9]+\.[0-9]+\.[0-9]+ \([^)]+\)$' "${errlog}" || true)"
59+
if [[ -n "${unexpected}" ]]; then
60+
echo "debugfs produced unexpected output:" >&2
61+
echo "${unexpected}" >&2
62+
rm -f "${errlog}"
63+
exit 1
64+
fi
65+
rm -f "${errlog}"
66+
67+
if ! debugfs -R "stat ${target}" "${image}" 2>/dev/null | grep -q 'Size:'
68+
then
69+
echo "debugfs verify failed: '${target}' missing after write" >&2
70+
exit 1
71+
fi
72+
73+
if ! debugfs -R "ea_get ${target} security.selinux" "${image}" 2>/dev/null |
74+
grep -q 'system_u:object_r:os_t:s0'; then
75+
echo "debugfs verify failed: '${target}' selinux label not set" >&2
76+
exit 1
77+
fi
78+
}
79+
80+
# Write a partition back to a disk image and verify via hash comparison.
81+
write_partition() {
82+
local src="$1" dst="$2" seek_mib="$3"
83+
local src_bytes
84+
src_bytes="$(stat -c '%s' "${src}")"
85+
local expected
86+
expected="$(sha256sum < "${src}" | cut -d' ' -f1)"
87+
88+
dd if="${src}" of="${dst}" \
89+
iflag=fullblock conv=notrunc,fsync bs=1M seek="${seek_mib}"
90+
91+
local skip_bytes=$(( seek_mib * (1 << 20) ))
92+
local actual
93+
actual="$(dd if="${dst}" iflag=skip_bytes,count_bytes,fullblock \
94+
skip="${skip_bytes}" count="${src_bytes}" bs=1M 2>/dev/null |
95+
sha256sum | cut -d' ' -f1)"
96+
97+
if [[ "${expected}" != "${actual}" ]]; then
98+
echo "write verification failed at offset ${seek_mib}MiB (${src##*/})" >&2
5399
exit 1
54100
fi
55101
}
@@ -180,23 +226,16 @@ else
180226
echo "no new root artifacts found" >&2
181227
exit 1
182228
else
183-
# Write files from the root mount to the root image.
184-
ROOT_DEBUGFS_STDERR="${WORKDIR}/root.err"
185229
for artifact in "${new_root_artifacts[@]}"; do
186-
cat <<EOF | debugfs -w -f - "${ROOT_IMAGE}" 2>>"${ROOT_DEBUGFS_STDERR}"
187-
rm ${artifact#"${ROOT_MOUNT}"}
188-
write ${artifact} ${artifact#"${ROOT_MOUNT}"}
189-
ea_set ${artifact#"${ROOT_MOUNT}"} security.selinux system_u:object_r:os_t:s0
190-
EOF
230+
debugfs_replace "${ROOT_IMAGE}" \
231+
"${artifact}" "${artifact#"${ROOT_MOUNT}"}"
191232
done
192-
check_debugfs_errors "${ROOT_DEBUGFS_STDERR}"
193233
fi
194234
fi
195235

196236
# Validate and write root image back to the OS image.
197237
check_image_size "${ROOT_IMAGE}" "${partsize["ROOT-A"]}"
198-
dd if="${ROOT_IMAGE}" of="${OS_IMAGE}" \
199-
iflag=fullblock conv=notrunc bs=1M seek="${partoff["ROOT-A"]}"
238+
write_partition "${ROOT_IMAGE}" "${OS_IMAGE}" "${partoff["ROOT-A"]}"
200239

201240
# Report ROOT partition usage
202241
if [[ "${EROFS_ROOT_PARTITION}" == "yes" ]]; then
@@ -212,8 +251,7 @@ generate_verity_root "${ROOT_IMAGE}" "${VERITY_IMAGE}" \
212251

213252
# Validate and write root verity back to the OS image. The image size check
214253
# isn't needed here as it's already done in `generate_verity_root`.
215-
dd if="${VERITY_IMAGE}" of="${OS_IMAGE}" \
216-
iflag=fullblock conv=notrunc bs=1M seek="${partoff["HASH-A"]}"
254+
write_partition "${VERITY_IMAGE}" "${OS_IMAGE}" "${partoff["HASH-A"]}"
217255

218256
###############################################################################
219257
# Section 5: maybe secure boot
@@ -247,26 +285,15 @@ if [[ "${UEFI_SECURE_BOOT}" == "yes" ]]; then
247285

248286
# Write the EFI image back to the OS image.
249287
check_image_size "${EFI_IMAGE}" "${partsize["EFI-A"]}"
250-
dd if="${EFI_IMAGE}" of="${OS_IMAGE}" \
251-
iflag=fullblock conv=notrunc bs=1M seek="${partoff["EFI-A"]}"
288+
write_partition "${EFI_IMAGE}" "${OS_IMAGE}" "${partoff["EFI-A"]}"
252289

253290
# Resign the kernel and write to boot image.
254291
sign_vmlinuz "${BOOT_MOUNT}/vmlinuz"
255-
KERNEL_DEBUGFS_STDERR="${WORKDIR}/vmlinuz.err"
256-
cat <<EOF | debugfs -w -f - "${BOOT_IMAGE}" 2>>"${KERNEL_DEBUGFS_STDERR}"
257-
rm vmlinuz
258-
write ${BOOT_MOUNT}/vmlinuz vmlinuz
259-
ea_set vmlinuz security.selinux system_u:object_r:os_t:s0
260-
EOF
292+
debugfs_replace "${BOOT_IMAGE}" "${BOOT_MOUNT}/vmlinuz" vmlinuz
261293

262294
# Generate a new HMAC for the kernel after signing and write to boot image.
263295
generate_hmac "${BOOT_MOUNT}/vmlinuz"
264-
cat <<EOF | debugfs -w -f - "${BOOT_IMAGE}" 2>>"${KERNEL_DEBUGFS_STDERR}"
265-
rm .vmlinuz.hmac
266-
write ${BOOT_MOUNT}/.vmlinuz.hmac .vmlinuz.hmac
267-
ea_set .vmlinuz.hmac security.selinux system_u:object_r:os_t:s0
268-
EOF
269-
check_debugfs_errors "${KERNEL_DEBUGFS_STDERR}"
296+
debugfs_replace "${BOOT_IMAGE}" "${BOOT_MOUNT}/.vmlinuz.hmac" .vmlinuz.hmac
270297
fi
271298

272299
###############################################################################
@@ -280,28 +307,17 @@ sed -i \
280307
"${GRUB_CONFIG}"
281308

282309
# Replace grub.cfg on the boot image.
283-
GRUB_DEBUGFS_STDERR="${WORKDIR}/grub.err"
284-
cat <<EOF | debugfs -w -f - "${BOOT_IMAGE}" 2>>"${GRUB_DEBUGFS_STDERR}"
285-
rm /grub/grub.cfg
286-
write ${GRUB_CONFIG} /grub/grub.cfg
287-
ea_set /grub/grub.cfg security.selinux system_u:object_r:os_t:s0
288-
EOF
310+
debugfs_replace "${BOOT_IMAGE}" "${GRUB_CONFIG}" /grub/grub.cfg
289311

290312
# Sign the grub.cfg and replace the signature on the boot image, if needed.
291313
if [[ "${UEFI_SECURE_BOOT}" == "yes" ]]; then
292314
sign_grubcfg "${GRUB_CONFIG}"
293-
cat <<EOF | debugfs -w -f - "${BOOT_IMAGE}" 2>>"${GRUB_DEBUGFS_STDERR}"
294-
rm /grub/grub.cfg.sig
295-
write ${GRUB_CONFIG}.sig /grub/grub.cfg.sig
296-
ea_set /grub/grub.cfg.sig security.selinux system_u:object_r:os_t:s0
297-
EOF
315+
debugfs_replace "${BOOT_IMAGE}" "${GRUB_CONFIG}.sig" /grub/grub.cfg.sig
298316
fi
299-
check_debugfs_errors "${GRUB_DEBUGFS_STDERR}"
300317

301318
# Write the boot image back to the OS image.
302319
check_image_size "${BOOT_IMAGE}" "${partsize["BOOT-A"]}"
303-
dd if="${BOOT_IMAGE}" of="${OS_IMAGE}" \
304-
iflag=fullblock conv=notrunc bs=1M seek="${partoff["BOOT-A"]}"
320+
write_partition "${BOOT_IMAGE}" "${OS_IMAGE}" "${partoff["BOOT-A"]}"
305321

306322
# Report BOOT partition usage
307323
boot_usage=$(get_ext4_usage "${BOOT_IMAGE}" "BOOT-A")

0 commit comments

Comments
 (0)