1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
|
summary: End-to-end test for install Ubuntu Core via muinstaller
details: |
This test installs Ubuntu Core using the muinstaller. The muinstaller is a
program that interfaces with the snapd API to install an Ubuntu Core system.
We test both the encrypted and unencrypted cases. We also verify that the
system has properly installed system seed post-installation.
systems: [ubuntu-22.04-64, ubuntu-24.04-64]
environment:
# default (encrypted) case. all of the install_optional_* variants exercise
# the encrypted case
NESTED_ENABLE_TPM: true
NESTED_ENABLE_SECURE_BOOT: true
# encrypted + passphrase auth smoke test
# TODO: extract a more comprehensive core24 test similar
# to tests/nested/manual/passphrase-support-on-hybrid.
NESTED_PASSPHRASE/passphrase_auth: "ubuntu"
# unencrypted case
NESTED_ENABLE_TPM/plain: false
NESTED_ENABLE_SECURE_BOOT/plain: false
# ensure we use our latest code
NESTED_BUILD_SNAPD_FROM_CURRENT: true
NESTED_REPACK_KERNEL_SNAP: true
# image
IMAGE_MOUNTPOINT: /mnt/cloudimg
# by default, we don't specify any optional installs. this means we should
# install all snaps and components
INSTALL_OPTIONAL_SNAP: false
INSTALL_OPTIONAL_COMPONENT: false
INSTALL_OPTIONAL_ALL: false
INSTALL_OPTIONAL_EXPECT_ALL: true
INSTALL_OPTIONAL_SNAP/install_optional_snap: true
INSTALL_OPTIONAL_EXPECT_ALL/install_optional_snap: false
INSTALL_OPTIONAL_SNAP/install_optional_snap_and_comp: true
INSTALL_OPTIONAL_COMPONENT/install_optional_snap_and_comp: true
INSTALL_OPTIONAL_EXPECT_ALL/install_optional_snap_and_comp: true
INSTALL_OPTIONAL_ALL/install_optional_all: true
INSTALL_OPTIONAL_EXPECT_ALL/install_optional_all: true
prepare: |
if [ "$TRUST_TEST_KEYS" = "false" ]; then
echo "This test needs test keys to be trusted"
exit
fi
restore: |
rm -rf pc-kernel.* pc.* initrd* linux* kernel* tmp* pc-gadget snap-with-comps.snap snap-with-comps+comp1.comp core-seed fake-disk.img
execute: |
# shellcheck source=tests/lib/prepare.sh
. "$TESTSLIB/prepare.sh"
#shellcheck source=tests/lib/nested.sh
. "$TESTSLIB"/nested.sh
if ! os.query is-noble && [ -n "${NESTED_PASSPHRASE-}" ]; then
echo "SKIPPING: only run passphrase support variant for core24"
exit 0
fi
version="$(nested_get_version)"
# Retrieve the gadget
snap download --basename=pc --channel="$version/${KERNEL_CHANNEL}" pc
# Modify gadget, making sure we can access the device (we are not building
# the image in the usual way in snapd spread tests).
unsquashfs -d pc-gadget pc.snap
echo 'console=ttyS0' > pc-gadget/cmdline.extra
cat <<EOF > pc-gadget/cloud.conf
#cloud-config
datasource_list: [None]
ssh_pwauth: True
users:
- name: user1
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
chpasswd:
list: |
user1:ubuntu
expire: False
EOF
echo "Sign the shim binary"
KEY_NAME=$(tests.nested download snakeoil-key)
SNAKEOIL_KEY="$PWD/$KEY_NAME.key"
SNAKEOIL_CERT="$PWD/$KEY_NAME.pem"
tests.nested secboot-sign gadget pc-gadget "$SNAKEOIL_KEY" "$SNAKEOIL_CERT"
snap pack --filename=pc.snap pc-gadget/
rm -rf pc-gadget
# Retrieve kernel
snap download --basename=pc-kernel --channel="$version/${KERNEL_CHANNEL}" pc-kernel
# THIS IS A HACK
# TODO: Remove when pc-kernel snapd-info includes snap-bootstrap from snapd 2.68+
# Inject snapd-info file to account for kernels that don't yet include snapd 2.68+
unsquashfs -d tmp-pc-kernel pc-kernel.snap
cat <<EOF > tmp-pc-kernel/snapd-info
VERSION=2.68
SNAPD_APPARMOR_REEXEC=1
SNAPD_ASSERTS_FORMATS='{"account-key":1,"snap-declaration":6,"system-user":2}'
EOF
rm pc-kernel.snap
snap pack tmp-pc-kernel
rm -rf tmp-pc-kernel
mv pc-kernel_*.snap pc-kernel.snap
# HACK ENDS
# Build kernel with initramfs with the compiled snap-bootstrap
if os.query is-ubuntu-ge 24.04; then
uc24_build_initramfs_kernel_snap "$PWD/pc-kernel.snap" "$NESTED_ASSETS_DIR"
else
uc20_build_initramfs_kernel_snap "$PWD/pc-kernel.snap" "$NESTED_ASSETS_DIR"
fi
mv "${NESTED_ASSETS_DIR}"/pc-kernel_*.snap pc-kernel.snap
# shellcheck source=tests/lib/prepare.sh
. "$TESTSLIB"/prepare.sh
build_snapd_snap "$PWD"
mv snapd_*.snap snapd.snap
sed -i "s/\$BASE/core${version}/" ./snap-with-comps/meta/snap.yaml
snap pack ./snap-with-comps --filename=snap-with-comps.snap
snap pack ./comp1 --filename=snap-with-comps+comp1.comp
# TODO: refactor preparation/hacks in a reusable script
# similar to setup_nested_hybrid_system.sh
# prepare a core seed
SEED_DIR="core-seed"
wget -q https://raw.githubusercontent.com/snapcore/models/master/ubuntu-core-"$version"-amd64-dangerous.model -O my.model
snap prepare-image \
--channel=edge \
--snap ./pc-kernel.snap \
--snap ./pc.snap \
--snap ./snapd.snap \
--snap ./snap-with-comps.snap \
--comp ./snap-with-comps+comp1.comp \
my.model \
./"$SEED_DIR"
# get the label, which is generated by snap prepare-image and is based on the
# date
LABEL=$(basename ./"$SEED_DIR"/system-seed/systems/*)
cp -a ./"$SEED_DIR"/system-seed/ /var/lib/snapd/seed
# build the muinstaller snap
snap install snapcraft --candidate --classic
"$TESTSTOOLS"/lxd-state prepare-snap
(cd "$TESTSLIB"/muinstaller && snapcraft)
MUINSTALLER_SNAP="$(find "$TESTSLIB"/muinstaller/ -maxdepth 1 -name '*.snap')"
echo "found $MUINSTALLER_SNAP"
# create new disk for the installer to work on and attach to VM
truncate --size=4G fake-disk.img
# create a VM and mount a cloud image
tests.nested build-image classic
# TODO: nested classic images do not support secure boot today so
# this will not work to test the secure boot installer. So for
# now the workaround is to boot classic to create user/ssh
# keys, shutdown down, convert disk from qcow2->raw and rename
# from classic->core and use nested_start_core_vm (like below)
#
# start it so that cloud-init creates ssh keys and user
# We set a serial for our disk to easily locate it when invoking muinstaller (virtio-target)
NESTED_PARAM_EXTRA="-drive file=$(pwd)/fake-disk.img,if=none,snapshot=off,format=raw,id=disk2 \
-device virtio-blk-pci,drive=disk2,serial=target"
tests.nested create-vm classic --extra-param "$NESTED_PARAM_EXTRA"
# make sure classic image is bootable with snakeoil keys
# TODO: move to nested_create_classic_image
# XXX: use assets from gadget instead?
for s in BOOT/BOOTX64.EFI ubuntu/shimx64.efi; do
remote.exec "sudo cp -a /boot/efi/EFI/$s /tmp"
remote.exec "sudo chmod 755 /tmp/$(basename $s)"
remote.pull /tmp/"$(basename $s)" .
nested_secboot_sign_file "$(basename $s)" "$SNAKEOIL_KEY" "$SNAKEOIL_CERT"
remote.push "$(basename $s)"
remote.exec "sudo mv $(basename $s) /boot/efi/EFI/$s"
done
remote.exec "sudo sh -c 'echo SNAPD_DEBUG=1 >> /etc/environment'"
# push our snap down
remote.push "$GOHOME"/snapd_*.deb
remote.exec "sudo apt install -y ./snapd_*.deb"
# push our seed down
# TODO: merge with classic /var/lib/snapd/seed eventually
# XXX: port scp -r to remote.push
#remote.push ./"$SEED_DIR"/system-seed/ '~/'
sshpass -p ubuntu scp -r -P 8022 -o ConnectTimeout=10 \
-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
./"$SEED_DIR"/system-seed/ user1@localhost:~/install-seed
remote.exec "sudo mv /home/user1/install-seed /var/lib/snapd/"
# shutdown the classic vm to install with a core VM that supports
# secboot/tpm
tests.nested vm stop
sync
# HACK: convert "classic" qcow2 to raw "core" image because we need
# to boot with OVMF we really should fix this so that classic and
# core VMs are more similar
qemu-img convert -f qcow2 -O raw \
"$NESTED_IMAGES_DIR/$(nested_get_image_name classic)" \
"$NESTED_IMAGES_DIR/$(nested_get_image_name core)"
# and we don't need the classic image anymore
rm -f "$NESTED_IMAGES_DIR/$(nested_get_image_name classic)"
# TODO: this prevents "nested_prepare_ssh" inside nested_start_core_vm
# from running, we already have a user so this is not needed
IMAGE_NAME="$(nested_get_image_name core)"
touch "$NESTED_IMAGES_DIR/$IMAGE_NAME.configured"
tests.nested create-vm core --extra-param "$NESTED_PARAM_EXTRA"
# bind mount new seed
remote.exec sudo mkdir -p /var/lib/snapd/seed
remote.exec "sudo mount -o bind /var/lib/snapd/install-seed /var/lib/snapd/seed"
# push and install muinstaller
remote.push "$MUINSTALLER_SNAP"
remote.exec "sudo snap install --classic --dangerous $(basename "$MUINSTALLER_SNAP")"
# Run installation
install_disk=$(remote.exec "readlink -f /dev/disk/by-id/virtio-target")
if [ "$INSTALL_OPTIONAL_ALL" = "true" ]; then
echo '{"all": true}' > optional-install.json
elif [ "$INSTALL_OPTIONAL_COMPONENT" = "true" ]; then
echo '{"snaps": ["snap-with-comps"], "components": {"snap-with-comps": ["comp1"]}}' > optional-install.json
elif [ "$INSTALL_OPTIONAL_SNAP" = "true" ]; then
echo '{"snaps": ["snap-with-comps"]}' > optional-install.json
fi
# make sure that the system reports the expected available optional snaps
remote.exec "sudo snap debug api /v2/systems/$LABEL" > system-info.json
cat <<EOF > expected-available.json
{
"snaps": ["snap-with-comps"],
"components": {
"snap-with-comps": ["comp1"]
}
}
EOF
# gojq sorts keys by default
diff -u <(gojq '.result.["available-optional"]' system-info.json) <(gojq . expected-available.json)
muinstaller_args="-label $LABEL -device $install_disk"
if [ -f optional-install.json ]; then
remote.push optional-install.json
muinstaller_args="$muinstaller_args -optional ./optional-install.json"
fi
if [ -n "${NESTED_PASSPHRASE-}" ]; then
muinstaller_args="$muinstaller_args -passphrase ${NESTED_PASSPHRASE}"
fi
remote.exec "sudo muinstaller $muinstaller_args"
remote.exec "sudo sync"
# Stop and remove the classic vm now that the attached disk (fake-disk.img)
# contains a just installed UC image.
tests.nested vm remove
sync
# HACK: rename to "core" image because we need to boot with OVMF
# we really should fix this so that classic and core VMs are more similar
mv fake-disk.img "$NESTED_IMAGES_DIR/$IMAGE_NAME"
# Start installed image
if [ -n "${NESTED_PASSPHRASE-}" ]; then
tests.nested create-vm core --keep-firmware-state --passphrase "${NESTED_PASSPHRASE}"
else
tests.nested create-vm core --keep-firmware-state
fi
# things look fine
remote.exec "cat /etc/os-release" | MATCH 'NAME="Ubuntu Core"'
remote.exec "snap changes" | MATCH "Done.* Initialize system state"
remote.exec "snap list" | MATCH pc-kernel
# check encryption
remote.exec "sudo snap install test-snapd-tools"
remote.exec "snap list" | MATCH "test-snapd-tools"
if [ "$NESTED_ENABLE_TPM" = false ]; then
remote.exec "test-snapd-tools.cmd snapctl system-mode" | NOMATCH "storage-encrypted"
else
remote.exec "test-snapd-tools.cmd snapctl system-mode" | MATCH "storage-encrypted: managed"
remote.exec "sudo test -d /var/lib/snapd/device/fde"
remote.exec "sudo test -e /var/lib/snapd/device/fde/marker"
remote.exec "sudo test -e /var/lib/snapd/device/fde/marker"
remote.exec "sudo blkid /dev/disk/by-label/ubuntu-data-enc" | MATCH crypto_LUKS
echo "Ensure recovery keys are available"
remote.exec "sudo snap recovery --show-keys" > show-keys.out
MATCH 'recovery:\s+[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}' < show-keys.out
# check disk mappings
DISK_MAPPINGS=(/run/mnt/ubuntu-save/device/disk-mapping.json
/run/mnt/data/system-data/var/lib/snapd/device/disk-mapping.json)
for DM in "${DISK_MAPPINGS[@]}"; do
remote.exec "sudo cat $DM" | \
gojq '.pc."structure-encryption"."ubuntu-save".method' | MATCH '"LUKS"'
remote.exec "sudo cat $DM" | \
gojq '.pc."structure-encryption"."ubuntu-data".method' | MATCH '"LUKS"'
done
fi
# check that seed is properly installed
remote.exec test -d "/run/mnt/ubuntu-seed/systems/${LABEL}"
remote.exec "sudo snap recovery" | MATCH "${LABEL}\s+canonical\*\*\s+ubuntu-core-$version-amd64-dangerous\s+current"
# check for unasserted snaps
for sn in snapd pc pc-kernel; do
sn_version=$(remote.exec "snap list ${sn}" | awk 'NR != 1 { print $2 }')
remote.exec "test -f /run/mnt/ubuntu-seed/systems/${LABEL}/snaps/${sn}_${sn_version}.snap"
done
if [ "$INSTALL_OPTIONAL_SNAP" = "true" ] || [ "$INSTALL_OPTIONAL_EXPECT_ALL" = "true" ]; then
sn_version=$(remote.exec "snap list snap-with-comps" | awk 'NR != 1 { print $2 }')
# snap isn't asserted, but it is not in the model so it should end up here
remote.exec "test -f /run/mnt/ubuntu-seed/systems/${LABEL}/snaps/snap-with-comps_${sn_version}.snap"
if [ "$INSTALL_OPTIONAL_EXPECT_ALL" = "true" ]; then
# make sure our component is there too
remote.exec "snap run snap-with-comps.test comp1"
else
not remote.exec "snap run snap-with-comps.test comp1"
fi
else
not remote.exec "snap list snap-with-comps"
fi
# check for asserted snaps
#shellcheck disable=SC2043
for sn in core"$version"; do
rev=$(remote.exec "snap list ${sn}" | awk 'NR != 1 { print $3 }')
remote.exec "test -f /run/mnt/ubuntu-seed/snaps/${sn}_${rev}.snap"
done
# model in the seed should match the one that was installed on the system
remote.exec "diff <(snap model --assertion) /run/mnt/ubuntu-seed/systems/${LABEL}/model"
# test that we can actually boot into the installed recovery system
tests.nested transition "${LABEL}" recover
|