From caf8d51ea2f69c8e5422cfffec8f9c1fe451c890 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 27 Sep 2021 14:29:39 +0200 Subject: cmd/snap-confine: drop misplaced comment Signed-off-by: Maciej Borzecki --- cmd/snap-confine/udev-support.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/snap-confine/udev-support.c b/cmd/snap-confine/udev-support.c index b09ca3a385..8f937c7ba1 100644 --- a/cmd/snap-confine/udev-support.c +++ b/cmd/snap-confine/udev-support.c @@ -306,9 +306,6 @@ void sc_setup_device_cgroup(const char *security_tag) return; } - /* Note that -1 is the neutral value for a file descriptor. - * The cleanup function associated with this variable closes - * descriptors other than -1. */ sc_device_cgroup *cgroup SC_CLEANUP(sc_device_cgroup_cleanup) = sc_device_cgroup_new(security_tag, 0); /* Setup the device group access control list */ -- cgit v1.2.3 From cd6b1751b4f694996b5a189d7fe5c53b1d83fcbf Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 27 Sep 2021 14:29:54 +0200 Subject: cmd/libsnap-confine-private: cgrupv2 die when process is not in snap specific group It is possible that the snap process has not been moved to the snap specific tracking cgroup. In this case, setting up a device filtering on the group can negatively affect whatever group the process is part of. Try to catch test scenarios when thiss happens, so that we may reach a reasonable solution. Signed-off-by: Maciej Borzecki --- cmd/libsnap-confine-private/device-cgroup-support.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cmd/libsnap-confine-private/device-cgroup-support.c b/cmd/libsnap-confine-private/device-cgroup-support.c index 8c45709dd6..b3e2e983a1 100644 --- a/cmd/libsnap-confine-private/device-cgroup-support.c +++ b/cmd/libsnap-confine-private/device-cgroup-support.c @@ -284,6 +284,19 @@ static struct rlimit _sc_cgroup_v2_adjust_memlock_limit(void) { return old_limit; } +static bool _sc_is_snap_cgroup(const char *group) { + /* make a copy as basename may modify its input */ + char *copy SC_CLEANUP(sc_cleanup_string) = sc_strdup(group); + char *leaf = basename(copy); + if (!sc_startswith(leaf, "snap.")) { + return false; + } + if (!sc_endswith(leaf, ".service") && !sc_endswith(leaf, ".scope")) { + return false; + } + return true; +} + static int _sc_cgroup_v2_init_bpf(sc_device_cgroup *self, int flags) { self->v2.devmap_fd = -1; self->v2.cgroup_fd = -1; @@ -292,6 +305,13 @@ static int _sc_cgroup_v2_init_bpf(sc_device_cgroup *self, int flags) { if (own_group == NULL) { die("cannot obtain own group path"); } + debug("process in cgroup %s", own_group); + if (!_sc_is_snap_cgroup(own_group)) { + if (getenv_bool("SNAPPY_TESTING", false)) { + die("%s is not a snap cgroup", own_group); + } + debug("%s is not a snap cgroup", own_group); + } /* fix the memlock limit if needed, this affects creating maps */ self->v2.old_limit = _sc_cgroup_v2_adjust_memlock_limit(); -- cgit v1.2.3 From 26e7e7f5739d8c00866d3d9288434546a7defb13 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 27 Sep 2021 14:34:47 +0200 Subject: tests/main/snap-confine-undesired-mode-group: use tests.session helper Such that the snap application which apparently has matching devices will execute with proper tracking enabled. Signed-off-by: Maciej Borzecki --- tests/main/snap-confine-undesired-mode-group/task.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/main/snap-confine-undesired-mode-group/task.yaml b/tests/main/snap-confine-undesired-mode-group/task.yaml index 9f9f594278..b78658b70c 100644 --- a/tests/main/snap-confine-undesired-mode-group/task.yaml +++ b/tests/main/snap-confine-undesired-mode-group/task.yaml @@ -8,13 +8,15 @@ prepare: | snap install --dangerous ./test-snapd-app_1.0_all.snap snap connect test-snapd-app:opengl snap connect test-snapd-app:joystick + tests.session -u test prepare restore: | + tests.session -u test restore rm -rf /tmp/snap.test-snapd-app execute: | # Run the snap as a non-root user. - su test -c 'snap run test-snapd-app.sh -c /bin/true' + tests.session -u test exec sh -c 'snap run test-snapd-app.sh -c /bin/true' # Look for files that are owned by the test user, group owned by the test # user or are world-writable in /run/snapd, /sys/fs/cgroup and in /tmp -- cgit v1.2.3 From a8e8cd250629a4ab3723b900fd71ec0d038646c4 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 28 Sep 2021 09:41:22 +0200 Subject: cmd/libsnap-confine-private: require snap specific cgroup before setting up device control on v2 Signed-off-by: Maciej Borzecki --- cmd/libsnap-confine-private/device-cgroup-support.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/libsnap-confine-private/device-cgroup-support.c b/cmd/libsnap-confine-private/device-cgroup-support.c index b3e2e983a1..cbf0d9e276 100644 --- a/cmd/libsnap-confine-private/device-cgroup-support.c +++ b/cmd/libsnap-confine-private/device-cgroup-support.c @@ -307,10 +307,11 @@ static int _sc_cgroup_v2_init_bpf(sc_device_cgroup *self, int flags) { } debug("process in cgroup %s", own_group); if (!_sc_is_snap_cgroup(own_group)) { - if (getenv_bool("SNAPPY_TESTING", false)) { - die("%s is not a snap cgroup", own_group); - } - debug("%s is not a snap cgroup", own_group); + /* we cannot proceed to install a device filtering program when the + * process is not in a snap specific cgroup, as we would effectively + * lock down the group that can be shared with other processes or even + * the whole desktop session */ + die("%s is not a snap cgroup", own_group); } /* fix the memlock limit if needed, this affects creating maps */ -- cgit v1.2.3 From f232a3010711e15614b3e92c59b7df3613f1723e Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 28 Sep 2021 11:46:38 +0200 Subject: spread: when tests fail, list any present cgroup v2 device filter maps Signed-off-by: Maciej Borzecki --- spread.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spread.yaml b/spread.yaml index a570c2f250..5402a74f2b 100644 --- a/spread.yaml +++ b/spread.yaml @@ -458,6 +458,8 @@ debug-each: | echo "# device cgroup $cgroup_dev" cat "/sys/fs/cgroup/devices/$cgroup_dev/devices.list" || true fi + else + ls -l /sys/fs/bpf/snap || true fi case "$SPREAD_SYSTEM" in -- cgit v1.2.3 From 9d85bad70bdb239221917a3c341f87b33ed71ef4 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 28 Sep 2021 12:00:14 +0200 Subject: tests/main/snap-confine-privs: fix the test to use tests.session helper Signed-off-by: Maciej Borzecki --- tests/main/snap-confine-privs/task.yaml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/tests/main/snap-confine-privs/task.yaml b/tests/main/snap-confine-privs/task.yaml index 9312b04ce1..632ab005e5 100644 --- a/tests/main/snap-confine-privs/task.yaml +++ b/tests/main/snap-confine-privs/task.yaml @@ -33,6 +33,11 @@ prepare: | chgrp root "$P/uids-and-gids-setgid" chmod 2755 "$P/uids-and-gids-setgid" + tests.session -u test prepare + +restore: | + tests.session -u test restore + execute: | echo "The test executables files have the expected mode and ownership" #shellcheck disable=SC2012 @@ -45,15 +50,15 @@ execute: | echo "Running as regular user" # Spread runs all tests as root so we're using su to switch to the "test" user. # The "test" user inside the spread suite is guaranteed to have UID/GID of 12345. - su -l -c "$P/uids-and-gids" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345' - su -l -c "$P/uids-and-gids-setuid" test | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345' - su -l -c "$P/uids-and-gids-setgid" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 ' + tests.session -u test exec "$P/uids-and-gids" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345' + tests.session -u test exec "$P/uids-and-gids-setuid" | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345' + tests.session -u test exec "$P/uids-and-gids-setgid" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 ' echo "Running as regular user via sudo" # This is same as above except that we're also using sudo - su -l -c "sudo $P/uids-and-gids" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' - su -l -c "sudo $P/uids-and-gids-setuid" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' - su -l -c "sudo $P/uids-and-gids-setgid" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' + tests.session -u test exec sh -c "sudo $P/uids-and-gids" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' + tests.session -u test exec sh -c "sudo $P/uids-and-gids-setuid" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' + tests.session -u test exec sh -c "sudo $P/uids-and-gids-setgid" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' echo "Running as regular user under snap-confine" # This is the same as the two above but it goes through snap-confine as @@ -61,12 +66,12 @@ execute: | # expansions done. Note that we are using "snap run test-snapd-sh" in order # to ensure that we can start the progam even if su/sudo's secure PATH does # not contain the snap bin directory. - su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345' - su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" test | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345' - su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" test | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 ' + tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=12345 sgid=12345' + tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" | MATCH 'ruid=12345 euid=0 suid=0 rgid=12345 egid=12345 sgid=12345' + tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" | MATCH 'ruid=12345 euid=12345 suid=12345 rgid=12345 egid=0 sgid=0 ' echo "Running as regular user, uder snap-conifne under sudo" # This is the same one as the previous one but also using sudo. - su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' - su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' - su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" test | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' + tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids'" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' + tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setuid'" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' + tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/uids-and-gids-setgid'" | MATCH 'ruid=0 euid=0 suid=0 rgid=0 egid=0 sgid=0 ' -- cgit v1.2.3 From c7a02fa50fd3f65d1b945ec42819fb7929a837b2 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 28 Sep 2021 12:24:38 +0200 Subject: tests/main/snapshot-users: port the test to tests.session helper Signed-off-by: Maciej Borzecki --- tests/main/snapshot-users/task.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/main/snapshot-users/task.yaml b/tests/main/snapshot-users/task.yaml index 3133039469..c444ab8d45 100644 --- a/tests/main/snapshot-users/task.yaml +++ b/tests/main/snapshot-users/task.yaml @@ -2,12 +2,16 @@ summary: Check that the basic snapshots functionality works for different users prepare: | snap install test-snapd-sh + tests.session -u test prepare + +restore: | + tests.session -u test restore execute: | # use the snaps, so they create the dirs test-snapd-sh.sh -c 'true' SNAP_MOUNT_DIR="$(os.paths snap-mount-dir)" - su -p -c "$SNAP_MOUNT_DIR/bin/test-snapd-sh.sh -c 'true'" test + tests.session -u test exec sh -c "$SNAP_MOUNT_DIR/bin/test-snapd-sh.sh -c 'true'" test # drop in canaries for both users echo "hello versioned test-snapd-sh" > /root/snap/test-snapd-sh/current/canary.txt -- cgit v1.2.3 From aab4924297a8abce85e6b022d401d6d453ce41df Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 28 Sep 2021 12:25:54 +0200 Subject: tests/main/security-private-tmp: prepare test user session This seems to be enough to have the session bus and systemd up Signed-off-by: Maciej Borzecki --- tests/main/security-private-tmp/task.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/main/security-private-tmp/task.yaml b/tests/main/security-private-tmp/task.yaml index 17e0e9b40d..3fb4fbfd85 100644 --- a/tests/main/security-private-tmp/task.yaml +++ b/tests/main/security-private-tmp/task.yaml @@ -16,9 +16,11 @@ prepare: | sed -i 's/test-snapd-sh/not-test-snapd-sh/g' "$SNAP_INSTALL_DIR/meta/snap.yaml" snap pack "$SNAP_INSTALL_DIR" snap install --dangerous not-test-snapd-sh_1.0_all.snap + tests.session -u test prepare restore: | rm -rf "$SNAP_INSTALL_DIR" /tmp/foo /tmp/snap.not-test-snapd-sh /tmp/snap.test-snapd-sh/ + tests.session -u test restore execute: | SNAP_MOUNT_DIR="$(os.paths snap-mount-dir)" -- cgit v1.2.3 From a6a3bcc7df69f1256166ead73aa43d9688c07ccd Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 28 Sep 2021 13:24:33 +0200 Subject: tests/main/parallel-install-basic: start test user session before su Signed-off-by: Maciej Borzecki --- tests/main/parallel-install-basic/task.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/main/parallel-install-basic/task.yaml b/tests/main/parallel-install-basic/task.yaml index 04a5abe097..566c084258 100644 --- a/tests/main/parallel-install-basic/task.yaml +++ b/tests/main/parallel-install-basic/task.yaml @@ -7,9 +7,13 @@ prepare: | snap set system experimental.parallel-instances=true + tests.session -u test prepare + restore: | snap set system experimental.parallel-instances=null + tests.session -u test restore + execute: | "$TESTSTOOLS"/snaps-state install-local test-snapd-sh "$TESTSTOOLS"/snaps-state install-local-as test-snapd-sh test-snapd-sh_foo -- cgit v1.2.3 From 3b647472e00258a3ac292ccc87c5d3a3c2cb5ff7 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 29 Sep 2021 10:32:40 +0200 Subject: tests/main: move more tests to the tests.session helper Signed-off-by: Maciej Borzecki --- tests/main/cwd/task.yaml | 2 ++ tests/main/selinux-snap-restorecon/task.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index 40c617f207..48ff921783 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -9,9 +9,11 @@ details: | prepare: | "$TESTSTOOLS"/snaps-state install-local test-snapd-sh + tests.session -u test prepare restore: | rmdir /tmp/test || true + tests.session -u test restore debug: | # Much of what we do depends on permissions. If the permissions on those diff --git a/tests/main/selinux-snap-restorecon/task.yaml b/tests/main/selinux-snap-restorecon/task.yaml index a80034d7ff..e588232d2d 100644 --- a/tests/main/selinux-snap-restorecon/task.yaml +++ b/tests/main/selinux-snap-restorecon/task.yaml @@ -10,12 +10,14 @@ prepare: | if [ -d /home/test/snap ]; then mv /home/test/snap /home/test/snap.old fi + tests.session -u test prepare restore: | rm -rf /home/test/snap if [ -d /home/test/snap.old ]; then mv /home/test/snap.old /home/test/snap fi + tests.session -u test restore execute: | # TODO: extend the test to work for root when the policy is fixed for admin_home_t -- cgit v1.2.3 From 9c578cda39b69ba9423572861b28a38273184517 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 30 Sep 2021 12:11:58 +0200 Subject: cmd/libsnap-confine-private: use a buffer on stack, rather than a malloc'ed one Signed-off-by: Maciej Borzecki --- cmd/libsnap-confine-private/device-cgroup-support.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/libsnap-confine-private/device-cgroup-support.c b/cmd/libsnap-confine-private/device-cgroup-support.c index cbf0d9e276..0889f0a6cc 100644 --- a/cmd/libsnap-confine-private/device-cgroup-support.c +++ b/cmd/libsnap-confine-private/device-cgroup-support.c @@ -286,7 +286,8 @@ static struct rlimit _sc_cgroup_v2_adjust_memlock_limit(void) { static bool _sc_is_snap_cgroup(const char *group) { /* make a copy as basename may modify its input */ - char *copy SC_CLEANUP(sc_cleanup_string) = sc_strdup(group); + char copy[PATH_MAX] = {0}; + strncpy(copy, group, sizeof(copy) - 1); char *leaf = basename(copy); if (!sc_startswith(leaf, "snap.")) { return false; -- cgit v1.2.3 From c46e323cac846bccabb712c3beeeb3a336be44c4 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 30 Sep 2021 12:12:35 +0200 Subject: spread: echo before listing bpf maps Signed-off-by: Maciej Borzecki --- spread.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/spread.yaml b/spread.yaml index 5402a74f2b..9038258b27 100644 --- a/spread.yaml +++ b/spread.yaml @@ -459,6 +459,7 @@ debug-each: | cat "/sys/fs/cgroup/devices/$cgroup_dev/devices.list" || true fi else + echo "# snap confinement device filtering maps" ls -l /sys/fs/bpf/snap || true fi -- cgit v1.2.3 From fe2dbfd25fae67f0e2a655d4a2717bba497fac9d Mon Sep 17 00:00:00 2001 From: Tao Wang Date: Sat, 2 Oct 2021 20:47:26 +1000 Subject: Add '/com/canonical/dbusmenu' path access to 'unit7' interface Allow the path in unity7 inteface will fix systray issue in some applications. Signed-off-by: Tao Wang --- interfaces/builtin/unity7.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interfaces/builtin/unity7.go b/interfaces/builtin/unity7.go index 3dc9a71a13..04fb6f6c36 100644 --- a/interfaces/builtin/unity7.go +++ b/interfaces/builtin/unity7.go @@ -359,28 +359,28 @@ dbus (send) # dbusmenu dbus (send) bus=session - path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*} + path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}} interface=com.canonical.dbusmenu member="{LayoutUpdated,ItemsPropertiesUpdated}" peer=(name=org.freedesktop.DBus, label=unconfined), dbus (receive) bus=session - path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*} + path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}} interface="{com.canonical.dbusmenu,org.freedesktop.DBus.Properties}" member=Get* peer=(label=unconfined), dbus (receive) bus=session - path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*} + path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}} interface=com.canonical.dbusmenu member="{AboutTo*,Event*}" peer=(label=unconfined), dbus (receive) bus=session - path=/{MenuBar{,/[0-9A-F]*},com/canonical/menu/[0-9A-F]*} + path=/{MenuBar{,/[0-9A-F]*},com/canonical/{menu/[0-9A-F]*,dbusmenu}} interface=org.freedesktop.DBus.Introspectable member=Introspect peer=(label=unconfined), -- cgit v1.2.3 From 2e1ebf284e4a89a4c7c1aa9bd1e5b081593642a7 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 4 Oct 2021 10:30:55 +0200 Subject: tests/main: disable tests that require user session on ubuntu-14.04 Since this requires a more recent systemd Signed-off-by: Maciej Borzecki --- tests/main/cwd/task.yaml | 3 +++ tests/main/parallel-install-basic/task.yaml | 3 +++ tests/main/security-private-tmp/task.yaml | 3 ++- tests/main/snap-confine-privs/task.yaml | 3 ++- tests/main/snap-confine-undesired-mode-group/task.yaml | 3 +++ tests/main/snapshot-users/task.yaml | 3 +++ 6 files changed, 16 insertions(+), 2 deletions(-) diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index 48ff921783..9cd3c13b8f 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -7,6 +7,9 @@ details: | to the view inside the mount namespace. If the directory does not exist the special fallback /var/lib/snapd/void is used. +# ubuntu-14.04: the test sets up a user session, which requires more recent systemd +systems: [-ubuntu-14.04-*] + prepare: | "$TESTSTOOLS"/snaps-state install-local test-snapd-sh tests.session -u test prepare diff --git a/tests/main/parallel-install-basic/task.yaml b/tests/main/parallel-install-basic/task.yaml index 566c084258..63991f4d06 100644 --- a/tests/main/parallel-install-basic/task.yaml +++ b/tests/main/parallel-install-basic/task.yaml @@ -1,5 +1,8 @@ summary: Checks for parallel installation of a local snap files +# ubuntu-14.04: the test sets up a user session, which requires more recent systemd +systems: [-ubuntu-14.04-*] + prepare: | # ensure we have no snap user data directory yet rm -rf /home/test/snap diff --git a/tests/main/security-private-tmp/task.yaml b/tests/main/security-private-tmp/task.yaml index 3fb4fbfd85..c3cb2653df 100644 --- a/tests/main/security-private-tmp/task.yaml +++ b/tests/main/security-private-tmp/task.yaml @@ -1,7 +1,8 @@ summary: Ensure that the security rules for private tmp are in place. # ppc64el disabled because of https://bugs.launchpad.net/snappy/+bug/1655594 -systems: [-ubuntu-core-*, -ubuntu-*-ppc64el] +# ubuntu-14.04: the test sets up a user session, which requires more recent systemd +systems: [-ubuntu-core-*, -ubuntu-*-ppc64el, -ubuntu-14.04-*] environment: SNAP_INSTALL_DIR: $(pwd)/snap-install-dir diff --git a/tests/main/snap-confine-privs/task.yaml b/tests/main/snap-confine-privs/task.yaml index 632ab005e5..babba413a1 100644 --- a/tests/main/snap-confine-privs/task.yaml +++ b/tests/main/snap-confine-privs/task.yaml @@ -10,7 +10,8 @@ details: | # This test is not executed on a core system simply because of the hassle of # building the support C program. In the future it might be improved with the # use of the classic snap where we just use classic to build the helper. -systems: [-ubuntu-core-*] +# ubuntu-14.04: the test sets up a user session, which requires more recent systemd +systems: [-ubuntu-core-*, -ubuntu-14.04-*] environment: # This is used to abbreviate some of the paths below. diff --git a/tests/main/snap-confine-undesired-mode-group/task.yaml b/tests/main/snap-confine-undesired-mode-group/task.yaml index b78658b70c..1c996d46ec 100644 --- a/tests/main/snap-confine-undesired-mode-group/task.yaml +++ b/tests/main/snap-confine-undesired-mode-group/task.yaml @@ -1,5 +1,8 @@ summary: the snap-{run,confine,exec} chain does not create files with undesired properties. +# ubuntu-14.04: the test sets up a user session, which requires more recent systemd +systems: [-ubuntu-14.04-*] + prepare: | # Install a snap with opengl and joystick plugs. # This gives us all of the usual snap-confine configuration, along with all diff --git a/tests/main/snapshot-users/task.yaml b/tests/main/snapshot-users/task.yaml index c444ab8d45..bda95a9c24 100644 --- a/tests/main/snapshot-users/task.yaml +++ b/tests/main/snapshot-users/task.yaml @@ -1,5 +1,8 @@ summary: Check that the basic snapshots functionality works for different users +# ubuntu-14.04: the test sets up a user session, which requires more recent systemd +systems: [-ubuntu-14.04-*] + prepare: | snap install test-snapd-sh tests.session -u test prepare -- cgit v1.2.3 From d8fe0f5d9fd5983584f00728d746ebd7477ecbcd Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 1 Oct 2021 13:03:41 +0200 Subject: o/devicestate, o/servicestate: update gadget assets and cmdline when remodeling Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicestate.go | 64 ++++++++---------------- overlord/devicestate/devicestate_remodel_test.go | 63 +++++++++++++++++------ overlord/snapstate/snapstate.go | 47 +++++++++++++++++ 3 files changed, 115 insertions(+), 59 deletions(-) diff --git a/overlord/devicestate/devicestate.go b/overlord/devicestate/devicestate.go index 0ad0fcc28b..f4d98dd6c4 100644 --- a/overlord/devicestate/devicestate.go +++ b/overlord/devicestate/devicestate.go @@ -401,23 +401,34 @@ type modelSnapsForRemodel struct { newModelSnap *asserts.ModelSnap } -func remodelKernelOrBaseTasks(ctx context.Context, st *state.State, ms modelSnapsForRemodel, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { +func (ms *modelSnapsForRemodel) canHaveUC18PinnedTrack() bool { + return ms.newModelSnap != nil && + (ms.newModelSnap.SnapType == "kernel" || ms.newModelSnap.SnapType == "gadget") +} + +func remodelEssentialSnapTasks(ctx context.Context, st *state.State, ms modelSnapsForRemodel, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { userID := 0 newModelSnapChannel, err := modelSnapChannelFromDefaultOrPinnedTrack(ms.new, ms.newModelSnap) if err != nil { return nil, err } + + addExistingSnapTasks := snapstate.LinkNewBaseOrKernel + if ms.newModelSnap != nil && ms.newModelSnap.SnapType == "gadget" { + addExistingSnapTasks = snapstate.SwitchToNewGadget + } + if ms.currentSnap == ms.newSnap { + // new model uses the same base, kernel or gadget snap changed := false if ms.new.Grade() != asserts.ModelGradeUnset { // UC20 models can specify default channel for all snaps - // including base and kernel - // new model uses the same base or kernel + // including base, kernel and gadget changed, err = installedSnapChannelChanged(st, ms.newSnap, newModelSnapChannel) if err != nil { return nil, err } - } else if ms.newModelSnap != nil && ms.newModelSnap.SnapType == "kernel" { + } else if ms.canHaveUC18PinnedTrack() { // UC18 models could only specify track for the kernel // and gadget snaps changed = ms.currentModelSnap.PinnedTrack != ms.newModelSnap.PinnedTrack @@ -464,7 +475,7 @@ func remodelKernelOrBaseTasks(ctx context.Context, st *state.State, ms modelSnap // the update is not a simple // switch-snap-channel return ts, nil - } else { + } else if ms.newModelSnap.SnapType == "kernel" || ms.newModelSnap.SnapType == "base" { // in other cases make sure that the // kernel or base is linked and available return snapstate.AddLinkNewBaseOrKernel(st, ts) @@ -472,42 +483,7 @@ func remodelKernelOrBaseTasks(ctx context.Context, st *state.State, ms modelSnap } } } - return snapstate.LinkNewBaseOrKernel(st, ms.newSnap) -} - -func remodelGadgetTasks(ctx context.Context, st *state.State, ms modelSnapsForRemodel, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { - userID := 0 - newGadgetChannel, err := modelSnapChannelFromDefaultOrPinnedTrack(ms.new, ms.newModelSnap) - if err != nil { - return nil, err - } - if ms.currentSnap == ms.newSnap { - // already installed, but may be using a different channel - changed := false - if ms.new.Grade() != asserts.ModelGradeUnset { - // UC20 models can specify default channel for all snaps - // including the gadget - changed, err = installedSnapChannelChanged(st, ms.newSnap, newGadgetChannel) - if err != nil { - return nil, err - } - } else { - // pre UC20 models could only specify a track for the - // gadget - changed = ms.currentModelSnap.PinnedTrack != ms.newModelSnap.PinnedTrack - } - if changed { - return snapstateUpdateWithDeviceContext(st, ms.newSnap, - &snapstate.RevisionOptions{Channel: newGadgetChannel}, - userID, snapstate.Flags{NoReRefresh: true}, deviceCtx, fromChange) - } - return nil, nil - } - - // install the new gadget - return snapstateInstallWithDeviceContext(ctx, st, ms.newSnap, - &snapstate.RevisionOptions{Channel: newGadgetChannel}, - userID, snapstate.Flags{}, deviceCtx, fromChange) + return addExistingSnapTasks(st, ms.newSnap) } func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Model, deviceCtx snapstate.DeviceContext, fromChange string) ([]*state.TaskSet, error) { @@ -522,7 +498,7 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo newSnap: new.Kernel(), newModelSnap: new.KernelSnap(), } - ts, err := remodelKernelOrBaseTasks(ctx, st, kms, deviceCtx, fromChange) + ts, err := remodelEssentialSnapTasks(ctx, st, kms, deviceCtx, fromChange) if err != nil { return nil, err } @@ -537,7 +513,7 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo newSnap: new.Base(), newModelSnap: new.BaseSnap(), } - ts, err = remodelKernelOrBaseTasks(ctx, st, bms, deviceCtx, fromChange) + ts, err = remodelEssentialSnapTasks(ctx, st, bms, deviceCtx, fromChange) if err != nil { return nil, err } @@ -552,7 +528,7 @@ func remodelTasks(ctx context.Context, st *state.State, current, new *asserts.Mo newSnap: new.Gadget(), newModelSnap: new.GadgetSnap(), } - ts, err = remodelGadgetTasks(ctx, st, gms, deviceCtx, fromChange) + ts, err = remodelEssentialSnapTasks(ctx, st, gms, deviceCtx, fromChange) if err != nil { return nil, err } diff --git a/overlord/devicestate/devicestate_remodel_test.go b/overlord/devicestate/devicestate_remodel_test.go index c83f162780..1f3e9d28ed 100644 --- a/overlord/devicestate/devicestate_remodel_test.go +++ b/overlord/devicestate/devicestate_remodel_test.go @@ -2095,7 +2095,12 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelGadgetBaseSnaps(c *C) }) } -func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSnaps(c *C) { +func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstalledSnaps(c *C) { + // remodel switches to a new set of kernel, base and gadget snaps, but + // those happen to be already installed and tracking the right channels, + // this scenario can happen when the system has gone through many + // remodels and the new gadget, kernel, base snaps were required by one + // of the prior models s.state.Lock() defer s.state.Unlock() s.state.Set("seeded", true) @@ -2172,13 +2177,16 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna Active: true, TrackingChannel: "20/stable", }) - // new gadget and kernel which are already installed - for _, alreadyInstalledName := range []string{"pc-kernel-new", "core20-new"} { + // new gadget, base and kernel which are already installed + for _, alreadyInstalledName := range []string{"pc-new", "pc-kernel-new", "core20-new"} { snapYaml := "name: pc-kernel-new\nversion: 1\ntype: kernel\n" channel := "20/stable" - if alreadyInstalledName == "core20-new" { + switch alreadyInstalledName { + case "core20-new": snapYaml = "name: core20-new\nversion: 1\ntype: base\n" channel = "latest/stable" + case "pc-new": + snapYaml = "name: pc-new\nversion: 1\ntype: gadget\n" } si := &snap.SideInfo{ RealName: alreadyInstalledName, @@ -2211,8 +2219,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna "default-channel": "20/stable", }, map[string]interface{}{ - "name": "pc", - "id": snaptest.AssertedSnapID("pc"), + "name": "pc-new", + "id": snaptest.AssertedSnapID("pc-new"), "type": "gadget", "default-channel": "20", }, @@ -2223,8 +2231,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") tl := chg.Tasks() - // 2 snaps (2 tasks for each) + recovery system (2 tasks) + set-model - c.Assert(tl, HasLen, 2*2+2+1) + // 2 snaps (2 tasks for each) + gadget (3 tasks) + recovery system (2 tasks) + set-model + c.Assert(tl, HasLen, 2*2+3+2+1) deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil) c.Assert(err, IsNil) @@ -2241,9 +2249,12 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna tLinkKernel := tl[1] tPrepareBase := tl[2] tLinkBase := tl[3] - tCreateRecovery := tl[4] - tFinalizeRecovery := tl[5] - tSetModel := tl[6] + tPrepareGadget := tl[4] + tUpdateAssets := tl[5] + tUpdateCmdline := tl[6] + tCreateRecovery := tl[7] + tFinalizeRecovery := tl[8] + tSetModel := tl[9] // check the tasks c.Assert(tPrepareKernel.Kind(), Equals, "prepare-snap") @@ -2256,6 +2267,17 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna c.Assert(tPrepareBase.WaitTasks(), HasLen, 1) c.Assert(tLinkBase.Kind(), Equals, "link-snap") c.Assert(tLinkBase.Summary(), Equals, `Make snap "core20-new" (222) available to the system during remodel`) + c.Assert(tPrepareGadget.Kind(), Equals, "prepare-snap") + c.Assert(tPrepareGadget.Summary(), Equals, `Prepare snap "pc-new" (222) for remodel`) + c.Assert(tPrepareGadget.WaitTasks(), HasLen, 1) + c.Assert(tUpdateAssets.Kind(), Equals, "update-gadget-assets") + c.Assert(tUpdateAssets.Summary(), Equals, `Update assets from gadget "pc-new" (222) for remodel`) + c.Assert(tUpdateAssets.WaitTasks(), HasLen, 2) + c.Assert(tUpdateCmdline.Kind(), Equals, "update-gadget-cmdline") + c.Assert(tUpdateCmdline.Summary(), Equals, `Update kernel command line from gadget "pc-new" (222) for remodel`) + c.Assert(tUpdateCmdline.WaitTasks(), HasLen, 1) + c.Assert(tLinkBase.Kind(), Equals, "link-snap") + c.Assert(tLinkBase.Summary(), Equals, `Make snap "core20-new" (222) available to the system during remodel`) expectedLabel := now.Format("20060102") c.Assert(tCreateRecovery.Kind(), Equals, "create-recovery-system") c.Assert(tCreateRecovery.Summary(), Equals, fmt.Sprintf("Create recovery system with label %q", expectedLabel)) @@ -2267,7 +2289,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna c.Assert(tPrepareKernel.WaitTasks(), HasLen, 0) c.Assert(tLinkKernel.WaitTasks(), DeepEquals, []*state.Task{ tPrepareKernel, - tPrepareBase, + tPrepareGadget, tCreateRecovery, tFinalizeRecovery, }) @@ -2278,21 +2300,29 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna tPrepareBase, tLinkKernel, }) + c.Assert(tPrepareGadget.WaitTasks(), DeepEquals, []*state.Task{ + tPrepareBase, + }) + c.Assert(tUpdateAssets.WaitTasks(), DeepEquals, []*state.Task{ + tPrepareGadget, + tLinkBase, + }) c.Assert(tCreateRecovery.WaitTasks(), DeepEquals, []*state.Task{ // last snap of the download chain (in this case prepare & link // for existing snaps) - tPrepareBase, + tPrepareGadget, }) c.Assert(tFinalizeRecovery.WaitTasks(), DeepEquals, []*state.Task{ // recovery system being created tCreateRecovery, // last snap of the download chain (see above) - tPrepareBase, + tPrepareGadget, }) // setModel waits for everything in the change c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{ tPrepareKernel, tLinkKernel, tPrepareBase, tLinkBase, + tPrepareGadget, tUpdateAssets, tUpdateCmdline, tCreateRecovery, tFinalizeRecovery, }) // verify recovery system setup data on appropriate tasks @@ -2302,11 +2332,14 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna c.Assert(systemSetupData, DeepEquals, map[string]interface{}{ "label": expectedLabel, "directory": filepath.Join(boot.InitramfsUbuntuSeedDir, "systems", expectedLabel), - "snap-setup-tasks": []interface{}{tPrepareKernel.ID(), tPrepareBase.ID()}, + "snap-setup-tasks": []interface{}{tPrepareKernel.ID(), tPrepareBase.ID(), tPrepareGadget.ID()}, }) } func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSnapsDifferentChannelThanNew(c *C) { + // kernel and base snaps that are used by the new model are already + // installed, but track a different channel from what is set in the new + // model s.state.Lock() defer s.state.Unlock() s.state.Set("seeded", true) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 3ea477398f..f5f0d60bf4 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -2228,6 +2228,53 @@ func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, return ts, nil } +func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) { + var snapst SnapState + err := Get(st, name, &snapst) + if err == state.ErrNoState { + return nil, &snap.NotInstalledError{Snap: name} + } + if err != nil { + return nil, err + } + + if err := CheckChangeConflict(st, name, nil); err != nil { + return nil, err + } + + info, err := snapst.CurrentInfo() + if err != nil { + return nil, err + } + + if info.Type() != snap.TypeGadget { + return nil, fmt.Errorf("cannot link type %v", info.Type()) + } + + snapsup := &SnapSetup{ + SideInfo: snapst.CurrentSideInfo(), + Flags: snapst.Flags.ForSnapSetup(), + Type: info.Type(), + PlugsOnly: len(info.Slots) == 0, + InstanceKey: snapst.InstanceKey, + } + + prepareSnap := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s) for remodel"), snapsup.InstanceName(), snapst.Current)) + prepareSnap.Set("snap-setup", &snapsup) + + gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current)) + gadgetUpdate.WaitFor(prepareSnap) + gadgetUpdate.Set("snap-setup-task", prepareSnap.ID()) + gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current)) + gadgetCmdline.WaitFor(gadgetUpdate) + gadgetCmdline.Set("snap-setup-task", prepareSnap.ID()) + + // we need this for remodel + ts := state.NewTaskSet(prepareSnap, gadgetUpdate, gadgetCmdline) + ts.MarkEdge(prepareSnap, DownloadAndChecksDoneEdge) + return ts, nil +} + // Enable sets a snap to the active state func Enable(st *state.State, name string) (*state.TaskSet, error) { var snapst SnapState -- cgit v1.2.3 From f76398ef6e3fe3898dfa944969c7c07783511319 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 4 Oct 2021 13:08:56 +0200 Subject: o/devicestate: add comments on specific remodel unit tests Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicestate_remodel_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/overlord/devicestate/devicestate_remodel_test.go b/overlord/devicestate/devicestate_remodel_test.go index 1f3e9d28ed..e88730055c 100644 --- a/overlord/devicestate/devicestate_remodel_test.go +++ b/overlord/devicestate/devicestate_remodel_test.go @@ -2803,6 +2803,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna } func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsTrackingDifferentChannelThanDefaultSameAsNew(c *C) { + // essential snaps from new model are already installed and track + // channels different than declared in the old model, but already the + // same as in the new one s.state.Lock() defer s.state.Unlock() s.state.Set("seeded", true) @@ -2977,6 +2980,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsTrackingDifferentCh } func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsAlreadyInstalledAndLocal(c *C) { + // remodel when the essential snaps declared in new model are already + // installed, but have a local revision s.state.Lock() defer s.state.Unlock() s.state.Set("seeded", true) @@ -3148,6 +3153,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsAlreadyInstalledAnd } func (s *deviceMgrRemodelSuite) TestRemodelUC20NoDownloadSimpleChannelSwitch(c *C) { + // remodel when a channel declared in new model carries the same + // revision as already installed, so there is no full fledged, but a + // simple channel switch s.state.Lock() defer s.state.Unlock() s.state.Set("seeded", true) -- cgit v1.2.3 From 64abc4d324c96126190ebb8431920e2e0a4a8840 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 4 Oct 2021 15:04:42 +0200 Subject: tests/main: actually use tests.session for executing things as a user Signed-off-by: Maciej Borzecki --- tests/main/cwd/task.yaml | 2 +- tests/main/parallel-install-basic/task.yaml | 30 ++++++++++++++-------------- tests/main/selinux-snap-restorecon/task.yaml | 6 +++--- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index 9cd3c13b8f..dbe37dbfb5 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -44,7 +44,7 @@ execute: | # namespace but has permissions preventing the user to enter it (e.g. # via a symlink attack in /tmp) is remapped to a special directory. # FIXME: su doesn't have /snap/bin in PATH. - test "$(cd /root && su -c "snap run test-snapd-sh.sh -c pwd" test)" = "/var/lib/snapd/void" + test "$(cd /root && tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void" # Since the void directory is used when there are insufficient permissions # to enter the regular directory we must be able to go there in the first diff --git a/tests/main/parallel-install-basic/task.yaml b/tests/main/parallel-install-basic/task.yaml index 63991f4d06..9194deebd8 100644 --- a/tests/main/parallel-install-basic/task.yaml +++ b/tests/main/parallel-install-basic/task.yaml @@ -21,15 +21,15 @@ execute: | "$TESTSTOOLS"/snaps-state install-local test-snapd-sh "$TESTSTOOLS"/snaps-state install-local-as test-snapd-sh test-snapd-sh_foo - su -l -c '! test -d ~/snap/test-snapd-sh' test - su -l -c '! test -d ~/snap/test-snapd-sh_foo' test + tests.session -u test exec sh -c '! test -d ~/snap/test-snapd-sh' + tests.session -u test exec sh -c '! test -d ~/snap/test-snapd-sh_foo' - su -l -c 'test-snapd-sh_foo.sh -c "echo foo"' test | MATCH foo - su -l -c 'test -d ~/snap/test-snapd-sh' test - su -l -c 'test -d ~/snap/test-snapd-sh_foo' test + tests.session -u test exec sh -c 'test-snapd-sh_foo.sh -c "echo foo"' | MATCH foo + tests.session -u test exec sh -c 'test -d ~/snap/test-snapd-sh' + tests.session -u test exec sh -c 'test -d ~/snap/test-snapd-sh_foo' # instance environment variables are correctly set up - su -l -c 'test-snapd-sh_foo.sh -c "env"' test > snap_foo-env.txt + tests.session -u test exec sh -c 'test-snapd-sh_foo.sh -c "env"' test > snap_foo-env.txt MATCH 'SNAP_INSTANCE_NAME=test-snapd-sh_foo' < snap_foo-env.txt MATCH 'SNAP_NAME=test-snapd-sh' < snap_foo-env.txt MATCH 'SNAP_INSTANCE_KEY=foo' < snap_foo-env.txt @@ -40,7 +40,7 @@ execute: | MATCH 'SNAP_USER_COMMON=/home/test/snap/test-snapd-sh_foo/common' < snap_foo-env.txt # and non-instance one's are too - su -l -c 'test-snapd-sh.sh -c env' test > snap-env.txt + tests.session -u test exec sh -c 'test-snapd-sh.sh -c env' test > snap-env.txt MATCH 'SNAP_INSTANCE_NAME=test-snapd-sh' < snap-env.txt MATCH 'SNAP_NAME=test-snapd-sh' < snap-env.txt MATCH 'SNAP_INSTANCE_KEY=$' < snap-env.txt @@ -57,15 +57,15 @@ execute: | echo "Make sure snap data writes and reads work" # instance can access its data - su -l -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_COMMON/foobar/data'" test | MATCH canary-instance + tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_COMMON/foobar/data'" | MATCH canary-instance # non-instance sees its data - su -l -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/data'" test | MATCH canary-regular + tests.session -u test exec sh -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/data'" | MATCH canary-regular # instance can write data - su -l -c "test-snapd-sh_foo.sh -c 'echo hello from instance \$SNAP_INSTANCE_NAME > \$SNAP_COMMON/foobar/hello'" test + tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'echo hello from instance \$SNAP_INSTANCE_NAME > \$SNAP_COMMON/foobar/hello'" MATCH 'hello from instance test-snapd-sh_foo' < /var/snap/test-snapd-sh_foo/common/foobar/hello # and the file is not visible in non instance snap - su -l -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/hello || true'" test 2>&1 | MATCH 'cat: /var/snap/test-snapd-sh/common/foobar/hello: No such file or directory' + tests.session -u test exec sh -c "test-snapd-sh.sh -c 'cat \$SNAP_COMMON/foobar/hello || true'" 2>&1 | MATCH 'cat: /var/snap/test-snapd-sh/common/foobar/hello: No such file or directory' echo "Make sure snap user data writes work" echo canary-instance-snap > /home/test/snap/test-snapd-sh_foo/x1/canary @@ -74,16 +74,16 @@ execute: | chown test:test /home/test/snap/test-snapd-sh_foo/common/canary # instance snap can write to user data - su -l -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_DATA/data'" test + tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_DATA/data'" MATCH 'hello user data from test-snapd-sh_foo' < /home/test/snap/test-snapd-sh_foo/x1/data # the file not present in non-instance snap data not test -f /home/test/snap/test-snapd-sh/x1/data # instance snap can write to common user data - su -l -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_COMMON/data'" test + tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'echo hello user data from \$SNAP_INSTANCE_NAME > \$SNAP_USER_COMMON/data'" MATCH 'hello user data from test-snapd-sh_foo' < /home/test/snap/test-snapd-sh_foo/common/data # the file not present in non-instance snap data not test -f /home/test/snap/test-snapd-sh/common/data - su -l -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_COMMON/canary'" test | MATCH canary-instance-common - su -l -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_DATA/canary'" test | MATCH canary-instance-snap + tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_COMMON/canary'" | MATCH canary-instance-common + tests.session -u test exec sh -c "test-snapd-sh_foo.sh -c 'cat \$SNAP_USER_DATA/canary'" | MATCH canary-instance-snap diff --git a/tests/main/selinux-snap-restorecon/task.yaml b/tests/main/selinux-snap-restorecon/task.yaml index e588232d2d..fa9f74930b 100644 --- a/tests/main/selinux-snap-restorecon/task.yaml +++ b/tests/main/selinux-snap-restorecon/task.yaml @@ -24,7 +24,7 @@ execute: | # TODO: use snap debug sandbox-features once selinux backend is added test ! -d /home/test/snap - su -c "test-snapd-sh.sh -c 'touch \$SNAP_USER_DATA/foo'" test + tests.session -u test exec sh -c "test-snapd-sh.sh -c 'touch \$SNAP_USER_DATA/foo'" test -d /home/test/snap echo "The snap user directory and data inside has the right context" @@ -41,7 +41,7 @@ execute: | ls -dZ /home/test/snap | MATCH ':unlabeled_t:' echo "It gets restored recursively" - su -c "test-snapd-sh.sh -c 'id -Z'" test + tests.session -u test exec sh -c "test-snapd-sh.sh -c 'id -Z'" ls -dZ /home/test/snap /home/test/snap/test-snapd-sh /home/test/snap/test-snapd-sh/current/foo > test-labels MATCH '^.*:snappy_home_t:.*/home/test/snap$' < test-labels @@ -50,7 +50,7 @@ execute: | echo "Restoring happens only when the context of \$HOME/snap is incorrect" chcon -t unlabeled_t -R /home/test/snap/test-snapd-sh/current/foo - su -c "test-snapd-sh.sh -c 'id -Z'" test + tests.session -u test exec sh -c "test-snapd-sh.sh -c 'id -Z'" ls -dZ /home/test/snap /home/test/snap/test-snapd-sh /home/test/snap/test-snapd-sh/current/foo > test-labels MATCH '^.*:snappy_home_t:.*/home/test/snap$' < test-labels -- cgit v1.2.3 From 4f5e09cb4cdce98f2de59dbd7a7f6935e36b28a1 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 4 Oct 2021 15:45:26 +0200 Subject: tests/main/cwd: try su -l Signed-off-by: Maciej Borzecki --- tests/main/cwd/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index dbe37dbfb5..191c2a9c65 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -44,7 +44,7 @@ execute: | # namespace but has permissions preventing the user to enter it (e.g. # via a symlink attack in /tmp) is remapped to a special directory. # FIXME: su doesn't have /snap/bin in PATH. - test "$(cd /root && tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void" + test "$(cd /root && su -l -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void" # Since the void directory is used when there are insufficient permissions # to enter the regular directory we must be able to go there in the first -- cgit v1.2.3 From 01a4ca79d3080f26a3e65f401c3a862426eedb07 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 6 Oct 2021 15:54:47 +0200 Subject: overlord: managers test for uc20 remodel to old gadget Signed-off-by: Maciej Borzecki --- overlord/managers_test.go | 216 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 214 insertions(+), 2 deletions(-) diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 8aa63174f6..158aa5f0c8 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -4291,6 +4291,15 @@ func validateRecoverySystemTasks(c *C, tasks []*state.Task, label string) int { return i } +func validateGadgetSwitchTasks(c *C, tasks []*state.Task, label, rev string) int { + var i int + c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update assets from gadget %q (%s) for remodel`, label, rev)) + i++ + c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Update kernel command line from gadget %q (%s) for remodel`, label, rev)) + i++ + return i +} + // byReadyTime sorts a list of tasks by their "ready" time type byReadyTime []*state.Task @@ -5676,14 +5685,18 @@ volumes: } type mockUpdater struct { - onUpdate error + updateCalls int + onUpdate error } func (m *mockUpdater) Backup() error { return nil } func (m *mockUpdater) Rollback() error { return nil } -func (m *mockUpdater) Update() error { return m.onUpdate } +func (m *mockUpdater) Update() error { + m.updateCalls++ + return m.onUpdate +} func (s *mgrsSuite) TestRemodelSwitchToDifferentGadget(c *C) { bloader := bootloadertest.Mock("mock", c.MkDir()) @@ -6182,6 +6195,13 @@ type: gadget base: core20 ` +const oldPcGadgetSnapYaml = ` +version: 1.0 +name: pc +type: gadget +base: core20 +` + const pcKernelSnapYaml = ` version: 1.0 name: pc-kernel @@ -6200,6 +6220,32 @@ name: snapd type: snapd ` +const oldPcGadgetYamlForRemodel = ` +volumes: + pc: + schema: gpt + bootloader: grub + structure: + - name: ubuntu-seed + filesystem: vfat + type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B + role: system-seed + size: 100M + content: + - source: grubx64.efi + target: grubx64.efi + - name: ubuntu-boot + type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B + role: system-boot + filesystem: ext4 + size: 100M + - name: ubuntu-data + role: system-data + type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B + filesystem: ext4 + size: 500M +` + const grubBootConfig = "# Snapd-Boot-Config-Edition: 1\n" var ( @@ -6211,10 +6257,17 @@ var ( {"bootx64.efi", "content"}, {"grubx64.efi", "content"}, } + oldPcGadgetFiles = append(pcGadgetFiles, [][]string{ + {"meta/gadget.yaml", oldPcGadgetYamlForRemodel}, + // SHA3-384: 7e5c973da86f7398deffd45b9225175da1dd6ae8fcffa1a20219b32bab9f4846da10e823736cd818ceada74d35337c98 + {"grubx64.efi", "old-gadget-content"}, + {"cmdline.extra", "foo bar baz"}, + }...) pcKernelFiles = [][]string{ {"kernel.efi", "kernel-efi"}, } snapYamlsForRemodel = map[string]string{ + "old-pc": oldPcGadgetSnapYaml, "pc": pcGadgetSnapYaml, "pc-kernel": pcKernelSnapYaml, "core20": core20SnapYaml, @@ -6222,6 +6275,7 @@ var ( "baz": "version: 1.0\nname: baz\nbase: core20", } snapFilesForRemodel = map[string][][]string{ + "old-pc": oldPcGadgetFiles, "pc": pcGadgetFiles, "pc-kernel": pcKernelFiles, } @@ -7194,6 +7248,164 @@ func (s *mgrsSuite) TestRemodelUC20DifferentBaseChannel(c *C) { validateRefreshTasks(c, tasks[i:], "core20", "33", noConfigure) } +func (s *mgrsSuite) TestRemodelUC20BackToPreviousGadget(c *C) { + s.testRemodelUC20WithRecoverySystemSimpleSetUp(c) + c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "proc"), 0755), IsNil) + restore := osutil.MockProcCmdline(filepath.Join(dirs.GlobalRootDir, "proc/cmdline")) + defer restore() + newModel := s.brands.Model("can0nical", "my-model", uc20ModelDefaults, map[string]interface{}{ + "snaps": []interface{}{ + map[string]interface{}{ + "name": "pc-kernel", + "id": fakeSnapID("pc-kernel"), + "type": "kernel", + "default-channel": "20", + }, + map[string]interface{}{ + "name": "old-pc", + "id": fakeSnapID("old-pc"), + "type": "gadget", + "default-channel": "20/edge", + }, + }, + "revision": "1", + }) + bl, err := bootloader.Find(boot.InitramfsUbuntuSeedDir, &bootloader.Options{Role: bootloader.RoleRecovery}) + c.Assert(err, IsNil) + + st := s.o.State() + st.Lock() + defer st.Unlock() + + a11, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ + "series": "16", + "snap-name": "old-pc", + "snap-id": fakeSnapID("old-pc"), + "publisher-id": "can0nical", + "timestamp": time.Now().Format(time.RFC3339), + }, nil, "") + c.Assert(err, IsNil) + c.Assert(assertstate.Add(st, a11), IsNil) + c.Assert(s.storeSigning.Add(a11), IsNil) + + s.makeInstalledSnapInStateForRemodel(c, "old-pc", snap.R(1), "20/edge") + + now := time.Now() + expectedLabel := now.Format("20060102") + + updater := &mockUpdater{} + restore = gadget.MockUpdaterForStructure(func(ps *gadget.LaidOutStructure, rootDir, rollbackDir string, observer gadget.ContentUpdateObserver) (gadget.Updater, error) { + // use a mock updater pretends an update was applied + return updater, nil + }) + defer restore() + + chg, err := devicestate.Remodel(st, newModel) + c.Assert(err, IsNil) + st.Unlock() + err = s.o.Settle(settleTimeout) + st.Lock() + c.Assert(err, IsNil, Commentf(s.logbuf.String())) + // gadget update has not been applied yet + c.Check(updater.updateCalls, Equals, 0) + + // first comes a reboot to the new recovery system + c.Check(chg.Status(), Equals, state.DoingStatus, Commentf("remodel change failed: %v", chg.Err())) + c.Check(devicestate.RemodelingChange(st), NotNil) + restarting, kind := st.Restarting() + c.Check(restarting, Equals, true) + c.Assert(kind, Equals, state.RestartSystemNow) + m, err := boot.ReadModeenv("") + c.Assert(err, IsNil) + c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"1234", expectedLabel}) + c.Check(m.GoodRecoverySystems, DeepEquals, []string{"1234"}) + vars, err := bl.GetBootVars("try_recovery_system", "recovery_system_status") + c.Assert(err, IsNil) + c.Assert(vars, DeepEquals, map[string]string{ + "try_recovery_system": expectedLabel, + "recovery_system_status": "try", + }) + // simulate successful reboot to recovery and back + state.MockRestarting(st, state.RestartUnset) + // this would be done by snap-bootstrap in initramfs + err = bl.SetBootVars(map[string]string{ + "try_recovery_system": expectedLabel, + "recovery_system_status": "tried", + }) + c.Assert(err, IsNil) + // reset, so that after-reboot handling of tried system is executed + s.o.DeviceManager().ResetToPostBootState() + st.Unlock() + err = s.o.DeviceManager().Ensure() + st.Lock() + c.Assert(err, IsNil) + + st.Unlock() + err = s.o.Settle(settleTimeout) + st.Lock() + c.Assert(err, IsNil) + // update has been called for all 3 structures because of the remodel + // policy (there is no content bump, so there would be no updates + // otherwise) + c.Check(updater.updateCalls, Equals, 3) + // a reboot was requested, as mock updated were applied + restarting, kind = st.Restarting() + c.Check(restarting, Equals, true) + c.Assert(kind, Equals, state.RestartSystem) + + m, err = boot.ReadModeenv("") + c.Assert(err, IsNil) + c.Check([]string(m.CurrentKernelCommandLines), DeepEquals, []string{ + "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1", + "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz", + }) + + // pretend we have the right command line + c.Assert(ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "proc/cmdline"), + []byte("snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz"), 0444), + IsNil) + + // run post boot code again + s.o.DeviceManager().ResetToPostBootState() + st.Unlock() + err = s.o.DeviceManager().Ensure() + st.Lock() + c.Assert(err, IsNil) + + // verify command lines again + m, err = boot.ReadModeenv("") + c.Assert(err, IsNil) + c.Check([]string(m.CurrentKernelCommandLines), DeepEquals, []string{ + "snapd_recovery_mode=run console=ttyS0 console=tty1 panic=-1 foo bar baz", + }) + + c.Check(chg.Status(), Equals, state.DoneStatus, Commentf("remodel change failed: %v", chg.Err())) + + var snapst snapstate.SnapState + err = snapstate.Get(st, "old-pc", &snapst) + c.Assert(err, IsNil) + // and the gadget tracking channel is the same as in the model + c.Check(snapst.TrackingChannel, Equals, "20/edge") + + // ensure sorting is correct + tasks := chg.Tasks() + sort.Sort(byReadyTime(tasks)) + + var i int + + // prepare first + c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Prepare snap "old-pc" (1) for remodel`)) + i++ + // then recovery system + i += validateRecoverySystemTasks(c, tasks[i:], expectedLabel) + // then gadget switch with update of assets and kernel command line + i += validateGadgetSwitchTasks(c, tasks[i:], "old-pc", "1") + // finally new model assertion + c.Assert(tasks[i].Summary(), Equals, fmt.Sprintf(`Set new model assertion`)) + i++ + c.Check(i, Equals, len(tasks)) +} + func (s *mgrsSuite) TestCheckRefreshFailureWithConcurrentRemoveOfConnectedSnap(c *C) { hookMgr := s.o.HookManager() c.Assert(hookMgr, NotNil) -- cgit v1.2.3 From c6ee8071617b7d0c82a4a746b66bd0f60a8cfe89 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 7 Oct 2021 10:58:53 +0200 Subject: overlord: update remodel managers test to use the restart pacakge Signed-off-by: Maciej Borzecki --- overlord/managers_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 293213dca7..bb628d1a25 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -7327,9 +7327,9 @@ func (s *mgrsSuite) TestRemodelUC20BackToPreviousGadget(c *C) { // first comes a reboot to the new recovery system c.Check(chg.Status(), Equals, state.DoingStatus, Commentf("remodel change failed: %v", chg.Err())) c.Check(devicestate.RemodelingChange(st), NotNil) - restarting, kind := st.Restarting() + restarting, kind := restart.Pending(st) c.Check(restarting, Equals, true) - c.Assert(kind, Equals, state.RestartSystemNow) + c.Assert(kind, Equals, restart.RestartSystemNow) m, err := boot.ReadModeenv("") c.Assert(err, IsNil) c.Check(m.CurrentRecoverySystems, DeepEquals, []string{"1234", expectedLabel}) @@ -7341,7 +7341,7 @@ func (s *mgrsSuite) TestRemodelUC20BackToPreviousGadget(c *C) { "recovery_system_status": "try", }) // simulate successful reboot to recovery and back - state.MockRestarting(st, state.RestartUnset) + restart.MockPending(st, restart.RestartUnset) // this would be done by snap-bootstrap in initramfs err = bl.SetBootVars(map[string]string{ "try_recovery_system": expectedLabel, @@ -7364,9 +7364,9 @@ func (s *mgrsSuite) TestRemodelUC20BackToPreviousGadget(c *C) { // otherwise) c.Check(updater.updateCalls, Equals, 3) // a reboot was requested, as mock updated were applied - restarting, kind = st.Restarting() + restarting, kind = restart.Pending(st) c.Check(restarting, Equals, true) - c.Assert(kind, Equals, state.RestartSystem) + c.Assert(kind, Equals, restart.RestartSystem) m, err = boot.ReadModeenv("") c.Assert(err, IsNil) -- cgit v1.2.3 From f7b3f708ad90ade5399901570717a5677cfb00ed Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 7 Oct 2021 11:40:31 +0200 Subject: tests/main/cwd: runuser seems to work Signed-off-by: Maciej Borzecki --- tests/main/cwd/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index 191c2a9c65..d86277a31c 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -44,7 +44,7 @@ execute: | # namespace but has permissions preventing the user to enter it (e.g. # via a symlink attack in /tmp) is remapped to a special directory. # FIXME: su doesn't have /snap/bin in PATH. - test "$(cd /root && su -l -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void" + test "$(cd /root && runuser -u test -- sh -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void" # Since the void directory is used when there are insufficient permissions # to enter the regular directory we must be able to go there in the first -- cgit v1.2.3 From 5eeb693516234378a48ed8292b3b45986fa11029 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 8 Oct 2021 09:42:13 +0200 Subject: tests/main/cwd: skip void check on some distributions Signed-off-by: Maciej Borzecki --- tests/main/cwd/task.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index d86277a31c..acbe387788 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -44,7 +44,15 @@ execute: | # namespace but has permissions preventing the user to enter it (e.g. # via a symlink attack in /tmp) is remapped to a special directory. # FIXME: su doesn't have /snap/bin in PATH. - test "$(cd /root && runuser -u test -- sh -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void" + case "$SPREAD_SYSTEM" in + fedora-34-*|opensuse-tumbleweed-*) + # nothiing, we have to go through tests.session which always starts in + # the $HOME directory + ;; + *) + test "$(cd /root && runuser -u test -- sh -c "snap run test-snapd-sh.sh -c pwd" )" = "/var/lib/snapd/void" + ;; + esac # Since the void directory is used when there are insufficient permissions # to enter the regular directory we must be able to go there in the first -- cgit v1.2.3 From 85bf5d1e6e0b4aee92b48a8543fab59126e95805 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 8 Oct 2021 09:42:30 +0200 Subject: tests/main/snap-confine-drops-sys-admin: use tests.session Signed-off-by: Maciej Borzecki --- tests/main/snap-confine-drops-sys-admin/task.yaml | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/tests/main/snap-confine-drops-sys-admin/task.yaml b/tests/main/snap-confine-drops-sys-admin/task.yaml index 8e184b0d83..ce81426bd8 100644 --- a/tests/main/snap-confine-drops-sys-admin/task.yaml +++ b/tests/main/snap-confine-drops-sys-admin/task.yaml @@ -4,7 +4,8 @@ summary: ensure that snap-confine drops CAP_SYS_ADMIN for non-root # building the support C program. In the future it might be improved with the # use of the classic snap where we just use classic to build the helper. # Arch, CentOS, AMZN2, openSUSE do not have a static version of libcap. -systems: [-ubuntu-core-*, -arch-linux-*, -centos-*, -amazon-linux-*, -opensuse-* ] +# Ubuntu 14.04 does not have a proper session setup. +systems: [-ubuntu-core-*, -arch-linux-*, -centos-*, -amazon-linux-*, -opensuse-*, -ubuntu-14.04] environment: # This is used to abbreviate some of the paths below. @@ -33,8 +34,8 @@ execute: | # user. The "test" user inside the spread suite is guaranteed to have # UID/GID of 12345. First test that the program correctly detects that the # test user does not have CAP_SYS_ADMIN and root does. - su -l -c "$P/has-sys-admin" test | MATCH 'Does not have cap_sys_admin' - "$P/has-sys-admin" | MATCH 'Has cap_sys_admin' + tests.session -u test exec sh -c "$P/has-sys-admin" | MATCH 'Does not have cap_sys_admin' + "$P/has-sys-admin" | MATCH 'Has cap_sys_admin' echo "Running under snap-confine as non-root and root" # This is the same as the two above but it goes through snap-confine as @@ -43,17 +44,20 @@ execute: | # as there are two shell expansions done. Also, we are using "snap run # test-snapd-sh" in order to ensure that we can start the program even if # su/sudo's secure PATH does not contain the snap bin directory. - su -l -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" test | MATCH 'Does not have cap_sys_admin' + tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" | \ + MATCH 'Does not have cap_sys_admin' #shellcheck disable=SC2016 - snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin' + snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin' # We should preserve CAP_SYS_ADMIN when run under sudo from non-root or # root echo "Running under sudo and under snap-confine from non-root and root" - su -l -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" test | MATCH 'Has cap_sys_admin' - sudo snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin' + tests.session -u test exec sh -c "sudo snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" | \ + MATCH 'Has cap_sys_admin' + sudo snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Has cap_sys_admin' # We should drop CAP_SYS_ADMIN when run with sudo -u test echo "Running under sudo -u non-root under snap-confine from non-root and root" - su -l -c "sudo -u test snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" test | MATCH 'Does not have cap_sys_admin' - sudo -u test snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Does not have cap_sys_admin' + tests.session -u test exec sh -c "sudo -u test snap run test-snapd-sh.sh -c '\$SNAP_COMMON/has-sys-admin'" | \ + MATCH 'Does not have cap_sys_admin' + sudo -u test snap run test-snapd-sh.sh -c '$SNAP_COMMON/has-sys-admin' | MATCH 'Does not have cap_sys_admin' -- cgit v1.2.3 From 9850de7d6d9ecd082828b56841cf6bbd41bc0f30 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 8 Oct 2021 16:18:00 +0200 Subject: tests/main/non-home: use tests.session helper Signed-off-by: Maciej Borzecki --- tests/main/non-home/task.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/main/non-home/task.yaml b/tests/main/non-home/task.yaml index fabd4161f0..f475323b91 100644 --- a/tests/main/non-home/task.yaml +++ b/tests/main/non-home/task.yaml @@ -1,7 +1,8 @@ summary: Ensure running on none /home dirs gives a useful error -# limit to ubuntu for easier user creation -systems: [ubuntu-1*, ubuntu-2*] +# limit to ubuntu for easier user creation, skip 14.04 due to missing session +# setup +systems: [ubuntu-16*, ubuntu-18*, ubuntu-2*] environment: TUSER: jim @@ -11,19 +12,24 @@ environment: prepare: | echo "create a non home user" adduser --home "$THOME" "$TUSER" + tests.session -u test prepare + tests.session -u "$TUSER" prepare restore: | + tests.session -u "$TUSER" restore + tests.session -u test restore "$TESTSTOOLS"/user-state remove-with-group "$TUSER" + execute: | echo "Install a snap" snap install test-snapd-sh echo "Run as the test user (normal home dir)" - su -c "snap run test-snapd-sh.sh -c 'echo foo'" test | MATCH foo + tests.session -u test exec sh -c "snap run test-snapd-sh.sh -c 'echo foo'" | MATCH foo echo "Run as the non-home user (home dir outside of /home) - this will fail" - not su -c "snap run test-snapd-sh.sh -c 'echo foo'" "$TUSER" 2>stderr.log + not tests.session -u "$TUSER" exec sh -c "snap run test-snapd-sh.sh -c 'echo foo'" 2>stderr.log echo "Ensure we get a useful error message" MATCH "Sorry, home directories outside of /home" < stderr.log -- cgit v1.2.3 From 073af33d588629dae80fc797ffc359c8535a2fd1 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 8 Oct 2021 14:36:45 -0500 Subject: gadget/gadget.go: SaveDiskVolumesDeviceTraits needs to take a dir actually Sadly, since we need to save this information on UC20+ during install mode, we actually need to write to boot.InstallHostWritableDir, but we can't import boot into gadget where SaveDiskVolumesDeviceTraits is defined, so instead we need it to take an argument of the dir to save the file under. Signed-off-by: Ian Johnson --- gadget/gadget.go | 5 ++--- gadget/gadget_test.go | 10 +++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/gadget/gadget.go b/gadget/gadget.go index caf92f0da7..b1bad65a60 100644 --- a/gadget/gadget.go +++ b/gadget/gadget.go @@ -273,14 +273,13 @@ type DiskStructureDeviceTraits struct { // SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume / device // traits to a file on disk for later loading and verification. -func SaveDiskVolumesDeviceTraits(mapping map[string]DiskVolumeDeviceTraits) error { +func SaveDiskVolumesDeviceTraits(dir string, mapping map[string]DiskVolumeDeviceTraits) error { b, err := json.Marshal(mapping) if err != nil { return err } - // TODO: should this live in dirs? - filename := filepath.Join(dirs.SnapDeviceDir, "disk-mapping.json") + filename := filepath.Join(dir, "disk-mapping.json") if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil { return err diff --git a/gadget/gadget_test.go b/gadget/gadget_test.go index c54f0c730f..67c0cc1b18 100644 --- a/gadget/gadget_test.go +++ b/gadget/gadget_test.go @@ -3038,9 +3038,17 @@ func (s *gadgetYamlTestSuite) TestSaveLoadDiskVolumeDeviceTraits(c *C) { c.Assert(err, IsNil) c.Assert(mAbsent, HasLen, 0) - err = gadget.SaveDiskVolumesDeviceTraits(m) + // load looks in SnapDeviceDir since it is meant to be used during run mode + // when /var/lib/snapd/device/disk-mapping.json is the real version from + // ubuntu-data, but during install mode, we will need to save to the host + // ubuntu-data which is not located at /run/mnt/data or + // /var/lib/snapd/device, but rather + // /run/mnt/ubuntu-data/system-data/var/lib/snapd/device so this takes a + // directory argument when we save it + err = gadget.SaveDiskVolumesDeviceTraits(dirs.SnapDeviceDir, m) c.Assert(err, IsNil) + // now that it was saved to dirs.SnapDeviceDir, we can load it correctly m2, err := gadget.LoadDiskVolumesDeviceTraits() c.Assert(err, IsNil) -- cgit v1.2.3 From 7fedbe58f743d2a4cb57155e7773dd794ddcb656 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 8 Oct 2021 12:18:00 -0500 Subject: osutil/disks, many: switch to defining Partitions directly for MockDiskMapping This will be necessary for more complicated test cases in the gadget package, so time to rip this band-aid and get rid of the old methods. Signed-off-by: Ian Johnson --- bootloader/export_test.go | 42 ++++-- cmd/snap-bootstrap/cmd_initramfs_mounts_test.go | 162 +++++++++++++-------- osutil/disks/mockdisk.go | 183 ++++++++++++++---------- osutil/disks/mockdisk_test.go | 74 ++++++---- secboot/secboot_sb_test.go | 111 +++++++++----- 5 files changed, 371 insertions(+), 201 deletions(-) diff --git a/bootloader/export_test.go b/bootloader/export_test.go index 94fdfe622c..1330192b90 100644 --- a/bootloader/export_test.go +++ b/bootloader/export_test.go @@ -123,17 +123,41 @@ func MockLkFiles(c *C, rootdir string, opts *Options) (restore func()) { lkBootDisk := &disks.MockDiskMapping{ // mock the partition labels, since these structures won't have // filesystems, but they will have partition labels - PartitionLabelToPartUUID: map[string]string{ - "snapbootsel": "snapbootsel-partuuid", - "snapbootselbak": "snapbootselbak-partuuid", - "snaprecoverysel": "snaprecoverysel-partuuid", - "snaprecoveryselbak": "snaprecoveryselbak-partuuid", + Structure: []disks.Partition{ + { + PartitionLabel: "snapbootsel", + PartitionUUID: "snapbootsel-partuuid", + }, + { + PartitionLabel: "snapbootselbak", + PartitionUUID: "snapbootselbak-partuuid", + }, + { + PartitionLabel: "snaprecoverysel", + PartitionUUID: "snaprecoverysel-partuuid", + }, + { + PartitionLabel: "snaprecoveryselbak", + PartitionUUID: "snaprecoveryselbak-partuuid", + }, // for run mode kernel snaps - "boot_a": "boot-a-partuuid", - "boot_b": "boot-b-partuuid", + { + PartitionLabel: "boot_a", + PartitionUUID: "boot-a-partuuid", + }, + { + PartitionLabel: "boot_b", + PartitionUUID: "boot-b-partuuid", + }, // for recovery system kernel snaps - "boot_ra": "boot-ra-partuuid", - "boot_rb": "boot-rb-partuuid", + { + PartitionLabel: "boot_ra", + PartitionUUID: "boot-ra-partuuid", + }, + { + PartitionLabel: "boot_rb", + PartitionUUID: "boot-rb-partuuid", + }, }, DiskHasPartitions: true, DevNum: "lk-boot-disk-dev-num", diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go index c2f0dc2394..7d2733a770 100644 --- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go +++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go @@ -93,34 +93,64 @@ var ( NoSuid: true, } + seedPart = disks.Partition{ + FilesystemLabel: "ubuntu-seed", + PartitionUUID: "ubuntu-seed-partuuid", + } + + bootPart = disks.Partition{ + FilesystemLabel: "ubuntu-boot", + PartitionUUID: "ubuntu-boot-partuuid", + } + + savePart = disks.Partition{ + FilesystemLabel: "ubuntu-save", + PartitionUUID: "ubuntu-save-partuuid", + } + + dataPart = disks.Partition{ + FilesystemLabel: "ubuntu-data", + PartitionUUID: "ubuntu-data-partuuid", + } + + saveEncPart = disks.Partition{ + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "ubuntu-save-enc-partuuid", + } + + dataEncPart = disks.Partition{ + FilesystemLabel: "ubuntu-data-enc", + PartitionUUID: "ubuntu-data-enc-partuuid", + } + // a boot disk without ubuntu-save defaultBootDisk = &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data": "ubuntu-data-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + dataPart, }, DiskHasPartitions: true, DevNum: "default", } defaultBootWithSaveDisk = &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data": "ubuntu-data-partuuid", - "ubuntu-save": "ubuntu-save-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + dataPart, + savePart, }, DiskHasPartitions: true, DevNum: "default-with-save", } defaultEncBootDisk = &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + bootPart, + seedPart, + dataEncPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDev", @@ -2086,10 +2116,10 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataUnhappyNoS s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") defaultEncNoSaveBootDisk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + dataEncPart, // missing ubuntu-save }, DiskHasPartitions: true, @@ -3542,10 +3572,11 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb defer bootloader.Force(nil) defaultEncDiskNoBoot := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + // missing ubuntu-boot + dataEncPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDevNoBoot", @@ -3698,10 +3729,11 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb defer bootloader.Force(nil) defaultEncDiskNoBoot := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + // missing ubuntu-boot + dataEncPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDevNoBoot", @@ -4059,10 +4091,10 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedAbsentDataU // no ubuntu-data on the disk at all mockDiskNoData := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-save": "ubuntu-save-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + savePart, }, DiskHasPartitions: true, DevNum: "noDataUnenc", @@ -4239,12 +4271,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedUnencrypted // no ubuntu-data on the disk at all mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, // ubuntu-data is unencrypted but ubuntu-save is encrypted - "ubuntu-data": "ubuntu-data-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + dataPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "dataUnencSaveEnc", @@ -4372,12 +4404,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedEncryptedDa defer bootloader.Force(nil) mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, // ubuntu-data is encrypted but ubuntu-save is not - "ubuntu-save": "ubuntu-save-partuuid", + savePart, + dataEncPart, }, DiskHasPartitions: true, DevNum: "dataUnencSaveEnc", @@ -4627,12 +4659,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb bootloader.Force(bloader) defer bootloader.Force(nil) - // no ubuntu-data on the disk at all mockDiskNoData := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + // no ubuntu-data on the disk at all + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDev", @@ -5193,21 +5225,33 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedAttackerFS defer bootloader.Force(nil) mockDisk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + saveEncPart, + dataEncPart, }, DiskHasPartitions: true, DevNum: "bootDev", } attackerDisk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-attacker-partuuid", - "ubuntu-boot": "ubuntu-boot-attacker-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-attacker-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-attacker-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-seed", + PartitionUUID: "ubuntu-seed-attacker-partuuid", + }, + { + FilesystemLabel: "ubuntu-boot", + PartitionUUID: "ubuntu-boot-attacker-partuuid", + }, + { + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "ubuntu-save-enc-attacker-partuuid", + }, + { + FilesystemLabel: "ubuntu-data-enc", + PartitionUUID: "ubuntu-data-enc-attacker-partuuid", + }, }, DiskHasPartitions: true, DevNum: "attackerDev", @@ -5332,8 +5376,8 @@ func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C mockDiskMapping := map[disks.Mountpoint]*disks.MockDiskMapping{ {Mountpoint: boot.InitramfsUbuntuSeedDir}: { - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", + Structure: []disks.Partition{ + seedPart, }, DiskHasPartitions: true, }, @@ -5366,9 +5410,7 @@ func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C // also add the ubuntu-data and ubuntu-save fs labels to the // disk referenced by the ubuntu-seed partition disk := mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuSeedDir}] - disk.FilesystemLabelToPartUUID["ubuntu-boot"] = "ubuntu-boot-partuuid" - disk.FilesystemLabelToPartUUID["ubuntu-data"] = "ubuntu-data-partuuid" - disk.FilesystemLabelToPartUUID["ubuntu-save"] = "ubuntu-save-partuuid" + disk.Structure = append(disk.Structure, bootPart, savePart, dataPart) // and also add the /run/mnt/host/ubuntu-{boot,data,save} mountpoints // for cross-checking after mounting diff --git a/osutil/disks/mockdisk.go b/osutil/disks/mockdisk.go index 898827f92d..3ea070d47b 100644 --- a/osutil/disks/mockdisk.go +++ b/osutil/disks/mockdisk.go @@ -34,20 +34,19 @@ var _ = Disk(&MockDiskMapping{}) // DevNum must be a unique string per unique mocked disk, if only one disk is // being mocked it can be left empty. type MockDiskMapping struct { - // TODO: eliminate these manual mappings and instead switch all the users - // over to providing the full list of Partitions instead, but in the - // interest of smaller PR's we are doing that in a separate PR. - // FilesystemLabelToPartUUID is a mapping of the udev encoded filesystem - // labels to the expected partition uuids. - FilesystemLabelToPartUUID map[string]string - // PartitionLabelToPartUUID is a mapping of the udev encoded partition - // labels to the expected partition uuids. - PartitionLabelToPartUUID map[string]string - DiskHasPartitions bool - - // TODO: add an exported list of Partitions here - - // static variables for the disk + // TODO: should this be automatically determined if Structure has non-zero + // len instead? + DiskHasPartitions bool + + // Structure is the set of partitions or structures on the disk. These + // partitions are used with Partitions() as well as + // FindMatchingPartitionWith{Fs,Part}Label + Structure []Partition + + // static variables for the disk that must be unique for different disks, + // but note that there are potentially multiple DevNode values that could + // map to a single disk, but it's not worth encoding that complexity here + // by making DevNodes a list DevNum string DevNode string DevPath string @@ -58,19 +57,13 @@ type MockDiskMapping struct { func (d *MockDiskMapping) FindMatchingPartitionWithFsLabel(label string) (Partition, error) { // TODO: this should just iterate over the static list when that is a thing osutil.MustBeTestBinary("mock disks only to be used in tests") - if partuuid, ok := d.FilesystemLabelToPartUUID[label]; ok { - part := Partition{ - PartitionUUID: partuuid, - FilesystemLabel: label, - } - // add the partition label too if we have one for this partition uuid - for partlabel, partuuid2 := range d.PartitionLabelToPartUUID { - if partuuid2 == partuuid { - part.PartitionLabel = partlabel - } + + for _, p := range d.Structure { + if p.FilesystemLabel == label { + return p, nil } - return part, nil } + return Partition{}, PartitionNotFoundError{ SearchType: "filesystem-label", SearchQuery: label, @@ -80,21 +73,14 @@ func (d *MockDiskMapping) FindMatchingPartitionWithFsLabel(label string) (Partit // FindMatchingPartitionUUIDWithPartLabel returns a matching PartitionUUID // for the specified filesystem label if it exists. Part of the Disk interface. func (d *MockDiskMapping) FindMatchingPartitionWithPartLabel(label string) (Partition, error) { - // TODO: this should just iterate over the static list when that is a thing osutil.MustBeTestBinary("mock disks only to be used in tests") - if partuuid, ok := d.PartitionLabelToPartUUID[label]; ok { - part := Partition{ - PartitionUUID: partuuid, - PartitionLabel: label, - } - // add the filesystem label too if we have one for this partition uuid - for fsLabel, partuuid2 := range d.FilesystemLabelToPartUUID { - if partuuid2 == partuuid { - part.FilesystemLabel = fsLabel - } + + for _, p := range d.Structure { + if p.PartitionLabel == label { + return p, nil } - return part, nil } + return Partition{}, PartitionNotFoundError{ SearchType: "partition-label", SearchQuery: label, @@ -118,39 +104,7 @@ func (d *MockDiskMapping) FindMatchingPartitionUUIDWithPartLabel(label string) ( } func (d *MockDiskMapping) Partitions() ([]Partition, error) { - // TODO: this should just return the static list that was in the mapping - // when that is a thing - - // dynamically build up a list of partitions with the mappings we were - // provided - parts := make([]Partition, 0, len(d.PartitionLabelToPartUUID)) - - partUUIDToPart := map[string]Partition{} - - // first populate with all the partition labels - for partLabel, partuuid := range d.PartitionLabelToPartUUID { - part := Partition{ - PartitionLabel: partLabel, - PartitionUUID: partuuid, - } - - partUUIDToPart[partuuid] = part - } - - for fsLabel, partuuid := range d.FilesystemLabelToPartUUID { - existingPart, ok := partUUIDToPart[partuuid] - if !ok { - parts = append(parts, Partition{ - FilesystemLabel: fsLabel, - PartitionUUID: partuuid, - }) - continue - } - existingPart.FilesystemLabel = fsLabel - parts = append(parts, existingPart) - } - - return parts, nil + return d.Structure, nil } // HasPartitions returns if the mock disk has partitions or not. Part of the @@ -202,19 +156,100 @@ type Mountpoint struct { IsDecryptedDevice bool } +func checkMockDiskMappingsForDuplicates(mockedDisks map[string]*MockDiskMapping) { + // we do the minimal amount of validation here, where if things are + // specified as non-zero value we check that they make sense, but we don't + // require that every field is set for every partition since many tests + // don't care about every field + + // check partition uuid's and partition labels for duplication inter-disk + // we could have valid cloned disks where the same partition uuid/label + // appears on two disks, but never on the same disk + for _, disk := range mockedDisks { + seenPartUUID := make(map[string]bool, len(disk.Structure)) + seenPartLabel := make(map[string]bool, len(disk.Structure)) + for _, p := range disk.Structure { + if p.PartitionUUID != "" { + if seenPartUUID[p.PartitionUUID] { + panic("mock error: disk has duplicated partition uuids in its structure") + } + seenPartUUID[p.PartitionUUID] = true + } + + if p.PartitionLabel != "" { + if seenPartLabel[p.PartitionLabel] { + panic("mock error: disk has duplicated partition labels in its structure") + } + seenPartLabel[p.PartitionLabel] = true + } + } + } + + // check major/minors across all structures + type majmin struct{ maj, min int } + seenMajorMinors := map[majmin]bool{} + for _, disk := range mockedDisks { + for _, p := range disk.Structure { + if p.Major == 0 && p.Minor == 0 { + continue + } + + m := majmin{maj: p.Major, min: p.Minor} + if seenMajorMinors[m] { + panic("mock error: duplicated major minor numbers for partitions in disk mapping") + } + seenMajorMinors[m] = true + } + } + + // check device paths across all structures + seenDevPaths := map[string]bool{} + for _, disk := range mockedDisks { + for _, p := range disk.Structure { + if p.KernelDevicePath == "" { + continue + } + if seenDevPaths[p.KernelDevicePath] { + panic("mock error: duplicated kernel device paths for partitions in disk mapping") + } + seenDevPaths[p.KernelDevicePath] = true + } + } + + // check device nodes across all structures + seendDevNodes := map[string]bool{} + for _, disk := range mockedDisks { + for _, p := range disk.Structure { + if p.KernelDevicePath == "" { + continue + } + + if seendDevNodes[p.KernelDeviceNode] { + panic("mock error: duplicated kernel device nodes for partitions in disk mapping") + } + seendDevNodes[p.KernelDeviceNode] = true + } + } + + // no checking of filesystem label/uuid since those could be duplicated as + // they exist independent of any other structure +} + // MockDeviceNameDisksToPartitionMapping will mock DiskFromDeviceName such that // the provided map of device names to mock disks is used instead of the actual // implementation using udev. -func MockDeviceNameDisksToPartitionMapping(mockedMountPoints map[string]*MockDiskMapping) (restore func()) { +func MockDeviceNameDisksToPartitionMapping(mockedDisks map[string]*MockDiskMapping) (restore func()) { osutil.MustBeTestBinary("mock disks only to be used in tests") - // note that devices can have many names that are recognized by + checkMockDiskMappingsForDuplicates(mockedDisks) + + // note that devices can have multiple "names" that are recognized by // udev/kernel, so we don't do any validation of the mapping here like we do - // for MockMountPointDisksToPartitionMapping + // for MockMountPointDisksToPartitionMapping and MockDevicePathDisksToPartitionMapping old := diskFromDeviceName diskFromDeviceName = func(deviceName string) (Disk, error) { - disk, ok := mockedMountPoints[deviceName] + disk, ok := mockedDisks[deviceName] if !ok { return nil, fmt.Errorf("device name %q not mocked", deviceName) } diff --git a/osutil/disks/mockdisk_test.go b/osutil/disks/mockdisk_test.go index 79ca4cfd5d..46bf7a49b3 100644 --- a/osutil/disks/mockdisk_test.go +++ b/osutil/disks/mockdisk_test.go @@ -42,8 +42,11 @@ func (s *mockDiskSuite) SetUpTest(c *C) { func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { // one disk with different device names d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: true, DevNum: "d1", @@ -52,8 +55,11 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { } d2 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label2": "part2", + Structure: []disks.Partition{ + { + FilesystemLabel: "label2", + PartitionUUID: "part2", + }, }, DiskHasPartitions: true, DevNum: "d2", @@ -104,16 +110,22 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesUniqueness(c *C) { // two different disks with different DevNum's d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: true, DevNum: "d1", } d2 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: false, DevNum: "d2", @@ -152,8 +164,11 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesUniquen func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesConsistency(c *C) { d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: true, DevNum: "d1", @@ -177,22 +192,24 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesConsist func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) { d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", - }, - PartitionLabelToPartUUID: map[string]string{ - "part-label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + PartitionLabel: "part-label1", + }, }, DiskHasPartitions: true, DevNum: "d1", } d2 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label2": "part2", - }, - PartitionLabelToPartUUID: map[string]string{ - "part-label2": "part2", + Structure: []disks.Partition{ + { + FilesystemLabel: "label2", + PartitionUUID: "part2", + PartitionLabel: "part-label2", + }, }, DiskHasPartitions: true, DevNum: "d2", @@ -322,10 +339,19 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) { func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingDecryptedDevices(c *C) { d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-part", - "ubuntu-boot": "ubuntu-boot-part", - "ubuntu-data-enc": "ubuntu-data-enc-part", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-seed", + PartitionUUID: "ubuntu-seed-part", + }, + { + FilesystemLabel: "ubuntu-boot", + PartitionUUID: "ubuntu-boot-part", + }, + { + FilesystemLabel: "ubuntu-data-enc", + PartitionUUID: "ubuntu-data-enc-part", + }, }, DiskHasPartitions: true, DevNum: "d1", diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go index f334d68409..6866987a46 100644 --- a/secboot/secboot_sb_test.go +++ b/secboot/secboot_sb_test.go @@ -323,18 +323,22 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { // defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } - mockDiskWithoutAnyDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{}, - } + mockDiskWithoutAnyDev := &disks.MockDiskMapping{} mockDiskWithUnencDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "name": "unenc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "name", + PartitionUUID: "unenc-dev-partuuid", + }, }, } @@ -479,10 +483,18 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { if tc.hasEncdev { fsLabel += "-enc" } - partuuid, ok := tc.disk.FilesystemLabelToPartUUID[fsLabel] + + partuuid := "" if !tc.skipDiskEnsureCheck { - c.Assert(ok, Equals, true) + for _, p := range tc.disk.Structure { + if p.FilesystemLabel == fsLabel { + partuuid = p.PartitionUUID + break + } + } + c.Assert(partuuid, Not(Equals), "", Commentf("didn't find fs label %s in disk", fsLabel)) } + devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid) expKeyPath := tc.keyfile @@ -1126,9 +1138,7 @@ func mockSbTPMConnection(c *C, tpmErr error) (*sb_tpm2.Connection, func()) { } func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) { - disk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{}, - } + disk := &disks.MockDiskMapping{} unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`) c.Check(unlockRes, DeepEquals, secboot.UnlockResult{}) @@ -1136,8 +1146,11 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) { func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) { disk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-save-enc": "123-123-123", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "123-123-123", + }, }, } restore := secboot.MockRandomKernelUUID(func() string { @@ -1165,8 +1178,11 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) { func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) { disk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-save-enc": "123-123-123", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "123-123-123", + }, }, } restore := secboot.MockRandomKernelUUID(func() string { @@ -1200,8 +1216,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr( defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } defaultDevice := "name" @@ -1247,8 +1266,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1An defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1481,8 +1503,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1539,8 +1564,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1591,8 +1619,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1641,8 +1672,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Al defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1698,8 +1732,11 @@ func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey, defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1763,8 +1800,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1(c defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1817,8 +1857,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyBadJ defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } -- cgit v1.2.3 From b096b4fbf9ec118644de0d07a921ec5797dec313 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 8 Oct 2021 15:33:03 -0500 Subject: osutil/disks: set hasPartitions from diskFromUdevProps This is used in DiskFromDeviceName and DiskFromDevicePath, and the issue is that if the returned disk did not have hasPartitions set to true, then calls to disk.Partitions() would always hard-code returning nil, which is wrong for many disks. There is still a wart that needs to be sorted out around mapper devices of the sort we get by mounting a .img file with partitions via kpartx at least, since there we don't have real partitions show up underneath the root "disk" device in /dev, not sure the proper thing to do there yet unfortunately... Signed-off-by: Ian Johnson --- osutil/disks/disks_linux.go | 21 ++++++++++++--- osutil/disks/disks_linux_test.go | 57 ++++++++++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index 7f2c05ac43..f73f3ceb2b 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -144,11 +144,24 @@ func diskFromUdevProps(deviceIdentifier string, deviceIDType string, props map[s // create the full path by pre-pending /sys, since udev doesn't include /sys devpath = filepath.Join(dirs.SysfsDir, devpath) + // TODO: this doesn't seem to work for /dev/mapper/loop1p1 devices like we + // get in the spread test for uc20-create-partitions* tests, needs more + // investigation + + // check if the device has partitions by attempting to actually search for + // them in /sys with the DEVPATH and DEVNAME + + paths, err := filepath.Glob(filepath.Join(devpath, filepath.Base(devname)+"*")) + if err != nil { + return nil, fmt.Errorf("internal error with glob pattern: %v", err) + } + return &disk{ - major: major, - minor: minor, - devname: devname, - devpath: devpath, + major: major, + minor: minor, + devname: devname, + devpath: devpath, + hasPartitions: len(paths) != 0, }, nil } diff --git a/osutil/disks/disks_linux_test.go b/osutil/disks/disks_linux_test.go index f12a5dcd6c..fb8f5f8170 100644 --- a/osutil/disks/disks_linux_test.go +++ b/osutil/disks/disks_linux_test.go @@ -89,8 +89,11 @@ var ( } ) -func createVirtioDevicesInSysfs(c *C, devsToPartition map[string]bool) { - diskDir := filepath.Join(dirs.SysfsDir, virtioDiskDevPath) +func createVirtioDevicesInSysfs(c *C, path string, devsToPartition map[string]bool) { + if path == "" { + path = virtioDiskDevPath + } + diskDir := filepath.Join(dirs.SysfsDir, path) for dev, isPartition := range devsToPartition { err := os.MkdirAll(filepath.Join(diskDir, dev), 0755) c.Assert(err, IsNil) @@ -111,7 +114,7 @@ func (s *diskSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) } -func (s *diskSuite) TestDiskFromNameHappy(c *C) { +func (s *diskSuite) TestDiskFromDeviceNameHappy(c *C) { const sdaSysfsPath = "/devices/pci0000:00/0000:00:01.1/0000:01:00.1/ata1/host0/target0:0:0/0:0:0:0/block/sda" restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) { c.Assert(typeOpt, Equals, "--name") @@ -131,13 +134,29 @@ func (s *diskSuite) TestDiskFromNameHappy(c *C) { c.Assert(d.Dev(), Equals, "1:2") c.Assert(d.KernelDeviceNode(), Equals, "/dev/sda") c.Assert(d.KernelDevicePath(), Equals, filepath.Join(dirs.SysfsDir, sdaSysfsPath)) + // it doesn't have any partitions since we didn't mock any in sysfs + c.Assert(d.HasPartitions(), Equals, false) + + // if we mock some sysfs partitions then it has partitions when we it has + // some partitions on it it + createVirtioDevicesInSysfs(c, sdaSysfsPath, map[string]bool{ + "sda1": true, + "sda2": true, + }) + + d, err = disks.DiskFromDeviceName("sda") + c.Assert(err, IsNil) + c.Assert(d.Dev(), Equals, "1:2") + c.Assert(d.KernelDeviceNode(), Equals, "/dev/sda") + c.Assert(d.HasPartitions(), Equals, true) } -func (s *diskSuite) TestDiskFromPathHappy(c *C) { +func (s *diskSuite) TestDiskFromDevicePathHappy(c *C) { const vdaSysfsPath = "/devices/pci0000:00/0000:00:04.0/virtio2/block/vdb" + fullSysPath := filepath.Join("/sys", vdaSysfsPath) restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) { c.Assert(typeOpt, Equals, "--path") - c.Assert(dev, Equals, filepath.Join("/sys", vdaSysfsPath)) + c.Assert(dev, Equals, fullSysPath) return map[string]string{ "MAJOR": "1", "MINOR": "2", @@ -148,15 +167,31 @@ func (s *diskSuite) TestDiskFromPathHappy(c *C) { }) defer restore() - d, err := disks.DiskFromDevicePath(filepath.Join("/sys", vdaSysfsPath)) + d, err := disks.DiskFromDevicePath(fullSysPath) c.Assert(err, IsNil) c.Assert(d.Dev(), Equals, "1:2") c.Assert(d.KernelDeviceNode(), Equals, "/dev/vdb") // note that we don't always prepend exactly /sys, we use dirs.SysfsDir c.Assert(d.KernelDevicePath(), Equals, filepath.Join(dirs.SysfsDir, vdaSysfsPath)) + + // it doesn't have any partitions since we didn't mock any in sysfs + c.Assert(d.HasPartitions(), Equals, false) + + // if we mock some sysfs partitions then it has partitions when we it has + // some partitions on it it + createVirtioDevicesInSysfs(c, vdaSysfsPath, map[string]bool{ + "vdb1": true, + "vdb2": true, + }) + + d, err = disks.DiskFromDevicePath(fullSysPath) + c.Assert(err, IsNil) + c.Assert(d.Dev(), Equals, "1:2") + c.Assert(d.KernelDeviceNode(), Equals, "/dev/vdb") + c.Assert(d.HasPartitions(), Equals, true) } -func (s *diskSuite) TestDiskFromNameUnhappyPartition(c *C) { +func (s *diskSuite) TestDiskFromDeviceNameUnhappyPartition(c *C) { restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) { c.Assert(typeOpt, Equals, "--name") c.Assert(dev, Equals, "sda1") @@ -172,7 +207,7 @@ func (s *diskSuite) TestDiskFromNameUnhappyPartition(c *C) { c.Assert(err, ErrorMatches, "device \"sda1\" is not a disk, it has DEVTYPE of \"partition\"") } -func (s *diskSuite) TestDiskFromNameUnhappyBadUdevOutput(c *C) { +func (s *diskSuite) TestDiskFromDeviceNameUnhappyBadUdevOutput(c *C) { restore := disks.MockUdevPropertiesForDevice(func(typeOpt, dev string) (map[string]string, error) { c.Assert(typeOpt, Equals, "--name") c.Assert(dev, Equals, "sda") @@ -346,7 +381,7 @@ func (s *diskSuite) TestDiskFromMountPointHappySinglePartitionIgnoresNonPartitio // create just the single valid partition in sysfs, and an invalid // non-partition device that we should ignore - createVirtioDevicesInSysfs(c, map[string]bool{ + createVirtioDevicesInSysfs(c, "", map[string]bool{ "vda4": true, "vda5": false, }) @@ -601,7 +636,7 @@ func (s *diskSuite) TestDiskFromMountPointPartitionsHappy(c *C) { defer restore() // create all 4 partitions as device nodes in sysfs - createVirtioDevicesInSysfs(c, map[string]bool{ + createVirtioDevicesInSysfs(c, "", map[string]bool{ "vda1": true, "vda2": true, "vda3": true, @@ -833,7 +868,7 @@ func (s *diskSuite) TestDiskFromMountPointDecryptedDevicePartitionsHappy(c *C) { c.Assert(err, IsNil) // mock the dev nodes in sysfs for the partitions - createVirtioDevicesInSysfs(c, map[string]bool{ + createVirtioDevicesInSysfs(c, "", map[string]bool{ "vda1": true, "vda2": true, "vda3": true, -- cgit v1.2.3 From 62e1fb430881d7af07b3023bc9a913ca268fb7dc Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 11 Oct 2021 13:05:56 +0200 Subject: tests/main/snap-confine-drops-sys-admin: properly skip 14.04, do prepare and restore Signed-off-by: Maciej Borzecki --- tests/main/snap-confine-drops-sys-admin/task.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/main/snap-confine-drops-sys-admin/task.yaml b/tests/main/snap-confine-drops-sys-admin/task.yaml index ce81426bd8..b53ca628cc 100644 --- a/tests/main/snap-confine-drops-sys-admin/task.yaml +++ b/tests/main/snap-confine-drops-sys-admin/task.yaml @@ -5,7 +5,7 @@ summary: ensure that snap-confine drops CAP_SYS_ADMIN for non-root # use of the classic snap where we just use classic to build the helper. # Arch, CentOS, AMZN2, openSUSE do not have a static version of libcap. # Ubuntu 14.04 does not have a proper session setup. -systems: [-ubuntu-core-*, -arch-linux-*, -centos-*, -amazon-linux-*, -opensuse-*, -ubuntu-14.04] +systems: [-ubuntu-core-*, -arch-linux-*, -centos-*, -amazon-linux-*, -opensuse-*, -ubuntu-14.04-*] environment: # This is used to abbreviate some of the paths below. @@ -24,6 +24,11 @@ prepare: | # in the base snap. gcc -Wall -Wextra -Werror ./has-sys-admin.c -o "$P/has-sys-admin" -lcap -static + tests.session -u test prepare + +restore: | + tests.session -u test restore + execute: | echo "The test executables files have the expected mode and ownership" #shellcheck disable=SC2012 -- cgit v1.2.3 From 7ea2047929511b06650049adcdeb18834d2e7181 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Mon, 11 Oct 2021 11:05:50 -0500 Subject: osutil/disks/disks_linux.go: rm TODO about more investigation More investigation isn't necessary for the spread test at least, the spread test is doing the right thing, but my test scripts are not allegedly. Signed-off-by: Ian Johnson --- osutil/disks/disks_linux.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index f73f3ceb2b..bcb1e77999 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -144,10 +144,6 @@ func diskFromUdevProps(deviceIdentifier string, deviceIDType string, props map[s // create the full path by pre-pending /sys, since udev doesn't include /sys devpath = filepath.Join(dirs.SysfsDir, devpath) - // TODO: this doesn't seem to work for /dev/mapper/loop1p1 devices like we - // get in the spread test for uc20-create-partitions* tests, needs more - // investigation - // check if the device has partitions by attempting to actually search for // them in /sys with the DEVPATH and DEVNAME -- cgit v1.2.3 From f203ad5abef8c32f1565c1f5ef44956d869910c9 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Tue, 12 Oct 2021 11:27:02 -0300 Subject: Not testing lxd snap anymore on i386 architecture As lxd snap is based on core20 now, the i386 architecture is not supported anymore. This change updates the tests which are validating lxd in i386. More info here: https://discuss.linuxcontainers.org/t/lxd-snap-transitioning-to-core20-and-losing-i386-support/10887 --- tests/main/download-timeout/task.yaml | 4 ++-- tests/main/lxd-services-smoke/task.yaml | 3 ++- tests/main/lxd/task.yaml | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/main/download-timeout/task.yaml b/tests/main/download-timeout/task.yaml index cd948b521f..f8800547d8 100644 --- a/tests/main/download-timeout/task.yaml +++ b/tests/main/download-timeout/task.yaml @@ -38,7 +38,7 @@ execute: | tc filter add dev ens4 parent ffff: protocol ip u32 match u32 0 0 police rate 32kbit burst 16k drop flowid :1 echo "Installing a large snap fails if connection is very slow" - snap install lxd 2>&1 | MATCH "download too slow:" + snap install --edge test-snapd-huge 2>&1 | MATCH "download too slow:" echo "Downloading a large snap fails too" - snap download lxd 2>&1 | MATCH "download too slow:" + snap download --edge test-snapd-huge 2>&1 | MATCH "download too slow:" diff --git a/tests/main/lxd-services-smoke/task.yaml b/tests/main/lxd-services-smoke/task.yaml index 1f207dd47d..c048c620c7 100644 --- a/tests/main/lxd-services-smoke/task.yaml +++ b/tests/main/lxd-services-smoke/task.yaml @@ -10,7 +10,8 @@ details: | https://bugs.launchpad.net/snapd/+bug/1899614, this test should never fail on any of the above. -systems: [ubuntu-18.04*, ubuntu-20.04*] +# ubuntu-18.04-32: i386 is not supported by lxd +systems: [ubuntu-18.04-64, ubuntu-20.04*] execute: | echo "Installing lxd snap" diff --git a/tests/main/lxd/task.yaml b/tests/main/lxd/task.yaml index 77fb36f2bb..f211ed8bd0 100644 --- a/tests/main/lxd/task.yaml +++ b/tests/main/lxd/task.yaml @@ -7,7 +7,8 @@ backends: [-autopkgtest] # Only run this on ubuntu 16+, lxd will not work on !ubuntu systems # currently nor on ubuntu 14.04 # TODO: enable for ubuntu-21.10+ once the lxd image is published -systems: [ubuntu-16*, ubuntu-18*, ubuntu-20*, ubuntu-21.04-*, ubuntu-core-*] +# ubuntu-18.04-32: i386 is not supported by lxd +systems: [ubuntu-16*, ubuntu-18.04-64, ubuntu-20*, ubuntu-21.04-*, ubuntu-core-*] # Start before anything else as it can take a really long time. priority: 1000 -- cgit v1.2.3 From 67bf3fff97a3fa13580493532e1b81a20bcde639 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Tue, 12 Oct 2021 11:38:57 -0300 Subject: Updating 2 more tests which are also failing --- tests/main/lxd-postrm-purge/task.yaml | 3 ++- tests/main/snapd-snap/task.yaml | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/main/lxd-postrm-purge/task.yaml b/tests/main/lxd-postrm-purge/task.yaml index 50ecbef7fd..de36c79ad9 100644 --- a/tests/main/lxd-postrm-purge/task.yaml +++ b/tests/main/lxd-postrm-purge/task.yaml @@ -3,7 +3,8 @@ summary: Check that package remove and purge works inside LXD containers # Since it's only apt remove --purge and lxd tests are rather long, limit to a # couple of systems only. The postrm purge is more thoroughly checked in # tests/main/postrm-purge. -systems: [ubuntu-18.04-*, ubuntu-20.04-*] +# ubuntu-18.04-32: i386 is not supported by lxd +systems: [ubuntu-18.04-64, ubuntu-20.04-*] # start early priority: 1000 diff --git a/tests/main/snapd-snap/task.yaml b/tests/main/snapd-snap/task.yaml index cac0a685c3..e9b8884938 100644 --- a/tests/main/snapd-snap/task.yaml +++ b/tests/main/snapd-snap/task.yaml @@ -25,6 +25,9 @@ systems: # locale, see https://bugs.launchpad.net/snapcraft/+bug/1922140 # a separate error occurs on centos 8, see https://bugs.launchpad.net/snapcraft/+bug/1922981 - -centos-* + # i386 is not supported by lxd + - -ubuntu-18.04-32 + # Start early as it takes a long time. priority: 100 -- cgit v1.2.3 From 2bbfca3b4793a8fa61d98a7a75ae438de034f5a8 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 13 Oct 2021 10:08:14 +0200 Subject: overlord/devicestate: update boot assets and cmdline when switching channel When switching the kernel or gadget during a remodel, make sure that boot assets and command line is updated as well. Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicestate.go | 25 ++++++-- overlord/devicestate/devicestate_remodel_test.go | 82 +++++++++++++++--------- overlord/snapstate/snapstate.go | 63 ++++++++++++++---- 3 files changed, 126 insertions(+), 44 deletions(-) diff --git a/overlord/devicestate/devicestate.go b/overlord/devicestate/devicestate.go index f4d98dd6c4..46a601245b 100644 --- a/overlord/devicestate/devicestate.go +++ b/overlord/devicestate/devicestate.go @@ -475,10 +475,27 @@ func remodelEssentialSnapTasks(ctx context.Context, st *state.State, ms modelSna // the update is not a simple // switch-snap-channel return ts, nil - } else if ms.newModelSnap.SnapType == "kernel" || ms.newModelSnap.SnapType == "base" { - // in other cases make sure that the - // kernel or base is linked and available - return snapstate.AddLinkNewBaseOrKernel(st, ts) + } else { + if ms.newModelSnap.SnapType == "kernel" || ms.newModelSnap.SnapType == "base" { + // in other cases make sure that + // the kernel or base is linked + // and available, and that + // kernel updates boot assets if + // needed + ts, err = snapstate.AddLinkNewBaseOrKernel(st, ts) + if err != nil { + return nil, err + } + } else if ms.newModelSnap.SnapType == "gadget" { + // gadget snaps may need gadget + // related tasks such as assets + // update or command line update + ts, err = snapstate.AddGadgetAssetsTasks(st, ts) + if err != nil { + return nil, err + } + } + return ts, nil } } } diff --git a/overlord/devicestate/devicestate_remodel_test.go b/overlord/devicestate/devicestate_remodel_test.go index 2f2151d69c..837aab6112 100644 --- a/overlord/devicestate/devicestate_remodel_test.go +++ b/overlord/devicestate/devicestate_remodel_test.go @@ -2232,8 +2232,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") tl := chg.Tasks() - // 2 snaps (2 tasks for each) + gadget (3 tasks) + recovery system (2 tasks) + set-model - c.Assert(tl, HasLen, 2*2+3+2+1) + // 2 snaps (2 tasks for each) + assets update from kernel + gadget (3 tasks) + recovery system (2 tasks) + set-model + c.Assert(tl, HasLen, 2*2+1+3+2+1) deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil) c.Assert(err, IsNil) @@ -2248,14 +2248,15 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal // check the tasks tPrepareKernel := tl[0] tLinkKernel := tl[1] - tPrepareBase := tl[2] - tLinkBase := tl[3] - tPrepareGadget := tl[4] - tUpdateAssets := tl[5] - tUpdateCmdline := tl[6] - tCreateRecovery := tl[7] - tFinalizeRecovery := tl[8] - tSetModel := tl[9] + tUpdateAssetsKernel := tl[2] + tPrepareBase := tl[3] + tLinkBase := tl[4] + tPrepareGadget := tl[5] + tUpdateAssets := tl[6] + tUpdateCmdline := tl[7] + tCreateRecovery := tl[8] + tFinalizeRecovery := tl[9] + tSetModel := tl[10] // check the tasks c.Assert(tPrepareKernel.Kind(), Equals, "prepare-snap") @@ -2263,6 +2264,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal c.Assert(tPrepareKernel.WaitTasks(), HasLen, 0) c.Assert(tLinkKernel.Kind(), Equals, "link-snap") c.Assert(tLinkKernel.Summary(), Equals, `Make snap "pc-kernel-new" (222) available to the system during remodel`) + c.Assert(tUpdateAssetsKernel.Kind(), Equals, "update-gadget-assets") + c.Assert(tUpdateAssetsKernel.Summary(), Equals, `Update assets from kernel "pc-kernel-new" (222) for remodel`) c.Assert(tPrepareBase.Kind(), Equals, "prepare-snap") c.Assert(tPrepareBase.Summary(), Equals, `Prepare snap "core20-new" (222) for remodel`) c.Assert(tPrepareBase.WaitTasks(), HasLen, 1) @@ -2299,7 +2302,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal }) c.Assert(tLinkBase.WaitTasks(), DeepEquals, []*state.Task{ tPrepareBase, - tLinkKernel, + tUpdateAssetsKernel, }) c.Assert(tPrepareGadget.WaitTasks(), DeepEquals, []*state.Task{ tPrepareBase, @@ -2321,7 +2324,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal }) // setModel waits for everything in the change c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{ - tPrepareKernel, tLinkKernel, + tPrepareKernel, tLinkKernel, tUpdateAssetsKernel, tPrepareBase, tLinkBase, tPrepareGadget, tUpdateAssets, tUpdateCmdline, tCreateRecovery, tFinalizeRecovery, @@ -2337,17 +2340,17 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal }) } -func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSnapsDifferentChannelThanNew(c *C) { - // kernel and base snaps that are used by the new model are already - // installed, but track a different channel from what is set in the new - // model +func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstalledSnapsDifferentChannelThanNew(c *C) { + // kernel, base and gadget snaps that are used by the new model are + // already installed, but track a different channel from what is set in + // the new model s.state.Lock() defer s.state.Unlock() s.state.Set("seeded", true) s.state.Set("refresh-privacy-key", "some-privacy-key") restore := devicestate.MockSnapstateUpdateWithDeviceContext(func(st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags, deviceCtx snapstate.DeviceContext, fromChange string) (*state.TaskSet, error) { - c.Assert(strutil.ListContains([]string{"core20-new", "pc-kernel-new"}, name), Equals, true, + c.Assert(strutil.ListContains([]string{"core20-new", "pc-kernel-new", "pc-new"}, name), Equals, true, Commentf("unexpected snap %q", name)) c.Check(flags.Required, Equals, false) c.Check(flags.NoReRefresh, Equals, true) @@ -2361,6 +2364,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna if name == "core20-new" { typ = "base" rev = snap.R(223) + } else if name == "pc-new" { + typ = "gadget" + rev = snap.R(224) } tSwitchChannel.Set("snap-setup", &snapstate.SnapSetup{ SideInfo: &snap.SideInfo{ @@ -2442,11 +2448,13 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna TrackingChannel: "20/stable", }) // new gadget and kernel which are already installed - for _, alreadyInstalledName := range []string{"pc-kernel-new", "core20-new"} { + for _, alreadyInstalledName := range []string{"pc-kernel-new", "core20-new", "pc-new"} { snapYaml := "name: pc-kernel-new\nversion: 1\ntype: kernel\n" channel := "other/other" if alreadyInstalledName == "core20-new" { snapYaml = "name: core20-new\nversion: 1\ntype: base\n" + } else if alreadyInstalledName == "pc-new" { + snapYaml = "name: pc-new\nversion: 1\ntype: gadget\n" } si := &snap.SideInfo{ RealName: alreadyInstalledName, @@ -2480,8 +2488,8 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna "default-channel": "20", }, map[string]interface{}{ - "name": "pc", - "id": snaptest.AssertedSnapID("pc"), + "name": "pc-new", + "id": snaptest.AssertedSnapID("pc-new"), "type": "gadget", "default-channel": "20", }, @@ -2499,8 +2507,10 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna c.Assert(chg.Summary(), Equals, "Refresh model assertion from revision 0 to 1") tl := chg.Tasks() - // 2 snaps with (snap switch channel + link snap) + recovery system (2 tasks) + set-model - c.Assert(tl, HasLen, 2*2+2+1) + // 2 snaps with (snap switch channel + link snap) + gadget assets update + // for the kernel snap + gadget snap (switch channel, assets update, cmdline update) + + // recovery system (2 tasks) + set-model + c.Assert(tl, HasLen, 2*2+1+3+2+1) deviceCtx, err := devicestate.DeviceCtx(s.state, tl[0], nil) c.Assert(err, IsNil) @@ -2515,11 +2525,15 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna // check the tasks tSwitchChannelKernel := tl[0] tLinkKernel := tl[1] - tSwitchChannelBase := tl[2] - tLinkBase := tl[3] - tCreateRecovery := tl[4] - tFinalizeRecovery := tl[5] - tSetModel := tl[6] + tUpdateAssetsFromKernel := tl[2] + tSwitchChannelBase := tl[3] + tLinkBase := tl[4] + tSwitchChannelGadget := tl[5] + tUpdateAssetsFromGadget := tl[6] + tUpdateCmdlineFromGadget := tl[7] + tCreateRecovery := tl[8] + tFinalizeRecovery := tl[9] + tSetModel := tl[10] // check the tasks c.Assert(tSwitchChannelKernel.Kind(), Equals, "switch-snap-channel") @@ -2527,11 +2541,20 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna c.Assert(tSwitchChannelKernel.WaitTasks(), HasLen, 0) c.Assert(tLinkKernel.Kind(), Equals, "link-snap") c.Assert(tLinkKernel.Summary(), Equals, `Make snap "pc-kernel-new" (222) available to the system during remodel`) + c.Assert(tUpdateAssetsFromKernel.Kind(), Equals, "update-gadget-assets") + c.Assert(tUpdateAssetsFromKernel.Summary(), Equals, `Update assets from kernel "pc-kernel-new" (222) for remodel`) c.Assert(tSwitchChannelBase.Kind(), Equals, "switch-snap-channel") c.Assert(tSwitchChannelBase.Summary(), Equals, `Switch core20-new channel to latest/stable`) c.Assert(tSwitchChannelBase.WaitTasks(), HasLen, 0) c.Assert(tLinkBase.Kind(), Equals, "link-snap") c.Assert(tLinkBase.Summary(), Equals, `Make snap "core20-new" (223) available to the system during remodel`) + c.Assert(tSwitchChannelGadget.Kind(), Equals, "switch-snap-channel") + c.Assert(tSwitchChannelGadget.Summary(), Equals, `Switch pc-new channel to 20/stable`) + c.Assert(tSwitchChannelGadget.WaitTasks(), HasLen, 0) + c.Assert(tUpdateAssetsFromGadget.Kind(), Equals, "update-gadget-assets") + c.Assert(tUpdateAssetsFromGadget.Summary(), Equals, `Update assets from gadget "pc-new" (224) for remodel`) + c.Assert(tUpdateCmdlineFromGadget.Kind(), Equals, "update-gadget-cmdline") + c.Assert(tUpdateCmdlineFromGadget.Summary(), Equals, `Update kernel command line from gadget "pc-new" (224) for remodel`) expectedLabel := now.Format("20060102") c.Assert(tCreateRecovery.Kind(), Equals, "create-recovery-system") c.Assert(tCreateRecovery.Summary(), Equals, fmt.Sprintf("Create recovery system with label %q", expectedLabel)) @@ -2554,8 +2577,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna }) // setModel waits for everything in the change c.Assert(tSetModel.WaitTasks(), DeepEquals, []*state.Task{ - tSwitchChannelKernel, tLinkKernel, + tSwitchChannelKernel, tLinkKernel, tUpdateAssetsFromKernel, tSwitchChannelBase, tLinkBase, + tSwitchChannelGadget, tUpdateAssetsFromGadget, tUpdateCmdlineFromGadget, tCreateRecovery, tFinalizeRecovery, }) // verify recovery system setup data on appropriate tasks @@ -3153,7 +3177,7 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20EssentialSnapsAlreadyInstalledAnd }) } -func (s *deviceMgrRemodelSuite) TestRemodelUC20NoDownloadSimpleChannelSwitch(c *C) { +func (s *deviceMgrRemodelSuite) TestRemodelUC20BaseNoDownloadSimpleChannelSwitch(c *C) { // remodel when a channel declared in new model carries the same // revision as already installed, so there is no full fledged, but a // simple channel switch diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 7266399f7e..b229b471c9 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -2217,36 +2217,55 @@ func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) { linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapst.Current)) linkSnap.Set("snap-setup-task", prepareSnap.ID()) linkSnap.WaitFor(prepareSnap) - // we need this for remodel ts := state.NewTaskSet(prepareSnap, linkSnap) ts.MarkEdge(prepareSnap, DownloadAndChecksDoneEdge) + if info.Type() == snap.TypeKernel { + // kernel snaps can carry boot assets + gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapst.Current)) + gadgetUpdate.Set("snap-setup-task", prepareSnap.ID()) + gadgetUpdate.WaitFor(linkSnap) + ts.AddTask(gadgetUpdate) + } return ts, nil } -func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) { - var snapSetupTaskID string +func findSnapSetupTask(tasks []*state.Task) (*state.Task, *SnapSetup, error) { var snapsup SnapSetup - allTasks := ts.Tasks() - for _, tsk := range allTasks { + for _, tsk := range tasks { if tsk.Has("snap-setup") { - snapSetupTaskID = tsk.ID() if err := tsk.Get("snap-setup", &snapsup); err != nil { - return nil, err + return nil, nil, err } - break + return tsk, &snapsup, nil } } - if snapSetupTaskID == "" { + return nil, nil, nil +} + +func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) { + allTasks := ts.Tasks() + snapSetupTask, snapsup, err := findSnapSetupTask(allTasks) + if err != nil { + return nil, err + } + if snapSetupTask == nil { return nil, fmt.Errorf("internal error: cannot identify task with snap-setup") } - linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system during remodel"), snapsup.InstanceName(), snapsup.SideInfo.Revision)) - linkSnap.Set("snap-setup-task", snapSetupTaskID) + linkSnap.Set("snap-setup-task", snapSetupTask.ID()) // wait for the last task in existing set linkSnap.WaitFor(allTasks[len(allTasks)-1]) ts.AddTask(linkSnap) + if snapsup.Type == snap.TypeKernel { + // kernel snaps can carry boot assets + gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision())) + gadgetUpdate.Set("snap-setup-task", snapSetupTask.ID()) + // wait for the last task in existing set + gadgetUpdate.WaitFor(linkSnap) + ts.AddTask(gadgetUpdate) + } return ts, nil } @@ -2297,6 +2316,28 @@ func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) { return ts, nil } +func AddGadgetAssetsTasks(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) { + allTasks := ts.Tasks() + snapSetupTask, snapsup, err := findSnapSetupTask(allTasks) + if err != nil { + return nil, err + } + if snapSetupTask == nil { + return nil, fmt.Errorf("internal error: cannot identify task with snap-setup") + } + gadgetUpdate := st.NewTask("update-gadget-assets", fmt.Sprintf(i18n.G("Update assets from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision())) + gadgetUpdate.Set("snap-setup-task", snapSetupTask.ID()) + // wait for the last task in existing set + gadgetUpdate.WaitFor(allTasks[len(allTasks)-1]) + ts.AddTask(gadgetUpdate) + // gadget snaps can carry kernel command line fragments + gadgetCmdline := st.NewTask("update-gadget-cmdline", fmt.Sprintf(i18n.G("Update kernel command line from %s %q (%s) for remodel"), snapsup.Type, snapsup.InstanceName(), snapsup.Revision())) + gadgetCmdline.Set("snap-setup-task", snapSetupTask.ID()) + gadgetCmdline.WaitFor(gadgetUpdate) + ts.AddTask(gadgetCmdline) + return ts, nil +} + // Enable sets a snap to the active state func Enable(st *state.State, name string) (*state.TaskSet, error) { var snapst SnapState -- cgit v1.2.3 From 2c18cd7d1d63f096fc7688225eb939501c9cfc7a Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 13 Oct 2021 11:27:19 +0200 Subject: o/snapstate: use an internal error Signed-off-by: Maciej Borzecki --- overlord/snapstate/snapstate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index b229b471c9..cb2aab44c4 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -2289,7 +2289,7 @@ func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) { } if info.Type() != snap.TypeGadget { - return nil, fmt.Errorf("cannot link type %v", info.Type()) + return nil, fmt.Errorf("internal error: cannot link type %v", info.Type()) } snapsup := &SnapSetup{ -- cgit v1.2.3 From 7b2cd81f59b3d9762049fd2179afe138ed54e12c Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 13 Oct 2021 11:27:28 +0200 Subject: o/devicestate: drop duplicated checks in unit tests Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicestate_remodel_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/overlord/devicestate/devicestate_remodel_test.go b/overlord/devicestate/devicestate_remodel_test.go index 837aab6112..42765e35cb 100644 --- a/overlord/devicestate/devicestate_remodel_test.go +++ b/overlord/devicestate/devicestate_remodel_test.go @@ -2280,8 +2280,6 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal c.Assert(tUpdateCmdline.Kind(), Equals, "update-gadget-cmdline") c.Assert(tUpdateCmdline.Summary(), Equals, `Update kernel command line from gadget "pc-new" (222) for remodel`) c.Assert(tUpdateCmdline.WaitTasks(), HasLen, 1) - c.Assert(tLinkBase.Kind(), Equals, "link-snap") - c.Assert(tLinkBase.Summary(), Equals, `Make snap "core20-new" (222) available to the system during remodel`) expectedLabel := now.Format("20060102") c.Assert(tCreateRecovery.Kind(), Equals, "create-recovery-system") c.Assert(tCreateRecovery.Summary(), Equals, fmt.Sprintf("Create recovery system with label %q", expectedLabel)) -- cgit v1.2.3 From 12f5eeae7676633a502191ce2495a6cbdf0d4a93 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Wed, 13 Oct 2021 11:37:54 -0300 Subject: Use --channel="3.0/$LXD_SNAP_CHANNEL" for pc-i386 arch in snapd-snap test --- tests/main/snapd-snap/task.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/main/snapd-snap/task.yaml b/tests/main/snapd-snap/task.yaml index e9b8884938..46587e139e 100644 --- a/tests/main/snapd-snap/task.yaml +++ b/tests/main/snapd-snap/task.yaml @@ -25,9 +25,6 @@ systems: # locale, see https://bugs.launchpad.net/snapcraft/+bug/1922140 # a separate error occurs on centos 8, see https://bugs.launchpad.net/snapcraft/+bug/1922981 - -centos-* - # i386 is not supported by lxd - - -ubuntu-18.04-32 - # Start early as it takes a long time. priority: 100 @@ -83,7 +80,11 @@ prepare: | modprobe fuse echo "Install lxd" - snap install lxd --channel="$LXD_SNAP_CHANNEL" + if os.query is-pc-i386; then + snap install lxd --channel="3.0/$LXD_SNAP_CHANNEL" + else + snap install lxd --channel="$LXD_SNAP_CHANNEL" + fi tests.cleanup defer snap remove --purge lxd echo "Setup the lxd snap" -- cgit v1.2.3 From 2543412cb6b4ba0cc2495e40f73f67632ba8ab36 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Wed, 13 Oct 2021 10:01:05 -0500 Subject: gadget: take a directory argument in LoadDiskVolumesDeviceTraits too Save needs a directory so for symmetry Load should take one too. Thanks to Samuele for the suggestion. Signed-off-by: Ian Johnson --- gadget/gadget.go | 9 ++++----- gadget/gadget_test.go | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/gadget/gadget.go b/gadget/gadget.go index b1bad65a60..1004c14f6f 100644 --- a/gadget/gadget.go +++ b/gadget/gadget.go @@ -35,7 +35,6 @@ import ( "gopkg.in/yaml.v2" "github.com/snapcore/snapd/asserts" - "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget/edition" "github.com/snapcore/snapd/gadget/quantity" "github.com/snapcore/snapd/metautil" @@ -271,8 +270,8 @@ type DiskStructureDeviceTraits struct { Size quantity.Size `json:"size"` } -// SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume / device -// traits to a file on disk for later loading and verification. +// SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume / +// device traits to a file on disk for later loading and verification. func SaveDiskVolumesDeviceTraits(dir string, mapping map[string]DiskVolumeDeviceTraits) error { b, err := json.Marshal(mapping) if err != nil { @@ -290,10 +289,10 @@ func SaveDiskVolumesDeviceTraits(dir string, mapping map[string]DiskVolumeDevice // LoadDiskVolumesDeviceTraits loads the mapping of volumes to disk traits if // there is any. If there is no file with the mapping available, nil is // returned. -func LoadDiskVolumesDeviceTraits() (map[string]DiskVolumeDeviceTraits, error) { +func LoadDiskVolumesDeviceTraits(dir string) (map[string]DiskVolumeDeviceTraits, error) { var mapping map[string]DiskVolumeDeviceTraits - filename := filepath.Join(dirs.SnapDeviceDir, "disk-mapping.json") + filename := filepath.Join(dir, "disk-mapping.json") if !osutil.FileExists(filename) { return nil, nil } diff --git a/gadget/gadget_test.go b/gadget/gadget_test.go index 67c0cc1b18..a8817b34da 100644 --- a/gadget/gadget_test.go +++ b/gadget/gadget_test.go @@ -3034,7 +3034,7 @@ func (s *gadgetYamlTestSuite) TestSaveLoadDiskVolumeDeviceTraits(c *C) { // when there is no mapping file, it is not an error, the map returned is // just nil/has no items in it - mAbsent, err := gadget.LoadDiskVolumesDeviceTraits() + mAbsent, err := gadget.LoadDiskVolumesDeviceTraits(dirs.SnapDeviceDir) c.Assert(err, IsNil) c.Assert(mAbsent, HasLen, 0) @@ -3049,7 +3049,7 @@ func (s *gadgetYamlTestSuite) TestSaveLoadDiskVolumeDeviceTraits(c *C) { c.Assert(err, IsNil) // now that it was saved to dirs.SnapDeviceDir, we can load it correctly - m2, err := gadget.LoadDiskVolumesDeviceTraits() + m2, err := gadget.LoadDiskVolumesDeviceTraits(dirs.SnapDeviceDir) c.Assert(err, IsNil) c.Assert(m, DeepEquals, m2) -- cgit v1.2.3 From ced6d6befb954ce8f95d2519cf4395b96c7a8014 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Wed, 13 Oct 2021 12:17:23 -0500 Subject: osutil/disks: fix BlkIDEncodeLabel encoding format The existing code was not properly encoding non-allowed single length runes less than 0x10 by not including the prefixing "0". Signed-off-by: Ian Johnson --- osutil/disks/labels.go | 2 +- osutil/disks/labels_test.go | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osutil/disks/labels.go b/osutil/disks/labels.go index ab5c25245e..b9df5d3ac5 100644 --- a/osutil/disks/labels.go +++ b/osutil/disks/labels.go @@ -39,7 +39,7 @@ func BlkIDEncodeLabel(in string) string { case utf8.RuneLen(r) > 1: buf.WriteRune(r) case !strings.ContainsRune(allowed, r): - fmt.Fprintf(buf, `\x%x`, r) + fmt.Fprintf(buf, `\x%02x`, r) default: buf.WriteRune(r) } diff --git a/osutil/disks/labels_test.go b/osutil/disks/labels_test.go index bc89b067e8..f165d027de 100644 --- a/osutil/disks/labels_test.go +++ b/osutil/disks/labels_test.go @@ -72,8 +72,9 @@ func (ts *diskLabelSuite) TestEncodeHexBlkIDFormat(c *C) { // these are "unsafe" chars, so they get encoded {"ubuntu data", `ubuntu\x20data`}, - {"ubuntu\ttab", `ubuntu\x9tab`}, - {"ubuntu\nnewline", `ubuntu\xanewline`}, + {"ubuntu\ttab", `ubuntu\x09tab`}, + {"ubuntu\t9tab", `ubuntu\x099tab`}, + {"ubuntu\nnewline", `ubuntu\x0anewline`}, {"foo bar", `foo\x20bar`}, {"foo/bar", `foo\x2fbar`}, {"foo/../bar", `foo\x2f..\x2fbar`}, @@ -81,6 +82,13 @@ func (ts *diskLabelSuite) TestEncodeHexBlkIDFormat(c *C) { {"pinkié pie", `pinkié\x20pie`}, {"(EFI Boot)", `\x28EFI\x20Boot\x29`}, {"[System Boot]", `\x5bSystem\x20Boot\x5d`}, + // 0x7e is just a 1-rune long character that is not in the allowed set + // to demonstrate that these two input strings are encoded/decoded + // properly with the constant double width + {"ubuntu\x7etab", `ubuntu\x7etab`}, + {"ubuntu\x07" + "etab", `ubuntu\x07etab`}, + // works when the only character is an escaped one too + {"\t", `\x09`}, } for _, t := range tt { c.Logf("tc: %v %q", t.in, t.out) -- cgit v1.2.3 From b29b30ed93c95877da21e8dae0e6c16a4337332e Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Wed, 13 Oct 2021 12:18:16 -0500 Subject: osutil/disks: add BlkIDDecodeLabel This is needed since some labels we read from udev need to be compared with normal Go string values we get from i.e. gadget.yaml. Signed-off-by: Ian Johnson --- osutil/disks/labels.go | 89 +++++++++++++++++++++++++++++++++++++++++++++ osutil/disks/labels_test.go | 41 ++++++++++++++++++++- 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/osutil/disks/labels.go b/osutil/disks/labels.go index b9df5d3ac5..e1136ce2a8 100644 --- a/osutil/disks/labels.go +++ b/osutil/disks/labels.go @@ -22,6 +22,7 @@ package disks import ( "bytes" "fmt" + "strconv" "strings" "unicode/utf8" ) @@ -46,3 +47,91 @@ func BlkIDEncodeLabel(in string) string { } return buf.String() } + +type blkIdDecodeState int + +const ( + stNormal blkIdDecodeState = iota + stSlashEscape + stSlashEscapeX + stSlashEscapeXNum +) + +// BlkIDDecodeLabel decodes a string such as a filesystem or partition label +// encoded by udev in BlkIDEncodeLabel for normal comparison, i.e. +// "BIOS\x20Boot" becomes "BIOS Boot" +func BlkIDDecodeLabel(in string) (string, error) { + out := strings.Builder{} + escapedHexDigits := []rune{} + st := stNormal + for _, r := range in { + switch st { + case stNormal: + // check if this char is the beginning of an escape sequence + if r == '\\' { + st = stSlashEscape + continue + } + // otherwise write it + out.WriteRune(r) + case stSlashEscape: + // next char to check is 'x' + if r == 'x' { + st = stSlashEscapeX + continue + } + // otherwise it's a format error, "\" is not in the set of + // characters allowed, so if we see one that is not followed by an + // x, then the string is malformed and can't be decoded + return "", fmt.Errorf("string is malformed, unexpected '\\' character not part of a valid escape sequence") + case stSlashEscapeX: + // now we expect exactly two hex digits, since the encoding would + // have written valid multi-byte runes that are UTF8 directly + // without escaping, the only possible escaped runes are those which + // are one byte and not in the allowed set + + // TODO: though can one have multi-byte runes that are not UTF8 + // encodable? it seems the only possibilities are runes that are + // either in the surrogate range or that are larger than the maximum + // rune value - for now we will just ignore those + + if strings.ContainsRune(`0123456789abcedf`, r) { + escapedHexDigits = append(escapedHexDigits, r) + st = stSlashEscapeXNum + continue + } + return "", fmt.Errorf("string is malformed, unexpected %q character not part of a valid escape sequence", r) + case stSlashEscapeXNum: + // got one digit, make sure we get a second digit + if strings.ContainsRune(`0123456789abcedf`, r) { + escapedHexDigits = append(escapedHexDigits, r) + + // the escapedHexDigits can now be decoded and written out + v, err := strconv.ParseUint(string(escapedHexDigits), 16, 8) + if err != nil { + // should be logically impossible, we ensured that only + // rune digits in the hexadecimal range above were put into this rune + // buffer + return "", fmt.Errorf("internal error, unable to parse escape sequence: %v", err) + } + escapedHexDigits = []rune{} + out.WriteRune(rune(v)) + st = stNormal + continue + } + return "", fmt.Errorf("string is malformed, unexpected %q character not part of a valid escape sequence", r) + default: + return "", fmt.Errorf("internal error, unexpected parsing state") + } + } + + // check that we had a valid end state + switch st { + case stNormal: + return out.String(), nil + case stSlashEscape, stSlashEscapeX, stSlashEscapeXNum: + return "", fmt.Errorf("string is malformed, unfinished escape sequence") + default: + return "", fmt.Errorf("internal error, unexpected parsing state") + } +} diff --git a/osutil/disks/labels_test.go b/osutil/disks/labels_test.go index f165d027de..0db0485d7a 100644 --- a/osutil/disks/labels_test.go +++ b/osutil/disks/labels_test.go @@ -33,7 +33,7 @@ type diskLabelSuite struct{} var _ = Suite(&diskLabelSuite{}) -func (ts *diskLabelSuite) TestEncodeHexBlkIDFormat(c *C) { +func (ts *diskLabelSuite) TestBlkIDEncodeDecodeLabelHappy(c *C) { // Test output obtained with the following program: // // #include @@ -93,5 +93,44 @@ func (ts *diskLabelSuite) TestEncodeHexBlkIDFormat(c *C) { for _, t := range tt { c.Logf("tc: %v %q", t.in, t.out) c.Assert(disks.BlkIDEncodeLabel(t.in), Equals, t.out) + + // make sure the other way around works too + expin, err := disks.BlkIDDecodeLabel(t.out) + c.Assert(err, IsNil) + + c.Assert(expin, Equals, t.in) + } +} + +func (ts *diskLabelSuite) TestBlkIDDecodeLabelUnhappy(c *C) { + tt := []struct { + in string + experr string + }{ + { + `\x7z`, + "string is malformed, unexpected 'z' character not part of a valid escape sequence", + }, + { + `\z`, + `string is malformed, unexpected '\\' character not part of a valid escape sequence`, + }, + { + `\`, + `string is malformed, unfinished escape sequence`, + }, + { + `\x`, + `string is malformed, unfinished escape sequence`, + }, + { + `\x0`, + `string is malformed, unfinished escape sequence`, + }, + } + + for _, t := range tt { + _, err := disks.BlkIDDecodeLabel(t.in) + c.Assert(err, ErrorMatches, t.experr) } } -- cgit v1.2.3 From c18d9b35466ad69f0a8e14aacc7e01e6d69cb245 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Wed, 13 Oct 2021 14:27:44 -0300 Subject: Force 3.0/stable for i386 --- tests/main/snapd-snap/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/snapd-snap/task.yaml b/tests/main/snapd-snap/task.yaml index 46587e139e..07c9b4e0ab 100644 --- a/tests/main/snapd-snap/task.yaml +++ b/tests/main/snapd-snap/task.yaml @@ -81,7 +81,7 @@ prepare: | echo "Install lxd" if os.query is-pc-i386; then - snap install lxd --channel="3.0/$LXD_SNAP_CHANNEL" + snap install lxd --channel="3.0/stable" else snap install lxd --channel="$LXD_SNAP_CHANNEL" fi -- cgit v1.2.3 From 06c8892536c5a159d5f33c395aacd2227800e7e6 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 8 Oct 2021 12:18:00 -0500 Subject: osutil/disks, many: switch to defining Partitions directly for MockDiskMapping This will be necessary for more complicated test cases in the gadget package, so time to rip this band-aid and get rid of the old methods. Signed-off-by: Ian Johnson --- bootloader/export_test.go | 42 ++++-- cmd/snap-bootstrap/cmd_initramfs_mounts_test.go | 162 +++++++++++++-------- osutil/disks/mockdisk.go | 183 ++++++++++++++---------- osutil/disks/mockdisk_test.go | 74 ++++++---- secboot/secboot_sb_test.go | 111 +++++++++----- 5 files changed, 371 insertions(+), 201 deletions(-) diff --git a/bootloader/export_test.go b/bootloader/export_test.go index 94fdfe622c..1330192b90 100644 --- a/bootloader/export_test.go +++ b/bootloader/export_test.go @@ -123,17 +123,41 @@ func MockLkFiles(c *C, rootdir string, opts *Options) (restore func()) { lkBootDisk := &disks.MockDiskMapping{ // mock the partition labels, since these structures won't have // filesystems, but they will have partition labels - PartitionLabelToPartUUID: map[string]string{ - "snapbootsel": "snapbootsel-partuuid", - "snapbootselbak": "snapbootselbak-partuuid", - "snaprecoverysel": "snaprecoverysel-partuuid", - "snaprecoveryselbak": "snaprecoveryselbak-partuuid", + Structure: []disks.Partition{ + { + PartitionLabel: "snapbootsel", + PartitionUUID: "snapbootsel-partuuid", + }, + { + PartitionLabel: "snapbootselbak", + PartitionUUID: "snapbootselbak-partuuid", + }, + { + PartitionLabel: "snaprecoverysel", + PartitionUUID: "snaprecoverysel-partuuid", + }, + { + PartitionLabel: "snaprecoveryselbak", + PartitionUUID: "snaprecoveryselbak-partuuid", + }, // for run mode kernel snaps - "boot_a": "boot-a-partuuid", - "boot_b": "boot-b-partuuid", + { + PartitionLabel: "boot_a", + PartitionUUID: "boot-a-partuuid", + }, + { + PartitionLabel: "boot_b", + PartitionUUID: "boot-b-partuuid", + }, // for recovery system kernel snaps - "boot_ra": "boot-ra-partuuid", - "boot_rb": "boot-rb-partuuid", + { + PartitionLabel: "boot_ra", + PartitionUUID: "boot-ra-partuuid", + }, + { + PartitionLabel: "boot_rb", + PartitionUUID: "boot-rb-partuuid", + }, }, DiskHasPartitions: true, DevNum: "lk-boot-disk-dev-num", diff --git a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go index c2f0dc2394..7d2733a770 100644 --- a/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go +++ b/cmd/snap-bootstrap/cmd_initramfs_mounts_test.go @@ -93,34 +93,64 @@ var ( NoSuid: true, } + seedPart = disks.Partition{ + FilesystemLabel: "ubuntu-seed", + PartitionUUID: "ubuntu-seed-partuuid", + } + + bootPart = disks.Partition{ + FilesystemLabel: "ubuntu-boot", + PartitionUUID: "ubuntu-boot-partuuid", + } + + savePart = disks.Partition{ + FilesystemLabel: "ubuntu-save", + PartitionUUID: "ubuntu-save-partuuid", + } + + dataPart = disks.Partition{ + FilesystemLabel: "ubuntu-data", + PartitionUUID: "ubuntu-data-partuuid", + } + + saveEncPart = disks.Partition{ + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "ubuntu-save-enc-partuuid", + } + + dataEncPart = disks.Partition{ + FilesystemLabel: "ubuntu-data-enc", + PartitionUUID: "ubuntu-data-enc-partuuid", + } + // a boot disk without ubuntu-save defaultBootDisk = &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data": "ubuntu-data-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + dataPart, }, DiskHasPartitions: true, DevNum: "default", } defaultBootWithSaveDisk = &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data": "ubuntu-data-partuuid", - "ubuntu-save": "ubuntu-save-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + dataPart, + savePart, }, DiskHasPartitions: true, DevNum: "default-with-save", } defaultEncBootDisk = &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + bootPart, + seedPart, + dataEncPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDev", @@ -2086,10 +2116,10 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRunModeEncryptedDataUnhappyNoS s.mockProcCmdlineContent(c, "snapd_recovery_mode=run") defaultEncNoSaveBootDisk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + dataEncPart, // missing ubuntu-save }, DiskHasPartitions: true, @@ -3542,10 +3572,11 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb defer bootloader.Force(nil) defaultEncDiskNoBoot := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + // missing ubuntu-boot + dataEncPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDevNoBoot", @@ -3698,10 +3729,11 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb defer bootloader.Force(nil) defaultEncDiskNoBoot := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + // missing ubuntu-boot + dataEncPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDevNoBoot", @@ -4059,10 +4091,10 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedAbsentDataU // no ubuntu-data on the disk at all mockDiskNoData := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-save": "ubuntu-save-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + savePart, }, DiskHasPartitions: true, DevNum: "noDataUnenc", @@ -4239,12 +4271,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedUnencrypted // no ubuntu-data on the disk at all mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, // ubuntu-data is unencrypted but ubuntu-save is encrypted - "ubuntu-data": "ubuntu-data-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + dataPart, + saveEncPart, }, DiskHasPartitions: true, DevNum: "dataUnencSaveEnc", @@ -4372,12 +4404,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeDegradedEncryptedDa defer bootloader.Force(nil) mockDiskDataUnencSaveEnc := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, // ubuntu-data is encrypted but ubuntu-save is not - "ubuntu-save": "ubuntu-save-partuuid", + savePart, + dataEncPart, }, DiskHasPartitions: true, DevNum: "dataUnencSaveEnc", @@ -4627,12 +4659,12 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedDegradedAb bootloader.Force(bloader) defer bootloader.Force(nil) - // no ubuntu-data on the disk at all mockDiskNoData := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + // no ubuntu-data on the disk at all + saveEncPart, }, DiskHasPartitions: true, DevNum: "defaultEncDev", @@ -5193,21 +5225,33 @@ func (s *initramfsMountsSuite) TestInitramfsMountsRecoverModeEncryptedAttackerFS defer bootloader.Force(nil) mockDisk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", - "ubuntu-boot": "ubuntu-boot-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-partuuid", + Structure: []disks.Partition{ + seedPart, + bootPart, + saveEncPart, + dataEncPart, }, DiskHasPartitions: true, DevNum: "bootDev", } attackerDisk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-attacker-partuuid", - "ubuntu-boot": "ubuntu-boot-attacker-partuuid", - "ubuntu-data-enc": "ubuntu-data-enc-attacker-partuuid", - "ubuntu-save-enc": "ubuntu-save-enc-attacker-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-seed", + PartitionUUID: "ubuntu-seed-attacker-partuuid", + }, + { + FilesystemLabel: "ubuntu-boot", + PartitionUUID: "ubuntu-boot-attacker-partuuid", + }, + { + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "ubuntu-save-enc-attacker-partuuid", + }, + { + FilesystemLabel: "ubuntu-data-enc", + PartitionUUID: "ubuntu-data-enc-attacker-partuuid", + }, }, DiskHasPartitions: true, DevNum: "attackerDev", @@ -5332,8 +5376,8 @@ func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C mockDiskMapping := map[disks.Mountpoint]*disks.MockDiskMapping{ {Mountpoint: boot.InitramfsUbuntuSeedDir}: { - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-partuuid", + Structure: []disks.Partition{ + seedPart, }, DiskHasPartitions: true, }, @@ -5366,9 +5410,7 @@ func (s *initramfsMountsSuite) testInitramfsMountsInstallRecoverModeMeasure(c *C // also add the ubuntu-data and ubuntu-save fs labels to the // disk referenced by the ubuntu-seed partition disk := mockDiskMapping[disks.Mountpoint{Mountpoint: boot.InitramfsUbuntuSeedDir}] - disk.FilesystemLabelToPartUUID["ubuntu-boot"] = "ubuntu-boot-partuuid" - disk.FilesystemLabelToPartUUID["ubuntu-data"] = "ubuntu-data-partuuid" - disk.FilesystemLabelToPartUUID["ubuntu-save"] = "ubuntu-save-partuuid" + disk.Structure = append(disk.Structure, bootPart, savePart, dataPart) // and also add the /run/mnt/host/ubuntu-{boot,data,save} mountpoints // for cross-checking after mounting diff --git a/osutil/disks/mockdisk.go b/osutil/disks/mockdisk.go index 898827f92d..3ea070d47b 100644 --- a/osutil/disks/mockdisk.go +++ b/osutil/disks/mockdisk.go @@ -34,20 +34,19 @@ var _ = Disk(&MockDiskMapping{}) // DevNum must be a unique string per unique mocked disk, if only one disk is // being mocked it can be left empty. type MockDiskMapping struct { - // TODO: eliminate these manual mappings and instead switch all the users - // over to providing the full list of Partitions instead, but in the - // interest of smaller PR's we are doing that in a separate PR. - // FilesystemLabelToPartUUID is a mapping of the udev encoded filesystem - // labels to the expected partition uuids. - FilesystemLabelToPartUUID map[string]string - // PartitionLabelToPartUUID is a mapping of the udev encoded partition - // labels to the expected partition uuids. - PartitionLabelToPartUUID map[string]string - DiskHasPartitions bool - - // TODO: add an exported list of Partitions here - - // static variables for the disk + // TODO: should this be automatically determined if Structure has non-zero + // len instead? + DiskHasPartitions bool + + // Structure is the set of partitions or structures on the disk. These + // partitions are used with Partitions() as well as + // FindMatchingPartitionWith{Fs,Part}Label + Structure []Partition + + // static variables for the disk that must be unique for different disks, + // but note that there are potentially multiple DevNode values that could + // map to a single disk, but it's not worth encoding that complexity here + // by making DevNodes a list DevNum string DevNode string DevPath string @@ -58,19 +57,13 @@ type MockDiskMapping struct { func (d *MockDiskMapping) FindMatchingPartitionWithFsLabel(label string) (Partition, error) { // TODO: this should just iterate over the static list when that is a thing osutil.MustBeTestBinary("mock disks only to be used in tests") - if partuuid, ok := d.FilesystemLabelToPartUUID[label]; ok { - part := Partition{ - PartitionUUID: partuuid, - FilesystemLabel: label, - } - // add the partition label too if we have one for this partition uuid - for partlabel, partuuid2 := range d.PartitionLabelToPartUUID { - if partuuid2 == partuuid { - part.PartitionLabel = partlabel - } + + for _, p := range d.Structure { + if p.FilesystemLabel == label { + return p, nil } - return part, nil } + return Partition{}, PartitionNotFoundError{ SearchType: "filesystem-label", SearchQuery: label, @@ -80,21 +73,14 @@ func (d *MockDiskMapping) FindMatchingPartitionWithFsLabel(label string) (Partit // FindMatchingPartitionUUIDWithPartLabel returns a matching PartitionUUID // for the specified filesystem label if it exists. Part of the Disk interface. func (d *MockDiskMapping) FindMatchingPartitionWithPartLabel(label string) (Partition, error) { - // TODO: this should just iterate over the static list when that is a thing osutil.MustBeTestBinary("mock disks only to be used in tests") - if partuuid, ok := d.PartitionLabelToPartUUID[label]; ok { - part := Partition{ - PartitionUUID: partuuid, - PartitionLabel: label, - } - // add the filesystem label too if we have one for this partition uuid - for fsLabel, partuuid2 := range d.FilesystemLabelToPartUUID { - if partuuid2 == partuuid { - part.FilesystemLabel = fsLabel - } + + for _, p := range d.Structure { + if p.PartitionLabel == label { + return p, nil } - return part, nil } + return Partition{}, PartitionNotFoundError{ SearchType: "partition-label", SearchQuery: label, @@ -118,39 +104,7 @@ func (d *MockDiskMapping) FindMatchingPartitionUUIDWithPartLabel(label string) ( } func (d *MockDiskMapping) Partitions() ([]Partition, error) { - // TODO: this should just return the static list that was in the mapping - // when that is a thing - - // dynamically build up a list of partitions with the mappings we were - // provided - parts := make([]Partition, 0, len(d.PartitionLabelToPartUUID)) - - partUUIDToPart := map[string]Partition{} - - // first populate with all the partition labels - for partLabel, partuuid := range d.PartitionLabelToPartUUID { - part := Partition{ - PartitionLabel: partLabel, - PartitionUUID: partuuid, - } - - partUUIDToPart[partuuid] = part - } - - for fsLabel, partuuid := range d.FilesystemLabelToPartUUID { - existingPart, ok := partUUIDToPart[partuuid] - if !ok { - parts = append(parts, Partition{ - FilesystemLabel: fsLabel, - PartitionUUID: partuuid, - }) - continue - } - existingPart.FilesystemLabel = fsLabel - parts = append(parts, existingPart) - } - - return parts, nil + return d.Structure, nil } // HasPartitions returns if the mock disk has partitions or not. Part of the @@ -202,19 +156,100 @@ type Mountpoint struct { IsDecryptedDevice bool } +func checkMockDiskMappingsForDuplicates(mockedDisks map[string]*MockDiskMapping) { + // we do the minimal amount of validation here, where if things are + // specified as non-zero value we check that they make sense, but we don't + // require that every field is set for every partition since many tests + // don't care about every field + + // check partition uuid's and partition labels for duplication inter-disk + // we could have valid cloned disks where the same partition uuid/label + // appears on two disks, but never on the same disk + for _, disk := range mockedDisks { + seenPartUUID := make(map[string]bool, len(disk.Structure)) + seenPartLabel := make(map[string]bool, len(disk.Structure)) + for _, p := range disk.Structure { + if p.PartitionUUID != "" { + if seenPartUUID[p.PartitionUUID] { + panic("mock error: disk has duplicated partition uuids in its structure") + } + seenPartUUID[p.PartitionUUID] = true + } + + if p.PartitionLabel != "" { + if seenPartLabel[p.PartitionLabel] { + panic("mock error: disk has duplicated partition labels in its structure") + } + seenPartLabel[p.PartitionLabel] = true + } + } + } + + // check major/minors across all structures + type majmin struct{ maj, min int } + seenMajorMinors := map[majmin]bool{} + for _, disk := range mockedDisks { + for _, p := range disk.Structure { + if p.Major == 0 && p.Minor == 0 { + continue + } + + m := majmin{maj: p.Major, min: p.Minor} + if seenMajorMinors[m] { + panic("mock error: duplicated major minor numbers for partitions in disk mapping") + } + seenMajorMinors[m] = true + } + } + + // check device paths across all structures + seenDevPaths := map[string]bool{} + for _, disk := range mockedDisks { + for _, p := range disk.Structure { + if p.KernelDevicePath == "" { + continue + } + if seenDevPaths[p.KernelDevicePath] { + panic("mock error: duplicated kernel device paths for partitions in disk mapping") + } + seenDevPaths[p.KernelDevicePath] = true + } + } + + // check device nodes across all structures + seendDevNodes := map[string]bool{} + for _, disk := range mockedDisks { + for _, p := range disk.Structure { + if p.KernelDevicePath == "" { + continue + } + + if seendDevNodes[p.KernelDeviceNode] { + panic("mock error: duplicated kernel device nodes for partitions in disk mapping") + } + seendDevNodes[p.KernelDeviceNode] = true + } + } + + // no checking of filesystem label/uuid since those could be duplicated as + // they exist independent of any other structure +} + // MockDeviceNameDisksToPartitionMapping will mock DiskFromDeviceName such that // the provided map of device names to mock disks is used instead of the actual // implementation using udev. -func MockDeviceNameDisksToPartitionMapping(mockedMountPoints map[string]*MockDiskMapping) (restore func()) { +func MockDeviceNameDisksToPartitionMapping(mockedDisks map[string]*MockDiskMapping) (restore func()) { osutil.MustBeTestBinary("mock disks only to be used in tests") - // note that devices can have many names that are recognized by + checkMockDiskMappingsForDuplicates(mockedDisks) + + // note that devices can have multiple "names" that are recognized by // udev/kernel, so we don't do any validation of the mapping here like we do - // for MockMountPointDisksToPartitionMapping + // for MockMountPointDisksToPartitionMapping and MockDevicePathDisksToPartitionMapping old := diskFromDeviceName diskFromDeviceName = func(deviceName string) (Disk, error) { - disk, ok := mockedMountPoints[deviceName] + disk, ok := mockedDisks[deviceName] if !ok { return nil, fmt.Errorf("device name %q not mocked", deviceName) } diff --git a/osutil/disks/mockdisk_test.go b/osutil/disks/mockdisk_test.go index 79ca4cfd5d..46bf7a49b3 100644 --- a/osutil/disks/mockdisk_test.go +++ b/osutil/disks/mockdisk_test.go @@ -42,8 +42,11 @@ func (s *mockDiskSuite) SetUpTest(c *C) { func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { // one disk with different device names d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: true, DevNum: "d1", @@ -52,8 +55,11 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { } d2 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label2": "part2", + Structure: []disks.Partition{ + { + FilesystemLabel: "label2", + PartitionUUID: "part2", + }, }, DiskHasPartitions: true, DevNum: "d2", @@ -104,16 +110,22 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesUniqueness(c *C) { // two different disks with different DevNum's d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: true, DevNum: "d1", } d2 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: false, DevNum: "d2", @@ -152,8 +164,11 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesUniquen func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesConsistency(c *C) { d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + }, }, DiskHasPartitions: true, DevNum: "d1", @@ -177,22 +192,24 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingVerifiesConsist func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) { d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label1": "part1", - }, - PartitionLabelToPartUUID: map[string]string{ - "part-label1": "part1", + Structure: []disks.Partition{ + { + FilesystemLabel: "label1", + PartitionUUID: "part1", + PartitionLabel: "part-label1", + }, }, DiskHasPartitions: true, DevNum: "d1", } d2 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "label2": "part2", - }, - PartitionLabelToPartUUID: map[string]string{ - "part-label2": "part2", + Structure: []disks.Partition{ + { + FilesystemLabel: "label2", + PartitionUUID: "part2", + PartitionLabel: "part-label2", + }, }, DiskHasPartitions: true, DevNum: "d2", @@ -322,10 +339,19 @@ func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMapping(c *C) { func (s *mockDiskSuite) TestMockMountPointDisksToPartitionMappingDecryptedDevices(c *C) { d1 := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-seed": "ubuntu-seed-part", - "ubuntu-boot": "ubuntu-boot-part", - "ubuntu-data-enc": "ubuntu-data-enc-part", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-seed", + PartitionUUID: "ubuntu-seed-part", + }, + { + FilesystemLabel: "ubuntu-boot", + PartitionUUID: "ubuntu-boot-part", + }, + { + FilesystemLabel: "ubuntu-data-enc", + PartitionUUID: "ubuntu-data-enc-part", + }, }, DiskHasPartitions: true, DevNum: "d1", diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go index f334d68409..6866987a46 100644 --- a/secboot/secboot_sb_test.go +++ b/secboot/secboot_sb_test.go @@ -323,18 +323,22 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { // defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } - mockDiskWithoutAnyDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{}, - } + mockDiskWithoutAnyDev := &disks.MockDiskMapping{} mockDiskWithUnencDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "name": "unenc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "name", + PartitionUUID: "unenc-dev-partuuid", + }, }, } @@ -479,10 +483,18 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { if tc.hasEncdev { fsLabel += "-enc" } - partuuid, ok := tc.disk.FilesystemLabelToPartUUID[fsLabel] + + partuuid := "" if !tc.skipDiskEnsureCheck { - c.Assert(ok, Equals, true) + for _, p := range tc.disk.Structure { + if p.FilesystemLabel == fsLabel { + partuuid = p.PartitionUUID + break + } + } + c.Assert(partuuid, Not(Equals), "", Commentf("didn't find fs label %s in disk", fsLabel)) } + devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid) expKeyPath := tc.keyfile @@ -1126,9 +1138,7 @@ func mockSbTPMConnection(c *C, tpmErr error) (*sb_tpm2.Connection, func()) { } func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) { - disk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{}, - } + disk := &disks.MockDiskMapping{} unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo")) c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`) c.Check(unlockRes, DeepEquals, secboot.UnlockResult{}) @@ -1136,8 +1146,11 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) { func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) { disk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-save-enc": "123-123-123", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "123-123-123", + }, }, } restore := secboot.MockRandomKernelUUID(func() string { @@ -1165,8 +1178,11 @@ func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) { func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) { disk := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "ubuntu-save-enc": "123-123-123", + Structure: []disks.Partition{ + { + FilesystemLabel: "ubuntu-save-enc", + PartitionUUID: "123-123-123", + }, }, } restore := secboot.MockRandomKernelUUID(func() string { @@ -1200,8 +1216,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr( defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } defaultDevice := "name" @@ -1247,8 +1266,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1An defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1481,8 +1503,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1539,8 +1564,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1591,8 +1619,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Mo defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1641,8 +1672,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2Al defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1698,8 +1732,11 @@ func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey, defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1763,8 +1800,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1(c defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } @@ -1817,8 +1857,11 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyBadJ defer restore() mockDiskWithEncDev := &disks.MockDiskMapping{ - FilesystemLabelToPartUUID: map[string]string{ - "device-name-enc": "enc-dev-partuuid", + Structure: []disks.Partition{ + { + FilesystemLabel: "device-name-enc", + PartitionUUID: "enc-dev-partuuid", + }, }, } -- cgit v1.2.3 From 89d3d02ec9eeadab034a4ab6046cd1dd58a81392 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Wed, 13 Oct 2021 20:35:43 -0500 Subject: many: mv MockDeviceNameDisksToPartitionMapping -> MockDeviceNameToDiskMapping This isn't mapping to partitions anymore, rather to a Disk itself. Signed-off-by: Ian Johnson --- bootloader/export_test.go | 2 +- osutil/disks/disks_linux.go | 4 ++-- osutil/disks/mockdisk.go | 6 +++--- osutil/disks/mockdisk_test.go | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bootloader/export_test.go b/bootloader/export_test.go index 1330192b90..c37992d584 100644 --- a/bootloader/export_test.go +++ b/bootloader/export_test.go @@ -168,7 +168,7 @@ func MockLkFiles(c *C, rootdir string, opts *Options) (restore func()) { } // mock the disk - r := disks.MockDeviceNameDisksToPartitionMapping(m) + r := disks.MockDeviceNameToDiskMapping(m) cleanups = append(cleanups, r) // now mock the kernel command line diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index 7f2c05ac43..31f6f68e9a 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -159,7 +159,7 @@ func DiskFromDevicePath(devicePath string) (Disk, error) { } // diskFromDevicePath is exposed for mocking from other tests via -// MockDeviceNameDisksToPartitionMapping. +// MockDevicePathToDiskMapping (which is yet to be added). var diskFromDevicePath = func(devicePath string) (Disk, error) { // query for the disk props using udev with --path props, err := udevPropertiesForPath(devicePath) @@ -177,7 +177,7 @@ func DiskFromDeviceName(deviceName string) (Disk, error) { } // diskFromDeviceName is exposed for mocking from other tests via -// MockDeviceNameDisksToPartitionMapping. +// MockDeviceNameToDiskMapping. var diskFromDeviceName = func(deviceName string) (Disk, error) { // query for the disk props using udev with --name props, err := udevPropertiesForName(deviceName) diff --git a/osutil/disks/mockdisk.go b/osutil/disks/mockdisk.go index 3ea070d47b..6546eb66a4 100644 --- a/osutil/disks/mockdisk.go +++ b/osutil/disks/mockdisk.go @@ -235,10 +235,10 @@ func checkMockDiskMappingsForDuplicates(mockedDisks map[string]*MockDiskMapping) // they exist independent of any other structure } -// MockDeviceNameDisksToPartitionMapping will mock DiskFromDeviceName such that -// the provided map of device names to mock disks is used instead of the actual +// MockDeviceNameToDiskMapping will mock DiskFromDeviceName such that the +// provided map of device names to mock disks is used instead of the actual // implementation using udev. -func MockDeviceNameDisksToPartitionMapping(mockedDisks map[string]*MockDiskMapping) (restore func()) { +func MockDeviceNameToDiskMapping(mockedDisks map[string]*MockDiskMapping) (restore func()) { osutil.MustBeTestBinary("mock disks only to be used in tests") checkMockDiskMappingsForDuplicates(mockedDisks) diff --git a/osutil/disks/mockdisk_test.go b/osutil/disks/mockdisk_test.go index 46bf7a49b3..a7b9127adb 100644 --- a/osutil/disks/mockdisk_test.go +++ b/osutil/disks/mockdisk_test.go @@ -39,7 +39,7 @@ func (s *mockDiskSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) } -func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { +func (s *mockDiskSuite) TestMockDeviceNameToDiskMapping(c *C) { // one disk with different device names d1 := &disks.MockDiskMapping{ Structure: []disks.Partition{ @@ -73,7 +73,7 @@ func (s *mockDiskSuite) TestMockDeviceNameDisksToPartitionMapping(c *C) { "other-disk": d2, } - r := disks.MockDeviceNameDisksToPartitionMapping(m) + r := disks.MockDeviceNameToDiskMapping(m) defer r() res, err := disks.DiskFromDeviceName("devName1") -- cgit v1.2.3 From 40609c1cac72b2d06ac37b1d4566e4f85dfb5189 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 14 Oct 2021 12:47:15 -0500 Subject: Revert "secboot: move to new version" --- go.mod | 8 +- go.sum | 18 ++-- secboot/export_sb_test.go | 54 +++++------- secboot/secboot_sb_test.go | 207 +++++++++++++++++++-------------------------- secboot/secboot_tpm.go | 113 +++++++++++-------------- secboot/test-data/keyfile | Bin 131303 -> 0 bytes 6 files changed, 171 insertions(+), 229 deletions(-) delete mode 100644 secboot/test-data/keyfile diff --git a/go.mod b/go.mod index 5f5c15c233..e371076058 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,9 @@ module github.com/snapcore/snapd go 1.13 require ( - github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4 // indirect github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 // indirect - github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61 - github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2 // indirect + github.com/canonical/go-tpm2 v0.0.0-20210314160024-32171bd353b1 + github.com/canonical/tcglog-parser v0.0.0-20200908165021-12a3a7bcf5a1 // indirect github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 github.com/gorilla/mux v1.7.4-0.20190701202633-d83b6ffe499a @@ -18,7 +17,8 @@ require ( github.com/mvo5/libseccomp-golang v0.9.1-0.20180308152521-f4de83b52afb github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8 github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 - github.com/snapcore/secboot v0.0.0-20210909111405-e3a397e2da90 + github.com/snapcore/secboot v0.0.0-20210805184555-c9f2139ee92b + go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 diff --git a/go.sum b/go.sum index f579a8bb87..80162bf027 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,9 @@ -github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4 h1:rSWREoNHHbcIC1iQeKKraBlsDm7cmKg8eS+N48jMVKA= -github.com/canonical/go-efilib v0.0.0-20210909101908-41435fa545d4/go.mod h1:9Sr9kd7IhQPYqaU5nut8Ky97/CtlhHDzQncQnrULgDM= -github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9 h1:USzKjrfWo/ESzozv2i3OMM7XDgxrZRvaHFrKkIKRtwU= -github.com/canonical/go-sp800.108-kdf v0.0.0-20210314145419-a3359f2d21b9/go.mod h1:Zrs3YjJr+w51u0R/dyLh/oWt/EcBVdLPCVFYC4daW5s= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM= -github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61 h1:DsyeCtFXqOdukmhPOunohjSlyxDHTqWSW1O4rD9N3L8= -github.com/canonical/go-tpm2 v0.0.0-20210827151749-f80ff5afff61/go.mod h1:vG41hdbBjV4+/fkubTT1ENBBqSkLwLr7mCeW9Y6kpZY= -github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2 h1:CbwVq64ruNLx/S3XA0LO6QMsw6Vc2inK+RcS6D2c4Ns= -github.com/canonical/tcglog-parser v0.0.0-20210824131805-69fa1e9f0ad2/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8= +github.com/canonical/go-tpm2 v0.0.0-20210314160024-32171bd353b1 h1:FGWb/opVaD42utMEAkDgO9QqXiTlwESSr7VAirVtW/Q= +github.com/canonical/go-tpm2 v0.0.0-20210314160024-32171bd353b1/go.mod h1:j23KcThy5uN+suQ1HiKSpQxuR54apc2EF2P8C/FHSuE= +github.com/canonical/tcglog-parser v0.0.0-20200908165021-12a3a7bcf5a1 h1:8uxbbF6v0M9G9sadgaGYnmTlhYXN6vDTahXnKsEdz00= +github.com/canonical/tcglog-parser v0.0.0-20200908165021-12a3a7bcf5a1/go.mod h1:QoW2apR2tBl6T/4czdND/EHjL1Ia9cCmQnIj9Xe0Kt8= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= @@ -42,8 +38,8 @@ github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8 h1:WmyDfH38e3MaMWr github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8/go.mod h1:Z6z3sf12AMDjT/4tbT/PmzzdACAxkWGhkuKWiVpTWLM= github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785 h1:PaunR+BhraKSLxt2awQ42zofkP+NKh/VjQ0PjIMk/y4= github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785/go.mod h1:D3SsWAXK7wCCBZu+Vk5hc1EuKj/L3XN1puEMXTU4LrQ= -github.com/snapcore/secboot v0.0.0-20210909111405-e3a397e2da90 h1:XCbqRVVmFztGHPWN+wdPcRahQY7moJBRd/g/zMXLJmA= -github.com/snapcore/secboot v0.0.0-20210909111405-e3a397e2da90/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY= +github.com/snapcore/secboot v0.0.0-20210805184555-c9f2139ee92b h1:r8G3o2em2zKDyMDdHthy+FARm9qEiyGtIsJIkGVBMYo= +github.com/snapcore/secboot v0.0.0-20210805184555-c9f2139ee92b/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY= github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= @@ -56,7 +52,6 @@ golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c/go.mod h1:iQL9McJNjoIa5mjH6n golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 h1:6wSTsvPddg9gc/mVEEyk9oOAoxn+bT4Z9q1zx+4RwA4= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= @@ -65,6 +60,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/macaroon.v1 v1.0.0-20150121114231-ab3940c6c165 h1:85xqOSyTpSzplW7fyO9bOZpSsemJc9UKzEQR2L4k32k= diff --git a/secboot/export_sb_test.go b/secboot/export_sb_test.go index 7c8356dd3f..1f7cdc51cf 100644 --- a/secboot/export_sb_test.go +++ b/secboot/export_sb_test.go @@ -24,8 +24,6 @@ import ( "io" sb "github.com/snapcore/secboot" - sb_efi "github.com/snapcore/secboot/efi" - sb_tpm2 "github.com/snapcore/secboot/tpm2" ) var ( @@ -33,7 +31,7 @@ var ( LockTPMSealedKeys = lockTPMSealedKeys ) -func MockSbConnectToDefaultTPM(f func() (*sb_tpm2.Connection, error)) (restore func()) { +func MockSbConnectToDefaultTPM(f func() (*sb.TPMConnection, error)) (restore func()) { old := sbConnectToDefaultTPM sbConnectToDefaultTPM = f return func() { @@ -41,7 +39,7 @@ func MockSbConnectToDefaultTPM(f func() (*sb_tpm2.Connection, error)) (restore f } } -func MockProvisionTPM(f func(tpm *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode, newLockoutAuth []byte) error) (restore func()) { +func MockProvisionTPM(f func(tpm *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error) (restore func()) { old := provisionTPM provisionTPM = f return func() { @@ -49,31 +47,31 @@ func MockProvisionTPM(f func(tpm *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode } } -func MockSbEfiAddSecureBootPolicyProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error) (restore func()) { - old := sbefiAddSecureBootPolicyProfile - sbefiAddSecureBootPolicyProfile = f +func MockSbAddEFISecureBootPolicyProfile(f func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error) (restore func()) { + old := sbAddEFISecureBootPolicyProfile + sbAddEFISecureBootPolicyProfile = f return func() { - sbefiAddSecureBootPolicyProfile = old + sbAddEFISecureBootPolicyProfile = old } } -func MockSbEfiAddBootManagerProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error) (restore func()) { - old := sbefiAddBootManagerProfile - sbefiAddBootManagerProfile = f +func MockSbAddEFIBootManagerProfile(f func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error) (restore func()) { + old := sbAddEFIBootManagerProfile + sbAddEFIBootManagerProfile = f return func() { - sbefiAddBootManagerProfile = old + sbAddEFIBootManagerProfile = old } } -func MockSbEfiAddSystemdStubProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error) (restore func()) { - old := sbefiAddSystemdStubProfile - sbefiAddSystemdStubProfile = f +func MockSbAddSystemdEFIStubProfile(f func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error) (restore func()) { + old := sbAddSystemdEFIStubProfile + sbAddSystemdEFIStubProfile = f return func() { - sbefiAddSystemdStubProfile = old + sbAddSystemdEFIStubProfile = old } } -func MockSbAddSnapModelProfile(f func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error) (restore func()) { +func MockSbAddSnapModelProfile(f func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error) (restore func()) { old := sbAddSnapModelProfile sbAddSnapModelProfile = f return func() { @@ -81,7 +79,7 @@ func MockSbAddSnapModelProfile(f func(profile *sb_tpm2.PCRProtectionProfile, par } } -func MockSbSealKeyToTPMMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.SealKeyRequest, params *sb_tpm2.KeyCreationParams) (sb_tpm2.PolicyAuthKey, error)) (restore func()) { +func MockSbSealKeyToTPMMultiple(f func(tpm *sb.TPMConnection, keys []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error)) (restore func()) { old := sbSealKeyToTPMMultiple sbSealKeyToTPMMultiple = f return func() { @@ -89,7 +87,7 @@ func MockSbSealKeyToTPMMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2. } } -func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb_tpm2.PolicyAuthKey, pcrProfile *sb_tpm2.PCRProtectionProfile) error) (restore func()) { +func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, pcrProfile *sb.PCRProtectionProfile) error) (restore func()) { old := sbUpdateKeyPCRProtectionPolicyMultiple sbUpdateKeyPCRProtectionPolicyMultiple = f return func() { @@ -97,7 +95,7 @@ func MockSbUpdateKeyPCRProtectionPolicyMultiple(f func(tpm *sb_tpm2.Connection, } } -func MockSbBlockPCRProtectionPolicies(f func(tpm *sb_tpm2.Connection, pcrs []int) error) (restore func()) { +func MockSbBlockPCRProtectionPolicies(f func(tpm *sb.TPMConnection, pcrs []int) error) (restore func()) { old := sbBlockPCRProtectionPolicies sbBlockPCRProtectionPolicies = f return func() { @@ -114,7 +112,7 @@ func MockSbActivateVolumeWithRecoveryKey(f func(volumeName, sourceDevicePath str } } -func MockSbActivateVolumeWithTPMSealedKey(f func(tpm *sb_tpm2.Connection, volumeName, sourceDevicePath, keyPath string, +func MockSbActivateVolumeWithTPMSealedKey(f func(tpm *sb.TPMConnection, volumeName, sourceDevicePath, keyPath string, pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error)) (restore func()) { old := sbActivateVolumeWithTPMSealedKey sbActivateVolumeWithTPMSealedKey = f @@ -140,7 +138,7 @@ func MockSbActivateVolumeWithKeyData(f func(volumeName, sourceDevicePath string, } } -func MockSbMeasureSnapSystemEpochToTPM(f func(tpm *sb_tpm2.Connection, pcrIndex int) error) (restore func()) { +func MockSbMeasureSnapSystemEpochToTPM(f func(tpm *sb.TPMConnection, pcrIndex int) error) (restore func()) { old := sbMeasureSnapSystemEpochToTPM sbMeasureSnapSystemEpochToTPM = f return func() { @@ -148,7 +146,7 @@ func MockSbMeasureSnapSystemEpochToTPM(f func(tpm *sb_tpm2.Connection, pcrIndex } } -func MockSbMeasureSnapModelToTPM(f func(tpm *sb_tpm2.Connection, pcrIndex int, model sb.SnapModel) error) (restore func()) { +func MockSbMeasureSnapModelToTPM(f func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error) (restore func()) { old := sbMeasureSnapModelToTPM sbMeasureSnapModelToTPM = f return func() { @@ -181,7 +179,7 @@ func MockSbAddRecoveryKeyToLUKS2Container(f func(devicePath string, key []byte, } } -func MockIsTPMEnabled(f func(tpm *sb_tpm2.Connection) bool) (restore func()) { +func MockIsTPMEnabled(f func(tpm *sb.TPMConnection) bool) (restore func()) { old := isTPMEnabled isTPMEnabled = f return func() { @@ -204,11 +202,3 @@ func MockSbDeactivateVolume(f func(volumeName string) error) (restore func()) { sbDeactivateVolume = old } } - -func MockSbReadSealedKeyObject(f func(string) (*sb_tpm2.SealedKeyObject, error)) (restore func()) { - old := sbReadSealedKeyObject - sbReadSealedKeyObject = f - return func() { - sbReadSealedKeyObject = old - } -} diff --git a/secboot/secboot_sb_test.go b/secboot/secboot_sb_test.go index f334d68409..9df8dda31a 100644 --- a/secboot/secboot_sb_test.go +++ b/secboot/secboot_sb_test.go @@ -33,10 +33,7 @@ import ( "path/filepath" "github.com/canonical/go-tpm2" - "github.com/canonical/go-tpm2/linux" sb "github.com/snapcore/secboot" - sb_efi "github.com/snapcore/secboot/efi" - sb_tpm2 "github.com/snapcore/secboot/tpm2" . "gopkg.in/check.v1" "github.com/snapcore/snapd/asserts" @@ -97,14 +94,14 @@ func (s *secbootSuite) TestCheckTPMKeySealingSupported(c *C) { // TPM was detected but it's not enabled {tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"}, // No TPM device - {tpmErr: sb_tpm2.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"}, + {tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"}, } { c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err) _, restore := mockSbTPMConnection(c, tc.tpmErr) defer restore() - restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool { + restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { return tc.tpmEnabled }) defer restore() @@ -147,19 +144,19 @@ func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) { }, { // TPM device does not exist - tpmErr: sb_tpm2.ErrNoTPM2Device, + tpmErr: sb.ErrNoTPM2Device, }, } { mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) defer restore() - restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool { + restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { return tc.tpmEnabled }) defer restore() calls := 0 - restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb_tpm2.Connection, pcrIndex int) error { + restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error { calls++ c.Assert(tpm, Equals, mockTpm) c.Assert(pcrIndex, Equals, 12) @@ -205,7 +202,7 @@ func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) { }, { // TPM device does not exist - tpmErr: sb_tpm2.ErrNoTPM2Device, + tpmErr: sb.ErrNoTPM2Device, }, } { c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled) @@ -214,13 +211,13 @@ func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) { mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr) defer restore() - restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool { + restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { return tc.tpmEnabled }) defer restore() calls := 0 - restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb_tpm2.Connection, pcrIndex int, model sb.SnapModel) error { + restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error { calls++ c.Assert(tpm, Equals, mockTpm) c.Assert(model, Equals, mockModel) @@ -260,7 +257,7 @@ func (s *secbootSuite) TestLockTPMSealedKeys(c *C) { }, // no TPM2 device, shouldn't return an error { - tpmErr: sb_tpm2.ErrNoTPM2Device, + tpmErr: sb.ErrNoTPM2Device, }, // tpm is not enabled but we can lock it { @@ -284,13 +281,13 @@ func (s *secbootSuite) TestLockTPMSealedKeys(c *C) { mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr) defer restoreConnect() - restore := secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool { + restore := secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { return tc.tpmEnabled }) defer restore() sbBlockPCRProtectionPolicesCalls := 0 - restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb_tpm2.Connection, pcrs []int) error { + restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb.TPMConnection, pcrs []int) error { sbBlockPCRProtectionPolicesCalls++ c.Assert(tpm, Equals, mockSbTPM) c.Assert(pcrs, DeepEquals, []int{12}) @@ -384,7 +381,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { // happy case with tpm and encrypted device, activation // with recovery key tpmEnabled: true, hasEncdev: true, activated: true, - activateErr: &sb_tpm2.ActivateWithSealedKeyError{ + activateErr: &sb.ActivateWithTPMSealedKeyError{ // activation error with nil recovery key error // implies volume activated successfully using // the recovery key, @@ -396,7 +393,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { // tpm and encrypted device, successful activation, but // recovery key non-nil is an unexpected state tpmEnabled: true, hasEncdev: true, activated: true, - activateErr: &sb_tpm2.ActivateWithSealedKeyError{ + activateErr: &sb.ActivateWithTPMSealedKeyError{ RecoveryKeyUsageErr: fmt.Errorf("unexpected"), }, expUnlockMethod: secboot.UnlockStatusUnknown, @@ -428,25 +425,25 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, }, { // no tpm, has encrypted device, unlocked using the recovery key - tpmErr: sb_tpm2.ErrNoTPM2Device, hasEncdev: true, + tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, rkAllow: true, disk: mockDiskWithEncDev, expUnlockMethod: secboot.UnlockedWithRecoveryKey, }, { // no tpm, has encrypted device, unlocking with recovery key not allowed - tpmErr: sb_tpm2.ErrNoTPM2Device, hasEncdev: true, + tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, disk: mockDiskWithEncDev, err: `cannot activate encrypted device ".*/enc-dev-partuuid": activation error`, }, { // no tpm, has encrypted device, recovery key unlocking fails rkErr: errors.New("cannot unlock with recovery key"), - tpmErr: sb_tpm2.ErrNoTPM2Device, hasEncdev: true, + tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, rkAllow: true, disk: mockDiskWithEncDev, err: `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`, }, { // no tpm, no encrypted device - tpmErr: sb_tpm2.ErrNoTPM2Device, + tpmErr: sb.ErrNoTPM2Device, disk: mockDiskWithUnencDev, }, { // no disks at all @@ -468,7 +465,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { _, restoreConnect := mockSbTPMConnection(c, tc.tpmErr) defer restoreConnect() - restore = secboot.MockIsTPMEnabled(func(tpm *sb_tpm2.Connection) bool { + restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool { return tc.tpmEnabled }) defer restore() @@ -490,7 +487,7 @@ func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) { expKeyPath = "vanilla-keyfile" } - restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb_tpm2.Connection, volumeName, sourceDevicePath, + restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath, keyPath string, pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error) { c.Assert(volumeName, Equals, "name-"+randomUUID) c.Assert(sourceDevicePath, Equals, devicePath) @@ -573,13 +570,13 @@ func (s *secbootSuite) TestEFIImageFromBootFile(c *C) { for _, tc := range []struct { bootFile bootloader.BootFile - efiImage sb_efi.Image + efiImage sb.EFIImage err string }{ { // happy case for EFI image bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery), - efiImage: sb_efi.FileImage(existingFile), + efiImage: sb.FileEFIImage(existingFile), }, { // missing EFI image @@ -589,7 +586,7 @@ func (s *secbootSuite) TestEFIImageFromBootFile(c *C) { { // happy case for snap file bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery), - efiImage: sb_efi.SnapFileImage{Container: snapf, FileName: "rel"}, + efiImage: sb.SnapFileEFIImage{Container: snapf, FileName: "rel"}, }, { // invalid snap file @@ -726,14 +723,14 @@ func (s *secbootSuite) TestSealKey(c *C) { // events for // a -> kernel - sequences1 := []*sb_efi.ImageLoadEvent{ + sequences1 := []*sb.EFIImageLoadEvent{ { - Source: sb_efi.Firmware, - Image: sb_efi.FileImage(mockBF[0].Path), - Next: []*sb_efi.ImageLoadEvent{ + Source: sb.Firmware, + Image: sb.FileEFIImage(mockBF[0].Path), + Next: []*sb.EFIImageLoadEvent{ { - Source: sb_efi.Shim, - Image: sb_efi.SnapFileImage{ + Source: sb.Shim, + Image: sb.SnapFileEFIImage{ Container: kernelSnap, FileName: "kernel.efi", }, @@ -745,14 +742,14 @@ func (s *secbootSuite) TestSealKey(c *C) { // "cdk" events for // c -> kernel OR // d -> kernel - cdk := []*sb_efi.ImageLoadEvent{ + cdk := []*sb.EFIImageLoadEvent{ { - Source: sb_efi.Shim, - Image: sb_efi.FileImage(mockBF[2].Path), - Next: []*sb_efi.ImageLoadEvent{ + Source: sb.Shim, + Image: sb.FileEFIImage(mockBF[2].Path), + Next: []*sb.EFIImageLoadEvent{ { - Source: sb_efi.Shim, - Image: sb_efi.SnapFileImage{ + Source: sb.Shim, + Image: sb.SnapFileEFIImage{ Container: kernelSnap, FileName: "kernel.efi", }, @@ -760,12 +757,12 @@ func (s *secbootSuite) TestSealKey(c *C) { }, }, { - Source: sb_efi.Shim, - Image: sb_efi.FileImage(mockBF[3].Path), - Next: []*sb_efi.ImageLoadEvent{ + Source: sb.Shim, + Image: sb.FileEFIImage(mockBF[3].Path), + Next: []*sb.EFIImageLoadEvent{ { - Source: sb_efi.Shim, - Image: sb_efi.SnapFileImage{ + Source: sb.Shim, + Image: sb.SnapFileEFIImage{ Container: kernelSnap, FileName: "kernel.efi", }, @@ -777,15 +774,15 @@ func (s *secbootSuite) TestSealKey(c *C) { // events for // a -> "cdk" // b -> "cdk" - sequences2 := []*sb_efi.ImageLoadEvent{ + sequences2 := []*sb.EFIImageLoadEvent{ { - Source: sb_efi.Firmware, - Image: sb_efi.FileImage(mockBF[0].Path), + Source: sb.Firmware, + Image: sb.FileEFIImage(mockBF[0].Path), Next: cdk, }, { - Source: sb_efi.Firmware, - Image: sb_efi.FileImage(mockBF[1].Path), + Source: sb.Firmware, + Image: sb.FileEFIImage(mockBF[1].Path), Next: cdk, }, } @@ -794,9 +791,9 @@ func (s *secbootSuite) TestSealKey(c *C) { defer restore() // mock adding EFI secure boot policy profile - var pcrProfile *sb_tpm2.PCRProtectionProfile + var pcrProfile *sb.PCRProtectionProfile addEFISbPolicyCalls := 0 - restore = secboot.MockSbEfiAddSecureBootPolicyProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error { + restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error { addEFISbPolicyCalls++ pcrProfile = profile c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -806,7 +803,7 @@ func (s *secbootSuite) TestSealKey(c *C) { case 2: c.Assert(params.LoadSequences, DeepEquals, sequences2) default: - c.Error("AddSecureBootPolicyProfile shouldn't be called a third time") + c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time") } return tc.addEFISbPolicyErr }) @@ -814,7 +811,7 @@ func (s *secbootSuite) TestSealKey(c *C) { // mock adding EFI boot manager profile addEFIBootManagerCalls := 0 - restore = secboot.MockSbEfiAddBootManagerProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error { + restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error { addEFIBootManagerCalls++ c.Assert(profile, Equals, pcrProfile) c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -824,7 +821,7 @@ func (s *secbootSuite) TestSealKey(c *C) { case 2: c.Assert(params.LoadSequences, DeepEquals, sequences2) default: - c.Error("AddBootManagerProfile shouldn't be called a third time") + c.Error("AddEFIBootManagerProfile shouldn't be called a third time") } return tc.addEFIBootManagerErr }) @@ -832,7 +829,7 @@ func (s *secbootSuite) TestSealKey(c *C) { // mock adding systemd EFI stub profile addSystemdEfiStubCalls := 0 - restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error { + restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error { addSystemdEfiStubCalls++ c.Assert(profile, Equals, pcrProfile) c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -843,7 +840,7 @@ func (s *secbootSuite) TestSealKey(c *C) { case 2: c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines) default: - c.Error("AddSystemdStubProfile shouldn't be called a third time") + c.Error("AddSystemdEFIStubProfile shouldn't be called a third time") } return tc.addSystemdEFIStubErr }) @@ -851,7 +848,7 @@ func (s *secbootSuite) TestSealKey(c *C) { // mock adding snap model profile addSnapModelCalls := 0 - restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error { + restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error { addSnapModelCalls++ c.Assert(profile, Equals, pcrProfile) c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -870,10 +867,10 @@ func (s *secbootSuite) TestSealKey(c *C) { // mock provisioning provisioningCalls := 0 - restore = secboot.MockProvisionTPM(func(t *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode, newLockoutAuth []byte) error { + restore = secboot.MockProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error { provisioningCalls++ c.Assert(t, Equals, tpm) - c.Assert(mode, Equals, sb_tpm2.ProvisionModeFull) + c.Assert(mode, Equals, sb.ProvisionModeFull) c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent) return tc.provisioningErr }) @@ -881,18 +878,18 @@ func (s *secbootSuite) TestSealKey(c *C) { // mock sealing sealCalls := 0 - restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb_tpm2.Connection, kr []*sb_tpm2.SealKeyRequest, params *sb_tpm2.KeyCreationParams) (sb_tpm2.PolicyAuthKey, error) { + restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb.TPMConnection, kr []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error) { sealCalls++ c.Assert(t, Equals, tpm) - c.Assert(kr, DeepEquals, []*sb_tpm2.SealKeyRequest{{Key: myKey, Path: "keyfile"}, {Key: myKey2, Path: "keyfile2"}}) + c.Assert(kr, DeepEquals, []*sb.SealKeyRequest{{Key: myKey, Path: "keyfile"}, {Key: myKey2, Path: "keyfile2"}}) c.Assert(params.AuthKey, Equals, myAuthKey) c.Assert(params.PCRPolicyCounterHandle, Equals, tpm2.Handle(42)) - return sb_tpm2.PolicyAuthKey{}, tc.sealErr + return sb.TPMPolicyAuthKey{}, tc.sealErr }) defer restore() // mock TPM enabled check - restore = secboot.MockIsTPMEnabled(func(t *sb_tpm2.Connection) bool { + restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool { return tc.tpmEnabled }) defer restore() @@ -917,23 +914,18 @@ func (s *secbootSuite) TestResealKey(c *C) { mockErr := errors.New("some error") for _, tc := range []struct { - tpmErr error - tpmEnabled bool - missingFile bool - addEFISbPolicyErr error - addEFIBootManagerErr error - addSystemdEFIStubErr error - addSnapModelErr error - readSealedKeyObjectErr error - provisioningErr error - resealErr error - resealCalls int - expectedErr string + tpmErr error + tpmEnabled bool + missingFile bool + addEFISbPolicyErr error + addEFIBootManagerErr error + addSystemdEFIStubErr error + addSnapModelErr error + provisioningErr error + resealErr error + resealCalls int + expectedErr string }{ - // happy case - {tpmEnabled: true, resealCalls: 1, expectedErr: ""}, - - // unhappy cases {tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"}, {tpmEnabled: false, expectedErr: "TPM device is not enabled"}, {tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file .*/file.efi does not exist"}, @@ -941,8 +933,8 @@ func (s *secbootSuite) TestResealKey(c *C) { {tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"}, {tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"}, {tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"}, - {tpmEnabled: true, readSealedKeyObjectErr: mockErr, expectedErr: "some error"}, {tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "some error"}, + {tpmEnabled: true, resealCalls: 1, expectedErr: ""}, } { mockTPMPolicyAuthKey := []byte{1, 3, 3, 7} mockTPMPolicyAuthKeyFile := filepath.Join(c.MkDir(), "policy-auth-key-file") @@ -967,24 +959,10 @@ func (s *secbootSuite) TestResealKey(c *C) { TPMPolicyAuthKeyFile: mockTPMPolicyAuthKeyFile, } - numMockSealedKeyObjects := len(myParams.KeyFiles) - mockSealedKeyObjects := make([]*sb_tpm2.SealedKeyObject, 0, numMockSealedKeyObjects) - for range myParams.KeyFiles { - // Copy of - // https://github.com/snapcore/secboot/blob/master/internal/compattest/testdata/v1/key - // To create full looking - // mockSealedKeyObjects, although {},{} would - // have been enough as well - mockSealedKeyFile := filepath.Join("test-data", "keyfile") - mockSealedKeyObject, err := sb_tpm2.ReadSealedKeyObject(mockSealedKeyFile) - c.Assert(err, IsNil) - mockSealedKeyObjects = append(mockSealedKeyObjects, mockSealedKeyObject) - } - - sequences := []*sb_efi.ImageLoadEvent{ + sequences := []*sb.EFIImageLoadEvent{ { - Source: sb_efi.Firmware, - Image: sb_efi.FileImage(mockEFI.Path), + Source: sb.Firmware, + Image: sb.FileEFIImage(mockEFI.Path), }, } @@ -993,15 +971,15 @@ func (s *secbootSuite) TestResealKey(c *C) { defer restore() // mock TPM enabled check - restore = secboot.MockIsTPMEnabled(func(t *sb_tpm2.Connection) bool { + restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool { return tc.tpmEnabled }) defer restore() // mock adding EFI secure boot policy profile - var pcrProfile *sb_tpm2.PCRProtectionProfile + var pcrProfile *sb.PCRProtectionProfile addEFISbPolicyCalls := 0 - restore = secboot.MockSbEfiAddSecureBootPolicyProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SecureBootPolicyProfileParams) error { + restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error { addEFISbPolicyCalls++ pcrProfile = profile c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -1012,7 +990,7 @@ func (s *secbootSuite) TestResealKey(c *C) { // mock adding EFI boot manager profile addEFIBootManagerCalls := 0 - restore = secboot.MockSbEfiAddBootManagerProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.BootManagerProfileParams) error { + restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error { addEFIBootManagerCalls++ c.Assert(profile, Equals, pcrProfile) c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -1023,7 +1001,7 @@ func (s *secbootSuite) TestResealKey(c *C) { // mock adding systemd EFI stub profile addSystemdEfiStubCalls := 0 - restore = secboot.MockSbEfiAddSystemdStubProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_efi.SystemdStubProfileParams) error { + restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error { addSystemdEfiStubCalls++ c.Assert(profile, Equals, pcrProfile) c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -1035,7 +1013,7 @@ func (s *secbootSuite) TestResealKey(c *C) { // mock adding snap model profile addSnapModelCalls := 0 - restore = secboot.MockSbAddSnapModelProfile(func(profile *sb_tpm2.PCRProtectionProfile, params *sb_tpm2.SnapModelProfileParams) error { + restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error { addSnapModelCalls++ c.Assert(profile, Equals, pcrProfile) c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256) @@ -1045,22 +1023,13 @@ func (s *secbootSuite) TestResealKey(c *C) { }) defer restore() - // mock ReadSealedKeyObject - readSealedKeyObjectCalls := 0 - restore = secboot.MockSbReadSealedKeyObject(func(keyfile string) (*sb_tpm2.SealedKeyObject, error) { - readSealedKeyObjectCalls++ - c.Assert(keyfile, Equals, myParams.KeyFiles[readSealedKeyObjectCalls-1]) - return mockSealedKeyObjects[readSealedKeyObjectCalls-1], tc.readSealedKeyObjectErr - }) - defer restore() - // mock PCR protection policy update resealCalls := 0 - restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb_tpm2.Connection, keys []*sb_tpm2.SealedKeyObject, authKey sb_tpm2.PolicyAuthKey, profile *sb_tpm2.PCRProtectionProfile) error { + restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, profile *sb.PCRProtectionProfile) error { resealCalls++ c.Assert(t, Equals, tpm) - c.Assert(keys, DeepEquals, mockSealedKeyObjects) - c.Assert(authKey, DeepEquals, sb_tpm2.PolicyAuthKey(mockTPMPolicyAuthKey)) + c.Assert(keyPaths, DeepEquals, []string{"keyfile", "keyfile2"}) + c.Assert(authKey, DeepEquals, sb.TPMPolicyAuthKey(mockTPMPolicyAuthKey)) c.Assert(profile, Equals, pcrProfile) return tc.resealErr }) @@ -1073,7 +1042,7 @@ func (s *secbootSuite) TestResealKey(c *C) { c.Assert(addSystemdEfiStubCalls, Equals, 1) c.Assert(addSnapModelCalls, Equals, 1) } else { - c.Assert(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc)) + c.Assert(err, ErrorMatches, tc.expectedErr) } c.Assert(resealCalls, Equals, tc.resealCalls) } @@ -1110,13 +1079,13 @@ func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, err return snapfile.Open(snapPath) } -func mockSbTPMConnection(c *C, tpmErr error) (*sb_tpm2.Connection, func()) { - tcti, err := linux.OpenDevice("/dev/null") +func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) { + tcti, err := tpm2.OpenTPMDevice("/dev/null") c.Assert(err, IsNil) - tpmctx := tpm2.NewTPMContext(tcti) + tpmctx, err := tpm2.NewTPMContext(tcti) c.Assert(err, IsNil) - tpm := &sb_tpm2.Connection{TPMContext: tpmctx} - restore := secboot.MockSbConnectToDefaultTPM(func() (*sb_tpm2.Connection, error) { + tpm := &sb.TPMConnection{TPMContext: tpmctx} + restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) { if tpmErr != nil { return nil, tpmErr } diff --git a/secboot/secboot_tpm.go b/secboot/secboot_tpm.go index e6fdc9aa76..815b9d45cd 100644 --- a/secboot/secboot_tpm.go +++ b/secboot/secboot_tpm.go @@ -28,8 +28,6 @@ import ( "github.com/canonical/go-tpm2" sb "github.com/snapcore/secboot" - sb_efi "github.com/snapcore/secboot/efi" - sb_tpm2 "github.com/snapcore/secboot/tpm2" "golang.org/x/xerrors" "github.com/snapcore/snapd/asserts" @@ -46,18 +44,17 @@ const ( ) var ( - sbConnectToDefaultTPM = sb_tpm2.ConnectToDefaultTPM - sbMeasureSnapSystemEpochToTPM = sb_tpm2.MeasureSnapSystemEpochToTPM - sbMeasureSnapModelToTPM = sb_tpm2.MeasureSnapModelToTPM - sbBlockPCRProtectionPolicies = sb_tpm2.BlockPCRProtectionPolicies - sbActivateVolumeWithTPMSealedKey = sb_tpm2.ActivateVolumeWithSealedKey - sbefiAddSecureBootPolicyProfile = sb_efi.AddSecureBootPolicyProfile - sbefiAddBootManagerProfile = sb_efi.AddBootManagerProfile - sbefiAddSystemdStubProfile = sb_efi.AddSystemdStubProfile - sbAddSnapModelProfile = sb_tpm2.AddSnapModelProfile - sbSealKeyToTPMMultiple = sb_tpm2.SealKeyToTPMMultiple - sbUpdateKeyPCRProtectionPolicyMultiple = sb_tpm2.UpdateKeyPCRProtectionPolicyMultiple - sbReadSealedKeyObject = sb_tpm2.ReadSealedKeyObject + sbConnectToDefaultTPM = sb.ConnectToDefaultTPM + sbMeasureSnapSystemEpochToTPM = sb.MeasureSnapSystemEpochToTPM + sbMeasureSnapModelToTPM = sb.MeasureSnapModelToTPM + sbBlockPCRProtectionPolicies = sb.BlockPCRProtectionPolicies + sbActivateVolumeWithTPMSealedKey = sb.ActivateVolumeWithTPMSealedKey + sbAddEFISecureBootPolicyProfile = sb.AddEFISecureBootPolicyProfile + sbAddEFIBootManagerProfile = sb.AddEFIBootManagerProfile + sbAddSystemdEFIStubProfile = sb.AddSystemdEFIStubProfile + sbAddSnapModelProfile = sb.AddSnapModelProfile + sbSealKeyToTPMMultiple = sb.SealKeyToTPMMultiple + sbUpdateKeyPCRProtectionPolicyMultiple = sb.UpdateKeyPCRProtectionPolicyMultiple randutilRandomKernelUUID = randutil.RandomKernelUUID @@ -68,7 +65,7 @@ var ( _ (sb.SnapModel) = ModelForSealing(nil) ) -func isTPMEnabledImpl(tpm *sb_tpm2.Connection) bool { +func isTPMEnabledImpl(tpm *sb.TPMConnection) bool { return tpm.IsEnabled() } @@ -122,15 +119,15 @@ func checkSecureBootEnabled() error { // for measurement from the initramfs. const initramfsPCR = 12 -func insecureConnectToTPM() (*sb_tpm2.Connection, error) { +func insecureConnectToTPM() (*sb.TPMConnection, error) { return sbConnectToDefaultTPM() } -func measureWhenPossible(whatHow func(tpm *sb_tpm2.Connection) error) error { +func measureWhenPossible(whatHow func(tpm *sb.TPMConnection) error) error { // the model is ready, we're good to try measuring it now tpm, err := insecureConnectToTPM() if err != nil { - if xerrors.Is(err, sb_tpm2.ErrNoTPM2Device) { + if xerrors.Is(err, sb.ErrNoTPM2Device) { return nil } return fmt.Errorf("cannot open TPM connection: %v", err) @@ -147,7 +144,7 @@ func measureWhenPossible(whatHow func(tpm *sb_tpm2.Connection) error) error { // MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the // TPM device is available. If there's no TPM device success is returned. func MeasureSnapSystemEpochWhenPossible() error { - measure := func(tpm *sb_tpm2.Connection) error { + measure := func(tpm *sb.TPMConnection) error { return sbMeasureSnapSystemEpochToTPM(tpm, initramfsPCR) } @@ -161,7 +158,7 @@ func MeasureSnapSystemEpochWhenPossible() error { // MeasureSnapModelWhenPossible measures the snap model only if the TPM device is // available. If there's no TPM device success is returned. func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error { - measure := func(tpm *sb_tpm2.Connection) error { + measure := func(tpm *sb.TPMConnection) error { model, err := findModel() if err != nil { return err @@ -179,7 +176,7 @@ func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) erro func lockTPMSealedKeys() error { tpm, tpmErr := sbConnectToDefaultTPM() if tpmErr != nil { - if xerrors.Is(tpmErr, sb_tpm2.ErrNoTPM2Device) { + if xerrors.Is(tpmErr, sb.ErrNoTPM2Device) { logger.Noticef("cannot open TPM connection: %v", tpmErr) return nil } @@ -211,7 +208,7 @@ func unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, // Obtain a TPM connection. tpm, tpmErr := sbConnectToDefaultTPM() if tpmErr != nil { - if !xerrors.Is(tpmErr, sb_tpm2.ErrNoTPM2Device) { + if !xerrors.Is(tpmErr, sb.ErrNoTPM2Device) { return res, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr) } logger.Noticef("cannot open TPM connection: %v", tpmErr) @@ -248,10 +245,10 @@ func isActivatedWithRecoveryKey(err error) bool { if err == nil { return false } - // with non-nil err, we should check for err being ActivateWithSealedKeyError + // with non-nil err, we should check for err being ActivateWithTPMSealedKeyError // and RecoveryKeyUsageErr inside that being nil - this indicates that the // recovery key was used to unlock it - activateErr, ok := err.(*sb_tpm2.ActivateWithSealedKeyError) + activateErr, ok := err.(*sb.ActivateWithTPMSealedKeyError) if !ok { return false } @@ -275,7 +272,7 @@ func activateVolOpts(allowRecoveryKey bool) *sb.ActivateVolumeOptions { // unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted // device. If activation with the sealed key fails, this function will attempt to // activate it with the fallback recovery key instead. -func unlockEncryptedPartitionWithSealedKey(tpm *sb_tpm2.Connection, name, device, keyfile string, allowRecovery bool) (UnlockMethod, error) { +func unlockEncryptedPartitionWithSealedKey(tpm *sb.TPMConnection, name, device, keyfile string, allowRecovery bool) (UnlockMethod, error) { options := activateVolOpts(allowRecovery) // XXX: pinfile is currently not used activated, err := sbActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, options) @@ -328,15 +325,15 @@ func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error { } // Seal the provided keys to the TPM - creationParams := sb_tpm2.KeyCreationParams{ + creationParams := sb.KeyCreationParams{ PCRProfile: pcrProfile, PCRPolicyCounterHandle: tpm2.Handle(params.PCRPolicyCounterHandle), AuthKey: params.TPMPolicyAuthKey, } - sbKeys := make([]*sb_tpm2.SealKeyRequest, 0, len(keys)) + sbKeys := make([]*sb.SealKeyRequest, 0, len(keys)) for i := range keys { - sbKeys = append(sbKeys, &sb_tpm2.SealKeyRequest{ + sbKeys = append(sbKeys, &sb.SealKeyRequest{ Key: keys[i].Key, Path: keys[i].KeyFile, }) @@ -382,25 +379,15 @@ func ResealKeys(params *ResealKeysParams) error { return fmt.Errorf("cannot read the policy auth key file: %v", err) } - numSealedKeyObjects := len(params.KeyFiles) - sealedKeyObjects := make([]*sb_tpm2.SealedKeyObject, 0, numSealedKeyObjects) - for _, keyfile := range params.KeyFiles { - sealedKeyObject, err := sbReadSealedKeyObject(keyfile) - if err != nil { - return err - } - sealedKeyObjects = append(sealedKeyObjects, sealedKeyObject) - } - - return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, sealedKeyObjects, authKey, pcrProfile) + return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, params.KeyFiles, authKey, pcrProfile) } -func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRProtectionProfile, error) { +func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb.PCRProtectionProfile, error) { numModels := len(modelParams) - modelPCRProfiles := make([]*sb_tpm2.PCRProtectionProfile, 0, numModels) + modelPCRProfiles := make([]*sb.PCRProtectionProfile, 0, numModels) for _, mp := range modelParams { - modelProfile := sb_tpm2.NewPCRProtectionProfile() + modelProfile := sb.NewPCRProtectionProfile() loadSequences, err := buildLoadSequences(mp.EFILoadChains) if err != nil { @@ -408,7 +395,7 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP } // Add EFI secure boot policy profile - policyParams := sb_efi.SecureBootPolicyProfileParams{ + policyParams := sb.EFISecureBootPolicyProfileParams{ PCRAlgorithm: tpm2.HashAlgorithmSHA256, LoadSequences: loadSequences, // TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden @@ -417,34 +404,34 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP // ensure that the PCR profile is updated before/after sbkeysync executes. } - if err := sbefiAddSecureBootPolicyProfile(modelProfile, &policyParams); err != nil { + if err := sbAddEFISecureBootPolicyProfile(modelProfile, &policyParams); err != nil { return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err) } // Add EFI boot manager profile - bootManagerParams := sb_efi.BootManagerProfileParams{ + bootManagerParams := sb.EFIBootManagerProfileParams{ PCRAlgorithm: tpm2.HashAlgorithmSHA256, LoadSequences: loadSequences, } - if err := sbefiAddBootManagerProfile(modelProfile, &bootManagerParams); err != nil { + if err := sbAddEFIBootManagerProfile(modelProfile, &bootManagerParams); err != nil { return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err) } // Add systemd EFI stub profile if len(mp.KernelCmdlines) != 0 { - systemdStubParams := sb_efi.SystemdStubProfileParams{ + systemdStubParams := sb.SystemdEFIStubProfileParams{ PCRAlgorithm: tpm2.HashAlgorithmSHA256, PCRIndex: initramfsPCR, KernelCmdlines: mp.KernelCmdlines, } - if err := sbefiAddSystemdStubProfile(modelProfile, &systemdStubParams); err != nil { + if err := sbAddSystemdEFIStubProfile(modelProfile, &systemdStubParams); err != nil { return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err) } } // Add snap model profile if mp.Model != nil { - snapModelParams := sb_tpm2.SnapModelProfileParams{ + snapModelParams := sb.SnapModelProfileParams{ PCRAlgorithm: tpm2.HashAlgorithmSHA256, PCRIndex: initramfsPCR, Models: []sb.SnapModel{mp.Model}, @@ -457,9 +444,9 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP modelPCRProfiles = append(modelPCRProfiles, modelProfile) } - var pcrProfile *sb_tpm2.PCRProtectionProfile + var pcrProfile *sb.PCRProtectionProfile if numModels > 1 { - pcrProfile = sb_tpm2.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...) + pcrProfile = sb.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...) } else { pcrProfile = modelPCRProfiles[0] } @@ -469,7 +456,7 @@ func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRP return pcrProfile, nil } -func tpmProvision(tpm *sb_tpm2.Connection, lockoutAuthFile string) error { +func tpmProvision(tpm *sb.TPMConnection, lockoutAuthFile string) error { // Create and save the lockout authorization file lockoutAuth := make([]byte, 16) // crypto rand is protected against short reads @@ -484,19 +471,19 @@ func tpmProvision(tpm *sb_tpm2.Connection, lockoutAuthFile string) error { // TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot // if the device has previously been provisioned, see // https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI - if err := provisionTPM(tpm, sb_tpm2.ProvisionModeFull, lockoutAuth); err != nil { + if err := provisionTPM(tpm, sb.ProvisionModeFull, lockoutAuth); err != nil { logger.Noticef("TPM provisioning error: %v", err) return fmt.Errorf("cannot provision TPM: %v", err) } return nil } -func provisionTPMImpl(tpm *sb_tpm2.Connection, mode sb_tpm2.ProvisionMode, lockoutAuth []byte) error { +func provisionTPMImpl(tpm *sb.TPMConnection, mode sb.ProvisionMode, lockoutAuth []byte) error { return tpm.EnsureProvisioned(mode, lockoutAuth) } // buildLoadSequences builds EFI load image event trees from this package LoadChains -func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent, err error) { +func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb.EFIImageLoadEvent, err error) { // this will build load event trees for the current // device configuration, e.g. something like: // @@ -508,7 +495,7 @@ func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent, for _, chain := range chains { // root of load events has source Firmware - loadseq, err := chain.loadEvent(sb_efi.Firmware) + loadseq, err := chain.loadEvent(sb.Firmware) if err != nil { return nil, err } @@ -518,11 +505,11 @@ func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent, } // loadEvent builds the corresponding load event and its tree -func (lc *LoadChain) loadEvent(source sb_efi.ImageLoadEventSource) (*sb_efi.ImageLoadEvent, error) { - var next []*sb_efi.ImageLoadEvent +func (lc *LoadChain) loadEvent(source sb.EFIImageLoadEventSource) (*sb.EFIImageLoadEvent, error) { + var next []*sb.EFIImageLoadEvent for _, nextChain := range lc.Next { // everything that is not the root has source shim - ev, err := nextChain.loadEvent(sb_efi.Shim) + ev, err := nextChain.loadEvent(sb.Shim) if err != nil { return nil, err } @@ -532,26 +519,26 @@ func (lc *LoadChain) loadEvent(source sb_efi.ImageLoadEventSource) (*sb_efi.Imag if err != nil { return nil, err } - return &sb_efi.ImageLoadEvent{ + return &sb.EFIImageLoadEvent{ Source: source, Image: image, Next: next, }, nil } -func efiImageFromBootFile(b *bootloader.BootFile) (sb_efi.Image, error) { +func efiImageFromBootFile(b *bootloader.BootFile) (sb.EFIImage, error) { if b.Snap == "" { if !osutil.FileExists(b.Path) { return nil, fmt.Errorf("file %s does not exist", b.Path) } - return sb_efi.FileImage(b.Path), nil + return sb.FileEFIImage(b.Path), nil } snapf, err := snapfile.Open(b.Snap) if err != nil { return nil, err } - return sb_efi.SnapFileImage{ + return sb.SnapFileEFIImage{ Container: snapf, FileName: b.Path, }, nil diff --git a/secboot/test-data/keyfile b/secboot/test-data/keyfile deleted file mode 100644 index c588b55e7e..0000000000 Binary files a/secboot/test-data/keyfile and /dev/null differ -- cgit v1.2.3 From ed1d989112056e54524258f41c34900b96e83c86 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 14 Oct 2021 13:16:12 -0500 Subject: tests/nested/manual/grade-signed-*: rename to have core20 prefix All these tests are specific to UC20, so name them as such. Signed-off-by: Ian Johnson --- .../defaults.yaml | 6 + .../prepare-device | 3 + .../task.yaml | 128 ++++++++++++++++ .../defaults.yaml | 6 + .../prepare-device | 3 + .../task.yaml | 163 +++++++++++++++++++++ .../grade-signed-above-testkeys-boot/defaults.yaml | 6 - .../prepare-device | 3 - .../grade-signed-above-testkeys-boot/task.yaml | 128 ---------------- .../grade-signed-cloud-init-testkeys/defaults.yaml | 6 - .../prepare-device | 3 - .../grade-signed-cloud-init-testkeys/task.yaml | 163 --------------------- 12 files changed, 309 insertions(+), 309 deletions(-) create mode 100644 tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml create mode 100755 tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device create mode 100644 tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml create mode 100644 tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml create mode 100755 tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device create mode 100644 tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml delete mode 100644 tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml delete mode 100755 tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device delete mode 100644 tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml delete mode 100644 tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml delete mode 100755 tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device delete mode 100644 tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml diff --git a/tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml new file mode 100644 index 0000000000..30292153b8 --- /dev/null +++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml @@ -0,0 +1,6 @@ +defaults: + system: + refresh: + hold: "HOLD-TIME" + journal: + persistent: true diff --git a/tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device new file mode 100755 index 0000000000..357c0850fa --- /dev/null +++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device @@ -0,0 +1,3 @@ +#!/bin/sh +# 10.0.2.2 is the host from a nested VM +snapctl set device-service.url=http://10.0.2.2:11029 diff --git a/tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml new file mode 100644 index 0000000000..69b1cca28e --- /dev/null +++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml @@ -0,0 +1,128 @@ +summary: Test that snapd with testkeys on UC20 can boot a model with grade signed. + +systems: [ubuntu-20.04-64] + +environment: + # use tpm + secure boot to get full disk encryption, this is explicitly needed + # for grade: secured + NESTED_ENABLE_TPM: true + NESTED_ENABLE_SECURE_BOOT: true + + # use snapd from the spread run so that we have testkeys trusted in the snapd + # run + NESTED_BUILD_SNAPD_FROM_CURRENT: true + + # don't use cloud-init, that will be a separate test, we only use sys-user + # assertions to create the user for this test + NESTED_USE_CLOUD_INIT: false + + # sign all the snaps we build for the image with fakestore + NESTED_SIGN_SNAPS_FAKESTORE: true + + # use the testrootorg auto-import assertion + # TODO: commit the Go code used to create this assertion from the json file + NESTED_CUSTOM_AUTO_IMPORT_ASSERTION: $TESTSLIB/assertions/developer1-auto-import.assert + + # two variants, for signed and secured grades + MODEL_GRADE/secured: secured + MODEL_GRADE/signed: signed + + NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/developer1-20-${MODEL_GRADE}.model + NESTED_IMAGE_ID: testkeys-${MODEL_GRADE} + + # for the fake store + NESTED_FAKESTORE_BLOB_DIR: $(pwd)/fake-store-blobdir + NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL: http://localhost:11028 + + # unset this otherwise ubuntu-image complains about overriding the channel for + # a model with grade higher than dangerous when building the image + NESTED_CORE_CHANNEL: "" + +prepare: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + + #shellcheck source=tests/lib/nested.sh + . "$TESTSLIB/nested.sh" + + #shellcheck source=tests/lib/store.sh + . "$TESTSLIB"/store.sh + + # setup the fakestore, but don't use it for our snapd here on the host VM, so + # tear down the staging_store immediately afterwards so that only the SAS is + # running and our snapd is not pointed at it, ubuntu-image is the only thing + # that actually needs to use the fakestore, and we will manually point it at + # the fakestore below using NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL + setup_fake_store "$NESTED_FAKESTORE_BLOB_DIR" + teardown_staging_store + + echo Expose the needed assertions through the fakestore + cp "$TESTSLIB"/assertions/developer1.account "$NESTED_FAKESTORE_BLOB_DIR/asserts" + cp "$TESTSLIB"/assertions/developer1.account-key "$NESTED_FAKESTORE_BLOB_DIR/asserts" + + # modify and repack gadget snap to add a defaults section and use our own + # prepare-device hook to use the fakedevicesvc + + # Get the snakeoil key and cert for signing gadget assets (shim) + KEY_NAME=$(tests.nested download snakeoil-key) + SNAKEOIL_KEY="$PWD/$KEY_NAME.key" + SNAKEOIL_CERT="$PWD/$KEY_NAME.pem" + + snap download --basename=pc --channel="20/edge" pc + unsquashfs -d pc-gadget pc.snap + + # delay all refreshes for a week from now, as otherwise refreshes for our + # snaps (which are asserted by the testrootorg authority-id) may happen, which + # will break things because the signing keys won't match, etc. and + # specifically snap-bootstrap in the kernel snap from the store won't trust + # the seed keys to unlock the encrypted data partition in the initramfs + sed defaults.yaml -e "s/HOLD-TIME/$(date --date="next week" +%Y-%m-%dT%H:%M:%S%:z)/" >> \ + pc-gadget/meta/gadget.yaml + + # copy the prepare-device hook to use our fakedevicesvc + mkdir -p pc-gadget/meta/hooks/ + cp prepare-device pc-gadget/meta/hooks/ + + tests.nested secboot-sign gadget pc-gadget "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" + snap pack pc-gadget/ "$(tests.nested get extra-snaps-path)" + rm -rf pc-gadget/ + + rm -f "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" + + # start fake device svc + systemd-run --collect --unit fakedevicesvc fakedevicesvc localhost:11029 + + tests.nested build-image core + tests.nested create-vm core + +restore: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + + # stop fake device svc + systemctl stop fakedevicesvc + + #shellcheck source=tests/lib/store.sh + . "$TESTSLIB"/store.sh + teardown_fake_store "$NESTED_FAKESTORE_BLOB_DIR" + +debug: | + systemctl status fakedevicesvc || true + +execute: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + + # wait for the initialize device task to be done + retry -n 200 --wait 1 sh -c "tests.nested exec snap changes | MATCH 'Done.*Initialize device'" + + echo "Check we have the right model from snap model" + tests.nested exec "sudo snap model --verbose" | MATCH "model:\s+testkeys-snapd-${MODEL_GRADE}-core-20-amd64" + tests.nested exec "sudo snap model --verbose" | MATCH "grade:\s+${MODEL_GRADE}" + tests.nested exec "sudo snap model --verbose --serial" | MATCH "serial:\s+7777" diff --git a/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml new file mode 100644 index 0000000000..6417309d65 --- /dev/null +++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml @@ -0,0 +1,6 @@ +defaults: + system: + refresh: + hold: "@HOLD-TIME@" + journal: + persistent: true diff --git a/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device new file mode 100755 index 0000000000..357c0850fa --- /dev/null +++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device @@ -0,0 +1,3 @@ +#!/bin/sh +# 10.0.2.2 is the host from a nested VM +snapctl set device-service.url=http://10.0.2.2:11029 diff --git a/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml new file mode 100644 index 0000000000..a930eb5b08 --- /dev/null +++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml @@ -0,0 +1,163 @@ +summary: Test that UC20 with testkeys can boot a grade signed model with cloud-init. + +systems: [ubuntu-20.04-64] + +environment: + # use tpm + secure boot to get full disk encryption, this is explicitly needed + # for grade: secured + NESTED_ENABLE_TPM: true + NESTED_ENABLE_SECURE_BOOT: true + + # use snapd from the spread run so that we have testkeys trusted in the snapd + # run + NESTED_BUILD_SNAPD_FROM_CURRENT: true + + # don't use cloud-init to create the user, we manually use cloud-init via + # --param-cdrom in the test setup + NESTED_USE_CLOUD_INIT: false + + # sign all the snaps we build for the image with fakestore + NESTED_SIGN_SNAPS_FAKESTORE: true + + # use the testrootorg auto-import assertion + # TODO: commit the Go code used to create this assertion from the json file + NESTED_CUSTOM_AUTO_IMPORT_ASSERTION: $TESTSLIB/assertions/developer1-auto-import.assert + + NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/developer1-20-signed.model + NESTED_IMAGE_ID: cloud-init-signed-testkeys + + # for the fake store + NESTED_FAKESTORE_BLOB_DIR: $(pwd)/fake-store-blobdir + NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL: http://localhost:11028 + + # unset this otherwise ubuntu-image complains about overriding the channel for + # a model with grade higher than dangerous when building the image + NESTED_CORE_CHANNEL: "" + +prepare: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + + #shellcheck source=tests/lib/nested.sh + . "$TESTSLIB/nested.sh" + + #shellcheck source=tests/lib/store.sh + . "$TESTSLIB"/store.sh + + # setup the fakestore, but don't use it for our snapd here on the host VM, so + # tear down the staging_store immediately afterwards so that only the SAS is + # running and our snapd is not pointed at it, ubuntu-image is the only thing + # that actually needs to use the fakestore, and we will manually point it at + # the fakestore below using NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL + setup_fake_store "$NESTED_FAKESTORE_BLOB_DIR" + teardown_staging_store + + echo "Expose the needed assertions through the fakestore" + cp "$TESTSLIB"/assertions/developer1.account "$NESTED_FAKESTORE_BLOB_DIR/asserts" + cp "$TESTSLIB"/assertions/developer1.account-key "$NESTED_FAKESTORE_BLOB_DIR/asserts" + + # modify and repack gadget snap to add a defaults section and use our own + # prepare-device hook to use the fakedevicesvc + + # Get the snakeoil key and cert for signing gadget assets (shim) + KEY_NAME=$(tests.nested download snakeoil-key) + SNAKEOIL_KEY="$PWD/$KEY_NAME.key" + SNAKEOIL_CERT="$PWD/$KEY_NAME.pem" + + snap download --basename=pc --channel="20/edge" pc + unsquashfs -d pc-gadget pc.snap + + # delay all refreshes for a week from now, as otherwise refreshes for our + # snaps (which are asserted by the testrootorg authority-id) may happen, which + # will break things because the signing keys won't match, etc. and + # specifically snap-bootstrap in the kernel snap from the store won't trust + # the seed keys to unlock the encrypted data partition in the initramfs + sed defaults.yaml -e "s/@HOLD-TIME@/$(date --date='next week' +%Y-%m-%dT%H:%M:%S%:z)/" >> \ + pc-gadget/meta/gadget.yaml + + # copy the prepare-device hook to use our fakedevicesvc + mkdir -p pc-gadget/meta/hooks/ + cp prepare-device pc-gadget/meta/hooks/ + + tests.nested secboot-sign gadget pc-gadget "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" + snap pack pc-gadget/ "$(tests.nested get extra-snaps-path)" + rm -rf pc-gadget/ + + rm -f "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" + + systemd-run --collect --unit fakedevicesvc fakedevicesvc localhost:11029 + + # first boot - legit NoCloud usage + tests.nested build-seed "$TESTSLIB/cloud-init-seeds/normal-user" seed.iso cidata user-data meta-data + + # second boot - attacker drive + tests.nested build-seed "$TESTSLIB/cloud-init-seeds/attacker-user" seed2.iso cidata user-data meta-data + + tests.nested build-image core + # first boot will use seed1 to create the normal-user in addition to the + # system-user assertion + tests.nested create-vm core --param-cdrom "-cdrom $(pwd)/seed.iso" + +restore: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + + # stop fake device svc + systemctl stop fakedevicesvc + + #shellcheck source=tests/lib/store.sh + . "$TESTSLIB"/store.sh + teardown_fake_store "$NESTED_FAKESTORE_BLOB_DIR" + +debug: | + systemctl status fakedevicesvc || true + +execute: | + if [ "$TRUST_TEST_KEYS" = "false" ]; then + echo "This test needs test keys to be trusted" + exit + fi + + # wait for the initialize device task to be done + retry -n 200 --wait 1 sh -c "tests.nested exec snap changes | MATCH 'Done.*Initialize device'" + + echo "The initial cloud-init user was created" + tests.nested exec "cat /var/lib/extrausers/passwd" | MATCH normal-user + + echo "And we can run things as the normal user" + tests.nested exec --user normal-user --pwd ubuntu "sudo true" + + echo "And we got a serial assertion from the fakestore" + tests.nested exec "sudo snap model --verbose --serial" | MATCH "serial:\s+7777" + + echo "Waiting for snapd to react to cloud-init" + retry --wait 1 -n 60 sh -c 'tests.nested exec sudo journalctl --no-pager -u snapd | MATCH "cloud-init reported"' + + echo "Ensuring that cloud-init got disabled after running" + tests.nested exec "cloud-init status" | MATCH "status: disabled" + tests.nested exec "test -f /etc/cloud/cloud-init.disabled" + tests.nested exec "! test -f /etc/cloud/cloud.cfg.d/zzzz_snapd.cfg" + + # gracefully shutdown so that we don't have file corruption + echo "Gracefully shutting down the nested VM to prepare a simulated attack" + boot_id="$(tests.nested boot-id)" + tests.nested vm stop + + # replace the seed.iso with the new attacker iso + mv seed2.iso seed.iso + + echo "Restarting nested VM with attacker cloud-init CD-ROM drive" + tests.nested vm start + tests.nested wait-for reboot "${boot_id}" + + echo "The cloud-init attacker user was not created" + tests.nested exec "cat /var/lib/extrausers/passwd" | NOMATCH attacker-user + + echo "cloud-init is still disabled" + tests.nested exec "cloud-init status" | MATCH "status: disabled" + tests.nested exec "test -f /etc/cloud/cloud-init.disabled" + tests.nested exec "! test -f /etc/cloud/cloud.cfg.d/zzzz_snapd.cfg" diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml b/tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml deleted file mode 100644 index 30292153b8..0000000000 --- a/tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml +++ /dev/null @@ -1,6 +0,0 @@ -defaults: - system: - refresh: - hold: "HOLD-TIME" - journal: - persistent: true diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device b/tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device deleted file mode 100755 index 357c0850fa..0000000000 --- a/tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -# 10.0.2.2 is the host from a nested VM -snapctl set device-service.url=http://10.0.2.2:11029 diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml b/tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml deleted file mode 100644 index 69b1cca28e..0000000000 --- a/tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml +++ /dev/null @@ -1,128 +0,0 @@ -summary: Test that snapd with testkeys on UC20 can boot a model with grade signed. - -systems: [ubuntu-20.04-64] - -environment: - # use tpm + secure boot to get full disk encryption, this is explicitly needed - # for grade: secured - NESTED_ENABLE_TPM: true - NESTED_ENABLE_SECURE_BOOT: true - - # use snapd from the spread run so that we have testkeys trusted in the snapd - # run - NESTED_BUILD_SNAPD_FROM_CURRENT: true - - # don't use cloud-init, that will be a separate test, we only use sys-user - # assertions to create the user for this test - NESTED_USE_CLOUD_INIT: false - - # sign all the snaps we build for the image with fakestore - NESTED_SIGN_SNAPS_FAKESTORE: true - - # use the testrootorg auto-import assertion - # TODO: commit the Go code used to create this assertion from the json file - NESTED_CUSTOM_AUTO_IMPORT_ASSERTION: $TESTSLIB/assertions/developer1-auto-import.assert - - # two variants, for signed and secured grades - MODEL_GRADE/secured: secured - MODEL_GRADE/signed: signed - - NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/developer1-20-${MODEL_GRADE}.model - NESTED_IMAGE_ID: testkeys-${MODEL_GRADE} - - # for the fake store - NESTED_FAKESTORE_BLOB_DIR: $(pwd)/fake-store-blobdir - NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL: http://localhost:11028 - - # unset this otherwise ubuntu-image complains about overriding the channel for - # a model with grade higher than dangerous when building the image - NESTED_CORE_CHANNEL: "" - -prepare: | - if [ "$TRUST_TEST_KEYS" = "false" ]; then - echo "This test needs test keys to be trusted" - exit - fi - - #shellcheck source=tests/lib/nested.sh - . "$TESTSLIB/nested.sh" - - #shellcheck source=tests/lib/store.sh - . "$TESTSLIB"/store.sh - - # setup the fakestore, but don't use it for our snapd here on the host VM, so - # tear down the staging_store immediately afterwards so that only the SAS is - # running and our snapd is not pointed at it, ubuntu-image is the only thing - # that actually needs to use the fakestore, and we will manually point it at - # the fakestore below using NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL - setup_fake_store "$NESTED_FAKESTORE_BLOB_DIR" - teardown_staging_store - - echo Expose the needed assertions through the fakestore - cp "$TESTSLIB"/assertions/developer1.account "$NESTED_FAKESTORE_BLOB_DIR/asserts" - cp "$TESTSLIB"/assertions/developer1.account-key "$NESTED_FAKESTORE_BLOB_DIR/asserts" - - # modify and repack gadget snap to add a defaults section and use our own - # prepare-device hook to use the fakedevicesvc - - # Get the snakeoil key and cert for signing gadget assets (shim) - KEY_NAME=$(tests.nested download snakeoil-key) - SNAKEOIL_KEY="$PWD/$KEY_NAME.key" - SNAKEOIL_CERT="$PWD/$KEY_NAME.pem" - - snap download --basename=pc --channel="20/edge" pc - unsquashfs -d pc-gadget pc.snap - - # delay all refreshes for a week from now, as otherwise refreshes for our - # snaps (which are asserted by the testrootorg authority-id) may happen, which - # will break things because the signing keys won't match, etc. and - # specifically snap-bootstrap in the kernel snap from the store won't trust - # the seed keys to unlock the encrypted data partition in the initramfs - sed defaults.yaml -e "s/HOLD-TIME/$(date --date="next week" +%Y-%m-%dT%H:%M:%S%:z)/" >> \ - pc-gadget/meta/gadget.yaml - - # copy the prepare-device hook to use our fakedevicesvc - mkdir -p pc-gadget/meta/hooks/ - cp prepare-device pc-gadget/meta/hooks/ - - tests.nested secboot-sign gadget pc-gadget "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" - snap pack pc-gadget/ "$(tests.nested get extra-snaps-path)" - rm -rf pc-gadget/ - - rm -f "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" - - # start fake device svc - systemd-run --collect --unit fakedevicesvc fakedevicesvc localhost:11029 - - tests.nested build-image core - tests.nested create-vm core - -restore: | - if [ "$TRUST_TEST_KEYS" = "false" ]; then - echo "This test needs test keys to be trusted" - exit - fi - - # stop fake device svc - systemctl stop fakedevicesvc - - #shellcheck source=tests/lib/store.sh - . "$TESTSLIB"/store.sh - teardown_fake_store "$NESTED_FAKESTORE_BLOB_DIR" - -debug: | - systemctl status fakedevicesvc || true - -execute: | - if [ "$TRUST_TEST_KEYS" = "false" ]; then - echo "This test needs test keys to be trusted" - exit - fi - - # wait for the initialize device task to be done - retry -n 200 --wait 1 sh -c "tests.nested exec snap changes | MATCH 'Done.*Initialize device'" - - echo "Check we have the right model from snap model" - tests.nested exec "sudo snap model --verbose" | MATCH "model:\s+testkeys-snapd-${MODEL_GRADE}-core-20-amd64" - tests.nested exec "sudo snap model --verbose" | MATCH "grade:\s+${MODEL_GRADE}" - tests.nested exec "sudo snap model --verbose --serial" | MATCH "serial:\s+7777" diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml b/tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml deleted file mode 100644 index 6417309d65..0000000000 --- a/tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml +++ /dev/null @@ -1,6 +0,0 @@ -defaults: - system: - refresh: - hold: "@HOLD-TIME@" - journal: - persistent: true diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device b/tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device deleted file mode 100755 index 357c0850fa..0000000000 --- a/tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -# 10.0.2.2 is the host from a nested VM -snapctl set device-service.url=http://10.0.2.2:11029 diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml b/tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml deleted file mode 100644 index a930eb5b08..0000000000 --- a/tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml +++ /dev/null @@ -1,163 +0,0 @@ -summary: Test that UC20 with testkeys can boot a grade signed model with cloud-init. - -systems: [ubuntu-20.04-64] - -environment: - # use tpm + secure boot to get full disk encryption, this is explicitly needed - # for grade: secured - NESTED_ENABLE_TPM: true - NESTED_ENABLE_SECURE_BOOT: true - - # use snapd from the spread run so that we have testkeys trusted in the snapd - # run - NESTED_BUILD_SNAPD_FROM_CURRENT: true - - # don't use cloud-init to create the user, we manually use cloud-init via - # --param-cdrom in the test setup - NESTED_USE_CLOUD_INIT: false - - # sign all the snaps we build for the image with fakestore - NESTED_SIGN_SNAPS_FAKESTORE: true - - # use the testrootorg auto-import assertion - # TODO: commit the Go code used to create this assertion from the json file - NESTED_CUSTOM_AUTO_IMPORT_ASSERTION: $TESTSLIB/assertions/developer1-auto-import.assert - - NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/developer1-20-signed.model - NESTED_IMAGE_ID: cloud-init-signed-testkeys - - # for the fake store - NESTED_FAKESTORE_BLOB_DIR: $(pwd)/fake-store-blobdir - NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL: http://localhost:11028 - - # unset this otherwise ubuntu-image complains about overriding the channel for - # a model with grade higher than dangerous when building the image - NESTED_CORE_CHANNEL: "" - -prepare: | - if [ "$TRUST_TEST_KEYS" = "false" ]; then - echo "This test needs test keys to be trusted" - exit - fi - - #shellcheck source=tests/lib/nested.sh - . "$TESTSLIB/nested.sh" - - #shellcheck source=tests/lib/store.sh - . "$TESTSLIB"/store.sh - - # setup the fakestore, but don't use it for our snapd here on the host VM, so - # tear down the staging_store immediately afterwards so that only the SAS is - # running and our snapd is not pointed at it, ubuntu-image is the only thing - # that actually needs to use the fakestore, and we will manually point it at - # the fakestore below using NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL - setup_fake_store "$NESTED_FAKESTORE_BLOB_DIR" - teardown_staging_store - - echo "Expose the needed assertions through the fakestore" - cp "$TESTSLIB"/assertions/developer1.account "$NESTED_FAKESTORE_BLOB_DIR/asserts" - cp "$TESTSLIB"/assertions/developer1.account-key "$NESTED_FAKESTORE_BLOB_DIR/asserts" - - # modify and repack gadget snap to add a defaults section and use our own - # prepare-device hook to use the fakedevicesvc - - # Get the snakeoil key and cert for signing gadget assets (shim) - KEY_NAME=$(tests.nested download snakeoil-key) - SNAKEOIL_KEY="$PWD/$KEY_NAME.key" - SNAKEOIL_CERT="$PWD/$KEY_NAME.pem" - - snap download --basename=pc --channel="20/edge" pc - unsquashfs -d pc-gadget pc.snap - - # delay all refreshes for a week from now, as otherwise refreshes for our - # snaps (which are asserted by the testrootorg authority-id) may happen, which - # will break things because the signing keys won't match, etc. and - # specifically snap-bootstrap in the kernel snap from the store won't trust - # the seed keys to unlock the encrypted data partition in the initramfs - sed defaults.yaml -e "s/@HOLD-TIME@/$(date --date='next week' +%Y-%m-%dT%H:%M:%S%:z)/" >> \ - pc-gadget/meta/gadget.yaml - - # copy the prepare-device hook to use our fakedevicesvc - mkdir -p pc-gadget/meta/hooks/ - cp prepare-device pc-gadget/meta/hooks/ - - tests.nested secboot-sign gadget pc-gadget "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" - snap pack pc-gadget/ "$(tests.nested get extra-snaps-path)" - rm -rf pc-gadget/ - - rm -f "$SNAKEOIL_KEY" "$SNAKEOIL_CERT" - - systemd-run --collect --unit fakedevicesvc fakedevicesvc localhost:11029 - - # first boot - legit NoCloud usage - tests.nested build-seed "$TESTSLIB/cloud-init-seeds/normal-user" seed.iso cidata user-data meta-data - - # second boot - attacker drive - tests.nested build-seed "$TESTSLIB/cloud-init-seeds/attacker-user" seed2.iso cidata user-data meta-data - - tests.nested build-image core - # first boot will use seed1 to create the normal-user in addition to the - # system-user assertion - tests.nested create-vm core --param-cdrom "-cdrom $(pwd)/seed.iso" - -restore: | - if [ "$TRUST_TEST_KEYS" = "false" ]; then - echo "This test needs test keys to be trusted" - exit - fi - - # stop fake device svc - systemctl stop fakedevicesvc - - #shellcheck source=tests/lib/store.sh - . "$TESTSLIB"/store.sh - teardown_fake_store "$NESTED_FAKESTORE_BLOB_DIR" - -debug: | - systemctl status fakedevicesvc || true - -execute: | - if [ "$TRUST_TEST_KEYS" = "false" ]; then - echo "This test needs test keys to be trusted" - exit - fi - - # wait for the initialize device task to be done - retry -n 200 --wait 1 sh -c "tests.nested exec snap changes | MATCH 'Done.*Initialize device'" - - echo "The initial cloud-init user was created" - tests.nested exec "cat /var/lib/extrausers/passwd" | MATCH normal-user - - echo "And we can run things as the normal user" - tests.nested exec --user normal-user --pwd ubuntu "sudo true" - - echo "And we got a serial assertion from the fakestore" - tests.nested exec "sudo snap model --verbose --serial" | MATCH "serial:\s+7777" - - echo "Waiting for snapd to react to cloud-init" - retry --wait 1 -n 60 sh -c 'tests.nested exec sudo journalctl --no-pager -u snapd | MATCH "cloud-init reported"' - - echo "Ensuring that cloud-init got disabled after running" - tests.nested exec "cloud-init status" | MATCH "status: disabled" - tests.nested exec "test -f /etc/cloud/cloud-init.disabled" - tests.nested exec "! test -f /etc/cloud/cloud.cfg.d/zzzz_snapd.cfg" - - # gracefully shutdown so that we don't have file corruption - echo "Gracefully shutting down the nested VM to prepare a simulated attack" - boot_id="$(tests.nested boot-id)" - tests.nested vm stop - - # replace the seed.iso with the new attacker iso - mv seed2.iso seed.iso - - echo "Restarting nested VM with attacker cloud-init CD-ROM drive" - tests.nested vm start - tests.nested wait-for reboot "${boot_id}" - - echo "The cloud-init attacker user was not created" - tests.nested exec "cat /var/lib/extrausers/passwd" | NOMATCH attacker-user - - echo "cloud-init is still disabled" - tests.nested exec "cloud-init status" | MATCH "status: disabled" - tests.nested exec "test -f /etc/cloud/cloud-init.disabled" - tests.nested exec "! test -f /etc/cloud/cloud.cfg.d/zzzz_snapd.cfg" -- cgit v1.2.3 From c169048a4199a6ae30b400e247c29368ceab754c Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 14 Oct 2021 13:34:17 -0500 Subject: tests/nested/manual/refresh-revert-fundamentals: re-enable encryption This test had encryption temporarily disabled as part of snapcore/snapd#9632, so it's now high time we re-enable it almost a year later /o\ Signed-off-by: Ian Johnson --- tests/nested/manual/refresh-revert-fundamentals/task.yaml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/nested/manual/refresh-revert-fundamentals/task.yaml b/tests/nested/manual/refresh-revert-fundamentals/task.yaml index b2785c3bbf..5f049c5c45 100644 --- a/tests/nested/manual/refresh-revert-fundamentals/task.yaml +++ b/tests/nested/manual/refresh-revert-fundamentals/task.yaml @@ -11,16 +11,13 @@ environment: NESTED_CORE_REFRESH_CHANNEL: edge NESTED_BUILD_SNAPD_FROM_CURRENT: false NESTED_USE_CLOUD_INIT: true - # TODO:UC20: temporarily disable secure boot and encryption support. The - # location of encryption keys has changed, thus the nested VM will not boot - # until the kernel snap is rebuilt with snapd 2.48. - NESTED_ENABLE_SECURE_BOOT: false - NESTED_ENABLE_TPM: false + NESTED_ENABLE_SECURE_BOOT: true + NESTED_ENABLE_TPM: true SNAP/kernel: pc-kernel TRACK/kernel: 20 - SNAP/gadget: pc + SNAP/gadget: pc TRACK/gadget: 20 SNAP/snapd: snapd @@ -54,7 +51,7 @@ execute: | TO_REV="$(tests.nested snap-rev "$SNAP" $TRACK/$NESTED_CORE_REFRESH_CHANNEL)" tests.nested exec "snap list $SNAP" | MATCH "^${SNAP}.*${FROM_REV}.*${TRACK}/${NESTED_CORE_CHANNEL}.*" - + echo "Refresh the snap $SNAP" INITIAL_BOOT_ID=$(tests.nested boot-id) REFRESH_ID=$(tests.nested exec "sudo snap refresh --no-wait --channel $NESTED_CORE_REFRESH_CHANNEL $SNAP") -- cgit v1.2.3 From 51e649b394e09c38d0c9f4391b5cef9eb386d49c Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Thu, 14 Oct 2021 15:49:45 -0300 Subject: Remove extra-snaps-assertions test This test is not adding any value, and the description is not reflecting what the test is actually doing --- tests/nested/core/extra-snaps-assertions/task.yaml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 tests/nested/core/extra-snaps-assertions/task.yaml diff --git a/tests/nested/core/extra-snaps-assertions/task.yaml b/tests/nested/core/extra-snaps-assertions/task.yaml deleted file mode 100644 index bb4a3e2304..0000000000 --- a/tests/nested/core/extra-snaps-assertions/task.yaml +++ /dev/null @@ -1,18 +0,0 @@ -summary: create ubuntu-core image and execute the suite in a nested qemu instance - -systems: [ubuntu-18.04-64] - -execute: | - echo "Wait for first boot to be done" - tests.nested exec "retry --wait 1 -n 120 sh -c 'snap changes | MATCH \"Done.*Initialize system state\"'" - - echo "We have a model assertion" - tests.nested exec "snap known model" | MATCH "series: 16" - - EXPRESSION="^core18 .* +latest/$NESTED_CORE_CHANNEL +canonical\\* +base" - if [ "$NESTED_BUILD_SNAPD_FROM_CURRENT" = "true" ]; then - EXPRESSION="^core18 .* +x1 .* base" - fi - - echo "Make sure core has an actual revision" - tests.nested exec "snap list --unicode=never" | MATCH "$EXPRESSION" -- cgit v1.2.3 From ad2ea9cca585b35548dc924ad1cd0d92500c30b0 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 14 Oct 2021 16:11:42 -0500 Subject: interfaces/builtin/hardware-observer: add /proc/bus/input/devices too This is needed for a customer request, see ticket 00320804 for full details. Signed-off-by: Ian Johnson --- interfaces/builtin/hardware_observe.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interfaces/builtin/hardware_observe.go b/interfaces/builtin/hardware_observe.go index 3bab1331b5..2ee7f8f115 100644 --- a/interfaces/builtin/hardware_observe.go +++ b/interfaces/builtin/hardware_observe.go @@ -45,6 +45,9 @@ capability sys_admin, /etc/modprobe.d/{,*} r, /{,usr/}lib/modprobe.d/{,*} r, +# for reading the available input devices on the system +/proc/bus/input/devices r, + # files in /sys pertaining to hardware (eg, 'lspci -A linux-sysfs') /sys/{block,bus,class,devices,firmware}/{,**} r, -- cgit v1.2.3 From e83eb8208072dabd4d693cb6d7dbd3d020d1ad30 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Thu, 14 Oct 2021 15:15:44 -0500 Subject: tests/nested/manual: add regression test with old/existing initrd and new snapd This test ensures that new snapd when doing resealing on a system with an "old" initrd (i.e. one that is currently published and not from the PR), we are able to come back after the reboot to try the new kernel after resealing. This test has two variants, one which starts with the new snapd and one which starts with the old/existing snapd on the stable channel. Signed-off-by: Ian Johnson --- .../task.yaml | 86 ++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml diff --git a/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml new file mode 100644 index 0000000000..99f32320f4 --- /dev/null +++ b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml @@ -0,0 +1,86 @@ +summary: verify that new snapd's do not break old snap-bootstrap/kernel initrds + +systems: [ubuntu-20.04-64] + +# we have two variants here: +# +# 1. we start with stable kernel + stable snapd -> refresh to new snapd +# 2. we start with stable kernel + new snapd +# +# and then in both cases we then trigger a reseal operation and reboot to make +# sure that the old snap-bootstrap/initrd in the stable kernel can still unlock +# the encrypted partitions + +environment: + NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/ubuntu-core-20-amd64.model + + # don't bundle the snapd snap via nested.sh's machinery, instead we will + # side-load our snapd snap built from this branch into the image via + # extra-snaps since we still want to use MS keys and such for this image, and + # defining this to be true brings with it i.e. snakeoil keys in the OVMF + # firmware for example + NESTED_BUILD_SNAPD_FROM_CURRENT: false + + # we want snaps from the stable channel by default with the exception of the + # snapd snap which we will conditionally repack as per NESTED_BUILD_SNAPD_FROM_CURRENT + NESTED_CORE_CHANNEL: stable + + START_SNAPD_VERSION/startwithnew: new + START_SNAPD_VERSION/startwithstable: old + + NESTED_IMAGE_ID: uc20-breakages-testing-$START_SNAPD_VERSION + + # all variants need encryption turned on + NESTED_ENABLE_TPM: true + NESTED_ENABLE_SECURE_BOOT: true + + INITIAL_KERNEL_REV_URL: https://storage.googleapis.com/snapd-spread-tests/snaps/pc-kernel_838.snap + +prepare: | + # always build the snapd snap from this branch - on the new variant it gets + # put into the image, on the old variant it will be refreshed to + snap download --channel="latest/edge" snapd + "$TESTSTOOLS"/snaps-state repack_snapd_deb_into_snap snapd + mv snapd-from-deb.snap snapd-from-branch.snap + + # only install it on the image in the "new" variant + if [ "$START_SNAPD_VERSION" = "new" ]; then + mv snapd-from-branch.snap "$(tests.nested get extra-snaps-path)" + fi + + # use a specific version of the kernel snap and thus initramfs that we know + # doesn't support v2 secboot keys + wget "$INITIAL_KERNEL_REV_URL" + + # unpack it and repack it so it doesn't match any store assertions and thus + # won't be automatically refreshed behind our backs when we boot the VM + unsquashfs -d pc-kernel-snap pc-kernel_838.snap + snap pack pc-kernel-snap --filename=pc-kernel.snap + mv pc-kernel.snap "$(tests.nested get extra-snaps-path)" + + # download the new kernel to try and refresh to, triggering a reseal + snap download pc-kernel --channel=20/candidate --basename=new-kernel + + # build the image and start the VM up + tests.nested build-image core + tests.nested create-vm core + +execute: | + # on the old variant, copy and install the new snapd to it + if [ "$START_SNAPD_VERSION" = "old" ]; then + tests.nested copy snapd-from-branch.snap + tests.nested exec "sudo snap install --dangerous snapd-from-branch.snap" + fi + + # try a refresh to a new kernel revision which will trigger a reseal and then + # a reboot + tests.nested copy new-kernel.snap + + boot_id="$( tests.nested boot-id )" + REMOTE_CHG_ID=$(tests.nested exec "sudo snap install --dangerous new-kernel.snap --no-wait") + tests.nested wait-for reboot "${boot_id}" + tests.nested exec sudo snap watch "${REMOTE_CHG_ID}" + + tests.nested exec "snap changes" | tail -n +2 | awk '{print $2}' | NOMATCH Error + + # TODO: also check transitioning to the recovery seed system too? -- cgit v1.2.3 From fcdd8e259eca82162182ac3d836b7c91fc3d4750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alfonso=20S=C3=A1nchez-Beato?= Date: Fri, 15 Oct 2021 10:31:17 +0200 Subject: interface/modem-manager: add accept for MBIM/QMI proxy clients We need accept too for clients to connect. --- interfaces/builtin/modem_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interfaces/builtin/modem_manager.go b/interfaces/builtin/modem_manager.go index ead2cd1c0c..9fe81508bd 100644 --- a/interfaces/builtin/modem_manager.go +++ b/interfaces/builtin/modem_manager.go @@ -67,7 +67,7 @@ network netlink raw, capability sys_admin, # For {mbim,qmi}-proxy -unix (bind, listen) type=stream addr="@{mbim,qmi}-proxy", +unix (bind, listen, accept) type=stream addr="@{mbim,qmi}-proxy", /sys/devices/**/usb**/{descriptors,manufacturer,product,bInterfaceClass,bInterfaceSubClass,bInterfaceProtocol,bInterfaceNumber} r, # See https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-net-qmi /sys/devices/**/net/*/qmi/* rw, -- cgit v1.2.3 From e8bea37c43c455a3bd5aaff9a0141d8e2de47dc0 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 15 Oct 2021 10:32:35 +0200 Subject: tests/nested/core/core20-create-recovery: fix passing of data to curl After recent switch to test-snapd-curl, there was no data being passed in the request. Signed-off-by: Maciej Borzecki --- tests/nested/core/core20-create-recovery/task.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/nested/core/core20-create-recovery/task.yaml b/tests/nested/core/core20-create-recovery/task.yaml index 2778a67662..af0394a2ae 100644 --- a/tests/nested/core/core20-create-recovery/task.yaml +++ b/tests/nested/core/core20-create-recovery/task.yaml @@ -8,7 +8,8 @@ prepare: | execute: | echo "Create a recovery system with a typical recovery system label" boot_id="$( tests.nested boot-id )" - echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234"}}' | tests.nested exec sudo test-snapd-curl.curl -X POST --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out + echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234"}}' | \ + tests.nested exec sudo test-snapd-curl.curl -X POST -d @- --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out REMOTE_CHG_ID=$(jq -r .change < change.out) tests.nested wait-for reboot "${boot_id}" tests.nested exec sudo snap watch "${REMOTE_CHG_ID}" @@ -23,7 +24,8 @@ execute: | echo "Create a recovery system with an alternative recovery system label" boot_id="$( tests.nested boot-id )" - echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234-1"}}' | tests.nested exec sudo test-snapd-curl.curl -X POST --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out + echo '{"action":"create-recovery-system","params":{"recovery-system-label":"1234-1"}}' | \ + tests.nested exec sudo test-snapd-curl.curl -X POST -d @- --unix-socket /run/snapd.socket http://localhost/v2/debug > change.out REMOTE_CHG_ID=$(jq -r .change < change.out) tests.nested wait-for reboot "${boot_id}" tests.nested exec sudo snap watch "${REMOTE_CHG_ID}" -- cgit v1.2.3 From d0fd9640979a2590a612f9f77c249bb757bf3e68 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 15 Oct 2021 11:22:11 -0500 Subject: tests/nested/manual/core20-new-snapd-does-not-break-old-initrd: quiet wget Signed-off-by: Ian Johnson --- .../nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml index 99f32320f4..b632660b45 100644 --- a/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml +++ b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml @@ -50,7 +50,7 @@ prepare: | # use a specific version of the kernel snap and thus initramfs that we know # doesn't support v2 secboot keys - wget "$INITIAL_KERNEL_REV_URL" + wget --quiet "$INITIAL_KERNEL_REV_URL" # unpack it and repack it so it doesn't match any store assertions and thus # won't be automatically refreshed behind our backs when we boot the VM -- cgit v1.2.3 From 4ebb4e24ad46a1067ffc85af74d2943662650672 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 15 Oct 2021 11:25:40 -0500 Subject: tests/lib/nested.sh: retry the snap wait command up to 3 times MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sometimes we see in the test this sort of output: + tests.nested create-vm core '/tmp/work-dir/images/ubuntu-core-20-custom-uc20-breakages-testing-old.img' -> '/tmp/work-dir/images/ubuntu-core-current.img' retry: command test -S /var/snap/swtpm-mvo/current/swtpm-sock failed with code 1 retry: next attempt in 1.0 second(s) (attempt 1 of 10) Created symlink /etc/systemd/system/multi-user.target.wants/nested-vm.service → /etc/systemd/system/nested-vm.service. Connection timed out during banner exchange ... Warning: Permanently added '[localhost]:8022' (ECDSA) to the list of known hosts. /usr/bin/snap Warning: Permanently added '[localhost]:8022' (ECDSA) to the list of known hosts. sudo: unable to execute /usr/bin/snap: No such file or directory ----- where clearly we waited until the system was available, then the snap command was found, but immediately trying to execute the snap command via sudo failed. So to avoid this sort of erroneous failure, retry the snap wait command up to 3 times with 1 second sleep between tries, in most cases this command should not fail in the case that it does it should either recover quickly or never at all so we don't want to waste a lot of time retrying this check/command. Signed-off-by: Ian Johnson --- tests/lib/nested.sh | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh index 2d4378e5c7..4dc55b6bb4 100644 --- a/tests/lib/nested.sh +++ b/tests/lib/nested.sh @@ -1044,7 +1044,18 @@ nested_start_core_vm_unit() { # Wait for the snap command to be available nested_wait_for_snap_command # Wait for snap seeding to be done - nested_exec "sudo snap wait system seed.loaded" + # retry this wait command up to 3 times since we sometimes see races + # where the snap command appears, then immediately disappears and then + # re-appears immediately after and so the next command fails + attempts=0 + until nested_exec "sudo snap wait system seed.loaded"; do + attempts=$(( attempts + 1)) + if [ "$attempts" = 3 ]; then + echo "failed to wait for snap wait command to return successfully" + return 1 + fi + sleep 1 + done # Copy tools to be used on tests nested_prepare_tools # Wait for cloud init to be done if the system is using cloud-init -- cgit v1.2.3 From 51f4e38778c55e2c8f363131455e73093d96d21a Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 15 Oct 2021 13:32:42 -0500 Subject: tests/core20-new-snapd-does-not-break-old-initrd: avoid auto-refreshes in test Avoid the problem of snapd being auto-refreshed during first boot. We sometimes see this issue because when we build the image, we end up with a snapd snap from the stable channel, but then there is a new one available by the time the image finishes booting, so snapd would try to auto-refresh itself simultaneously while we were trying to install the new snapd snap. So download and unpack the snap and seed it into the image as local, unasserted snap to prevent snapd from refreshing itself. Signed-off-by: Ian Johnson --- .../task.yaml | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml index b632660b45..9d32fba916 100644 --- a/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml +++ b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml @@ -26,7 +26,7 @@ environment: NESTED_CORE_CHANNEL: stable START_SNAPD_VERSION/startwithnew: new - START_SNAPD_VERSION/startwithstable: old + START_SNAPD_VERSION/startwithold: old NESTED_IMAGE_ID: uc20-breakages-testing-$START_SNAPD_VERSION @@ -43,9 +43,19 @@ prepare: | "$TESTSTOOLS"/snaps-state repack_snapd_deb_into_snap snapd mv snapd-from-deb.snap snapd-from-branch.snap - # only install it on the image in the "new" variant + # on both variants we use a local, non-asserted version of the snapd snap + # for the startwithnew variant, we use the snapd from this branch, for the + # startwithold variant, we use the snapd from stable but unpack it to + # prevent auto-refreshes from happening which may affect the test setup if [ "$START_SNAPD_VERSION" = "new" ]; then - mv snapd-from-branch.snap "$(tests.nested get extra-snaps-path)" + mv snapd-from-branch.snap "$(tests.nested get extra-snaps-path)" + else + # TODO: download a specific version of snapd from a GCE bucket instead + snap download snapd --stable --basename=snapd-stable-store + unsquashfs -d snapd snapd-stable-store.snap + touch ./snapd/in-case-mksquashfs-becomes-deterministic-someday + sudo snap pack snapd --filename=snapd-stable.snap + mv snapd-stable.snap "$(tests.nested get extra-snaps-path)" fi # use a specific version of the kernel snap and thus initramfs that we know @@ -55,6 +65,7 @@ prepare: | # unpack it and repack it so it doesn't match any store assertions and thus # won't be automatically refreshed behind our backs when we boot the VM unsquashfs -d pc-kernel-snap pc-kernel_838.snap + touch ./pc-kernel-snap/in-case-mksquashfs-becomes-deterministic-someday snap pack pc-kernel-snap --filename=pc-kernel.snap mv pc-kernel.snap "$(tests.nested get extra-snaps-path)" -- cgit v1.2.3 From 7ae9947ddd51142e579e38a8a9ccdfb951a14f76 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Fri, 15 Oct 2021 15:08:40 +0300 Subject: tests/snapd-sigterm: be more robust against service restart Pass the -q option to nc to let it hang for a while before closing the connection. If we don't do this, we are not properly testing that snapd proeprly handles hanging connections. --- tests/main/snapd-sigterm/task.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/main/snapd-sigterm/task.yaml b/tests/main/snapd-sigterm/task.yaml index 03e8debfc0..2f5ef6e0cc 100644 --- a/tests/main/snapd-sigterm/task.yaml +++ b/tests/main/snapd-sigterm/task.yaml @@ -11,7 +11,7 @@ restore: | execute: | echo "Make a request, keep the connection open" - nc -U /run/snapd.socket << EOF & + nc -q 20 -U /run/snapd.socket << EOF & GET /v2/apps HTTP/1.1 Host: localhost @@ -21,7 +21,12 @@ execute: | TEST_TIME0="$(date +'%s')" systemctl stop snapd.service - retry -n 10 sh -c 'systemctl status snapd.service | MATCH "inactive"' + # The systemctl command waits for the operation to complete, but just to be + # extra safe check that it's either inactive or has just been restarted. We + # don't want to stop the socket itself, as that might hide the issue we + # want to test. + retry -n 100 --wait 0.1 sh -c 'systemctl status snapd.service | MATCH "inactive"' + TEST_TIME1="$(date +'%s')" if ((TEST_TIME1 > TEST_TIME0 + 5)); then -- cgit v1.2.3 From 293ff35dec28ea27fdb00001920670f60a80ce00 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 18 Oct 2021 11:15:49 +0200 Subject: o/devicestate: extend test checks Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicestate_remodel_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/overlord/devicestate/devicestate_remodel_test.go b/overlord/devicestate/devicestate_remodel_test.go index 42765e35cb..d7364e5b6d 100644 --- a/overlord/devicestate/devicestate_remodel_test.go +++ b/overlord/devicestate/devicestate_remodel_test.go @@ -2295,6 +2295,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal tCreateRecovery, tFinalizeRecovery, }) + c.Assert(tUpdateAssetsKernel.WaitTasks(), DeepEquals, []*state.Task{ + tLinkKernel, + }) c.Assert(tPrepareBase.WaitTasks(), DeepEquals, []*state.Task{ tPrepareKernel, }) @@ -2309,6 +2312,9 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseGadgetSnapsInstal tPrepareGadget, tLinkBase, }) + c.Assert(tUpdateCmdline.WaitTasks(), DeepEquals, []*state.Task{ + tUpdateAssets, + }) c.Assert(tCreateRecovery.WaitTasks(), DeepEquals, []*state.Task{ // last snap of the download chain (in this case prepare & link // for existing snaps) -- cgit v1.2.3 From 966e6a2c446eaf089fdf6e9824769f3b4a7d9c79 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 18 Oct 2021 11:21:19 +0200 Subject: overlord/snapstate: add comments on task generating remodel helpers Signed-off-by: Maciej Borzecki --- overlord/snapstate/snapstate.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index cb2aab44c4..56f8734cf4 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -2175,7 +2175,8 @@ func autoRefreshPhase2(ctx context.Context, st *state.State, updates []*refreshC return tasksets, nil } -// LinkNewBaseOrKernel will create prepare/link-snap tasks for a remodel +// LinkNewBaseOrKernel creates a new task set with prepare/link-snap, and +// additionally update-gadget-assets for the kernel snap, tasks for a remodel. func LinkNewBaseOrKernel(st *state.State, name string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, name, &snapst) @@ -2243,6 +2244,8 @@ func findSnapSetupTask(tasks []*state.Task) (*state.Task, *SnapSetup, error) { return nil, nil, nil } +// AddLinkNewBaseOrKernel creates the same tasks as LinkNewBaseOrKernel but adds +// them to the provided task set. func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) { allTasks := ts.Tasks() snapSetupTask, snapsup, err := findSnapSetupTask(allTasks) @@ -2269,6 +2272,9 @@ func AddLinkNewBaseOrKernel(st *state.State, ts *state.TaskSet) (*state.TaskSet, return ts, nil } +// LinkNewBaseOrKernel creates a new task set with +// prepare/update-gadget-assets/update-gadget-cmdline tasks for the gadget snap, +// for remodel. func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, name, &snapst) @@ -2316,6 +2322,8 @@ func SwitchToNewGadget(st *state.State, name string) (*state.TaskSet, error) { return ts, nil } +// AddGadgetAssetsTasks creates the same tasks as SwitchToNewGadget but adds +// them to the provided task set. func AddGadgetAssetsTasks(st *state.State, ts *state.TaskSet) (*state.TaskSet, error) { allTasks := ts.Tasks() snapSetupTask, snapsup, err := findSnapSetupTask(allTasks) -- cgit v1.2.3 From 51aeb423d33c72efbf5e94dc0bed0c087dd78bef Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 18 Oct 2021 11:29:22 +0200 Subject: tests/main/cwd: workaround all Fedoras and Arch Signed-off-by: Maciej Borzecki --- tests/main/cwd/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index acbe387788..a682ce0a9f 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -45,7 +45,7 @@ execute: | # via a symlink attack in /tmp) is remapped to a special directory. # FIXME: su doesn't have /snap/bin in PATH. case "$SPREAD_SYSTEM" in - fedora-34-*|opensuse-tumbleweed-*) + fedora-*|opensuse-tumbleweed-*|arch-linux-*) # nothiing, we have to go through tests.session which always starts in # the $HOME directory ;; -- cgit v1.2.3 From 22c38061083efed2bb53dd8e478330a6bccfeab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Mon, 19 Jul 2021 12:37:41 +0200 Subject: Support --ignore-validation with snap install client command. --- cmd/snap/cmd_snap_op.go | 13 ++++++++++--- cmd/snap/cmd_snap_op_test.go | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go index 4f68096db7..f15955596c 100644 --- a/cmd/snap/cmd_snap_op.go +++ b/cmd/snap/cmd_snap_op.go @@ -474,9 +474,10 @@ type cmdInstall struct { Name string `long:"name"` - Cohort string `long:"cohort"` - IgnoreRunning bool `long:"ignore-running" hidden:"yes"` - Positional struct { + Cohort string `long:"cohort"` + IgnoreValidation bool `long:"ignore-validation"` + IgnoreRunning bool `long:"ignore-running" hidden:"yes"` + Positional struct { Snaps []remoteSnapName `positional-arg-name:""` } `positional-args:"yes" required:"yes"` } @@ -597,6 +598,7 @@ func (x *cmdInstall) Execute([]string) error { Dangerous: dangerous, Unaliased: x.Unaliased, CohortKey: x.Cohort, + IgnoreValidation: x.IgnoreValidation, IgnoreRunning: x.IgnoreRunning, } x.setModes(opts) @@ -618,6 +620,9 @@ func (x *cmdInstall) Execute([]string) error { if x.asksForMode() || x.asksForChannel() { return errors.New(i18n.G("a single snap name is needed to specify mode or channel flags")) } + if x.IgnoreValidation { + return errors.New(i18n.G("a single snap name must be specified when ignoring validation")) + } if x.Name != "" { return errors.New(i18n.G("cannot use instance name when installing multiple snaps")) @@ -1107,6 +1112,8 @@ func init() { // TRANSLATORS: This should not start with a lowercase letter. "cohort": i18n.G("Install the snap in the given cohort"), // TRANSLATORS: This should not start with a lowercase letter. + "ignore-validation": i18n.G("Ignore validation by other snaps blocking the refresh"), + // TRANSLATORS: This should not start with a lowercase letter. "ignore-running": i18n.G("Ignore running hooks or applications blocking the installation"), }), nil) addCommand("refresh", shortRefreshHelp, longRefreshHelp, func() flags.Commander { return &cmdRefresh{} }, diff --git a/cmd/snap/cmd_snap_op_test.go b/cmd/snap/cmd_snap_op_test.go index 4aa831664d..e222a2f185 100644 --- a/cmd/snap/cmd_snap_op_test.go +++ b/cmd/snap/cmd_snap_op_test.go @@ -1627,6 +1627,26 @@ func (s *SnapOpSuite) TestInstallFromChannel(c *check.C) { c.Check(s.srv.n, check.Equals, s.srv.total) } +func (s *SnapOpSuite) TestInstallOneIgnoreValidation(c *check.C) { + s.RedirectClientToTestServer(s.srv.handle) + s.srv.checker = func(r *http.Request) { + c.Check(r.Method, check.Equals, "POST") + c.Check(r.URL.Path, check.Equals, "/v2/snaps/one") + c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ + "action": "install", + "ignore-validation": true, + }) + } + _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--ignore-validation", "one"}) + c.Assert(err, check.IsNil) +} + +func (s *SnapOpSuite) TestInstallManyIgnoreValidation(c *check.C) { + s.RedirectClientToTestServer(nil) + _, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--ignore-validation", "one", "two"}) + c.Assert(err, check.ErrorMatches, `a single snap name must be specified when ignoring validation`) +} + func (s *SnapOpSuite) TestEnable(c *check.C) { s.srv.total = 3 s.srv.checker = func(r *http.Request) { -- cgit v1.2.3 From e2b459d75a78313a9744bc6f4a8bd29b4d92a2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Thu, 22 Jul 2021 09:00:12 +0200 Subject: Fix help message. --- cmd/snap/cmd_snap_op.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go index f15955596c..52a90316d9 100644 --- a/cmd/snap/cmd_snap_op.go +++ b/cmd/snap/cmd_snap_op.go @@ -1112,7 +1112,7 @@ func init() { // TRANSLATORS: This should not start with a lowercase letter. "cohort": i18n.G("Install the snap in the given cohort"), // TRANSLATORS: This should not start with a lowercase letter. - "ignore-validation": i18n.G("Ignore validation by other snaps blocking the refresh"), + "ignore-validation": i18n.G("Ignore validation by other snaps blocking the installation"), // TRANSLATORS: This should not start with a lowercase letter. "ignore-running": i18n.G("Ignore running hooks or applications blocking the installation"), }), nil) -- cgit v1.2.3 From c1afefcaf22cb790011024618a34791eb3bbc2c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Mon, 18 Oct 2021 13:17:45 +0200 Subject: Fix format. --- cmd/snap/cmd_snap_op.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go index 52a90316d9..f839a2f492 100644 --- a/cmd/snap/cmd_snap_op.go +++ b/cmd/snap/cmd_snap_op.go @@ -593,13 +593,13 @@ func (x *cmdInstall) Execute([]string) error { dangerous := x.Dangerous || x.ForceDangerous opts := &client.SnapOptions{ - Channel: x.Channel, - Revision: x.Revision, - Dangerous: dangerous, - Unaliased: x.Unaliased, - CohortKey: x.Cohort, + Channel: x.Channel, + Revision: x.Revision, + Dangerous: dangerous, + Unaliased: x.Unaliased, + CohortKey: x.Cohort, IgnoreValidation: x.IgnoreValidation, - IgnoreRunning: x.IgnoreRunning, + IgnoreRunning: x.IgnoreRunning, } x.setModes(opts) -- cgit v1.2.3 From 07d09698025b5effb5bf1d69a13fc188a2eb8a4e Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Mon, 18 Oct 2021 08:44:12 -0300 Subject: Disable i386 until it is possible to build snapd using lxd --- tests/main/snapd-snap/task.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/main/snapd-snap/task.yaml b/tests/main/snapd-snap/task.yaml index 07c9b4e0ab..476fc69ee9 100644 --- a/tests/main/snapd-snap/task.yaml +++ b/tests/main/snapd-snap/task.yaml @@ -25,6 +25,10 @@ systems: # locale, see https://bugs.launchpad.net/snapcraft/+bug/1922140 # a separate error occurs on centos 8, see https://bugs.launchpad.net/snapcraft/+bug/1922981 - -centos-* + # ubuntu-18.04-32: i386 is not supported by lxd + # TODO: enable i386 by using lxd 3.0, currently snapcraft is failing to get the + # lxd image when it is trying to build snapd + - ubuntu-18.04-32 # Start early as it takes a long time. priority: 100 @@ -80,11 +84,7 @@ prepare: | modprobe fuse echo "Install lxd" - if os.query is-pc-i386; then - snap install lxd --channel="3.0/stable" - else - snap install lxd --channel="$LXD_SNAP_CHANNEL" - fi + snap install lxd --channel="$LXD_SNAP_CHANNEL" tests.cleanup defer snap remove --purge lxd echo "Setup the lxd snap" -- cgit v1.2.3 From cadf522cf6fc336de2c1747442b2723f626fa2a4 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Mon, 18 Oct 2021 10:56:10 -0300 Subject: Fix format for systems removal --- tests/main/snapd-snap/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/snapd-snap/task.yaml b/tests/main/snapd-snap/task.yaml index 476fc69ee9..d68458f688 100644 --- a/tests/main/snapd-snap/task.yaml +++ b/tests/main/snapd-snap/task.yaml @@ -28,7 +28,7 @@ systems: # ubuntu-18.04-32: i386 is not supported by lxd # TODO: enable i386 by using lxd 3.0, currently snapcraft is failing to get the # lxd image when it is trying to build snapd - - ubuntu-18.04-32 + - -ubuntu-18.04-32 # Start early as it takes a long time. priority: 100 -- cgit v1.2.3 From bfd32f2a2ce2a829b94d1085fd8d2e4fa136fdf1 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Mon, 18 Oct 2021 10:45:50 -0500 Subject: gadget/gadget.go: fix doc-comment Thanks to Maciej for the suggestion Co-authored-by: Maciej Borzecki --- gadget/gadget.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gadget/gadget.go b/gadget/gadget.go index 65dc3021be..7129af57f2 100644 --- a/gadget/gadget.go +++ b/gadget/gadget.go @@ -272,7 +272,8 @@ type DiskStructureDeviceTraits struct { } // SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume / -// device traits to a file on disk for later loading and verification. +// device traits to a file inside the provided directory on disk for +// later loading and verification. func SaveDiskVolumesDeviceTraits(dir string, mapping map[string]DiskVolumeDeviceTraits) error { b, err := json.Marshal(mapping) if err != nil { -- cgit v1.2.3 From ec6dff9e139406b871d47ed7e0ac6025283fb6d3 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Mon, 18 Oct 2021 11:19:55 -0500 Subject: gadget/gadget.go: fix whitespace Thanks gofmt Signed-off-by: Ian Johnson --- gadget/gadget.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gadget/gadget.go b/gadget/gadget.go index 7129af57f2..a0da7a8830 100644 --- a/gadget/gadget.go +++ b/gadget/gadget.go @@ -272,7 +272,7 @@ type DiskStructureDeviceTraits struct { } // SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume / -// device traits to a file inside the provided directory on disk for +// device traits to a file inside the provided directory on disk for // later loading and verification. func SaveDiskVolumesDeviceTraits(dir string, mapping map[string]DiskVolumeDeviceTraits) error { b, err := json.Marshal(mapping) -- cgit v1.2.3 From 2bc0b26e5035fb3d9eb65a712c33e3daeb497df0 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Mon, 18 Oct 2021 11:32:34 -0500 Subject: osutil/disks: adjust error message to use a single format string Thanks to Maciej for the suggestion which led to this simplification. Signed-off-by: Ian Johnson --- osutil/disks/labels.go | 9 ++++++--- osutil/disks/labels_test.go | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osutil/disks/labels.go b/osutil/disks/labels.go index e1136ce2a8..387b49227e 100644 --- a/osutil/disks/labels.go +++ b/osutil/disks/labels.go @@ -61,6 +61,9 @@ const ( // encoded by udev in BlkIDEncodeLabel for normal comparison, i.e. // "BIOS\x20Boot" becomes "BIOS Boot" func BlkIDDecodeLabel(in string) (string, error) { + + const errFmtStr = "string is malformed, unexpected character '%c' not part of a valid escape sequence" + out := strings.Builder{} escapedHexDigits := []rune{} st := stNormal @@ -83,7 +86,7 @@ func BlkIDDecodeLabel(in string) (string, error) { // otherwise it's a format error, "\" is not in the set of // characters allowed, so if we see one that is not followed by an // x, then the string is malformed and can't be decoded - return "", fmt.Errorf("string is malformed, unexpected '\\' character not part of a valid escape sequence") + return "", fmt.Errorf(errFmtStr, '\\') case stSlashEscapeX: // now we expect exactly two hex digits, since the encoding would // have written valid multi-byte runes that are UTF8 directly @@ -100,7 +103,7 @@ func BlkIDDecodeLabel(in string) (string, error) { st = stSlashEscapeXNum continue } - return "", fmt.Errorf("string is malformed, unexpected %q character not part of a valid escape sequence", r) + return "", fmt.Errorf(errFmtStr, r) case stSlashEscapeXNum: // got one digit, make sure we get a second digit if strings.ContainsRune(`0123456789abcedf`, r) { @@ -119,7 +122,7 @@ func BlkIDDecodeLabel(in string) (string, error) { st = stNormal continue } - return "", fmt.Errorf("string is malformed, unexpected %q character not part of a valid escape sequence", r) + return "", fmt.Errorf(errFmtStr, r) default: return "", fmt.Errorf("internal error, unexpected parsing state") } diff --git a/osutil/disks/labels_test.go b/osutil/disks/labels_test.go index 0db0485d7a..2735a3d357 100644 --- a/osutil/disks/labels_test.go +++ b/osutil/disks/labels_test.go @@ -109,11 +109,11 @@ func (ts *diskLabelSuite) TestBlkIDDecodeLabelUnhappy(c *C) { }{ { `\x7z`, - "string is malformed, unexpected 'z' character not part of a valid escape sequence", + "string is malformed, unexpected character 'z' not part of a valid escape sequence", }, { `\z`, - `string is malformed, unexpected '\\' character not part of a valid escape sequence`, + `string is malformed, unexpected character '\\' not part of a valid escape sequence`, }, { `\`, -- cgit v1.2.3 From b2017cacd8e3ac82c8382799b4c56f7246286ce6 Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Mon, 18 Oct 2021 14:03:20 -0500 Subject: osutil/disks: use static array for parsed hex characters Thanks to Maciej for the suggestion. Also add some more unit tests for invalid characters _after_ valid ones to ensure that one successful parsing doesn't preclude unsuccessful ones in the future when parsing. Signed-off-by: Ian Johnson --- osutil/disks/labels.go | 10 +++++----- osutil/disks/labels_test.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/osutil/disks/labels.go b/osutil/disks/labels.go index 387b49227e..19aba4fc1d 100644 --- a/osutil/disks/labels.go +++ b/osutil/disks/labels.go @@ -65,7 +65,7 @@ func BlkIDDecodeLabel(in string) (string, error) { const errFmtStr = "string is malformed, unexpected character '%c' not part of a valid escape sequence" out := strings.Builder{} - escapedHexDigits := []rune{} + escapedHexDigits := [2]rune{} st := stNormal for _, r := range in { switch st { @@ -99,7 +99,7 @@ func BlkIDDecodeLabel(in string) (string, error) { // rune value - for now we will just ignore those if strings.ContainsRune(`0123456789abcedf`, r) { - escapedHexDigits = append(escapedHexDigits, r) + escapedHexDigits[0] = r st = stSlashEscapeXNum continue } @@ -107,17 +107,17 @@ func BlkIDDecodeLabel(in string) (string, error) { case stSlashEscapeXNum: // got one digit, make sure we get a second digit if strings.ContainsRune(`0123456789abcedf`, r) { - escapedHexDigits = append(escapedHexDigits, r) + escapedHexDigits[1] = r // the escapedHexDigits can now be decoded and written out - v, err := strconv.ParseUint(string(escapedHexDigits), 16, 8) + v, err := strconv.ParseUint(string(escapedHexDigits[:]), 16, 8) if err != nil { // should be logically impossible, we ensured that only // rune digits in the hexadecimal range above were put into this rune // buffer return "", fmt.Errorf("internal error, unable to parse escape sequence: %v", err) } - escapedHexDigits = []rune{} + escapedHexDigits = [2]rune{0, 0} out.WriteRune(rune(v)) st = stNormal continue diff --git a/osutil/disks/labels_test.go b/osutil/disks/labels_test.go index 2735a3d357..10277d8c54 100644 --- a/osutil/disks/labels_test.go +++ b/osutil/disks/labels_test.go @@ -111,6 +111,10 @@ func (ts *diskLabelSuite) TestBlkIDDecodeLabelUnhappy(c *C) { `\x7z`, "string is malformed, unexpected character 'z' not part of a valid escape sequence", }, + { + `\x09\x7y`, + "string is malformed, unexpected character 'y' not part of a valid escape sequence", + }, { `\z`, `string is malformed, unexpected character '\\' not part of a valid escape sequence`, @@ -119,17 +123,30 @@ func (ts *diskLabelSuite) TestBlkIDDecodeLabelUnhappy(c *C) { `\`, `string is malformed, unfinished escape sequence`, }, + { + `\x40\`, + `string is malformed, unfinished escape sequence`, + }, { `\x`, `string is malformed, unfinished escape sequence`, }, + { + `\x40\x`, + `string is malformed, unfinished escape sequence`, + }, { `\x0`, `string is malformed, unfinished escape sequence`, }, + { + `\x40\x4`, + `string is malformed, unfinished escape sequence`, + }, } for _, t := range tt { + c.Logf("input: %q", t.in) _, err := disks.BlkIDDecodeLabel(t.in) c.Assert(err, ErrorMatches, t.experr) } -- cgit v1.2.3 From 8373aa5ac6c6d166882db8cd59d853cb4f795db0 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 19 Oct 2021 14:44:11 +0300 Subject: tests: simplify mock script for apparmor_parser (#10941) Instead of using bash arithmetics to count the iterations and have a switch for the exit values (which makes it cumbersome to add new invocations), directly write the exit code into the input file: at every invocation the first exit code is read (and used), while all the others are written back into the file. --- sandbox/apparmor/apparmor_test.go | 41 +++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/sandbox/apparmor/apparmor_test.go b/sandbox/apparmor/apparmor_test.go index 8df1641750..dec4c82622 100644 --- a/sandbox/apparmor/apparmor_test.go +++ b/sandbox/apparmor/apparmor_test.go @@ -195,48 +195,43 @@ func (s *apparmorSuite) TestProbeAppArmorKernelFeatures(c *C) { func (s *apparmorSuite) TestProbeAppArmorParserFeatures(c *C) { var testcases = []struct { - exitCodes []string + exitCodes []int expFeatures []string }{ { - exitCodes: []string{"1", "1"}, + exitCodes: []int{1, 1}, }, { - exitCodes: []string{"1", "0"}, + exitCodes: []int{1, 0}, expFeatures: []string{"qipcrtr-socket"}, }, { - exitCodes: []string{"0", "1"}, + exitCodes: []int{0, 1}, expFeatures: []string{"unsafe"}, }, { - exitCodes: []string{"0", "0"}, + exitCodes: []int{0, 0}, expFeatures: []string{"qipcrtr-socket", "unsafe"}, }, } for _, t := range testcases { d := c.MkDir() - err := ioutil.WriteFile(filepath.Join(d, "iter"), []byte("0"), 0755) + contents := "" + for _, code := range t.exitCodes { + contents += fmt.Sprintf("%d ", code) + } + err := ioutil.WriteFile(filepath.Join(d, "codes"), []byte(contents), 0755) c.Assert(err, IsNil) - c.Assert(t.exitCodes, HasLen, 2, Commentf("invalid test setup, must have two exit codes for two apparmor parser features probed")) mockParserCmd := testutil.MockCommand(c, "apparmor_parser", fmt.Sprintf(` cat >> %[1]s/stdin echo "" >> %[1]s/stdin -iter=$(cat %[1]s/iter) -iter=$(( iter + 1 )) -echo $iter > %[1]s/iter - -case $iter in - 1) - exit %[2]s - ;; - 2) - exit %[3]s - ;; -esac -`, d, t.exitCodes[0], t.exitCodes[1])) +read -r EXIT_CODE CODES_FOR_NEXT_CALLS < %[1]s/codes +echo "$CODES_FOR_NEXT_CALLS" > %[1]s/codes + +exit "$EXIT_CODE" +`, d)) defer mockParserCmd.Restore() restore := apparmor.MockParserSearchPath(mockParserCmd.BinDir()) defer restore() @@ -249,7 +244,11 @@ esac c.Check(features, DeepEquals, t.expFeatures) } - c.Check(mockParserCmd.Calls(), DeepEquals, [][]string{{"apparmor_parser", "--preprocess"}, {"apparmor_parser", "--preprocess"}}) + var expectedCalls [][]string + for range t.exitCodes { + expectedCalls = append(expectedCalls, []string{"apparmor_parser", "--preprocess"}) + } + c.Check(mockParserCmd.Calls(), DeepEquals, expectedCalls) data, err := ioutil.ReadFile(filepath.Join(d, "stdin")) c.Assert(err, IsNil) c.Check(string(data), Equals, `profile snap-test { -- cgit v1.2.3 From 0e725131555a298ec201ef418adf6e4977abdf44 Mon Sep 17 00:00:00 2001 From: Pawel Stolowski Date: Tue, 19 Oct 2021 17:53:07 +0200 Subject: o/snapstate, hookstate: print remaining hold time on snapctl --hold (#10883) Print remaining hold time after snapctl --hold; this time is the minimum value of hold times of all snaps held by the gating snap that calls snapctl command. In the future this may get extended to allow snaps to ask for 1 extra hour. --- overlord/hookstate/ctlcmd/refresh.go | 15 ++- overlord/hookstate/ctlcmd/refresh_test.go | 5 +- overlord/hookstate/hooks.go | 2 +- overlord/hookstate/hooks_test.go | 6 +- overlord/snapstate/autorefresh_gating.go | 24 +++- overlord/snapstate/autorefresh_gating_test.go | 182 ++++++++++++++++++++------ overlord/snapstate/snapstate_remove_test.go | 9 +- overlord/snapstate/snapstate_update_test.go | 3 +- tests/main/auto-refresh-gating/task.yaml | 5 + 9 files changed, 194 insertions(+), 57 deletions(-) diff --git a/overlord/hookstate/ctlcmd/refresh.go b/overlord/hookstate/ctlcmd/refresh.go index c715776ad3..34867f0c8f 100644 --- a/overlord/hookstate/ctlcmd/refresh.go +++ b/overlord/hookstate/ctlcmd/refresh.go @@ -152,6 +152,10 @@ type updateDetails struct { Restart bool `yaml:"restart"` } +type holdDetails struct { + Hold string `yaml:"hold"` +} + // refreshCandidate is a subset of refreshCandidate defined by snapstate and // stored in "refresh-candidates". type refreshCandidate struct { @@ -259,10 +263,19 @@ func (c *refreshCommand) hold() error { // no duration specified, use maximum allowed for this gating snap. var holdDuration time.Duration - if err := snapstate.HoldRefresh(st, ctx.InstanceName(), holdDuration, affecting...); err != nil { + remaining, err := snapstate.HoldRefresh(st, ctx.InstanceName(), holdDuration, affecting...) + if err != nil { // TODO: let a snap hold again once for 1h. return err } + var details holdDetails + details.Hold = remaining.String() + + out, err := yaml.Marshal(details) + if err != nil { + return err + } + c.printf("%s", string(out)) return nil } diff --git a/overlord/hookstate/ctlcmd/refresh_test.go b/overlord/hookstate/ctlcmd/refresh_test.go index 045ca15e4b..72a8c12229 100644 --- a/overlord/hookstate/ctlcmd/refresh_test.go +++ b/overlord/hookstate/ctlcmd/refresh_test.go @@ -197,7 +197,7 @@ version: 1 stdout, stderr, err := ctlcmd.Run(mockContext, []string{"refresh", "--hold"}, 0) c.Assert(err, IsNil) - c.Check(string(stdout), Equals, "") + c.Check(string(stdout), Equals, "hold: 48h0m0s\n") c.Check(string(stderr), Equals, "") mockContext.Lock() @@ -223,7 +223,8 @@ version: 1 `) // pretend snap foo is held initially - c.Check(snapstate.HoldRefresh(s.st, "snap1", 0, "foo"), IsNil) + _, err = snapstate.HoldRefresh(s.st, "snap1", 0, "foo") + c.Check(err, IsNil) s.st.Unlock() // sanity check diff --git a/overlord/hookstate/hooks.go b/overlord/hookstate/hooks.go index 9f994d3d74..448c83e32b 100644 --- a/overlord/hookstate/hooks.go +++ b/overlord/hookstate/hooks.go @@ -241,7 +241,7 @@ func (h *gateAutoRefreshHookHandler) Error(hookErr error) (ignoreHookErr bool, e // no duration specified, use maximum allowed for this gating snap. var holdDuration time.Duration - if err := snapstate.HoldRefresh(st, snapName, holdDuration, affecting...); err != nil { + if _, err := snapstate.HoldRefresh(st, snapName, holdDuration, affecting...); err != nil { // log the original hook error as we either ignore it or error out from // this handler, in both cases hookErr won't be logged by hook manager. h.context.Errorf("error: %v (while handling previous hook error: %v)", err, hookErr) diff --git a/overlord/hookstate/hooks_test.go b/overlord/hookstate/hooks_test.go index d4efdb29b6..6dd4d642f5 100644 --- a/overlord/hookstate/hooks_test.go +++ b/overlord/hookstate/hooks_test.go @@ -242,7 +242,8 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshDefaultProceedUnlocksRunin defer st.Unlock() // pretend that snap-a is initially held by itself. - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-a") + c.Assert(err, IsNil) // sanity checkIsHeld(c, st, "snap-a", "snap-a") @@ -292,7 +293,8 @@ func (s *gateAutoRefreshHookSuite) TestGateAutorefreshDefaultProceed(c *C) { defer st.Unlock() // pretend that snap-b is initially held by snap-a. - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b") + c.Assert(err, IsNil) // sanity checkIsHeld(c, st, "snap-b", "snap-a") diff --git a/overlord/snapstate/autorefresh_gating.go b/overlord/snapstate/autorefresh_gating.go index 4ad5cf44e6..d94e5319d2 100644 --- a/overlord/snapstate/autorefresh_gating.go +++ b/overlord/snapstate/autorefresh_gating.go @@ -144,16 +144,21 @@ func holdDurationLeft(now time.Time, lastRefresh, firstHeld time.Time, maxDurati // HoldRefresh marks affectingSnaps as held for refresh for up to holdTime. // HoldTime of zero denotes maximum allowed hold time. -// Holding may fail for only some snaps in which case HoldError is returned and -// it contains the details of failed ones. -func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration, affectingSnaps ...string) error { +// Holding fails if not all snaps can be held, in that case HoldError is returned +// and it contains the details of snaps that prevented holding. On success the +// function returns the remaining hold time. The remaining hold time is the +// minimum of the remaining hold time for all affecting snaps. +func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration, affectingSnaps ...string) (time.Duration, error) { gating, err := refreshGating(st) if err != nil { - return err + return 0, err } herr := &HoldError{ SnapsInError: make(map[string]HoldDurationError), } + + var durationMin time.Duration + now := timeNow() for _, heldSnap := range affectingSnaps { hold, ok := gating[heldSnap][gatingSnap] @@ -165,7 +170,7 @@ func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration, lastRefreshTime, err := lastRefreshed(st, heldSnap) if err != nil { - return err + return 0, err } mp := maxPostponement - maxPostponementBuffer @@ -213,6 +218,11 @@ func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration, gating[heldSnap] = make(map[string]*holdState) } gating[heldSnap][gatingSnap] = hold + + // note, left is guaranteed to be > 0 at this point + if durationMin == 0 || left < durationMin { + durationMin = left + } } if len(herr.SnapsInError) > 0 { @@ -228,9 +238,9 @@ func HoldRefresh(st *state.State, gatingSnap string, holdDuration time.Duration, } st.Set("snaps-hold", gating) if len(herr.SnapsInError) > 0 { - return herr + return 0, herr } - return nil + return durationMin, nil } // ProceedWithRefresh unblocks all snaps held by gatingSnap for refresh. This diff --git a/overlord/snapstate/autorefresh_gating_test.go b/overlord/snapstate/autorefresh_gating_test.go index df7ab1293a..a8ea5a1b0f 100644 --- a/overlord/snapstate/autorefresh_gating_test.go +++ b/overlord/snapstate/autorefresh_gating_test.go @@ -353,12 +353,16 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) { mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f") - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c") + c.Assert(err, IsNil) // this could be merged with the above HoldRefresh call, but it's fine if // done separately too. - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-e"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-e"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-f", 0, "snap-f"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-e") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-e") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "snap-f", 0, "snap-f") + c.Assert(err, IsNil) var gating map[string]map[string]*snapstate.HoldState c.Assert(st.Get("snaps-hold", &gating), IsNil) @@ -381,6 +385,65 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelper(c *C) { }) } +func (s *autorefreshGatingSuite) TestHoldRefreshReturnsMinimumHoldTime(c *C) { + st := s.state + st.Lock() + defer st.Unlock() + + now := "2021-05-10T10:00:00Z" + restore := snapstate.MockTimeNow(func() time.Time { + t, err := time.Parse(time.RFC3339, now) + c.Assert(err, IsNil) + return t + }) + defer restore() + + mockInstalledSnap(c, st, snapAyaml, false) + mockInstalledSnap(c, st, snapByaml, false) + mockInstalledSnap(c, st, snapCyaml, false) + mockInstalledSnap(c, st, snapDyaml, false) + mockInstalledSnap(c, st, snapEyaml, false) + mockInstalledSnap(c, st, snapFyaml, false) + + mockLastRefreshed(c, st, "2021-05-09T10:00:00Z", "snap-a", "snap-b", "snap-c", "snap-d", "snap-e", "snap-f") + + // only holding self: max postponement - buffer time returned + rem, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-a") + c.Assert(err, IsNil) + c.Check(rem.String(), Equals, "2136h0m0s") + + // holding self and some other snaps, max hold time of holding other snaps returned. + rem, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-e") + c.Assert(err, IsNil) + c.Check(rem.String(), Equals, "48h0m0s") + + // advance time + now = "2021-05-11T12:00:00Z" + rem, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-e") + c.Assert(err, IsNil) + // it's now less due to previous hold + c.Check(rem.String(), Equals, "22h0m0s") + + var gating map[string]map[string]*snapstate.HoldState + c.Assert(st.Get("snaps-hold", &gating), IsNil) + c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ + "snap-b": { + // holding of other snaps for maxOtherHoldDuration (48h) + "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), + }, + "snap-c": { + "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), + }, + "snap-e": { + "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-05-12T10:00:00Z"), + }, + "snap-a": { + // holding self set for maxPostponement minus 1 day due to last refresh. + "snap-a": snapstate.MockHoldState("2021-05-10T10:00:00Z", "2021-08-07T10:00:00Z"), + }, + }) +} + func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) { st := s.state st.Lock() @@ -402,7 +465,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) { // hold it for just a bit (10h) initially hold := time.Hour * 10 - c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) + rem, err := snapstate.HoldRefresh(st, "snap-b", hold, "snap-a") + c.Assert(err, IsNil) + c.Check(rem.String(), Equals, "48h0m0s") var gating map[string]map[string]*snapstate.HoldState c.Assert(st.Get("snaps-hold", &gating), IsNil) c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ @@ -413,7 +478,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) { // holding for a shorter time is fine too hold = time.Hour * 5 - c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) + rem, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-a") + c.Assert(err, IsNil) + c.Check(rem.String(), Equals, "48h0m0s") c.Assert(st.Get("snaps-hold", &gating), IsNil) c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ "snap-a": { @@ -428,7 +495,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperMultipleTimes(c *C) { // default hold time requested hold = 0 - c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) + rem, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-a") + c.Assert(err, IsNil) + c.Check(rem.String(), Equals, "26h0m0s") c.Assert(st.Get("snaps-hold", &gating), IsNil) c.Check(gating, DeepEquals, map[string]map[string]*snapstate.HoldState{ "snap-a": { @@ -459,7 +528,9 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperCloseToMaxPostponement(c * // request default hold time var hold time.Duration - c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) + rem, err := snapstate.HoldRefresh(st, "snap-b", hold, "snap-a") + c.Assert(err, IsNil) + c.Check(rem.String(), Equals, "24h0m0s") var gating map[string]map[string]*snapstate.HoldState c.Assert(st.Get("snaps-hold", &gating), IsNil) @@ -485,11 +556,13 @@ func (s *autorefreshGatingSuite) TestHoldRefreshExplicitHoldTime(c *C) { hold := time.Hour * 24 * 3 // holding self for 3 days - c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-a") + c.Assert(err, IsNil) // snap-b holds snap-a for 1 day hold = time.Hour * 24 - c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-a"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-a") + c.Assert(err, IsNil) var gating map[string]map[string]*snapstate.HoldState c.Assert(st.Get("snaps-hold", &gating), IsNil) @@ -521,11 +594,12 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) { // holding itself hold := time.Hour * 24 * 96 - c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-a"), ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-a" of 2304h0m0s by snap "snap-a" exceeds maximum holding time`) + _, err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-a") + c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-a" of 2304h0m0s by snap "snap-a" exceeds maximum holding time`) // holding other snap hold = time.Hour * 49 - err := snapstate.HoldRefresh(st, "snap-a", hold, "snap-b") + _, err = snapstate.HoldRefresh(st, "snap-a", hold, "snap-b") c.Check(err, ErrorMatches, `cannot hold some snaps:\n - requested holding duration for snap "snap-b" of 49h0m0s by snap "snap-a" exceeds maximum holding time`) herr, ok := err.(*snapstate.HoldError) c.Assert(ok, Equals, true) @@ -538,17 +612,21 @@ func (s *autorefreshGatingSuite) TestHoldRefreshHelperErrors(c *C) { // hold for maximum allowed for other snaps hold = time.Hour * 48 - c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-a", hold, "snap-b") + c.Assert(err, IsNil) // 2 days passed since it was first held now = "2021-05-12T10:00:00Z" hold = time.Minute * 2 - c.Assert(snapstate.HoldRefresh(st, "snap-a", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-a" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) + _, err = snapstate.HoldRefresh(st, "snap-a", hold, "snap-b") + c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-a" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) // refreshed long time ago (> maxPostponement) mockLastRefreshed(c, st, "2021-01-01T10:00:00Z", "snap-b") hold = time.Hour * 2 - c.Assert(snapstate.HoldRefresh(st, "snap-b", hold, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) - c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) + _, err = snapstate.HoldRefresh(st, "snap-b", hold, "snap-b") + c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) + _, err = snapstate.HoldRefresh(st, "snap-b", 0, "snap-b") + c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "snap-b" anymore, maximum refresh postponement exceeded`) } func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) { @@ -575,10 +653,13 @@ func (s *autorefreshGatingSuite) TestHoldAndProceedWithRefreshHelper(c *C) { c.Assert(err, IsNil) c.Check(held, IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-c") + c.Assert(err, IsNil) // holding self - c.Assert(snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-d", time.Hour*24*4, "snap-d") + c.Assert(err, IsNil) held, err = snapstate.HeldSnaps(st) c.Assert(err, IsNil) @@ -622,16 +703,20 @@ func (s *autorefreshGatingSuite) TestDontHoldSomeSnapsIfSomeFail(c *C) { defer restore() // snap-b, base-snap-b get refreshed and affect snap-b (gating snap) - c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b") + c.Assert(err, IsNil) // unrealted snap-d gets refreshed and holds itself - c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-d") + c.Assert(err, IsNil) // advance time by 49h now = "2021-05-03T11:00:00Z" // snap-b, base-snap-b and snap-c get refreshed and snap-a (gating snap) wants to hold them - c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b", "snap-c"), ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "base-snap-b" anymore, maximum refresh postponement exceeded`) + _, err = snapstate.HoldRefresh(st, "snap-b", 0, "snap-b", "base-snap-b", "snap-c") + c.Assert(err, ErrorMatches, `cannot hold some snaps:\n - snap "snap-b" cannot hold snap "base-snap-b" anymore, maximum refresh postponement exceeded`) // snap-bb (gating snap) wants to hold base-snap-b as well and succeeds since it didn't exceed its holding time yet - c.Assert(snapstate.HoldRefresh(st, "snap-bb", 0, "base-snap-b"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-bb", 0, "base-snap-b") + c.Assert(err, IsNil) held, err := snapstate.HeldSnaps(st) c.Assert(err, IsNil) @@ -660,8 +745,10 @@ func (s *autorefreshGatingSuite) TestPruneGatingHelper(c *C) { mockInstalledSnap(c, st, snapCyaml, false) mockInstalledSnap(c, st, snapDyaml, false) - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c") + c.Assert(err, IsNil) // sanity held, err := snapstate.HeldSnaps(st) c.Assert(err, IsNil) @@ -731,8 +818,10 @@ func (s *autorefreshGatingSuite) TestResetGatingForRefreshedHelper(c *C) { mockInstalledSnap(c, st, snapCyaml, false) mockInstalledSnap(c, st, snapDyaml, false) - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-b", "snap-c") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-d", "snap-c") + c.Assert(err, IsNil) c.Assert(snapstate.ResetGatingForRefreshed(st, "snap-b", "snap-c"), IsNil) var gating map[string]map[string]*snapstate.HoldState @@ -760,9 +849,11 @@ func (s *autorefreshGatingSuite) TestPruneSnapsHold(c *C) { mockInstalledSnap(c, st, snapDyaml, false) // snap-a is holding itself and 3 other snaps - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-d"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-a", 0, "snap-a", "snap-b", "snap-c", "snap-d") + c.Assert(err, IsNil) // in addition, snap-c is held by snap-d. - c.Assert(snapstate.HoldRefresh(st, "snap-d", 0, "snap-c"), IsNil) + _, err = snapstate.HoldRefresh(st, "snap-d", 0, "snap-c") + c.Assert(err, IsNil) // sanity check held, err := snapstate.HeldSnaps(st) @@ -1283,7 +1374,8 @@ func (s *autorefreshGatingSuite) TestAutoRefreshPhase1(c *C) { defer restore() // pretend some snaps are held - c.Assert(snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d"), IsNil) + _, err := snapstate.HoldRefresh(st, "gating-snap", 0, "snap-a", "snap-d") + c.Assert(err, IsNil) // sanity check heldSnaps, err := snapstate.HeldSnaps(st) c.Assert(err, IsNil) @@ -1799,7 +1891,8 @@ func (s *snapmgrTestSuite) TestAutoRefreshPhase2Held(c *C) { chg := s.testAutoRefreshPhase2(c, nil, func(snapName string) { if snapName == "snap-b" { // pretend than snap-b calls snapctl --hold to hold refresh of base-snap-b - c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) + _, err := snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b") + c.Assert(err, IsNil) } }, expected) @@ -1852,8 +1945,10 @@ func (s *snapmgrTestSuite) TestAutoRefreshPhase2Proceed(c *C) { s.testAutoRefreshPhase2(c, func() { // pretend that snap-a and base-snap-b are initially held - c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) - c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) + _, err := snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b") + c.Assert(err, IsNil) }, func(snapName string) { if snapName == "snap-a" { // pretend than snap-a calls snapctl --proceed @@ -1883,10 +1978,12 @@ func (s *snapmgrTestSuite) TestAutoRefreshPhase2AllHeld(c *C) { switch snapName { case "snap-b": // pretend that snap-b calls snapctl --hold to hold refresh of base-snap-b - c.Assert(snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b"), IsNil) + _, err := snapstate.HoldRefresh(s.state, "snap-b", 0, "base-snap-b") + c.Assert(err, IsNil) case "snap-a": // pretend that snap-a calls snapctl --hold to hold itself - c.Assert(snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a"), IsNil) + _, err := snapstate.HoldRefresh(s.state, "snap-a", 0, "snap-a") + c.Assert(err, IsNil) default: c.Fatalf("unexpected snap %q", snapName) } @@ -2309,8 +2406,10 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnap(c *C) { defer restore() // pretend some snaps are held - c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a") + c.Assert(err, IsNil) lastRefreshTime := time.Now().Add(-99 * time.Hour) st.Set("last-refresh", lastRefreshTime) @@ -2419,7 +2518,8 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapMoreAffectedSnaps(c defer restore() // pretend snap-b holds base-snap-b. - c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b") + c.Assert(err, IsNil) // pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed) c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil) @@ -2519,8 +2619,10 @@ func (s *autorefreshGatingSuite) TestAutoRefreshForGatingSnapNoCandidatesAnymore defer restore() // pretend some snaps are held - c.Assert(snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "snap-a", 0, "snap-a"), IsNil) + _, err := snapstate.HoldRefresh(st, "snap-b", 0, "base-snap-b") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "snap-a", 0, "snap-a") + c.Assert(err, IsNil) // pretend that snap-b triggers auto-refresh (by calling snapctl refresh --proceed) c.Assert(snapstate.AutoRefreshForGatingSnap(st, "snap-b"), IsNil) diff --git a/overlord/snapstate/snapstate_remove_test.go b/overlord/snapstate/snapstate_remove_test.go index 05c024f4ff..fb3176ef35 100644 --- a/overlord/snapstate/snapstate_remove_test.go +++ b/overlord/snapstate/snapstate_remove_test.go @@ -1642,8 +1642,10 @@ func (s *snapmgrTestSuite) TestRemovePrunesRefreshGatingDataOnLastRevision(c *C) } st.Set("refresh-candidates", rc) - c.Assert(snapstate.HoldRefresh(st, "some-snap", 0, "foo-snap"), IsNil) - c.Assert(snapstate.HoldRefresh(st, "another-snap", 0, "some-snap"), IsNil) + _, err := snapstate.HoldRefresh(st, "some-snap", 0, "foo-snap") + c.Assert(err, IsNil) + _, err = snapstate.HoldRefresh(st, "another-snap", 0, "some-snap") + c.Assert(err, IsNil) held, err := snapstate.HeldSnaps(st) c.Assert(err, IsNil) @@ -1699,7 +1701,8 @@ func (s *snapmgrTestSuite) TestRemoveKeepsGatingDataIfNotLastRevision(c *C) { rc := map[string]*snapstate.RefreshCandidate{"some-snap": {}} st.Set("refresh-candidates", rc) - c.Assert(snapstate.HoldRefresh(st, "some-snap", 0, "some-snap"), IsNil) + _, err := snapstate.HoldRefresh(st, "some-snap", 0, "some-snap") + c.Assert(err, IsNil) held, err := snapstate.HeldSnaps(st) c.Assert(err, IsNil) diff --git a/overlord/snapstate/snapstate_update_test.go b/overlord/snapstate/snapstate_update_test.go index 49b3439ec0..506340049b 100644 --- a/overlord/snapstate/snapstate_update_test.go +++ b/overlord/snapstate/snapstate_update_test.go @@ -1108,7 +1108,8 @@ func (s *snapmgrTestSuite) TestUpdateResetsHoldState(c *C) { tr.Commit() // pretend that the snap was held during last auto-refresh - c.Assert(snapstate.HoldRefresh(s.state, "gating-snap", 0, "some-snap", "other-snap"), IsNil) + _, err := snapstate.HoldRefresh(s.state, "gating-snap", 0, "some-snap", "other-snap") + c.Assert(err, IsNil) // sanity check held, err := snapstate.HeldSnaps(s.state) c.Assert(err, IsNil) diff --git a/tests/main/auto-refresh-gating/task.yaml b/tests/main/auto-refresh-gating/task.yaml index fe93c3a3ce..a7f3cefd1a 100644 --- a/tests/main/auto-refresh-gating/task.yaml +++ b/tests/main/auto-refresh-gating/task.yaml @@ -68,6 +68,11 @@ execute: | MATCH "pending: none" < "$DEBUG_LOG_FILE" NOMATCH "version:" < "$DEBUG_LOG_FILE" + echo "Check that --hold output contains remaining hold time" + # we cannot match precisely, this might be 48h0m0s if we are lucky, or + # a tiny bit less depending on timing. + MATCH "hold: 4[78]h.*m.*s" < "$DEBUG_LOG_FILE" + echo "Ensure our content snap was held and is still at version 1" snap list | MATCH "$CONTENT_SNAP_NAME +1\.0\.0" # sanity check for the gating snap. -- cgit v1.2.3