diff options
| author | Paweł Stołowski <stolowski@gmail.com> | 2021-10-20 10:08:18 +0200 |
|---|---|---|
| committer | Paweł Stołowski <stolowski@gmail.com> | 2021-10-20 10:08:18 +0200 |
| commit | 19777ca67d6a78577a01d11aaf7bb011fde60ce1 (patch) | |
| tree | 8127e10e39577f26842e4e15bb42d61e72aa25cf | |
| parent | b6abd7f487bb0ea29e8bf78bbbcd0aff3242dcd7 (diff) | |
| parent | 79cec58dc0cee0c757ffe758279ddc44210b4ed8 (diff) | |
Merge branch 'master' into validation-sets/enforce-with-prereqvalidation-sets/enforce-with-prereq
64 files changed, 1775 insertions, 739 deletions
diff --git a/bootloader/export_test.go b/bootloader/export_test.go index 94fdfe622c..c37992d584 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", @@ -144,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/cmd/libsnap-confine-private/device-cgroup-support.c b/cmd/libsnap-confine-private/device-cgroup-support.c index 8c45709dd6..0889f0a6cc 100644 --- a/cmd/libsnap-confine-private/device-cgroup-support.c +++ b/cmd/libsnap-confine-private/device-cgroup-support.c @@ -284,6 +284,20 @@ 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[PATH_MAX] = {0}; + strncpy(copy, group, sizeof(copy) - 1); + 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 +306,14 @@ 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)) { + /* 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 */ self->v2.old_limit = _sc_cgroup_v2_adjust_memlock_limit(); 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/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 */ diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go index 4f68096db7..f839a2f492 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:"<snap>"` } `positional-args:"yes" required:"yes"` } @@ -592,12 +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, - IgnoreRunning: x.IgnoreRunning, + Channel: x.Channel, + Revision: x.Revision, + 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 installation"), + // 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) { diff --git a/gadget/gadget.go b/gadget/gadget.go index bb550ba517..a0da7a8830 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" @@ -272,16 +271,16 @@ 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. -func SaveDiskVolumesDeviceTraits(mapping map[string]DiskVolumeDeviceTraits) error { +// SaveDiskVolumesDeviceTraits saves the mapping of volume names to volume / +// 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 { 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 @@ -292,10 +291,10 @@ func SaveDiskVolumesDeviceTraits(mapping map[string]DiskVolumeDeviceTraits) erro // 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 c54f0c730f..a8817b34da 100644 --- a/gadget/gadget_test.go +++ b/gadget/gadget_test.go @@ -3034,14 +3034,22 @@ 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) - 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) - m2, err := gadget.LoadDiskVolumesDeviceTraits() + // now that it was saved to dirs.SnapDeviceDir, we can load it correctly + m2, err := gadget.LoadDiskVolumesDeviceTraits(dirs.SnapDeviceDir) c.Assert(err, IsNil) c.Assert(m, DeepEquals, m2) @@ -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 @@ -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/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, 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, 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), diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index 7f2c05ac43..09142bddf4 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -144,11 +144,20 @@ 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) + // 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 } @@ -159,7 +168,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 +186,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/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, diff --git a/osutil/disks/labels.go b/osutil/disks/labels.go index ab5c25245e..19aba4fc1d 100644 --- a/osutil/disks/labels.go +++ b/osutil/disks/labels.go @@ -22,6 +22,7 @@ package disks import ( "bytes" "fmt" + "strconv" "strings" "unicode/utf8" ) @@ -39,10 +40,101 @@ 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) } } 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) { + + const errFmtStr = "string is malformed, unexpected character '%c' not part of a valid escape sequence" + + out := strings.Builder{} + escapedHexDigits := [2]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(errFmtStr, '\\') + 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[0] = r + st = stSlashEscapeXNum + continue + } + return "", fmt.Errorf(errFmtStr, r) + case stSlashEscapeXNum: + // got one digit, make sure we get a second digit + if strings.ContainsRune(`0123456789abcedf`, r) { + escapedHexDigits[1] = 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 = [2]rune{0, 0} + out.WriteRune(rune(v)) + st = stNormal + continue + } + return "", fmt.Errorf(errFmtStr, 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 bc89b067e8..10277d8c54 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 <string.h> @@ -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,9 +82,72 @@ 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) 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 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`, + }, + { + `\`, + `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) } } diff --git a/osutil/disks/mockdisk.go b/osutil/disks/mockdisk.go index 898827f92d..6546eb66a4 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 } -// MockDeviceNameDisksToPartitionMapping will mock DiskFromDeviceName such that -// the provided map of device names to mock disks is used instead of the actual +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 +} + +// 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(mockedMountPoints map[string]*MockDiskMapping) (restore func()) { +func MockDeviceNameToDiskMapping(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..a7b9127adb 100644 --- a/osutil/disks/mockdisk_test.go +++ b/osutil/disks/mockdisk_test.go @@ -39,11 +39,14 @@ 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{ - 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", @@ -67,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") @@ -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/overlord/devicestate/devicestate.go b/overlord/devicestate/devicestate.go index 0ad0fcc28b..46a601245b 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 @@ -465,49 +476,31 @@ func remodelKernelOrBaseTasks(ctx context.Context, st *state.State, ms modelSnap // switch-snap-channel return ts, nil } else { - // in other cases make sure that the - // kernel or base is linked and available - return snapstate.AddLinkNewBaseOrKernel(st, ts) + 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 } } } } - 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 +515,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 +530,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 +545,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 8e28ec3bfb..d7364e5b6d 100644 --- a/overlord/devicestate/devicestate_remodel_test.go +++ b/overlord/devicestate/devicestate_remodel_test.go @@ -2096,7 +2096,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) @@ -2173,13 +2178,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, @@ -2212,8 +2220,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", }, @@ -2224,8 +2232,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) + 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) @@ -2240,11 +2248,15 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna // check the tasks tPrepareKernel := tl[0] tLinkKernel := tl[1] - tPrepareBase := tl[2] - tLinkBase := tl[3] - tCreateRecovery := tl[4] - tFinalizeRecovery := tl[5] - tSetModel := tl[6] + 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") @@ -2252,11 +2264,22 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna 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) 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) 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)) @@ -2268,32 +2291,46 @@ func (s *deviceMgrRemodelSuite) TestRemodelUC20SwitchKernelBaseSnapsInstalledSna c.Assert(tPrepareKernel.WaitTasks(), HasLen, 0) c.Assert(tLinkKernel.WaitTasks(), DeepEquals, []*state.Task{ tPrepareKernel, - tPrepareBase, + tPrepareGadget, tCreateRecovery, tFinalizeRecovery, }) + c.Assert(tUpdateAssetsKernel.WaitTasks(), DeepEquals, []*state.Task{ + tLinkKernel, + }) c.Assert(tPrepareBase.WaitTasks(), DeepEquals, []*state.Task{ tPrepareKernel, }) c.Assert(tLinkBase.WaitTasks(), DeepEquals, []*state.Task{ tPrepareBase, - tLinkKernel, + tUpdateAssetsKernel, + }) + c.Assert(tPrepareGadget.WaitTasks(), DeepEquals, []*state.Task{ + tPrepareBase, + }) + c.Assert(tUpdateAssets.WaitTasks(), DeepEquals, []*state.Task{ + 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) - 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, + tPrepareKernel, tLinkKernel, tUpdateAssetsKernel, tPrepareBase, tLinkBase, + tPrepareGadget, tUpdateAssets, tUpdateCmdline, tCreateRecovery, tFinalizeRecovery, }) // verify recovery system setup data on appropriate tasks @@ -2303,18 +2340,21 @@ 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) { +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) @@ -2328,6 +2368,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{ @@ -2409,11 +2452,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, @@ -2447,8 +2492,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", }, @@ -2466,8 +2511,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) @@ -2482,11 +2529,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") @@ -2494,11 +2545,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)) @@ -2521,8 +2581,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 @@ -2771,6 +2832,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) @@ -2945,6 +3009,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) @@ -3115,7 +3181,10 @@ 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 s.state.Lock() defer s.state.Unlock() s.state.Set("seeded", true) 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/managers_test.go b/overlord/managers_test.go index c80abdff03..d54cf8afe8 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -4294,6 +4294,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 @@ -5679,14 +5688,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()) @@ -6185,6 +6198,13 @@ type: gadget base: core20 ` +const oldPcGadgetSnapYaml = ` +version: 1.0 +name: pc +type: gadget +base: core20 +` + const pcKernelSnapYaml = ` version: 1.0 name: pc-kernel @@ -6203,6 +6223,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 ( @@ -6214,10 +6260,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, @@ -6225,7 +6278,8 @@ var ( "baz": "version: 1.0\nname: baz\nbase: core20", } snapFilesForRemodel = map[string][][]string{ - "pc": pcGadgetFiles, + "old-pc": oldPcGadgetFiles, + "pc": pcGadgetFiles, // use a different fileset, such that the pc snap with this // content will have a different digest than the regular pc snap "pc-rev-33": append(pcGadgetFiles, []string{ @@ -7211,6 +7265,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 := restart.Pending(st) + c.Check(restarting, Equals, true) + c.Assert(kind, Equals, restart.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 + restart.MockPending(st, restart.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 = restart.Pending(st) + c.Check(restarting, Equals, true) + c.Assert(kind, Equals, restart.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) 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 1541b7eca3..ef38539d3b 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.go b/overlord/snapstate/snapstate.go index f541d55816..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) @@ -2217,36 +2218,131 @@ 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 +} + +// 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) + 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 +} + +// 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) + 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("internal error: 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 +} + +// 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) + 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 } 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 1421412ab9..0987d71f14 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/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 { 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..2a21d39985 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}) @@ -323,18 +320,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", + }, }, } @@ -384,7 +385,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 +397,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 +429,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 +469,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() @@ -479,10 +480,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 @@ -490,7 +499,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 +582,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 +598,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 +735,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 +754,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 +769,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 +786,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 +803,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 +815,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 +823,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 +833,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 +841,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 +852,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 +860,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 +879,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 +890,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 +926,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 +945,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 +971,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 +983,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 +1002,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 +1013,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 +1025,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 +1035,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 +1054,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 +1091,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 } @@ -1126,9 +1107,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 +1115,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 +1147,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 +1185,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 +1235,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 +1472,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 +1533,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 +1588,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 +1641,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 +1701,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 +1769,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 +1826,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", + }, }, } 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 Binary files differdeleted file mode 100644 index c588b55e7e..0000000000 --- a/secboot/test-data/keyfile +++ /dev/null diff --git a/spread.yaml b/spread.yaml index c40aa5ff01..9203c0bd9c 100644 --- a/spread.yaml +++ b/spread.yaml @@ -462,6 +462,9 @@ debug-each: | echo "# device cgroup $cgroup_dev" 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 case "$SPREAD_SYSTEM" in 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 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. diff --git a/tests/main/cwd/task.yaml b/tests/main/cwd/task.yaml index 40c617f207..a682ce0a9f 100644 --- a/tests/main/cwd/task.yaml +++ b/tests/main/cwd/task.yaml @@ -7,11 +7,16 @@ 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 restore: | rmdir /tmp/test || true + tests.session -u test restore debug: | # Much of what we do depends on permissions. If the permissions on those @@ -39,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 && su -c "snap run test-snapd-sh.sh -c pwd" test)" = "/var/lib/snapd/void" + case "$SPREAD_SYSTEM" in + fedora-*|opensuse-tumbleweed-*|arch-linux-*) + # 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 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-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/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 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 diff --git a/tests/main/parallel-install-basic/task.yaml b/tests/main/parallel-install-basic/task.yaml index 04a5abe097..9194deebd8 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 @@ -7,22 +10,26 @@ 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 - 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 @@ -33,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 @@ -50,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 @@ -67,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/security-private-tmp/task.yaml b/tests/main/security-private-tmp/task.yaml index 17e0e9b40d..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 @@ -16,9 +17,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)" diff --git a/tests/main/selinux-snap-restorecon/task.yaml b/tests/main/selinux-snap-restorecon/task.yaml index a80034d7ff..fa9f74930b 100644 --- a/tests/main/selinux-snap-restorecon/task.yaml +++ b/tests/main/selinux-snap-restorecon/task.yaml @@ -10,19 +10,21 @@ 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 # 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" @@ -39,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 @@ -48,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 diff --git a/tests/main/snap-confine-drops-sys-admin/task.yaml b/tests/main/snap-confine-drops-sys-admin/task.yaml index 8e184b0d83..b53ca628cc 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. @@ -23,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 @@ -33,8 +39,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 +49,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' diff --git a/tests/main/snap-confine-privs/task.yaml b/tests/main/snap-confine-privs/task.yaml index 9312b04ce1..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. @@ -33,6 +34,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 +51,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 +67,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 ' diff --git a/tests/main/snap-confine-undesired-mode-group/task.yaml b/tests/main/snap-confine-undesired-mode-group/task.yaml index 9f9f594278..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 @@ -8,13 +11,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 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 diff --git a/tests/main/snapd-snap/task.yaml b/tests/main/snapd-snap/task.yaml index cac0a685c3..d68458f688 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 diff --git a/tests/main/snapshot-users/task.yaml b/tests/main/snapshot-users/task.yaml index 3133039469..bda95a9c24 100644 --- a/tests/main/snapshot-users/task.yaml +++ b/tests/main/snapshot-users/task.yaml @@ -1,13 +1,20 @@ 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 + +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 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}" 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" diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml index 30292153b8..30292153b8 100644 --- a/tests/nested/manual/grade-signed-above-testkeys-boot/defaults.yaml +++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/defaults.yaml diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device index 357c0850fa..357c0850fa 100755 --- a/tests/nested/manual/grade-signed-above-testkeys-boot/prepare-device +++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/prepare-device diff --git a/tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml index 69b1cca28e..69b1cca28e 100644 --- a/tests/nested/manual/grade-signed-above-testkeys-boot/task.yaml +++ b/tests/nested/manual/core20-grade-signed-above-testkeys-boot/task.yaml diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml index 6417309d65..6417309d65 100644 --- a/tests/nested/manual/grade-signed-cloud-init-testkeys/defaults.yaml +++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/defaults.yaml diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device index 357c0850fa..357c0850fa 100755 --- a/tests/nested/manual/grade-signed-cloud-init-testkeys/prepare-device +++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/prepare-device diff --git a/tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/task.yaml index a930eb5b08..a930eb5b08 100644 --- a/tests/nested/manual/grade-signed-cloud-init-testkeys/task.yaml +++ b/tests/nested/manual/core20-grade-signed-cloud-init-testkeys/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..9d32fba916 --- /dev/null +++ b/tests/nested/manual/core20-new-snapd-does-not-break-old-initrd/task.yaml @@ -0,0 +1,97 @@ +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/startwithold: 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 + + # 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)" + 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 + # doesn't support v2 secboot keys + 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 + 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)" + + # 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? 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") |
