From 1c87e133c7ab7ecfecb3e4848acd1a0127a4862d Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 19 Apr 2022 13:49:33 +0200 Subject: [PATCH 001/153] tests/nested/manual/core20-early-config: revert changes that disable netplan checks We no longer need to skip those checks. Signed-off-by: Maciej Borzecki --- tests/nested/manual/core20-early-config/task.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/nested/manual/core20-early-config/task.yaml b/tests/nested/manual/core20-early-config/task.yaml index 3c97684a5b1..d546b866928 100644 --- a/tests/nested/manual/core20-early-config/task.yaml +++ b/tests/nested/manual/core20-early-config/task.yaml @@ -72,11 +72,6 @@ execute: | tests.nested exec "cat /etc/hostname" | MATCH "foo" tests.nested exec "hostname" | MATCH "foo" - # TODO: 2022.04.19 exercise netplan checks once - # https://bugs.launchpad.net/ubuntu/+source/netplan.io/+bug/1967084 is - # fixed - return - # netplan config defaults are applied tests.nested exec "cat /etc/netplan/0-snapd-defaults.yaml" | MATCH br54 tests.nested exec "netplan get bridges.br54.dhcp4" | MATCH true From 35d689c666d9bf54a9ba0b3daa49a83fab6ea79b Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Tue, 23 Nov 2021 13:10:33 +1030 Subject: [PATCH 002/153] many: Reimplement snapd-apparmor in Go This ensures snapd-apparmor will use the same apparmor_parser and compilation options as snapd itself when compiling AppArmor policies. Signed-off-by: Alex Murray --- .gitignore | 1 + cmd/Makefile.am | 14 +-- cmd/snapd-apparmor/export_test.go | 25 +++++ cmd/snapd-apparmor/main.go | 153 +++++++++++++++++++++++++++ cmd/snapd-apparmor/main_test.go | 122 +++++++++++++++++++++ packaging/debian-sid/snapd.install | 4 +- packaging/snapd.mk | 6 +- packaging/ubuntu-14.04/rules | 2 +- packaging/ubuntu-16.04/snapd.install | 4 +- 9 files changed, 311 insertions(+), 20 deletions(-) create mode 100644 cmd/snapd-apparmor/export_test.go create mode 100644 cmd/snapd-apparmor/main.go create mode 100644 cmd/snapd-apparmor/main_test.go diff --git a/.gitignore b/.gitignore index b98bf411ada..4d8b34ad240 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ cmd/snap-mgmt/snap-mgmt cmd/snap-seccomp/snap-seccomp cmd/snap-update-ns/snap-update-ns cmd/snap-update-ns/unit-tests +cmd/snapd-apparmor/snapd-apparmor cmd/snapd-env-generator/snapd-env-generator cmd/snapd-generator/snapd-generator cmd/system-shutdown/system-shutdown diff --git a/cmd/Makefile.am b/cmd/Makefile.am index 5ec0dfcb0a9..714c22130c7 100644 --- a/cmd/Makefile.am +++ b/cmd/Makefile.am @@ -100,7 +100,7 @@ fmt:: $(filter-out $(addprefix %,$(new_format)),$(foreach dir,$(subdirs),$(wildc # The hack target helps developers work on snap-confine on their live system by # installing a fresh copy of snap confine and the appropriate apparmor profile. .PHONY: hack -hack: snap-confine/snap-confine-debug snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns snap-seccomp/snap-seccomp snap-discard-ns/snap-discard-ns snap-device-helper/snap-device-helper +hack: snap-confine/snap-confine-debug snap-confine/snap-confine.apparmor snap-update-ns/snap-update-ns snap-seccomp/snap-seccomp snap-discard-ns/snap-discard-ns snap-device-helper/snap-device-helper snapd-apparmor/snapd-apparmor sudo install -D -m 4755 snap-confine/snap-confine-debug $(DESTDIR)$(libexecdir)/snap-confine if [ -d /etc/apparmor.d ]; then sudo install -m 644 snap-confine/snap-confine.apparmor $(DESTDIR)/etc/apparmor.d/$(patsubst .%,%,$(subst /,.,$(libexecdir))).snap-confine.real; fi sudo install -d -m 755 $(DESTDIR)/var/lib/snapd/apparmor/snap-confine/ @@ -109,6 +109,7 @@ hack: snap-confine/snap-confine-debug snap-confine/snap-confine.apparmor snap-up sudo install -m 755 snap-discard-ns/snap-discard-ns $(DESTDIR)$(libexecdir)/snap-discard-ns sudo install -m 755 snap-seccomp/snap-seccomp $(DESTDIR)$(libexecdir)/snap-seccomp sudo install -m 755 snap-device-helper/snap-device-helper $(DESTDIR)$(libexecdir)/snap-device-helper + sudo install -m 755 snapd-apparmor/snapd-apparmor $(DESTDIR)$(libexecdir)/snapd-apparmor if [ "$$(command -v restorecon)" != "" ]; then sudo restorecon -R -v $(DESTDIR)$(libexecdir)/; fi # for the hack target also: @@ -117,6 +118,8 @@ snap-update-ns/snap-update-ns: snap-update-ns/*.go snap-update-ns/*.[ch] -ldflags='-extldflags=-static -linkmode=external' -v snap-seccomp/snap-seccomp: snap-seccomp/*.go cd snap-seccomp && GOPATH=$(or $(GOPATH),$(realpath $(srcdir)/../../../../..)) go build -v +snapd-apparmor/snapd-apparmor: snapd-apparmor/*.go + cd snapd-apparmor && GOPATH=$(or $(GOPATH),$(realpath $(srcdir)/../../../../..)) go build -v ## ## libsnap-confine-private.a @@ -571,14 +574,5 @@ dist_man_MANS += snapd-env-generator/snapd-env-generator.8 CLEANFILES += snapd-env-generator/snapd-env-generator.8 endif -## -## snapd-apparmor -## - -EXTRA_DIST += snapd-apparmor/snapd-apparmor - install-exec-local:: install -d -m 755 $(DESTDIR)$(libexecdir) -if APPARMOR - install -m 755 $(srcdir)/snapd-apparmor/snapd-apparmor $(DESTDIR)$(libexecdir) -endif diff --git a/cmd/snapd-apparmor/export_test.go b/cmd/snapd-apparmor/export_test.go new file mode 100644 index 00000000000..9b637bb8437 --- /dev/null +++ b/cmd/snapd-apparmor/export_test.go @@ -0,0 +1,25 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +var ( + IsContainerWithInternalPolicy = isContainerWithInternalPolicy + LoadAppArmorProfiles = loadAppArmorProfiles +) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go new file mode 100644 index 00000000000..878510647f9 --- /dev/null +++ b/cmd/snapd-apparmor/main.go @@ -0,0 +1,153 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +// This script is provided for integration with systemd on distributions where +// apparmor profiles generated and managed by snapd are not loaded by the +// system-wide apparmor systemd integration on early boot-up. +// +// Only the start operation is provided as all other activity is managed by +// snapd as a part of the life-cycle of particular snaps. +// +// In addition the script assumes that the system-wide apparmor service has +// already executed, initializing apparmor file-systems as necessary. +// +// NOTE: This script ignores failures in some scenarios as the intent is to +// simply load application profiles ahead of time, as many as we can (for +// performance reasons), even if for whatever reason some of those fail. + +package main + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" + apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor" +) + +// Checks to see if the current container is capable of having internal AppArmor +// profiles that should be loaded. +// +// The only known container environments capable of supporting internal policy +// are LXD and LXC environment. +// +// Returns true if the container environment is capable of having its own internal +// policy and false otherwise. +// +// IMPORTANT: This function will return true in the case of a non-LXD/non-LXC +// system container technology being nested inside of a LXD/LXC container that +// utilized an AppArmor namespace and profile stacking. The reason true will be +// returned is because .ns_stacked will be "yes" and .ns_name will still match +// "lx[dc]-*" since the nested system container technology will not have set up +// a new AppArmor profile namespace. This will result in the nested system +// container's boot process to experience failed policy loads but the boot +// process should continue without any loss of functionality. This is an +// unsupported configuration that cannot be properly handled by this function. +// +func isContainerWithInternalPolicy() bool { + var appArmorSecurityFSPath = filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor") + var nsStackedPath = filepath.Join(appArmorSecurityFSPath, ".ns_stacked") + var nsNamePath = filepath.Join(appArmorSecurityFSPath, ".ns_name") + + for _, path := range []string{nsStackedPath, nsNamePath} { + if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { + return false + } + } + + contents, err := ioutil.ReadFile(nsStackedPath) + if err != nil { + return false + } + + if strings.TrimSpace(string(contents)) != "yes" { + return false + } + + contents, err = ioutil.ReadFile(nsNamePath) + if err != nil { + return false + } + + // LXD and LXC set up AppArmor namespaces starting with "lxd-" and + // "lxc-", respectively. Return false for all other namespace + // identifiers. + name := strings.TrimSpace(string(contents)) + if !strings.HasPrefix(name, "lxd-") && !strings.HasPrefix(name, "lxc-") { + return false + } + return true +} + +func loadAppArmorProfiles() error { + candidates, err := filepath.Glob(dirs.SnapAppArmorDir + "/*") + if err != nil { + return err + } + + profiles := make([]string, 0, len(candidates)) + for _, profile := range candidates { + // Filter out profiles with names ending with ~, those are + // temporary files created by snapd. + if strings.HasSuffix(profile, "~") { + continue + } + profiles = append(profiles, profile) + } + if len(profiles) == 0 { + logger.Noticef("No profiles to load") + return nil + } + logger.Noticef("Loading profiles %v", profiles) + err = apparmor_sandbox.LoadProfiles(profiles, apparmor_sandbox.SystemCacheDir, 0) + if err != nil { + return err + } + return nil +} + +func main() { + if len(os.Args) != 2 || os.Args[1] != "start" { + fmt.Fprintln(os.Stderr, "Expected to be called with 'start' argument.") + os.Exit(1) + } + if err := exec.Command("systemd-detect-virt", "--quiet", "--container").Run(); err == nil { + logger.Debugf("inside container environment") + // in container environment - see if container has own + // policy that we need to manage otherwise get out of the + // way + if !isContainerWithInternalPolicy() { + logger.Noticef("Inside container environment without internal policy") + os.Exit(0) + } + } + + err := loadAppArmorProfiles() + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + os.Exit(0) +} diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go new file mode 100644 index 00000000000..3133dbc162c --- /dev/null +++ b/cmd/snapd-apparmor/main_test.go @@ -0,0 +1,122 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + . "gopkg.in/check.v1" + + snapd_apparmor "github.com/snapcore/snapd/cmd/snapd-apparmor" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/testutil" +) + +// Hook up check.v1 into the "go test" runner +func Test(t *testing.T) { TestingT(t) } + +type mainSuite struct { + testutil.BaseTest +} + +var _ = Suite(&mainSuite{}) + +func (s *mainSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) + +} + +func (s *mainSuite) TearDownTest(c *C) { + dirs.SetRootDir("/") +} + +func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + + appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/") + if err := os.MkdirAll(appArmorSecurityFSPath, 0755); err != nil { + panic(err) + } + + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + + // simulate being inside a container environment + testutil.MockCommand(c, "systemd-detect-virt", "") + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + + f, err := os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_stacked")) + if err != nil { + panic(err) + } + f.WriteString("yes") + f.Close() + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + + f, err = os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_name")) + if err != nil { + panic(err) + } + defer f.Close() + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + + f.WriteString("foo") + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + // lxc/lxd name should result in a container with internal policy + f.Seek(0, 0) + f.WriteString("lxc-foo") + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, true) +} + +func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { + testutil.MockCommand(c, "apparmor_parser", "") + err := snapd_apparmor.LoadAppArmorProfiles() + c.Assert(err, IsNil) + + // mock a profile + if err = os.MkdirAll(dirs.SnapAppArmorDir, 0755); err != nil { + panic(err) + } + + profile := filepath.Join(dirs.SnapAppArmorDir, "foo") + f, err := os.Create(profile) + if err != nil { + panic(err) + } + f.Close() + + err = snapd_apparmor.LoadAppArmorProfiles() + c.Assert(err, IsNil) + + // catch unexpected changes to apparmor_parser compiler flags etc + testutil.MockCommand(c, "apparmor_parser", `echo "$@"; ""exit "$#"`) + err = snapd_apparmor.LoadAppArmorProfiles() + c.Check(err.Error(), Equals, fmt.Sprintf("cannot load apparmor profiles: exit status 7\napparmor_parser output:\n--replace --write-cache -O no-expr-simplify --cache-loc=%s/var/cache/apparmor --quiet %s\n", dirs.GlobalRootDir, profile)) + + // rename so file is ignored + err = os.Rename(profile, profile+"~") + if err != nil { + panic(err) + } + err = snapd_apparmor.LoadAppArmorProfiles() + c.Assert(err, IsNil) +} diff --git a/packaging/debian-sid/snapd.install b/packaging/debian-sid/snapd.install index ac0658fa9e2..4ef013b415f 100644 --- a/packaging/debian-sid/snapd.install +++ b/packaging/debian-sid/snapd.install @@ -5,6 +5,7 @@ usr/bin/snap-exec /usr/lib/snapd/ usr/bin/snap-update-ns /usr/lib/snapd/ usr/bin/snapd /usr/lib/snapd/ usr/bin/snap-seccomp /usr/lib/snapd/ +usr/bin/snapd-apparmor /usr/lib/snapd/ # bash completion data/completion/bash/snap /usr/share/bash-completion/completions @@ -38,6 +39,3 @@ usr/lib/snapd/snap-gdbserver-shim usr/lib/systemd/system-environment-generators # but system generators end up in lib lib/systemd/system-generators - -# service for loading apparmor profiles for snap applications -usr/lib/snapd/snapd-apparmor diff --git a/packaging/snapd.mk b/packaging/snapd.mk index 2d961654af6..aff49f7f0f1 100644 --- a/packaging/snapd.mk +++ b/packaging/snapd.mk @@ -53,7 +53,7 @@ snap_mount_dir = /snap endif # The list of go binaries we are expected to build. -go_binaries = $(addprefix $(builddir)/, snap snapctl snap-seccomp snap-update-ns snap-exec snapd) +go_binaries = $(addprefix $(builddir)/, snap snapctl snap-seccomp snap-update-ns snap-exec snapd snapd-apparmor) GO_TAGS = nosecboot ifeq ($(with_testkeys),1) @@ -66,7 +66,7 @@ endif all: $(go_binaries) $(builddir)/snap: GO_TAGS += nomanagers -$(builddir)/snap $(builddir)/snap-seccomp: +$(builddir)/snap $(builddir)/snap-seccomp $(builddir)/snapd-apparmor: go build -o $@ $(if $(GO_TAGS),-tags "$(GO_TAGS)") \ -buildmode=pie -ldflags=-w -mod=vendor \ $(import_path)/cmd/$(notdir $@) @@ -100,7 +100,7 @@ install:: $(builddir)/snap | $(DESTDIR)$(bindir) install -m 755 $^ $| # Install snapctl snapd, snap-{exec,update-ns,seccomp} into /usr/lib/snapd/ -install:: $(addprefix $(builddir)/,snapctl snapd snap-exec snap-update-ns snap-seccomp) | $(DESTDIR)$(libexecdir)/snapd +install:: $(addprefix $(builddir)/,snapctl snapd snap-exec snap-update-ns snap-seccomp snapd-apparmor) | $(DESTDIR)$(libexecdir)/snapd install -m 755 $^ $| # Ensure /usr/bin/snapctl is a symlink to /usr/lib/snapd/snapctl diff --git a/packaging/ubuntu-14.04/rules b/packaging/ubuntu-14.04/rules index c6797129e38..4cc674e47e5 100755 --- a/packaging/ubuntu-14.04/rules +++ b/packaging/ubuntu-14.04/rules @@ -215,7 +215,7 @@ override_dh_install: # On Ubuntu and Debian we don't need to install the apparmor helper service. rm $(CURDIR)/debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/snapd.apparmor.service - rm $(CURDIR)/debian/tmp/usr/lib/snapd/snapd-apparmor + rm $(CURDIR)/debian/tmp/usr/bin/snapd-apparmor dh_install diff --git a/packaging/ubuntu-16.04/snapd.install b/packaging/ubuntu-16.04/snapd.install index f62d4ba1661..13144f2441c 100644 --- a/packaging/ubuntu-16.04/snapd.install +++ b/packaging/ubuntu-16.04/snapd.install @@ -11,6 +11,7 @@ usr/bin/snap-bootstrap /usr/lib/snapd/ usr/bin/snap-preseed /usr/lib/snapd/ usr/bin/snap-recovery-chooser /usr/lib/snapd/ usr/bin/snap-fde-keymgr /usr/lib/snapd/ +usr/bin/snapd-apparmor /usr/lib/snapd/ # bash completion data/completion/bash/snap /usr/share/bash-completion/completions @@ -49,6 +50,3 @@ c-vendor/squashfuse/snapfuse usr/bin usr/lib/systemd/system-environment-generators # but system generators end up in lib lib/systemd/system-generators - -# service for loading apparmor profiles for snap applications -usr/lib/snapd/snapd-apparmor From 7f907f12cda9d61b3065a09c009c99a401094944 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Mon, 29 Nov 2021 15:56:24 +1030 Subject: [PATCH 003/153] tests/main/snapd-apparmor: Add spread test for snapd-apparmor Test snapd-apparmor by installing a snap, stopping snapd then manually unloading the snap's apparmor profiles and then restarting snapd-apparmor and checking the profiles were reloaded. Signed-off-by: Alex Murray --- tests/main/snapd-apparmor/task.yaml | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/main/snapd-apparmor/task.yaml diff --git a/tests/main/snapd-apparmor/task.yaml b/tests/main/snapd-apparmor/task.yaml new file mode 100644 index 00000000000..d9336f14ae5 --- /dev/null +++ b/tests/main/snapd-apparmor/task.yaml @@ -0,0 +1,55 @@ +summary: Ensure snapd-apparmor works as expected + +environment: + CONSUMER_SNAP: test-snapd-policy-app-consumer + +debug: | + journalctl -u snap.apparmor.service + +execute: | + if ! systemctl is-active snapd.apparmor.service; then + echo "Skipping test since snapd.apparmor.service is not active" + exit 0 + fi + + echo "Ensure snapd.apparmor is enabled" + systemctl is-enabled snapd.apparmor.service + + # install a test snap which generates a lot of apparmor policies + echo "Given a test snap is installed" + "$TESTSTOOLS"/snaps-state install-local "$CONSUMER_SNAP" + tests.cleanup defer snap remove --purge "$CONSUMER_SNAP" + + # stop snapd so it does not try and load apparmor policies directly + echo "And snapd is stopped" + systemctl stop snapd.service + tests.cleanup defer systemctl restart snapd.service + + # get the current set of profiles but ignore any which may have gotten + # created on-the-fly by apparmor for namespaced commands - these + # contain / - as these won't get automatically recreated by + # snapd.apparmor + grep -v / /sys/kernel/security/apparmor/profiles | sort | cut -f1 -d" " > profiles.txt + + # manually unload all profiles defined by snap apparmor policies + echo "If we unload existing snap apparmor policy" + for p in /var/lib/snapd/apparmor/profiles/*; do + while IFS= read -r profile; do + echo "Unloading $profile..." + echo -n "$profile" > /sys/kernel/security/apparmor/.remove + # check it is now unloaded + NOMATCH "^$profile " /sys/kernel/security/apparmor/profiles + done < <(grep ^profile < "$p" | cut -f2 -d" " | sed s/'"'//g) + done + + # restart snapd.apparmor service to reload profiles + echo "And restart snapd.apparmor.service" + systemctl restart snapd.apparmor.service + + # get the set of profiles which now exist + grep -v / /sys/kernel/security/apparmor/profiles | sort | cut -f1 -d" " > reloaded_profiles.txt + + # and check there is no difference (ie. that snapd-apparmor reloaded + # all profiles as expected) + echo "Then profiles should have reloaded successfully..." + diff -u profiles.txt reloaded_profiles.txt From 463969e48aadd4e74708d34290205d135a12134f Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Tue, 30 Nov 2021 15:01:36 +1030 Subject: [PATCH 004/153] cmd/snapd-apparmor: Add support for SNAP_REXEC This should ensure that whether snapd-apparmor is executed from the snapd snap or not it should still use the vendored apparmor if present. Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index 878510647f9..a49c8040002 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -45,6 +45,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/logger" apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor" + "github.com/snapcore/snapd/snapdtool" ) // Checks to see if the current container is capable of having internal AppArmor @@ -129,6 +130,8 @@ func loadAppArmorProfiles() error { } func main() { + snapdtool.ExecInSnapdOrCoreSnap() + if len(os.Args) != 2 || os.Args[1] != "start" { fmt.Fprintln(os.Stderr, "Expected to be called with 'start' argument.") os.Exit(1) From 00bc84b75a24206132fcb7606302c84864cbb8fd Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Tue, 30 Nov 2021 15:03:37 +1030 Subject: [PATCH 005/153] tests/main/snapd-apparmor: Test snapd-apparmor fails when expected snapd.apparmor.service should fail when snapd-apparmor fails to compile AppArmor policies - add a test for this to ensure it works / fails as expected. Signed-off-by: Alex Murray --- tests/main/snapd-apparmor/task.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/main/snapd-apparmor/task.yaml b/tests/main/snapd-apparmor/task.yaml index d9336f14ae5..65393c97580 100644 --- a/tests/main/snapd-apparmor/task.yaml +++ b/tests/main/snapd-apparmor/task.yaml @@ -42,6 +42,11 @@ execute: | done < <(grep ^profile < "$p" | cut -f2 -d" " | sed s/'"'//g) done + # ensure we are actually testing something - ie snapd.apparmor will + # actually have to do some work + grep -v / /sys/kernel/security/apparmor/profiles | sort | cut -f1 -d" " > unloaded_profiles.txt + diff -u profiles.txt unloaded_profiles.txt && exit 1 + # restart snapd.apparmor service to reload profiles echo "And restart snapd.apparmor.service" systemctl restart snapd.apparmor.service @@ -53,3 +58,16 @@ execute: | # all profiles as expected) echo "Then profiles should have reloaded successfully..." diff -u profiles.txt reloaded_profiles.txt + + # also check that snapd.apparmor.service fails when a profile is invalid + sed -i s/profile/profileinvalidnametobereplaced/ /var/lib/snapd/apparmor/profiles/snap.$CONSUMER_SNAP.* + tests.cleanup defer sed -i s/profileinvalidnametobereplaced/profile/ /var/lib/snapd/apparmor/profiles/snap.$CONSUMER_SNAP.* + + systemctl restart snapd.apparmor.service && exit 1 + systemctl status snapd.apparmor.service && exit 1 + + # fixup the profiles again + sed -i s/profileinvalidnametobereplaced/profile/ /var/lib/snapd/apparmor/profiles/snap.$CONSUMER_SNAP.* + # and restart + systemctl restart snapd.apparmor.service + From 2095db4fb4ba4b62112cb3a5d023b331f943a073 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 1 Dec 2021 15:47:07 +1030 Subject: [PATCH 006/153] cmd/snapd-apparmor: Address feedback from PR #11096 Signed-off-by: Alex Murray --- cmd/snapd-apparmor/export_test.go | 1 + cmd/snapd-apparmor/main.go | 13 +++++--- cmd/snapd-apparmor/main_test.go | 55 ++++++++++++++++++------------- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/cmd/snapd-apparmor/export_test.go b/cmd/snapd-apparmor/export_test.go index 9b637bb8437..8c2722a1fc6 100644 --- a/cmd/snapd-apparmor/export_test.go +++ b/cmd/snapd-apparmor/export_test.go @@ -20,6 +20,7 @@ package main var ( + IsContainer = isContainer IsContainerWithInternalPolicy = isContainerWithInternalPolicy LoadAppArmorProfiles = loadAppArmorProfiles ) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index a49c8040002..a03df388d67 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -17,17 +17,17 @@ * */ -// This script is provided for integration with systemd on distributions where +// This tool is provided for integration with systemd on distributions where // apparmor profiles generated and managed by snapd are not loaded by the // system-wide apparmor systemd integration on early boot-up. // // Only the start operation is provided as all other activity is managed by // snapd as a part of the life-cycle of particular snaps. // -// In addition the script assumes that the system-wide apparmor service has +// In addition this tool assumes that the system-wide apparmor service has // already executed, initializing apparmor file-systems as necessary. // -// NOTE: This script ignores failures in some scenarios as the intent is to +// NOTE: This tool ignores failures in some scenarios as the intent is to // simply load application profiles ahead of time, as many as we can (for // performance reasons), even if for whatever reason some of those fail. @@ -129,6 +129,10 @@ func loadAppArmorProfiles() error { return nil } +func isContainer() error { + return exec.Command("systemd-detect-virt", "--quiet", "--container").Run() +} + func main() { snapdtool.ExecInSnapdOrCoreSnap() @@ -136,7 +140,7 @@ func main() { fmt.Fprintln(os.Stderr, "Expected to be called with 'start' argument.") os.Exit(1) } - if err := exec.Command("systemd-detect-virt", "--quiet", "--container").Run(); err == nil { + if err := isContainer(); err == nil { logger.Debugf("inside container environment") // in container environment - see if container has own // policy that we need to manage otherwise get out of the @@ -152,5 +156,4 @@ func main() { fmt.Fprintln(os.Stderr, err) os.Exit(2) } - os.Exit(0) } diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index 3133dbc162c..516c4a2f6ae 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -54,9 +54,8 @@ func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/") - if err := os.MkdirAll(appArmorSecurityFSPath, 0755); err != nil { - panic(err) - } + err := os.MkdirAll(appArmorSecurityFSPath, 0755) + c.Assert(err, IsNil) c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) @@ -65,17 +64,13 @@ func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) f, err := os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_stacked")) - if err != nil { - panic(err) - } + c.Assert(err, IsNil) f.WriteString("yes") f.Close() c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) f, err = os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_name")) - if err != nil { - panic(err) - } + c.Assert(err, IsNil) defer f.Close() c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) @@ -88,35 +83,51 @@ func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { } func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { - testutil.MockCommand(c, "apparmor_parser", "") + parserCmd := testutil.MockCommand(c, "apparmor_parser", "") + defer parserCmd.Restore() err := snapd_apparmor.LoadAppArmorProfiles() c.Assert(err, IsNil) // mock a profile - if err = os.MkdirAll(dirs.SnapAppArmorDir, 0755); err != nil { - panic(err) - } + err = os.MkdirAll(dirs.SnapAppArmorDir, 0755) + c.Assert(err, IsNil) profile := filepath.Join(dirs.SnapAppArmorDir, "foo") f, err := os.Create(profile) - if err != nil { - panic(err) - } + c.Assert(err, IsNil) f.Close() err = snapd_apparmor.LoadAppArmorProfiles() c.Assert(err, IsNil) - // catch unexpected changes to apparmor_parser compiler flags etc - testutil.MockCommand(c, "apparmor_parser", `echo "$@"; ""exit "$#"`) + // check arguments to the parser are as expected + c.Assert(parserCmd.Calls(), DeepEquals, [][]string{ + {"apparmor_parser", "--replace", "--write-cache", + "-O", "no-expr-simplify", + fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", dirs.GlobalRootDir), + "--quiet", profile}}) + + // test error case + testutil.MockCommand(c, "apparmor_parser", "exit 1") err = snapd_apparmor.LoadAppArmorProfiles() - c.Check(err.Error(), Equals, fmt.Sprintf("cannot load apparmor profiles: exit status 7\napparmor_parser output:\n--replace --write-cache -O no-expr-simplify --cache-loc=%s/var/cache/apparmor --quiet %s\n", dirs.GlobalRootDir, profile)) + c.Check(err.Error(), Equals, fmt.Sprintf("cannot load apparmor profiles: exit status 1\napparmor_parser output:\n")) // rename so file is ignored err = os.Rename(profile, profile+"~") - if err != nil { - panic(err) - } + c.Assert(err, IsNil) err = snapd_apparmor.LoadAppArmorProfiles() c.Assert(err, IsNil) } + +func (s *mainSuite) TestIsContainer(c *C) { + err := snapd_apparmor.IsContainer() + c.Check(err.Error(), Equals, fmt.Sprintf("exit status 1")) + + detectCmd := testutil.MockCommand(c, "systemd-detect-virt", "") + defer detectCmd.Restore() + + err = snapd_apparmor.IsContainer() + c.Assert(err, IsNil) + c.Assert(detectCmd.Calls(), DeepEquals, [][]string{ + {"systemd-detect-virt", "--quiet", "--container"}}) +} From d4e583a612543626b3a14801619a7706aa04ad52 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Mon, 6 Dec 2021 15:31:43 +1030 Subject: [PATCH 007/153] tests/main/snapd-apparmor: sort profiles as last step during test This means we only sort the minimum output and it makes the shell pipeline a little easier to grok. Signed-off-by: Alex Murray --- tests/main/snapd-apparmor/task.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/main/snapd-apparmor/task.yaml b/tests/main/snapd-apparmor/task.yaml index 65393c97580..c3289181742 100644 --- a/tests/main/snapd-apparmor/task.yaml +++ b/tests/main/snapd-apparmor/task.yaml @@ -29,7 +29,7 @@ execute: | # created on-the-fly by apparmor for namespaced commands - these # contain / - as these won't get automatically recreated by # snapd.apparmor - grep -v / /sys/kernel/security/apparmor/profiles | sort | cut -f1 -d" " > profiles.txt + grep -v / /sys/kernel/security/apparmor/profiles | cut -f1 -d" " | sort > profiles.txt # manually unload all profiles defined by snap apparmor policies echo "If we unload existing snap apparmor policy" @@ -44,7 +44,7 @@ execute: | # ensure we are actually testing something - ie snapd.apparmor will # actually have to do some work - grep -v / /sys/kernel/security/apparmor/profiles | sort | cut -f1 -d" " > unloaded_profiles.txt + grep -v / /sys/kernel/security/apparmor/profiles | cut -f1 -d" " | sort > unloaded_profiles.txt diff -u profiles.txt unloaded_profiles.txt && exit 1 # restart snapd.apparmor service to reload profiles @@ -52,7 +52,7 @@ execute: | systemctl restart snapd.apparmor.service # get the set of profiles which now exist - grep -v / /sys/kernel/security/apparmor/profiles | sort | cut -f1 -d" " > reloaded_profiles.txt + grep -v / /sys/kernel/security/apparmor/profiles | cut -f1 -d" " | sort > reloaded_profiles.txt # and check there is no difference (ie. that snapd-apparmor reloaded # all profiles as expected) From c54cc897361b70409936e1476d56288bf6b7b553 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Mon, 6 Dec 2021 15:33:03 +1030 Subject: [PATCH 008/153] cmd/snapd-apparmor: Refactor isContainer() to return a bool Since we don't actually use any possible error generated by exec.Command() it is easier to swallow it and just return a boolean instead. Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main.go | 6 +++--- cmd/snapd-apparmor/main_test.go | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index a03df388d67..f35af2f5a81 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -129,8 +129,8 @@ func loadAppArmorProfiles() error { return nil } -func isContainer() error { - return exec.Command("systemd-detect-virt", "--quiet", "--container").Run() +func isContainer() bool { + return (exec.Command("systemd-detect-virt", "--quiet", "--container").Run() == nil) } func main() { @@ -140,7 +140,7 @@ func main() { fmt.Fprintln(os.Stderr, "Expected to be called with 'start' argument.") os.Exit(1) } - if err := isContainer(); err == nil { + if isContainer() { logger.Debugf("inside container environment") // in container environment - see if container has own // policy that we need to manage otherwise get out of the diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index 516c4a2f6ae..d62467d5c02 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -120,14 +120,12 @@ func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { } func (s *mainSuite) TestIsContainer(c *C) { - err := snapd_apparmor.IsContainer() - c.Check(err.Error(), Equals, fmt.Sprintf("exit status 1")) + c.Check(snapd_apparmor.IsContainer(), Equals, false) detectCmd := testutil.MockCommand(c, "systemd-detect-virt", "") defer detectCmd.Restore() - err = snapd_apparmor.IsContainer() - c.Assert(err, IsNil) + c.Check(snapd_apparmor.IsContainer(), Equals, true) c.Assert(detectCmd.Calls(), DeepEquals, [][]string{ {"systemd-detect-virt", "--quiet", "--container"}}) } From de6d59928f9506f55d99b947bd809c0107a48151 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Fri, 10 Dec 2021 10:24:52 +1030 Subject: [PATCH 009/153] cmd/snapd-apparmor: Rename internal function to be more descriptive Since isContainerWithInternalPolicy() is LXD specific, name it as isContainerWithInternalLXDPolicy() as suggested by @bboozzoo. Signed-off-by: Alex Murray --- cmd/snapd-apparmor/export_test.go | 6 +++--- cmd/snapd-apparmor/main.go | 4 ++-- cmd/snapd-apparmor/main_test.go | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/snapd-apparmor/export_test.go b/cmd/snapd-apparmor/export_test.go index 8c2722a1fc6..d2bdb152ffe 100644 --- a/cmd/snapd-apparmor/export_test.go +++ b/cmd/snapd-apparmor/export_test.go @@ -20,7 +20,7 @@ package main var ( - IsContainer = isContainer - IsContainerWithInternalPolicy = isContainerWithInternalPolicy - LoadAppArmorProfiles = loadAppArmorProfiles + IsContainer = isContainer + IsContainerWithInternalLXDPolicy = isContainerWithInternalLXDPolicy + LoadAppArmorProfiles = loadAppArmorProfiles ) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index f35af2f5a81..c7de4c99515 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -67,7 +67,7 @@ import ( // process should continue without any loss of functionality. This is an // unsupported configuration that cannot be properly handled by this function. // -func isContainerWithInternalPolicy() bool { +func isContainerWithInternalLXDPolicy() bool { var appArmorSecurityFSPath = filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor") var nsStackedPath = filepath.Join(appArmorSecurityFSPath, ".ns_stacked") var nsNamePath = filepath.Join(appArmorSecurityFSPath, ".ns_name") @@ -145,7 +145,7 @@ func main() { // in container environment - see if container has own // policy that we need to manage otherwise get out of the // way - if !isContainerWithInternalPolicy() { + if !isContainerWithInternalLXDPolicy() { logger.Noticef("Inside container environment without internal policy") os.Exit(0) } diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index d62467d5c02..babba4449d0 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -51,35 +51,35 @@ func (s *mainSuite) TearDownTest(c *C) { } func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { - c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/") err := os.MkdirAll(appArmorSecurityFSPath, 0755) c.Assert(err, IsNil) - c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) // simulate being inside a container environment testutil.MockCommand(c, "systemd-detect-virt", "") - c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) f, err := os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_stacked")) c.Assert(err, IsNil) f.WriteString("yes") f.Close() - c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) f, err = os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_name")) c.Assert(err, IsNil) defer f.Close() - c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) f.WriteString("foo") - c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) // lxc/lxd name should result in a container with internal policy f.Seek(0, 0) f.WriteString("lxc-foo") - c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, true) + c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, true) } func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { From 0d9c2f4d8153721ebdb3e00bb4d35dddd788801e Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Thu, 24 Feb 2022 13:19:15 +1030 Subject: [PATCH 010/153] cmd/snapd-apparmor: Add support for WSL as in PR #11349 Signed-off-by: Alex Murray --- cmd/snapd-apparmor/export_test.go | 6 +++--- cmd/snapd-apparmor/main.go | 16 ++++++++++++++-- cmd/snapd-apparmor/main_test.go | 20 ++++++++++++-------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/cmd/snapd-apparmor/export_test.go b/cmd/snapd-apparmor/export_test.go index d2bdb152ffe..8c2722a1fc6 100644 --- a/cmd/snapd-apparmor/export_test.go +++ b/cmd/snapd-apparmor/export_test.go @@ -20,7 +20,7 @@ package main var ( - IsContainer = isContainer - IsContainerWithInternalLXDPolicy = isContainerWithInternalLXDPolicy - LoadAppArmorProfiles = loadAppArmorProfiles + IsContainer = isContainer + IsContainerWithInternalPolicy = isContainerWithInternalPolicy + LoadAppArmorProfiles = loadAppArmorProfiles ) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index c7de4c99515..2338ea6a9ad 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -48,6 +48,14 @@ import ( "github.com/snapcore/snapd/snapdtool" ) +func isWSL() bool { + if output, err := exec.Command("systemd-detect-virt", "--container").Output(); err == nil { + virt := strings.TrimSpace(string(output)) + return virt == "wsl" + } + return false +} + // Checks to see if the current container is capable of having internal AppArmor // profiles that should be loaded. // @@ -67,11 +75,15 @@ import ( // process should continue without any loss of functionality. This is an // unsupported configuration that cannot be properly handled by this function. // -func isContainerWithInternalLXDPolicy() bool { +func isContainerWithInternalPolicy() bool { var appArmorSecurityFSPath = filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor") var nsStackedPath = filepath.Join(appArmorSecurityFSPath, ".ns_stacked") var nsNamePath = filepath.Join(appArmorSecurityFSPath, ".ns_name") + if isWSL() { + return true + } + for _, path := range []string{nsStackedPath, nsNamePath} { if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { return false @@ -145,7 +157,7 @@ func main() { // in container environment - see if container has own // policy that we need to manage otherwise get out of the // way - if !isContainerWithInternalLXDPolicy() { + if !isContainerWithInternalPolicy() { logger.Noticef("Inside container environment without internal policy") os.Exit(0) } diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index babba4449d0..95bd7c4c221 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -51,35 +51,39 @@ func (s *mainSuite) TearDownTest(c *C) { } func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { - c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/") err := os.MkdirAll(appArmorSecurityFSPath, 0755) c.Assert(err, IsNil) - c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) + + // simulate being inside WSL + testutil.MockCommand(c, "systemd-detect-virt", "echo wsl") + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, true) // simulate being inside a container environment - testutil.MockCommand(c, "systemd-detect-virt", "") - c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) + testutil.MockCommand(c, "systemd-detect-virt", "echo lxc") + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) f, err := os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_stacked")) c.Assert(err, IsNil) f.WriteString("yes") f.Close() - c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) f, err = os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_name")) c.Assert(err, IsNil) defer f.Close() - c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) f.WriteString("foo") - c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, false) + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) // lxc/lxd name should result in a container with internal policy f.Seek(0, 0) f.WriteString("lxc-foo") - c.Assert(snapd_apparmor.IsContainerWithInternalLXDPolicy(), Equals, true) + c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, true) } func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { From 7506adc4ee5452af9ee0099fcd87c48a5dee13a2 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Tue, 12 Apr 2022 17:24:48 +0930 Subject: [PATCH 011/153] cmd/snapd-apparmor: Refactor main into run and test it Refactor main() into a run() and validateArgs() functions which we can then easily add unit tests for to ensure we have tests for as much of the logic in snapd-apparmor as possible. Signed-off-by: Alex Murray --- cmd/snapd-apparmor/export_test.go | 2 ++ cmd/snapd-apparmor/main.go | 27 ++++++++++++++----- cmd/snapd-apparmor/main_test.go | 43 +++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/cmd/snapd-apparmor/export_test.go b/cmd/snapd-apparmor/export_test.go index 8c2722a1fc6..bae0a0dbbfa 100644 --- a/cmd/snapd-apparmor/export_test.go +++ b/cmd/snapd-apparmor/export_test.go @@ -20,6 +20,8 @@ package main var ( + Run = run + ValidateArgs = validateArgs IsContainer = isContainer IsContainerWithInternalPolicy = isContainerWithInternalPolicy LoadAppArmorProfiles = loadAppArmorProfiles diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index 2338ea6a9ad..4f34c50457a 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -146,12 +146,19 @@ func isContainer() bool { } func main() { + if err := run(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} + +func run() error { snapdtool.ExecInSnapdOrCoreSnap() - if len(os.Args) != 2 || os.Args[1] != "start" { - fmt.Fprintln(os.Stderr, "Expected to be called with 'start' argument.") - os.Exit(1) + if err := validateArgs(os.Args[1:]); err != nil { + return err } + if isContainer() { logger.Debugf("inside container environment") // in container environment - see if container has own @@ -159,13 +166,21 @@ func main() { // way if !isContainerWithInternalPolicy() { logger.Noticef("Inside container environment without internal policy") - os.Exit(0) + return nil } } err := loadAppArmorProfiles() if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(2) + return err } + + return nil +} + +func validateArgs(args []string) error { + if len(args) != 1 || args[0] != "start" { + return errors.New("Expected to be called with a single 'start' argument.") + } + return nil } diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index 95bd7c4c221..c99ece5ec8c 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -91,6 +91,8 @@ func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { defer parserCmd.Restore() err := snapd_apparmor.LoadAppArmorProfiles() c.Assert(err, IsNil) + // since no profiles to load the parser should not have been called + c.Assert(parserCmd.Calls(), DeepEquals, [][]string(nil)) // mock a profile err = os.MkdirAll(dirs.SnapAppArmorDir, 0755) @@ -133,3 +135,44 @@ func (s *mainSuite) TestIsContainer(c *C) { c.Assert(detectCmd.Calls(), DeepEquals, [][]string{ {"systemd-detect-virt", "--quiet", "--container"}}) } + +func (s *mainSuite) TestValidateArgs(c *C) { + testCases := []struct { + args []string + errMsg string + }{ + { + args: []string{"start"}, + errMsg: "", + }, + { + args: []string{"foo"}, + errMsg: "Expected to be called with a single 'start' argument.", + }, + { + args: []string{"start", "foo"}, + errMsg: "Expected to be called with a single 'start' argument.", + }, + } + for _, tc := range testCases { + err := snapd_apparmor.ValidateArgs(tc.args) + if err != nil { + c.Check(err.Error(), Equals, tc.errMsg) + } else { + c.Check(tc.errMsg, Equals, "") + } + } +} + +func (s *mainSuite) TestRun(c *C) { + os.Args = []string{"snapd-apparmor", "start"} + err := snapd_apparmor.Run() + c.Assert(err, IsNil) + + // simulate being inside a container environment + detectCmd := testutil.MockCommand(c, "systemd-detect-virt", "echo wsl") + defer detectCmd.Restore() + + err = snapd_apparmor.Run() + c.Assert(err, IsNil) +} From 259362f45ca62085cc717a7baa0fc8faa0c37095 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Tue, 12 Apr 2022 20:52:49 +0930 Subject: [PATCH 012/153] cmd/snapd-apparmor: Set SNAPD_DEBUG before LoadAppArmorProfiles() The apparmor interface will only add --quiet when invoking apparmor_parser if SNAPD_DEBUG is *not* set. But since the unit tests may be run with or without SNAPD_DEBUG being set, we hard-code it to being set to ensure --quiet is not used regardless of what environment the unit tests are run under. Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index c99ece5ec8c..0d8b09aa043 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -103,6 +103,10 @@ func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { c.Assert(err, IsNil) f.Close() + // ensure SNAPD_DEBUG is set in the environment so then --quiet + // will *not* be included in the apparmor_parser arguments (since + // when these test are run in via CI SNAPD_DEBUG is set) + os.Setenv("SNAPD_DEBUG", "1") err = snapd_apparmor.LoadAppArmorProfiles() c.Assert(err, IsNil) @@ -111,7 +115,7 @@ func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { {"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s/var/cache/apparmor", dirs.GlobalRootDir), - "--quiet", profile}}) + profile}}) // test error case testutil.MockCommand(c, "apparmor_parser", "exit 1") From bb2af434538ae7ef7de91334da3332d99411a4ef Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Thu, 19 May 2022 15:25:05 +0200 Subject: [PATCH 013/153] release: 2.56 --- packaging/arch/PKGBUILD | 2 +- packaging/debian-sid/changelog | 280 +++++++++++++++++++++++++++++++ packaging/fedora/snapd.spec | 279 +++++++++++++++++++++++++++++- packaging/opensuse/snapd.changes | 5 + packaging/opensuse/snapd.spec | 2 +- packaging/ubuntu-14.04/changelog | 280 +++++++++++++++++++++++++++++++ packaging/ubuntu-16.04/changelog | 280 +++++++++++++++++++++++++++++++ 7 files changed, 1125 insertions(+), 3 deletions(-) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 9ec2199632b..8905fb72061 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -11,7 +11,7 @@ pkgdesc="Service and tools for management of snap packages." depends=('squashfs-tools' 'libseccomp' 'libsystemd' 'apparmor') optdepends=('bash-completion: bash completion support' 'xdg-desktop-portal: desktop integration') -pkgver=2.55.5 +pkgver=2.56 pkgrel=1 arch=('x86_64' 'i686' 'armv7h' 'aarch64') url="https://github.com/snapcore/snapd" diff --git a/packaging/debian-sid/changelog b/packaging/debian-sid/changelog index 62f03ac39a5..1cc8a70880e 100644 --- a/packaging/debian-sid/changelog +++ b/packaging/debian-sid/changelog @@ -1,3 +1,283 @@ +snapd (2.56-1) unstable; urgency=medium + + * New upstream release, LP: #1974147 + - portal-info: Add CommonID Field + - asserts/info,mkversion.sh: capture max assertion formats in + snapd/info + - tests: improve the unit testing workflow to run in parallel + - interfaces: allow map and execute permissions for files on + removable media + - tests: add spread test to verify that connections are preserved if + snap refresh fails + - tests: Apparmor sandbox profile mocking + - cmd/snap-fde-keymgr: support for multiple devices and + authorizations for add/remove recovery key + - cmd/snap-bootstrap: Listen to keyboard added after start and + handle switch root + - interfaces,overlord: add support for adding extra mount layouts + - cmd/snap: replace existing code for 'snap model' to use shared + code in clientutil (2/3) + - interfaces: fix opengl interface on RISC-V + - interfaces: allow access to the file locking for cryptosetup in + the dm-crypt interface + - interfaces: network-manager: add AppArmor rule for configuring + bridges + - i/b/hardware-observe.go: add access to the thermal sysfs + - interfaces: opengl: add rules for NXP i.MX GPU drivers + - i/b/mount_control: add an optional "/" to the mount target rule + - snap/quota: add values for journal quotas (journal quota 2/n) + - tests: spread test for uc20 preseeding covering snap prepare-image + - o/snapstate: remove deadcode breaking static checks + - secboot/keymgr: extend unit tests, add helper for identify keyslot + used error + - tests: use new snaps.name and snaps.cleanup tools + - interfaces: tweak getPath() slightly and add some more tests + - tests: update snapd testing tools + - client/clientutil: add shared code for printing model assertions + as yaml or json (1/3) + - debug-tools: list all snaps + - cmd/snap: join search terms passed in the command line + - osutil/disks: partition UUID lookup + - o/snapshotstate: refactor snapshot read/write logic + - interfaces: Allow locking in block-devices + - daemon: /v2/system-recovery-keys remove API + - snapstate: do not auto-migrate to ~/Snap for core22 just yet + - tests: run failed tests by default + - o/snapshotstate: check installed snaps before running 'save' tasks + - secboot/keymgr: remove recovery key, authorize with existing key + - deps: bump libseccomp to include build fixes, run unit tests using + CC=clang + - cmd/snap-seccomp: only compare the bottom 32-bits of the flags arg + of copy_file_range + - osutil/disks: helper for obtaining the UUID of a partition which + is a mount point source + - image/preseed: umount the base snap last after writable paths + - tests: new set of nested tests for uc22 + - tests: run failed tests on nested suite + - interfaces: posix-mq: add new interface + - tests/main/user-session-env: remove openSUSE-specific tweaks + - tests: skip external backend in mem-cgroup-disabled test + - snap/quota: change the journal quota period to be a time.Duration + - interfaces/apparmor: allow executing /usr/bin/numfmt in the base + template + - tests: add lz4 dependency for jammy to avoid issues repacking + kernel + - snap-bootstrap, o/devicestate: use seed parallelism + - cmd/snap-update-ns: correctly set sticky bit on created + directories where applicable + - tests: install snapd while restoring in snap-mgmt + - .github: skip misspell and ineffassign on go 1.13 + - many: use UC20+/pre-UC20 in user messages as needed + - o/devicestate: use snap handler for copying and checksuming + preseeded snaps + - image, cmd/snap-preseed: allow passing custom apparmor features + path + - o/assertstate: fix handling of validation set tracking update in + enforcing mode + - packaging: restart our units only after the upgrade + - interfaces: add a steam-support interface + - gadget/install, o/devicestate: do not create recovery and + reinstall keys during installation + - many: move recovery key responsibility to devicestate/secboot, + prepare for a future with just optional recovery key + - tests: do not run mem-cgroup-disabled on external backends + - snap: implement "star" developers + - o/devicestate: fix install tests on systems with + /var/lib/snapd/snap + - cmd/snap-fde-keymgr, secboot: followup cleanups + - seed: let SnapHandler provided a different final path for snaps + - o/devicestate: implement maybeApplyPreseededData function to apply + preseed artifact + - tests/lib/tools: add piboot to boot_path() + - interfaces/builtin: shared-memory drop plugs allow-installation: + true + - tests/main/user-session-env: for for opensuse + - cmd/snap-fde-keymgr, secboot: add a tiny FDE key manager + - tests: re-execute the failed tests when "Run failed" label is set + in the PR + - interfaces/builtin/custom-device: fix unit tests on hosts with + different libexecdir + - sandbox: move profile load/unload to sandbox/apparmor + - cmd/snap: handler call verifications for cmd_quota_tests + - secboot/keys: introduce a package for secboot key types, use the + package throughout the code base + - snap/quota: add journal quotas to resources.go + - many: let provide a SnapHandler to Seed.Load*Meta* + - osutil: allow setting desired mtime on the AtomicFile, preserve + mtime on copy + - systemd: add systemd.Run() wrapper for systemd-run + - tests: test fresh install of core22-based snap (#11696) + - tests: initial set of tests to uc22 nested execution + - o/snapstate: migration overwrites existing snap dir + - tests: fix interfaces-location-control tests leaking provider.py + process + - tests/nested: fix custom-device test + - tests: test migration w/ revert, refresh and XDG dir creation + - asserts,store: complete support for optional primary key headers + for assertions + - seed: support parallelism when loading/verifying snap metadata + - image/preseed, cmd/snap-preseed: create and sign preseed assertion + - tests: Initial changes to run nested tests on uc22 + - o/snapstate: fix TestSnapdRefreshTasks test after two r-a-a PRs + - interfaces: add ACRN hypervisor support + - o/snapstate: exclude TypeSnapd and TypeOS snaps from refresh-app- + awareness + - features: enable refresh-app-awareness by default + - libsnap-confine-private: show proper error when aa_change_onexec() + fails + - i/apparmor: remove leftover comment + - gadget: drop unused code in unit tests + - image, store: move ToolingStore to store/tooling package + - HACKING: update info for snapcraft remote build + - seed: return all essential snaps found if no types are given to + LoadEssentialMeta + - i/b/custom_device: fix generation of udev rules + - tests/nested/manual/core20-early-config: disable netplan checks + - bootloader/assets, tests: add factory-reset mode, test non- + encrypted factory-reset + - interfaces/modem-manager: add support for Cinterion modules + - gadget: fully support multi-volume gadget asset updates in + Update() on UC20+ + - i/b/content: use slot.Lookup() as suggested by TODO comment + - tests: install linux-tools-gcp on jammy to avoid bpftool + dependency error + - tests/main: add spread tests for new cpu and thread quotas + - snap-debug-info: print validation sets and validation set + assertions + - many: renaming related to inclusive language part 2 + - c/snap-seccomp: update syscalls to match libseccomp 2657109 + - github: cancel workflows when pushing to pull request branches + - .github: use reviewdog action from woke tool + - interfaces/system-packages-doc: allow read-only access to + /usr/share/gtk-doc + - interfaces: add max_map_count to system-observe + - o/snapstate: print pids of running processes on BusySnapError + - .github: run woke tool on PR's + - snapshots: follow-up on exclusions PR + - cmd/snap: add check switch for snap debug state + - tests: do not run mount-order-regression test on i386 + - interfaces/system-packages-doc: allow read-only access to + /usr/share/xubuntu-docs + - interfaces/hardware_observe: add read access for various devices + - packaging: use latest go to build spread + - tests: Enable more tests for UC22 + - interfaces/builtin/network-control: also allow for mstp and bchat + devices too + - interfaces/builtin: update apparmor profile to allow creating + mimic over /usr/share* + - data/selinux: allow snap-update-ns to mount on top of /var/snap + inside the mount ns + - interfaces/cpu-control: fix apparmor rules of paths with CPU ID + - tests: remove the file that configures nm as default + - tests: fix the change done for netplan-cfg test + - tests: disable netplan-cfg test + - cmd/snap-update-ns: apply content mounts before layouts + - overlord/state: add a helper to detect cyclic dependencies between + tasks in change + - packaging/ubuntu-16.04/control: recommend `fuse3 | fuse` + - many: change "transactional" flag to a "transaction" option + - b/piboot.go: check EEPROM version for RPi4 + - snap/quota,spread: raise lower memory quota limit to 640kb + - boot,bootloader: add missing grub.cfg assets mocks in some tests + - many: support --ignore-running with refresh many + - tests: skip the test interfaces-many-snap-provided in + trusty + - o/snapstate: rename XDG dirs during HOME migration + - cmd/snap,wrappers: fix wrong implementation of zero count cpu + quota + - i/b/kernel_module_load: expand $SNAP_COMMON in module options + - interfaces/u2f-devices: add Solo V2 + - overlord: add missing grub.cfg assets mocks in manager_tests.go + - asserts: extend optional primary keys support to the in-memory + backend + - tests: update the lxd-no-fuse test + - many: fix failing golangci checks + - seed,many: allow to limit LoadMeta to snaps of a precise mode + - tests: allow ubuntu-image to be built with a compatible snapd tree + - o/snapstate: account for repeat migration in ~/Snap undo + - asserts: start supporting optional primary keys in fs backend, + assemble and signing + - b/a: do not set console in kernel command line for arm64 + - tests/main/snap-quota-groups: fix spread test + - sandbox,quota: ensure cgroup is available when creating mem + quotas + - tests: add debug output what keeps `/home` busy + - sanity: rename "sanity.Check" to "syscheck.CheckSystem" + - interfaces: add pkcs11 interface + - o/snapstate: undo migration on 'snap revert' + - overlord: snapshot exclusions + - interfaces: add private /dev/shm support to shared-memory + interface + - gadget/install: implement factory reset for unencrypted system + - packaging: install Go snap from 1.17 channel in the integration + tests + - snap-exec: fix detection if `cups` interface is connected + - tests: extend gadget-config-defaults test with refresh.retain + - cmd/snap,strutil: move lineWrap to WordWrapPadded + - bootloader/piboot: add support for armhf + - snap,wrappers: add `sigint{,-all}` to supported stop-modes + - packaging/ubuntu-16.04/control: depend on fuse3 | fuse + - interfaces/system-packages-doc: allow read-only access to + /usr/share/libreoffice/help + - daemon: add a /v2/accessories/changes/{ID} endpoint + - interfaces/appstream-metadata: Re-create app-info links to + swcatalog + - debug-tools: add script to help debugging GCE instances which fail + to boot + - gadget/install, kernel: more ICE helpers/support + - asserts: exclude empty snap id from duplicates lookup with preseed + assert + - cmd/snap, signtool: move key-manager related helpers to signtool + package + - tests/main/snap-quota-groups: add 219 as possible exit code + - store: set validation-sets on actions when refreshing + - github/workflows: update golangci-lint version + - run-check: use go install instead of go get + - tests: set as manual the interfaces-cups-control test + - interfaces/appstream-metadata: Support new swcatalog directory + names + - image/preseed: migrate tests from cmd/snap-preseed + - tests/main/uc20-create-partitions: update the test for new Go + versions + - strutil: move wrapGeneric function to strutil as WordWrap + - many: small inconsequential tweaks + - quota: detect/error if cpu-set is used with cgroup v1 + - tests: moving ubuntu-image to candidate to fix uc16 tests + - image: integrate UC20 preseeding with image.Prepare + - cmd/snap,client: frontend for cpu/thread quotas + - quota: add test for `Resource.clone()` + - many: replace use of "sanity" with more inclusive naming (part 2) + - tests: switch to "test-snapd-swtpm" + - i/b/network-manager: split rule with more than one peers + - tests: fix restore of the BUILD_DIR in failover test on uc18 + - cmd/snap/debug: sort changes by their spawn times + - asserts,interfaces/policy: slot-snap-id allow-installation + constraints + - o/devicestate: factory reset mode, no encryption + - debug-tools/snap-debug-info.sh: print message if no gadget snap + found + - overlord/devicestate: install system cleanups + - cmd/snap-bootstrap: support booting into factory-reset mode + - o/snapstate, ifacestate: pass preseeding flag to + AddSnapdSnapServices + - o/devicestate: restore device key and serial when assertion is + found + - data: add static preseed.json file + - sandbox: improve error message from `ProbeCgroupVersion()` + - tests: fix the nested remodel tests + - quota: add some more unit tests around Resource.Change() + - debug-tools/snap-debug-info.sh: add debug script + - tests: workaround lxd issue lp:10079 (function not implemented) on + prep-snapd-in-lxd + - osutil/disks: blockdev need not be available in the PATH + - cmd/snap-preseed: address deadcode linter + - tests/lib/fakestore/store: return snap base in details + - tests/lib/nested.sh: rm core18 snap after download + - systemd: do not reload system when enabling/disabling services + - i/b/kubernetes_support: add access to Java certificates + + -- Michael Vogt Thu, 19 May 2022 09:57:33 +0200 + snapd (2.55.5-1) unstable; urgency=medium * New upstream release, LP: #1965808 diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec index c6cba375fbf..7c7820af645 100644 --- a/packaging/fedora/snapd.spec +++ b/packaging/fedora/snapd.spec @@ -102,7 +102,7 @@ %endif Name: snapd -Version: 2.55.5 +Version: 2.56 Release: 0%{?dist} Summary: A transactional software package manager License: GPLv3 @@ -981,6 +981,283 @@ fi %changelog +* Thu May 19 2022 Michael Vogt +- New upstream release 2.56 + - portal-info: Add CommonID Field + - asserts/info,mkversion.sh: capture max assertion formats in + snapd/info + - tests: improve the unit testing workflow to run in parallel + - interfaces: allow map and execute permissions for files on + removable media + - tests: add spread test to verify that connections are preserved if + snap refresh fails + - tests: Apparmor sandbox profile mocking + - cmd/snap-fde-keymgr: support for multiple devices and + authorizations for add/remove recovery key + - cmd/snap-bootstrap: Listen to keyboard added after start and + handle switch root + - interfaces,overlord: add support for adding extra mount layouts + - cmd/snap: replace existing code for 'snap model' to use shared + code in clientutil (2/3) + - interfaces: fix opengl interface on RISC-V + - interfaces: allow access to the file locking for cryptosetup in + the dm-crypt interface + - interfaces: network-manager: add AppArmor rule for configuring + bridges + - i/b/hardware-observe.go: add access to the thermal sysfs + - interfaces: opengl: add rules for NXP i.MX GPU drivers + - i/b/mount_control: add an optional "/" to the mount target rule + - snap/quota: add values for journal quotas (journal quota 2/n) + - tests: spread test for uc20 preseeding covering snap prepare-image + - o/snapstate: remove deadcode breaking static checks + - secboot/keymgr: extend unit tests, add helper for identify keyslot + used error + - tests: use new snaps.name and snaps.cleanup tools + - interfaces: tweak getPath() slightly and add some more tests + - tests: update snapd testing tools + - client/clientutil: add shared code for printing model assertions + as yaml or json (1/3) + - debug-tools: list all snaps + - cmd/snap: join search terms passed in the command line + - osutil/disks: partition UUID lookup + - o/snapshotstate: refactor snapshot read/write logic + - interfaces: Allow locking in block-devices + - daemon: /v2/system-recovery-keys remove API + - snapstate: do not auto-migrate to ~/Snap for core22 just yet + - tests: run failed tests by default + - o/snapshotstate: check installed snaps before running 'save' tasks + - secboot/keymgr: remove recovery key, authorize with existing key + - deps: bump libseccomp to include build fixes, run unit tests using + CC=clang + - cmd/snap-seccomp: only compare the bottom 32-bits of the flags arg + of copy_file_range + - osutil/disks: helper for obtaining the UUID of a partition which + is a mount point source + - image/preseed: umount the base snap last after writable paths + - tests: new set of nested tests for uc22 + - tests: run failed tests on nested suite + - interfaces: posix-mq: add new interface + - tests/main/user-session-env: remove openSUSE-specific tweaks + - tests: skip external backend in mem-cgroup-disabled test + - snap/quota: change the journal quota period to be a time.Duration + - interfaces/apparmor: allow executing /usr/bin/numfmt in the base + template + - tests: add lz4 dependency for jammy to avoid issues repacking + kernel + - snap-bootstrap, o/devicestate: use seed parallelism + - cmd/snap-update-ns: correctly set sticky bit on created + directories where applicable + - tests: install snapd while restoring in snap-mgmt + - .github: skip misspell and ineffassign on go 1.13 + - many: use UC20+/pre-UC20 in user messages as needed + - o/devicestate: use snap handler for copying and checksuming + preseeded snaps + - image, cmd/snap-preseed: allow passing custom apparmor features + path + - o/assertstate: fix handling of validation set tracking update in + enforcing mode + - packaging: restart our units only after the upgrade + - interfaces: add a steam-support interface + - gadget/install, o/devicestate: do not create recovery and + reinstall keys during installation + - many: move recovery key responsibility to devicestate/secboot, + prepare for a future with just optional recovery key + - tests: do not run mem-cgroup-disabled on external backends + - snap: implement "star" developers + - o/devicestate: fix install tests on systems with + /var/lib/snapd/snap + - cmd/snap-fde-keymgr, secboot: followup cleanups + - seed: let SnapHandler provided a different final path for snaps + - o/devicestate: implement maybeApplyPreseededData function to apply + preseed artifact + - tests/lib/tools: add piboot to boot_path() + - interfaces/builtin: shared-memory drop plugs allow-installation: + true + - tests/main/user-session-env: for for opensuse + - cmd/snap-fde-keymgr, secboot: add a tiny FDE key manager + - tests: re-execute the failed tests when "Run failed" label is set + in the PR + - interfaces/builtin/custom-device: fix unit tests on hosts with + different libexecdir + - sandbox: move profile load/unload to sandbox/apparmor + - cmd/snap: handler call verifications for cmd_quota_tests + - secboot/keys: introduce a package for secboot key types, use the + package throughout the code base + - snap/quota: add journal quotas to resources.go + - many: let provide a SnapHandler to Seed.Load*Meta* + - osutil: allow setting desired mtime on the AtomicFile, preserve + mtime on copy + - systemd: add systemd.Run() wrapper for systemd-run + - tests: test fresh install of core22-based snap (#11696) + - tests: initial set of tests to uc22 nested execution + - o/snapstate: migration overwrites existing snap dir + - tests: fix interfaces-location-control tests leaking provider.py + process + - tests/nested: fix custom-device test + - tests: test migration w/ revert, refresh and XDG dir creation + - asserts,store: complete support for optional primary key headers + for assertions + - seed: support parallelism when loading/verifying snap metadata + - image/preseed, cmd/snap-preseed: create and sign preseed assertion + - tests: Initial changes to run nested tests on uc22 + - o/snapstate: fix TestSnapdRefreshTasks test after two r-a-a PRs + - interfaces: add ACRN hypervisor support + - o/snapstate: exclude TypeSnapd and TypeOS snaps from refresh-app- + awareness + - features: enable refresh-app-awareness by default + - libsnap-confine-private: show proper error when aa_change_onexec() + fails + - i/apparmor: remove leftover comment + - gadget: drop unused code in unit tests + - image, store: move ToolingStore to store/tooling package + - HACKING: update info for snapcraft remote build + - seed: return all essential snaps found if no types are given to + LoadEssentialMeta + - i/b/custom_device: fix generation of udev rules + - tests/nested/manual/core20-early-config: disable netplan checks + - bootloader/assets, tests: add factory-reset mode, test non- + encrypted factory-reset + - interfaces/modem-manager: add support for Cinterion modules + - gadget: fully support multi-volume gadget asset updates in + Update() on UC20+ + - i/b/content: use slot.Lookup() as suggested by TODO comment + - tests: install linux-tools-gcp on jammy to avoid bpftool + dependency error + - tests/main: add spread tests for new cpu and thread quotas + - snap-debug-info: print validation sets and validation set + assertions + - many: renaming related to inclusive language part 2 + - c/snap-seccomp: update syscalls to match libseccomp 2657109 + - github: cancel workflows when pushing to pull request branches + - .github: use reviewdog action from woke tool + - interfaces/system-packages-doc: allow read-only access to + /usr/share/gtk-doc + - interfaces: add max_map_count to system-observe + - o/snapstate: print pids of running processes on BusySnapError + - .github: run woke tool on PR's + - snapshots: follow-up on exclusions PR + - cmd/snap: add check switch for snap debug state + - tests: do not run mount-order-regression test on i386 + - interfaces/system-packages-doc: allow read-only access to + /usr/share/xubuntu-docs + - interfaces/hardware_observe: add read access for various devices + - packaging: use latest go to build spread + - tests: Enable more tests for UC22 + - interfaces/builtin/network-control: also allow for mstp and bchat + devices too + - interfaces/builtin: update apparmor profile to allow creating + mimic over /usr/share* + - data/selinux: allow snap-update-ns to mount on top of /var/snap + inside the mount ns + - interfaces/cpu-control: fix apparmor rules of paths with CPU ID + - tests: remove the file that configures nm as default + - tests: fix the change done for netplan-cfg test + - tests: disable netplan-cfg test + - cmd/snap-update-ns: apply content mounts before layouts + - overlord/state: add a helper to detect cyclic dependencies between + tasks in change + - packaging/ubuntu-16.04/control: recommend `fuse3 | fuse` + - many: change "transactional" flag to a "transaction" option + - b/piboot.go: check EEPROM version for RPi4 + - snap/quota,spread: raise lower memory quota limit to 640kb + - boot,bootloader: add missing grub.cfg assets mocks in some tests + - many: support --ignore-running with refresh many + - tests: skip the test interfaces-many-snap-provided in + trusty + - o/snapstate: rename XDG dirs during HOME migration + - cmd/snap,wrappers: fix wrong implementation of zero count cpu + quota + - i/b/kernel_module_load: expand $SNAP_COMMON in module options + - interfaces/u2f-devices: add Solo V2 + - overlord: add missing grub.cfg assets mocks in manager_tests.go + - asserts: extend optional primary keys support to the in-memory + backend + - tests: update the lxd-no-fuse test + - many: fix failing golangci checks + - seed,many: allow to limit LoadMeta to snaps of a precise mode + - tests: allow ubuntu-image to be built with a compatible snapd tree + - o/snapstate: account for repeat migration in ~/Snap undo + - asserts: start supporting optional primary keys in fs backend, + assemble and signing + - b/a: do not set console in kernel command line for arm64 + - tests/main/snap-quota-groups: fix spread test + - sandbox,quota: ensure cgroup is available when creating mem + quotas + - tests: add debug output what keeps `/home` busy + - sanity: rename "sanity.Check" to "syscheck.CheckSystem" + - interfaces: add pkcs11 interface + - o/snapstate: undo migration on 'snap revert' + - overlord: snapshot exclusions + - interfaces: add private /dev/shm support to shared-memory + interface + - gadget/install: implement factory reset for unencrypted system + - packaging: install Go snap from 1.17 channel in the integration + tests + - snap-exec: fix detection if `cups` interface is connected + - tests: extend gadget-config-defaults test with refresh.retain + - cmd/snap,strutil: move lineWrap to WordWrapPadded + - bootloader/piboot: add support for armhf + - snap,wrappers: add `sigint{,-all}` to supported stop-modes + - packaging/ubuntu-16.04/control: depend on fuse3 | fuse + - interfaces/system-packages-doc: allow read-only access to + /usr/share/libreoffice/help + - daemon: add a /v2/accessories/changes/{ID} endpoint + - interfaces/appstream-metadata: Re-create app-info links to + swcatalog + - debug-tools: add script to help debugging GCE instances which fail + to boot + - gadget/install, kernel: more ICE helpers/support + - asserts: exclude empty snap id from duplicates lookup with preseed + assert + - cmd/snap, signtool: move key-manager related helpers to signtool + package + - tests/main/snap-quota-groups: add 219 as possible exit code + - store: set validation-sets on actions when refreshing + - github/workflows: update golangci-lint version + - run-check: use go install instead of go get + - tests: set as manual the interfaces-cups-control test + - interfaces/appstream-metadata: Support new swcatalog directory + names + - image/preseed: migrate tests from cmd/snap-preseed + - tests/main/uc20-create-partitions: update the test for new Go + versions + - strutil: move wrapGeneric function to strutil as WordWrap + - many: small inconsequential tweaks + - quota: detect/error if cpu-set is used with cgroup v1 + - tests: moving ubuntu-image to candidate to fix uc16 tests + - image: integrate UC20 preseeding with image.Prepare + - cmd/snap,client: frontend for cpu/thread quotas + - quota: add test for `Resource.clone()` + - many: replace use of "sanity" with more inclusive naming (part 2) + - tests: switch to "test-snapd-swtpm" + - i/b/network-manager: split rule with more than one peers + - tests: fix restore of the BUILD_DIR in failover test on uc18 + - cmd/snap/debug: sort changes by their spawn times + - asserts,interfaces/policy: slot-snap-id allow-installation + constraints + - o/devicestate: factory reset mode, no encryption + - debug-tools/snap-debug-info.sh: print message if no gadget snap + found + - overlord/devicestate: install system cleanups + - cmd/snap-bootstrap: support booting into factory-reset mode + - o/snapstate, ifacestate: pass preseeding flag to + AddSnapdSnapServices + - o/devicestate: restore device key and serial when assertion is + found + - data: add static preseed.json file + - sandbox: improve error message from `ProbeCgroupVersion()` + - tests: fix the nested remodel tests + - quota: add some more unit tests around Resource.Change() + - debug-tools/snap-debug-info.sh: add debug script + - tests: workaround lxd issue lp:10079 (function not implemented) on + prep-snapd-in-lxd + - osutil/disks: blockdev need not be available in the PATH + - cmd/snap-preseed: address deadcode linter + - tests/lib/fakestore/store: return snap base in details + - tests/lib/nested.sh: rm core18 snap after download + - systemd: do not reload system when enabling/disabling services + - i/b/kubernetes_support: add access to Java certificates + * Wed May 11 2022 Michael Vogt - New upstream release 2.55.5 - snapstate: do not auto-migrate to ~/Snap for core22 just yet diff --git a/packaging/opensuse/snapd.changes b/packaging/opensuse/snapd.changes index 4be6177fb1a..6f0af82874d 100644 --- a/packaging/opensuse/snapd.changes +++ b/packaging/opensuse/snapd.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Thu May 19 07:57:33 UTC 2022 - michael.vogt@ubuntu.com + +- Update to upstream release 2.56 + ------------------------------------------------------------------- Wed May 11 04:38:24 UTC 2022 - michael.vogt@ubuntu.com diff --git a/packaging/opensuse/snapd.spec b/packaging/opensuse/snapd.spec index f04d1ae4185..db055bccfdf 100644 --- a/packaging/opensuse/snapd.spec +++ b/packaging/opensuse/snapd.spec @@ -81,7 +81,7 @@ Name: snapd -Version: 2.55.5 +Version: 2.56 Release: 0 Summary: Tools enabling systems to work with .snap files License: GPL-3.0 diff --git a/packaging/ubuntu-14.04/changelog b/packaging/ubuntu-14.04/changelog index 2aa31f6985e..00b9a039b9a 100644 --- a/packaging/ubuntu-14.04/changelog +++ b/packaging/ubuntu-14.04/changelog @@ -1,3 +1,283 @@ +snapd (2.56~14.04) trusty; urgency=medium + + * New upstream release, LP: #1974147 + - portal-info: Add CommonID Field + - asserts/info,mkversion.sh: capture max assertion formats in + snapd/info + - tests: improve the unit testing workflow to run in parallel + - interfaces: allow map and execute permissions for files on + removable media + - tests: add spread test to verify that connections are preserved if + snap refresh fails + - tests: Apparmor sandbox profile mocking + - cmd/snap-fde-keymgr: support for multiple devices and + authorizations for add/remove recovery key + - cmd/snap-bootstrap: Listen to keyboard added after start and + handle switch root + - interfaces,overlord: add support for adding extra mount layouts + - cmd/snap: replace existing code for 'snap model' to use shared + code in clientutil (2/3) + - interfaces: fix opengl interface on RISC-V + - interfaces: allow access to the file locking for cryptosetup in + the dm-crypt interface + - interfaces: network-manager: add AppArmor rule for configuring + bridges + - i/b/hardware-observe.go: add access to the thermal sysfs + - interfaces: opengl: add rules for NXP i.MX GPU drivers + - i/b/mount_control: add an optional "/" to the mount target rule + - snap/quota: add values for journal quotas (journal quota 2/n) + - tests: spread test for uc20 preseeding covering snap prepare-image + - o/snapstate: remove deadcode breaking static checks + - secboot/keymgr: extend unit tests, add helper for identify keyslot + used error + - tests: use new snaps.name and snaps.cleanup tools + - interfaces: tweak getPath() slightly and add some more tests + - tests: update snapd testing tools + - client/clientutil: add shared code for printing model assertions + as yaml or json (1/3) + - debug-tools: list all snaps + - cmd/snap: join search terms passed in the command line + - osutil/disks: partition UUID lookup + - o/snapshotstate: refactor snapshot read/write logic + - interfaces: Allow locking in block-devices + - daemon: /v2/system-recovery-keys remove API + - snapstate: do not auto-migrate to ~/Snap for core22 just yet + - tests: run failed tests by default + - o/snapshotstate: check installed snaps before running 'save' tasks + - secboot/keymgr: remove recovery key, authorize with existing key + - deps: bump libseccomp to include build fixes, run unit tests using + CC=clang + - cmd/snap-seccomp: only compare the bottom 32-bits of the flags arg + of copy_file_range + - osutil/disks: helper for obtaining the UUID of a partition which + is a mount point source + - image/preseed: umount the base snap last after writable paths + - tests: new set of nested tests for uc22 + - tests: run failed tests on nested suite + - interfaces: posix-mq: add new interface + - tests/main/user-session-env: remove openSUSE-specific tweaks + - tests: skip external backend in mem-cgroup-disabled test + - snap/quota: change the journal quota period to be a time.Duration + - interfaces/apparmor: allow executing /usr/bin/numfmt in the base + template + - tests: add lz4 dependency for jammy to avoid issues repacking + kernel + - snap-bootstrap, o/devicestate: use seed parallelism + - cmd/snap-update-ns: correctly set sticky bit on created + directories where applicable + - tests: install snapd while restoring in snap-mgmt + - .github: skip misspell and ineffassign on go 1.13 + - many: use UC20+/pre-UC20 in user messages as needed + - o/devicestate: use snap handler for copying and checksuming + preseeded snaps + - image, cmd/snap-preseed: allow passing custom apparmor features + path + - o/assertstate: fix handling of validation set tracking update in + enforcing mode + - packaging: restart our units only after the upgrade + - interfaces: add a steam-support interface + - gadget/install, o/devicestate: do not create recovery and + reinstall keys during installation + - many: move recovery key responsibility to devicestate/secboot, + prepare for a future with just optional recovery key + - tests: do not run mem-cgroup-disabled on external backends + - snap: implement "star" developers + - o/devicestate: fix install tests on systems with + /var/lib/snapd/snap + - cmd/snap-fde-keymgr, secboot: followup cleanups + - seed: let SnapHandler provided a different final path for snaps + - o/devicestate: implement maybeApplyPreseededData function to apply + preseed artifact + - tests/lib/tools: add piboot to boot_path() + - interfaces/builtin: shared-memory drop plugs allow-installation: + true + - tests/main/user-session-env: for for opensuse + - cmd/snap-fde-keymgr, secboot: add a tiny FDE key manager + - tests: re-execute the failed tests when "Run failed" label is set + in the PR + - interfaces/builtin/custom-device: fix unit tests on hosts with + different libexecdir + - sandbox: move profile load/unload to sandbox/apparmor + - cmd/snap: handler call verifications for cmd_quota_tests + - secboot/keys: introduce a package for secboot key types, use the + package throughout the code base + - snap/quota: add journal quotas to resources.go + - many: let provide a SnapHandler to Seed.Load*Meta* + - osutil: allow setting desired mtime on the AtomicFile, preserve + mtime on copy + - systemd: add systemd.Run() wrapper for systemd-run + - tests: test fresh install of core22-based snap (#11696) + - tests: initial set of tests to uc22 nested execution + - o/snapstate: migration overwrites existing snap dir + - tests: fix interfaces-location-control tests leaking provider.py + process + - tests/nested: fix custom-device test + - tests: test migration w/ revert, refresh and XDG dir creation + - asserts,store: complete support for optional primary key headers + for assertions + - seed: support parallelism when loading/verifying snap metadata + - image/preseed, cmd/snap-preseed: create and sign preseed assertion + - tests: Initial changes to run nested tests on uc22 + - o/snapstate: fix TestSnapdRefreshTasks test after two r-a-a PRs + - interfaces: add ACRN hypervisor support + - o/snapstate: exclude TypeSnapd and TypeOS snaps from refresh-app- + awareness + - features: enable refresh-app-awareness by default + - libsnap-confine-private: show proper error when aa_change_onexec() + fails + - i/apparmor: remove leftover comment + - gadget: drop unused code in unit tests + - image, store: move ToolingStore to store/tooling package + - HACKING: update info for snapcraft remote build + - seed: return all essential snaps found if no types are given to + LoadEssentialMeta + - i/b/custom_device: fix generation of udev rules + - tests/nested/manual/core20-early-config: disable netplan checks + - bootloader/assets, tests: add factory-reset mode, test non- + encrypted factory-reset + - interfaces/modem-manager: add support for Cinterion modules + - gadget: fully support multi-volume gadget asset updates in + Update() on UC20+ + - i/b/content: use slot.Lookup() as suggested by TODO comment + - tests: install linux-tools-gcp on jammy to avoid bpftool + dependency error + - tests/main: add spread tests for new cpu and thread quotas + - snap-debug-info: print validation sets and validation set + assertions + - many: renaming related to inclusive language part 2 + - c/snap-seccomp: update syscalls to match libseccomp 2657109 + - github: cancel workflows when pushing to pull request branches + - .github: use reviewdog action from woke tool + - interfaces/system-packages-doc: allow read-only access to + /usr/share/gtk-doc + - interfaces: add max_map_count to system-observe + - o/snapstate: print pids of running processes on BusySnapError + - .github: run woke tool on PR's + - snapshots: follow-up on exclusions PR + - cmd/snap: add check switch for snap debug state + - tests: do not run mount-order-regression test on i386 + - interfaces/system-packages-doc: allow read-only access to + /usr/share/xubuntu-docs + - interfaces/hardware_observe: add read access for various devices + - packaging: use latest go to build spread + - tests: Enable more tests for UC22 + - interfaces/builtin/network-control: also allow for mstp and bchat + devices too + - interfaces/builtin: update apparmor profile to allow creating + mimic over /usr/share* + - data/selinux: allow snap-update-ns to mount on top of /var/snap + inside the mount ns + - interfaces/cpu-control: fix apparmor rules of paths with CPU ID + - tests: remove the file that configures nm as default + - tests: fix the change done for netplan-cfg test + - tests: disable netplan-cfg test + - cmd/snap-update-ns: apply content mounts before layouts + - overlord/state: add a helper to detect cyclic dependencies between + tasks in change + - packaging/ubuntu-16.04/control: recommend `fuse3 | fuse` + - many: change "transactional" flag to a "transaction" option + - b/piboot.go: check EEPROM version for RPi4 + - snap/quota,spread: raise lower memory quota limit to 640kb + - boot,bootloader: add missing grub.cfg assets mocks in some tests + - many: support --ignore-running with refresh many + - tests: skip the test interfaces-many-snap-provided in + trusty + - o/snapstate: rename XDG dirs during HOME migration + - cmd/snap,wrappers: fix wrong implementation of zero count cpu + quota + - i/b/kernel_module_load: expand $SNAP_COMMON in module options + - interfaces/u2f-devices: add Solo V2 + - overlord: add missing grub.cfg assets mocks in manager_tests.go + - asserts: extend optional primary keys support to the in-memory + backend + - tests: update the lxd-no-fuse test + - many: fix failing golangci checks + - seed,many: allow to limit LoadMeta to snaps of a precise mode + - tests: allow ubuntu-image to be built with a compatible snapd tree + - o/snapstate: account for repeat migration in ~/Snap undo + - asserts: start supporting optional primary keys in fs backend, + assemble and signing + - b/a: do not set console in kernel command line for arm64 + - tests/main/snap-quota-groups: fix spread test + - sandbox,quota: ensure cgroup is available when creating mem + quotas + - tests: add debug output what keeps `/home` busy + - sanity: rename "sanity.Check" to "syscheck.CheckSystem" + - interfaces: add pkcs11 interface + - o/snapstate: undo migration on 'snap revert' + - overlord: snapshot exclusions + - interfaces: add private /dev/shm support to shared-memory + interface + - gadget/install: implement factory reset for unencrypted system + - packaging: install Go snap from 1.17 channel in the integration + tests + - snap-exec: fix detection if `cups` interface is connected + - tests: extend gadget-config-defaults test with refresh.retain + - cmd/snap,strutil: move lineWrap to WordWrapPadded + - bootloader/piboot: add support for armhf + - snap,wrappers: add `sigint{,-all}` to supported stop-modes + - packaging/ubuntu-16.04/control: depend on fuse3 | fuse + - interfaces/system-packages-doc: allow read-only access to + /usr/share/libreoffice/help + - daemon: add a /v2/accessories/changes/{ID} endpoint + - interfaces/appstream-metadata: Re-create app-info links to + swcatalog + - debug-tools: add script to help debugging GCE instances which fail + to boot + - gadget/install, kernel: more ICE helpers/support + - asserts: exclude empty snap id from duplicates lookup with preseed + assert + - cmd/snap, signtool: move key-manager related helpers to signtool + package + - tests/main/snap-quota-groups: add 219 as possible exit code + - store: set validation-sets on actions when refreshing + - github/workflows: update golangci-lint version + - run-check: use go install instead of go get + - tests: set as manual the interfaces-cups-control test + - interfaces/appstream-metadata: Support new swcatalog directory + names + - image/preseed: migrate tests from cmd/snap-preseed + - tests/main/uc20-create-partitions: update the test for new Go + versions + - strutil: move wrapGeneric function to strutil as WordWrap + - many: small inconsequential tweaks + - quota: detect/error if cpu-set is used with cgroup v1 + - tests: moving ubuntu-image to candidate to fix uc16 tests + - image: integrate UC20 preseeding with image.Prepare + - cmd/snap,client: frontend for cpu/thread quotas + - quota: add test for `Resource.clone()` + - many: replace use of "sanity" with more inclusive naming (part 2) + - tests: switch to "test-snapd-swtpm" + - i/b/network-manager: split rule with more than one peers + - tests: fix restore of the BUILD_DIR in failover test on uc18 + - cmd/snap/debug: sort changes by their spawn times + - asserts,interfaces/policy: slot-snap-id allow-installation + constraints + - o/devicestate: factory reset mode, no encryption + - debug-tools/snap-debug-info.sh: print message if no gadget snap + found + - overlord/devicestate: install system cleanups + - cmd/snap-bootstrap: support booting into factory-reset mode + - o/snapstate, ifacestate: pass preseeding flag to + AddSnapdSnapServices + - o/devicestate: restore device key and serial when assertion is + found + - data: add static preseed.json file + - sandbox: improve error message from `ProbeCgroupVersion()` + - tests: fix the nested remodel tests + - quota: add some more unit tests around Resource.Change() + - debug-tools/snap-debug-info.sh: add debug script + - tests: workaround lxd issue lp:10079 (function not implemented) on + prep-snapd-in-lxd + - osutil/disks: blockdev need not be available in the PATH + - cmd/snap-preseed: address deadcode linter + - tests/lib/fakestore/store: return snap base in details + - tests/lib/nested.sh: rm core18 snap after download + - systemd: do not reload system when enabling/disabling services + - i/b/kubernetes_support: add access to Java certificates + + -- Michael Vogt Thu, 19 May 2022 09:57:33 +0200 + snapd (2.55.5~14.04) trusty; urgency=medium * New upstream release, LP: #1965808 diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog index c84f416cdf9..1441c24f267 100644 --- a/packaging/ubuntu-16.04/changelog +++ b/packaging/ubuntu-16.04/changelog @@ -1,3 +1,283 @@ +snapd (2.56) xenial; urgency=medium + + * New upstream release, LP: #1974147 + - portal-info: Add CommonID Field + - asserts/info,mkversion.sh: capture max assertion formats in + snapd/info + - tests: improve the unit testing workflow to run in parallel + - interfaces: allow map and execute permissions for files on + removable media + - tests: add spread test to verify that connections are preserved if + snap refresh fails + - tests: Apparmor sandbox profile mocking + - cmd/snap-fde-keymgr: support for multiple devices and + authorizations for add/remove recovery key + - cmd/snap-bootstrap: Listen to keyboard added after start and + handle switch root + - interfaces,overlord: add support for adding extra mount layouts + - cmd/snap: replace existing code for 'snap model' to use shared + code in clientutil (2/3) + - interfaces: fix opengl interface on RISC-V + - interfaces: allow access to the file locking for cryptosetup in + the dm-crypt interface + - interfaces: network-manager: add AppArmor rule for configuring + bridges + - i/b/hardware-observe.go: add access to the thermal sysfs + - interfaces: opengl: add rules for NXP i.MX GPU drivers + - i/b/mount_control: add an optional "/" to the mount target rule + - snap/quota: add values for journal quotas (journal quota 2/n) + - tests: spread test for uc20 preseeding covering snap prepare-image + - o/snapstate: remove deadcode breaking static checks + - secboot/keymgr: extend unit tests, add helper for identify keyslot + used error + - tests: use new snaps.name and snaps.cleanup tools + - interfaces: tweak getPath() slightly and add some more tests + - tests: update snapd testing tools + - client/clientutil: add shared code for printing model assertions + as yaml or json (1/3) + - debug-tools: list all snaps + - cmd/snap: join search terms passed in the command line + - osutil/disks: partition UUID lookup + - o/snapshotstate: refactor snapshot read/write logic + - interfaces: Allow locking in block-devices + - daemon: /v2/system-recovery-keys remove API + - snapstate: do not auto-migrate to ~/Snap for core22 just yet + - tests: run failed tests by default + - o/snapshotstate: check installed snaps before running 'save' tasks + - secboot/keymgr: remove recovery key, authorize with existing key + - deps: bump libseccomp to include build fixes, run unit tests using + CC=clang + - cmd/snap-seccomp: only compare the bottom 32-bits of the flags arg + of copy_file_range + - osutil/disks: helper for obtaining the UUID of a partition which + is a mount point source + - image/preseed: umount the base snap last after writable paths + - tests: new set of nested tests for uc22 + - tests: run failed tests on nested suite + - interfaces: posix-mq: add new interface + - tests/main/user-session-env: remove openSUSE-specific tweaks + - tests: skip external backend in mem-cgroup-disabled test + - snap/quota: change the journal quota period to be a time.Duration + - interfaces/apparmor: allow executing /usr/bin/numfmt in the base + template + - tests: add lz4 dependency for jammy to avoid issues repacking + kernel + - snap-bootstrap, o/devicestate: use seed parallelism + - cmd/snap-update-ns: correctly set sticky bit on created + directories where applicable + - tests: install snapd while restoring in snap-mgmt + - .github: skip misspell and ineffassign on go 1.13 + - many: use UC20+/pre-UC20 in user messages as needed + - o/devicestate: use snap handler for copying and checksuming + preseeded snaps + - image, cmd/snap-preseed: allow passing custom apparmor features + path + - o/assertstate: fix handling of validation set tracking update in + enforcing mode + - packaging: restart our units only after the upgrade + - interfaces: add a steam-support interface + - gadget/install, o/devicestate: do not create recovery and + reinstall keys during installation + - many: move recovery key responsibility to devicestate/secboot, + prepare for a future with just optional recovery key + - tests: do not run mem-cgroup-disabled on external backends + - snap: implement "star" developers + - o/devicestate: fix install tests on systems with + /var/lib/snapd/snap + - cmd/snap-fde-keymgr, secboot: followup cleanups + - seed: let SnapHandler provided a different final path for snaps + - o/devicestate: implement maybeApplyPreseededData function to apply + preseed artifact + - tests/lib/tools: add piboot to boot_path() + - interfaces/builtin: shared-memory drop plugs allow-installation: + true + - tests/main/user-session-env: for for opensuse + - cmd/snap-fde-keymgr, secboot: add a tiny FDE key manager + - tests: re-execute the failed tests when "Run failed" label is set + in the PR + - interfaces/builtin/custom-device: fix unit tests on hosts with + different libexecdir + - sandbox: move profile load/unload to sandbox/apparmor + - cmd/snap: handler call verifications for cmd_quota_tests + - secboot/keys: introduce a package for secboot key types, use the + package throughout the code base + - snap/quota: add journal quotas to resources.go + - many: let provide a SnapHandler to Seed.Load*Meta* + - osutil: allow setting desired mtime on the AtomicFile, preserve + mtime on copy + - systemd: add systemd.Run() wrapper for systemd-run + - tests: test fresh install of core22-based snap (#11696) + - tests: initial set of tests to uc22 nested execution + - o/snapstate: migration overwrites existing snap dir + - tests: fix interfaces-location-control tests leaking provider.py + process + - tests/nested: fix custom-device test + - tests: test migration w/ revert, refresh and XDG dir creation + - asserts,store: complete support for optional primary key headers + for assertions + - seed: support parallelism when loading/verifying snap metadata + - image/preseed, cmd/snap-preseed: create and sign preseed assertion + - tests: Initial changes to run nested tests on uc22 + - o/snapstate: fix TestSnapdRefreshTasks test after two r-a-a PRs + - interfaces: add ACRN hypervisor support + - o/snapstate: exclude TypeSnapd and TypeOS snaps from refresh-app- + awareness + - features: enable refresh-app-awareness by default + - libsnap-confine-private: show proper error when aa_change_onexec() + fails + - i/apparmor: remove leftover comment + - gadget: drop unused code in unit tests + - image, store: move ToolingStore to store/tooling package + - HACKING: update info for snapcraft remote build + - seed: return all essential snaps found if no types are given to + LoadEssentialMeta + - i/b/custom_device: fix generation of udev rules + - tests/nested/manual/core20-early-config: disable netplan checks + - bootloader/assets, tests: add factory-reset mode, test non- + encrypted factory-reset + - interfaces/modem-manager: add support for Cinterion modules + - gadget: fully support multi-volume gadget asset updates in + Update() on UC20+ + - i/b/content: use slot.Lookup() as suggested by TODO comment + - tests: install linux-tools-gcp on jammy to avoid bpftool + dependency error + - tests/main: add spread tests for new cpu and thread quotas + - snap-debug-info: print validation sets and validation set + assertions + - many: renaming related to inclusive language part 2 + - c/snap-seccomp: update syscalls to match libseccomp 2657109 + - github: cancel workflows when pushing to pull request branches + - .github: use reviewdog action from woke tool + - interfaces/system-packages-doc: allow read-only access to + /usr/share/gtk-doc + - interfaces: add max_map_count to system-observe + - o/snapstate: print pids of running processes on BusySnapError + - .github: run woke tool on PR's + - snapshots: follow-up on exclusions PR + - cmd/snap: add check switch for snap debug state + - tests: do not run mount-order-regression test on i386 + - interfaces/system-packages-doc: allow read-only access to + /usr/share/xubuntu-docs + - interfaces/hardware_observe: add read access for various devices + - packaging: use latest go to build spread + - tests: Enable more tests for UC22 + - interfaces/builtin/network-control: also allow for mstp and bchat + devices too + - interfaces/builtin: update apparmor profile to allow creating + mimic over /usr/share* + - data/selinux: allow snap-update-ns to mount on top of /var/snap + inside the mount ns + - interfaces/cpu-control: fix apparmor rules of paths with CPU ID + - tests: remove the file that configures nm as default + - tests: fix the change done for netplan-cfg test + - tests: disable netplan-cfg test + - cmd/snap-update-ns: apply content mounts before layouts + - overlord/state: add a helper to detect cyclic dependencies between + tasks in change + - packaging/ubuntu-16.04/control: recommend `fuse3 | fuse` + - many: change "transactional" flag to a "transaction" option + - b/piboot.go: check EEPROM version for RPi4 + - snap/quota,spread: raise lower memory quota limit to 640kb + - boot,bootloader: add missing grub.cfg assets mocks in some tests + - many: support --ignore-running with refresh many + - tests: skip the test interfaces-many-snap-provided in + trusty + - o/snapstate: rename XDG dirs during HOME migration + - cmd/snap,wrappers: fix wrong implementation of zero count cpu + quota + - i/b/kernel_module_load: expand $SNAP_COMMON in module options + - interfaces/u2f-devices: add Solo V2 + - overlord: add missing grub.cfg assets mocks in manager_tests.go + - asserts: extend optional primary keys support to the in-memory + backend + - tests: update the lxd-no-fuse test + - many: fix failing golangci checks + - seed,many: allow to limit LoadMeta to snaps of a precise mode + - tests: allow ubuntu-image to be built with a compatible snapd tree + - o/snapstate: account for repeat migration in ~/Snap undo + - asserts: start supporting optional primary keys in fs backend, + assemble and signing + - b/a: do not set console in kernel command line for arm64 + - tests/main/snap-quota-groups: fix spread test + - sandbox,quota: ensure cgroup is available when creating mem + quotas + - tests: add debug output what keeps `/home` busy + - sanity: rename "sanity.Check" to "syscheck.CheckSystem" + - interfaces: add pkcs11 interface + - o/snapstate: undo migration on 'snap revert' + - overlord: snapshot exclusions + - interfaces: add private /dev/shm support to shared-memory + interface + - gadget/install: implement factory reset for unencrypted system + - packaging: install Go snap from 1.17 channel in the integration + tests + - snap-exec: fix detection if `cups` interface is connected + - tests: extend gadget-config-defaults test with refresh.retain + - cmd/snap,strutil: move lineWrap to WordWrapPadded + - bootloader/piboot: add support for armhf + - snap,wrappers: add `sigint{,-all}` to supported stop-modes + - packaging/ubuntu-16.04/control: depend on fuse3 | fuse + - interfaces/system-packages-doc: allow read-only access to + /usr/share/libreoffice/help + - daemon: add a /v2/accessories/changes/{ID} endpoint + - interfaces/appstream-metadata: Re-create app-info links to + swcatalog + - debug-tools: add script to help debugging GCE instances which fail + to boot + - gadget/install, kernel: more ICE helpers/support + - asserts: exclude empty snap id from duplicates lookup with preseed + assert + - cmd/snap, signtool: move key-manager related helpers to signtool + package + - tests/main/snap-quota-groups: add 219 as possible exit code + - store: set validation-sets on actions when refreshing + - github/workflows: update golangci-lint version + - run-check: use go install instead of go get + - tests: set as manual the interfaces-cups-control test + - interfaces/appstream-metadata: Support new swcatalog directory + names + - image/preseed: migrate tests from cmd/snap-preseed + - tests/main/uc20-create-partitions: update the test for new Go + versions + - strutil: move wrapGeneric function to strutil as WordWrap + - many: small inconsequential tweaks + - quota: detect/error if cpu-set is used with cgroup v1 + - tests: moving ubuntu-image to candidate to fix uc16 tests + - image: integrate UC20 preseeding with image.Prepare + - cmd/snap,client: frontend for cpu/thread quotas + - quota: add test for `Resource.clone()` + - many: replace use of "sanity" with more inclusive naming (part 2) + - tests: switch to "test-snapd-swtpm" + - i/b/network-manager: split rule with more than one peers + - tests: fix restore of the BUILD_DIR in failover test on uc18 + - cmd/snap/debug: sort changes by their spawn times + - asserts,interfaces/policy: slot-snap-id allow-installation + constraints + - o/devicestate: factory reset mode, no encryption + - debug-tools/snap-debug-info.sh: print message if no gadget snap + found + - overlord/devicestate: install system cleanups + - cmd/snap-bootstrap: support booting into factory-reset mode + - o/snapstate, ifacestate: pass preseeding flag to + AddSnapdSnapServices + - o/devicestate: restore device key and serial when assertion is + found + - data: add static preseed.json file + - sandbox: improve error message from `ProbeCgroupVersion()` + - tests: fix the nested remodel tests + - quota: add some more unit tests around Resource.Change() + - debug-tools/snap-debug-info.sh: add debug script + - tests: workaround lxd issue lp:10079 (function not implemented) on + prep-snapd-in-lxd + - osutil/disks: blockdev need not be available in the PATH + - cmd/snap-preseed: address deadcode linter + - tests/lib/fakestore/store: return snap base in details + - tests/lib/nested.sh: rm core18 snap after download + - systemd: do not reload system when enabling/disabling services + - i/b/kubernetes_support: add access to Java certificates + + -- Michael Vogt Thu, 19 May 2022 09:57:33 +0200 + snapd (2.55.5) xenial; urgency=medium * New upstream release, LP: #1965808 From 68951bc9ea80d8246cceeac63d167af00b4f3730 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Mon, 23 May 2022 09:53:50 +0930 Subject: [PATCH 014/153] interfaces/shared-memory: Update AppArmor permissions for mmap+link Allow snaps to mmap and link files under their own shared-memory paths. Fixes LP: #1974464. Signed-off-by: Alex Murray --- interfaces/builtin/shared_memory.go | 2 +- interfaces/builtin/shared_memory_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interfaces/builtin/shared_memory.go b/interfaces/builtin/shared_memory.go index 97a7e000e04..6a999496223 100644 --- a/interfaces/builtin/shared_memory.go +++ b/interfaces/builtin/shared_memory.go @@ -220,7 +220,7 @@ func writeSharedMemoryPaths(w io.Writer, slot *interfaces.ConnectedSlot, snippetType sharedMemorySnippetType) { emitWritableRule := func(path string) { // Ubuntu 14.04 uses /run/shm instead of the most common /dev/shm - fmt.Fprintf(w, "\"/{dev,run}/shm/%s\" rwk,\n", path) + fmt.Fprintf(w, "\"/{dev,run}/shm/%s\" mrwlk,\n", path) } // All checks were already done in BeforePrepare{Plug,Slot} diff --git a/interfaces/builtin/shared_memory_test.go b/interfaces/builtin/shared_memory_test.go index 989c7beeee5..206e4593890 100644 --- a/interfaces/builtin/shared_memory_test.go +++ b/interfaces/builtin/shared_memory_test.go @@ -393,12 +393,12 @@ func (s *SharedMemoryInterfaceSuite) TestAppArmorSpec(c *C) { c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.provider.app"}) - c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar" rwk,`) + c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar" mrwlk,`) c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" r,`) // Slot has read-write permissions to all paths - c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar" rwk,`) - c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" rwk,`) + c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar" mrwlk,`) + c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" mrwlk,`) wildcardSpec := &apparmor.Specification{} c.Assert(wildcardSpec.AddConnectedPlug(s.iface, s.wildcardPlug, s.wildcardSlot), IsNil) @@ -409,12 +409,12 @@ func (s *SharedMemoryInterfaceSuite) TestAppArmorSpec(c *C) { c.Assert(wildcardSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.provider.app"}) - c.Check(wildcardPlugSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" rwk,`) + c.Check(wildcardPlugSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" mrwlk,`) c.Check(wildcardPlugSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro*" r,`) // Slot has read-write permissions to all paths - c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" rwk,`) - c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro*" rwk,`) + c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" mrwlk,`) + c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro*" mrwlk,`) spec = &apparmor.Specification{} c.Assert(spec.AddConnectedPlug(s.iface, s.privatePlug, s.privateSlot), IsNil) From a2a0c5315487a401d492e0289f77f61ae65c4333 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 24 May 2022 12:32:01 +0200 Subject: [PATCH 015/153] interfaces/opengl: update allowed PCI accesses for RPi When investigating why Firefox is particularly slow on Raspberry Pi the denials reading PCI IO resources were logged. Further attempts with --devmode showed this: apparmor="ALLOWED" operation="open" profile="snap.firefox.firefox" name="/sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/resource" pid=8675 comm="firefox" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 apparmor="ALLOWED" operation="open" profile="snap.firefox.firefox" name="/sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/irq" pid=8675 comm="firefox" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 apparmor="ALLOWED" operation="open" profile="snap.firefox.firefox" name="/sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/vendor" pid=8675 comm="firefox" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 apparmor="ALLOWED" operation="open" profile="snap.firefox.firefox" name="/sys/devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/device" pid=8675 comm="firefox" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 The patch updates the opengl interface to allow access to resource and irq attributes, as well as uses alternative platform path. Signed-off-by: Maciej Borzecki --- interfaces/builtin/opengl.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/interfaces/builtin/opengl.go b/interfaces/builtin/opengl.go index c04d2aa8293..f179aef84b4 100644 --- a/interfaces/builtin/opengl.go +++ b/interfaces/builtin/opengl.go @@ -136,12 +136,14 @@ unix (bind,listen) type=seqpacket addr="@cuda-uvmfd-[0-9a-f]*", @{PROC}/driver/prl_vtg rw, # /sys/devices -/sys/devices/{,*pcie-controller/,platform/soc/*.pcie/}pci[0-9a-f]*/**/config r, -/sys/devices/{,*pcie-controller/,platform/soc/*.pcie/}pci[0-9a-f]*/**/revision r, -/sys/devices/{,*pcie-controller/,platform/soc/*.pcie/}pci[0-9a-f]*/**/boot_vga r, -/sys/devices/{,*pcie-controller/,platform/soc/*.pcie/}pci[0-9a-f]*/**/{,subsystem_}class r, -/sys/devices/{,*pcie-controller/,platform/soc/*.pcie/}pci[0-9a-f]*/**/{,subsystem_}device r, -/sys/devices/{,*pcie-controller/,platform/soc/*.pcie/}pci[0-9a-f]*/**/{,subsystem_}vendor r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/config r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/revision r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/resource r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/irq r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/boot_vga r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/{,subsystem_}class r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/{,subsystem_}device r, +/sys/devices/{,*pcie-controller/,platform/{soc,scb}/*.pcie/}pci[0-9a-f]*/**/{,subsystem_}vendor r, /sys/devices/**/drm{,_dp_aux_dev}/** r, # FIXME: this is an information leak and snapd should instead query udev for From d2bb5da7059fd4e4f2f905685036f63739d1e0e8 Mon Sep 17 00:00:00 2001 From: Pawel Stolowski Date: Wed, 1 Jun 2022 16:27:18 +0200 Subject: [PATCH 016/153] o/snapstate: fix validation sets restoring and snap revert on failed refresh * Make sure snap revert triggered by maybeRestoreValidationSetsAndRevertSnaps() doesn't conflict with the main change where check-rerefresh runs. Add more tests. * Report internal error if not all snaps with wrong revisions are among refreshed snaps (and can be reverted). --- daemon/api_snaps.go | 4 +- daemon/api_snaps_test.go | 4 +- daemon/export_test.go | 4 +- overlord/managers_test.go | 2 +- overlord/snapstate/backend_test.go | 6 + overlord/snapstate/booted.go | 2 +- overlord/snapstate/export_test.go | 2 +- overlord/snapstate/handlers.go | 48 ++++-- overlord/snapstate/handlers_rerefresh_test.go | 137 +++++++++++++++++- overlord/snapstate/snapstate.go | 8 +- overlord/snapstate/snapstate_test.go | 54 +++---- overlord/snapstate/snapstate_update_test.go | 98 +++++++++---- 12 files changed, 284 insertions(+), 85 deletions(-) diff --git a/daemon/api_snaps.go b/daemon/api_snaps.go index e807b64f215..db78db24415 100644 --- a/daemon/api_snaps.go +++ b/daemon/api_snaps.go @@ -387,9 +387,9 @@ func snapRevert(inst *snapInstruction, st *state.State) (string, []*state.TaskSe } if inst.Revision.Unset() { - ts, err = snapstateRevert(st, inst.Snaps[0], flags) + ts, err = snapstateRevert(st, inst.Snaps[0], flags, "") } else { - ts, err = snapstateRevertToRevision(st, inst.Snaps[0], inst.Revision, flags) + ts, err = snapstateRevertToRevision(st, inst.Snaps[0], inst.Revision, flags, "") } if err != nil { return "", nil, err diff --git a/daemon/api_snaps_test.go b/daemon/api_snaps_test.go index 442180028db..3c4b3e11509 100644 --- a/daemon/api_snaps_test.go +++ b/daemon/api_snaps_test.go @@ -2073,12 +2073,12 @@ func (s *snapsSuite) testRevertSnap(inst *daemon.SnapInstruction, c *check.C) { instFlags, err := inst.ModeFlags() c.Assert(err, check.IsNil) - defer daemon.MockSnapstateRevert(func(s *state.State, name string, flags snapstate.Flags) (*state.TaskSet, error) { + defer daemon.MockSnapstateRevert(func(s *state.State, name string, flags snapstate.Flags, fromChange string) (*state.TaskSet, error) { c.Check(flags, check.Equals, instFlags) queue = append(queue, name) return nil, nil })() - defer daemon.MockSnapstateRevertToRevision(func(s *state.State, name string, rev snap.Revision, flags snapstate.Flags) (*state.TaskSet, error) { + defer daemon.MockSnapstateRevertToRevision(func(s *state.State, name string, rev snap.Revision, flags snapstate.Flags, fromChange string) (*state.TaskSet, error) { c.Check(flags, check.Equals, instFlags) queue = append(queue, fmt.Sprintf("%s (%s)", name, rev)) return nil, nil diff --git a/daemon/export_test.go b/daemon/export_test.go index 8c45b22fb93..6ba228b384b 100644 --- a/daemon/export_test.go +++ b/daemon/export_test.go @@ -156,7 +156,7 @@ func MockSnapstateSwitch(mock func(*state.State, string, *snapstate.RevisionOpti } } -func MockSnapstateRevert(mock func(*state.State, string, snapstate.Flags) (*state.TaskSet, error)) (restore func()) { +func MockSnapstateRevert(mock func(*state.State, string, snapstate.Flags, string) (*state.TaskSet, error)) (restore func()) { oldSnapstateRevert := snapstateRevert snapstateRevert = mock return func() { @@ -164,7 +164,7 @@ func MockSnapstateRevert(mock func(*state.State, string, snapstate.Flags) (*stat } } -func MockSnapstateRevertToRevision(mock func(*state.State, string, snap.Revision, snapstate.Flags) (*state.TaskSet, error)) (restore func()) { +func MockSnapstateRevertToRevision(mock func(*state.State, string, snap.Revision, snapstate.Flags, string) (*state.TaskSet, error)) (restore func()) { oldSnapstateRevertToRevision := snapstateRevertToRevision snapstateRevertToRevision = mock return func() { diff --git a/overlord/managers_test.go b/overlord/managers_test.go index e2a67f6593d..ce58c2c7a0d 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -3086,7 +3086,7 @@ apps: c.Assert(err, ErrorMatches, ".*no such file.*") // now do the revert - ts, err := snapstate.Revert(st, "foo", snapstate.Flags{}) + ts, err := snapstate.Revert(st, "foo", snapstate.Flags{}, "") c.Assert(err, IsNil) chg := st.NewChange("revert-snap", "...") chg.AddAll(ts) diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go index dcf7aa21504..0a6186a9f51 100644 --- a/overlord/snapstate/backend_test.go +++ b/overlord/snapstate/backend_test.go @@ -411,6 +411,12 @@ func (f *fakeStore) lookupRefresh(cand refreshCand) (*snap.Info, error) { name = "outdated-consumer" case "outdated-producer-id": name = "outdated-producer" + // for validation-sets testing + case "aaqKhntON3vR7kwEbVPsILm7bUViPDzx": + name = "some-snap" + // for validation-sets testing + case "bgtKhntON3vR7kwEbVPsILm7bUViPDzx": + name = "some-other-snap" default: panic(fmt.Sprintf("refresh: unknown snap-id: %s", cand.snapID)) } diff --git a/overlord/snapstate/booted.go b/overlord/snapstate/booted.go index 2d67447b4b2..482396397e6 100644 --- a/overlord/snapstate/booted.go +++ b/overlord/snapstate/booted.go @@ -78,7 +78,7 @@ func UpdateBootRevisions(st *state.State) error { if actual.SnapRevision() != info.SideInfo.Revision { // FIXME: check that there is no task // for this already in progress - ts, err := RevertToRevision(st, actual.SnapName(), actual.SnapRevision(), Flags{}) + ts, err := RevertToRevision(st, actual.SnapName(), actual.SnapRevision(), Flags{}, "") if err != nil { return err } diff --git a/overlord/snapstate/export_test.go b/overlord/snapstate/export_test.go index 69655843cba..843c2cd3273 100644 --- a/overlord/snapstate/export_test.go +++ b/overlord/snapstate/export_test.go @@ -414,7 +414,7 @@ func MockRestoreValidationSetsTracking(f func(*state.State) error) (restore func } } -func MockMaybeRestoreValidationSetsAndRevertSnaps(f func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error)) (restore func()) { +func MockMaybeRestoreValidationSetsAndRevertSnaps(f func(st *state.State, refreshedSnaps []string, fromChange string) ([]*state.TaskSet, error)) (restore func()) { old := maybeRestoreValidationSetsAndRevertSnaps maybeRestoreValidationSetsAndRevertSnaps = f return func() { diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go index 89b2ec21e6a..714990f488e 100644 --- a/overlord/snapstate/handlers.go +++ b/overlord/snapstate/handlers.go @@ -3554,9 +3554,11 @@ func (m *SnapManager) doCheckReRefresh(t *state.Task, tomb *tomb.Tomb) error { } } + chg := t.Change() + // if any snap failed to refresh, reconsider validation set tracking if failed { - tasksets, err := maybeRestoreValidationSetsAndRevertSnaps(st, snaps) + tasksets, err := maybeRestoreValidationSetsAndRevertSnaps(st, snaps, chg.ID()) if err != nil { return err } @@ -3596,7 +3598,7 @@ func (m *SnapManager) doCheckReRefresh(t *state.Task, tomb *tomb.Tomb) error { if err := t.Get("rerefresh-setup", &re); err != nil { return err } - chg := t.Change() + updated, tasksets, err := reRefreshUpdateMany(tomb.Context(nil), st, snaps, re.UserID, reRefreshFilter, re.Flags, chg.ID()) if err != nil { return err @@ -3663,11 +3665,12 @@ func (m *SnapManager) doConditionalAutoRefresh(t *state.Task, tomb *tomb.Tomb) e // validation sets and - if necessary - creates tasksets to revert some or all // of the refreshed snaps to their previous revisions to satisfy the restored // validation sets tracking. -var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error) { +var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSnaps []string, fromChange string) ([]*state.TaskSet, error) { enforcedSets, err := EnforcedValidationSets(st, nil) if err != nil { return nil, err } + if enforcedSets == nil { // no enforced validation sets, nothing to do return nil, nil @@ -3675,10 +3678,12 @@ var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSn installedSnaps, ignoreValidation, err := InstalledSnaps(st) if err != nil { - return nil, err + return nil, fmt.Errorf("internal error: cannot get installed snaps: %v", err) } + if err := enforcedSets.CheckInstalledSnaps(installedSnaps, ignoreValidation); err == nil { // validation sets are still correct, nothing to do + logger.Debugf("validation sets are still correct after partial refresh") return nil, nil } @@ -3690,51 +3695,72 @@ var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSn // no snaps were refreshed, after restoring validation sets tracking // there is nothing else to do if len(refreshedSnaps) == 0 { + logger.Debugf("validation set tracking restored, no snaps were refreshed") return nil, nil } - // check installed snaps again against restored validation-sets. - // this may fail which is fine, but it tells us which snaps are - // at invalid revisions and need reverting. - // note: we need to fetch enforced sets again because of RestoreValidationSetsTracking. + // we need to fetch enforced sets again because of RestoreValidationSetsTracking. enforcedSets, err = EnforcedValidationSets(st, nil) if err != nil { return nil, err } + if enforcedSets == nil { return nil, fmt.Errorf("internal error: no enforced validation sets after restoring from the stack") } + + // check installed snaps again against restored validation-sets. + // this may fail which is fine, but it tells us which snaps are + // at invalid revisions and need reverting. err = enforcedSets.CheckInstalledSnaps(installedSnaps, ignoreValidation) if err == nil { // all fine after restoring validation sets: this can happen if previous // validation sets only required a snap (regardless of its revision), then // after update they require a specific snap revision, so after restoring // we are back with the good state. + logger.Debugf("validation sets still valid after partial refresh, no snaps need reverting") return nil, nil } verr, ok := err.(*snapasserts.ValidationSetsValidationError) if !ok { - return nil, err + return nil, fmt.Errorf("internal error: %v", err) } + if len(verr.WrongRevisionSnaps) == 0 { // if we hit ValidationSetsValidationError but it's not about wrong revisions, // then something is really broken (we shouldn't have invalid or missing required // snaps at this point). return nil, fmt.Errorf("internal error: unexpected validation error of installed snaps after unsuccesfull refresh: %v", verr) } + + wrongRevSnaps := func() []string { + snaps := make([]string, 0, len(verr.WrongRevisionSnaps)) + for sn := range verr.WrongRevisionSnaps { + snaps = append(snaps, sn) + } + return snaps + } + logger.Debugf("refreshed snaps: %s, snaps at wrong revisions: %s", strutil.Quoted(refreshedSnaps), strutil.Quoted(wrongRevSnaps())) + // revert some or all snaps var tss []*state.TaskSet for _, snapName := range refreshedSnaps { if verr.WrongRevisionSnaps[snapName] != nil { // XXX: should we be extra paranoid and use RevertToRevision with // the specific revision from verr.WrongRevisionSnaps? - ts, err := Revert(st, snapName, Flags{RevertStatus: NotBlocked}) + ts, err := Revert(st, snapName, Flags{RevertStatus: NotBlocked}, fromChange) if err != nil { - return nil, err + return nil, fmt.Errorf("cannot revert snap %q: %v", snapName, err) } tss = append(tss, ts) + delete(verr.WrongRevisionSnaps, snapName) } } + + if len(verr.WrongRevisionSnaps) > 0 { + return nil, fmt.Errorf("internal error: some snaps were not refreshed but are at wrong revisions: %s", strutil.Quoted(wrongRevSnaps())) + } + return tss, nil } diff --git a/overlord/snapstate/handlers_rerefresh_test.go b/overlord/snapstate/handlers_rerefresh_test.go index 13e7d69de56..318f9846cc9 100644 --- a/overlord/snapstate/handlers_rerefresh_test.go +++ b/overlord/snapstate/handlers_rerefresh_test.go @@ -373,6 +373,8 @@ func (s *reRefreshSuite) TestFilterReturnsFalseIfEpochEqualZero(c *C) { c.Check(snapstate.ReRefreshFilter(&snap.Info{Epoch: snap.Epoch{}}, snapst), Equals, false) } +// validation-sets related tests + func (s *refreshSuite) TestMaybeRestoreValidationSetsAndRevertSnaps(c *C) { restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil @@ -385,7 +387,7 @@ func (s *refreshSuite) TestMaybeRestoreValidationSetsAndRevertSnaps(c *C) { refreshedSnaps := []string{"foo", "bar"} // nothing to do with no enforced validation sets - ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps) + ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps, "") c.Assert(err, IsNil) c.Check(ts, IsNil) } @@ -484,9 +486,18 @@ func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertSnapsOneRev }) snaptest.MockSnap(c, `name: some-snap3`, si3) + chg := s.state.NewChange("install change", "...") + t1 := s.state.NewTask("link-snap", "...") + t1.Set("snap-setup", &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "some-snap1", SnapID: "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(1)}}) + t1.SetStatus(state.DoneStatus) + t2 := s.state.NewTask("check-rerefresh", "...") + chg.AddTask(t1) + chg.AddTask(t2) + // some-snap2 failed to refresh refreshedSnaps := []string{"some-snap1", "some-snap3"} - ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps) + // pass change id to make sure revert doesn't conflict + ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps, chg.ID()) c.Assert(err, IsNil) // we expect revert of snap1 @@ -518,6 +529,57 @@ func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertSnapsOneRev c.Check(enforcedValidationSetsCalled, Equals, 2) } +func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertNoSnapsRefreshed(c *C) { + var enforcedValidationSetsCalled int + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + enforcedValidationSetsCalled++ + + vs := snapasserts.NewValidationSets() + snap1 := map[string]interface{}{ + "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", + "name": "some-snap1", + "presence": "required", + "revision": "3", + } + + c.Assert(enforcedValidationSetsCalled, Equals, 1, Commentf("unexpected call to EnforcedValidatioSets")) + vsa1 := s.mockValidationSetAssert(c, "bar", "2", snap1) + vs.Add(vsa1.(*asserts.ValidationSet)) + return vs, nil + }) + defer restore() + + var restoreValidationSetsTrackingCalled int + restoreRestoreValidationSetsTracking := snapstate.MockRestoreValidationSetsTracking(func(*state.State) error { + restoreValidationSetsTrackingCalled++ + return nil + }) + defer restoreRestoreValidationSetsTracking() + + st := s.state + st.Lock() + defer st.Unlock() + + // snaps in the system + si1 := &snap.SideInfo{RealName: "some-snap1", SnapID: "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(1)} + snapstate.Set(s.state, "some-snap1", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si1}, + Current: snap.R(1), + SnapType: "app", + }) + snaptest.MockSnap(c, `name: some-snap1`, si1) + + // no snaps get refreshed + ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, nil, "") + c.Assert(err, IsNil) + + // we expect no snap reverts + c.Assert(ts, HasLen, 0) + c.Check(restoreValidationSetsTrackingCalled, Equals, 1) + c.Check(enforcedValidationSetsCalled, Equals, 1) +} + func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertJustValidationSetsRestore(c *C) { var enforcedValidationSetsCalled int restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { @@ -589,7 +651,7 @@ func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertJustValidat snaptest.MockSnap(c, `name: some-snap2`, si3) refreshedSnaps := []string{"some-snap2"} - ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps) + ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps, "") c.Assert(err, IsNil) // we expect no snap reverts @@ -597,3 +659,72 @@ func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertJustValidat c.Check(restoreValidationSetsTrackingCalled, Equals, 1) c.Check(enforcedValidationSetsCalled, Equals, 2) } + +func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertStillValid(c *C) { + var enforcedValidationSetsCalled int + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + enforcedValidationSetsCalled++ + + vs := snapasserts.NewValidationSets() + snap2 := map[string]interface{}{ + "id": "abcKhntON3vR7kwEbVPsILm7bUViPDzx", + "name": "some-snap2", + "presence": "required", + } + + c.Assert(enforcedValidationSetsCalled, Equals, 1, Commentf("unexpected call to EnforcedValidatioSets")) + + // refreshed validation sets + // snap1 revision 3 is now required (but snap wasn't refreshed) + snap1 := map[string]interface{}{ + "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", + "name": "some-snap1", + "presence": "required", + } + vsa1 := s.mockValidationSetAssert(c, "bar", "3", snap1, snap2) + vs.Add(vsa1.(*asserts.ValidationSet)) + return vs, nil + }) + defer restore() + + var restoreValidationSetsTrackingCalled int + restoreRestoreValidationSetsTracking := snapstate.MockRestoreValidationSetsTracking(func(*state.State) error { + restoreValidationSetsTrackingCalled++ + return nil + }) + defer restoreRestoreValidationSetsTracking() + + st := s.state + st.Lock() + defer st.Unlock() + + // snaps in the system after partial refresh + si1 := &snap.SideInfo{RealName: "some-snap1", SnapID: "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(3)} + snapstate.Set(s.state, "some-snap1", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si1}, + Current: snap.R(3), + SnapType: "app", + }) + snaptest.MockSnap(c, `name: some-snap1`, si1) + + si3 := &snap.SideInfo{RealName: "some-snap2", SnapID: "abcKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(1)} + snapstate.Set(s.state, "some-snap2", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si3}, + Current: snap.R(1), + SnapType: "app", + }) + snaptest.MockSnap(c, `name: some-snap2`, si3) + + // pretend that some-snap1 was refreshed (and some-snap2 failed), some-snap1 is now at revision 3; validation set + // with sequence 3 is still valid though so no snap reverts. + refreshedSnaps := []string{"some-snap1"} + ts, err := snapstate.MaybeRestoreValidationSetsAndRevertSnaps(st, refreshedSnaps, "") + c.Assert(err, IsNil) + + // we expect no snap reverts + c.Assert(ts, HasLen, 0) + c.Check(restoreValidationSetsTrackingCalled, Equals, 0) + c.Check(enforcedValidationSetsCalled, Equals, 1) +} diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index faff6dffc85..1ddc68d776a 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -3082,7 +3082,7 @@ func validateSnapNames(names []string) error { // Revert returns a set of tasks for reverting to the previous version of the snap. // Note that the state must be locked by the caller. -func Revert(st *state.State, name string, flags Flags) (*state.TaskSet, error) { +func Revert(st *state.State, name string, flags Flags, fromChange string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, name, &snapst) if err != nil && err != state.ErrNoState { @@ -3094,10 +3094,10 @@ func Revert(st *state.State, name string, flags Flags) (*state.TaskSet, error) { return nil, fmt.Errorf("no revision to revert to") } - return RevertToRevision(st, name, pi.Revision, flags) + return RevertToRevision(st, name, pi.Revision, flags, fromChange) } -func RevertToRevision(st *state.State, name string, rev snap.Revision, flags Flags) (*state.TaskSet, error) { +func RevertToRevision(st *state.State, name string, rev snap.Revision, flags Flags, fromChange string) (*state.TaskSet, error) { var snapst SnapState err := Get(st, name, &snapst) if err != nil && err != state.ErrNoState { @@ -3144,7 +3144,7 @@ func RevertToRevision(st *state.State, name string, rev snap.Revision, flags Fla PlugsOnly: len(info.Slots) == 0, InstanceKey: snapst.InstanceKey, } - return doInstall(st, &snapst, snapsup, 0, "", nil) + return doInstall(st, &snapst, snapsup, 0, fromChange, nil) } // TransitionCore transitions from an old core snap name to a new core diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index bbfc96bf700..0aaceb0ba86 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -558,7 +558,7 @@ func (s *snapmgrTestSuite) testRevertTasksFullFlags(flags fullFlags, c *C) { SnapType: "app", }) - ts, err := snapstate.Revert(s.state, "some-snap", flags.change) + ts, err := snapstate.Revert(s.state, "some-snap", flags.change, "") c.Assert(err, IsNil) tasks := ts.Tasks() @@ -671,7 +671,7 @@ func (s *snapmgrTestSuite) TestRevertCreatesNoGCTasks(c *C) { Current: snap.R(2), }) - ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R(4), snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R(4), snapstate.Flags{}, "") c.Assert(err, IsNil) // ensure that we do not run any form of garbage-collection @@ -1340,7 +1340,7 @@ func (s *snapmgrTestSuite) TestRevertRestoresConfigSnapshot(c *C) { tr.Commit() chg := s.state.NewChange("revert", "revert snap") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -1382,7 +1382,7 @@ func (s *snapmgrTestSuite) TestRevertNoRevertAgain(c *C) { Current: snap.R(7), }) - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, ErrorMatches, "no revision to revert to") c.Assert(ts, IsNil) } @@ -1402,7 +1402,7 @@ func (s *snapmgrTestSuite) TestRevertNothingToRevertTo(c *C) { Current: si.Revision, }) - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, ErrorMatches, "no revision to revert to") c.Assert(ts, IsNil) } @@ -1426,7 +1426,7 @@ func (s *snapmgrTestSuite) TestRevertToRevisionNoValidVersion(c *C) { Current: snap.R(77), }) - ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R("99"), snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R("99"), snapstate.Flags{}, "") c.Assert(err, ErrorMatches, `cannot find revision 99 for snap "some-snap"`) c.Assert(ts, IsNil) } @@ -1450,7 +1450,7 @@ func (s *snapmgrTestSuite) TestRevertToRevisionAlreadyCurrent(c *C) { Current: snap.R(77), }) - ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R("77"), snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R("77"), snapstate.Flags{}, "") c.Assert(err, ErrorMatches, `already on requested revision`) c.Assert(ts, IsNil) } @@ -1476,7 +1476,7 @@ func (s *snapmgrTestSuite) TestRevertRunThrough(c *C) { }) chg := s.state.NewChange("revert", "revert a snap backwards") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -1571,7 +1571,7 @@ func (s *snapmgrTestSuite) TestRevertRevisionNotBlocked(c *C) { }) chg := s.state.NewChange("revert", "revert a snap backwards") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{RevertStatus: snapstate.NotBlocked}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{RevertStatus: snapstate.NotBlocked}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -1630,7 +1630,7 @@ func (s *snapmgrTestSuite) TestRevertRevisionNotBlockedUndo(c *C) { }) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{RevertStatus: snapstate.NotBlocked}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{RevertStatus: snapstate.NotBlocked}, "") c.Assert(err, IsNil) tasks := ts.Tasks() last := tasks[len(tasks)-1] @@ -1696,7 +1696,7 @@ func (s *snapmgrTestSuite) TestRevertWithBaseRunThrough(c *C) { }) chg := s.state.NewChange("revert", "revert a snap backwards") - ts, err := snapstate.Revert(s.state, "some-snap-with-base", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap-with-base", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -1785,7 +1785,7 @@ func (s *snapmgrTestSuite) TestParallelInstanceRevertRunThrough(c *C) { }) chg := s.state.NewChange("revert", "revert a snap backwards") - ts, err := snapstate.Revert(s.state, "some-snap_instance", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap_instance", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -1885,7 +1885,7 @@ func (s *snapmgrTestSuite) TestRevertWithLocalRevisionRunThrough(c *C) { }) chg := s.state.NewChange("revert", "revert a snap backwards") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -1927,7 +1927,7 @@ func (s *snapmgrTestSuite) TestRevertToRevisionNewVersion(c *C) { }) chg := s.state.NewChange("revert", "revert a snap forward") - ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R(7), snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", snap.R(7), snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2009,7 +2009,7 @@ func (s *snapmgrTestSuite) TestRevertTotalUndoRunThrough(c *C) { }) chg := s.state.NewChange("revert", "revert a snap") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2123,7 +2123,7 @@ func (s *snapmgrTestSuite) TestRevertUndoRunThrough(c *C) { }) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2220,7 +2220,7 @@ func (s *snapmgrTestSuite) TestRevertUndoMigrationAfterUnsetFlag(c *C) { c.Assert(snapstate.WriteSeqFile("some-snap", snapst), IsNil) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2259,7 +2259,7 @@ func (s *snapmgrTestSuite) TestRevertKeepMigrationWithSetFlag(c *C) { tr.Commit() chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2297,7 +2297,7 @@ func (s *snapmgrTestSuite) TestRevertDoHiddenMigration(c *C) { tr.Commit() chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2340,7 +2340,7 @@ func (s *snapmgrTestSuite) TestUndoRevertDoHiddenMigration(c *C) { tr.Commit() chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2414,7 +2414,7 @@ func (s *snapmgrTestSuite) TestRevertFromCore22WithSetFlagKeepMigration(c *C) { }) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2468,7 +2468,7 @@ func (s *snapmgrTestSuite) TestRevertToCore22WithoutFlagSet(c *C) { }) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2544,7 +2544,7 @@ func (s *snapmgrTestSuite) testRevertToCore22AfterRevertedMigration(c *C, migrat }) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2625,7 +2625,7 @@ func (s *snapmgrTestSuite) testUndoRevertToCore22AfterRevertedMigration(c *C, mi chg := s.state.NewChange("revert", "install a revert") s.fakeBackend.linkSnapFailTrigger = filepath.Join(dirs.SnapMountDir, "/some-snap/2") - ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}) + ts, err := snapstate.RevertToRevision(s.state, "some-snap", si2.Revision, snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2684,7 +2684,7 @@ func (s *snapmgrTestSuite) testRevertUndoHiddenMigrationFails(c *C, prepFail pre c.Assert(snapstate.WriteSeqFile("some-snap", snapst), IsNil) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "some-snap", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2728,7 +2728,7 @@ func (s *snapmgrTestSuite) TestRevertUndoExposedMigrationFailsAfterWritingState( tr.Commit() chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "snap-core18-to-core22", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "snap-core18-to-core22", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) @@ -2788,7 +2788,7 @@ func (s *snapmgrTestSuite) testRevertUndoFullMigrationFails(c *C, prepFail prepF c.Assert(snapstate.WriteSeqFile("snap-core18-to-core22", snapst), IsNil) chg := s.state.NewChange("revert", "install a revert") - ts, err := snapstate.Revert(s.state, "snap-core18-to-core22", snapstate.Flags{}) + ts, err := snapstate.Revert(s.state, "snap-core18-to-core22", snapstate.Flags{}, "") c.Assert(err, IsNil) chg.AddAll(ts) diff --git a/overlord/snapstate/snapstate_update_test.go b/overlord/snapstate/snapstate_update_test.go index d1952aaf65e..6b5fce96822 100644 --- a/overlord/snapstate/snapstate_update_test.go +++ b/overlord/snapstate/snapstate_update_test.go @@ -447,7 +447,7 @@ func (s *snapmgrTestSuite) testOpSequence(c *C, opts *opSeqOpts) (*snapstate.Sna var err error if opts.revert { chg = s.state.NewChange("revert", "revert a snap") - ts, err = snapstate.RevertToRevision(s.state, "some-snap", snap.R(opts.via), snapstate.Flags{}) + ts, err = snapstate.RevertToRevision(s.state, "some-snap", snap.R(opts.via), snapstate.Flags{}, "") } else { chg = s.state.NewChange("refresh", "refresh a snap") ts, err = snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Revision: snap.R(opts.via)}, s.user.ID, snapstate.Flags{}) @@ -7090,6 +7090,12 @@ func (s *validationSetsSuite) testUpdateManyValidationSetsPartialFailure(c *C) * logbuf, rest := logger.MockLogger() defer rest() + s.fakeStore.refreshRevnos = map[string]snap.Revision{ + "aaqKhntON3vR7kwEbVPsILm7bUViPDz": snap.R(11), + "bgtKhntON3vR7kwEbVPsILm7bUViPDzx": snap.R(11), + } + + var enforcedValidationSetsCalls int restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() snap1 := map[string]interface{}{ @@ -7102,8 +7108,19 @@ func (s *validationSetsSuite) testUpdateManyValidationSetsPartialFailure(c *C) * "name": "some-other-snap", "presence": "required", } - vsa1 := s.mockValidationSetAssert(c, "bar", "2", snap1, snap2) - vs.Add(vsa1.(*asserts.ValidationSet)) + var sequence string + if enforcedValidationSetsCalls == 0 { + snap1["revision"] = "11" + snap2["revision"] = "11" + sequence = "3" + } else { + snap1["revision"] = "1" + snap2["revision"] = "1" + sequence = "2" + } + vsa1 := s.mockValidationSetAssert(c, "bar", sequence, snap1, snap2) + c.Assert(vs.Add(vsa1.(*asserts.ValidationSet)), IsNil) + enforcedValidationSetsCalls++ return vs, nil }) defer restore() @@ -7119,7 +7136,7 @@ func (s *validationSetsSuite) testUpdateManyValidationSetsPartialFailure(c *C) * } assertstate.UpdateValidationSet(s.state, &tr) - si1 := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)} + si1 := &snap.SideInfo{RealName: "some-snap", SnapID: "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(1)} snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ Active: true, Sequence: []*snap.SideInfo{si1}, @@ -7128,7 +7145,7 @@ func (s *validationSetsSuite) testUpdateManyValidationSetsPartialFailure(c *C) * }) snaptest.MockSnap(c, `name: some-snap`, si1) - si2 := &snap.SideInfo{RealName: "some-other-snap", SnapID: "some-other-snap-id", Revision: snap.R(1)} + si2 := &snap.SideInfo{RealName: "some-other-snap", SnapID: "bgtKhntON3vR7kwEbVPsILm7bUViPDzx", Revision: snap.R(1)} snapstate.Set(s.state, "some-other-snap", &snapstate.SnapState{ Active: true, Sequence: []*snap.SideInfo{si2}, @@ -7155,7 +7172,7 @@ func (s *validationSetsSuite) testUpdateManyValidationSetsPartialFailure(c *C) * func (s *validationSetsSuite) TestUpdateManyValidationSetsPartialFailureNothingToRestore(c *C) { var refreshed []string - restoreMaybeRestoreValidationSetsAndRevertSnaps := snapstate.MockMaybeRestoreValidationSetsAndRevertSnaps(func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error) { + restoreMaybeRestoreValidationSetsAndRevertSnaps := snapstate.MockMaybeRestoreValidationSetsAndRevertSnaps(func(st *state.State, refreshedSnaps []string, fromChange string) ([]*state.TaskSet, error) { refreshed = refreshedSnaps // nothing to restore return nil, nil @@ -7182,43 +7199,62 @@ func (s *validationSetsSuite) TestUpdateManyValidationSetsPartialFailureNothingT } func (s *validationSetsSuite) TestUpdateManyValidationSetsPartialFailureRevertTasks(c *C) { - var refreshed []string - restoreMaybeRestoreValidationSetsAndRevertSnaps := snapstate.MockMaybeRestoreValidationSetsAndRevertSnaps(func(st *state.State, refreshedSnaps []string) ([]*state.TaskSet, error) { - refreshed = refreshedSnaps - ts := state.NewTaskSet(st.NewTask("fake-revert-task", "")) - return []*state.TaskSet{ts}, nil - }) - defer restoreMaybeRestoreValidationSetsAndRevertSnaps() - - var addCurrentTrackingToValidationSetsStackCalled int - restoreAddCurrentTrackingToValidationSetsStack := snapstate.MockAddCurrentTrackingToValidationSetsStack(func(st *state.State) error { - addCurrentTrackingToValidationSetsStackCalled++ + restore := snapstate.MockRestoreValidationSetsTracking(func(st *state.State) error { + tr := assertstate.ValidationSetTracking{ + AccountID: "foo", + Name: "bar", + Mode: assertstate.Enforce, + Current: 2, + } + assertstate.UpdateValidationSet(s.state, &tr) return nil }) - defer restoreAddCurrentTrackingToValidationSetsStack() + defer restore() chg := s.testUpdateManyValidationSetsPartialFailure(c) - // only some-snap was successfully refreshed, this also confirms that - // mockMaybeRestoreValidationSetsAndRevertSnaps was called. - c.Check(refreshed, DeepEquals, []string{"some-snap"}) - s.state.Lock() defer s.state.Unlock() - // check that a fake revert task returned by maybeRestoreValidationSetsAndRevertSnaps - // got injected into the refresh change. - var seen bool + seenLinkSnap := make(map[string]int) + var checkReRefreshTask *state.Task for _, t := range chg.Tasks() { - if t.Kind() == "fake-revert-task" { - seen = true - break + if t.Kind() == "check-rerefresh" { + checkReRefreshTask = t + } + if t.Kind() == "link-snap" { + sup, err := snapstate.TaskSnapSetup(t) + c.Assert(err, IsNil) + if sup.SnapName() == "some-snap" && t.Status() == state.DoneStatus { + c.Assert(t.Status(), Equals, state.DoneStatus) + } + // some-other-snap failed to refresh + if sup.SnapName() == "some-other-snap" { + c.Assert(t.Status(), Equals, state.ErrorStatus) + } + seenLinkSnap[fmt.Sprintf("%s:%s", sup.SnapName(), sup.Revision())]++ } } - c.Check(seen, Equals, true) - // we haven't updated validation sets history - c.Check(addCurrentTrackingToValidationSetsStackCalled, Equals, 0) + // some-snap was seen twice, first time was successful refresh, second time was for + // the revert to previous revision + c.Check(seenLinkSnap, DeepEquals, map[string]int{ + "some-snap:11": 1, + "some-snap:1": 1, + // some-other-snap failed + "some-other-snap:11": 1, + }) + + var snapSt snapstate.SnapState + // both snap are at the initial revisions + c.Assert(snapstate.Get(s.state, "some-snap", &snapSt), IsNil) + c.Check(snapSt.Current, Equals, snap.R("1")) + c.Assert(snapstate.Get(s.state, "some-other-snap", &snapSt), IsNil) + c.Check(snapSt.Current, Equals, snap.R("1")) + + c.Check(chg.Status(), Equals, state.ErrorStatus) + c.Assert(checkReRefreshTask, NotNil) + c.Check(checkReRefreshTask.Status(), Equals, state.DoneStatus) } func (s *snapmgrTestSuite) TestUpdatePrerequisiteBackwardsCompat(c *C) { From 48ca2e13588ebde6854c2ac8e302c0ea41d54885 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Thu, 2 Jun 2022 17:50:46 +0300 Subject: [PATCH 017/153] o/ifacestate: warn if the snapd.apparmor service is disabled If the snapd.apparmor service is not started, the AppArmor profiles of snap-confine and snap-update-ns might not be loaded. If that happens, no snap applications can start, as the calls to aa_change_onexec() will fail. We might want to take more drastic measures when this happens (like, re-enabling the service ourselves?), but while we ponder the decision emitting a warning seems like a harmless yet useful behavioural change. See also: https://bugs.launchpad.net/snapd/+bug/1806135 --- overlord/ifacestate/export_test.go | 7 +++++++ overlord/ifacestate/helpers.go | 17 +++++++++++++++-- overlord/ifacestate/ifacemgr.go | 4 ++++ overlord/ifacestate/ifacestate_test.go | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/overlord/ifacestate/export_test.go b/overlord/ifacestate/export_test.go index 9f6eefb131e..9f73e79a11a 100644 --- a/overlord/ifacestate/export_test.go +++ b/overlord/ifacestate/export_test.go @@ -25,6 +25,7 @@ import ( "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" "github.com/snapcore/snapd/timings" ) @@ -87,6 +88,12 @@ func MockRemoveStaleConnections(f func(st *state.State) error) (restore func()) return func() { removeStaleConnections = old } } +func MockSnapdAppArmorServiceIsDisabled(f func() bool) (restore func()) { + r := testutil.Backup(&snapdAppArmorServiceIsDisabled) + snapdAppArmorServiceIsDisabled = f + return r +} + func MockContentLinkRetryTimeout(d time.Duration) (restore func()) { old := contentLinkRetryTimeout contentLinkRetryTimeout = d diff --git a/overlord/ifacestate/helpers.go b/overlord/ifacestate/helpers.go index 725e5cdcc70..97fa6db882a 100644 --- a/overlord/ifacestate/helpers.go +++ b/overlord/ifacestate/helpers.go @@ -41,9 +41,17 @@ import ( "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/systemd" "github.com/snapcore/snapd/timings" ) +var ( + snapdAppArmorServiceIsDisabled = snapdAppArmorServiceIsDisabledImpl + profilesNeedRegeneration = profilesNeedRegenerationImpl + + writeSystemKey = interfaces.WriteSystemKey +) + func (m *InterfaceManager) selectInterfaceMapper(snaps []*snap.Info) { for _, snapInfo := range snaps { if snapInfo.Type() == snap.TypeSnapd { @@ -141,8 +149,13 @@ func profilesNeedRegenerationImpl() bool { return mismatch } -var profilesNeedRegeneration = profilesNeedRegenerationImpl -var writeSystemKey = interfaces.WriteSystemKey +// snapdAppArmorServiceIsDisabledImpl returns true if the snapd.apparmor +// service unit exists but is disabled +func snapdAppArmorServiceIsDisabledImpl() bool { + sysd := systemd.New(systemd.SystemMode, nil) + isEnabled, err := sysd.IsEnabled("snapd.apparmor") + return err == nil && !isEnabled +} // regenerateAllSecurityProfiles will regenerate all security profiles. func (m *InterfaceManager) regenerateAllSecurityProfiles(tm timings.Measurer) error { diff --git a/overlord/ifacestate/ifacemgr.go b/overlord/ifacestate/ifacemgr.go index 15096eedca7..0475039cfa8 100644 --- a/overlord/ifacestate/ifacemgr.go +++ b/overlord/ifacestate/ifacemgr.go @@ -174,6 +174,10 @@ func (m *InterfaceManager) StartUp() error { return err } } + if snapdAppArmorServiceIsDisabled() { + s.Warnf(`the snapd.apparmor service is disabled; snap applications will likely not start. +Run "systemctl enable snapd.apparmor" to correct this.`) + } ifacerepo.Replace(s, m.repo) diff --git a/overlord/ifacestate/ifacestate_test.go b/overlord/ifacestate/ifacestate_test.go index 3fb1844a5cd..bfd94659fa0 100644 --- a/overlord/ifacestate/ifacestate_test.go +++ b/overlord/ifacestate/ifacestate_test.go @@ -5607,6 +5607,24 @@ func (s *interfaceManagerSuite) TestStartupTimings(c *C) { c.Check(tags, DeepEquals, map[string]interface{}{"startup": "ifacemgr"}) } +func (s *interfaceManagerSuite) TestStartupWarningForDisabledAppArmor(c *C) { + invocationCount := 0 + restore := ifacestate.MockSnapdAppArmorServiceIsDisabled(func() bool { + invocationCount++ + return true + }) + defer restore() + _ = s.manager(c) + + c.Check(invocationCount, Equals, 1) + + s.state.Lock() + defer s.state.Unlock() + warns := s.state.AllWarnings() + c.Assert(warns, HasLen, 1) + c.Check(warns[0].String(), Matches, `the snapd\.apparmor service is disabled.*\nRun .* to correct this\.`) +} + func (s *interfaceManagerSuite) TestAutoconnectSelf(c *C) { s.MockModel(c, nil) From f7efa54e13e47dc5159f3b5965a685f7e0cd9ee9 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 31 May 2022 13:43:11 +0200 Subject: [PATCH 018/153] secboot: alternative PCR handles Signed-off-by: Maciej Borzecki --- secboot/secboot.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/secboot/secboot.go b/secboot/secboot.go index 2a16945e3d1..4397e347510 100644 --- a/secboot/secboot.go +++ b/secboot/secboot.go @@ -33,9 +33,15 @@ import ( ) const ( - // Handles are in the block reserved for TPM owner objects (0x01800000 - 0x01bfffff) - RunObjectPCRPolicyCounterHandle = 0x01880001 - FallbackObjectPCRPolicyCounterHandle = 0x01880002 + // Handles are in the block reserved for TPM owner objects (0x01800000 - 0x01bfffff). + // + // Handles are rotated during factory reset, depending on the PCR handle + // thet was used when sealing key objects during installation (or a + // previous factory reset). + RunObjectPCRPolicyCounterHandle = uint32(0x01880001) + FallbackObjectPCRPolicyCounterHandle = uint32(0x01880002) + AltRunObjectPCRPolicyCounterHandle = uint32(0x01880003) + AltFallbackObjectPCRPolicyCounterHandle = uint32(0x01880004) ) // WithSecbootSupport is true if this package was built with githbu.com/snapcore/secboot. From d4bab5fa3af7e41c244ccb100713dfae250085aa Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 20 Apr 2022 13:10:34 +0200 Subject: [PATCH 019/153] boot: pass factory-reset flags when sealing keys, explicit PCR handles Seal keys takes flag that indicates a reprovisioning scenario, which happens during factory reset. Signed-off-by: Maciej Borzecki --- boot/export_test.go | 7 +++ boot/makebootable.go | 39 ++++++++++++----- boot/makebootable_test.go | 68 +++++++++++++++++++++++++++-- boot/seal.go | 91 +++++++++++++++++++++++++++++++------- boot/seal_test.go | 92 ++++++++++++++++++++++++++++++--------- 5 files changed, 246 insertions(+), 51 deletions(-) diff --git a/boot/export_test.go b/boot/export_test.go index 50b6809e2da..3e65e7d1ca9 100644 --- a/boot/export_test.go +++ b/boot/export_test.go @@ -77,6 +77,7 @@ var ( type BootAssetsMap = bootAssetsMap type BootCommandLines = bootCommandLines type TrackedAsset = trackedAsset +type SealKeyToModeenvFlags = sealKeyToModeenvFlags func (t *TrackedAsset) Equals(blName, name, hash string) error { equal := t.hash == hash && @@ -134,6 +135,12 @@ func MockSeedReadSystemEssential(f func(seedDir, label string, essentialTypes [] } } +func MockSecbootPCRHandleOfSealedKey(f func(p string) (uint32, error)) (restore func()) { + restore = testutil.Backup(&secbootPCRHandleOfSealedKey) + secbootPCRHandleOfSealedKey = f + return restore +} + func (o *TrustedAssetsUpdateObserver) InjectChangedAsset(blName, assetName, hash string, recovery bool) { ta := &trackedAsset{ blName: blName, diff --git a/boot/makebootable.go b/boot/makebootable.go index 4956f1a0859..3b185ea1631 100644 --- a/boot/makebootable.go +++ b/boot/makebootable.go @@ -290,16 +290,11 @@ func MakeRecoverySystemBootable(rootdir string, relativeRecoverySystemDir string return nil } -// MakeRunnableSystem is like MakeBootableImage in that it sets up a system to -// be able to boot, but is unique in that it is intended to be called from UC20 -// install mode and makes the run system bootable (hence it is called -// "runnable"). -// Note that this function does not update the recovery bootloader env to -// actually transition to run mode here, that is left to the caller via -// something like boot.EnsureNextBootToRunMode(). This is to enable separately -// setting up a run system and actually transitioning to it, with hooks, etc. -// running in between. -func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { +type makeRunnableOptions struct { + AfterReset bool +} + +func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver, makeOpts makeRunnableOptions) error { if model.Grade() == asserts.ModelGradeUnset { return fmt.Errorf("internal error: cannot make pre-UC20 system runnable") } @@ -457,8 +452,11 @@ func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *Tru } if sealer != nil { + flags := sealKeyToModeenvFlags{ + FactoryReset: makeOpts.AfterReset, + } // seal the encryption key to the parameters specified in modeenv - if err := sealKeyToModeenv(sealer.dataEncryptionKey, sealer.saveEncryptionKey, model, modeenv); err != nil { + if err := sealKeyToModeenv(sealer.dataEncryptionKey, sealer.saveEncryptionKey, model, modeenv, flags); err != nil { return err } } @@ -470,3 +468,22 @@ func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *Tru } return nil } + +// MakeRunnableSystem is like MakeBootableImage in that it sets up a system to +// be able to boot, but is unique in that it is intended to be called from UC20 +// install mode and makes the run system bootable (hence it is called +// "runnable"). +// Note that this function does not update the recovery bootloader env to +// actually transition to run mode here, that is left to the caller via +// something like boot.EnsureNextBootToRunMode(). This is to enable separately +// setting up a run system and actually transitioning to it, with hooks, etc. +// running in between. +func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { + return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{}) +} + +func MakeRunnableSystemAfterReset(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { + return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{ + AfterReset: true, + }) +} diff --git a/boot/makebootable_test.go b/boot/makebootable_test.go index c6b87d841d0..189f2d0b6a9 100644 --- a/boot/makebootable_test.go +++ b/boot/makebootable_test.go @@ -457,7 +457,7 @@ func (s *makeBootable20Suite) TestMakeSystemRunnable16Fails(c *C) { c.Assert(err, ErrorMatches, `internal error: cannot make pre-UC20 system runnable`) } -func (s *makeBootable20Suite) TestMakeSystemRunnable20(c *C) { +func (s *makeBootable20Suite) testMakeSystemRunnable20(c *C, factoryReset bool) { bootloader.Force(nil) model := boottest.MakeMockUC20Model() @@ -584,10 +584,30 @@ version: 5.0 restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error { provisionCalls++ c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth")) - c.Check(mode, Equals, secboot.TPMProvisionFull) + if factoryReset { + c.Check(mode, Equals, secboot.TPMPartialReprovision) + } else { + c.Check(mode, Equals, secboot.TPMProvisionFull) + } return nil }) defer restore() + + pcrHandleOfKeyCalls := 0 + restore = boot.MockSecbootPCRHandleOfSealedKey(func(p string) (uint32, error) { + pcrHandleOfKeyCalls++ + c.Check(provisionCalls, Equals, 0) + if !factoryReset { + c.Errorf("unexpected call in non-factory-reset scenario") + return 0, fmt.Errorf("unexpected call") + } + c.Check(p, Equals, + filepath.Join(s.rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) + // trigger use of alt handles + return secboot.FallbackObjectPCRPolicyCounterHandle, nil + }) + defer restore() + // set mock key sealing sealKeysCalls := 0 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { @@ -597,10 +617,32 @@ version: 5.0 case 1: c.Check(keys, HasLen, 1) c.Check(keys[0].Key, DeepEquals, myKey) + c.Check(keys[0].KeyFile, Equals, + filepath.Join(s.rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key")) + if factoryReset { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle) + } else { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle) + } case 2: c.Check(keys, HasLen, 2) c.Check(keys[0].Key, DeepEquals, myKey) c.Check(keys[1].Key, DeepEquals, myKey2) + c.Check(keys[0].KeyFile, Equals, + filepath.Join(s.rootdir, + "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key")) + if factoryReset { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle) + c.Check(keys[1].KeyFile, Equals, + filepath.Join(s.rootdir, + "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory")) + + } else { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle) + c.Check(keys[1].KeyFile, Equals, + filepath.Join(s.rootdir, + "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) + } default: c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) } @@ -647,7 +689,11 @@ version: 5.0 }) defer restore() - err = boot.MakeRunnableSystem(model, bootWith, obs) + if !factoryReset { + err = boot.MakeRunnableSystem(model, bootWith, obs) + } else { + err = boot.MakeRunnableSystemAfterReset(model, bootWith, obs) + } c.Assert(err, IsNil) // also do the logical thing and make the next boot go to run mode @@ -734,6 +780,12 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty c.Check(provisionCalls, Equals, 1) // make sure SealKey was called for the run object and the fallback object c.Check(sealKeysCalls, Equals, 2) + // PCR handle checks + if factoryReset { + c.Check(pcrHandleOfKeyCalls, Equals, 1) + } else { + c.Check(pcrHandleOfKeyCalls, Equals, 0) + } // make sure the marker file for sealed key was created c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys"), testutil.FilePresent) @@ -742,6 +794,16 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty c.Check(filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "boot-chains"), testutil.FilePresent) } +func (s *makeBootable20Suite) TestMakeSystemRunnable20Install(c *C) { + const factoryReset = false + s.testMakeSystemRunnable20(c, factoryReset) +} + +func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryReset(c *C) { + const factoryReset = true + s.testMakeSystemRunnable20(c, factoryReset) +} + func (s *makeBootable20Suite) TestMakeRunnableSystem20ModeInstallBootConfigErr(c *C) { bootloader.Force(nil) diff --git a/boot/seal.go b/boot/seal.go index 66ec456f195..a3f312ff69b 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -49,6 +49,7 @@ var ( secbootSealKeys = secboot.SealKeys secbootSealKeysWithFDESetupHook = secboot.SealKeysWithFDESetupHook secbootResealKeys = secboot.ResealKeys + secbootPCRHandleOfSealedKey = secboot.PCRHandleOfSealedKey seedReadSystemEssential = seed.ReadSystemEssential ) @@ -102,10 +103,16 @@ func recoveryBootChainsFileUnder(rootdir string) string { return filepath.Join(dirs.SnapFDEDirUnder(rootdir), "recovery-boot-chains") } +type sealKeyToModeenvFlags struct { + // FactoryReset indicates that the sealing is happening during factory + // reset. + FactoryReset bool +} + // sealKeyToModeenv seals the supplied keys to the parameters specified // in modeenv. // It assumes to be invoked in install mode. -func sealKeyToModeenv(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv) error { +func sealKeyToModeenv(key, saveKey keys.EncryptionKey, model *asserts.Model, modeenv *Modeenv, flags sealKeyToModeenvFlags) error { // make sure relevant locations exist for _, p := range []string{ InitramfsSeedEncryptionKeyDir, @@ -124,10 +131,10 @@ func sealKeyToModeenv(key, saveKey keys.EncryptionKey, model *asserts.Model, mod return fmt.Errorf("cannot check for fde-setup hook %v", err) } if hasHook { - return sealKeyToModeenvUsingFDESetupHook(key, saveKey, modeenv) + return sealKeyToModeenvUsingFDESetupHook(key, saveKey, modeenv, flags) } - return sealKeyToModeenvUsingSecboot(key, saveKey, modeenv) + return sealKeyToModeenvUsingSecboot(key, saveKey, modeenv, flags) } func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest { @@ -140,7 +147,15 @@ func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest { } } -func fallbackKeySealRequests(key, saveKey keys.EncryptionKey) []secboot.SealKeyRequest { +func fallbackKeySealRequests(key, saveKey keys.EncryptionKey, factoryReset bool) []secboot.SealKeyRequest { + saveFallbackKey := filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key") + + if factoryReset { + // factory reset uses alternative sealed key location, such that + // until we boot into the run mode, both sealed keys are present + // on disk + saveFallbackKey = filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory") + } return []secboot.SealKeyRequest{ { Key: key, @@ -150,12 +165,12 @@ func fallbackKeySealRequests(key, saveKey keys.EncryptionKey) []secboot.SealKeyR { Key: saveKey, KeyName: "ubuntu-save", - KeyFile: filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + KeyFile: saveFallbackKey, }, } } -func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv *Modeenv) error { +func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv *Modeenv, flags sealKeyToModeenvFlags) error { // XXX: Move the auxKey creation to a more generic place, see // PR#10123 for a possible way of doing this. However given // that the equivalent key for the TPM case is also created in @@ -171,7 +186,8 @@ func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv AuxKey: auxKey, AuxKeyFile: filepath.Join(InstallHostFDESaveDir, "aux-key"), } - skrs := append(runKeySealRequests(key), fallbackKeySealRequests(key, saveKey)...) + factoryReset := flags.FactoryReset + skrs := append(runKeySealRequests(key), fallbackKeySealRequests(key, saveKey, factoryReset)...) if err := secbootSealKeysWithFDESetupHook(RunFDESetupHook, skrs, ¶ms); err != nil { return err } @@ -183,7 +199,7 @@ func sealKeyToModeenvUsingFDESetupHook(key, saveKey keys.EncryptionKey, modeenv return nil } -func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Modeenv) error { +func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Modeenv, flags sealKeyToModeenvFlags) error { // build the recovery mode boot chain rbl, err := bootloader.Find(InitramfsUbuntuSeedDir, &bootloader.Options{ Role: bootloader.RoleRecovery, @@ -241,17 +257,43 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode return fmt.Errorf("cannot generate key for signing dynamic authorization policies: %v", err) } + runObjectKeyPCRHandle := uint32(secboot.RunObjectPCRPolicyCounterHandle) + fallbackObjectKeyPCRHandle := uint32(secboot.FallbackObjectPCRPolicyCounterHandle) + if flags.FactoryReset { + // during factory reset we may need to rotate the PCR handles, + // seal the new keys using a new set of handles such that the + // old sealed ubuntu-save key is still usable + needAlt, err := needAltPCRHandles() + if err != nil { + return err + } + if needAlt { + logger.Noticef("using alternative PCR handles") + runObjectKeyPCRHandle = secboot.AltRunObjectPCRPolicyCounterHandle + fallbackObjectKeyPCRHandle = secboot.AltFallbackObjectPCRPolicyCounterHandle + } + } + // we are preparing a new system, hence the TPM needs to be provisioned lockoutAuthFile := filepath.Join(InstallHostFDESaveDir, "tpm-lockout-auth") - if err := secbootProvisionTPM(secboot.TPMProvisionFull, lockoutAuthFile); err != nil { + tpmProvisionMode := secboot.TPMProvisionFull + if flags.FactoryReset { + tpmProvisionMode = secboot.TPMPartialReprovision + } + if err := secbootProvisionTPM(tpmProvisionMode, lockoutAuthFile); err != nil { return err } - if err := sealRunObjectKeys(key, pbc, authKey, roleToBlName); err != nil { + // TODO: refactor sealing functions to take a struct instead of so many + // parameters + err = sealRunObjectKeys(key, pbc, authKey, roleToBlName, flags.FactoryReset, runObjectKeyPCRHandle) + if err != nil { return err } - if err := sealFallbackObjectKeys(key, saveKey, rpbc, authKey, roleToBlName); err != nil { + err = sealFallbackObjectKeys(key, saveKey, rpbc, authKey, roleToBlName, flags.FactoryReset, + fallbackObjectKeyPCRHandle) + if err != nil { return err } @@ -272,7 +314,18 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode return nil } -func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string) error { +func needAltPCRHandles() (bool, error) { + saveFallbackKey := filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key") + // inspect the PCR handle of the ubuntu-save fallback key + handle, err := secbootPCRHandleOfSealedKey(saveFallbackKey) + if err != nil { + return false, err + } + logger.Noticef("sealed key %v PCR handle: %#x", saveFallbackKey, handle) + return handle == secboot.FallbackObjectPCRPolicyCounterHandle, nil +} + +func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, partialReprovisionTPM bool, pcrHandle uint32) error { modelParams, err := sealKeyModelParams(pbc, roleToBlName) if err != nil { return fmt.Errorf("cannot prepare for key sealing: %v", err) @@ -282,8 +335,10 @@ func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKe ModelParams: modelParams, TPMPolicyAuthKey: authKey, TPMPolicyAuthKeyFile: filepath.Join(InstallHostFDESaveDir, "tpm-policy-auth-key"), - PCRPolicyCounterHandle: secboot.RunObjectPCRPolicyCounterHandle, + PCRPolicyCounterHandle: pcrHandle, } + + logger.Debugf("sealing run key with PCR handle: %#x", sealKeyParams.PCRPolicyCounterHandle) // The run object contains only the ubuntu-data key; the ubuntu-save key // is then stored inside the encrypted data partition, so that the normal run // path only unseals one object because unsealing is expensive. @@ -296,7 +351,7 @@ func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKe return nil } -func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string) error { +func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, partialReprovisionTPM bool, pcrHandle uint32) error { // also seal the keys to the recovery bootchains as a fallback modelParams, err := sealKeyModelParams(pbc, roleToBlName) if err != nil { @@ -305,12 +360,16 @@ func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBoot sealKeyParams := &secboot.SealKeysParams{ ModelParams: modelParams, TPMPolicyAuthKey: authKey, - PCRPolicyCounterHandle: secboot.FallbackObjectPCRPolicyCounterHandle, + PCRPolicyCounterHandle: pcrHandle, } + logger.Debugf("sealing fallback key with PCR handle: %#x", sealKeyParams.PCRPolicyCounterHandle) // The fallback object contains the ubuntu-data and ubuntu-save keys. The // key files are stored on ubuntu-seed, separate from ubuntu-data so they // can be used if ubuntu-data and ubuntu-boot are corrupted or unavailable. - if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey), sealKeyParams); err != nil { + + // XXX find a better name + factoryReset := partialReprovisionTPM + if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey, factoryReset), sealKeyParams); err != nil { return fmt.Errorf("cannot seal the fallback encryption keys: %v", err) } diff --git a/boot/seal_test.go b/boot/seal_test.go index f3c0c6300c1..c81032053c5 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -100,15 +100,43 @@ func mockGadgetSeedSnap(c *C, files [][]string) *seed.Snap { } func (s *sealSuite) TestSealKeyToModeenv(c *C) { - for _, tc := range []struct { - sealErr error - provisionErr error - err string + for idx, tc := range []struct { + sealErr error + provisionErr error + factoryReset bool + pcrHandleOfKey uint32 + pcrHandleOfKeyErr error + usesAltPCR bool + err string + expProvisionCalls int + expSealCalls int + expPCRHandleOfKeyCalls int }{ - {sealErr: nil, err: ""}, - {sealErr: errors.New("seal error"), err: "cannot seal the encryption keys: seal error"}, - {provisionErr: errors.New("provision error"), sealErr: errors.New("unexpected call"), err: "provision error"}, + { + sealErr: nil, err: "", + expProvisionCalls: 1, expSealCalls: 2, + }, { + sealErr: nil, factoryReset: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, + usesAltPCR: true, + expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, + }, { + sealErr: nil, factoryReset: true, pcrHandleOfKey: secboot.AltFallbackObjectPCRPolicyCounterHandle, + usesAltPCR: false, + expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, + }, { + sealErr: nil, factoryReset: true, pcrHandleOfKeyErr: errors.New("PCR handle error"), + err: "PCR handle error", + expPCRHandleOfKeyCalls: 1, + }, { + sealErr: errors.New("seal error"), err: "cannot seal the encryption keys: seal error", + expProvisionCalls: 1, expSealCalls: 1, + }, { + provisionErr: errors.New("provision error"), sealErr: errors.New("unexpected call"), + err: "provision error", + expProvisionCalls: 1, + }, } { + c.Logf("tc %v", idx) rootdir := c.MkDir() dirs.SetRootDir(rootdir) defer dirs.SetRootDir("") @@ -170,11 +198,24 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { restore = boot.MockSecbootProvisionTPM(func(mode secboot.TPMProvisionMode, lockoutAuthFile string) error { provisionCalls++ c.Check(lockoutAuthFile, Equals, filepath.Join(boot.InstallHostFDESaveDir, "tpm-lockout-auth")) - c.Check(mode, Equals, secboot.TPMProvisionFull) + if tc.factoryReset { + c.Check(mode, Equals, secboot.TPMPartialReprovision) + } else { + c.Check(mode, Equals, secboot.TPMProvisionFull) + } return tc.provisionErr }) defer restore() + pcrHandleOfKeyCalls := 0 + restore = boot.MockSecbootPCRHandleOfSealedKey(func(p string) (uint32, error) { + pcrHandleOfKeyCalls++ + c.Check(provisionCalls, Equals, 0) + c.Check(p, Equals, filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) + return tc.pcrHandleOfKey, tc.pcrHandleOfKeyErr + }) + defer restore() + // set mock key sealing sealKeysCalls := 0 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { @@ -187,13 +228,27 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key") c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}}) + if !tc.usesAltPCR { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle) + } else { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle) + } case 2: // the fallback object seals the ubuntu-data and the ubuntu-save keys c.Check(params.TPMPolicyAuthKeyFile, Equals, "") dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-data.recovery.sealed-key") saveKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key") + if tc.factoryReset { + // during factory reset we use a different key location + saveKeyFile = filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory") + } c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}}) + if !tc.usesAltPCR { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle) + } else { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle) + } default: c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) } @@ -244,17 +299,12 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { }) defer restore() - err = boot.SealKeyToModeenv(myKey, myKey2, model, modeenv) - c.Assert(provisionCalls, Equals, 1) - if tc.provisionErr != nil { - c.Assert(sealKeysCalls, Equals, 0) - } else { - if tc.sealErr != nil { - c.Assert(sealKeysCalls, Equals, 1) - } else { - c.Assert(sealKeysCalls, Equals, 2) - } - } + err = boot.SealKeyToModeenv(myKey, myKey2, model, modeenv, boot.SealKeyToModeenvFlags{ + FactoryReset: tc.factoryReset, + }) + c.Check(pcrHandleOfKeyCalls, Equals, tc.expPCRHandleOfKeyCalls) + c.Check(provisionCalls, Equals, tc.expProvisionCalls) + c.Check(sealKeysCalls, Equals, tc.expSealCalls) if tc.err == "" { c.Assert(err, IsNil) } else { @@ -1603,7 +1653,7 @@ func (s *sealSuite) TestSealToModeenvWithFdeHookHappy(c *C) { key := keys.EncryptionKey{1, 2, 3, 4} saveKey := keys.EncryptionKey{5, 6, 7, 8} - err := boot.SealKeyToModeenv(key, saveKey, model, modeenv) + err := boot.SealKeyToModeenv(key, saveKey, model, modeenv, boot.SealKeyToModeenvFlags{}) c.Assert(err, IsNil) // check that runFDESetupHook was called the expected way c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{ @@ -1648,7 +1698,7 @@ func (s *sealSuite) TestSealToModeenvWithFdeHookSad(c *C) { saveKey := keys.EncryptionKey{5, 6, 7, 8} model := boottest.MakeMockUC20Model() - err := boot.SealKeyToModeenv(key, saveKey, model, modeenv) + err := boot.SealKeyToModeenv(key, saveKey, model, modeenv, boot.SealKeyToModeenvFlags{}) c.Assert(err, ErrorMatches, "hook failed") marker := filepath.Join(dirs.SnapFDEDirUnder(boot.InstallHostWritableDir), "sealed-keys") c.Check(marker, testutil.FileAbsent) From f2ff73672d36a2ec82aa2f2917e93d83ab29f263 Mon Sep 17 00:00:00 2001 From: Pawel Stolowski Date: Fri, 3 Jun 2022 12:44:32 +0200 Subject: [PATCH 020/153] many: print valid/invalid status on snap validate --monitor ... * Print valid/invalid status on snap validate --monitor <...> * Also return validation tracking result for enforce (but ignore it in the client). * Removed unneeded return statement. Co-authored-by: Miguel Pires * Add checks for result when using enforce mode. Co-authored-by: Miguel Pires --- client/validate.go | 17 ++-- client/validate_test.go | 51 ++++++++-- cmd/snap/cmd_validate.go | 16 +++- cmd/snap/cmd_validate_test.go | 12 +-- daemon/api_validate.go | 79 +++++++++------- daemon/api_validate_test.go | 113 ++++++++++++++++++----- daemon/export_api_validate_test.go | 5 +- overlord/assertstate/assertstate.go | 17 ++-- overlord/assertstate/assertstate_test.go | 53 ++++++++--- tests/main/snap-validate-basic/task.yaml | 8 +- 10 files changed, 271 insertions(+), 100 deletions(-) diff --git a/client/validate.go b/client/validate.go index e13adf10ecf..ab3c5af6fb6 100644 --- a/client/validate.go +++ b/client/validate.go @@ -77,10 +77,12 @@ func (client *Client) ForgetValidationSet(accountID, name string, sequence int) return nil } -// ApplyValidationSet applies the given validation set identified by account and name. -func (client *Client) ApplyValidationSet(accountID, name string, opts *ValidateApplyOptions) error { +// ApplyValidationSet applies the given validation set identified by account and name and returns +// the new validation set tracking info. For monitoring mode the returned res may indicate invalid +// state. +func (client *Client) ApplyValidationSet(accountID, name string, opts *ValidateApplyOptions) (res *ValidationSetResult, err error) { if accountID == "" || name == "" { - return xerrors.Errorf("cannot apply validation set without account ID and name") + return nil, xerrors.Errorf("cannot apply validation set without account ID and name") } data := &postValidationSetData{ @@ -91,14 +93,15 @@ func (client *Client) ApplyValidationSet(accountID, name string, opts *ValidateA var body bytes.Buffer if err := json.NewEncoder(&body).Encode(data); err != nil { - return err + return nil, err } path := fmt.Sprintf("/v2/validation-sets/%s/%s", accountID, name) - if _, err := client.doSync("POST", path, nil, nil, &body, nil); err != nil { + + if _, err := client.doSync("POST", path, nil, nil, &body, &res); err != nil { fmt := "cannot apply validation set: %w" - return xerrors.Errorf(fmt, err) + return nil, xerrors.Errorf(fmt, err) } - return nil + return res, nil } // ListValidationsSets queries all validation sets. diff --git a/client/validate_test.go b/client/validate_test.go index e7265c40d40..ba96c77f134 100644 --- a/client/validate_test.go +++ b/client/validate_test.go @@ -78,13 +78,22 @@ func (cs *clientSuite) TestListValidationsSets(c *check.C) { }) } -func (cs *clientSuite) TestApplyValidationSet(c *check.C) { +func (cs *clientSuite) TestApplyValidationSetMonitor(c *check.C) { cs.rsp = `{ "type": "sync", - "status-code": 200 + "status-code": 200, + "result": {"account-id": "foo", "name": "bar", "mode": "monitor", "sequence": 3, "valid": true} }` opts := &client.ValidateApplyOptions{Mode: "monitor", Sequence: 3} - c.Assert(cs.cli.ApplyValidationSet("foo", "bar", opts), check.IsNil) + vs, err := cs.cli.ApplyValidationSet("foo", "bar", opts) + c.Assert(err, check.IsNil) + c.Check(vs, check.DeepEquals, &client.ValidationSetResult{ + AccountID: "foo", + Name: "bar", + Mode: "monitor", + Sequence: 3, + Valid: true, + }) c.Check(cs.req.Method, check.Equals, "POST") c.Check(cs.req.URL.Path, check.Equals, "/v2/validation-sets/foo/bar") body, err := ioutil.ReadAll(cs.req.Body) @@ -99,11 +108,41 @@ func (cs *clientSuite) TestApplyValidationSet(c *check.C) { }) } +func (cs *clientSuite) TestApplyValidationSetEnforce(c *check.C) { + cs.rsp = `{ + "type": "sync", + "status-code": 200, + "result": {"account-id": "foo", "name": "bar", "mode": "enforce", "sequence": 3, "valid": true} + }` + opts := &client.ValidateApplyOptions{Mode: "enforce", Sequence: 3} + vs, err := cs.cli.ApplyValidationSet("foo", "bar", opts) + c.Assert(err, check.IsNil) + c.Check(vs, check.DeepEquals, &client.ValidationSetResult{ + AccountID: "foo", + Name: "bar", + Mode: "enforce", + Sequence: 3, + Valid: true, + }) + c.Check(cs.req.Method, check.Equals, "POST") + c.Check(cs.req.URL.Path, check.Equals, "/v2/validation-sets/foo/bar") + body, err := ioutil.ReadAll(cs.req.Body) + c.Assert(err, check.IsNil) + var req map[string]interface{} + err = json.Unmarshal(body, &req) + c.Assert(err, check.IsNil) + c.Assert(req, check.DeepEquals, map[string]interface{}{ + "action": "apply", + "mode": "enforce", + "sequence": float64(3), + }) +} + func (cs *clientSuite) TestApplyValidationSetError(c *check.C) { cs.status = 500 cs.rsp = errorResponseJSON opts := &client.ValidateApplyOptions{Mode: "monitor"} - err := cs.cli.ApplyValidationSet("foo", "bar", opts) + _, err := cs.cli.ApplyValidationSet("foo", "bar", opts) c.Assert(err, check.ErrorMatches, "cannot apply validation set: failed") c.Check(cs.req.Method, check.Equals, "POST") c.Check(cs.req.URL.Path, check.Equals, "/v2/validation-sets/foo/bar") @@ -111,9 +150,9 @@ func (cs *clientSuite) TestApplyValidationSetError(c *check.C) { func (cs *clientSuite) TestApplyValidationSetInvalidArgs(c *check.C) { opts := &client.ValidateApplyOptions{} - err := cs.cli.ApplyValidationSet("", "bar", opts) + _, err := cs.cli.ApplyValidationSet("", "bar", opts) c.Assert(err, check.ErrorMatches, `cannot apply validation set without account ID and name`) - err = cs.cli.ApplyValidationSet("", "bar", opts) + _, err = cs.cli.ApplyValidationSet("", "bar", opts) c.Assert(err, check.ErrorMatches, `cannot apply validation set without account ID and name`) } diff --git a/cmd/snap/cmd_validate.go b/cmd/snap/cmd_validate.go index f1a1169d8c4..dc78a37440b 100644 --- a/cmd/snap/cmd_validate.go +++ b/cmd/snap/cmd_validate.go @@ -33,9 +33,8 @@ import ( type cmdValidate struct { clientMixin - Monitor bool `long:"monitor"` - // XXX: enforce mode is not supported yet - Enforce bool `long:"enforce" hidden:"yes"` + Monitor bool `long:"monitor"` + Enforce bool `long:"enforce"` Forget bool `long:"forget"` Positional struct { ValidationSet string `positional-arg-name:""` @@ -152,7 +151,16 @@ func (cmd *cmdValidate) Execute(args []string) error { Mode: action, Sequence: seq, } - return cmd.client.ApplyValidationSet(accountID, name, opts) + res, err := cmd.client.ApplyValidationSet(accountID, name, opts) + if err != nil { + return err + } + // only print valid/invalid status for monitor mode; enforce fails with an error if invalid + // and otherwise has no output. + if action == "monitor" { + fmt.Fprintln(Stdout, fmtValid(res)) + } + return nil } // no validation set argument, print list with extended info diff --git a/cmd/snap/cmd_validate_test.go b/cmd/snap/cmd_validate_test.go index 118d191f427..c5418dfe8ea 100644 --- a/cmd/snap/cmd_validate_test.go +++ b/cmd/snap/cmd_validate_test.go @@ -117,27 +117,27 @@ func (s *validateSuite) TestValidateInvalidArgs(c *check.C) { } func (s *validateSuite) TestValidateMonitor(c *check.C) { - s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": []}`, "monitor", 0)) + s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": {"account-id":"foo","name":"bar","mode":"monitor","sequence":3,"valid":false}}`, "monitor", 0)) rest, err := main.Parser(main.Client()).ParseArgs([]string{"validate", "--monitor", "foo/bar"}) c.Assert(err, check.IsNil) c.Check(rest, check.HasLen, 0) c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, "") + c.Check(s.Stdout(), check.Equals, "invalid\n") } func (s *validateSuite) TestValidateMonitorPinned(c *check.C) { - s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": []}`, "monitor", 3)) + s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": {"account-id":"foo","name":"bar","mode":"monitor","sequence":3,"valid":true}}}`, "monitor", 3)) rest, err := main.Parser(main.Client()).ParseArgs([]string{"validate", "--monitor", "foo/bar=3"}) c.Assert(err, check.IsNil) c.Check(rest, check.HasLen, 0) c.Check(s.Stderr(), check.Equals, "") - c.Check(s.Stdout(), check.Equals, "") + c.Check(s.Stdout(), check.Equals, "valid\n") } func (s *validateSuite) TestValidateEnforce(c *check.C) { - s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": []}`, "enforce", 0)) + s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": {"account-id":"foo","name":"bar","mode":"enforce","sequence":3,"valid":true}}}`, "enforce", 0)) rest, err := main.Parser(main.Client()).ParseArgs([]string{"validate", "--enforce", "foo/bar"}) c.Assert(err, check.IsNil) @@ -147,7 +147,7 @@ func (s *validateSuite) TestValidateEnforce(c *check.C) { } func (s *validateSuite) TestValidateEnforcePinned(c *check.C) { - s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": []}`, "enforce", 5)) + s.RedirectClientToTestServer(makeFakeValidationSetPostHandler(c, `{"type": "sync", "status-code": 200, "result": {"account-id":"foo","name":"bar","mode":"enforce","sequence":3,"valid":true}}}`, "enforce", 5)) rest, err := main.Parser(main.Client()).ParseArgs([]string{"validate", "--enforce", "foo/bar=5"}) c.Assert(err, check.IsNil) diff --git a/daemon/api_validate.go b/daemon/api_validate.go index 509c4d0112c..2cda27c71c7 100644 --- a/daemon/api_validate.go +++ b/daemon/api_validate.go @@ -143,6 +143,38 @@ var checkInstalledSnaps = func(vsets *snapasserts.ValidationSets, snaps []*snapa return vsets.CheckInstalledSnaps(snaps, ignoreValidation) } +func validationSetResultFromTracking(st *state.State, tr *assertstate.ValidationSetTracking) (*validationSetResult, error) { + var sequence int + if tr.PinnedAt > 0 { + sequence = tr.PinnedAt + } else { + sequence = tr.Current + } + modeStr, err := modeString(tr.Mode) + if err != nil { + return nil, err + } + + sets, err := validationSetForAssert(st, tr.AccountID, tr.Name, sequence) + if err != nil { + return nil, err + } + snaps, _, err := snapstate.InstalledSnaps(st) + if err != nil { + return nil, err + } + + validErr := checkInstalledSnaps(sets, snaps, nil) + return &validationSetResult{ + AccountID: tr.AccountID, + Name: tr.Name, + PinnedAt: tr.PinnedAt, + Mode: modeStr, + Sequence: tr.Current, + Valid: validErr == nil, + }, nil +} + func getValidationSet(c *Command, r *http.Request, user *auth.UserState) Response { vars := muxVars(r) accountID := vars["account"] @@ -185,37 +217,12 @@ func getValidationSet(c *Command, r *http.Request, user *auth.UserState) Respons return InternalError("accessing validation sets failed: %v", err) } - modeStr, err := modeString(tr.Mode) - if err != nil { - return InternalError(err.Error()) - } - // evaluate against installed snaps - - if tr.PinnedAt > 0 { - sequence = tr.PinnedAt - } else { - sequence = tr.Current - } - sets, err := validationSetForAssert(st, tr.AccountID, tr.Name, sequence) + res, err := validationSetResultFromTracking(st, &tr) if err != nil { return InternalError(err.Error()) } - snaps, _, err := snapstate.InstalledSnaps(st) - if err != nil { - return InternalError(err.Error()) - } - - validErr := checkInstalledSnaps(sets, snaps, nil) - res := validationSetResult{ - AccountID: tr.AccountID, - Name: tr.Name, - PinnedAt: tr.PinnedAt, - Mode: modeStr, - Sequence: tr.Current, - Valid: validErr == nil, - } - return SyncResponse(res) + return SyncResponse(*res) } type validationSetApplyRequest struct { @@ -286,11 +293,16 @@ func updateValidationSet(st *state.State, accountID, name string, reqMode string return enforceValidationSet(st, accountID, name, sequence, userID) } - err := assertstateMonitorValidationSet(st, accountID, name, sequence, userID) + tr, err := assertstateMonitorValidationSet(st, accountID, name, sequence, userID) if err != nil { return BadRequest("cannot get validation set assertion for %v: %v", assertstate.ValidationSetKey(accountID, name), err) } - return SyncResponse(nil) + + res, err := validationSetResultFromTracking(st, tr) + if err != nil { + return InternalError(err.Error()) + } + return SyncResponse(*res) } // forgetValidationSet forgets the validation set. @@ -398,11 +410,16 @@ func enforceValidationSet(st *state.State, accountID, name string, sequence, use if err != nil { return InternalError(err.Error()) } - if err := assertstateEnforceValidationSet(st, accountID, name, sequence, userID, snaps, ignoreValidation); err != nil { + tr, err := assertstateEnforceValidationSet(st, accountID, name, sequence, userID, snaps, ignoreValidation) + if err != nil { // XXX: provide more specific error kinds? This would probably require // assertstate.ValidationSetAssertionForEnforce tuning too. return BadRequest("cannot enforce validation set: %v", err) } - return SyncResponse(nil) + res, err := validationSetResultFromTracking(st, tr) + if err != nil { + return InternalError(err.Error()) + } + return SyncResponse(*res) } diff --git a/daemon/api_validate_test.go b/daemon/api_validate_test.go index dee5ac62877..4dd5b539894 100644 --- a/daemon/api_validate_test.go +++ b/daemon/api_validate_test.go @@ -599,12 +599,27 @@ func (s *apiValidationSetsSuite) TestGetValidationSetPinnedNotFound(c *check.C) } func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedLocalOnly(c *check.C) { + st := s.d.Overlord().State() + st.Lock() + s.mockValidationSetsTracking(st) + assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) + as := s.mockAssert(c, "bar", "99") + err := assertstate.Add(st, as) + st.Unlock() + c.Assert(err, check.IsNil) + var called int - restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) error { + restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) (*assertstate.ValidationSetTracking, error) { c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) c.Assert(name, check.Equals, "bar") c.Assert(sequence, check.Equals, 99) called++ + return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, PinnedAt: 99, Current: 99999}, nil + }) + defer restore() + + restore = daemon.MockCheckInstalledSnaps(func(vsets *snapasserts.ValidationSets, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + // nil indicates successful validation return nil }) defer restore() @@ -615,12 +630,21 @@ func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModePinnedLocalOnl rsp := s.syncReq(c, req, nil) c.Assert(rsp.Status, check.Equals, 200) + res := rsp.Result.(daemon.ValidationSetResult) + c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ + AccountID: s.dev1acct.AccountID(), + Name: "bar", + Mode: "monitor", + PinnedAt: 99, + Sequence: 99999, + Valid: true, + }) c.Check(called, check.Equals, 1) } func (s *apiValidationSetsSuite) TestApplyValidationSetMonitorModeError(c *check.C) { - restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) error { - return fmt.Errorf("boom") + restore := daemon.MockAssertstateMonitorValidationSet(func(st *state.State, accountID, name string, sequence, userID int) (*assertstate.ValidationSetTracking, error) { + return nil, fmt.Errorf("boom") }) defer restore() @@ -755,22 +779,28 @@ func (s *apiValidationSetsSuite) TestApplyValidationSetUnsupportedAction(c *chec } func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceMode(c *check.C) { + st := s.d.Overlord().State() + st.Lock() + defer st.Unlock() + + s.mockValidationSetsTracking(st) + assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) + as := s.mockAssert(c, "bar", "99") + err := assertstate.Add(st, as) + c.Assert(err, check.IsNil) + var called int - restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { c.Check(ignoreValidation, check.HasLen, 0) c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) c.Assert(name, check.Equals, "bar") c.Assert(sequence, check.Equals, 0) c.Check(userID, check.Equals, 0) called++ - return nil + return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, Current: 99}, nil }) defer restore() - st := s.d.Overlord().State() - st.Lock() - defer st.Unlock() - snapstate.Set(st, "snap-b", &snapstate.SnapState{ Active: true, Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, @@ -787,12 +817,30 @@ func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceMode(c *check.C) { rsp := s.syncReq(c, req, nil) c.Assert(rsp.Status, check.Equals, 200) + res := rsp.Result.(daemon.ValidationSetResult) + c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ + AccountID: s.dev1acct.AccountID(), + Name: "bar", + Mode: "enforce", + Sequence: 99, + Valid: true, + }) c.Check(called, check.Equals, 1) } func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeIgnoreValidationOK(c *check.C) { + st := s.d.Overlord().State() + st.Lock() + defer st.Unlock() + + s.mockValidationSetsTracking(st) + assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) + as := s.mockAssert(c, "bar", "99") + err := assertstate.Add(st, as) + c.Assert(err, check.IsNil) + var called int - restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { c.Check(ignoreValidation, check.DeepEquals, map[string]bool{"snap-b": true}) c.Check(snaps, testutil.DeepUnsortedMatches, []*snapasserts.InstalledSnap{ snapasserts.NewInstalledSnap("snap-b", "yOqKhntON3vR7kwEbVPsILm7bUViPDzz", snap.R("1"))}) @@ -801,14 +849,10 @@ func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeIgnoreValidati c.Assert(sequence, check.Equals, 0) c.Check(userID, check.Equals, 0) called++ - return nil + return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, Current: 99}, nil }) defer restore() - st := s.d.Overlord().State() - st.Lock() - defer st.Unlock() - snapstate.Set(st, "snap-b", &snapstate.SnapState{ Active: true, Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, @@ -826,25 +870,39 @@ func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeIgnoreValidati rsp := s.syncReq(c, req, nil) c.Assert(rsp.Status, check.Equals, 200) + res := rsp.Result.(daemon.ValidationSetResult) + c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ + AccountID: s.dev1acct.AccountID(), + Name: "bar", + Mode: "enforce", + Sequence: 99, + Valid: true, + }) c.Check(called, check.Equals, 1) } func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeSpecificSequence(c *check.C) { + st := s.d.Overlord().State() + st.Lock() + defer st.Unlock() + + s.mockValidationSetsTracking(st) + assertstatetest.AddMany(st, s.dev1acct, s.acct1Key) + as := s.mockAssert(c, "bar", "5") + err := assertstate.Add(st, as) + c.Assert(err, check.IsNil) + var called int - restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { c.Assert(accountID, check.Equals, s.dev1acct.AccountID()) c.Assert(name, check.Equals, "bar") c.Assert(sequence, check.Equals, 5) c.Check(userID, check.Equals, 0) called++ - return nil + return &assertstate.ValidationSetTracking{AccountID: accountID, Name: name, Mode: assertstate.Enforce, PinnedAt: 5, Current: 5}, nil }) defer restore() - st := s.d.Overlord().State() - st.Lock() - defer st.Unlock() - snapstate.Set(st, "snap-b", &snapstate.SnapState{ Active: true, Sequence: []*snap.SideInfo{{RealName: "snap-b", Revision: snap.R(1), SnapID: "yOqKhntON3vR7kwEbVPsILm7bUViPDzz"}}, @@ -861,12 +919,21 @@ func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeSpecificSequen rsp := s.syncReq(c, req, nil) c.Assert(rsp.Status, check.Equals, 200) + res := rsp.Result.(daemon.ValidationSetResult) + c.Check(res, check.DeepEquals, daemon.ValidationSetResult{ + AccountID: s.dev1acct.AccountID(), + Name: "bar", + Mode: "enforce", + PinnedAt: 5, + Sequence: 5, + Valid: true, + }) c.Check(called, check.Equals, 1) } func (s *apiValidationSetsSuite) TestApplyValidationSetEnforceModeError(c *check.C) { - restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { - return fmt.Errorf("boom") + restore := daemon.MockAssertstateEnforceValidationSet(func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error) { + return nil, fmt.Errorf("boom") }) defer restore() diff --git a/daemon/export_api_validate_test.go b/daemon/export_api_validate_test.go index f0c5ae4f912..e8543add4a1 100644 --- a/daemon/export_api_validate_test.go +++ b/daemon/export_api_validate_test.go @@ -21,6 +21,7 @@ package daemon import ( "github.com/snapcore/snapd/asserts/snapasserts" + "github.com/snapcore/snapd/overlord/assertstate" "github.com/snapcore/snapd/overlord/state" ) @@ -36,7 +37,7 @@ func MockCheckInstalledSnaps(f func(vsets *snapasserts.ValidationSets, snaps []* } } -func MockAssertstateMonitorValidationSet(f func(st *state.State, accountID, name string, sequence int, userID int) error) func() { +func MockAssertstateMonitorValidationSet(f func(st *state.State, accountID, name string, sequence int, userID int) (*assertstate.ValidationSetTracking, error)) func() { old := assertstateMonitorValidationSet assertstateMonitorValidationSet = f return func() { @@ -44,7 +45,7 @@ func MockAssertstateMonitorValidationSet(f func(st *state.State, accountID, name } } -func MockAssertstateEnforceValidationSet(f func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error) func() { +func MockAssertstateEnforceValidationSet(f func(st *state.State, accountID, name string, sequence int, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*assertstate.ValidationSetTracking, error)) func() { old := assertstateEnforceValidationSet assertstateEnforceValidationSet = f return func() { diff --git a/overlord/assertstate/assertstate.go b/overlord/assertstate/assertstate.go index 6dc39be615d..6d1d91b20d9 100644 --- a/overlord/assertstate/assertstate.go +++ b/overlord/assertstate/assertstate.go @@ -750,10 +750,10 @@ func validationSetAssertionForEnforce(st *state.State, accountID, name string, s // EnforceValidationSet tries to fetch the given validation set and enforce it. // If all validation sets constrains are satisfied, the current validation sets // tracking state is saved in validation sets history. -func EnforceValidationSet(st *state.State, accountID, name string, sequence, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { +func EnforceValidationSet(st *state.State, accountID, name string, sequence, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) (*ValidationSetTracking, error) { _, current, err := validationSetAssertionForEnforce(st, accountID, name, sequence, userID, snaps, ignoreValidation) if err != nil { - return err + return nil, err } tr := ValidationSetTracking{ @@ -766,20 +766,21 @@ func EnforceValidationSet(st *state.State, accountID, name string, sequence, use } UpdateValidationSet(st, &tr) - return addCurrentTrackingToValidationSetsHistory(st) + err = addCurrentTrackingToValidationSetsHistory(st) + return &tr, err } // MonitorValidationSet tries to fetch the given validation set and monitor it. // The current validation sets tracking state is saved in validation sets history. -func MonitorValidationSet(st *state.State, accountID, name string, sequence int, userID int) error { +func MonitorValidationSet(st *state.State, accountID, name string, sequence int, userID int) (*ValidationSetTracking, error) { pinned := sequence > 0 opts := ResolveOptions{AllowLocalFallback: true} as, local, err := validationSetAssertionForMonitor(st, accountID, name, sequence, pinned, userID, &opts) if err != nil { - return fmt.Errorf("cannot get validation set assertion for %v: %v", ValidationSetKey(accountID, name), err) + return nil, fmt.Errorf("cannot get validation set assertion for %v: %v", ValidationSetKey(accountID, name), err) } - tr := ValidationSetTracking{ + tr := &ValidationSetTracking{ AccountID: accountID, Name: name, Mode: Monitor, @@ -789,8 +790,8 @@ func MonitorValidationSet(st *state.State, accountID, name string, sequence int, LocalOnly: local, } - UpdateValidationSet(st, &tr) - return addCurrentTrackingToValidationSetsHistory(st) + UpdateValidationSet(st, tr) + return tr, addCurrentTrackingToValidationSetsHistory(st) } // TemporaryDB returns a temporary database stacked on top of the assertions diff --git a/overlord/assertstate/assertstate_test.go b/overlord/assertstate/assertstate_test.go index ab245bd4082..596677b12dd 100644 --- a/overlord/assertstate/assertstate_test.go +++ b/overlord/assertstate/assertstate_test.go @@ -3357,7 +3357,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertion(c *C) { } sequence := 2 - err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) + tracking, err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) c.Assert(err, IsNil) // and it has been committed @@ -3372,6 +3372,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertion(c *C) { var tr assertstate.ValidationSetTracking c.Assert(assertstate.GetValidationSet(s.state, s.dev1Acct.AccountID(), "bar", &tr), IsNil) + c.Check(tr, DeepEquals, *tracking) c.Check(tr, DeepEquals, assertstate.ValidationSetTracking{ AccountID: s.dev1Acct.AccountID(), @@ -3417,7 +3418,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionUpdate(c *C) { } sequence := 2 - err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) + tracking, err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) c.Assert(err, IsNil) // and it has been committed @@ -3439,6 +3440,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionUpdate(c *C) { PinnedAt: 2, Current: 2, }) + c.Check(tr, DeepEquals, *tracking) // and it was added to the history vshist, err := assertstate.ValidationSetsHistory(st) @@ -3455,7 +3457,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionUpdate(c *C) { // not pinned sequence = 0 - err = assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) + tracking, err = assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) c.Assert(err, IsNil) c.Assert(assertstate.GetValidationSet(s.state, s.dev1Acct.AccountID(), "bar", &tr), IsNil) @@ -3466,6 +3468,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionUpdate(c *C) { PinnedAt: 0, Current: 2, }) + c.Check(tr, DeepEquals, *tracking) } func (s *assertMgrSuite) TestEnforceValidationSetAssertionPinToOlderSequence(c *C) { @@ -3492,7 +3495,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionPinToOlderSequence(c * } sequence := 2 - err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) + tracking, err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) c.Assert(err, IsNil) // and it has been committed @@ -3514,10 +3517,11 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionPinToOlderSequence(c * PinnedAt: 2, Current: 2, }) + c.Check(tr, DeepEquals, *tracking) // pin to older sequence = 1 - err = assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) + tracking, err = assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) c.Assert(err, IsNil) c.Assert(assertstate.GetValidationSet(s.state, s.dev1Acct.AccountID(), "bar", &tr), IsNil) @@ -3529,6 +3533,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionPinToOlderSequence(c * // and current points at the latest sequence available Current: 2, }) + c.Check(tr, DeepEquals, *tracking) } func (s *assertMgrSuite) TestEnforceValidationSetAssertionAfterMonitor(c *C) { @@ -3565,7 +3570,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionAfterMonitor(c *C) { c.Assert(s.storeSigning.Add(vsetAs), IsNil) sequence := 2 - err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) + tracking, err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, nil) c.Assert(err, IsNil) // and it has been committed @@ -3588,6 +3593,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionAfterMonitor(c *C) { PinnedAt: 2, Current: 2, }) + c.Check(tr, DeepEquals, *tracking) } func (s *assertMgrSuite) TestEnforceValidationSetAssertionIgnoreValidation(c *C) { @@ -3613,13 +3619,13 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionIgnoreValidation(c *C) sequence := 2 ignoreValidation := map[string]bool{} - err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, ignoreValidation) + _, err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, ignoreValidation) wrongRevErr, ok := err.(*snapasserts.ValidationSetsValidationError) c.Assert(ok, Equals, true) c.Check(wrongRevErr.WrongRevisionSnaps["foo"], NotNil) ignoreValidation["foo"] = true - err = assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, ignoreValidation) + tracking, err := assertstate.EnforceValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0, snaps, ignoreValidation) c.Assert(err, IsNil) // and it has been committed @@ -3642,6 +3648,7 @@ func (s *assertMgrSuite) TestEnforceValidationSetAssertionIgnoreValidation(c *C) PinnedAt: 2, Current: 2, }) + c.Check(tr, DeepEquals, *tracking) } func (s *assertMgrSuite) TestMonitorValidationSet(c *C) { @@ -3662,8 +3669,15 @@ func (s *assertMgrSuite) TestMonitorValidationSet(c *C) { c.Assert(s.storeSigning.Add(vsetAs), IsNil) sequence := 2 - err := assertstate.MonitorValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0) + tr1, err := assertstate.MonitorValidationSet(st, s.dev1Acct.AccountID(), "bar", sequence, 0) c.Assert(err, IsNil) + c.Check(tr1, DeepEquals, &assertstate.ValidationSetTracking{ + AccountID: s.dev1Acct.AccountID(), + Name: "bar", + Mode: assertstate.Monitor, + PinnedAt: 2, + Current: 2, + }) // and it has been committed _, err = assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{ @@ -3720,8 +3734,25 @@ func (s *assertMgrSuite) TestForgetValidationSet(c *C) { vsetAs2 := s.validationSetAssert(c, "baz", "2", "2", "required", "1") c.Assert(s.storeSigning.Add(vsetAs2), IsNil) - c.Assert(assertstate.MonitorValidationSet(st, s.dev1Acct.AccountID(), "bar", 2, 0), IsNil) - c.Assert(assertstate.MonitorValidationSet(st, s.dev1Acct.AccountID(), "baz", 2, 0), IsNil) + tr1, err := assertstate.MonitorValidationSet(st, s.dev1Acct.AccountID(), "bar", 2, 0) + c.Assert(err, IsNil) + c.Check(tr1, DeepEquals, &assertstate.ValidationSetTracking{ + AccountID: s.dev1Acct.AccountID(), + Name: "bar", + Mode: assertstate.Monitor, + PinnedAt: 2, + Current: 2, + }) + + tr2, err := assertstate.MonitorValidationSet(st, s.dev1Acct.AccountID(), "baz", 2, 0) + c.Assert(err, IsNil) + c.Check(tr2, DeepEquals, &assertstate.ValidationSetTracking{ + AccountID: s.dev1Acct.AccountID(), + Name: "baz", + Mode: assertstate.Monitor, + PinnedAt: 2, + Current: 2, + }) c.Assert(assertstate.ForgetValidationSet(st, s.dev1Acct.AccountID(), "bar"), IsNil) diff --git a/tests/main/snap-validate-basic/task.yaml b/tests/main/snap-validate-basic/task.yaml index ef3d787bfd0..21b3e7c5c02 100644 --- a/tests/main/snap-validate-basic/task.yaml +++ b/tests/main/snap-validate-basic/task.yaml @@ -45,7 +45,7 @@ execute: | snap validate "$ACCOUNT_ID"/bar=1 2>&1 | MATCH "^invalid" echo "Checking that monitor mode is supported with a pinned sequence and local validation-set" - snap validate --monitor "$ACCOUNT_ID"/bar=1 + snap validate --monitor "$ACCOUNT_ID"/bar=1 | MATCH "^invalid" snap validate | MATCH "$ACCOUNT_ID/bar=1 +monitor +1 +invalid" echo "Checking that validation-set is valid or invalid depending on presence of the snap" @@ -72,7 +72,7 @@ execute: | snap validate 2>&1 | MATCH "No validations are available" echo "Checking that monitor mode is supported with a local validation-set (non-pinned)" - snap validate --monitor "$ACCOUNT_ID"/bar + snap validate --monitor "$ACCOUNT_ID"/bar | MATCH "^invalid" snap validate | MATCH "$ACCOUNT_ID/bar +monitor +1 +invalid" snap validate "$ACCOUNT_ID"/bar=1 | MATCH "^invalid" snap validate "$ACCOUNT_ID"/bar | MATCH "^invalid" @@ -80,3 +80,7 @@ execute: | snap validate | MATCH "$ACCOUNT_ID/bar +monitor +1 +valid" snap validate "$ACCOUNT_ID"/bar=1 | MATCH "^valid" snap validate "$ACCOUNT_ID"/bar | MATCH "^valid" + + echo "Check that --monitor mode report valid status when enabled" + snap validate --monitor "$ACCOUNT_ID"/bar | MATCH "^valid" + snap validate | MATCH "$ACCOUNT_ID/bar +monitor +1 +valid" From 18815d6f138aa5d823232bfeb939feb04a93bf9c Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 3 Jun 2022 13:47:01 +0200 Subject: [PATCH 021/153] boot: simplify alt PCR handle check Signed-off-by: Maciej Borzecki --- boot/makebootable_test.go | 2 +- boot/seal.go | 10 +++++----- boot/seal_test.go | 15 ++++++--------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/boot/makebootable_test.go b/boot/makebootable_test.go index 189f2d0b6a9..7121a7d9449 100644 --- a/boot/makebootable_test.go +++ b/boot/makebootable_test.go @@ -603,7 +603,7 @@ version: 5.0 } c.Check(p, Equals, filepath.Join(s.rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) - // trigger use of alt handles + // trigger use of alt handles as current key is using the main handle return secboot.FallbackObjectPCRPolicyCounterHandle, nil }) defer restore() diff --git a/boot/seal.go b/boot/seal.go index a3f312ff69b..c3fc4713f8e 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -263,11 +263,11 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode // during factory reset we may need to rotate the PCR handles, // seal the new keys using a new set of handles such that the // old sealed ubuntu-save key is still usable - needAlt, err := needAltPCRHandles() + usesAlt, err := usesAltPCRHandles() if err != nil { return err } - if needAlt { + if !usesAlt { logger.Noticef("using alternative PCR handles") runObjectKeyPCRHandle = secboot.AltRunObjectPCRPolicyCounterHandle fallbackObjectKeyPCRHandle = secboot.AltFallbackObjectPCRPolicyCounterHandle @@ -314,15 +314,15 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode return nil } -func needAltPCRHandles() (bool, error) { +func usesAltPCRHandles() (bool, error) { saveFallbackKey := filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key") // inspect the PCR handle of the ubuntu-save fallback key handle, err := secbootPCRHandleOfSealedKey(saveFallbackKey) if err != nil { return false, err } - logger.Noticef("sealed key %v PCR handle: %#x", saveFallbackKey, handle) - return handle == secboot.FallbackObjectPCRPolicyCounterHandle, nil + logger.Noticef("fallback sealed key %v PCR handle: %#x", saveFallbackKey, handle) + return handle == secboot.AltFallbackObjectPCRPolicyCounterHandle, nil } func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, partialReprovisionTPM bool, pcrHandle uint32) error { diff --git a/boot/seal_test.go b/boot/seal_test.go index c81032053c5..df7aec13c99 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -106,7 +106,6 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { factoryReset bool pcrHandleOfKey uint32 pcrHandleOfKeyErr error - usesAltPCR bool err string expProvisionCalls int expSealCalls int @@ -117,11 +116,9 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { expProvisionCalls: 1, expSealCalls: 2, }, { sealErr: nil, factoryReset: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, - usesAltPCR: true, expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, }, { sealErr: nil, factoryReset: true, pcrHandleOfKey: secboot.AltFallbackObjectPCRPolicyCounterHandle, - usesAltPCR: false, expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, }, { sealErr: nil, factoryReset: true, pcrHandleOfKeyErr: errors.New("PCR handle error"), @@ -228,10 +225,10 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { dataKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-boot/device/fde/ubuntu-data.sealed-key") c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}}) - if !tc.usesAltPCR { - c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle) - } else { + if tc.pcrHandleOfKey == secboot.FallbackObjectPCRPolicyCounterHandle { c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltRunObjectPCRPolicyCounterHandle) + } else { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.RunObjectPCRPolicyCounterHandle) } case 2: // the fallback object seals the ubuntu-data and the ubuntu-save keys @@ -244,10 +241,10 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { saveKeyFile = filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory") } c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}}) - if !tc.usesAltPCR { - c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle) - } else { + if tc.pcrHandleOfKey == secboot.FallbackObjectPCRPolicyCounterHandle { c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle) + } else { + c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle) } default: c.Errorf("unexpected additional call to secboot.SealKeys (call # %d)", sealKeysCalls) From 3f6b247f66c39144e4d86119938e822f654a9f5e Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Mon, 6 Jun 2022 12:34:54 +0300 Subject: [PATCH 022/153] snap-confine: advise the user to check the snapd.apparmor service If the AppArmor profiles for snap-confine or snap-update-ns are not loaded, there's a chance that this is due to the snapd.apparmor systemd unit being disabled. Provide this information as a hint to the user. --- cmd/libsnap-confine-private/apparmor-support.c | 9 ++++++++- cmd/snap-confine/snap-confine.c | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cmd/libsnap-confine-private/apparmor-support.c b/cmd/libsnap-confine-private/apparmor-support.c index 9b8f580e4a9..c12eed1b849 100644 --- a/cmd/libsnap-confine-private/apparmor-support.c +++ b/cmd/libsnap-confine-private/apparmor-support.c @@ -128,7 +128,14 @@ sc_maybe_aa_change_onexec(struct sc_apparmor *apparmor, const char *profile) int aa_change_onexec_errno = errno; if (secure_getenv("SNAPPY_LAUNCHER_INSIDE_TESTS") == NULL) { errno = aa_change_onexec_errno; - die("cannot change profile for the next exec call"); + if (errno == ENOENT) { + fprintf(stderr, "missing profile %s.\n" + "Please make sure that the snapd.apparmor service is enabled\n", + profile); + exit(1); + } else { + die("cannot change profile for the next exec call"); + } } } #endif // ifdef HAVE_APPARMOR diff --git a/cmd/snap-confine/snap-confine.c b/cmd/snap-confine/snap-confine.c index 2877299dda2..c8fb39453d8 100644 --- a/cmd/snap-confine/snap-confine.c +++ b/cmd/snap-confine/snap-confine.c @@ -393,9 +393,11 @@ int main(int argc, char **argv) // id is non-root. This protects against, for example, unprivileged // users trying to leverage the snap-confine in the core snap to // escalate privileges. + errno = 0; // errno is insignificant here die("snap-confine has elevated permissions and is not confined" " but should be. Refusing to continue to avoid" - " permission escalation attacks"); + " permission escalation attacks\n" + "Please make sure that the snapd.apparmor service is enabled."); } log_startup_stage("snap-confine mount namespace start"); From 93591ae673053c001e28cbe7790e6b9626f0c04b Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 6 Jun 2022 14:10:40 +0200 Subject: [PATCH 023/153] boot: tweak test naming Thanks to @mardy for the suggestion Signed-off-by: Maciej Borzecki --- boot/seal_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/boot/seal_test.go b/boot/seal_test.go index df7aec13c99..a342202d644 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -106,13 +106,13 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { factoryReset bool pcrHandleOfKey uint32 pcrHandleOfKeyErr error - err string + expErr string expProvisionCalls int expSealCalls int expPCRHandleOfKeyCalls int }{ { - sealErr: nil, err: "", + sealErr: nil, expErr: "", expProvisionCalls: 1, expSealCalls: 2, }, { sealErr: nil, factoryReset: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, @@ -122,14 +122,14 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, }, { sealErr: nil, factoryReset: true, pcrHandleOfKeyErr: errors.New("PCR handle error"), - err: "PCR handle error", + expErr: "PCR handle error", expPCRHandleOfKeyCalls: 1, }, { - sealErr: errors.New("seal error"), err: "cannot seal the encryption keys: seal error", + sealErr: errors.New("seal error"), expErr: "cannot seal the encryption keys: seal error", expProvisionCalls: 1, expSealCalls: 1, }, { provisionErr: errors.New("provision error"), sealErr: errors.New("unexpected call"), - err: "provision error", + expErr: "provision error", expProvisionCalls: 1, }, } { @@ -302,10 +302,10 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { c.Check(pcrHandleOfKeyCalls, Equals, tc.expPCRHandleOfKeyCalls) c.Check(provisionCalls, Equals, tc.expProvisionCalls) c.Check(sealKeysCalls, Equals, tc.expSealCalls) - if tc.err == "" { + if tc.expErr == "" { c.Assert(err, IsNil) } else { - c.Assert(err, ErrorMatches, tc.err) + c.Assert(err, ErrorMatches, tc.expErr) continue } From b33813ed7644abe8d39cff0c68ffe5449266cbf3 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 6 Jun 2022 14:11:02 +0200 Subject: [PATCH 024/153] boot: tweak naming, drop unused parameter Missed when cleaning up the patches for upstreaming Signed-off-by: Maciej Borzecki --- boot/seal.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/boot/seal.go b/boot/seal.go index c3fc4713f8e..c524378ff50 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -286,7 +286,7 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode // TODO: refactor sealing functions to take a struct instead of so many // parameters - err = sealRunObjectKeys(key, pbc, authKey, roleToBlName, flags.FactoryReset, runObjectKeyPCRHandle) + err = sealRunObjectKeys(key, pbc, authKey, roleToBlName, runObjectKeyPCRHandle) if err != nil { return err } @@ -325,7 +325,7 @@ func usesAltPCRHandles() (bool, error) { return handle == secboot.AltFallbackObjectPCRPolicyCounterHandle, nil } -func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, partialReprovisionTPM bool, pcrHandle uint32) error { +func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, pcrHandle uint32) error { modelParams, err := sealKeyModelParams(pbc, roleToBlName) if err != nil { return fmt.Errorf("cannot prepare for key sealing: %v", err) @@ -351,7 +351,7 @@ func sealRunObjectKeys(key keys.EncryptionKey, pbc predictableBootChains, authKe return nil } -func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, partialReprovisionTPM bool, pcrHandle uint32) error { +func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBootChains, authKey *ecdsa.PrivateKey, roleToBlName map[bootloader.Role]string, factoryReset bool, pcrHandle uint32) error { // also seal the keys to the recovery bootchains as a fallback modelParams, err := sealKeyModelParams(pbc, roleToBlName) if err != nil { @@ -367,8 +367,6 @@ func sealFallbackObjectKeys(key, saveKey keys.EncryptionKey, pbc predictableBoot // key files are stored on ubuntu-seed, separate from ubuntu-data so they // can be used if ubuntu-data and ubuntu-boot are corrupted or unavailable. - // XXX find a better name - factoryReset := partialReprovisionTPM if err := secbootSealKeys(fallbackKeySealRequests(key, saveKey, factoryReset), sealKeyParams); err != nil { return fmt.Errorf("cannot seal the fallback encryption keys: %v", err) } From f0574927f7e7edf51375ee141ee9d101c3487a1b Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Fri, 3 Jun 2022 18:00:03 +0800 Subject: [PATCH 025/153] interfaces/builtin: remove the name=org.freedesktop.DBus restriction in cups-control AppArmor rules This seems to have been included erroneously: the org.freedesktop.DBus bus name is owned by the bus itself rather than any particular peer connected to the bus. While it makes sense to use in a rule dealing with communication with the bus, it seems to block legitimate traffic in this case. --- interfaces/builtin/cups_control.go | 6 +++--- interfaces/builtin/cups_control_test.go | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interfaces/builtin/cups_control.go b/interfaces/builtin/cups_control.go index 6a44b386677..3c625d3685f 100644 --- a/interfaces/builtin/cups_control.go +++ b/interfaces/builtin/cups_control.go @@ -81,7 +81,7 @@ dbus (send) bus=system path=/org/cups/cupsd/Notifier interface=org.cups.cupsd.Notifier - peer=(name=org.freedesktop.DBus,label=unconfined), + peer=(label=unconfined), # Allow daemon to send signals to its snap_daemon processes capability kill, @@ -96,7 +96,7 @@ dbus (send) bus=system path=/org/cups/cupsd/Notifier interface=org.cups.cupsd.Notifier - peer=(name=org.freedesktop.DBus,label=###PLUG_SECURITY_TAGS###), + peer=(label=###PLUG_SECURITY_TAGS###), ` const cupsControlConnectedPlugAppArmor = ` @@ -111,7 +111,7 @@ dbus (receive) bus=system path=/org/cups/cupsd/Notifier interface=org.cups.cupsd.Notifier - peer=(name=org.freedesktop.DBus,label=###SLOT_SECURITY_TAGS###), + peer=(label=###SLOT_SECURITY_TAGS###), ` type cupsControlInterface struct { diff --git a/interfaces/builtin/cups_control_test.go b/interfaces/builtin/cups_control_test.go index a220b598dd0..8b5227e5d5f 100644 --- a/interfaces/builtin/cups_control_test.go +++ b/interfaces/builtin/cups_control_test.go @@ -106,7 +106,7 @@ func (s *cupsControlSuite) TestAppArmorSpecCore(c *C) { c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow communicating with the cups server for printing and configuration.") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "#include ") - c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.freedesktop.DBus,label=\"snap.provider.app\"") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=\"snap.provider.app\"") c.Assert(spec.SnippetForTag("snap.provider.app"), Not(testutil.Contains), "# Allow daemon access to create the CUPS socket") // provider to consumer on core for PermanentSlot @@ -120,7 +120,7 @@ func (s *cupsControlSuite) TestAppArmorSpecCore(c *C) { spec = &apparmor.Specification{} c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.providerSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) - c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "peer=(name=org.freedesktop.DBus,label=\"snap.consumer.app\"") + c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "peer=(label=\"snap.consumer.app\"") } func (s *cupsControlSuite) TestAppArmorSpecClassic(c *C) { @@ -133,7 +133,7 @@ func (s *cupsControlSuite) TestAppArmorSpecClassic(c *C) { c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow communicating with the cups server for printing and configuration.") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "#include ") - c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.freedesktop.DBus,label=\"{unconfined,/usr/sbin/cupsd,cupsd}\"") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=\"{unconfined,/usr/sbin/cupsd,cupsd}\"") c.Assert(spec.SnippetForTag("snap.provider.app"), Not(testutil.Contains), "# Allow daemon access to create the CUPS socket") // core to consumer on classic is empty for PermanentSlot @@ -152,7 +152,7 @@ func (s *cupsControlSuite) TestAppArmorSpecClassic(c *C) { c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow communicating with the cups server for printing and configuration.") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "#include ") - c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(name=org.freedesktop.DBus,label=\"snap.provider.app\"") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "peer=(label=\"snap.provider.app\"") c.Assert(spec.SnippetForTag("snap.provider.app"), Not(testutil.Contains), "# Allow daemon access to create the CUPS socket") // provider to consumer on classic for PermanentSlot @@ -166,7 +166,7 @@ func (s *cupsControlSuite) TestAppArmorSpecClassic(c *C) { spec = &apparmor.Specification{} c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.providerSlot), IsNil) c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.provider.app"}) - c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "peer=(name=org.freedesktop.DBus,label=\"snap.consumer.app\"") + c.Assert(spec.SnippetForTag("snap.provider.app"), testutil.Contains, "peer=(label=\"snap.consumer.app\"") } func (s *cupsControlSuite) TestStaticInfo(c *C) { From 9e3fe37ef6fe1a82e56f770dea9b34a35545e63d Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 7 Jun 2022 12:56:39 +0300 Subject: [PATCH 026/153] many: make warnings on snapd.apparmor more informative Apply suggestions from code review Co-authored-by: Maciej Borzecki --- cmd/libsnap-confine-private/apparmor-support.c | 2 +- cmd/snap-confine/snap-confine.c | 2 +- overlord/ifacestate/ifacemgr.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/libsnap-confine-private/apparmor-support.c b/cmd/libsnap-confine-private/apparmor-support.c index c12eed1b849..be2a54d2c7c 100644 --- a/cmd/libsnap-confine-private/apparmor-support.c +++ b/cmd/libsnap-confine-private/apparmor-support.c @@ -130,7 +130,7 @@ sc_maybe_aa_change_onexec(struct sc_apparmor *apparmor, const char *profile) errno = aa_change_onexec_errno; if (errno == ENOENT) { fprintf(stderr, "missing profile %s.\n" - "Please make sure that the snapd.apparmor service is enabled\n", + "Please make sure that the snapd.apparmor service is enabled and started\n", profile); exit(1); } else { diff --git a/cmd/snap-confine/snap-confine.c b/cmd/snap-confine/snap-confine.c index c8fb39453d8..8e3116c0fb8 100644 --- a/cmd/snap-confine/snap-confine.c +++ b/cmd/snap-confine/snap-confine.c @@ -397,7 +397,7 @@ int main(int argc, char **argv) die("snap-confine has elevated permissions and is not confined" " but should be. Refusing to continue to avoid" " permission escalation attacks\n" - "Please make sure that the snapd.apparmor service is enabled."); + "Please make sure that the snapd.apparmor service is enabled and started."); } log_startup_stage("snap-confine mount namespace start"); diff --git a/overlord/ifacestate/ifacemgr.go b/overlord/ifacestate/ifacemgr.go index 0475039cfa8..01e7e22c4df 100644 --- a/overlord/ifacestate/ifacemgr.go +++ b/overlord/ifacestate/ifacemgr.go @@ -176,7 +176,7 @@ func (m *InterfaceManager) StartUp() error { } if snapdAppArmorServiceIsDisabled() { s.Warnf(`the snapd.apparmor service is disabled; snap applications will likely not start. -Run "systemctl enable snapd.apparmor" to correct this.`) +Run "systemctl enable --now snapd.apparmor" to correct this.`) } ifacerepo.Replace(s, m.repo) From 298c65c21370ec02b53e74c8e58533c640696185 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Mon, 23 May 2022 09:23:27 +0200 Subject: [PATCH 027/153] dirs: add directory entry for systemd root dir --- dirs/dirs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dirs/dirs.go b/dirs/dirs.go index 2a83f879850..188c0c5ea2f 100644 --- a/dirs/dirs.go +++ b/dirs/dirs.go @@ -99,6 +99,7 @@ var ( SnapRuntimeServicesDir string SnapUserServicesDir string SnapSystemdConfDir string + SnapSystemdDir string SnapDesktopFilesDir string SnapDesktopIconsDir string SnapPolkitPolicyDir string From cc01bbdd9fdcde749abaf9bf24e0426fde2beeac Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Mon, 23 May 2022 09:24:04 +0200 Subject: [PATCH 028/153] overlord/ifacestate: add code to handle journal quota groups, which adds the correct mount layout in confinement options when a snap has a quota group with a journal quota set --- overlord/ifacestate/handlers.go | 87 +++++++++++++++++++++++++++------ overlord/ifacestate/helpers.go | 2 +- 2 files changed, 73 insertions(+), 16 deletions(-) diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index e23209e1d6b..26aac1c43bd 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -22,6 +22,7 @@ package ifacestate import ( "errors" "fmt" + "path" "reflect" "sort" "strings" @@ -29,26 +30,82 @@ import ( "gopkg.in/tomb.v2" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/interfaces" "github.com/snapcore/snapd/interfaces/hotplug" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/overlord/hookstate" + "github.com/snapcore/snapd/overlord/servicestate" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/quota" "github.com/snapcore/snapd/timings" ) var snapstateFinishRestart = snapstate.FinishRestart +func getQuotaGroup(st *state.State, snapName string) *quota.Group { + allGrps, err := servicestate.AllQuotas(st) + if err != nil { + return nil + } + + for _, grp := range allGrps { + for _, name := range grp.Snaps { + if name == snapName { + return grp + } + } + } + return nil +} + +func addJournalQuotaLayout(quotaGroup *quota.Group, layouts *[]snap.Layout) error { + if quotaGroup.JournalLimit == nil { + return nil + } + + // We need to bind mount the journal namespace folder ontop of + // the journal folder + // /etc/systemd/journal. -> /etc/systemd/journal + journalLayout := snap.Layout{ + Bind: path.Join(dirs.SnapSystemdDir, fmt.Sprintf("journal.snap-%s", quotaGroup.Name)), + Path: path.Join(dirs.SnapSystemdDir, "journal"), + Mode: 0755, + } + *layouts = append(*layouts, journalLayout) + return nil +} + +func getExtraLayouts(st *state.State, snapName string) ([]snap.Layout, error) { + var extraLayouts []snap.Layout + if quotaGrp := getQuotaGroup(st, snapName); quotaGrp != nil { + if err := addJournalQuotaLayout(quotaGrp, &extraLayouts); err != nil { + return extraLayouts, err + } + } + + return extraLayouts, nil +} + // confinementOptions returns interfaces.ConfinementOptions from snapstate.Flags. -func confinementOptions(flags snapstate.Flags) interfaces.ConfinementOptions { +func confinementOptions(flags snapstate.Flags, extraLayouts []snap.Layout) interfaces.ConfinementOptions { return interfaces.ConfinementOptions{ - DevMode: flags.DevMode, - JailMode: flags.JailMode, - Classic: flags.Classic, + DevMode: flags.DevMode, + JailMode: flags.JailMode, + Classic: flags.Classic, + ExtraLayouts: extraLayouts, + } +} + +func buildConfinementOptions(st *state.State, snapName string, flags snapstate.Flags) interfaces.ConfinementOptions { + extraLayouts, err := getExtraLayouts(st, snapName) + if err != nil { + logger.Noticef("cannot get extra mount layouts of snap %q: %s", snapName, err) } + return confinementOptions(flags, extraLayouts) } func (m *InterfaceManager) setupAffectedSnaps(task *state.Task, affectingSnap string, affectedSnaps []string, tm timings.Measurer) error { @@ -72,7 +129,7 @@ func (m *InterfaceManager) setupAffectedSnaps(task *state.Task, affectingSnap st if err := addImplicitSlots(st, affectedSnapInfo); err != nil { return err } - opts := confinementOptions(snapst.Flags) + opts := buildConfinementOptions(st, affectedSnapInfo.SnapName(), snapst.Flags) if err := m.setupSnapSecurity(task, affectedSnapInfo, opts, tm); err != nil { return err } @@ -115,7 +172,7 @@ func (m *InterfaceManager) doSetupProfiles(task *state.Task, tomb *tomb.Tomb) er return nil } - opts := confinementOptions(snapsup.Flags) + opts := buildConfinementOptions(task.State(), snapInfo.SnapName(), snapsup.Flags) return m.setupProfilesForSnap(task, tomb, snapInfo, opts, perfTimings) } @@ -206,7 +263,7 @@ func (m *InterfaceManager) setupProfilesForSnap(task *state.Task, _ *tomb.Tomb, return err } affectedSnaps = append(affectedSnaps, snapInfo) - confinementOpts = append(confinementOpts, confinementOptions(snapst.Flags)) + confinementOpts = append(confinementOpts, buildConfinementOptions(st, name, snapst.Flags)) } return m.setupSecurityByBackend(task, affectedSnaps, confinementOpts, tm) @@ -297,7 +354,7 @@ func (m *InterfaceManager) undoSetupProfiles(task *state.Task, tomb *tomb.Tomb) if err != nil { return err } - opts := confinementOptions(snapst.Flags) + opts := buildConfinementOptions(task.State(), snapName, snapst.Flags) return m.setupProfilesForSnap(task, tomb, snapInfo, opts, perfTimings) } } @@ -499,12 +556,12 @@ func (m *InterfaceManager) doConnect(task *state.Task, _ *tomb.Tomb) (err error) }() if !delayedSetupProfiles { - slotOpts := confinementOptions(slotSnapst.Flags) + slotOpts := buildConfinementOptions(st, slotRef.Snap, slotSnapst.Flags) if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := confinementOptions(plugSnapst.Flags) + plugOpts := buildConfinementOptions(st, plugRef.Name, plugSnapst.Flags) if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } @@ -605,7 +662,7 @@ func (m *InterfaceManager) doDisconnect(task *state.Task, _ *tomb.Tomb) error { if err != nil { return err } - opts := confinementOptions(snapst.Flags) + opts := buildConfinementOptions(st, snapInfo.SnapName(), snapst.Flags) if err := m.setupSnapSecurity(task, snapInfo, opts, perfTimings); err != nil { return err } @@ -711,11 +768,11 @@ func (m *InterfaceManager) undoDisconnect(task *state.Task, _ *tomb.Tomb) error return err } - slotOpts := confinementOptions(slotSnapst.Flags) + slotOpts := buildConfinementOptions(st, connRef.SlotRef.Snap, slotSnapst.Flags) if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := confinementOptions(plugSnapst.Flags) + plugOpts := buildConfinementOptions(st, connRef.PlugRef.Snap, plugSnapst.Flags) if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } @@ -794,11 +851,11 @@ func (m *InterfaceManager) undoConnect(task *state.Task, _ *tomb.Tomb) error { if err != nil { return err } - slotOpts := confinementOptions(slotSnapst.Flags) + slotOpts := buildConfinementOptions(st, slotRef.Snap, slotSnapst.Flags) if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := confinementOptions(plugSnapst.Flags) + plugOpts := buildConfinementOptions(st, plugRef.Snap, plugSnapst.Flags) if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } diff --git a/overlord/ifacestate/helpers.go b/overlord/ifacestate/helpers.go index 725e5cdcc70..fc5d3c68cd0 100644 --- a/overlord/ifacestate/helpers.go +++ b/overlord/ifacestate/helpers.go @@ -177,7 +177,7 @@ func (m *InterfaceManager) regenerateAllSecurityProfiles(tm timings.Measurer) er if err := snapstate.Get(m.state, snapName, &snapst); err != nil { logger.Noticef("cannot get state of snap %q: %s", snapName, err) } - return confinementOptions(snapst.Flags) + return buildConfinementOptions(m.state, snapName, snapst.Flags) } // For each backend: From e2fa16e55192d9e67f182a67d80fec5af2aeecfd Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 7 Jun 2022 12:18:17 +0200 Subject: [PATCH 029/153] dirs: fix rebase --- dirs/dirs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/dirs/dirs.go b/dirs/dirs.go index 188c0c5ea2f..2a83f879850 100644 --- a/dirs/dirs.go +++ b/dirs/dirs.go @@ -99,7 +99,6 @@ var ( SnapRuntimeServicesDir string SnapUserServicesDir string SnapSystemdConfDir string - SnapSystemdDir string SnapDesktopFilesDir string SnapDesktopIconsDir string SnapPolkitPolicyDir string From 9fd2e81bcfeed92d312e046749e74022ecce76e0 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 7 Jun 2022 13:13:06 +0200 Subject: [PATCH 030/153] overlord/ifacestate: update docs, add unit test, reuse SnapServiceOptions in servicestate instead of looking up quota group manually --- overlord/ifacestate/export_test.go | 1 + overlord/ifacestate/handlers.go | 65 +++++++------- overlord/ifacestate/handlers_test.go | 123 ++++++++++++++++++++++----- overlord/ifacestate/helpers.go | 2 +- 4 files changed, 135 insertions(+), 56 deletions(-) diff --git a/overlord/ifacestate/export_test.go b/overlord/ifacestate/export_test.go index 9f6eefb131e..ae147661e34 100644 --- a/overlord/ifacestate/export_test.go +++ b/overlord/ifacestate/export_test.go @@ -61,6 +61,7 @@ var ( BatchConnectTasks = batchConnectTasks FirstTaskAfterBootWhenPreseeding = firstTaskAfterBootWhenPreseeding + BuildConfinementOptions = buildConfinementOptions ) type ConnectOpts = connectOpts diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index 26aac1c43bd..f05b6b4c7c9 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -22,6 +22,7 @@ package ifacestate import ( "errors" "fmt" + "log" "path" "reflect" "sort" @@ -46,27 +47,16 @@ import ( var snapstateFinishRestart = snapstate.FinishRestart -func getQuotaGroup(st *state.State, snapName string) *quota.Group { - allGrps, err := servicestate.AllQuotas(st) - if err != nil { - return nil - } - - for _, grp := range allGrps { - for _, name := range grp.Snaps { - if name == snapName { - return grp - } - } - } - return nil -} - +// addJournalQuotaLayout handles the addition of a journal quota bind mount +// in case the snap has a journal quota. This mimicks what systemd does for +// services with log namespaces. func addJournalQuotaLayout(quotaGroup *quota.Group, layouts *[]snap.Layout) error { + log.Println("3.1") if quotaGroup.JournalLimit == nil { return nil } + log.Println("3.2") // We need to bind mount the journal namespace folder ontop of // the journal folder // /etc/systemd/journal. -> /etc/systemd/journal @@ -79,10 +69,18 @@ func addJournalQuotaLayout(quotaGroup *quota.Group, layouts *[]snap.Layout) erro return nil } -func getExtraLayouts(st *state.State, snapName string) ([]snap.Layout, error) { +// getExtraLayouts helper function to dynamically calculate the extra mount layouts for +// a snap instance. These are the layouts which can change during the lifetime of a snap +// like for instance mimicking systemd journal namespace mount layouts. +func getExtraLayouts(st *state.State, snapInstanceName string) ([]snap.Layout, error) { + snapOpts, err := servicestate.SnapServiceOptions(st, snapInstanceName, nil) + if err != nil { + return nil, err + } + var extraLayouts []snap.Layout - if quotaGrp := getQuotaGroup(st, snapName); quotaGrp != nil { - if err := addJournalQuotaLayout(quotaGrp, &extraLayouts); err != nil { + if snapOpts.QuotaGroup != nil { + if err := addJournalQuotaLayout(snapOpts.QuotaGroup, &extraLayouts); err != nil { return extraLayouts, err } } @@ -100,10 +98,11 @@ func confinementOptions(flags snapstate.Flags, extraLayouts []snap.Layout) inter } } -func buildConfinementOptions(st *state.State, snapName string, flags snapstate.Flags) interfaces.ConfinementOptions { - extraLayouts, err := getExtraLayouts(st, snapName) +func buildConfinementOptions(st *state.State, snapInstanceName string, flags snapstate.Flags) interfaces.ConfinementOptions { + extraLayouts, err := getExtraLayouts(st, snapInstanceName) if err != nil { - logger.Noticef("cannot get extra mount layouts of snap %q: %s", snapName, err) + logger.Noticef("cannot get extra mount layouts of snap %q: %s", snapInstanceName, err) + log.Printf("cannot get extra mount layouts of snap %q: %s", snapInstanceName, err) } return confinementOptions(flags, extraLayouts) } @@ -129,7 +128,7 @@ func (m *InterfaceManager) setupAffectedSnaps(task *state.Task, affectingSnap st if err := addImplicitSlots(st, affectedSnapInfo); err != nil { return err } - opts := buildConfinementOptions(st, affectedSnapInfo.SnapName(), snapst.Flags) + opts := buildConfinementOptions(st, affectedSnapInfo.InstanceName(), snapst.Flags) if err := m.setupSnapSecurity(task, affectedSnapInfo, opts, tm); err != nil { return err } @@ -172,7 +171,7 @@ func (m *InterfaceManager) doSetupProfiles(task *state.Task, tomb *tomb.Tomb) er return nil } - opts := buildConfinementOptions(task.State(), snapInfo.SnapName(), snapsup.Flags) + opts := buildConfinementOptions(task.State(), snapInfo.InstanceName(), snapsup.Flags) return m.setupProfilesForSnap(task, tomb, snapInfo, opts, perfTimings) } @@ -263,7 +262,7 @@ func (m *InterfaceManager) setupProfilesForSnap(task *state.Task, _ *tomb.Tomb, return err } affectedSnaps = append(affectedSnaps, snapInfo) - confinementOpts = append(confinementOpts, buildConfinementOptions(st, name, snapst.Flags)) + confinementOpts = append(confinementOpts, buildConfinementOptions(st, snapInfo.InstanceName(), snapst.Flags)) } return m.setupSecurityByBackend(task, affectedSnaps, confinementOpts, tm) @@ -354,7 +353,7 @@ func (m *InterfaceManager) undoSetupProfiles(task *state.Task, tomb *tomb.Tomb) if err != nil { return err } - opts := buildConfinementOptions(task.State(), snapName, snapst.Flags) + opts := buildConfinementOptions(task.State(), snapInfo.InstanceName(), snapst.Flags) return m.setupProfilesForSnap(task, tomb, snapInfo, opts, perfTimings) } } @@ -556,12 +555,12 @@ func (m *InterfaceManager) doConnect(task *state.Task, _ *tomb.Tomb) (err error) }() if !delayedSetupProfiles { - slotOpts := buildConfinementOptions(st, slotRef.Snap, slotSnapst.Flags) + slotOpts := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := buildConfinementOptions(st, plugRef.Name, plugSnapst.Flags) + plugOpts := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } @@ -662,7 +661,7 @@ func (m *InterfaceManager) doDisconnect(task *state.Task, _ *tomb.Tomb) error { if err != nil { return err } - opts := buildConfinementOptions(st, snapInfo.SnapName(), snapst.Flags) + opts := buildConfinementOptions(st, snapInfo.InstanceName(), snapst.Flags) if err := m.setupSnapSecurity(task, snapInfo, opts, perfTimings); err != nil { return err } @@ -768,11 +767,11 @@ func (m *InterfaceManager) undoDisconnect(task *state.Task, _ *tomb.Tomb) error return err } - slotOpts := buildConfinementOptions(st, connRef.SlotRef.Snap, slotSnapst.Flags) + slotOpts := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := buildConfinementOptions(st, connRef.PlugRef.Snap, plugSnapst.Flags) + plugOpts := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } @@ -851,11 +850,11 @@ func (m *InterfaceManager) undoConnect(task *state.Task, _ *tomb.Tomb) error { if err != nil { return err } - slotOpts := buildConfinementOptions(st, slotRef.Snap, slotSnapst.Flags) + slotOpts := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := buildConfinementOptions(st, plugRef.Snap, plugSnapst.Flags) + plugOpts := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } diff --git a/overlord/ifacestate/handlers_test.go b/overlord/ifacestate/handlers_test.go index b87ba029832..b1179be9c90 100644 --- a/overlord/ifacestate/handlers_test.go +++ b/overlord/ifacestate/handlers_test.go @@ -20,25 +20,49 @@ package ifacestate_test import ( + "path" + . "gopkg.in/check.v1" + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/ifacestate" + "github.com/snapcore/snapd/overlord/servicestate/servicestatetest" + "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/quota" + "github.com/snapcore/snapd/snap/snaptest" ) -type handlersSuite struct{} +const snapAyaml = `name: snap-a +type: app +base: base-snap-a +` + +type handlersSuite struct { + st *state.State +} var _ = Suite(&handlersSuite{}) +func (s *handlersSuite) SetUpTest(c *C) { + s.st = state.New(nil) + dirs.SetRootDir(c.MkDir()) +} + +func (s *handlersSuite) TearDownTest(c *C) { + dirs.SetRootDir("") +} + func (s *handlersSuite) TestInSameChangeWaitChain(c *C) { - st := state.New(nil) - st.Lock() - defer st.Unlock() + s.st.Lock() + defer s.st.Unlock() // no wait chain (yet) - startT := st.NewTask("start", "...start") - intermediateT := st.NewTask("intermediateT", "...intermediateT") - searchT := st.NewTask("searchT", "...searchT") + startT := s.st.NewTask("start", "...start") + intermediateT := s.st.NewTask("intermediateT", "...intermediateT") + searchT := s.st.NewTask("searchT", "...searchT") c.Check(ifacestate.InSameChangeWaitChain(startT, searchT), Equals, false) // add (indirect) wait chain @@ -48,16 +72,15 @@ func (s *handlersSuite) TestInSameChangeWaitChain(c *C) { } func (s *handlersSuite) TestInSameChangeWaitChainDifferentChanges(c *C) { - st := state.New(nil) - st.Lock() - defer st.Unlock() + s.st.Lock() + defer s.st.Unlock() - t1 := st.NewTask("t1", "...") - chg1 := st.NewChange("chg1", "...") + t1 := s.st.NewTask("t1", "...") + chg1 := s.st.NewChange("chg1", "...") chg1.AddTask(t1) - t2 := st.NewTask("t2", "...") - chg2 := st.NewChange("chg2", "...") + t2 := s.st.NewTask("t2", "...") + chg2 := s.st.NewChange("chg2", "...") chg2.AddTask(t2) // add a cross change wait chain @@ -66,23 +89,79 @@ func (s *handlersSuite) TestInSameChangeWaitChainDifferentChanges(c *C) { } func (s *handlersSuite) TestInSameChangeWaitChainWithCycles(c *C) { - st := state.New(nil) - st.Lock() - defer st.Unlock() + s.st.Lock() + defer s.st.Unlock() // cycles like this are unexpected in practice but are easier to test than // the exponential paths situation that e.g. seed changes present. - startT := st.NewTask("start", "...start") - task1 := st.NewTask("task1", "...") + startT := s.st.NewTask("start", "...start") + task1 := s.st.NewTask("task1", "...") task1.WaitFor(startT) - task2 := st.NewTask("task2", "...") + task2 := s.st.NewTask("task2", "...") task2.WaitFor(task1) - task3 := st.NewTask("task3", "...") + task3 := s.st.NewTask("task3", "...") task3.WaitFor(task2) startT.WaitFor(task2) startT.WaitFor(task3) - unrelated := st.NewTask("unrelated", "...") + unrelated := s.st.NewTask("unrelated", "...") c.Check(ifacestate.InSameChangeWaitChain(startT, unrelated), Equals, false) } + +func mockInstalledSnap(c *C, st *state.State, snapYaml string) *snap.Info { + snapInfo := snaptest.MockSnap(c, snapYaml, &snap.SideInfo{ + Revision: snap.R(1), + }) + + snapName := snapInfo.SnapName() + si := &snap.SideInfo{RealName: snapName, SnapID: snapName + "-id", Revision: snap.R(1)} + snapstate.Set(st, snapName, &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: si.Revision, + SnapType: string(snapInfo.Type()), + }) + return snapInfo +} + +func (s *handlersSuite) TestBuildConfinementOptions(c *C) { + // Mock installed snap + s.st.Lock() + defer s.st.Unlock() + + snapInfo := mockInstalledSnap(c, s.st, snapAyaml) + flags := snapstate.Flags{} + opts := ifacestate.BuildConfinementOptions(s.st, snapInfo.InstanceName(), snapstate.Flags{}) + + c.Check(len(opts.ExtraLayouts), Equals, 0) + c.Check(opts.Classic, Equals, flags.Classic) + c.Check(opts.DevMode, Equals, flags.DevMode) + c.Check(opts.JailMode, Equals, flags.JailMode) +} + +func (s *handlersSuite) TestBuildConfinementOptionsWithLogNamespace(c *C) { + // Mock installed snap + s.st.Lock() + defer s.st.Unlock() + + tr := config.NewTransaction(s.st) + tr.Set("core", "experimental.quota-groups", true) + tr.Commit() + + snapInfo := mockInstalledSnap(c, s.st, snapAyaml) + + // Create a new quota group with a journal quota + err := servicestatetest.MockQuotaInState(s.st, "foo", "", []string{snapInfo.InstanceName()}, quota.NewResourcesBuilder().WithJournalNamespace().Build()) + c.Assert(err, IsNil) + + flags := snapstate.Flags{} + opts := ifacestate.BuildConfinementOptions(s.st, snapInfo.InstanceName(), snapstate.Flags{}) + + c.Assert(len(opts.ExtraLayouts), Equals, 1) + c.Check(opts.ExtraLayouts[0].Bind, Equals, path.Join(dirs.SnapSystemdDir, "journal.snap-foo")) + c.Check(opts.ExtraLayouts[0].Path, Equals, path.Join(dirs.SnapSystemdDir, "journal")) + c.Check(opts.Classic, Equals, flags.Classic) + c.Check(opts.DevMode, Equals, flags.DevMode) + c.Check(opts.JailMode, Equals, flags.JailMode) +} diff --git a/overlord/ifacestate/helpers.go b/overlord/ifacestate/helpers.go index fc5d3c68cd0..9d3d545c4aa 100644 --- a/overlord/ifacestate/helpers.go +++ b/overlord/ifacestate/helpers.go @@ -177,7 +177,7 @@ func (m *InterfaceManager) regenerateAllSecurityProfiles(tm timings.Measurer) er if err := snapstate.Get(m.state, snapName, &snapst); err != nil { logger.Noticef("cannot get state of snap %q: %s", snapName, err) } - return buildConfinementOptions(m.state, snapName, snapst.Flags) + return buildConfinementOptions(m.state, snapst.InstanceName(), snapst.Flags) } // For each backend: From 00ceecd3c8c372da0f1e276ec58e2513e40c3a78 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 7 Jun 2022 13:24:46 +0200 Subject: [PATCH 031/153] overlord/ifacestate: remove debugging --- overlord/ifacestate/handlers.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index f05b6b4c7c9..b5350775021 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -22,7 +22,6 @@ package ifacestate import ( "errors" "fmt" - "log" "path" "reflect" "sort" @@ -51,12 +50,10 @@ var snapstateFinishRestart = snapstate.FinishRestart // in case the snap has a journal quota. This mimicks what systemd does for // services with log namespaces. func addJournalQuotaLayout(quotaGroup *quota.Group, layouts *[]snap.Layout) error { - log.Println("3.1") if quotaGroup.JournalLimit == nil { return nil } - log.Println("3.2") // We need to bind mount the journal namespace folder ontop of // the journal folder // /etc/systemd/journal. -> /etc/systemd/journal @@ -102,7 +99,6 @@ func buildConfinementOptions(st *state.State, snapInstanceName string, flags sna extraLayouts, err := getExtraLayouts(st, snapInstanceName) if err != nil { logger.Noticef("cannot get extra mount layouts of snap %q: %s", snapInstanceName, err) - log.Printf("cannot get extra mount layouts of snap %q: %s", snapInstanceName, err) } return confinementOptions(flags, extraLayouts) } From c22b7d355f85fea5e5d7815afb8a26594a534f4e Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 7 Jun 2022 13:54:56 +0200 Subject: [PATCH 032/153] overlord/ifacestate: cleanup comments a bit --- overlord/ifacestate/handlers.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index b5350775021..0da91fae8e6 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -47,15 +47,13 @@ import ( var snapstateFinishRestart = snapstate.FinishRestart // addJournalQuotaLayout handles the addition of a journal quota bind mount -// in case the snap has a journal quota. This mimicks what systemd does for -// services with log namespaces. +// to mimicks what systemd does for services with log namespaces. func addJournalQuotaLayout(quotaGroup *quota.Group, layouts *[]snap.Layout) error { if quotaGroup.JournalLimit == nil { return nil } - // We need to bind mount the journal namespace folder ontop of - // the journal folder + // bind mount the journal namespace folder ontop of the journal folder // /etc/systemd/journal. -> /etc/systemd/journal journalLayout := snap.Layout{ Bind: path.Join(dirs.SnapSystemdDir, fmt.Sprintf("journal.snap-%s", quotaGroup.Name)), From eca2703015ba8dbc49b38a415c6dc1671d7c1e9e Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 10 May 2022 08:49:38 +0200 Subject: [PATCH 033/153] cmd/snap: refactor parsing of quota values a bit cleanup and make the code a bit easier to read/maintain for many quota values. remove empty structures when serializing --- cmd/snap/cmd_quota.go | 63 ++++++++++++++++++-------------------- cmd/snap/cmd_quota_test.go | 12 ++++---- cmd/snap/export_test.go | 13 ++++++-- 3 files changed, 47 insertions(+), 41 deletions(-) diff --git a/cmd/snap/cmd_quota.go b/cmd/snap/cmd_quota.go index 09bc3400420..e661abdae48 100644 --- a/cmd/snap/cmd_quota.go +++ b/cmd/snap/cmd_quota.go @@ -165,23 +165,19 @@ func parseCpuQuota(cpuMax string) (count int, percentage int, err error) { return count, percentage, nil } -func parseQuotas(maxMemory string, cpuMax string, cpuSet string, threadsMax string) (*client.QuotaValues, error) { - var mem int64 - var cpuCount int - var cpuPercentage int - var cpus []int - var threads int - - if maxMemory != "" { - value, err := strutil.ParseByteSize(maxMemory) +func (x *cmdSetQuota) parseQuotas() (*client.QuotaValues, error) { + var quotaValues client.QuotaValues + + if x.MemoryMax != "" { + value, err := strutil.ParseByteSize(x.MemoryMax) if err != nil { return nil, err } - mem = value + quotaValues.Memory = quantity.Size(value) } - if cpuMax != "" { - countValue, percentageValue, err := parseCpuQuota(cpuMax) + if x.CPUMax != "" { + countValue, percentageValue, err := parseCpuQuota(x.CPUMax) if err != nil { return nil, err } @@ -189,12 +185,15 @@ func parseQuotas(maxMemory string, cpuMax string, cpuSet string, threadsMax stri return nil, fmt.Errorf("cannot use value %v: cpu quota percentage must be between 1 and 100", percentageValue) } - cpuCount = countValue - cpuPercentage = percentageValue + quotaValues.CPU = &client.QuotaCPUValues{ + Count: countValue, + Percentage: percentageValue, + } } - if cpuSet != "" { - cpuTokens := strutil.CommaSeparatedList(cpuSet) + if x.CPUSet != "" { + var cpus []int + cpuTokens := strutil.CommaSeparatedList(x.CPUSet) for _, cpuToken := range cpuTokens { cpu, err := strconv.ParseUint(cpuToken, 10, 32) if err != nil { @@ -202,31 +201,29 @@ func parseQuotas(maxMemory string, cpuMax string, cpuSet string, threadsMax stri } cpus = append(cpus, int(cpu)) } + + quotaValues.CPUSet = &client.QuotaCPUSetValues{ + CPUs: cpus, + } } - if threadsMax != "" { - value, err := strconv.ParseUint(threadsMax, 10, 32) + if x.ThreadsMax != "" { + value, err := strconv.ParseUint(x.ThreadsMax, 10, 32) if err != nil { - return nil, fmt.Errorf("cannot use threads value %q", threadsMax) + return nil, fmt.Errorf("cannot use threads value %q", x.ThreadsMax) } - threads = int(value) + quotaValues.Threads = int(value) } - return &client.QuotaValues{ - Memory: quantity.Size(mem), - CPU: &client.QuotaCPUValues{ - Count: cpuCount, - Percentage: cpuPercentage, - }, - CPUSet: &client.QuotaCPUSetValues{ - CPUs: cpus, - }, - Threads: threads, - }, nil + return "aValues, nil +} + +func (x *cmdSetQuota) hasQuotaSet() bool { + return x.MemoryMax != "" || x.CPUMax != "" || x.CPUSet != "" || x.ThreadsMax != "" } func (x *cmdSetQuota) Execute(args []string) (err error) { - quotaProvided := x.MemoryMax != "" || x.CPUMax != "" || x.CPUSet != "" || x.ThreadsMax != "" + quotaProvided := x.hasQuotaSet() names := installedSnapNames(x.Positional.Snaps) @@ -267,7 +264,7 @@ func (x *cmdSetQuota) Execute(args []string) (err error) { // we have a limits to set for this group, so specify that along // with whatever snaps may have been provided and whatever parent may // have been specified - quotaValues, err := parseQuotas(x.MemoryMax, x.CPUMax, x.CPUSet, x.ThreadsMax) + quotaValues, err := x.parseQuotas() if err != nil { return err } diff --git a/cmd/snap/cmd_quota_test.go b/cmd/snap/cmd_quota_test.go index 6e0eec0cad3..9170599e09d 100644 --- a/cmd/snap/cmd_quota_test.go +++ b/cmd/snap/cmd_quota_test.go @@ -212,11 +212,11 @@ func (s *quotaSuite) TestParseQuotas(c *check.C) { quotas string err string }{ - {maxMemory: "12KB", quotas: `{"memory":12000,"cpu":{},"cpu-set":{}}`}, - {cpuMax: "12x40%", quotas: `{"cpu":{"count":12,"percentage":40},"cpu-set":{}}`}, - {cpuMax: "40%", quotas: `{"cpu":{"percentage":40},"cpu-set":{}}`}, - {cpuSet: "1,3", quotas: `{"cpu":{},"cpu-set":{"cpus":[1,3]}}`}, - {threadsMax: "2", quotas: `{"cpu":{},"cpu-set":{},"threads":2}`}, + {maxMemory: "12KB", quotas: `{"memory":12000}`}, + {cpuMax: "12x40%", quotas: `{"cpu":{"count":12,"percentage":40}}`}, + {cpuMax: "40%", quotas: `{"cpu":{"percentage":40}}`}, + {cpuSet: "1,3", quotas: `{"cpu-set":{"cpus":[1,3]}}`}, + {threadsMax: "2", quotas: `{"threads":2}`}, // Error cases {cpuMax: "ASD", err: `cannot parse cpu quota string "ASD"`}, {cpuMax: "0x100%", err: `cannot parse cpu quota string "0x100%"`}, @@ -230,7 +230,7 @@ func (s *quotaSuite) TestParseQuotas(c *check.C) { {threadsMax: "xxx", err: `cannot use threads value "xxx"`}, {threadsMax: "-3", err: `cannot use threads value "-3"`}, } { - quotas, err := main.ParseQuotas(testData.maxMemory, testData.cpuMax, testData.cpuSet, testData.threadsMax) + quotas, err := main.ParseQuotaValues(testData.maxMemory, testData.cpuMax, testData.cpuSet, testData.threadsMax) testLabel := check.Commentf("%v", testData) if testData.err == "" { c.Check(err, check.IsNil, testLabel) diff --git a/cmd/snap/export_test.go b/cmd/snap/export_test.go index 868ea267b0e..0f72ddf5f5b 100644 --- a/cmd/snap/export_test.go +++ b/cmd/snap/export_test.go @@ -95,8 +95,6 @@ var ( IsStopping = isStopping GetSnapDirOptions = getSnapDirOptions - - ParseQuotas = parseQuotas ) func HiddenCmd(descr string, completeHidden bool) *cmdInfo { @@ -459,3 +457,14 @@ func MockAutostartSessionApps(f func(string) error) func() { autostartSessionApps = old } } + +func ParseQuotaValues(maxMemory, cpuMax, cpuSet, threadsMax string) (*client.QuotaValues, error) { + var quotas cmdSetQuota + + quotas.MemoryMax = maxMemory + quotas.CPUMax = cpuMax + quotas.CPUSet = cpuSet + quotas.ThreadsMax = threadsMax + + return quotas.parseQuotas() +} From 05d7e24820e611e535b19854cfa035c8a2fe5043 Mon Sep 17 00:00:00 2001 From: Alberto Mardegan Date: Tue, 7 Jun 2022 15:17:11 +0300 Subject: [PATCH 034/153] tests: enable the snapd.apparmor.service in Arch --- tests/lib/pkgdb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/pkgdb.sh b/tests/lib/pkgdb.sh index 8794b0ee339..2ef6530b989 100755 --- a/tests/lib/pkgdb.sh +++ b/tests/lib/pkgdb.sh @@ -479,7 +479,7 @@ distro_install_build_snapd(){ fi fi - if os.query is-opensuse-tumbleweed; then + if os.query is-opensuse-tumbleweed || os.query is-arch-linux; then # Package installation applies vendor presets only, which leaves # snapd.apparmor disabled. systemctl enable --now snapd.apparmor.service From d31a32fdf07e38c2ba79c6324f2e9c9b3711f892 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 7 Jun 2022 19:38:53 +0200 Subject: [PATCH 035/153] overlord/ifacestate: review comments code cleanup, include error handling --- overlord/ifacestate/handlers.go | 99 ++++++++++++++++++---------- overlord/ifacestate/handlers_test.go | 8 +-- overlord/ifacestate/helpers.go | 6 +- 3 files changed, 74 insertions(+), 39 deletions(-) diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index 0da91fae8e6..a974e550067 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -46,22 +46,21 @@ import ( var snapstateFinishRestart = snapstate.FinishRestart -// addJournalQuotaLayout handles the addition of a journal quota bind mount -// to mimicks what systemd does for services with log namespaces. -func addJournalQuotaLayout(quotaGroup *quota.Group, layouts *[]snap.Layout) error { +// getJournalQuotaLayout returns the necessary journal quota mount layouts +// to mimick what systemd does for services with log namespaces. +func getJournalQuotaLayout(quotaGroup *quota.Group) []snap.Layout { if quotaGroup.JournalLimit == nil { - return nil + return []snap.Layout{} } - // bind mount the journal namespace folder ontop of the journal folder + // bind mount the journal namespace folder on top of the journal folder // /etc/systemd/journal. -> /etc/systemd/journal - journalLayout := snap.Layout{ + layouts := []snap.Layout{{ Bind: path.Join(dirs.SnapSystemdDir, fmt.Sprintf("journal.snap-%s", quotaGroup.Name)), Path: path.Join(dirs.SnapSystemdDir, "journal"), Mode: 0755, - } - *layouts = append(*layouts, journalLayout) - return nil + }} + return layouts } // getExtraLayouts helper function to dynamically calculate the extra mount layouts for @@ -75,30 +74,24 @@ func getExtraLayouts(st *state.State, snapInstanceName string) ([]snap.Layout, e var extraLayouts []snap.Layout if snapOpts.QuotaGroup != nil { - if err := addJournalQuotaLayout(snapOpts.QuotaGroup, &extraLayouts); err != nil { - return extraLayouts, err - } + extraLayouts = append(extraLayouts, getJournalQuotaLayout(snapOpts.QuotaGroup)...) } return extraLayouts, nil } -// confinementOptions returns interfaces.ConfinementOptions from snapstate.Flags. -func confinementOptions(flags snapstate.Flags, extraLayouts []snap.Layout) interfaces.ConfinementOptions { +func buildConfinementOptions(st *state.State, snapInstanceName string, flags snapstate.Flags) (interfaces.ConfinementOptions, error) { + extraLayouts, err := getExtraLayouts(st, snapInstanceName) + if err != nil { + return interfaces.ConfinementOptions{}, fmt.Errorf("cannot get extra mount layouts of snap %q: %s", snapInstanceName, err) + } + return interfaces.ConfinementOptions{ DevMode: flags.DevMode, JailMode: flags.JailMode, Classic: flags.Classic, ExtraLayouts: extraLayouts, - } -} - -func buildConfinementOptions(st *state.State, snapInstanceName string, flags snapstate.Flags) interfaces.ConfinementOptions { - extraLayouts, err := getExtraLayouts(st, snapInstanceName) - if err != nil { - logger.Noticef("cannot get extra mount layouts of snap %q: %s", snapInstanceName, err) - } - return confinementOptions(flags, extraLayouts) + }, nil } func (m *InterfaceManager) setupAffectedSnaps(task *state.Task, affectingSnap string, affectedSnaps []string, tm timings.Measurer) error { @@ -122,7 +115,10 @@ func (m *InterfaceManager) setupAffectedSnaps(task *state.Task, affectingSnap st if err := addImplicitSlots(st, affectedSnapInfo); err != nil { return err } - opts := buildConfinementOptions(st, affectedSnapInfo.InstanceName(), snapst.Flags) + opts, err := buildConfinementOptions(st, affectedSnapInfo.InstanceName(), snapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, affectedSnapInfo, opts, tm); err != nil { return err } @@ -165,7 +161,10 @@ func (m *InterfaceManager) doSetupProfiles(task *state.Task, tomb *tomb.Tomb) er return nil } - opts := buildConfinementOptions(task.State(), snapInfo.InstanceName(), snapsup.Flags) + opts, err := buildConfinementOptions(task.State(), snapInfo.InstanceName(), snapsup.Flags) + if err != nil { + return err + } return m.setupProfilesForSnap(task, tomb, snapInfo, opts, perfTimings) } @@ -255,8 +254,13 @@ func (m *InterfaceManager) setupProfilesForSnap(task *state.Task, _ *tomb.Tomb, if err := addImplicitSlots(st, snapInfo); err != nil { return err } + opts, err := buildConfinementOptions(st, snapInfo.InstanceName(), snapst.Flags) + if err != nil { + return err + } + affectedSnaps = append(affectedSnaps, snapInfo) - confinementOpts = append(confinementOpts, buildConfinementOptions(st, snapInfo.InstanceName(), snapst.Flags)) + confinementOpts = append(confinementOpts, opts) } return m.setupSecurityByBackend(task, affectedSnaps, confinementOpts, tm) @@ -347,7 +351,10 @@ func (m *InterfaceManager) undoSetupProfiles(task *state.Task, tomb *tomb.Tomb) if err != nil { return err } - opts := buildConfinementOptions(task.State(), snapInfo.InstanceName(), snapst.Flags) + opts, err := buildConfinementOptions(task.State(), snapInfo.InstanceName(), snapst.Flags) + if err != nil { + return err + } return m.setupProfilesForSnap(task, tomb, snapInfo, opts, perfTimings) } } @@ -549,12 +556,18 @@ func (m *InterfaceManager) doConnect(task *state.Task, _ *tomb.Tomb) (err error) }() if !delayedSetupProfiles { - slotOpts := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) + slotOpts, err := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) + plugOpts, err := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } @@ -655,7 +668,10 @@ func (m *InterfaceManager) doDisconnect(task *state.Task, _ *tomb.Tomb) error { if err != nil { return err } - opts := buildConfinementOptions(st, snapInfo.InstanceName(), snapst.Flags) + opts, err := buildConfinementOptions(st, snapInfo.InstanceName(), snapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, snapInfo, opts, perfTimings); err != nil { return err } @@ -761,11 +777,18 @@ func (m *InterfaceManager) undoDisconnect(task *state.Task, _ *tomb.Tomb) error return err } - slotOpts := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) + slotOpts, err := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) + + plugOpts, err := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } @@ -844,11 +867,19 @@ func (m *InterfaceManager) undoConnect(task *state.Task, _ *tomb.Tomb) error { if err != nil { return err } - slotOpts := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) + + slotOpts, err := buildConfinementOptions(st, slotSnapst.InstanceName(), slotSnapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, slot.Snap, slotOpts, perfTimings); err != nil { return err } - plugOpts := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) + + plugOpts, err := buildConfinementOptions(st, plugSnapst.InstanceName(), plugSnapst.Flags) + if err != nil { + return err + } if err := m.setupSnapSecurity(task, plug.Snap, plugOpts, perfTimings); err != nil { return err } diff --git a/overlord/ifacestate/handlers_test.go b/overlord/ifacestate/handlers_test.go index b1179be9c90..59c8869299b 100644 --- a/overlord/ifacestate/handlers_test.go +++ b/overlord/ifacestate/handlers_test.go @@ -126,14 +126,14 @@ func mockInstalledSnap(c *C, st *state.State, snapYaml string) *snap.Info { } func (s *handlersSuite) TestBuildConfinementOptions(c *C) { - // Mock installed snap s.st.Lock() defer s.st.Unlock() snapInfo := mockInstalledSnap(c, s.st, snapAyaml) flags := snapstate.Flags{} - opts := ifacestate.BuildConfinementOptions(s.st, snapInfo.InstanceName(), snapstate.Flags{}) + opts, err := ifacestate.BuildConfinementOptions(s.st, snapInfo.InstanceName(), snapstate.Flags{}) + c.Check(err, IsNil) c.Check(len(opts.ExtraLayouts), Equals, 0) c.Check(opts.Classic, Equals, flags.Classic) c.Check(opts.DevMode, Equals, flags.DevMode) @@ -141,7 +141,6 @@ func (s *handlersSuite) TestBuildConfinementOptions(c *C) { } func (s *handlersSuite) TestBuildConfinementOptionsWithLogNamespace(c *C) { - // Mock installed snap s.st.Lock() defer s.st.Unlock() @@ -156,8 +155,9 @@ func (s *handlersSuite) TestBuildConfinementOptionsWithLogNamespace(c *C) { c.Assert(err, IsNil) flags := snapstate.Flags{} - opts := ifacestate.BuildConfinementOptions(s.st, snapInfo.InstanceName(), snapstate.Flags{}) + opts, err := ifacestate.BuildConfinementOptions(s.st, snapInfo.InstanceName(), snapstate.Flags{}) + c.Check(err, IsNil) c.Assert(len(opts.ExtraLayouts), Equals, 1) c.Check(opts.ExtraLayouts[0].Bind, Equals, path.Join(dirs.SnapSystemdDir, "journal.snap-foo")) c.Check(opts.ExtraLayouts[0].Path, Equals, path.Join(dirs.SnapSystemdDir, "journal")) diff --git a/overlord/ifacestate/helpers.go b/overlord/ifacestate/helpers.go index 9d3d545c4aa..008e7eb1708 100644 --- a/overlord/ifacestate/helpers.go +++ b/overlord/ifacestate/helpers.go @@ -177,7 +177,11 @@ func (m *InterfaceManager) regenerateAllSecurityProfiles(tm timings.Measurer) er if err := snapstate.Get(m.state, snapName, &snapst); err != nil { logger.Noticef("cannot get state of snap %q: %s", snapName, err) } - return buildConfinementOptions(m.state, snapst.InstanceName(), snapst.Flags) + opts, err := buildConfinementOptions(m.state, snapst.InstanceName(), snapst.Flags) + if err != nil { + logger.Noticef("cannot get confinement options for snap %q: %s", snapName, err) + } + return opts } // For each backend: From 1c11c73422d42ada8e0dd4bb646089f3614af516 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 8 Jun 2022 13:24:56 +0930 Subject: [PATCH 036/153] tests/main/snapd-apparmor: Address feedback from mardy Explain the explicit use of whitespace when matching a profile name and use more explicit file names for state storage during the test. Signed-off-by: Alex Murray --- tests/main/snapd-apparmor/task.yaml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/main/snapd-apparmor/task.yaml b/tests/main/snapd-apparmor/task.yaml index c3289181742..7ff174e151c 100644 --- a/tests/main/snapd-apparmor/task.yaml +++ b/tests/main/snapd-apparmor/task.yaml @@ -37,27 +37,30 @@ execute: | while IFS= read -r profile; do echo "Unloading $profile..." echo -n "$profile" > /sys/kernel/security/apparmor/.remove - # check it is now unloaded + # check it is now unloaded - ensure we match the complete profile + # name so we don't inadvertently match on the wrong profile via a + # prefix where a snap has one command that is a prefix of another + # (ie. snap.foo.foo and snap.foo.foo-bar) NOMATCH "^$profile " /sys/kernel/security/apparmor/profiles done < <(grep ^profile < "$p" | cut -f2 -d" " | sed s/'"'//g) done # ensure we are actually testing something - ie snapd.apparmor will # actually have to do some work - grep -v / /sys/kernel/security/apparmor/profiles | cut -f1 -d" " | sort > unloaded_profiles.txt - diff -u profiles.txt unloaded_profiles.txt && exit 1 + grep -v / /sys/kernel/security/apparmor/profiles | cut -f1 -d" " | sort > profiles_after_unload.txt + diff -u profiles.txt profiles_after_unload.txt && exit 1 # restart snapd.apparmor service to reload profiles echo "And restart snapd.apparmor.service" systemctl restart snapd.apparmor.service # get the set of profiles which now exist - grep -v / /sys/kernel/security/apparmor/profiles | cut -f1 -d" " | sort > reloaded_profiles.txt + grep -v / /sys/kernel/security/apparmor/profiles | cut -f1 -d" " | sort > profiles_after_reload.txt # and check there is no difference (ie. that snapd-apparmor reloaded # all profiles as expected) echo "Then profiles should have reloaded successfully..." - diff -u profiles.txt reloaded_profiles.txt + diff -u profiles.txt profiles_after_reload.txt # also check that snapd.apparmor.service fails when a profile is invalid sed -i s/profile/profileinvalidnametobereplaced/ /var/lib/snapd/apparmor/profiles/snap.$CONSUMER_SNAP.* From 796a0324ffd109389f5e2918b3310f38804a2618 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 8 Jun 2022 13:29:02 +0930 Subject: [PATCH 037/153] cmd/snapd-apparmor/snapd-apparmor: Delete the old shell script This is now a go binary instead. Signed-off-by: Alex Murray --- cmd/snapd-apparmor/snapd-apparmor | 102 ------------------------------ 1 file changed, 102 deletions(-) delete mode 100755 cmd/snapd-apparmor/snapd-apparmor diff --git a/cmd/snapd-apparmor/snapd-apparmor b/cmd/snapd-apparmor/snapd-apparmor deleted file mode 100755 index fd799feaab6..00000000000 --- a/cmd/snapd-apparmor/snapd-apparmor +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/sh -# This script is provided for integration with systemd on distributions where -# apparmor profiles generated and managed by snapd are not loaded by the -# system-wide apparmor systemd integration on early boot-up. -# -# Only the start operation is provided as all other activity is managed by -# snapd as a part of the life-cycle of particular snaps. -# -# In addition the script assumes that the system-wide apparmor service has -# already executed, initializing apparmor file-systems as necessary. - -# NOTE: This script doesn't set -e as it contains code copied from apparmor -# init script that also does not set it. In addition the intent is to simply -# load application profiles, as many as we can, even if for whatever reason -# some of those fail. - -# The following portion is copied from /lib/apparmor/functions as shipped by Ubuntu -# - -SECURITYFS="/sys/kernel/security" -export AA_SFS="$SECURITYFS/apparmor" - - -# Checks to see if the current container is capable of having internal AppArmor -# profiles that should be loaded. Callers of this function should have already -# verified that they're running inside of a container environment with -# something like `systemd-detect-virt --container`. -# -# The only known container environments capable of supporting internal policy -# are LXD and LXC environment. -# -# Returns 0 if the container environment is capable of having its own internal -# policy and non-zero otherwise. -# -# IMPORTANT: This function will return 0 in the case of a non-LXD/non-LXC -# system container technology being nested inside of a LXD/LXC container that -# utilized an AppArmor namespace and profile stacking. The reason 0 will be -# returned is because .ns_stacked will be "yes" and .ns_name will still match -# "lx[dc]-*" since the nested system container technology will not have set up -# a new AppArmor profile namespace. This will result in the nested system -# container's boot process to experience failed policy loads but the boot -# process should continue without any loss of functionality. This is an -# unsupported configuration that cannot be properly handled by this function. -is_container_with_internal_policy() { - ns_stacked_path="${AA_SFS}/.ns_stacked" - ns_name_path="${AA_SFS}/.ns_name" - # shellcheck disable=SC3043,SC2039 - local ns_stacked - # shellcheck disable=SC3043,SC2039 - local ns_name - - if ! [ -f "$ns_stacked_path" ] || ! [ -f "$ns_name_path" ]; then - return 1 - fi - - read -r ns_stacked < "$ns_stacked_path" - if [ "$ns_stacked" != "yes" ]; then - return 1 - fi - - # LXD and LXC set up AppArmor namespaces starting with "lxd-" and - # "lxc-", respectively. Return non-zero for all other namespace - # identifiers. - read -r ns_name < "$ns_name_path" - if [ "${ns_name#lxd-*}" = "$ns_name" ] && \ - [ "${ns_name#lxc-*}" = "$ns_name" ]; then - return 1 - fi - - return 0 -} - -# This terminates code copied from /lib/apparmor/functions on Ubuntu -# - -case "$1" in - start) - # - if [ -x /usr/bin/systemd-detect-virt ] && \ - systemd-detect-virt --quiet --container && \ - ! is_container_with_internal_policy; then - exit 0 - fi - # - - if [ "$(find /var/lib/snapd/apparmor/profiles/ -type f | wc -l)" -eq 0 ]; then - exit 0 - fi - for profile in /var/lib/snapd/apparmor/profiles/*; do - # Filter out profiles with names ending with ~, those are temporary files created by snapd. - test "${profile%\~}" != "${profile}" && continue - echo "$profile" - done | xargs \ - -P"$(getconf _NPROCESSORS_ONLN)" \ - apparmor_parser \ - --replace \ - --write-cache \ - --cache-loc=/var/cache/apparmor \ - -O no-expr-simplify \ - --quiet - ;; -esac From 2b28dda312e87baa1e803186f4aa90ab4f859de7 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Wed, 8 Jun 2022 11:41:28 +0200 Subject: [PATCH 038/153] overlord/ifacestate: review feedback code cleanup --- overlord/ifacestate/handlers.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index a974e550067..dd2d01e123e 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -46,11 +46,11 @@ import ( var snapstateFinishRestart = snapstate.FinishRestart -// getJournalQuotaLayout returns the necessary journal quota mount layouts +// journalQuotaLayout returns the necessary journal quota mount layouts // to mimick what systemd does for services with log namespaces. -func getJournalQuotaLayout(quotaGroup *quota.Group) []snap.Layout { +func journalQuotaLayout(quotaGroup *quota.Group) []snap.Layout { if quotaGroup.JournalLimit == nil { - return []snap.Layout{} + return nil } // bind mount the journal namespace folder on top of the journal folder @@ -74,7 +74,7 @@ func getExtraLayouts(st *state.State, snapInstanceName string) ([]snap.Layout, e var extraLayouts []snap.Layout if snapOpts.QuotaGroup != nil { - extraLayouts = append(extraLayouts, getJournalQuotaLayout(snapOpts.QuotaGroup)...) + extraLayouts = append(extraLayouts, journalQuotaLayout(snapOpts.QuotaGroup)...) } return extraLayouts, nil From 5aa61378154f622fb7e6995a2ae8604028f554cd Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 9 Jun 2022 09:06:09 +0200 Subject: [PATCH 039/153] spread: add openSUSE Leap 15.4 openSUSE Leap 15.4 has been released, let's add it to the test suite. Signed-off-by: Maciej Borzecki --- spread.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spread.yaml b/spread.yaml index 0df75487aff..93716e54876 100644 --- a/spread.yaml +++ b/spread.yaml @@ -173,6 +173,8 @@ backends: # unstable systems below - opensuse-15.3-64: workers: 6 + - opensuse-15.4-64: + workers: 6 - opensuse-tumbleweed-64: workers: 6 manual: true From 91bfc1efad2a73cc3dbb3dce9e0eeea7d822058c Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Thu, 9 Jun 2022 09:40:24 +0100 Subject: [PATCH 040/153] features: add move snap home experimental feature Signed-off-by: Miguel Pires --- features/features.go | 4 ++++ features/features_test.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/features/features.go b/features/features.go index 6415b7c10f0..d6c95ef7b9b 100644 --- a/features/features.go +++ b/features/features.go @@ -53,6 +53,8 @@ const ( DbusActivation // HiddenSnapDataHomeDir controls if the snaps' data dir is ~/.snap/data instead of ~/snap HiddenSnapDataHomeDir + // MoveSnapHomeDir controls whether snap user data under ~/snap (or ~/.snap/data) are moved to ~/Snap. + MoveSnapHomeDir // CheckDiskSpaceRemove controls free disk space check on remove whenever automatic snapshot needs to be created. CheckDiskSpaceRemove // CheckDiskSpaceInstall controls free disk space check on snap install. @@ -95,6 +97,7 @@ var featureNames = map[SnapdFeature]string{ DbusActivation: "dbus-activation", HiddenSnapDataHomeDir: "hidden-snap-folder", + MoveSnapHomeDir: "move-snap-home-dir", CheckDiskSpaceInstall: "check-disk-space-install", CheckDiskSpaceRefresh: "check-disk-space-refresh", @@ -123,6 +126,7 @@ var featuresExported = map[SnapdFeature]bool{ ClassicPreservesXdgRuntimeDir: true, RobustMountNamespaceUpdates: true, HiddenSnapDataHomeDir: true, + MoveSnapHomeDir: true, } // String returns the name of a snapd feature. diff --git a/features/features_test.go b/features/features_test.go index f64570cf467..3b359b69f73 100644 --- a/features/features_test.go +++ b/features/features_test.go @@ -50,6 +50,7 @@ func (*featureSuite) TestName(c *C) { c.Check(features.UserDaemons.String(), Equals, "user-daemons") c.Check(features.DbusActivation.String(), Equals, "dbus-activation") c.Check(features.HiddenSnapDataHomeDir.String(), Equals, "hidden-snap-folder") + c.Check(features.MoveSnapHomeDir.String(), Equals, "move-snap-home-dir") c.Check(features.CheckDiskSpaceInstall.String(), Equals, "check-disk-space-install") c.Check(features.CheckDiskSpaceRefresh.String(), Equals, "check-disk-space-refresh") c.Check(features.CheckDiskSpaceRemove.String(), Equals, "check-disk-space-remove") @@ -79,6 +80,7 @@ func (*featureSuite) TestIsExported(c *C) { c.Check(features.UserDaemons.IsExported(), Equals, false) c.Check(features.DbusActivation.IsExported(), Equals, false) c.Check(features.HiddenSnapDataHomeDir.IsExported(), Equals, true) + c.Check(features.MoveSnapHomeDir.IsExported(), Equals, true) c.Check(features.CheckDiskSpaceInstall.IsExported(), Equals, false) c.Check(features.CheckDiskSpaceRefresh.IsExported(), Equals, false) c.Check(features.CheckDiskSpaceRemove.IsExported(), Equals, false) @@ -116,6 +118,7 @@ func (*featureSuite) TestIsEnabledWhenUnset(c *C) { c.Check(features.UserDaemons.IsEnabledWhenUnset(), Equals, false) c.Check(features.DbusActivation.IsEnabledWhenUnset(), Equals, true) c.Check(features.HiddenSnapDataHomeDir.IsEnabledWhenUnset(), Equals, false) + c.Check(features.MoveSnapHomeDir.IsEnabledWhenUnset(), Equals, false) c.Check(features.CheckDiskSpaceInstall.IsEnabledWhenUnset(), Equals, false) c.Check(features.CheckDiskSpaceRefresh.IsEnabledWhenUnset(), Equals, false) c.Check(features.CheckDiskSpaceRemove.IsEnabledWhenUnset(), Equals, false) @@ -128,6 +131,7 @@ func (*featureSuite) TestControlFile(c *C) { c.Check(features.ParallelInstances.ControlFile(), Equals, "/var/lib/snapd/features/parallel-instances") c.Check(features.RobustMountNamespaceUpdates.ControlFile(), Equals, "/var/lib/snapd/features/robust-mount-namespace-updates") c.Check(features.HiddenSnapDataHomeDir.ControlFile(), Equals, "/var/lib/snapd/features/hidden-snap-folder") + c.Check(features.MoveSnapHomeDir.ControlFile(), Equals, "/var/lib/snapd/features/move-snap-home-dir") // Features that are not exported don't have a control file. c.Check(features.Layouts.ControlFile, PanicMatches, `cannot compute the control file of feature "layouts" because that feature is not exported`) } From 0dea01b1ac0e66ec08395901208680968268190c Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 9 Jun 2022 10:41:24 +0200 Subject: [PATCH 041/153] github: run spread workflow on opensuse 15.4 Signed-off-by: Maciej Borzecki --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bc1f8d3cce3..d484476766b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -385,6 +385,7 @@ jobs: - fedora-34-64 - fedora-35-64 - opensuse-15.3-64 + - opensuse-15.4-64 - opensuse-tumbleweed-64 - ubuntu-14.04-64 - ubuntu-16.04-64 From 7a753c2cbcc3b3a89d209b11129839cc58b64211 Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Thu, 9 Jun 2022 12:20:54 +0100 Subject: [PATCH 042/153] o/snapstate: support migrate snap home to ~/Snap as a change Signed-off-by: Miguel Pires --- overlord/snapstate/backend.go | 2 +- overlord/snapstate/backend/copydata.go | 10 +- overlord/snapstate/backend/copydata_test.go | 14 +- overlord/snapstate/backend_test.go | 2 +- overlord/snapstate/handlers.go | 64 +++++++- overlord/snapstate/snapmgr.go | 1 + overlord/snapstate/snapstate.go | 90 ++++++++++- overlord/snapstate/snapstate_test.go | 171 ++++++++++++++++++++ 8 files changed, 337 insertions(+), 17 deletions(-) diff --git a/overlord/snapstate/backend.go b/overlord/snapstate/backend.go index c6b39f182d6..3bc954912c6 100644 --- a/overlord/snapstate/backend.go +++ b/overlord/snapstate/backend.go @@ -112,7 +112,7 @@ type managerBackend interface { // ~/.snap/data migration related HideSnapData(snapName string) error UndoHideSnapData(snapName string) error - InitExposedSnapHome(snapName string, rev snap.Revision) (*backend.UndoInfo, error) + InitExposedSnapHome(snapName string, rev snap.Revision, opts *dirs.SnapDirOptions) (*backend.UndoInfo, error) UndoInitExposedSnapHome(snapName string, undoInfo *backend.UndoInfo) error InitXDGDirs(info *snap.Info) error } diff --git a/overlord/snapstate/backend/copydata.go b/overlord/snapstate/backend/copydata.go index 83f777abf9d..c2c49dc2df8 100644 --- a/overlord/snapstate/backend/copydata.go +++ b/overlord/snapstate/backend/copydata.go @@ -272,12 +272,10 @@ type UndoInfo struct { } // InitExposedSnapHome creates and initializes ~/Snap/ based on the -// specified revision. Must be called after the snap has been migrated. If no -// error occurred, returns a non-nil undoInfo so that the operation can be undone. -// If an error occurred, an attempt is made to undo so no undoInfo is returned. -func (b Backend) InitExposedSnapHome(snapName string, rev snap.Revision) (undoInfo *UndoInfo, err error) { - opts := &dirs.SnapDirOptions{HiddenSnapDataDir: true} - +// specified revision. If no error occurred, returns a non-nil undoInfo so that +// the operation can be undone. If an error occurred, an attempt is made to undo +// so no undoInfo is returned. +func (b Backend) InitExposedSnapHome(snapName string, rev snap.Revision, opts *dirs.SnapDirOptions) (undoInfo *UndoInfo, err error) { users, err := allUsers(opts) if err != nil { return nil, err diff --git a/overlord/snapstate/backend/copydata_test.go b/overlord/snapstate/backend/copydata_test.go index c9545b4b808..1bb33e9790b 100644 --- a/overlord/snapstate/backend/copydata_test.go +++ b/overlord/snapstate/backend/copydata_test.go @@ -973,7 +973,7 @@ func (s *copydataSuite) TestInitSnapUserHome(c *C) { dirPath := filepath.Join(revDir, "dir") c.Assert(os.Mkdir(dirPath, 0775), IsNil) - undoInfo, err := s.be.InitExposedSnapHome(snapName, rev) + undoInfo, err := s.be.InitExposedSnapHome(snapName, rev, opts) c.Assert(err, IsNil) exposedHome := filepath.Join(homeDir, dirs.ExposedSnapHomeDir, snapName) c.Check(undoInfo.Created, DeepEquals, []string{exposedHome}) @@ -1025,7 +1025,7 @@ func (s *copydataSuite) TestInitExposedHomeIgnoreXDGDirs(c *C) { localPath := filepath.Join(revDir, ".local", "share") c.Assert(os.MkdirAll(localPath, 0700), IsNil) - undoInfo, err := s.be.InitExposedSnapHome(snapName, rev) + undoInfo, err := s.be.InitExposedSnapHome(snapName, rev, opts) c.Assert(err, IsNil) exposedHome := snap.UserExposedHomeDir(homeDir, snapName) c.Check(undoInfo.Created, DeepEquals, []string{exposedHome}) @@ -1074,7 +1074,7 @@ func (s *copydataSuite) TestInitSnapFailOnFirstErr(c *C) { rev, err := snap.ParseRevision("2") c.Assert(err, IsNil) - undoInfo, err := s.be.InitExposedSnapHome(snapName, rev) + undoInfo, err := s.be.InitExposedSnapHome(snapName, rev, nil) c.Assert(err, ErrorMatches, ".*: boom") c.Check(undoInfo, IsNil) @@ -1123,7 +1123,7 @@ func (s *copydataSuite) TestInitSnapUndoOnErr(c *C) { rev, err := snap.ParseRevision("2") c.Assert(err, IsNil) - undoInfo, err := s.be.InitExposedSnapHome(snapName, rev) + undoInfo, err := s.be.InitExposedSnapHome(snapName, rev, nil) c.Assert(err, ErrorMatches, ".*: boom") c.Check(undoInfo, IsNil) @@ -1152,7 +1152,7 @@ func (s *copydataSuite) TestInitSnapNothingToCopy(c *C) { rev, err := snap.ParseRevision("2") c.Assert(err, IsNil) - undoInfo, err := s.be.InitExposedSnapHome(snapName, rev) + undoInfo, err := s.be.InitExposedSnapHome(snapName, rev, nil) c.Assert(err, IsNil) c.Check(undoInfo.Created, DeepEquals, []string{snap.UserExposedHomeDir(usr.HomeDir, snapName)}) @@ -1187,7 +1187,7 @@ func (s *copydataSuite) TestInitAlreadyExistsFile(c *C) { rev, err := snap.ParseRevision("2") c.Assert(err, IsNil) - undoInfo, err := s.be.InitExposedSnapHome(snapName, rev) + undoInfo, err := s.be.InitExposedSnapHome(snapName, rev, nil) c.Assert(err, ErrorMatches, fmt.Sprintf("cannot initialize new user HOME %q: already exists but is not a directory", newHome)) c.Check(undoInfo, IsNil) @@ -1217,7 +1217,7 @@ func (s *copydataSuite) TestInitAlreadyExistsDir(c *C) { rev, err := snap.ParseRevision("2") c.Assert(err, IsNil) - undoInfo, err := s.be.InitExposedSnapHome(snapName, rev) + undoInfo, err := s.be.InitExposedSnapHome(snapName, rev, nil) c.Assert(err, IsNil) c.Check(undoInfo.Created, HasLen, 0) diff --git a/overlord/snapstate/backend_test.go b/overlord/snapstate/backend_test.go index 011f30af8e8..f569d709c7d 100644 --- a/overlord/snapstate/backend_test.go +++ b/overlord/snapstate/backend_test.go @@ -1308,7 +1308,7 @@ func (f *fakeSnappyBackend) UndoHideSnapData(snapName string) error { return f.maybeErrForLastOp() } -func (f *fakeSnappyBackend) InitExposedSnapHome(snapName string, rev snap.Revision) (*backend.UndoInfo, error) { +func (f *fakeSnappyBackend) InitExposedSnapHome(snapName string, rev snap.Revision, opts *dirs.SnapDirOptions) (*backend.UndoInfo, error) { f.appendOp(&fakeOp{op: "init-exposed-snap-home", name: snapName, revno: rev}) if err := f.maybeErrForLastOp(); err != nil { diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go index c3d9885ac80..25a9df3b195 100644 --- a/overlord/snapstate/handlers.go +++ b/overlord/snapstate/handlers.go @@ -1282,7 +1282,7 @@ func (m *SnapManager) doCopySnapData(t *state.Task, _ *tomb.Tomb) (err error) { snapsup.MigratedHidden = true fallthrough case home: - undo, err := m.backend.InitExposedSnapHome(snapName, newInfo.Revision) + undo, err := m.backend.InitExposedSnapHome(snapName, newInfo.Revision, opts.getSnapDirOpts()) if err != nil { return err } @@ -3660,6 +3660,68 @@ func (m *SnapManager) doConditionalAutoRefresh(t *state.Task, tomb *tomb.Tomb) e return nil } +func (m *SnapManager) doMigrateSnapHome(t *state.Task, tomb *tomb.Tomb) error { + st := t.State() + st.Lock() + snapsup, snapst, err := snapSetupAndState(t) + st.Unlock() + if err != nil { + return err + } + + st.Lock() + opts, err := getDirMigrationOpts(st, snapst, snapsup) + st.Unlock() + if err != nil { + return err + } + + dirOpts := opts.getSnapDirOpts() + undo, err := m.backend.InitExposedSnapHome(snapsup.InstanceName(), snapsup.Revision(), dirOpts) + if err != nil { + return err + } + + st.Lock() + t.Set("undo-exposed-home-init", undo) + st.Unlock() + snapsup.MigratedToExposedHome = true + + st.Lock() + defer st.Unlock() + return SetTaskSnapSetup(t, snapsup) +} + +func (m *SnapManager) undoMigrateSnapHome(t *state.Task, tomb *tomb.Tomb) error { + st := t.State() + st.Lock() + snapsup, snapst, err := snapSetupAndState(t) + st.Unlock() + if err != nil { + return err + } + + var undo backend.UndoInfo + + st.Lock() + err = t.Get("undo-exposed-home-init", &undo) + st.Unlock() + if err != nil { + return err + } + + if err := m.backend.UndoInitExposedSnapHome(snapsup.InstanceName(), &undo); err != nil { + return err + } + + snapsup.MigratedToExposedHome = false + snapst.MigratedToExposedHome = false + + st.Lock() + defer st.Unlock() + return writeMigrationStatus(t, snapst, snapsup) +} + // maybeRestoreValidationSetsAndRevertSnaps restores validation-sets to their // previous state using validation sets stack if there are any enforced // validation sets and - if necessary - creates tasksets to revert some or all diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go index bda222ecb01..ba18a3e0531 100644 --- a/overlord/snapstate/snapmgr.go +++ b/overlord/snapstate/snapmgr.go @@ -528,6 +528,7 @@ func Manager(st *state.State, runner *state.TaskRunner) (*SnapManager, error) { // misc runner.AddHandler("switch-snap", m.doSwitchSnap, nil) + runner.AddHandler("migrate-snap-home", m.doMigrateSnapHome, m.undoMigrateSnapHome) // control serialisation runner.AddBlocked(m.blockedTask) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 1c553935631..7cd7213d231 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2016-2021 Canonical Ltd + * Copyright (C) 2016-2022 Canonical Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as @@ -1152,6 +1152,13 @@ func InstallWithDeviceContext(ctx context.Context, st *state.State, name string, return doInstall(st, &snapst, snapsup, 0, fromChange, nil) } +// InstallPathMany returns a set of tasks for installing snaps from a file paths +// and snap.Infos. +// +// The state must be locked by the caller. +// The provided SideInfos can contain just a name which results in a +// local revision and sideloading, or full metadata in which case +// the snaps will appear as installed from the store. func InstallPathMany(ctx context.Context, st *state.State, sideInfos []*snap.SideInfo, paths []string, userID int, flags *Flags) ([]*state.TaskSet, error) { if flags == nil { flags = &Flags{} @@ -2431,6 +2438,87 @@ func checkDiskSpace(st *state.State, changeKind string, infos []minimalInstallIn return nil } +// MigrateHome migrates a set of snaps to use a ~/Snap sub-directory as HOME. +// The state must be locked by the caller. +func MigrateHome(st *state.State, snaps []string) ([]*state.TaskSet, error) { + tr := config.NewTransaction(st) + moveDir, err := features.Flag(tr, features.MoveSnapHomeDir) + if err != nil { + return nil, err + } + + if !moveDir { + _, confName := features.MoveSnapHomeDir.ConfigOption() + return nil, fmt.Errorf("cannot migrate to ~/Snap: flag %q is not set", confName) + } + + allSnaps, err := All(st) + if err != nil { + return nil, err + } + + for _, name := range snaps { + if snapst, ok := allSnaps[name]; !ok { + return nil, snap.NotInstalledError{Snap: name} + } else if snapst.MigratedToExposedHome { + return nil, fmt.Errorf("cannot migrate %q to ~/Snap: already migrated", name) + } + } + + var tss []*state.TaskSet + for _, name := range snaps { + si := allSnaps[name].CurrentSideInfo() + snapsup := &SnapSetup{ + SideInfo: si, + } + + var tasks []*state.Task + prepare := st.NewTask("prepare-snap", fmt.Sprintf(i18n.G("Prepare snap %q (%s)"), name, si.Revision)) + prepare.Set("snap-setup", snapsup) + tasks = append(tasks, prepare) + + prev := prepare + addTask := func(t *state.Task) { + t.Set("snap-setup-task", prepare.ID()) + t.WaitFor(prev) + tasks = append(tasks, t) + } + + stop := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), snapsup.InstanceName())) + stop.Set("stop-reason", "home-migration") + addTask(stop) + prev = stop + + unlink := st.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapsup.InstanceName())) + addTask(unlink) + prev = unlink + + migrate := st.NewTask("migrate-snap-home", fmt.Sprintf(i18n.G("Migrate %q to ~/Snap"), name)) + addTask(migrate) + prev = migrate + + // finalize (wrappers+current symlink) + linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), snapsup.InstanceName(), si.Revision)) + addTask(linkSnap) + prev = linkSnap + + // run new services + startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), snapsup.InstanceName(), si.Revision)) + addTask(startSnapServices) + prev = startSnapServices + + var ts state.TaskSet + for _, t := range tasks { + ts.AddTask(t) + } + + ts.JoinLane(st.NewLane()) + tss = append(tss, &ts) + } + + return tss, nil +} + // 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) { diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index b222c412ce9..cb04dfacc6e 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -8155,6 +8155,177 @@ func (s *snapmgrTestSuite) TestRemodelAddGadgetAssetTasks(c *C) { c.Assert(tsNew, IsNil) } +func (s *snapmgrTestSuite) TestMigrateHome(c *C) { + s.state.Lock() + defer s.state.Unlock() + + tr := config.NewTransaction(s.state) + c.Assert(tr.Set("core", "experimental.move-snap-home-dir", true), IsNil) + tr.Commit() + + si := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(3)} + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: si.Revision, + SnapType: "app", + }) + + chg := s.state.NewChange("migrate-home", "...") + tss, err := snapstate.MigrateHome(s.state, []string{"some-snap"}) + c.Assert(err, IsNil) + for _, ts := range tss { + chg.AddAll(ts) + } + + defer s.se.Stop() + s.settle(c) + + c.Assert(tss, HasLen, 1) + c.Assert(taskNames(tss[0].Tasks()), DeepEquals, []string{ + `prepare-snap`, + `stop-snap-services`, + `unlink-current-snap`, + `migrate-snap-home`, + `link-snap`, + `start-snap-services`, + }) + + c.Assert(chg.Err(), IsNil) + c.Assert(chg.IsReady(), Equals, true) + + var undo backend.UndoInfo + migrateTask := findLastTask(chg, "migrate-snap-home") + c.Assert(migrateTask.Get("undo-exposed-home-init", &undo), IsNil) + c.Assert(undo.Created, HasLen, 1) + + s.fakeBackend.ops.MustFindOp(c, "init-exposed-snap-home") + + // check migration is off in state and seq file + assertMigrationState(c, s.state, "some-snap", &dirs.SnapDirOptions{MigratedToExposedHome: true}) +} + +func (s *snapmgrTestSuite) TestMigrateHomeUndo(c *C) { + s.state.Lock() + defer s.state.Unlock() + + tr := config.NewTransaction(s.state) + c.Assert(tr.Set("core", "experimental.move-snap-home-dir", true), IsNil) + tr.Commit() + + si := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(3)} + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: si.Revision, + }) + + chg := s.state.NewChange("migrate-home", "...") + tss, err := snapstate.MigrateHome(s.state, []string{"some-snap"}) + c.Assert(err, IsNil) + + c.Assert(tss, HasLen, 1) + c.Assert(taskNames(tss[0].Tasks()), DeepEquals, []string{ + `prepare-snap`, + `stop-snap-services`, + `unlink-current-snap`, + `migrate-snap-home`, + `link-snap`, + `start-snap-services`, + }) + + for _, ts := range tss { + chg.AddAll(ts) + } + + // fail the change after the link-snap task (after state is saved) + s.o.TaskRunner().AddHandler("fail", func(*state.Task, *tomb.Tomb) error { + return errors.New("boom") + }, nil) + + failingTask := s.state.NewTask("fail", "expected failure") + chg.AddTask(failingTask) + linkTask := findLastTask(chg, "link-snap") + failingTask.WaitFor(linkTask) + for _, lane := range linkTask.Lanes() { + failingTask.JoinLane(lane) + } + + defer s.se.Stop() + s.settle(c) + + c.Assert(chg.Err(), ErrorMatches, `(.|\s)* expected failure \(boom\)`) + c.Assert(chg.IsReady(), Equals, true) + + s.fakeBackend.ops.MustFindOp(c, "init-exposed-snap-home") + s.fakeBackend.ops.MustFindOp(c, "undo-init-exposed-snap-home") + + // check migration is off in state and seq file + assertMigrationState(c, s.state, "some-snap", nil) +} + +func (s *snapmgrTestSuite) TestMigrateHomeFailIfUnsetFeature(c *C) { + s.state.Lock() + defer s.state.Unlock() + + tss, err := snapstate.MigrateHome(s.state, []string{"some-snap"}) + c.Check(tss, IsNil) + c.Assert(err, ErrorMatches, `cannot migrate to ~/Snap: flag "experimental.move-snap-home-dir" is not set`) +} + +func (s *snapmgrTestSuite) TestMigrateHomeFailIfSnapNotInstalled(c *C) { + s.state.Lock() + defer s.state.Unlock() + + tr := config.NewTransaction(s.state) + c.Assert(tr.Set("core", "experimental.move-snap-home-dir", true), IsNil) + tr.Commit() + + si := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(3)} + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: si.Revision, + SnapType: "app", + }) + + tss, err := snapstate.MigrateHome(s.state, []string{"some-snap", "other-snap"}) + c.Check(tss, IsNil) + c.Assert(err, ErrorMatches, `snap "other-snap" is not installed`) +} + +func (s *snapmgrTestSuite) TestMigrateHomeFailIfAlreadyMigrated(c *C) { + s.state.Lock() + defer s.state.Unlock() + + tr := config.NewTransaction(s.state) + c.Assert(tr.Set("core", "experimental.move-snap-home-dir", true), IsNil) + tr.Commit() + + si := &snap.SideInfo{RealName: "some-snap", Revision: snap.R(3)} + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: si.Revision, + SnapType: "app", + MigratedToExposedHome: true, + }) + + tss, err := snapstate.MigrateHome(s.state, []string{"some-snap"}) + c.Check(tss, IsNil) + c.Assert(err, ErrorMatches, `cannot migrate "some-snap" to ~/Snap: already migrated`) +} + +func taskNames(tasks []*state.Task) []string { + var names []string + + for _, t := range tasks { + names = append(names, t.Kind()) + } + + return names +} + func (s *snapmgrTestSuite) TestMigrationTriggers(c *C) { c.Skip("TODO:Snap-folder: no automatic migration for core22 snaps to ~/Snap folder for now") From 94b1056fbf0b043b52227c1f19f24306ee1e3bd0 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 8 Jun 2022 12:36:26 +0200 Subject: [PATCH 043/153] secboot/keymgr: stage and transition encryption keys Signed-off-by: Maciej Borzecki --- secboot/keymgr/export_test.go | 6 - secboot/keymgr/keymgr_luks2.go | 111 ++++++++++---- secboot/keymgr/keymgr_luks2_test.go | 215 ++++++++++++++++------------ 3 files changed, 205 insertions(+), 127 deletions(-) diff --git a/secboot/keymgr/export_test.go b/secboot/keymgr/export_test.go index 2607f5658cc..95802e732b2 100644 --- a/secboot/keymgr/export_test.go +++ b/secboot/keymgr/export_test.go @@ -30,10 +30,4 @@ func MockGetDiskUnlockKeyFromKernel(f func(prefix, devicePath string, remove boo return restore } -func MockAddKeyToUserKeyring(f func(key []byte, devicePath, purpose, prefix string) error) (restore func()) { - restore = testutil.Backup(&keyringAddKeyToUserKeyring) - keyringAddKeyToUserKeyring = f - return restore -} - var RecoveryKDF = recoveryKDF diff --git a/secboot/keymgr/keymgr_luks2.go b/secboot/keymgr/keymgr_luks2.go index 74a5c2d2933..9042fabbf81 100644 --- a/secboot/keymgr/keymgr_luks2.go +++ b/secboot/keymgr/keymgr_luks2.go @@ -27,7 +27,6 @@ import ( sb "github.com/snapcore/secboot" "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/secboot/keyring" "github.com/snapcore/snapd/secboot/keys" "github.com/snapcore/snapd/secboot/luks2" ) @@ -43,7 +42,6 @@ const ( var ( sbGetDiskUnlockKeyFromKernel = sb.GetDiskUnlockKeyFromKernel - keyringAddKeyToUserKeyring = keyring.AddKeyToUserKeyring ) func getEncryptionKeyFromUserKeyring(dev string) ([]byte, error) { @@ -59,7 +57,9 @@ func getEncryptionKeyFromUserKeyring(dev string) ([]byte, error) { return currKey, err } -var keyslotFull = regexp.MustCompile(`^.* cryptsetup failed with: Key slot [0-9]+ is full, please select another one\.$`) +// TODO rather than inspecting the error messages, parse the LUKS2 headers + +var keyslotFull = regexp.MustCompile(`^.*cryptsetup failed with: Key slot [0-9]+ is full, please select another one\.$`) // IsKeyslotAlreadyUsed returns true if the error indicates that the keyslot // attempted for a given key is already used @@ -160,26 +160,24 @@ func RemoveRecoveryKeyFromLUKSDeviceUsingKey(currKey keys.EncryptionKey, dev str return nil } -// ChangeLUKSDeviceEncryptionKey changes the main encryption key of the device. -// Uses an existing unlock key of that device, which is present in the kernel -// user keyring. Once complete the user keyring contains the new encryption key. -func ChangeLUKSDeviceEncryptionKey(newKey keys.EncryptionKey, dev string) error { +// StageLUKSDeviceEncryptionKeyChange stages a new encryption key with the goal +// of changing the main encryption key referenced in keyslot 0. The operation is +// authorized using the key that unlocked the device and is stored in the +// keyring (as it happens during factory reset). +func StageLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error { if len(newKey) != keys.EncryptionKeySize { return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize) } + // the key to authorize the device is in the keyring currKey, err := getEncryptionKeyFromUserKeyring(dev) if err != nil { return err } - // we only have the current key, we cannot add a key to an occupied - // keyslot, and cannot start with killing its keyslot as that would make - // the device unusable, so instead add the new key to an auxiliary - // keyslot, then use the new key to authorize removal of keyslot 0 - // (which refers to the old key), add the new key again, but this time - // to keyslot 0, lastly kill the aux keyslot + // TODO rather than inspecting the errors, parse the LUKS2 headers + // free up the temp slot if err := luks2.KillSlot(dev, tempKeySlot, currKey); err != nil { if !isKeyslotNotActive(err) { return fmt.Errorf("cannot kill the temporary keyslot: %v", err) @@ -193,35 +191,86 @@ func ChangeLUKSDeviceEncryptionKey(newKey keys.EncryptionKey, dev string) error if err := luks2.AddKey(dev, currKey[:], newKey, &options); err != nil { return fmt.Errorf("cannot add temporary key: %v", err) } + return nil +} - // now it should be possible to kill the original keyslot by using the - // new key for authorization +// TransitionLUKSDeviceEncryptionKeyChange completes the main encryption key +// change to the new key provided in the parameters. The new key must have been +// staged before, thus it can authorize LUKS operations. Lastly, the unlock key +// in the keyring is updated to the new key. +func TransitionLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error { + if len(newKey) != keys.EncryptionKeySize { + return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize) + } + + // the expected state is as follows: + // keys lot 0 - the old encryption key + // key slot 2 - the new encryption key (added during --stage) + // the desired state is: + // key slot 0 - the new encryption key + // key slot 2 - empty + // it is possible that the system was rebooted right after key slot 0 was + // populated with the new key and key slot 2 was emptied + + // there is no state information on disk which would if scenario 1 + // occurred and to which stage it was executed, but we need to find out + // if key slot 2 is in use (as the caller believes that a key was staged + // earlier); do this indirectly by trying to add a key to key slot 2 + + // TODO rather than inspecting the errors, parse the LUKS2 headers + + tempKeyslotAlreadyUsed := true + + options := luks2.AddKeyOptions{ + KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond}, + Slot: tempKeySlot, + } + err := luks2.AddKey(dev, newKey, newKey, &options) + if err == nil { + // key slot is not in use, so we are dealing with unexpected reboot scenario + tempKeyslotAlreadyUsed = false + } else if err != nil && !IsKeyslotAlreadyUsed(err) { + return fmt.Errorf("cannot add new encryption key: %v", err) + } + + if !tempKeyslotAlreadyUsed { + // since the key slot was not used, it means that the transition + // was already carried out (since it got authorized by the new + // key), so now all is needed is to remove the added key + if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil { + return fmt.Errorf("cannot kill temporary key slot: %v", err) + } + + return nil + } + + // first kill the main encryption key slot, authorize the operation + // using the new key which must have been added to the temp keyslot in + // the stage operation if err := luks2.KillSlot(dev, encryptionKeySlot, newKey); err != nil { if !isKeyslotNotActive(err) { - return fmt.Errorf("cannot kill existing slot: %v", err) + return fmt.Errorf("cannot kill the encryption key slot: %v", err) } } - options.Slot = encryptionKeySlot - // add the new key to keyslot 0 + + options = luks2.AddKeyOptions{ + KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond}, + Slot: encryptionKeySlot, + } if err := luks2.AddKey(dev, newKey, newKey, &options); err != nil { - return fmt.Errorf("cannot add key: %v", err) + return fmt.Errorf("cannot add new encryption key: %v", err) } - // and kill the aux slot + + // now it should be possible to kill the temporary keyslot by using the + // new key for authorization if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil { - return fmt.Errorf("cannot kill temporary key slot: %v", err) + if !isKeyslotNotActive(err) { + return fmt.Errorf("cannot kill temporary key slot: %v", err) + } } // TODO needed? if err := luks2.SetSlotPriority(dev, encryptionKeySlot, luks2.SlotPriorityHigh); err != nil { - return fmt.Errorf("cannot change keyslot priority: %v", err) - } - - const keyringPurposeDiskUnlock = "unlock" - const keyringPrefix = "ubuntu-fde" - // TODO: make the key permanent in the keyring, investigate why timeout - // is set to a very large number of weeks, but not tagged as perm in - // /proc/keys - if err := keyringAddKeyToUserKeyring(newKey, dev, keyringPurposeDiskUnlock, keyringPrefix); err != nil { - return fmt.Errorf("cannot add key to user keyring: %v", err) + return fmt.Errorf("cannot change key slot priority: %v", err) } return nil } diff --git a/secboot/keymgr/keymgr_luks2_test.go b/secboot/keymgr/keymgr_luks2_test.go index a84afc5c8b0..36bd7de62f4 100644 --- a/secboot/keymgr/keymgr_luks2_test.go +++ b/secboot/keymgr/keymgr_luks2_test.go @@ -327,7 +327,7 @@ done c.Assert(filepath.Join(s.rootDir, "unlock.key"), testutil.FileEquals, key) } -func (s *keymgrSuite) TestChangeEncryptionKeyHappy(c *C) { +func (s *keymgrSuite) TestStageEncryptionKeyHappy(c *C) { unlockKey := "1234abcd" getCalls := 0 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { @@ -339,17 +339,6 @@ func (s *keymgrSuite) TestChangeEncryptionKeyHappy(c *C) { }) defer restore() - addCalls := 0 - restore = keymgr.MockAddKeyToUserKeyring(func(key []byte, devicePath, purpose, prefix string) error { - addCalls++ - c.Assert(key, DeepEquals, bytes.Repeat([]byte{1}, 32)) - c.Check(devicePath, Equals, "/dev/foobar") - c.Check(purpose, Equals, "unlock") - c.Check(prefix, Equals, "ubuntu-fde") - return nil - }) - defer restore() - cmd := testutil.MockCommand(c, "cryptsetup", ` while [ "$#" -gt 1 ]; do case "$1" in @@ -366,16 +355,15 @@ done defer cmd.Restore() // try a too short key key := bytes.Repeat([]byte{1}, 12) - err := keymgr.ChangeLUKSDeviceEncryptionKey(key, "/dev/foobar") + err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") c.Assert(err, ErrorMatches, "cannot use a key of size different than 32") key = bytes.Repeat([]byte{1}, 32) - err = keymgr.ChangeLUKSDeviceEncryptionKey(key, "/dev/foobar") + err = keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") c.Assert(err, IsNil) c.Assert(getCalls, Equals, 1) - c.Assert(addCalls, Equals, 1) calls := cmd.Calls() - c.Assert(calls, HasLen, 6) + c.Assert(calls, HasLen, 2) c.Assert(calls[0], DeepEquals, []string{ "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", }) @@ -391,32 +379,9 @@ done "--key-slot", "2", "/dev/foobar", "-", }) - c.Assert(calls[2], DeepEquals, []string{ - "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0", - }) - c.Assert(calls[3], HasLen, 14) - c.Assert(calls[3][5], testutil.Contains, dirs.RunDir) - calls[3][5] = "" - // actual new key - c.Assert(calls[3], DeepEquals, []string{ - "cryptsetup", "luksAddKey", "--type", "luks2", - "--key-file", "", - "--pbkdf", "argon2i", - "--iter-time", "100", - "--key-slot", "0", - "/dev/foobar", "-", - }) - // kill the temp key - c.Assert(calls[4], DeepEquals, []string{ - "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", - }) - // set priority - c.Assert(calls[5], DeepEquals, []string{ - "cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar", - }) } -func (s *keymgrSuite) TestChangeEncryptionKeyGetUnlockFail(c *C) { +func (s *keymgrSuite) TestStageEncryptionKeyGetUnlockFail(c *C) { getCalls := 0 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { getCalls++ @@ -425,46 +390,73 @@ func (s *keymgrSuite) TestChangeEncryptionKeyGetUnlockFail(c *C) { }) defer restore() - restore = keymgr.MockAddKeyToUserKeyring(func(key []byte, devicePath, purpose, prefix string) error { - c.Fatalf("unexpected call") - return fmt.Errorf("unexpected call") - }) - defer restore() - key := bytes.Repeat([]byte{1}, 32) - err := keymgr.ChangeLUKSDeviceEncryptionKey(key, "/dev/foobar") + err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") c.Assert(err, ErrorMatches, "cannot obtain current unlock key for /dev/foobar: cannot find key in kernel keyring") c.Assert(s.cryptsetupCmd.Calls(), HasLen, 0) } -func (s *keymgrSuite) TestChangeEncryptionKeyAddKeyringFails(c *C) { +func (s *keymgrSuite) TestChangeEncryptionTempKeyFails(c *C) { unlockKey := "1234abcd" getCalls := 0 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { getCalls++ - c.Check(devicePath, Equals, "/dev/foobar") - c.Check(remove, Equals, false) - c.Check(prefix, Equals, "ubuntu-fde") return []byte(unlockKey), nil }) defer restore() - addCalls := 0 - restore = keymgr.MockAddKeyToUserKeyring(func(key []byte, devicePath, purpose, prefix string) error { - addCalls++ - c.Assert(key, DeepEquals, bytes.Repeat([]byte{1}, 32)) - c.Check(devicePath, Equals, "/dev/foobar") - c.Check(purpose, Equals, "unlock") - c.Check(prefix, Equals, "ubuntu-fde") - return fmt.Errorf("add keyring fails") + cmd := testutil.MockCommand(c, "cryptsetup", ` +while [ "$#" -gt 1 ]; do + case "$1" in + --key-file) + cat "$2" > /dev/null + shift + ;; + luksAddKey) + add=1 + ;; + --key-slot) + if [ "$add" = "1" ] && [ "$2" = "2" ]; then + echo "mock failure" >&2 + exit 1 + fi + ;; + *) + ;; + esac + shift +done +`) + defer cmd.Restore() + + key := bytes.Repeat([]byte{1}, 32) + err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, ErrorMatches, "cannot add temporary key: cryptsetup failed with: mock failure") + c.Assert(getCalls, Equals, 1) + calls := cmd.Calls() + c.Assert(calls, HasLen, 2) +} + +func (s *keymgrSuite) TestTransitionEncryptionKeyExpectedHappy(c *C) { + restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { + c.Errorf("unepected call") + return nil, fmt.Errorf("unexpected call") }) defer restore() cmd := testutil.MockCommand(c, "cryptsetup", ` while [ "$#" -gt 1 ]; do case "$1" in + luksAddKey) + keyadd=1 + shift + ;; + --key-slot) + keyslot="$2" + shift 2 + ;; --key-file) - cat "$2" + cat "$2" > /dev/null shift 2 ;; *) @@ -472,29 +464,65 @@ while [ "$#" -gt 1 ]; do ;; esac done +if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then + echo "Key slot 2 is full, please select another one." >&2 + exit 1 +fi `) defer cmd.Restore() + // try a too short key + key := bytes.Repeat([]byte{1}, 12) + err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, ErrorMatches, "cannot use a key of size different than 32") - key := bytes.Repeat([]byte{1}, 32) - err := keymgr.ChangeLUKSDeviceEncryptionKey(key, "/dev/foobar") - c.Assert(err, ErrorMatches, "cannot add key to user keyring: add keyring fails") - c.Assert(getCalls, Equals, 1) - c.Assert(addCalls, Equals, 1) + key = bytes.Repeat([]byte{1}, 32) + err = keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, IsNil) calls := cmd.Calls() - c.Assert(calls, HasLen, 6) + c.Assert(calls, HasLen, 5) + // probing the key slot use + c.Assert(calls[0], HasLen, 14) + c.Assert(calls[0][5], testutil.Contains, dirs.RunDir) + calls[0][5] = "" + // temporary key + c.Assert(calls[0], DeepEquals, []string{ + "cryptsetup", "luksAddKey", "--type", "luks2", + "--key-file", "", + "--pbkdf", "argon2i", + "--iter-time", "100", + "--key-slot", "2", + "/dev/foobar", "-", + }) + // killing the encryption key + c.Assert(calls[1], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0", + }) + // adding the new encryption key + c.Assert(calls[2], HasLen, 14) + c.Assert(calls[2][5], testutil.Contains, dirs.RunDir) + calls[2][5] = "" + c.Assert(calls[2], DeepEquals, []string{ + "cryptsetup", "luksAddKey", "--type", "luks2", + "--key-file", "", + "--pbkdf", "argon2i", + "--iter-time", "100", + "--key-slot", "0", + "/dev/foobar", "-", + }) + // kill the temp key + c.Assert(calls[3], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", + }) + // set priority + c.Assert(calls[4], DeepEquals, []string{ + "cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar", + }) } -func (s *keymgrSuite) TestChangeEncryptionTempKeyFails(c *C) { - unlockKey := "1234abcd" - getCalls := 0 +func (s *keymgrSuite) TestTransitionEncryptionKeyPostRebootHappy(c *C) { restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { - getCalls++ - return []byte(unlockKey), nil - }) - defer restore() - restore = keymgr.MockAddKeyToUserKeyring(func(key []byte, devicePath, purpose, prefix string) error { - c.Fatalf("unexpected call") - return fmt.Errorf("add keyring fails") + c.Errorf("unepected call") + return nil, fmt.Errorf("unexpected call") }) defer restore() @@ -503,31 +531,38 @@ while [ "$#" -gt 1 ]; do case "$1" in --key-file) cat "$2" > /dev/null - shift - ;; - luksAddKey) - add=1 - ;; - --key-slot) - if [ "$add" = "1" ] && [ "$2" = "2" ]; then - echo "mock failure" >&2 - exit 1 - fi + shift 2 ;; *) + shift ;; esac - shift done `) defer cmd.Restore() key := bytes.Repeat([]byte{1}, 32) - err := keymgr.ChangeLUKSDeviceEncryptionKey(key, "/dev/foobar") - c.Assert(err, ErrorMatches, "cannot add temporary key: cryptsetup failed with: mock failure") - c.Assert(getCalls, Equals, 1) + err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, IsNil) calls := cmd.Calls() c.Assert(calls, HasLen, 2) + c.Assert(calls[0], HasLen, 14) + c.Assert(calls[0][5], testutil.Contains, dirs.RunDir) + calls[0][5] = "" + // adding to a temporary key slot is successful, indicating a previously + // successful transition + c.Assert(calls[0], DeepEquals, []string{ + "cryptsetup", "luksAddKey", "--type", "luks2", + "--key-file", "", + "--pbkdf", "argon2i", + "--iter-time", "100", + "--key-slot", "2", + "/dev/foobar", "-", + }) + // an a cleanup of the temp key slot + c.Assert(calls[1], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", + }) } func (s *keymgrSuite) TestRecoveryKDF(c *C) { From a9085d30f6c97ed060c7107cafbc1b18b9caa0a3 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 8 Jun 2022 08:27:16 +0200 Subject: [PATCH 044/153] cmd/snap-fde-keymgr: stage and transition encryption keys Signed-off-by: Maciej Borzecki --- cmd/snap-fde-keymgr/export_test.go | 12 +++- cmd/snap-fde-keymgr/main.go | 30 +++++++-- cmd/snap-fde-keymgr/main_test.go | 99 ++++++++++++++++++++++++++++-- 3 files changed, 128 insertions(+), 13 deletions(-) diff --git a/cmd/snap-fde-keymgr/export_test.go b/cmd/snap-fde-keymgr/export_test.go index a08a308ba7e..e8dc63bc4ac 100644 --- a/cmd/snap-fde-keymgr/export_test.go +++ b/cmd/snap-fde-keymgr/export_test.go @@ -51,9 +51,15 @@ func MockRemoveRecoveryKeyFromLUKSUsingKey(f func(key keys.EncryptionKey, dev st return restore } -func MockChangeLUKSEncryptionKey(f func(newKey keys.EncryptionKey, dev string) error) (restore func()) { - restore = testutil.Backup(&keymgrChangeLUKSDeviceEncryptionKey) - keymgrChangeLUKSDeviceEncryptionKey = f +func MockStageLUKSEncryptionKeyChange(f func(newKey keys.EncryptionKey, dev string) error) (restore func()) { + restore = testutil.Backup(&keymgrStageLUKSDeviceEncryptionKeyChange) + keymgrStageLUKSDeviceEncryptionKeyChange = f + return restore +} + +func MockTransitionLUKSEncryptionKeyChange(f func(newKey keys.EncryptionKey, dev string) error) (restore func()) { + restore = testutil.Backup(&keymgrTransitionLUKSDeviceEncryptionKeyChange) + keymgrTransitionLUKSDeviceEncryptionKeyChange = f return restore } diff --git a/cmd/snap-fde-keymgr/main.go b/cmd/snap-fde-keymgr/main.go index 934e3468d71..5b176b611d2 100644 --- a/cmd/snap-fde-keymgr/main.go +++ b/cmd/snap-fde-keymgr/main.go @@ -52,7 +52,9 @@ type cmdRemoveRecoveryKey struct { } type cmdChangeEncryptionKey struct { - Device string `long:"device" description:"encrypted device" required:"yes"` + Device string `long:"device" description:"encrypted device" required:"yes"` + Stage bool `long:"stage" description:"stage the new key"` + Transition bool `long:"transition" description:"replace the old key, unstage the new"` } type options struct { @@ -66,7 +68,8 @@ var ( keymgrAddRecoveryKeyToLUKSDeviceUsingKey = keymgr.AddRecoveryKeyToLUKSDeviceUsingKey keymgrRemoveRecoveryKeyFromLUKSDevice = keymgr.RemoveRecoveryKeyFromLUKSDevice keymgrRemoveRecoveryKeyFromLUKSDeviceUsingKey = keymgr.RemoveRecoveryKeyFromLUKSDeviceUsingKey - keymgrChangeLUKSDeviceEncryptionKey = keymgr.ChangeLUKSDeviceEncryptionKey + keymgrStageLUKSDeviceEncryptionKeyChange = keymgr.StageLUKSDeviceEncryptionKeyChange + keymgrTransitionLUKSDeviceEncryptionKeyChange = keymgr.TransitionLUKSDeviceEncryptionKeyChange ) func validateAuthorizations(authorizations []string) error { @@ -201,13 +204,32 @@ type newKey struct { } func (c *cmdChangeEncryptionKey) Execute(args []string) error { + if c.Stage && c.Transition { + return fmt.Errorf("cannot both stage and transition the encryption key change") + } + if !c.Stage && !c.Transition { + return fmt.Errorf("cannot change encryption key without stage or transition request") + } + var newEncryptionKeyData newKey dec := json.NewDecoder(osStdin) if err := dec.Decode(&newEncryptionKeyData); err != nil { return fmt.Errorf("cannot obtain new encryption key: %v", err) } - if err := keymgrChangeLUKSDeviceEncryptionKey(newEncryptionKeyData.Key, c.Device); err != nil { - return fmt.Errorf("cannot change LUKS device encryption key: %v", err) + switch { + case c.Stage: + // staging the key change authorizes the operation using a key + // from the keyring + if err := keymgrStageLUKSDeviceEncryptionKeyChange(newEncryptionKeyData.Key, c.Device); err != nil { + return fmt.Errorf("cannot stage LUKS device encryption key change: %v", err) + } + case c.Transition: + // transitioning the key change authorizes the operation using + // the currently provided key (which must have been staged + // before hence the op will be authorized successfully) + if err := keymgrTransitionLUKSDeviceEncryptionKeyChange(newEncryptionKeyData.Key, c.Device); err != nil { + return fmt.Errorf("cannot transition LUKS device encryption key change: %v", err) + } } return nil } diff --git a/cmd/snap-fde-keymgr/main_test.go b/cmd/snap-fde-keymgr/main_test.go index 3ceefa8ab0f..191091d770a 100644 --- a/cmd/snap-fde-keymgr/main_test.go +++ b/cmd/snap-fde-keymgr/main_test.go @@ -338,28 +338,115 @@ func (s *mainSuite) TestRemoveKeyRequiresAuthz(c *C) { c.Assert(err, ErrorMatches, `cannot remove recovery keys with invalid authorizations: authorization file .*/authz.key does not exist`) } +// 1 in ASCII repeated 32 times +const all1sKey = `{"key":"MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE="}` + func (s *mainSuite) TestChangeEncryptionKey(c *C) { - const all1sKey = `{"key":"MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE="}` + b := bytes.NewBufferString(all1sKey) + restore := main.MockOsStdin(b) + defer restore() + unexpectedCall := func(newKey keys.EncryptionKey, luksDev string) error { + c.Errorf("unexpected call") + return fmt.Errorf("unexpected call") + } + defer main.MockStageLUKSEncryptionKeyChange(unexpectedCall) + defer main.MockTransitionLUKSEncryptionKeyChange(unexpectedCall) + + err := main.Run([]string{ + "change-encryption-key", + "--device", "/dev/vda4", + }) + c.Assert(err, ErrorMatches, "cannot change encryption key without stage or transition request") + + err = main.Run([]string{ + "change-encryption-key", + "--device", "/dev/vda4", + "--stage", "--transition", + }) + c.Assert(err, ErrorMatches, "cannot both stage and transition the encryption key change") +} + +func (s *mainSuite) TestStageEncryptionKey(c *C) { b := bytes.NewBufferString(all1sKey) restore := main.MockOsStdin(b) defer restore() dev := "" - changeCalls := 0 + stageCalls := 0 var key []byte - restore = main.MockChangeLUKSEncryptionKey(func(newKey keys.EncryptionKey, luksDev string) error { - changeCalls++ + var stageErr error + restore = main.MockStageLUKSEncryptionKeyChange(func(newKey keys.EncryptionKey, luksDev string) error { + stageCalls++ dev = luksDev key = newKey - return nil + return stageErr + }) + defer restore() + restore = main.MockTransitionLUKSEncryptionKeyChange(func(newKey keys.EncryptionKey, luksDev string) error { + c.Errorf("unexpected call") + return fmt.Errorf("unexpected call") }) defer restore() err := main.Run([]string{ "change-encryption-key", "--device", "/dev/vda4", + "--stage", }) c.Assert(err, IsNil) - c.Check(changeCalls, Equals, 1) + c.Check(stageCalls, Equals, 1) c.Check(dev, Equals, "/dev/vda4") // secboot encryption key size c.Check(key, DeepEquals, bytes.Repeat([]byte("1"), 32)) + + restore = main.MockOsStdin(bytes.NewBufferString(all1sKey)) + defer restore() + stageErr = fmt.Errorf("mock stage error") + err = main.Run([]string{ + "change-encryption-key", + "--device", "/dev/vda4", + "--stage", + }) + c.Assert(err, ErrorMatches, "cannot stage LUKS device encryption key change: mock stage error") +} + +func (s *mainSuite) TestTransitionEncryptionKey(c *C) { + b := bytes.NewBufferString(all1sKey) + restore := main.MockOsStdin(b) + defer restore() + dev := "" + transitionCalls := 0 + var key []byte + var transitionErr error + restore = main.MockStageLUKSEncryptionKeyChange(func(newKey keys.EncryptionKey, luksDev string) error { + c.Errorf("unexpected call") + return fmt.Errorf("unexpected call") + }) + defer restore() + restore = main.MockTransitionLUKSEncryptionKeyChange(func(newKey keys.EncryptionKey, luksDev string) error { + transitionCalls++ + dev = luksDev + key = newKey + return transitionErr + }) + defer restore() + defer restore() + err := main.Run([]string{ + "change-encryption-key", + "--device", "/dev/vda4", + "--transition", + }) + c.Assert(err, IsNil) + c.Check(transitionCalls, Equals, 1) + c.Check(dev, Equals, "/dev/vda4") + // secboot encryption key size + c.Check(key, DeepEquals, bytes.Repeat([]byte("1"), 32)) + + restore = main.MockOsStdin(bytes.NewBufferString(all1sKey)) + defer restore() + transitionErr = fmt.Errorf("mock transition error") + err = main.Run([]string{ + "change-encryption-key", + "--device", "/dev/vda4", + "--transition", + }) + c.Assert(err, ErrorMatches, "cannot transition LUKS device encryption key change: mock transition error") } From 1e466281e2d8d7c21040d744e5b280c30f8bcbee Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 9 Jun 2022 15:19:11 +0200 Subject: [PATCH 045/153] tests/main/uc20-create-partitions-encrypt: verify encryption key stage and transition Signed-off-by: Maciej Borzecki --- .../uc20-create-partitions-encrypt/task.yaml | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/main/uc20-create-partitions-encrypt/task.yaml b/tests/main/uc20-create-partitions-encrypt/task.yaml index 801fa21a0b3..160f286d54c 100644 --- a/tests/main/uc20-create-partitions-encrypt/task.yaml +++ b/tests/main/uc20-create-partitions-encrypt/task.yaml @@ -71,6 +71,11 @@ restore: | losetup -d "$LOOP" losetup -l | NOMATCH "$LOOP" fi + + # purge the key we added + systemd-run --pipe --wait --collect -p KeyringMode=inherit -- \ + /usr/bin/keyctl purge -s user "ubuntu-fde:${LOOP}p4:unlock" || true + apt autoremove -y cryptsetup rm -Rf /run/mnt @@ -218,6 +223,44 @@ execute: | not cryptsetup open --key-file recovery-key "${LOOP}p5" test-data not cryptsetup open --key-file recovery-key "${LOOP}p4" test-save + echo "Verify encryption key change" + # first get the relevant ubuntu-save key to the user keyring + systemd-run --pipe --wait --collect -p KeyringMode=inherit -- \ + /usr/bin/keyctl padd user "ubuntu-fde:${LOOP}p4:unlock" @u < save-key + + # the new key is all ones + printf "11111111111111111111111111111111" > new-save-key + echo '{"key":"MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE="}' | \ + systemd-run --pipe --wait --collect -p KeyringMode=inherit -- \ + /usr/lib/snapd/snap-fde-keymgr change-encryption-key \ + --device "${LOOP}p4" --stage + # now it's possible to open save with both the old and new keys + cryptsetup open --key-file save-key "${LOOP}p4" test-save + cryptsetup close /dev/mapper/test-save + cryptsetup open --key-file new-save-key "${LOOP}p4" test-save + cryptsetup close /dev/mapper/test-save + + # now transition the key + echo '{"key":"MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE="}' | \ + systemd-run --pipe --wait --collect -p KeyringMode=inherit -- \ + /usr/lib/snapd/snap-fde-keymgr change-encryption-key \ + --device "${LOOP}p4" --transition + + # the old key no longer works + not cryptsetup open --key-file save-key "${LOOP}p4" test-save + # but the new key does + cryptsetup open --key-file new-save-key "${LOOP}p4" test-save + cryptsetup close /dev/mapper/test-save + + # try to transition once more, such that we execute a reboot like scenario + echo '{"key":"MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE="}' | \ + systemd-run --pipe --wait --collect -p KeyringMode=inherit -- \ + /usr/lib/snapd/snap-fde-keymgr change-encryption-key \ + --device "${LOOP}p4" --transition + # and opening should still work + cryptsetup open --key-file new-save-key "${LOOP}p4" test-save + cryptsetup close /dev/mapper/test-save + LOOP_BASENAME="$(basename "$LOOP")" # disk things From c54c9be335e0c429bef2d5e9d0b60e35ea22ab50 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 9 Jun 2022 10:52:08 +0200 Subject: [PATCH 046/153] gadget/install: do not assume dm device has same block size as disk --- gadget/install/install.go | 8 +++++++- gadget/install/install_test.go | 22 +++++++++++++++++++--- osutil/disks/disks_darwin.go | 4 ++++ osutil/disks/disks_linux.go | 4 ++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/gadget/install/install.go b/gadget/install/install.go index aa0197e314c..5ed5c6918c6 100644 --- a/gadget/install/install.go +++ b/gadget/install/install.go @@ -102,6 +102,7 @@ func installOnePartition(part *gadget.OnDiskStructure, encryptionType secboot.En // a device that carries the filesystem, which is either the raw // /dev/, or the mapped LUKS device if the structure is encrypted fsDevice = part.Node + fsSectorSize := sectorSize if mustEncrypt && roleNeedsEncryption(part.Role) { timings.Run(perfTimings, fmt.Sprintf("make-key-set[%s]", partDisp), @@ -143,6 +144,11 @@ func installOnePartition(part *gadget.OnDiskStructure, encryptionType secboot.En // operate on the right device fsDevice = dataPart.Node() logger.Noticef("encrypted filesystem device %v", fsDevice) + fsSectorSizeInt, err := disks.SectorSize(fsDevice) + if err != nil { + return "", nil, err + } + fsSectorSize = quantity.Size(fsSectorSizeInt) } timings.Run(perfTimings, fmt.Sprintf("make-filesystem[%s]", partDisp), @@ -153,7 +159,7 @@ func installOnePartition(part *gadget.OnDiskStructure, encryptionType secboot.En Device: fsDevice, Label: part.Label, Size: part.Size, - SectorSize: sectorSize, + SectorSize: fsSectorSize, }) }) if err != nil { diff --git a/gadget/install/install_test.go b/gadget/install/install_test.go index 88c0badd984..4a37667822c 100644 --- a/gadget/install/install_test.go +++ b/gadget/install/install_test.go @@ -153,6 +153,11 @@ func (s *installSuite) testInstall(c *C, opts installOpts) { mockCryptsetup := testutil.MockCommand(c, "cryptsetup", "") defer mockCryptsetup.Restore() + if opts.encryption { + mockBlockdev := testutil.MockCommand(c, "blockdev", "case ${1} in --getss) echo 4096; exit 0;; esac; exit 1") + defer mockBlockdev.Restore() + } + restore = install.MockEnsureNodesExist(func(dss []gadget.OnDiskStructure, timeout time.Duration) error { c.Assert(timeout, Equals, 5*time.Second) c.Assert(dss, DeepEquals, []gadget.OnDiskStructure{ @@ -257,22 +262,24 @@ func (s *installSuite) testInstall(c *C, opts installOpts) { c.Assert(typ, Equals, "ext4") if opts.encryption { c.Assert(img, Equals, "/dev/mapper/ubuntu-save") + c.Assert(sectorSize, Equals, quantity.Size(4096)) } else { c.Assert(img, Equals, "/dev/mmcblk0p3") + c.Assert(sectorSize, Equals, quantity.Size(512)) } c.Assert(label, Equals, "ubuntu-save") c.Assert(devSize, Equals, 16*quantity.SizeMiB) - c.Assert(sectorSize, Equals, quantity.Size(512)) case 3: c.Assert(typ, Equals, "ext4") if opts.encryption { c.Assert(img, Equals, "/dev/mapper/ubuntu-data") + c.Assert(sectorSize, Equals, quantity.Size(4096)) } else { c.Assert(img, Equals, "/dev/mmcblk0p4") + c.Assert(sectorSize, Equals, quantity.Size(512)) } c.Assert(label, Equals, "ubuntu-data") c.Assert(devSize, Equals, (30528-(1+1200+750+16))*quantity.SizeMiB) - c.Assert(sectorSize, Equals, quantity.Size(512)) default: c.Errorf("unexpected call (%d) to mkfs.Make()", mkfsCall) return fmt.Errorf("test broken") @@ -624,6 +631,11 @@ func (s *installSuite) testFactoryReset(c *C, opts factoryResetOpts) { mockCryptsetup := testutil.MockCommand(c, "cryptsetup", "") defer mockCryptsetup.Restore() + if opts.encryption { + mockBlockdev := testutil.MockCommand(c, "blockdev", "case ${1} in --getss) echo 4096; exit 0;; esac; exit 1") + defer mockBlockdev.Restore() + } + dataDev := "/dev/mmcblk0p4" if opts.noSave { dataDev = "/dev/mmcblk0p3" @@ -746,7 +758,11 @@ func (s *installSuite) testFactoryReset(c *C, opts factoryResetOpts) { } else { c.Assert(devSize, Equals, (30528-(1+1200+750+16))*quantity.SizeMiB) } - c.Assert(sectorSize, Equals, quantity.Size(512)) + if opts.encryption { + c.Assert(sectorSize, Equals, quantity.Size(4096)) + } else { + c.Assert(sectorSize, Equals, quantity.Size(512)) + } default: c.Errorf("unexpected call (%d) to mkfs.Make()", mkfsCall) return fmt.Errorf("test broken") diff --git a/osutil/disks/disks_darwin.go b/osutil/disks/disks_darwin.go index ed317aebb82..485440ebe30 100644 --- a/osutil/disks/disks_darwin.go +++ b/osutil/disks/disks_darwin.go @@ -74,3 +74,7 @@ func PartitionUUIDFromMountPoint(mountpoint string, opts *Options) (string, erro func PartitionUUID(node string) (string, error) { return "", osutil.ErrDarwin } + +func SectorSize(devname string) (uint64, error) { + return 0, osutil.ErrDarwin +} diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index d767108452c..40703c567b2 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -977,3 +977,7 @@ func PartitionUUID(node string) (string, error) { } return partUUID, nil } + +func SectorSize(devname string) (uint64, error) { + return blockDeviceSectorSize(devname) +} From 9c8d7055ee5596ba2e2e95a2916c3b44ae586fad Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Thu, 9 Jun 2022 11:45:59 -0300 Subject: [PATCH 047/153] Add spread execution for fedora 36 This change also removes executions from fedora-34 and some references to fedora-33 --- .github/workflows/test.yaml | 2 +- packaging/fedora-36 | 1 + spread.yaml | 7 ++----- tests/lib/reset.sh | 2 +- tests/main/cgroup-devices-v1/task.yaml | 2 +- tests/main/cgroup-freezer/task.yaml | 2 +- tests/main/interfaces-calendar-service/task.yaml | 5 +---- tests/main/interfaces-contacts-service/task.yaml | 4 +--- tests/main/microk8s-smoke/task.yaml | 3 +-- tests/main/snap-routine-portal-info/task.yaml | 1 - 10 files changed, 10 insertions(+), 19 deletions(-) create mode 120000 packaging/fedora-36 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index bc1f8d3cce3..ef49e21b355 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -382,8 +382,8 @@ jobs: - debian-10-64 - debian-11-64 - debian-sid-64 - - fedora-34-64 - fedora-35-64 + - fedora-36-64 - opensuse-15.3-64 - opensuse-tumbleweed-64 - ubuntu-14.04-64 diff --git a/packaging/fedora-36 b/packaging/fedora-36 new file mode 120000 index 00000000000..100fe0cd7bb --- /dev/null +++ b/packaging/fedora-36 @@ -0,0 +1 @@ +fedora \ No newline at end of file diff --git a/spread.yaml b/spread.yaml index 0df75487aff..26aeef7d201 100644 --- a/spread.yaml +++ b/spread.yaml @@ -143,13 +143,10 @@ backends: - debian-sid-64: workers: 6 - - fedora-33-64: - workers: 6 - manual: true - - fedora-34-64: - workers: 6 - fedora-35-64: workers: 6 + - fedora-36-64: + workers: 6 - arch-linux-64: workers: 6 diff --git a/tests/lib/reset.sh b/tests/lib/reset.sh index ea2efc11395..adffbe9a9f5 100755 --- a/tests/lib/reset.sh +++ b/tests/lib/reset.sh @@ -107,7 +107,7 @@ reset_classic() { EXTRA_NC_ARGS="-q 1" case "$SPREAD_SYSTEM" in - fedora-34-*|debian-10-*) + debian-10-*) # Param -q is not available on fedora 34 EXTRA_NC_ARGS="-w 1" ;; diff --git a/tests/main/cgroup-devices-v1/task.yaml b/tests/main/cgroup-devices-v1/task.yaml index 050dba37cc7..c17e1667d1c 100644 --- a/tests/main/cgroup-devices-v1/task.yaml +++ b/tests/main/cgroup-devices-v1/task.yaml @@ -1,6 +1,6 @@ summary: measuring basic properties of device cgroup # Disable the test on all systems that boot with cgroup v2 -systems: [ -fedora-33-*, -fedora-34-*, -fedora-35-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*, -ubuntu-22.04-*, -ubuntu-core-22-*] +systems: [ -fedora-35-*, -fedora-36-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*, -ubuntu-22.04-*, -ubuntu-core-22-*] execute: ./task.sh diff --git a/tests/main/cgroup-freezer/task.yaml b/tests/main/cgroup-freezer/task.yaml index c04a53e609d..d587177ac1a 100644 --- a/tests/main/cgroup-freezer/task.yaml +++ b/tests/main/cgroup-freezer/task.yaml @@ -5,7 +5,7 @@ details: | placed into the appropriate hierarchy under the freezer cgroup. # Disable the test on all systems that boot with cgroup v2 -systems: [ -fedora-33-*, -fedora-34-*, -fedora-35-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*, -ubuntu-22.04-*, -ubuntu-core-22-*] +systems: [ -fedora-35-*, -fedora-36-*, -debian-11-*, -debian-sid-*, -arch-*, -opensuse-tumbleweed-*, -ubuntu-21.10-*, -ubuntu-22.04-*, -ubuntu-core-22-*] prepare: | "$TESTSTOOLS"/snaps-state install-local test-snapd-sh diff --git a/tests/main/interfaces-calendar-service/task.yaml b/tests/main/interfaces-calendar-service/task.yaml index c2907dc5696..b5b67e8eb05 100644 --- a/tests/main/interfaces-calendar-service/task.yaml +++ b/tests/main/interfaces-calendar-service/task.yaml @@ -16,17 +16,14 @@ backends: [-autopkgtest] # https://github.com/snapcore/snapd/pull/7230 is landed # ubuntu-20.04+: test-snapd-eds is incompatible with eds version shipped with the distro # arch-linux: test-snapd-eds is incompatible with eds version shipped with the distro -# fedora-33,34: test-snapd-eds is incompatible with eds version shipped with the distro systems: - -amazon-* # no need to run this on amazon - -arch-linux-* # test-snapd-eds is incompatible with eds version shipped with the distro - -centos-* - -debian-11-* - -debian-sid-* - - -fedora-33-* # test-snapd-eds is incompatible with eds version shipped with the distro - - -fedora-34-* # test-snapd-eds is incompatible with eds version shipped with the distro - -fedora-35-* # test-snapd-eds is incompatible with eds version shipped with the distro - - -opensuse-15.2-* # test-snapd-eds is incompatible with eds version shipped with the distro + - -fedora-36-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-15.3-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-tumbleweed-* # test-snapd-eds is incompatible with eds version shipped with the distro - -ubuntu-14.04-* # no tests.session support, eds is too old diff --git a/tests/main/interfaces-contacts-service/task.yaml b/tests/main/interfaces-contacts-service/task.yaml index b65c8761640..e144cc9e82c 100644 --- a/tests/main/interfaces-contacts-service/task.yaml +++ b/tests/main/interfaces-contacts-service/task.yaml @@ -17,10 +17,8 @@ systems: - -centos-* - -debian-11-* - -debian-sid-* - - -fedora-33-* # test-snapd-eds is incompatible with eds version shipped with the distro - - -fedora-34-* # test-snapd-eds is incompatible with eds version shipped with the distro - -fedora-35-* # test-snapd-eds is incompatible with eds version shipped with the distro - - -opensuse-15.2-* # test-snapd-eds is incompatible with eds version shipped with the distro + - -fedora-36-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-15.3-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-tumbleweed-* # test-snapd-eds is incompatible with eds version shipped with the distro - -ubuntu-14.04-* # no tests.session support, eds is too old diff --git a/tests/main/microk8s-smoke/task.yaml b/tests/main/microk8s-smoke/task.yaml index 4fa53ce54d2..f21371ed7e5 100644 --- a/tests/main/microk8s-smoke/task.yaml +++ b/tests/main/microk8s-smoke/task.yaml @@ -4,9 +4,8 @@ systems: - -amazon-linux-2-* # fails to start service daemon-containerd - -centos-7-* # doesn't have libseccomp >= 2.4 - -centos-8-* # fails to start service daemon-containerd - - -fedora-33-* # fails to start service daemon-containerd - - -fedora-34-* # fails to start service daemon-containerd - -fedora-35-* # fails to start service daemon-containerd + - -fedora-36-* # fails to start service daemon-containerd - -debian-10-* # doesn't have libseccomp >= 2.4 - -ubuntu-14.04-* # doesn't have libseccomp >= 2.4 - -ubuntu-*-32 # no microk8s snap for 32 bit systems diff --git a/tests/main/snap-routine-portal-info/task.yaml b/tests/main/snap-routine-portal-info/task.yaml index a9a41b9ac40..ae0bca41437 100644 --- a/tests/main/snap-routine-portal-info/task.yaml +++ b/tests/main/snap-routine-portal-info/task.yaml @@ -2,7 +2,6 @@ summary: The portal-info command provides information about a confined process systems: - -ubuntu-14.04-* - - -fedora-33-* - -amazon-linux-* - -centos-7-* From e72243fbb6d5111d7abb8edae21c615c1a1f2c0d Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 09:04:48 +0200 Subject: [PATCH 048/153] secboot/keymgr: extend unit tests Signed-off-by: Maciej Borzecki --- secboot/keymgr/keymgr_luks2_test.go | 299 ++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) diff --git a/secboot/keymgr/keymgr_luks2_test.go b/secboot/keymgr/keymgr_luks2_test.go index 36bd7de62f4..31072e74fd3 100644 --- a/secboot/keymgr/keymgr_luks2_test.go +++ b/secboot/keymgr/keymgr_luks2_test.go @@ -381,6 +381,58 @@ done }) } +func (s *keymgrSuite) TestStageEncryptionKeyKilledSlotAlreadyEmpty(c *C) { + unlockKey := "1234abcd" + restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { + return []byte(unlockKey), nil + }) + defer restore() + + cmd := testutil.MockCommand(c, "cryptsetup", ` +while [ "$#" -gt 1 ]; do + case "$1" in + luksKillSlot) + killslot=1 + shift + ;; + --key-file) + cat "$2" > /dev/null + shift 2 + ;; + *) + shift + ;; + esac +done +if [ "$killslot" = "1" ]; then + echo "Keyslot 1 is not active." >&2 + exit 1 +fi +`) + defer cmd.Restore() + + key := bytes.Repeat([]byte{1}, 32) + err := keymgr.StageLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, IsNil) + calls := cmd.Calls() + c.Assert(calls, HasLen, 2) + c.Assert(calls[0], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", + }) + c.Assert(calls[1], HasLen, 14) + c.Assert(calls[1][5], testutil.Contains, dirs.RunDir) + calls[1][5] = "" + // temporary key + c.Assert(calls[1], DeepEquals, []string{ + "cryptsetup", "luksAddKey", "--type", "luks2", + "--key-file", "", + "--pbkdf", "argon2i", + "--iter-time", "100", + "--key-slot", "2", + "/dev/foobar", "-", + }) +} + func (s *keymgrSuite) TestStageEncryptionKeyGetUnlockFail(c *C) { getCalls := 0 restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { @@ -519,6 +571,206 @@ fi }) } +func (s *keymgrSuite) TestTransitionEncryptionKeyHappyKillSlotsInactive(c *C) { + restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { + c.Errorf("unepected call") + return nil, fmt.Errorf("unexpected call") + }) + defer restore() + + cmd := testutil.MockCommand(c, "cryptsetup", ` +while [ "$#" -gt 1 ]; do + case "$1" in + luksKillSlot) + killslot=1 + shift + ;; + luksAddKey) + keyadd=1 + shift + ;; + --key-slot) + keyslot="$2" + shift 2 + ;; + --key-file) + cat "$2" > /dev/null + shift 2 + ;; + *) + shift 1 + ;; + esac +done +if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then + echo "Key slot 2 is full, please select another one." >&2 + exit 1 +elif [ "$killslot" = "1" ]; then + echo "Keyslot 2 is not active." >&2 + exit 1 +fi +`) + defer cmd.Restore() + // try a too short key + key := bytes.Repeat([]byte{1}, 12) + err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, ErrorMatches, "cannot use a key of size different than 32") + + key = bytes.Repeat([]byte{1}, 32) + err = keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, IsNil) + calls := cmd.Calls() + c.Assert(calls, HasLen, 5) + // probing the key slot use + c.Assert(calls[0], HasLen, 14) + // temporary key + c.Assert(calls[0][:2], DeepEquals, []string{ + "cryptsetup", "luksAddKey", + }) + // killing the encryption key + c.Assert(calls[1], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0", + }) + // adding the new encryption key + c.Assert(calls[2], HasLen, 14) + c.Assert(calls[2][5], testutil.Contains, dirs.RunDir) + calls[2][5] = "" + c.Assert(calls[2], DeepEquals, []string{ + "cryptsetup", "luksAddKey", "--type", "luks2", + "--key-file", "", + "--pbkdf", "argon2i", + "--iter-time", "100", + "--key-slot", "0", + "/dev/foobar", "-", + }) + // kill the temp key + c.Assert(calls[3], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", + }) + // set priority + c.Assert(calls[4], DeepEquals, []string{ + "cryptsetup", "config", "--priority", "prefer", "--key-slot", "0", "/dev/foobar", + }) +} + +func (s *keymgrSuite) TestTransitionEncryptionKeyHappyOtherErrs(c *C) { + restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { + c.Errorf("unepected call") + return nil, fmt.Errorf("unexpected call") + }) + defer restore() + + cmd := testutil.MockCommand(c, "cryptsetup", ` +while [ "$#" -gt 1 ]; do + case "$1" in + luksAddKey) + keyadd=1 + shift + ;; + --key-slot) + keyslot="$2" + shift 2 + ;; + --key-file) + cat "$2" > /dev/null + shift 2 + ;; + *) + shift 1 + ;; + esac +done +if [ "$keyadd" = "1" ] && [ "$keyslot" = "2" ]; then + echo "Key slot 2 is full, please select another one." >&2 + exit 1 +elif [ "$keyadd" = "1" ] && [ "$keyslot" = "0" ]; then + echo "mock error" >&2 + exit 1 +fi +`) + defer cmd.Restore() + key := bytes.Repeat([]byte{1}, 32) + err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, ErrorMatches, "cannot add new encryption key: cryptsetup failed with: mock error") + calls := cmd.Calls() + c.Assert(calls, HasLen, 3) + // probing the key slot use + c.Assert(calls[0], HasLen, 14) + // temporary key + c.Assert(calls[0][:2], DeepEquals, []string{ + "cryptsetup", "luksAddKey", + }) + // killing the encryption key + c.Assert(calls[1], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "0", + }) + // adding the new encryption key + c.Assert(calls[2], HasLen, 14) + c.Assert(calls[2][5], testutil.Contains, dirs.RunDir) + calls[2][5] = "" + c.Assert(calls[2], DeepEquals, []string{ + "cryptsetup", "luksAddKey", "--type", "luks2", + "--key-file", "", + "--pbkdf", "argon2i", + "--iter-time", "100", + "--key-slot", "0", + "/dev/foobar", "-", + }) +} + +func (s *keymgrSuite) TestTransitionEncryptionKeyCannotAddKeyNotStaged(c *C) { + // conditions like when the encryption key has not been previously + // staged + + restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { + c.Errorf("unepected call") + return nil, fmt.Errorf("unexpected call") + }) + defer restore() + + cmd := testutil.MockCommand(c, "cryptsetup", ` +while [ "$#" -gt 1 ]; do + case "$1" in + luksAddKey) + keyadd=1 + shift + ;; + --key-file) + cat "$2" > /dev/null + shift 2 + ;; + *) + shift 1 + ;; + esac +done +if [ "$keyadd" = "1" ] ; then + echo "No key available with this passphrase." >&2 + exit 1 +fi +`) + defer cmd.Restore() + + key := bytes.Repeat([]byte{1}, 32) + err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, ErrorMatches, "cannot add new encryption key: cryptsetup failed with: No key available with this passphrase.") + calls := cmd.Calls() + c.Assert(calls, HasLen, 1) + // probing the key slot use + c.Assert(calls[0], HasLen, 14) + c.Assert(calls[0][5], testutil.Contains, dirs.RunDir) + calls[0][5] = "" + // temporary key + c.Assert(calls[0], DeepEquals, []string{ + "cryptsetup", "luksAddKey", "--type", "luks2", + "--key-file", "", + "--pbkdf", "argon2i", + "--iter-time", "100", + "--key-slot", "2", + "/dev/foobar", "-", + }) +} + func (s *keymgrSuite) TestTransitionEncryptionKeyPostRebootHappy(c *C) { restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { c.Errorf("unepected call") @@ -565,6 +817,53 @@ done }) } +func (s *keymgrSuite) TestTransitionEncryptionKeyPostRebootCannotKillSlot(c *C) { + // a post reboot scenario in which the luksKillSlot fails unexpectedly + + restore := keymgr.MockGetDiskUnlockKeyFromKernel(func(prefix, devicePath string, remove bool) (sb.DiskUnlockKey, error) { + c.Errorf("unepected call") + return nil, fmt.Errorf("unexpected call") + }) + defer restore() + + cmd := testutil.MockCommand(c, "cryptsetup", ` +while [ "$#" -gt 1 ]; do + case "$1" in + luksKillSlot) + killslot=1 + shift + ;; + --key-file) + cat "$2" > /dev/null + shift 2 + ;; + *) + shift + ;; + esac +done +if [ "$killslot" = "1" ]; then + echo "mock error" >&2 + exit 5 +fi +`) + defer cmd.Restore() + + key := bytes.Repeat([]byte{1}, 32) + err := keymgr.TransitionLUKSDeviceEncryptionKeyChange(key, "/dev/foobar") + c.Assert(err, ErrorMatches, "cannot kill temporary key slot: cryptsetup failed with: mock error") + calls := cmd.Calls() + c.Assert(calls, HasLen, 2) + c.Assert(calls[0], HasLen, 14) + c.Assert(calls[0][:2], DeepEquals, []string{ + "cryptsetup", "luksAddKey", + }) + // an a cleanup of the temp key slot + c.Assert(calls[1], DeepEquals, []string{ + "cryptsetup", "luksKillSlot", "--type", "luks2", "--key-file", "-", "/dev/foobar", "2", + }) +} + func (s *keymgrSuite) TestRecoveryKDF(c *C) { mockedMeminfoFile := filepath.Join(c.MkDir(), "meminfo") s.AddCleanup(osutil.MockProcMeminfo(mockedMeminfoFile)) From 1716ec29e7b605aab4600697652f9b79ed050e76 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 09:49:57 +0200 Subject: [PATCH 049/153] boot: extend comments Signed-off-by: Maciej Borzecki --- boot/makebootable.go | 3 +++ boot/seal.go | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/boot/makebootable.go b/boot/makebootable.go index 3b185ea1631..cb04612b917 100644 --- a/boot/makebootable.go +++ b/boot/makebootable.go @@ -482,6 +482,9 @@ func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *Tru return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{}) } +// MakeRunnableSystemAfterReset sets up the system to be able to boot, but it is +// intended to be called from UC20 factory reset mode right before switching +// back to the new run system. func MakeRunnableSystemAfterReset(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{ AfterReset: true, diff --git a/boot/seal.go b/boot/seal.go index c524378ff50..d6ba8a63c1e 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -262,7 +262,12 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode if flags.FactoryReset { // during factory reset we may need to rotate the PCR handles, // seal the new keys using a new set of handles such that the - // old sealed ubuntu-save key is still usable + // old sealed ubuntu-save key is still usable, for this we + // switch between two sets of handles in a round robin fashion, + // first looking at the PCR handle used by the current fallback + // key and then using the other set when sealing the new keys; + // the currently used handles will be released during the first + // boot of a new run system usesAlt, err := usesAltPCRHandles() if err != nil { return err From 9b58d6fee435ef8c53a239165d4aec1a52f6446f Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 09:50:09 +0200 Subject: [PATCH 050/153] boot: tweak the sealed fallback key file name Signed-off-by: Maciej Borzecki --- boot/makebootable_test.go | 2 +- boot/seal.go | 2 +- boot/seal_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boot/makebootable_test.go b/boot/makebootable_test.go index 7121a7d9449..6f25402cbaf 100644 --- a/boot/makebootable_test.go +++ b/boot/makebootable_test.go @@ -635,7 +635,7 @@ version: 5.0 c.Check(params.PCRPolicyCounterHandle, Equals, secboot.AltFallbackObjectPCRPolicyCounterHandle) c.Check(keys[1].KeyFile, Equals, filepath.Join(s.rootdir, - "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory")) + "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset")) } else { c.Check(params.PCRPolicyCounterHandle, Equals, secboot.FallbackObjectPCRPolicyCounterHandle) diff --git a/boot/seal.go b/boot/seal.go index d6ba8a63c1e..391811f3872 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -154,7 +154,7 @@ func fallbackKeySealRequests(key, saveKey keys.EncryptionKey, factoryReset bool) // factory reset uses alternative sealed key location, such that // until we boot into the run mode, both sealed keys are present // on disk - saveFallbackKey = filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory") + saveFallbackKey = filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset") } return []secboot.SealKeyRequest{ { diff --git a/boot/seal_test.go b/boot/seal_test.go index a342202d644..73fb93daa99 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -238,7 +238,7 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { saveKeyFile := filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key") if tc.factoryReset { // during factory reset we use a different key location - saveKeyFile = filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory") + saveKeyFile = filepath.Join(rootdir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset") } c.Check(keys, DeepEquals, []secboot.SealKeyRequest{{Key: myKey, KeyName: "ubuntu-data", KeyFile: dataKeyFile}, {Key: myKey2, KeyName: "ubuntu-save", KeyFile: saveKeyFile}}) if tc.pcrHandleOfKey == secboot.FallbackObjectPCRPolicyCounterHandle { From 1cefdf8693b567baa809910a8438116819cf4842 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 10 Jun 2022 11:51:17 +0200 Subject: [PATCH 051/153] tests: add nested test variant that adds 4k sector size This test would have caught https://bugs.launchpad.net/snappy/+bug/1968356 where UC22 does not install on devices that have 4k physical sector sizes. The downside of course is that it makes the nested tests slower. Alternatively we could simply unconditionally set all basic tests to 4k and rely on the other tests to catch possible regressions with physical sector size of 512 (which is the default). --- tests/lib/nested.sh | 5 +++++ tests/nested/core/core20-basic/task.yaml | 4 ++++ tests/nested/core/core20-tpm/task.yaml | 4 ++++ tests/nested/core/core22-basic/task.yaml | 4 ++++ 4 files changed, 17 insertions(+) diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh index 95fee8427a5..fe3ca6370a3 100644 --- a/tests/lib/nested.sh +++ b/tests/lib/nested.sh @@ -24,6 +24,8 @@ NESTED_FAKESTORE_SNAP_DECL_PC_GADGET="${NESTED_FAKESTORE_SNAP_DECL_PC_GADGET:-}" NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL="${NESTED_UBUNTU_IMAGE_SNAPPY_FORCE_SAS_URL:-}" NESTED_UBUNTU_IMAGE_PRESEED_KEY="${NESTED_UBUNTU_IMAGE_PRESEED_KEY:-}" +NESTED_PHYSICAL_4K_SECTOR_SIZE="${NESTED_PHYSICAL_4K_SECTOR_SIZE:-}" + nested_wait_for_ssh() { # TODO:UC20: the retry count should be lowered to something more reasonable. local retry=800 @@ -1134,6 +1136,9 @@ nested_start_core_vm_unit() { else PARAM_IMAGE="-drive file=$CURRENT_IMAGE,cache=none,format=raw" fi + if [ "$PHYSICAL_4K_SECTOR_SIZE" = "true" ]; then + PARAM_IMAGE="$PARAM_IMAGE,physical_block_size=4096,logical_block_size=512" + fi # ensure we have a log dir mkdir -p "$NESTED_LOGS_DIR" diff --git a/tests/nested/core/core20-basic/task.yaml b/tests/nested/core/core20-basic/task.yaml index 28c9528ce94..a4a1d21f4a6 100644 --- a/tests/nested/core/core20-basic/task.yaml +++ b/tests/nested/core/core20-basic/task.yaml @@ -5,6 +5,10 @@ details: | systems: [ubuntu-20.04-64] +environment: + PHYSICAL_4K_SECTOR_SIZE/true: "true" + PHYSICAL_4K_SECTOR_SIZE/false: "false" + execute: | echo "Wait for the system to be seeded first" tests.nested exec "sudo snap wait system seed.loaded" diff --git a/tests/nested/core/core20-tpm/task.yaml b/tests/nested/core/core20-tpm/task.yaml index 13b10c2e3dd..410560731bc 100644 --- a/tests/nested/core/core20-tpm/task.yaml +++ b/tests/nested/core/core20-tpm/task.yaml @@ -5,6 +5,10 @@ details: | systems: [ubuntu-20.04-64] +environment: + PHYSICAL_4K_SECTOR_SIZE/true: "true" + PHYSICAL_4K_SECTOR_SIZE/false: "false" + debug: | cat modeenv || true cat modeenv.after-reboot || true diff --git a/tests/nested/core/core22-basic/task.yaml b/tests/nested/core/core22-basic/task.yaml index aa594893e8e..7b79af20cdd 100644 --- a/tests/nested/core/core22-basic/task.yaml +++ b/tests/nested/core/core22-basic/task.yaml @@ -5,6 +5,10 @@ details: | systems: [ubuntu-22.04-64] +environment: + PHYSICAL_4K_SECTOR_SIZE/true: "true" + PHYSICAL_4K_SECTOR_SIZE/false: "false" + execute: | echo "Wait for the system to be seeded first" tests.nested exec "sudo snap wait system seed.loaded" From e8f5621e3ddbf811198028ba1c6db66cd67d2cf5 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 12:43:24 +0200 Subject: [PATCH 052/153] secboot/keymgr: typo fixes and comment tweaks Signed-off-by: Maciej Borzecki --- secboot/keymgr/keymgr_luks2.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/secboot/keymgr/keymgr_luks2.go b/secboot/keymgr/keymgr_luks2.go index 9042fabbf81..8dc4d2989d1 100644 --- a/secboot/keymgr/keymgr_luks2.go +++ b/secboot/keymgr/keymgr_luks2.go @@ -204,7 +204,7 @@ func TransitionLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev stri } // the expected state is as follows: - // keys lot 0 - the old encryption key + // key slot 0 - the old encryption key // key slot 2 - the new encryption key (added during --stage) // the desired state is: // key slot 0 - the new encryption key @@ -212,10 +212,11 @@ func TransitionLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev stri // it is possible that the system was rebooted right after key slot 0 was // populated with the new key and key slot 2 was emptied - // there is no state information on disk which would if scenario 1 - // occurred and to which stage it was executed, but we need to find out - // if key slot 2 is in use (as the caller believes that a key was staged - // earlier); do this indirectly by trying to add a key to key slot 2 + // there is no state information on disk which would tell if the + // scenario 1 above occurred and to which stage it was executed, but we + // need to find out if key slot 2 is in use (as the caller believes that + // a key was staged earlier); do this indirectly by trying to add a key + // to key slot 2 // TODO rather than inspecting the errors, parse the LUKS2 headers From 5870a0aa6fd2e39b4f059efb50b443c3423edda9 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 12:41:49 +0200 Subject: [PATCH 053/153] tests/main/uc20-create-partitions: update to match pc gadget from channel 22 Signed-off-by: Maciej Borzecki --- .../uc20-create-partitions-encrypt/task.yaml | 32 +++++++++++++++---- tests/main/uc20-create-partitions/task.yaml | 32 +++++++++++++++---- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/tests/main/uc20-create-partitions-encrypt/task.yaml b/tests/main/uc20-create-partitions-encrypt/task.yaml index 160f286d54c..e5a10a393af 100644 --- a/tests/main/uc20-create-partitions-encrypt/task.yaml +++ b/tests/main/uc20-create-partitions-encrypt/task.yaml @@ -84,6 +84,11 @@ debug: | cat /proc/partitions execute: | + channel=20 + if os.query is-jammy; then + channel=22 + fi + # this test simulates a reinstall, to clear the TPM this requires # a reboot so the losetup has to be redone if [ "$SPREAD_REBOOT" = 1 ]; then @@ -131,8 +136,13 @@ execute: | sfdisk -d "$LOOP" | MATCH "^${LOOP}p1 .*size=\s*2048, type=21686148-6449-6E6F-744E-656564454649,.*BIOS Boot" sfdisk -d "$LOOP" | MATCH "^${LOOP}p2 .*size=\s*2457600, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B,.*ubuntu-seed" sfdisk -d "$LOOP" | MATCH "^${LOOP}p3 .*size=\s*1536000, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,.*ubuntu-boot" - sfdisk -d "$LOOP" | MATCH "^${LOOP}p4 .*size=\s*32768, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,.*ubuntu-save" - sfdisk -d "$LOOP" | MATCH "^${LOOP}p5 .*size=\s*15500753, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,.*ubuntu-data" + if [ "$channel" = "20" ]; then + sfdisk -d "$LOOP" | MATCH "^${LOOP}p4 .*size=\s*32768, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,.*ubuntu-save" + sfdisk -d "$LOOP" | MATCH "^${LOOP}p5 .*size=\s*15500753, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,.*ubuntu-data" + else + sfdisk -d "$LOOP" | MATCH "^${LOOP}p4 .*size=\s*65536, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,.*ubuntu-save" + sfdisk -d "$LOOP" | MATCH "^${LOOP}p5 .*size=\s*15467985, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,.*ubuntu-data" + fi not cryptsetup isLuks "${LOOP}p1" not cryptsetup isLuks "${LOOP}p2" @@ -315,8 +325,13 @@ execute: | jq -r '.pc.structure[3]."filesystem-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-save-enc jq -r '.pc.structure[3]."partition-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-save jq -r '.pc.structure[3].id' < "$DISK_MAPPING_JSON" | MATCH "" - jq -r '.pc.structure[3].offset' < "$DISK_MAPPING_JSON" | MATCH 2046820352 - jq -r '.pc.structure[3].size' < "$DISK_MAPPING_JSON" | MATCH 16777216 + if [ "$channel" = "20" ]; then + jq -r '.pc.structure[3].offset' < "$DISK_MAPPING_JSON" | MATCH 2046820352 + jq -r '.pc.structure[3].size' < "$DISK_MAPPING_JSON" | MATCH 16777216 + else + jq -r '.pc.structure[3].offset' < "$DISK_MAPPING_JSON" | MATCH 2046820352 + jq -r '.pc.structure[3].size' < "$DISK_MAPPING_JSON" | MATCH 33554432 + fi # fifth structure - ubuntu-data-enc jq -r '.pc.structure[4]."device-path"' < "$DISK_MAPPING_JSON" | MATCH "/sys/devices/virtual/block/$LOOP_BASENAME/${LOOP_BASENAME}p5" @@ -324,5 +339,10 @@ execute: | jq -r '.pc.structure[4]."filesystem-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-data-enc jq -r '.pc.structure[4]."partition-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-data jq -r '.pc.structure[4].id' < "$DISK_MAPPING_JSON" | MATCH "" - jq -r '.pc.structure[4].offset' < "$DISK_MAPPING_JSON" | MATCH 2063597568 - jq -r '.pc.structure[4].size' < "$DISK_MAPPING_JSON" | MATCH 7936385536 + if [ "$channel" = "20" ]; then + jq -r '.pc.structure[4].offset' < "$DISK_MAPPING_JSON" | MATCH 2063597568 + jq -r '.pc.structure[4].size' < "$DISK_MAPPING_JSON" | MATCH 7936385536 + else + jq -r '.pc.structure[4].offset' < "$DISK_MAPPING_JSON" | MATCH 2080374784 + jq -r '.pc.structure[4].size' < "$DISK_MAPPING_JSON" | MATCH 7919608320 + fi diff --git a/tests/main/uc20-create-partitions/task.yaml b/tests/main/uc20-create-partitions/task.yaml index 6de18a33622..e264d8d28be 100644 --- a/tests/main/uc20-create-partitions/task.yaml +++ b/tests/main/uc20-create-partitions/task.yaml @@ -74,6 +74,11 @@ debug: | udevadm info --query property "${LOOP}p5" || true execute: | + channel=20 + if os.query is-jammy; then + channel=22 + fi + LOOP="$(cat loop.txt)" echo "Run the snap-bootstrap tool" @@ -164,8 +169,13 @@ execute: | sfdisk -l "$LOOP" | MATCH "${LOOP}p1 .* 1M\s* BIOS boot" sfdisk -l "$LOOP" | MATCH "${LOOP}p2 .* 1\.2G\s* EFI System" sfdisk -l "$LOOP" | MATCH "${LOOP}p3 .* 750M\s* Linux filesystem" - sfdisk -l "$LOOP" | MATCH "${LOOP}p4 .* 16M\s* Linux filesystem" - sfdisk -l "$LOOP" | MATCH "${LOOP}p5 .* 16\.7G\s* Linux filesystem" + if [ "$channel" = "20" ]; then + sfdisk -l "$LOOP" | MATCH "${LOOP}p4 .* 16M\s* Linux filesystem" + sfdisk -l "$LOOP" | MATCH "${LOOP}p5 .* 16\.7G\s* Linux filesystem" + else + sfdisk -l "$LOOP" | MATCH "${LOOP}p4 .* 32M\s* Linux filesystem" + sfdisk -l "$LOOP" | MATCH "${LOOP}p5 .* 16\.7G\s* Linux filesystem" + fi echo "check that the filesystems are created and mounted" mount | MATCH /run/mnt/ubuntu-boot @@ -226,8 +236,13 @@ execute: | jq -r '.pc.structure[3]."filesystem-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-save jq -r '.pc.structure[3]."partition-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-save jq -r '.pc.structure[3].id' < "$DISK_MAPPING_JSON" | MATCH "" - jq -r '.pc.structure[3].offset' < "$DISK_MAPPING_JSON" | MATCH 2046820352 - jq -r '.pc.structure[3].size' < "$DISK_MAPPING_JSON" | MATCH 16777216 + if [ "$channel" = "20" ]; then + jq -r '.pc.structure[3].offset' < "$DISK_MAPPING_JSON" | MATCH 2046820352 + jq -r '.pc.structure[3].size' < "$DISK_MAPPING_JSON" | MATCH 16777216 + else + jq -r '.pc.structure[3].offset' < "$DISK_MAPPING_JSON" | MATCH 2046820352 + jq -r '.pc.structure[3].size' < "$DISK_MAPPING_JSON" | MATCH 33554432 + fi # fifth structure - ubuntu-data jq -r '.pc.structure[4]."device-path"' < "$DISK_MAPPING_JSON" | MATCH "/sys/devices/virtual/block/$LOOP_BASENAME/${LOOP_BASENAME}p5" @@ -235,5 +250,10 @@ execute: | jq -r '.pc.structure[4]."filesystem-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-data jq -r '.pc.structure[4]."partition-label"' < "$DISK_MAPPING_JSON" | MATCH ubuntu-data jq -r '.pc.structure[4].id' < "$DISK_MAPPING_JSON" | MATCH "" - jq -r '.pc.structure[4].offset' < "$DISK_MAPPING_JSON" | MATCH 2063597568 - jq -r '.pc.structure[4].size' < "$DISK_MAPPING_JSON" | MATCH 17936385536 + if [ "$channel" = "20" ]; then + jq -r '.pc.structure[4].offset' < "$DISK_MAPPING_JSON" | MATCH 2063597568 + jq -r '.pc.structure[4].size' < "$DISK_MAPPING_JSON" | MATCH 17936385536 + else + jq -r '.pc.structure[4].offset' < "$DISK_MAPPING_JSON" | MATCH 2080374784 + jq -r '.pc.structure[4].size' < "$DISK_MAPPING_JSON" | MATCH 17919608320 + fi From 47e13c97eec5d6b62ce6ff4d3500fd6d3d121651 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Fri, 10 Jun 2022 08:52:59 -0300 Subject: [PATCH 054/153] New test in nested/manual used to check the 4k sector size Also reverted the changes in nested/core --- tests/nested/core/core20-basic/task.yaml | 4 -- tests/nested/core/core20-tpm/task.yaml | 4 -- tests/nested/core/core22-basic/task.yaml | 4 -- .../manual/core20-4k-sector-size/task.yaml | 52 +++++++++++++++++++ 4 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 tests/nested/manual/core20-4k-sector-size/task.yaml diff --git a/tests/nested/core/core20-basic/task.yaml b/tests/nested/core/core20-basic/task.yaml index a4a1d21f4a6..28c9528ce94 100644 --- a/tests/nested/core/core20-basic/task.yaml +++ b/tests/nested/core/core20-basic/task.yaml @@ -5,10 +5,6 @@ details: | systems: [ubuntu-20.04-64] -environment: - PHYSICAL_4K_SECTOR_SIZE/true: "true" - PHYSICAL_4K_SECTOR_SIZE/false: "false" - execute: | echo "Wait for the system to be seeded first" tests.nested exec "sudo snap wait system seed.loaded" diff --git a/tests/nested/core/core20-tpm/task.yaml b/tests/nested/core/core20-tpm/task.yaml index 410560731bc..13b10c2e3dd 100644 --- a/tests/nested/core/core20-tpm/task.yaml +++ b/tests/nested/core/core20-tpm/task.yaml @@ -5,10 +5,6 @@ details: | systems: [ubuntu-20.04-64] -environment: - PHYSICAL_4K_SECTOR_SIZE/true: "true" - PHYSICAL_4K_SECTOR_SIZE/false: "false" - debug: | cat modeenv || true cat modeenv.after-reboot || true diff --git a/tests/nested/core/core22-basic/task.yaml b/tests/nested/core/core22-basic/task.yaml index 7b79af20cdd..aa594893e8e 100644 --- a/tests/nested/core/core22-basic/task.yaml +++ b/tests/nested/core/core22-basic/task.yaml @@ -5,10 +5,6 @@ details: | systems: [ubuntu-22.04-64] -environment: - PHYSICAL_4K_SECTOR_SIZE/true: "true" - PHYSICAL_4K_SECTOR_SIZE/false: "false" - execute: | echo "Wait for the system to be seeded first" tests.nested exec "sudo snap wait system seed.loaded" diff --git a/tests/nested/manual/core20-4k-sector-size/task.yaml b/tests/nested/manual/core20-4k-sector-size/task.yaml new file mode 100644 index 00000000000..c1c2e66a8a1 --- /dev/null +++ b/tests/nested/manual/core20-4k-sector-size/task.yaml @@ -0,0 +1,52 @@ +summary: verify a simple UC2* scenario with 4k sector size + +systems: [ubuntu-20.04-64, ubuntu-22.04-64] + +environment: + NESTED_IMAGE_ID: core20-4k-sector-size + NESTED_ENABLE_TPM: true + NESTED_ENABLE_SECURE_BOOT: true + NESTED_BUILD_SNAPD_FROM_CURRENT: true + PHYSICAL_4K_SECTOR_SIZE/true: true + PHYSICAL_4K_SECTOR_SIZE/false: false + +prepare: | + tests.nested build-image core + tests.nested create-vm core + +execute: | + cho "Wait for the system to be seeded first" + tests.nested exec "sudo snap wait system seed.loaded" + + echo "Ensure 'snap install' works" + tests.nested exec "sudo snap install test-snapd-sh" + + echo "Ensure 'snap list' works and test-snapd-sh snap is installed" + tests.nested exec "snap list" | MATCH test-snapd-sh + + echo "Ensure 'snap find' works" + tests.nested exec "snap find test-snapd-sh" | MATCH ^test-snapd-sh + + echo "Ensure 'snap info' works" + tests.nested exec "snap info test-snapd-sh" | MATCH '^name:\ +test-snapd-sh' + + echo "Ensure 'snap remove' works" + tests.nested exec "sudo snap remove test-snapd-sh" + + echo "Ensure 'snap list' works and test-snapd-sh snap is removed" + tests.nested exec "! snap list test-snapd-sh" + + echo "Verifying tpm working on the nested vm" + tests.nested exec "dmesg | grep -i tpm" | MATCH "efi: +SMBIOS=.* +TPMFinalLog=.*" + tests.nested exec "test -e /sys/kernel/security/tpm0/binary_bios_measurements" + + echo "and secure boot is enabled on the nested vm" + tests.nested exec "xxd /sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c" | MATCH "00000000: 0600 0000 01\s+....." + + echo "Ensure 'snap recovery show-keys' works as root" + tests.nested exec "sudo snap recovery --show-keys" | MATCH 'recovery:\s+[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}-[0-9]{5}' + echo "But not as user (normal file permissions prevent this)" + if tests.nested exec "snap recovery --show-keys"; then + echo "snap recovery --show-key should not work as a user" + exit 1 + fi From c5dff8f7c474d8b9e1ab0cd555a2032bb3b3736b Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Fri, 10 Jun 2022 08:54:11 -0300 Subject: [PATCH 055/153] just variant with 4k sector size is enough --- tests/nested/manual/core20-4k-sector-size/task.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/nested/manual/core20-4k-sector-size/task.yaml b/tests/nested/manual/core20-4k-sector-size/task.yaml index c1c2e66a8a1..ac7b552935e 100644 --- a/tests/nested/manual/core20-4k-sector-size/task.yaml +++ b/tests/nested/manual/core20-4k-sector-size/task.yaml @@ -7,8 +7,7 @@ environment: NESTED_ENABLE_TPM: true NESTED_ENABLE_SECURE_BOOT: true NESTED_BUILD_SNAPD_FROM_CURRENT: true - PHYSICAL_4K_SECTOR_SIZE/true: true - PHYSICAL_4K_SECTOR_SIZE/false: false + PHYSICAL_4K_SECTOR_SIZE: true prepare: | tests.nested build-image core From f65830426bc5202a27b2d414ff27a6a4180c9042 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Fri, 10 Jun 2022 08:55:08 -0300 Subject: [PATCH 056/153] Updated the name of the var NESTED_PHYSICAL_4K_SECTOR_SIZE in the test --- tests/nested/manual/core20-4k-sector-size/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nested/manual/core20-4k-sector-size/task.yaml b/tests/nested/manual/core20-4k-sector-size/task.yaml index ac7b552935e..b9344c05009 100644 --- a/tests/nested/manual/core20-4k-sector-size/task.yaml +++ b/tests/nested/manual/core20-4k-sector-size/task.yaml @@ -7,7 +7,7 @@ environment: NESTED_ENABLE_TPM: true NESTED_ENABLE_SECURE_BOOT: true NESTED_BUILD_SNAPD_FROM_CURRENT: true - PHYSICAL_4K_SECTOR_SIZE: true + NESTED_PHYSICAL_4K_SECTOR_SIZE: true prepare: | tests.nested build-image core From 792b81ae10ffa96667f2e6be689a1904f4bec609 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Fri, 10 Jun 2022 08:55:59 -0300 Subject: [PATCH 057/153] Update the NESTED_PHYSICAL_4K_SECTOR_SIZE in the check --- tests/lib/nested.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/nested.sh b/tests/lib/nested.sh index fe3ca6370a3..0d4107a31c2 100644 --- a/tests/lib/nested.sh +++ b/tests/lib/nested.sh @@ -1136,7 +1136,7 @@ nested_start_core_vm_unit() { else PARAM_IMAGE="-drive file=$CURRENT_IMAGE,cache=none,format=raw" fi - if [ "$PHYSICAL_4K_SECTOR_SIZE" = "true" ]; then + if [ "$NESTED_PHYSICAL_4K_SECTOR_SIZE" = "true" ]; then PARAM_IMAGE="$PARAM_IMAGE,physical_block_size=4096,logical_block_size=512" fi From 8f26223cd0806360b3a1a434cbfc9b4d423e8538 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 8 Jun 2022 12:36:58 +0200 Subject: [PATCH 058/153] secboot: stage and transition encryption keys Signed-off-by: Maciej Borzecki --- secboot/encrypt_dummy.go | 6 ++- secboot/encrypt_sb.go | 43 ++++++++++++++++-- secboot/encrypt_sb_test.go | 90 ++++++++++++++++++++++++++++++++------ 3 files changed, 120 insertions(+), 19 deletions(-) diff --git a/secboot/encrypt_dummy.go b/secboot/encrypt_dummy.go index a531d5530e4..d36a809dcad 100644 --- a/secboot/encrypt_dummy.go +++ b/secboot/encrypt_dummy.go @@ -33,6 +33,10 @@ func RemoveRecoveryKeys(map[RecoveryKeyDevice]string) error { return errBuildWithoutSecboot } -func ChangeEncryptionKey(node string, key keys.EncryptionKey) error { +func StageEncryptionKeyChange(node string, key keys.EncryptionKey) error { + return errBuildWithoutSecboot +} + +func TransitionEncryptionKeyChange(mountpoint string, key keys.EncryptionKey) error { return errBuildWithoutSecboot } diff --git a/secboot/encrypt_sb.go b/secboot/encrypt_sb.go index 37368ad64af..f6fe348c082 100644 --- a/secboot/encrypt_sb.go +++ b/secboot/encrypt_sb.go @@ -170,16 +170,18 @@ func RemoveRecoveryKeys(rkeyDevToKey map[RecoveryKeyDevice]string) error { return nil } -// ChangeEncryptionKey changes the main encryption key of a given device to the -// new key. -func ChangeEncryptionKey(node string, key keys.EncryptionKey) error { +// StageEncryptionKeyChange stages a new encryption key for a given encrypted +// device. The new key is added into a temporary slot. To complete the +// encryption key change process, a call to TransitionEncryptionKeyChange is +// needed. +func StageEncryptionKeyChange(node string, key keys.EncryptionKey) error { partitionUUID, err := disks.PartitionUUID(node) if err != nil { return fmt.Errorf("cannot get UUID of partition %v: %v", node, err) } dev := filepath.Join("/dev/disk/by-partuuid", partitionUUID) - logger.Debugf("changing encryption key on device: %v", dev) + logger.Debugf("stage encryption key change on device: %v", dev) var buf bytes.Buffer err = json.NewEncoder(&buf).Encode(struct { @@ -194,6 +196,39 @@ func ChangeEncryptionKey(node string, key keys.EncryptionKey) error { command := []string{ "change-encryption-key", "--device", dev, + "--stage", + } + + if err := runSnapFDEKeymgr(command, &buf); err != nil { + return fmt.Errorf("cannot run FDE key manager tool: %v", err) + } + return nil +} + +// TransitionEncryptionKeyChange transitions the encryption key on an encrypted +// device corresponding to the given mount point. The change is authorized using +// the new key, thus a prior call to StageEncryptionKeyChange must be done. +func TransitionEncryptionKeyChange(mountpoint string, key keys.EncryptionKey) error { + dev, err := devByPartUUIDFromMount(mountpoint) + if err != nil { + return fmt.Errorf("cannot find matching device: %v", err) + } + logger.Debugf("transition encryption key change on device: %v", dev) + + var buf bytes.Buffer + err = json.NewEncoder(&buf).Encode(struct { + Key []byte `json:"key"` + }{ + Key: key, + }) + if err != nil { + return fmt.Errorf("cannot encode key for the FDE key manager tool: %v", err) + } + + command := []string{ + "change-encryption-key", + "--device", dev, + "--transition", } if err := runSnapFDEKeymgr(command, &buf); err != nil { diff --git a/secboot/encrypt_sb_test.go b/secboot/encrypt_sb_test.go index 36778ae5716..d5b292a2ee5 100644 --- a/secboot/encrypt_sb_test.go +++ b/secboot/encrypt_sb_test.go @@ -159,8 +159,8 @@ var ( key = keys.EncryptionKey{'e', 'n', 'c', 'r', 'y', 'p', 't', 1, 1, 1, 1} ) -func (s *keymgrSuite) TestChangeEncryptionKeyHappy(c *C) { - err := secboot.ChangeEncryptionKey("/dev/foo/bar", key) +func (s *keymgrSuite) TestStageEncryptionKeyHappy(c *C) { + err := secboot.StageEncryptionKeyChange("/dev/foo/bar", key) c.Assert(err, IsNil) c.Check(s.udevadmCmd.Calls(), DeepEquals, [][]string{ {"udevadm", "info", "--query", "property", "--name", "/dev/foo/bar"}, @@ -171,10 +171,11 @@ func (s *keymgrSuite) TestChangeEncryptionKeyHappy(c *C) { "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--property=KeyringMode=inherit", "--", s.keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/something", + "--stage", }, }) c.Check(s.keymgrCmd.Calls(), DeepEquals, [][]string{ - {"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/something"}, + {"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/something", "--stage"}, }) var b bytes.Buffer json.NewEncoder(&b).Encode(struct { @@ -185,12 +186,12 @@ func (s *keymgrSuite) TestChangeEncryptionKeyHappy(c *C) { c.Check(filepath.Join(s.d, "input"), testutil.FileEquals, b.String()) } -func (s *keymgrSuite) TestChangeEncryptionKeyBadUdev(c *C) { +func (s *keymgrSuite) TestStageEncryptionKeyBadUdev(c *C) { udevadmCmd := testutil.MockCommand(c, "udevadm", ` echo "unhappy udev" `) defer udevadmCmd.Restore() - err := secboot.ChangeEncryptionKey("/dev/foo/bar", key) + err := secboot.StageEncryptionKeyChange("/dev/foo/bar", key) c.Assert(err, ErrorMatches, "cannot get UUID of partition /dev/foo/bar: cannot get required udev partition UUID property") c.Check(udevadmCmd.Calls(), DeepEquals, [][]string{ {"udevadm", "info", "--query", "property", "--name", "/dev/foo/bar"}, @@ -199,7 +200,7 @@ func (s *keymgrSuite) TestChangeEncryptionKeyBadUdev(c *C) { c.Check(s.keymgrCmd.Calls(), HasLen, 0) } -func (s *keymgrSuite) TestChangeEncryptionKeyBadKeymgr(c *C) { +func (s *keymgrSuite) TestStageTransitionEncryptionKeyBadKeymgr(c *C) { keymgrCmd := testutil.MockCommand(c, "snap-fde-keymgr", `echo keymgr very unhappy; exit 1`) defer keymgrCmd.Restore() // update where /proc/self/exe resolves to @@ -208,26 +209,87 @@ func (s *keymgrSuite) TestChangeEncryptionKeyBadKeymgr(c *C) { }) defer restore() - err := secboot.ChangeEncryptionKey("/dev/foo/bar", key) + err := secboot.StageEncryptionKeyChange("/dev/foo/bar", key) c.Assert(err, ErrorMatches, "cannot run FDE key manager tool: cannot run .*: keymgr very unhappy") - c.Check(s.udevadmCmd.Calls(), DeepEquals, [][]string{ - {"udevadm", "info", "--query", "property", "--name", "/dev/foo/bar"}, - }) c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{ { "systemd-run", "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", "--property=KeyringMode=inherit", "--", keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/something", + "--stage", }, }) c.Check(keymgrCmd.Calls(), DeepEquals, [][]string{ - {"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/something"}, + {"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/something", "--stage"}, }) + + s.systemdRunCmd.ForgetCalls() + keymgrCmd.ForgetCalls() + + s.mocksForDeviceMounts(c) + err = secboot.TransitionEncryptionKeyChange("/foo", key) + c.Assert(err, ErrorMatches, "cannot run FDE key manager tool: cannot run .*: keymgr very unhappy") + + c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{ + { + "systemd-run", + "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", + "--property=KeyringMode=inherit", "--", + keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid", + "--transition", + }, + }) + c.Check(keymgrCmd.Calls(), DeepEquals, [][]string{ + {"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid", "--transition"}, + }) +} + +func (s *keymgrSuite) TestTransitionEncryptionKeyNoMountDev(c *C) { + restore := osutil.MockMountInfo(` +27 27 600:3 / /foo rw,relatime shared:7 - vfat /dev/mapper/foo rw +`[1:]) + s.AddCleanup(restore) + + udevadmCmd := testutil.MockCommand(c, "udevadm", `echo nope; exit 1`) + defer udevadmCmd.Restore() + + err := secboot.TransitionEncryptionKeyChange("/foo", key) + c.Assert(err, ErrorMatches, "cannot find matching device: cannot partition for mount /foo: cannot process udev properties of /dev/mapper/foo: nope") +} + +func (s *keymgrSuite) TestTransitionEncryptionKeyHappy(c *C) { + udevadmCmd := s.mocksForDeviceMounts(c) + + err := secboot.TransitionEncryptionKeyChange("/foo", key) + c.Assert(err, IsNil) + c.Check(udevadmCmd.Calls(), DeepEquals, [][]string{ + {"udevadm", "info", "--query", "property", "--name", "/dev/mapper/foo"}, + {"udevadm", "info", "--query", "property", "--name", "/dev/disk/by-uuid/5a522809-c87e-4dfa-81a8-8dc5667d1304"}, + }) + c.Check(s.systemdRunCmd.Calls(), DeepEquals, [][]string{ + { + "systemd-run", + "--wait", "--pipe", "--collect", "--service-type=exec", "--quiet", + "--property=KeyringMode=inherit", "--", + s.keymgrCmd.Exe(), "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid", + "--transition", + }, + }) + c.Check(s.keymgrCmd.Calls(), DeepEquals, [][]string{ + {"snap-fde-keymgr", "change-encryption-key", "--device", "/dev/disk/by-partuuid/foo-uuid", "--transition"}, + }) + var b bytes.Buffer + json.NewEncoder(&b).Encode(struct { + Key []byte `json:"key"` + }{ + Key: key, + }) + c.Check(filepath.Join(s.d, "input"), testutil.FileEquals, b.String()) } -func (s *keymgrSuite) mocksForRecoveryKeys(c *C) (udevadmCmd *testutil.MockCmd) { +func (s *keymgrSuite) mocksForDeviceMounts(c *C) (udevadmCmd *testutil.MockCmd) { restore := osutil.MockMountInfo(` 27 27 600:3 / /foo rw,relatime shared:7 - vfat /dev/mapper/foo rw 27 27 600:4 / /bar rw,relatime shared:7 - vfat /dev/mapper/bar rw @@ -279,7 +341,7 @@ done } func (s *keymgrSuite) TestEnsureRecoveryKey(c *C) { - udevadmCmd := s.mocksForRecoveryKeys(c) + udevadmCmd := s.mocksForDeviceMounts(c) rkey, err := secboot.EnsureRecoveryKey(filepath.Join(s.d, "recovery.key"), []secboot.RecoveryKeyDevice{ {Mountpoint: "/foo"}, @@ -317,7 +379,7 @@ func (s *keymgrSuite) TestEnsureRecoveryKey(c *C) { } func (s *keymgrSuite) TestRemoveRecoveryKey(c *C) { - udevadmCmd := s.mocksForRecoveryKeys(c) + udevadmCmd := s.mocksForDeviceMounts(c) snaptest.PopulateDir(s.d, [][]string{ {"recovery.key", "foobar"}, From dde621a9d90b048c4caef85335f7024feabba65c Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 15:29:30 +0200 Subject: [PATCH 059/153] boot: rename to MakeRunnableSystemAfterDataReset Signed-off-by: Maciej Borzecki --- boot/makebootable.go | 10 +++++----- boot/makebootable_test.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/boot/makebootable.go b/boot/makebootable.go index cb04612b917..196a4191dfb 100644 --- a/boot/makebootable.go +++ b/boot/makebootable.go @@ -291,7 +291,7 @@ func MakeRecoverySystemBootable(rootdir string, relativeRecoverySystemDir string } type makeRunnableOptions struct { - AfterReset bool + AfterDataReset bool } func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver, makeOpts makeRunnableOptions) error { @@ -453,7 +453,7 @@ func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *Tru if sealer != nil { flags := sealKeyToModeenvFlags{ - FactoryReset: makeOpts.AfterReset, + FactoryReset: makeOpts.AfterDataReset, } // seal the encryption key to the parameters specified in modeenv if err := sealKeyToModeenv(sealer.dataEncryptionKey, sealer.saveEncryptionKey, model, modeenv, flags); err != nil { @@ -482,11 +482,11 @@ func MakeRunnableSystem(model *asserts.Model, bootWith *BootableSet, sealer *Tru return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{}) } -// MakeRunnableSystemAfterReset sets up the system to be able to boot, but it is +// MakeRunnableSystemAfterDataReset sets up the system to be able to boot, but it is // intended to be called from UC20 factory reset mode right before switching // back to the new run system. -func MakeRunnableSystemAfterReset(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { +func MakeRunnableSystemAfterDataReset(model *asserts.Model, bootWith *BootableSet, sealer *TrustedAssetsInstallObserver) error { return makeRunnableSystem(model, bootWith, sealer, makeRunnableOptions{ - AfterReset: true, + AfterDataReset: true, }) } diff --git a/boot/makebootable_test.go b/boot/makebootable_test.go index 6f25402cbaf..ea62b15d2f4 100644 --- a/boot/makebootable_test.go +++ b/boot/makebootable_test.go @@ -692,7 +692,7 @@ version: 5.0 if !factoryReset { err = boot.MakeRunnableSystem(model, bootWith, obs) } else { - err = boot.MakeRunnableSystemAfterReset(model, bootWith, obs) + err = boot.MakeRunnableSystemAfterDataReset(model, bootWith, obs) } c.Assert(err, IsNil) From 54a9d2bb5d65e66be1043762dd079fe8b94a5bcc Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Fri, 10 Jun 2022 16:44:44 +0200 Subject: [PATCH 060/153] squashfs: improve error reporting when `unsquashfs` fails The current code in Snap.Unpack() will report a very generic: ` exit status 1` error if `unsquashfs` fails. This commit changes the code to first look at any unsquashfs errors reported on stderr and only if none of these are found it will report a (slightly less) generic error. This should help diagnose a bug in the pi-kernel refresh where the Snap.Unpack() of the kernel assets fails. --- snap/squashfs/squashfs.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/snap/squashfs/squashfs.go b/snap/squashfs/squashfs.go index 6cc9451fa9c..2f49c8eefd9 100644 --- a/snap/squashfs/squashfs.go +++ b/snap/squashfs/squashfs.go @@ -204,12 +204,16 @@ func (s *Snap) Unpack(src, dstDir string) error { cmd := exec.Command("unsquashfs", "-n", "-f", "-d", dstDir, s.path, src) cmd.Stderr = usw - if err := cmd.Run(); err != nil { - return err - } + err := cmd.Run() + // check unsquashfs errors first if usw.Err() != nil { return fmt.Errorf("cannot extract %q to %q: %v", src, dstDir, usw.Err()) } + // only if none are found report generic errors + if err != nil { + return fmt.Errorf("cannot run unsquashfs: %v", err) + } + return nil } From bc62d27ed332167f08908d04a415bca6ea48e60e Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Mon, 13 Jun 2022 09:50:05 +0200 Subject: [PATCH 061/153] overlord/hookstate/ctlcmd: fix timestamp coming out of sync on slower machines in unit tests --- overlord/hookstate/ctlcmd/model_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/overlord/hookstate/ctlcmd/model_test.go b/overlord/hookstate/ctlcmd/model_test.go index 2532347d64f..eceece9dffb 100644 --- a/overlord/hookstate/ctlcmd/model_test.go +++ b/overlord/hookstate/ctlcmd/model_test.go @@ -360,7 +360,7 @@ base: core18 gadget: pc kernel: pc-kernel timestamp: %s -`, time.Now().Format(time.RFC3339))) +`, current.Timestamp().Format(time.RFC3339))) c.Check(string(stderr), Equals, "") } @@ -403,7 +403,7 @@ base: core18 gadget: pc kernel: pc-kernel timestamp: %s -`, time.Now().Format(time.RFC3339))) +`, current.Timestamp().Format(time.RFC3339))) c.Check(string(stderr), Equals, "") } @@ -445,7 +445,7 @@ func (s *modelSuite) TestHappyModelCommandGadgetJson(c *C) { "model": "pc-model", "serial": null, "timestamp": "%s" -}`, time.Now().Format(time.RFC3339))) +}`, current.Timestamp().Format(time.RFC3339))) c.Check(string(stderr), Equals, "") } @@ -525,7 +525,7 @@ func (s *modelSuite) TestHappyModelCommandAssertionGadgetJson(c *C) { "timestamp": "%s", "type": "model" } -}`, current.SignKeyID(), time.Now().Format(time.RFC3339))) +}`, current.SignKeyID(), current.Timestamp().Format(time.RFC3339))) c.Check(string(stderr), Equals, "") } @@ -566,7 +566,7 @@ func (s *modelSuite) TestRunWithoutHook(c *C) { "model": "pc-model", "serial": null, "timestamp": "%s" -}`, time.Now().Format(time.RFC3339))) +}`, current.Timestamp().Format(time.RFC3339))) c.Check(string(stderr), Equals, "") } From 480459add944d6945ef2d82b26765592684a4322 Mon Sep 17 00:00:00 2001 From: Alexandre Lissy Date: Fri, 10 Jun 2022 08:45:59 +0200 Subject: [PATCH 062/153] interfaces/system-packages-doc: allow read-only access to /usr/share/cups/doc-root/ and /usr/share/gimp/2.0/help/ (https://bugzilla.mozilla.org/show_bug.cgi?id=1770462) --- interfaces/builtin/system_packages_doc.go | 18 +++++++++++ .../builtin/system_packages_doc_test.go | 30 ++++++++++++++----- .../interfaces-system-packages-doc/task.yaml | 8 +++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/interfaces/builtin/system_packages_doc.go b/interfaces/builtin/system_packages_doc.go index 1cde33ba2b0..56791b1fa32 100644 --- a/interfaces/builtin/system_packages_doc.go +++ b/interfaces/builtin/system_packages_doc.go @@ -40,6 +40,8 @@ const systemPackagesDocConnectedPlugAppArmor = ` # Description: can access documentation of system packages. /usr/share/doc/{,**} r, +/usr/share/cups/doc-root/{,**} r, +/usr/share/gimp/2.0/help/{,**} r, /usr/share/gtk-doc/{,**} r, /usr/share/libreoffice/help/{,**} r, /usr/share/xubuntu-docs/{,**} r, @@ -56,6 +58,12 @@ func (iface *systemPackagesDocInterface) AppArmorConnectedPlug(spec *apparmor.Sp emit(" mount options=(bind) /var/lib/snapd/hostfs/usr/share/doc/ -> /usr/share/doc/,\n") emit(" remount options=(bind, ro) /usr/share/doc/,\n") emit(" umount /usr/share/doc/,\n") + emit(" mount options=(bind) /var/lib/snapd/hostfs/usr/share/cups/doc-root/ -> /usr/share/cups/doc-root/,\n") + emit(" remount options=(bind, ro) /usr/share/cups/doc-root/,\n") + emit(" umount /usr/share/cups/doc-root/,\n") + emit(" mount options=(bind) /var/lib/snapd/hostfs/usr/share/gimp/2.0/help/ -> /usr/share/gimp/2.0/help/,\n") + emit(" remount options=(bind, ro) /usr/share/gimp/2.0/help/,\n") + emit(" umount /usr/share/gimp/2.0/help/,\n") emit(" mount options=(bind) /var/lib/snapd/hostfs/usr/share/gtk-doc/ -> /usr/share/gtk-doc/,\n") emit(" remount options=(bind, ro) /usr/share/gtk-doc/,\n") emit(" umount /usr/share/gtk-doc/,\n") @@ -77,6 +85,16 @@ func (iface *systemPackagesDocInterface) MountConnectedPlug(spec *mount.Specific Dir: "/usr/share/doc", Options: []string{"bind", "ro"}, }) + spec.AddMountEntry(osutil.MountEntry{ + Name: "/var/lib/snapd/hostfs/usr/share/cups/doc-root", + Dir: "/usr/share/cups/doc-root", + Options: []string{"bind", "ro"}, + }) + spec.AddMountEntry(osutil.MountEntry{ + Name: "/var/lib/snapd/hostfs/usr/share/gimp/2.0/help", + Dir: "/usr/share/gimp/2.0/help", + Options: []string{"bind", "ro"}, + }) spec.AddMountEntry(osutil.MountEntry{ Name: "/var/lib/snapd/hostfs/usr/share/gtk-doc", Dir: "/usr/share/gtk-doc", diff --git a/interfaces/builtin/system_packages_doc_test.go b/interfaces/builtin/system_packages_doc_test.go index 280e6885527..b3cf9e915fa 100644 --- a/interfaces/builtin/system_packages_doc_test.go +++ b/interfaces/builtin/system_packages_doc_test.go @@ -86,6 +86,8 @@ func (s *systemPackagesDocSuite) TestAppArmorSpec(c *C) { c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app"}) c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Description: can access documentation of system packages.") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/usr/share/doc/{,**} r,") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/usr/share/cups/doc-root/{,**} r,") + c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/usr/share/gimp/2.0/help/{,**} r,") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/usr/share/libreoffice/help/{,**} r,") c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/usr/share/xubuntu-docs/{,**} r,") @@ -95,6 +97,14 @@ func (s *systemPackagesDocSuite) TestAppArmorSpec(c *C) { c.Check(updateNS, testutil.Contains, " remount options=(bind, ro) /usr/share/doc/,\n") c.Check(updateNS, testutil.Contains, " umount /usr/share/doc/,\n") + c.Check(updateNS, testutil.Contains, " mount options=(bind) /var/lib/snapd/hostfs/usr/share/cups/doc-root/ -> /usr/share/cups/doc-root/,\n") + c.Check(updateNS, testutil.Contains, " remount options=(bind, ro) /usr/share/cups/doc-root/,\n") + c.Check(updateNS, testutil.Contains, " umount /usr/share/cups/doc-root/,\n") + + c.Check(updateNS, testutil.Contains, " mount options=(bind) /var/lib/snapd/hostfs/usr/share/gimp/2.0/help/ -> /usr/share/gimp/2.0/help/,\n") + c.Check(updateNS, testutil.Contains, " remount options=(bind, ro) /usr/share/gimp/2.0/help/,\n") + c.Check(updateNS, testutil.Contains, " umount /usr/share/gimp/2.0/help/,\n") + c.Check(updateNS, testutil.Contains, " mount options=(bind) /var/lib/snapd/hostfs/usr/share/gtk-doc/ -> /usr/share/gtk-doc/,\n") c.Check(updateNS, testutil.Contains, " remount options=(bind, ro) /usr/share/gtk-doc/,\n") c.Check(updateNS, testutil.Contains, " umount /usr/share/gtk-doc/,\n") @@ -123,19 +133,25 @@ func (s *systemPackagesDocSuite) TestMountSpec(c *C) { c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil) entries := spec.MountEntries() - c.Assert(entries, HasLen, 4) + c.Assert(entries, HasLen, 6) c.Check(entries[0].Name, Equals, "/var/lib/snapd/hostfs/usr/share/doc") c.Check(entries[0].Dir, Equals, "/usr/share/doc") c.Check(entries[0].Options, DeepEquals, []string{"bind", "ro"}) - c.Check(entries[1].Name, Equals, "/var/lib/snapd/hostfs/usr/share/gtk-doc") - c.Check(entries[1].Dir, Equals, "/usr/share/gtk-doc") + c.Check(entries[1].Name, Equals, "/var/lib/snapd/hostfs/usr/share/cups/doc-root") + c.Check(entries[1].Dir, Equals, "/usr/share/cups/doc-root") c.Check(entries[1].Options, DeepEquals, []string{"bind", "ro"}) - c.Check(entries[2].Name, Equals, "/var/lib/snapd/hostfs/usr/share/libreoffice/help") - c.Check(entries[2].Dir, Equals, "/usr/share/libreoffice/help") + c.Check(entries[2].Name, Equals, "/var/lib/snapd/hostfs/usr/share/gimp/2.0/help") + c.Check(entries[2].Dir, Equals, "/usr/share/gimp/2.0/help") c.Check(entries[2].Options, DeepEquals, []string{"bind", "ro"}) - c.Check(entries[3].Name, Equals, "/var/lib/snapd/hostfs/usr/share/xubuntu-docs") - c.Check(entries[3].Dir, Equals, "/usr/share/xubuntu-docs") + c.Check(entries[3].Name, Equals, "/var/lib/snapd/hostfs/usr/share/gtk-doc") + c.Check(entries[3].Dir, Equals, "/usr/share/gtk-doc") c.Check(entries[3].Options, DeepEquals, []string{"bind", "ro"}) + c.Check(entries[4].Name, Equals, "/var/lib/snapd/hostfs/usr/share/libreoffice/help") + c.Check(entries[4].Dir, Equals, "/usr/share/libreoffice/help") + c.Check(entries[4].Options, DeepEquals, []string{"bind", "ro"}) + c.Check(entries[5].Name, Equals, "/var/lib/snapd/hostfs/usr/share/xubuntu-docs") + c.Check(entries[5].Dir, Equals, "/usr/share/xubuntu-docs") + c.Check(entries[5].Options, DeepEquals, []string{"bind", "ro"}) entries = spec.UserMountEntries() c.Assert(entries, HasLen, 0) diff --git a/tests/main/interfaces-system-packages-doc/task.yaml b/tests/main/interfaces-system-packages-doc/task.yaml index 1a1b957abe1..c91589c96a6 100644 --- a/tests/main/interfaces-system-packages-doc/task.yaml +++ b/tests/main/interfaces-system-packages-doc/task.yaml @@ -13,6 +13,10 @@ prepare: | echo text >/usr/share/xubuntu-docs/content mkdir -p /usr/share/gtk-doc echo text >/usr/share/gtk-doc/content + mkdir -p /usr/share/cups/doc-root + echo text >/usr/share/cups/doc-root/content + mkdir -p /usr/share/gimp/2.0/help + echo text >/usr/share/gimp/2.0/help/content restore: | snap remove --purge test-snapd-app @@ -29,6 +33,8 @@ execute: | test-snapd-app.sh -c 'cat /usr/share/libreoffice/help/content' | MATCH text test-snapd-app.sh -c 'cat /usr/share/xubuntu-docs/content' | MATCH text test-snapd-app.sh -c 'cat /usr/share/gtk-doc/content' | MATCH text + test-snapd-app.sh -c 'cat /usr/share/cups/doc-root/content' | MATCH text + test-snapd-app.sh -c 'cat /usr/share/gimp/2.0/help/content' | MATCH text # The interface can be disconnected snap disconnect test-snapd-app:system-packages-doc @@ -36,3 +42,5 @@ execute: | not test-snapd-app.sh -c 'test -e /usr/share/libreoffice/help/content' not test-snapd-app.sh -c 'test -e /usr/share/xubuntu-docs/content' not test-snapd-app.sh -c 'test -e /usr/share/gtk-doc/content' + not test-snapd-app.sh -c 'test -e /usr/share/cups/doc-root/content' + not test-snapd-app.sh -c 'test -e /usr/share/gimp/2.0/help/content' From dd32173b78bce8b4c46a39c37815cbf221ecca6d Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Mon, 13 Jun 2022 09:34:20 +0100 Subject: [PATCH 063/153] o/snapstate: minor simplifications Signed-off-by: Miguel Pires --- features/features.go | 2 +- overlord/snapstate/handlers.go | 4 +--- overlord/snapstate/snapstate.go | 8 ++++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/features/features.go b/features/features.go index d6c95ef7b9b..56621a7499f 100644 --- a/features/features.go +++ b/features/features.go @@ -53,7 +53,7 @@ const ( DbusActivation // HiddenSnapDataHomeDir controls if the snaps' data dir is ~/.snap/data instead of ~/snap HiddenSnapDataHomeDir - // MoveSnapHomeDir controls whether snap user data under ~/snap (or ~/.snap/data) are moved to ~/Snap. + // MoveSnapHomeDir controls whether snap user data under ~/snap (or ~/.snap/data) can be moved to ~/Snap. MoveSnapHomeDir // CheckDiskSpaceRemove controls free disk space check on remove whenever automatic snapshot needs to be created. CheckDiskSpaceRemove diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go index 25a9df3b195..6bbc9b7fc76 100644 --- a/overlord/snapstate/handlers.go +++ b/overlord/snapstate/handlers.go @@ -3683,12 +3683,10 @@ func (m *SnapManager) doMigrateSnapHome(t *state.Task, tomb *tomb.Tomb) error { } st.Lock() + defer st.Unlock() t.Set("undo-exposed-home-init", undo) - st.Unlock() snapsup.MigratedToExposedHome = true - st.Lock() - defer st.Unlock() return SetTaskSnapSetup(t, snapsup) } diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 7cd7213d231..80d9b4a07a4 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -2484,12 +2484,12 @@ func MigrateHome(st *state.State, snaps []string) ([]*state.TaskSet, error) { tasks = append(tasks, t) } - stop := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), snapsup.InstanceName())) + stop := st.NewTask("stop-snap-services", fmt.Sprintf(i18n.G("Stop snap %q services"), name)) stop.Set("stop-reason", "home-migration") addTask(stop) prev = stop - unlink := st.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), snapsup.InstanceName())) + unlink := st.NewTask("unlink-current-snap", fmt.Sprintf(i18n.G("Make current revision for snap %q unavailable"), name)) addTask(unlink) prev = unlink @@ -2498,12 +2498,12 @@ func MigrateHome(st *state.State, snaps []string) ([]*state.TaskSet, error) { prev = migrate // finalize (wrappers+current symlink) - linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), snapsup.InstanceName(), si.Revision)) + linkSnap := st.NewTask("link-snap", fmt.Sprintf(i18n.G("Make snap %q (%s) available to the system"), name, si.Revision)) addTask(linkSnap) prev = linkSnap // run new services - startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), snapsup.InstanceName(), si.Revision)) + startSnapServices := st.NewTask("start-snap-services", fmt.Sprintf(i18n.G("Start snap %q (%s) services"), name, si.Revision)) addTask(startSnapServices) prev = startSnapServices From f19a80b0d244b0576753a12248cdd0379d9f6f7c Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 13 Jun 2022 10:58:33 +0200 Subject: [PATCH 064/153] squashfs: tweak error handling of Snap.Unpack() Use a multi-writer when detecting errors from unsquashfs and capture stderr in addition to the newUnsquashfsStderrWriter() we currently use. This ensures that on modern unsquashfs that properly returns non-zero exit codes we show a useful error message. Size of the buffer should not be a concern because unsquashfs stops early nowdays (unlike the old versions that would keep iterating over the squashfs and generating a lot of output). The behavior of unsquashfs was checked with: ``` mkdir /tmp/tiny sudo mount -o size=1M -t tmpfs none /tmp/tiny sudo unsquashfs -f -d /tmp/tiny /var/lib/snapd/snaps/core20_1524.snap ``` Thanks to Maciej for the suggestion with the multi-writer. --- snap/squashfs/squashfs.go | 14 +++++++------- snap/squashfs/squashfs_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/snap/squashfs/squashfs.go b/snap/squashfs/squashfs.go index 2f49c8eefd9..1f39d2f5845 100644 --- a/snap/squashfs/squashfs.go +++ b/snap/squashfs/squashfs.go @@ -202,17 +202,17 @@ func (u *unsquashfsStderrWriter) Err() error { func (s *Snap) Unpack(src, dstDir string) error { usw := newUnsquashfsStderrWriter() + var output bytes.Buffer cmd := exec.Command("unsquashfs", "-n", "-f", "-d", dstDir, s.path, src) - cmd.Stderr = usw - err := cmd.Run() - // check unsquashfs errors first + cmd.Stderr = io.MultiWriter(&output, usw) + if err := cmd.Run(); err != nil { + return fmt.Errorf("cannot extract %q to %q: %v", src, dstDir, osutil.OutputErr(output.Bytes(), err)) + } + // older versions of unsquashfs do not report errors via exit code, + // so we need this extra check. if usw.Err() != nil { return fmt.Errorf("cannot extract %q to %q: %v", src, dstDir, usw.Err()) } - // only if none are found report generic errors - if err != nil { - return fmt.Errorf("cannot run unsquashfs: %v", err) - } return nil } diff --git a/snap/squashfs/squashfs_test.go b/snap/squashfs/squashfs_test.go index 5618e5cc0e4..b0f1fe5f582 100644 --- a/snap/squashfs/squashfs_test.go +++ b/snap/squashfs/squashfs_test.go @@ -559,6 +559,34 @@ EOF c.Check(err.Error(), Equals, `cannot extract "*" to "some-output-dir": failed: "Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols, skipping", "Write on output file failed because No space left on device", "writer: failed to write data block 0", "Failed to write /tmp/1/modules/4.4.0-112-generic/modules.symbols.bin, skipping", and 15 more`) } +func (s *SquashfsTestSuite) TestUnpackDetectsFailuresViaExitCode(c *C) { + mockUnsquashfs := testutil.MockCommand(c, "unsquashfs", ` +cat <&2 < Date: Mon, 13 Jun 2022 11:05:07 +0200 Subject: [PATCH 065/153] snap: fix "woke" issue (rename blacklist.conf to some.conf) --- snap/squashfs/squashfs_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/squashfs/squashfs_test.go b/snap/squashfs/squashfs_test.go index b0f1fe5f582..84cb3e65fb7 100644 --- a/snap/squashfs/squashfs_test.go +++ b/snap/squashfs/squashfs_test.go @@ -569,7 +569,7 @@ EOF cat >&2 < Date: Fri, 10 Jun 2022 10:31:11 +0200 Subject: [PATCH 066/153] boot: helpers to obtain the name of fallback ubuntu-save sealed keys Signed-off-by: Maciej Borzecki --- boot/seal.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/boot/seal.go b/boot/seal.go index 391811f3872..f00c0501fdb 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -147,14 +147,25 @@ func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest { } } +// FallbackSaveSealedKeyUnder returns the name of a fallback ubuntu save key. +func FallbackSaveSealedKeyUnder(dir string) string { + return filepath.Join(dir, "ubuntu-save.recovery.sealed-key") +} + +// FactoryResetFallbackSaveSealedKeyUnder returns the name of a fallback ubuntu +// save key object generated during factory reset. +func FactoryResetFallbackSaveSealedKeyUnder(dir string) string { + return filepath.Join(dir, "ubuntu-save.recovery.sealed-key.factory-reset") +} + func fallbackKeySealRequests(key, saveKey keys.EncryptionKey, factoryReset bool) []secboot.SealKeyRequest { - saveFallbackKey := filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key") + saveFallbackKey := FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir) if factoryReset { // factory reset uses alternative sealed key location, such that // until we boot into the run mode, both sealed keys are present // on disk - saveFallbackKey = filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset") + saveFallbackKey = FactoryResetFallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir) } return []secboot.SealKeyRequest{ { From 781abf347a454793878ac30f8a70c105d7720223 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 11 Mar 2022 12:21:23 +0100 Subject: [PATCH 067/153] o/devicestate: factory reset mode, attempt to update the keys Signed-off-by: Maciej Borzecki --- .../devicestate_install_mode_test.go | 189 ++++++++++++++++-- overlord/devicestate/export_test.go | 18 +- overlord/devicestate/handlers_install.go | 127 +++++++++++- 3 files changed, 305 insertions(+), 29 deletions(-) diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go index 9d6a8996291..8f2bb7ec8d6 100644 --- a/overlord/devicestate/devicestate_install_mode_test.go +++ b/overlord/devicestate/devicestate_install_mode_test.go @@ -2263,13 +2263,16 @@ func (s *deviceMgrInstallModeSuite) TestInstallModeWritesTimesyncdClockErr(c *C) } type resetTestCase struct { - noSave bool - tpm bool + noSave bool + tpm bool + encrypt bool + trustedBootloader bool } func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts.Model, tc resetTestCase) error { restore := release.MockOnClassic(false) defer restore() + bootloaderRootdir := c.MkDir() // inject trusted keys defer sysdb.InjectTrusted([]asserts.Assertion{s.storeSigning.TrustedKey})() @@ -2288,9 +2291,26 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts brGadgetRoot = gadgetRoot brDevice = device brOpts = options + installSealingObserver = obs installFactoryResetCalled++ - // TODO encryption - return &install.InstalledSystemSideData{}, nil + var keyForRole map[string]keys.EncryptionKey + if tc.encrypt { + keyForRole = map[string]keys.EncryptionKey{ + gadget.SystemData: dataEncryptionKey, + gadget.SystemSave: saveKey, + } + } + devForRole := map[string]string{ + gadget.SystemData: "/dev/foo-data", + } + if tc.encrypt { + devForRole[gadget.SystemSave] = "/dev/foo-save" + } + c.Assert(os.MkdirAll(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), 0755), IsNil) + return &install.InstalledSystemSideData{ + KeyForRole: keyForRole, + DeviceForRole: devForRole, + }, nil }) defer restore() @@ -2303,19 +2323,54 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts }) defer restore() + if tc.trustedBootloader { + tab := bootloadertest.Mock("trusted", bootloaderRootdir).WithTrustedAssets() + tab.TrustedAssetsList = []string{"trusted-asset"} + bootloader.Force(tab) + s.AddCleanup(func() { bootloader.Force(nil) }) + + err := os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "trusted-asset"), nil, 0644) + c.Assert(err, IsNil) + } + s.state.Lock() s.makeMockInstalledPcGadget(c, "", "") s.state.Unlock() + var saveKey keys.EncryptionKey + restore = devicestate.MockSecbootStageEncryptionKeyChange(func(node string, key keys.EncryptionKey) error { + if tc.encrypt { + c.Check(node, Equals, "/dev/foo-save") + saveKey = key + return nil + } + c.Fail() + return fmt.Errorf("unexpected call") + }) + defer restore() + bootMakeBootableCalled := 0 - restore = devicestate.MockBootMakeSystemRunnable(func(makeRunnableModel *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error { + restore = devicestate.MockBootMakeSystemRunnableAfterDataReset(func(makeRunnableModel *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error { c.Check(makeRunnableModel, DeepEquals, model) c.Check(bootWith.KernelPath, Matches, ".*/var/lib/snapd/snaps/pc-kernel_1.snap") c.Check(bootWith.BasePath, Matches, ".*/var/lib/snapd/snaps/core20_2.snap") c.Check(bootWith.RecoverySystemDir, Matches, "/systems/20191218") c.Check(bootWith.UnpackedGadgetDir, Equals, filepath.Join(dirs.SnapMountDir, "pc/1")) - c.Check(seal, IsNil) + if tc.encrypt { + c.Check(seal, NotNil) + } else { + c.Check(seal, IsNil) + } bootMakeBootableCalled++ + + // this would be done by boot + if tc.encrypt { + err := ioutil.WriteFile(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + []byte{'s', 'a', 'v', 'e'}, 0644) + c.Check(err, IsNil) + } return nil }) defer restore() @@ -2358,15 +2413,46 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts c.Assert(factoryReset.Status(), Equals, state.DoneStatus) - c.Assert(installFactoryResetCalled, Equals, 1) + // in the right way c.Assert(brGadgetRoot, Equals, filepath.Join(dirs.SnapMountDir, "/pc/1")) c.Assert(brDevice, Equals, "") - c.Assert(brOpts, DeepEquals, install.Options{ - Mount: true, - }) - c.Assert(installSealingObserver, IsNil) + if tc.encrypt { + c.Assert(brOpts, DeepEquals, install.Options{ + Mount: true, + EncryptionType: secboot.EncryptionTypeLUKS, + }) + } else { + c.Assert(brOpts, DeepEquals, install.Options{ + Mount: true, + }) + } + if tc.encrypt { + // inteface is not nil + c.Assert(installSealingObserver, NotNil) + // we expect a very specific type + trustedInstallObserver, ok := installSealingObserver.(*boot.TrustedAssetsInstallObserver) + c.Assert(ok, Equals, true, Commentf("unexpected type: %T", installSealingObserver)) + c.Assert(trustedInstallObserver, NotNil) + } else { + c.Assert(installSealingObserver, IsNil) + } + + c.Assert(installFactoryResetCalled, Equals, 1) c.Assert(bootMakeBootableCalled, Equals, 1) c.Assert(s.restartRequests, DeepEquals, []restart.RestartType{restart.RestartSystemNow}) + if tc.encrypt { + c.Assert(saveKey, NotNil) + c.Check(filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey)) + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), testutil.FileAbsent) + // sha3-384 of the mocked ubuntu-save sealed key + c.Check(filepath.Join(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), "factory-reset"), + testutil.FileEquals, + `{"fallback-save-key-hash":"d192153f0a50e826c6eb400c8711750ed0466571df1d151aaecc8c73095da7ec104318e7bf74d5e5ae2940827bf8402b"} +`) + } else { + c.Check(filepath.Join(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), "factory-reset"), + testutil.FileEquals, "{}\n") + } return nil } @@ -2428,7 +2514,7 @@ echo "mock output of: $(basename "$0") $*" defer restore() err = s.doRunFactoryResetChange(c, model, resetTestCase{ - tpm: false, + tpm: false, encrypt: false, }) c.Logf("logs:\n%v", logbuf.String()) c.Assert(err, IsNil) @@ -2478,6 +2564,61 @@ echo "mock output of: $(basename "$0") $*" }) } +func (s *deviceMgrInstallModeSuite) TestFactoryResetEncryptionHappyFull(c *C) { + s.state.Lock() + model := s.makeMockInstallModel(c, "dangerous") + s.state.Unlock() + + // for debug timinigs + mockedSnapCmd := testutil.MockCommand(c, "snap", ` +echo "mock output of: $(basename "$0") $*" +`) + defer mockedSnapCmd.Restore() + + // pretend snap-bootstrap mounted ubuntu-save + err := os.MkdirAll(boot.InitramfsUbuntuSaveDir, 0755) + c.Assert(err, IsNil) + snaptest.PopulateDir(boot.InitramfsSeedEncryptionKeyDir, [][]string{ + {"ubuntu-data.recovery.sealed-key", ""}, + }) + + // and it has some content + serial := makeDeviceSerialAssertionInDir(c, boot.InstallHostDeviceSaveDir, s.storeSigning, s.brands, + model, devKey, "serial-1234") + + err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSaveDir, "device/fde"), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSaveDir, "device/fde/marker"), nil, 0644) + c.Assert(err, IsNil) + + logbuf, restore := logger.MockLogger() + defer restore() + + err = s.doRunFactoryResetChange(c, model, resetTestCase{ + tpm: true, encrypt: true, trustedBootloader: true, + }) + c.Logf("logs:\n%v", logbuf.String()) + c.Assert(err, IsNil) + + // verify that the serial assertion has been restored + assertsInResetSystem := filepath.Join(boot.InstallHostWritableDir, "var/lib/snapd/assertions") + bs, err := asserts.OpenFSBackstore(assertsInResetSystem) + c.Assert(err, IsNil) + db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + Backstore: bs, + Trusted: s.storeSigning.Trusted, + OtherPredefined: s.storeSigning.Generic, + }) + c.Assert(err, IsNil) + ass, err := db.FindMany(asserts.SerialType, map[string]string{ + "brand-id": serial.BrandID(), + "model": serial.Model(), + "device-key-sha3-384": serial.DeviceKey().ID(), + }) + c.Assert(err, IsNil) + c.Assert(ass, HasLen, 1) +} + func (s *deviceMgrInstallModeSuite) TestFactoryResetSerialsWithoutKey(c *C) { s.state.Lock() model := s.makeMockInstallModel(c, "dangerous") @@ -2502,7 +2643,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetSerialsWithoutKey(c *C) { defer restore() err = s.doRunFactoryResetChange(c, model, resetTestCase{ - tpm: false, + tpm: false, encrypt: false, }) c.Logf("logs:\n%v", logbuf.String()) c.Assert(err, IsNil) @@ -2527,7 +2668,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetNoSerials(c *C) { defer restore() err = s.doRunFactoryResetChange(c, model, resetTestCase{ - tpm: false, + tpm: false, encrypt: false, }) c.Logf("logs:\n%v", logbuf.String()) c.Assert(err, IsNil) @@ -2550,7 +2691,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetNoSave(c *C) { defer restore() err := s.doRunFactoryResetChange(c, model, resetTestCase{ - tpm: false, + tpm: false, encrypt: false, noSave: true, }) c.Logf("logs:\n%v", logbuf.String()) @@ -2583,7 +2724,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetPreviouslyEncrypted(c *C) { err = s.doRunFactoryResetChange(c, model, resetTestCase{ // no TPM - tpm: false, + tpm: false, encrypt: false, }) c.Logf("logs:\n%v", logbuf.String()) c.Assert(err, ErrorMatches, `(?s).*cannot perform factory reset using different encryption, the original system was encrypted\)`) @@ -2604,7 +2745,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetPreviouslyUnencrypted(c *C) err = s.doRunFactoryResetChange(c, model, resetTestCase{ // no TPM - tpm: true, + tpm: true, encrypt: false, }) c.Logf("logs:\n%v", logbuf.String()) c.Assert(err, ErrorMatches, `(?s).*cannot perform factory reset using different encryption, the original system was unencrypted\)`) @@ -2636,7 +2777,7 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetSerialManyOneValid(c *C) { defer restore() err = s.doRunFactoryResetChange(c, model, resetTestCase{ - tpm: false, + tpm: false, encrypt: false, }) c.Logf("logs:\n%v", logbuf.String()) c.Assert(err, IsNil) @@ -2676,8 +2817,18 @@ func (s *deviceMgrInstallModeSuite) TestFactoryResetExpectedTasks(c *C) { restore := release.MockOnClassic(false) defer restore() + restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { + return fmt.Errorf("TPM not available") + }) + defer restore() + restore = devicestate.MockInstallFactoryReset(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver, pertTimings timings.Measurer) (*install.InstalledSystemSideData, error) { - return nil, nil + c.Assert(os.MkdirAll(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), 0755), IsNil) + return &install.InstalledSystemSideData{ + DeviceForRole: map[string]string{ + "ubuntu-save": "/dev/foo", + }, + }, nil }) defer restore() diff --git a/overlord/devicestate/export_test.go b/overlord/devicestate/export_test.go index 12f3df74b11..85a526dbdcb 100644 --- a/overlord/devicestate/export_test.go +++ b/overlord/devicestate/export_test.go @@ -297,11 +297,15 @@ func MockGadgetIsCompatible(mock func(current, update *gadget.Info) error) (rest } func MockBootMakeSystemRunnable(f func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error) (restore func()) { - old := bootMakeRunnable + restore = testutil.Backup(&bootMakeRunnable) bootMakeRunnable = f - return func() { - bootMakeRunnable = old - } + return restore +} + +func MockBootMakeSystemRunnableAfterDataReset(f func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error) (restore func()) { + restore = testutil.Backup(&bootMakeRunnableAfterDataReset) + bootMakeRunnableAfterDataReset = f + return restore } func MockBootEnsureNextBootToRunMode(f func(systemLabel string) error) (restore func()) { @@ -350,6 +354,12 @@ func MockInstallFactoryReset(f func(model gadget.Model, gadgetRoot, kernelRoot, return restore } +func MockSecbootStageEncryptionKeyChange(f func(node string, key keys.EncryptionKey) error) (restore func()) { + restore = testutil.Backup(&secbootStageEncryptionKeyChange) + secbootStageEncryptionKeyChange = f + return restore +} + func MockCloudInitStatus(f func() (sysconfig.CloudInitState, error)) (restore func()) { old := cloudInitStatus cloudInitStatus = f diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index c185b60b777..c72a221f3a5 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -24,6 +24,8 @@ import ( "compress/gzip" "crypto" "encoding/base64" + "encoding/hex" + "encoding/json" "errors" "fmt" "io" @@ -31,6 +33,7 @@ import ( "os/exec" "path/filepath" + _ "golang.org/x/crypto/sha3" "gopkg.in/tomb.v2" "github.com/snapcore/snapd/asserts" @@ -56,10 +59,12 @@ import ( ) var ( - bootMakeRunnable = boot.MakeRunnableSystem - bootEnsureNextBootToRunMode = boot.EnsureNextBootToRunMode - installRun = install.Run - installFactoryReset = install.FactoryReset + bootMakeRunnable = boot.MakeRunnableSystem + bootMakeRunnableAfterDataReset = boot.MakeRunnableSystemAfterDataReset + bootEnsureNextBootToRunMode = boot.EnsureNextBootToRunMode + installRun = install.Run + installFactoryReset = install.FactoryReset + secbootStageEncryptionKeyChange = secboot.StageEncryptionKeyChange sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem ) @@ -920,16 +925,83 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err return fmt.Errorf("cannot use gadget: %v", err) } + var trustedInstallObserver *boot.TrustedAssetsInstallObserver + // get a nice nil interface by default + var installObserver gadget.ContentObserver + trustedInstallObserver, err = boot.TrustedAssetsInstallObserverForModel(model, gadgetDir, useEncryption) + if err != nil && err != boot.ErrObserverNotApplicable { + return fmt.Errorf("cannot setup asset install observer: %v", err) + } + if err == nil { + installObserver = trustedInstallObserver + if !useEncryption { + // there will be no key sealing, so past the + // installation pass no other methods need to be called + trustedInstallObserver = nil + } + } + + var installedSystem *install.InstalledSystemSideData // run the create partition code logger.Noticef("create and deploy partitions") timings.Run(perfTimings, "factory-reset", "Factory reset", func(tm timings.Measurer) { st.Unlock() defer st.Lock() - _, err = installFactoryReset(model, gadgetDir, kernelDir, "", bopts, nil, tm) + installedSystem, err = installFactoryReset(model, gadgetDir, kernelDir, "", bopts, installObserver, tm) }) if err != nil { return fmt.Errorf("cannot perform factory reset: %v", err) } + logger.Noticef("devs: %+v", installedSystem.DeviceForRole) + + if trustedInstallObserver != nil { + // at this point we removed boot and data. sealed keys are becoming + // useless + err := os.Remove(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key")) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("cannot cleanup obsolete key file: %v", err) + } + + // TODO: generate new encryption key for save + // TODO: generate new recovery key for save + key, err := keys.NewEncryptionKey() + if err != nil { + return fmt.Errorf("cannot create encryption key: %v", err) + } + + saveNode := installedSystem.DeviceForRole[gadget.SystemSave] + if saveNode == "" { + return fmt.Errorf("internal error: no system-save device") + } + + if err := secbootStageEncryptionKeyChange(saveNode, key); err != nil { + return fmt.Errorf("cannot change encryption keys: %v", err) + } + + installedSystem.KeyForRole[gadget.SystemSave] = key + + // sanity check + if len(installedSystem.KeyForRole) == 0 || installedSystem.KeyForRole[gadget.SystemData] == nil || installedSystem.KeyForRole[gadget.SystemSave] == nil { + return fmt.Errorf("internal error: system encryption keys are unset") + } + dataEncryptionKey := installedSystem.KeyForRole[gadget.SystemData] + saveEncryptionKey := installedSystem.KeyForRole[gadget.SystemSave] + + // make note of the encryption keys + trustedInstallObserver.ChosenEncryptionKeys(dataEncryptionKey, saveEncryptionKey) + + // keep track of recovery assets + if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil { + return fmt.Errorf("cannot observe existing trusted recovery assets: err") + } + if err := saveKeys(installedSystem.KeyForRole); err != nil { + return err + } + // write markers containing a secret to pair data and save + if err := writeMarkers(); err != nil { + return err + } + } // TODO: splice into a common install/reset helper? @@ -989,11 +1061,17 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err UnpackedGadgetDir: gadgetDir, } timings.Run(perfTimings, "boot-make-runnable", "Make target system runnable", func(timings.Measurer) { - err = bootMakeRunnable(deviceCtx.Model(), bootWith, nil) + err = bootMakeRunnableAfterDataReset(deviceCtx.Model(), bootWith, trustedInstallObserver) }) if err != nil { return fmt.Errorf("cannot make system runnable: %v", err) } + + // leave a marker that factory reset was performed + factoryResetMarker := filepath.Join(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), "factory-reset") + if err := writeFactoryResetMarker(factoryResetMarker, useEncryption); err != nil { + return fmt.Errorf("cannot write the marker file: %v", err) + } return nil } @@ -1101,3 +1179,40 @@ func restoreDeviceSerialFromSave(model *asserts.Model) error { } return nil } + +type factoryResetMarker struct { + FallbackSaveKeyHash string `json:"fallback-save-key-hash,omitempty"` +} + +func fileDigest(p string) (string, error) { + digest, _, err := osutil.FileDigest(p, crypto.SHA3_384) + if err != nil { + return "", err + } + return hex.EncodeToString(digest), nil +} + +func writeFactoryResetMarker(marker string, hasEncryption bool) error { + keyDigest := "" + if hasEncryption { + d, err := fileDigest(boot.FactoryResetFallbackSaveSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir)) + if err != nil { + return err + } + keyDigest = d + } + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(factoryResetMarker{ + FallbackSaveKeyHash: keyDigest, + }) + if err != nil { + return err + } + + if hasEncryption { + logger.Noticef("writing factory-reset marker at %v with key digest %q", marker, keyDigest) + } else { + logger.Noticef("writing factory-reset marker at %v", marker) + } + return osutil.AtomicWriteFile(marker, buf.Bytes(), 0644, 0) +} From 67c3267185f818705f4495636ced86b4aac36ac8 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 14:23:55 +0200 Subject: [PATCH 068/153] o/devicestate: remove recovery key during factory reset Signed-off-by: Maciej Borzecki --- .../devicestate/devicestate_install_mode_test.go | 14 ++++++++++++++ overlord/devicestate/handlers_install.go | 13 +++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go index 8f2bb7ec8d6..64c22b09539 100644 --- a/overlord/devicestate/devicestate_install_mode_test.go +++ b/overlord/devicestate/devicestate_install_mode_test.go @@ -2351,6 +2351,19 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts }) defer restore() + var recoveryKeyRemoved bool + defer devicestate.MockSecbootRemoveRecoveryKeys(func(r2k map[secboot.RecoveryKeyDevice]string) error { + if tc.encrypt { + recoveryKeyRemoved = true + c.Check(r2k, DeepEquals, map[secboot.RecoveryKeyDevice]string{ + {Mountpoint: boot.InitramfsUbuntuSaveDir}: filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), + }) + return nil + } + c.Errorf("unexpected call") + return fmt.Errorf("unexpected call") + })() + bootMakeBootableCalled := 0 restore = devicestate.MockBootMakeSystemRunnableAfterDataReset(func(makeRunnableModel *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error { c.Check(makeRunnableModel, DeepEquals, model) @@ -2442,6 +2455,7 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts c.Assert(s.restartRequests, DeepEquals, []restart.RestartType{restart.RestartSystemNow}) if tc.encrypt { c.Assert(saveKey, NotNil) + c.Check(recoveryKeyRemoved, Equals, true) c.Check(filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey)) c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), testutil.FileAbsent) // sha3-384 of the mocked ubuntu-save sealed key diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index c72a221f3a5..f3a952b8484 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -962,8 +962,17 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err return fmt.Errorf("cannot cleanup obsolete key file: %v", err) } - // TODO: generate new encryption key for save - // TODO: generate new recovery key for save + // it is ok if the recovery key file on disk does not exist; + // ubuntu-save was opened during boot, so the removal operation + // can be authorized with a key from the keyring + err = secbootRemoveRecoveryKeys(map[secboot.RecoveryKeyDevice]string{ + {Mountpoint: boot.InitramfsUbuntuSaveDir}: filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), + }) + if err != nil { + return fmt.Errorf("cannot remove recovery key: %v", err) + } + + // new encryption key for save key, err := keys.NewEncryptionKey() if err != nil { return fmt.Errorf("cannot create encryption key: %v", err) From d50856ecb14e54ab2fbcd2785618c6d84feab1ed Mon Sep 17 00:00:00 2001 From: Pawel Stolowski Date: Mon, 13 Jun 2022 11:42:44 +0200 Subject: [PATCH 069/153] interfaces: update network-control interface with permissions required by resolvectl * Update network-control interface with permissions required by resolvectl command. * Split send/receive permissions (thanks alexmurray). * CentOS, Debian, Arch and Amazon Linux don't have resolvectl installed, so don't run there Co-authored-by: Maciej Borzecki Co-authored-by: Maciej Borzecki --- interfaces/builtin/network_control.go | 33 +++++++++++++++++++ .../network-control-consumer/meta/snap.yaml | 1 + .../main/interfaces-network-control/task.yaml | 10 ++++++ 3 files changed, 44 insertions(+) diff --git a/interfaces/builtin/network_control.go b/interfaces/builtin/network_control.go index c6b25c6eff0..6d753c1f206 100644 --- a/interfaces/builtin/network_control.go +++ b/interfaces/builtin/network_control.go @@ -68,6 +68,38 @@ dbus (send) member="SetLink{DefaultRoute,DNSOverTLS,DNS,DNSEx,DNSSEC,DNSSECNegativeTrustAnchors,MulticastDNS,Domains,LLMNR}" peer=(label=unconfined), +# required by resolvectl command +dbus (send) + bus=system + path="/org/freedesktop/resolve1" + interface=org.freedesktop.DBus.Properties + member=Get{,All} + peer=(label=unconfined), + +# required by resolvectl command +dbus (receive) + bus=system + path="/org/freedesktop/resolve1" + interface=org.freedesktop.DBus.Properties + member=PropertiesChanged + peer=(label=unconfined), + +# required by resolvectl command +dbus (send) + bus=system + path="/org/freedesktop/resolve1/link/*" + interface="org.freedesktop.DBus.Properties" + member=Get{,All} + peer=(label=unconfined), + +# required by resolvectl command +dbus (receive) + bus=system + path="/org/freedesktop/resolve1/link/*" + interface="org.freedesktop.DBus.Properties" + member=PropertiesChanged + peer=(label=unconfined), + #include capability net_admin, @@ -131,6 +163,7 @@ network sna, /{,usr/}{,s}bin/pppdump ixr, /{,usr/}{,s}bin/pppoe-discovery ixr, #/{,usr/}{,s}bin/pppstats ixr, # needs sys_module +/{,usr/}{,s}bin/resolvectl ixr, /{,usr/}{,s}bin/route ixr, /{,usr/}{,s}bin/routef ixr, /{,usr/}{,s}bin/routel ixr, diff --git a/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml b/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml index b6d77dca823..4fa97b850a7 100644 --- a/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml +++ b/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml @@ -2,6 +2,7 @@ name: network-control-consumer version: 1.0 summary: Basic network-control consumer snap description: A basic snap declaring a plug on network-control +base: core20 apps: cmd: diff --git a/tests/main/interfaces-network-control/task.yaml b/tests/main/interfaces-network-control/task.yaml index 40984acb925..7b4cd7673d0 100644 --- a/tests/main/interfaces-network-control/task.yaml +++ b/tests/main/interfaces-network-control/task.yaml @@ -54,6 +54,16 @@ execute: | echo "Then the snap command can query network status information" network-control-consumer.cmd ss -lnt | MATCH "LISTEN.*:$PORT" + echo "And DNS information" + case "$SPREAD_SYSTEM" in + centos-*|debian-*|arch-linux-*|amazon-linux-*) + # echo no systemd-resolved in those images + ;; + *) + network-control-consumer.cmd resolvectl | MATCH "DNS Server" + ;; + esac + if [ "$(snap debug confinement)" = strict ] ; then echo "When the plug is disconnected" snap disconnect network-control-consumer:network-control From 007666dcc80ffdca8ab842d208c3a12aa6ac2873 Mon Sep 17 00:00:00 2001 From: Pawel Stolowski Date: Mon, 13 Jun 2022 11:42:44 +0200 Subject: [PATCH 070/153] interfaces: update network-control interface with permissions required by resolvectl * Update network-control interface with permissions required by resolvectl command. * Split send/receive permissions (thanks alexmurray). * CentOS, Debian, Arch and Amazon Linux don't have resolvectl installed, so don't run there Co-authored-by: Maciej Borzecki Co-authored-by: Maciej Borzecki --- interfaces/builtin/network_control.go | 33 +++++++++++++++++++ .../network-control-consumer/meta/snap.yaml | 1 + .../main/interfaces-network-control/task.yaml | 10 ++++++ 3 files changed, 44 insertions(+) diff --git a/interfaces/builtin/network_control.go b/interfaces/builtin/network_control.go index c6b25c6eff0..6d753c1f206 100644 --- a/interfaces/builtin/network_control.go +++ b/interfaces/builtin/network_control.go @@ -68,6 +68,38 @@ dbus (send) member="SetLink{DefaultRoute,DNSOverTLS,DNS,DNSEx,DNSSEC,DNSSECNegativeTrustAnchors,MulticastDNS,Domains,LLMNR}" peer=(label=unconfined), +# required by resolvectl command +dbus (send) + bus=system + path="/org/freedesktop/resolve1" + interface=org.freedesktop.DBus.Properties + member=Get{,All} + peer=(label=unconfined), + +# required by resolvectl command +dbus (receive) + bus=system + path="/org/freedesktop/resolve1" + interface=org.freedesktop.DBus.Properties + member=PropertiesChanged + peer=(label=unconfined), + +# required by resolvectl command +dbus (send) + bus=system + path="/org/freedesktop/resolve1/link/*" + interface="org.freedesktop.DBus.Properties" + member=Get{,All} + peer=(label=unconfined), + +# required by resolvectl command +dbus (receive) + bus=system + path="/org/freedesktop/resolve1/link/*" + interface="org.freedesktop.DBus.Properties" + member=PropertiesChanged + peer=(label=unconfined), + #include capability net_admin, @@ -131,6 +163,7 @@ network sna, /{,usr/}{,s}bin/pppdump ixr, /{,usr/}{,s}bin/pppoe-discovery ixr, #/{,usr/}{,s}bin/pppstats ixr, # needs sys_module +/{,usr/}{,s}bin/resolvectl ixr, /{,usr/}{,s}bin/route ixr, /{,usr/}{,s}bin/routef ixr, /{,usr/}{,s}bin/routel ixr, diff --git a/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml b/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml index b6d77dca823..4fa97b850a7 100644 --- a/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml +++ b/tests/main/interfaces-network-control/network-control-consumer/meta/snap.yaml @@ -2,6 +2,7 @@ name: network-control-consumer version: 1.0 summary: Basic network-control consumer snap description: A basic snap declaring a plug on network-control +base: core20 apps: cmd: diff --git a/tests/main/interfaces-network-control/task.yaml b/tests/main/interfaces-network-control/task.yaml index 40984acb925..7b4cd7673d0 100644 --- a/tests/main/interfaces-network-control/task.yaml +++ b/tests/main/interfaces-network-control/task.yaml @@ -54,6 +54,16 @@ execute: | echo "Then the snap command can query network status information" network-control-consumer.cmd ss -lnt | MATCH "LISTEN.*:$PORT" + echo "And DNS information" + case "$SPREAD_SYSTEM" in + centos-*|debian-*|arch-linux-*|amazon-linux-*) + # echo no systemd-resolved in those images + ;; + *) + network-control-consumer.cmd resolvectl | MATCH "DNS Server" + ;; + esac + if [ "$(snap debug confinement)" = strict ] ; then echo "When the plug is disconnected" snap disconnect network-control-consumer:network-control From 4755b9f5fc8200576e0359617841d2d3ae511cbe Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 13 Jun 2022 12:30:53 +0200 Subject: [PATCH 071/153] tests: fix typo (cho->echo) --- tests/nested/manual/core20-4k-sector-size/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nested/manual/core20-4k-sector-size/task.yaml b/tests/nested/manual/core20-4k-sector-size/task.yaml index b9344c05009..e10521b26e5 100644 --- a/tests/nested/manual/core20-4k-sector-size/task.yaml +++ b/tests/nested/manual/core20-4k-sector-size/task.yaml @@ -14,7 +14,7 @@ prepare: | tests.nested create-vm core execute: | - cho "Wait for the system to be seeded first" + echo "Wait for the system to be seeded first" tests.nested exec "sudo snap wait system seed.loaded" echo "Ensure 'snap install' works" From 62ad49d91d1488778a73595864719a1f565f6385 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 4 May 2022 14:03:13 +0200 Subject: [PATCH 072/153] tests/nested/core/core20-factory-reset: enable data encryption Signed-off-by: Maciej Borzecki --- .../core/core20-factory-reset/task.yaml | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/nested/core/core20-factory-reset/task.yaml b/tests/nested/core/core20-factory-reset/task.yaml index d187f7c042f..37b3d0cd7cc 100644 --- a/tests/nested/core/core20-factory-reset/task.yaml +++ b/tests/nested/core/core20-factory-reset/task.yaml @@ -6,8 +6,8 @@ details: | systems: [ubuntu-20.04-64, ubuntu-22.04-64] environment: - NESTED_ENABLE_SECURE_BOOT: false - NESTED_ENABLE_TPM: false + NESTED_ENABLE_SECURE_BOOT: true + NESTED_ENABLE_TPM: true execute: | echo "Wait for the system to be seeded first" @@ -27,6 +27,9 @@ execute: | tests.nested exec sudo touch /run/mnt/ubuntu-boot/marker tests.nested exec sudo touch /writable/marker + # grab the ubuntu-save key + tests.nested exec cat /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key > pre-reset-save-fallback-key + # add || true in case the SSH connection is broken while executing this # since this command causes an immediate reboot tests.nested exec "sudo snap reboot --factory-reset" || true @@ -38,7 +41,7 @@ execute: | # wait for the system to get setup and finish seeding tests.nested wait-for snap-command - tests.nested exec "sudo snap wait system seed.loaded" + retry -n 10 --wait 2 tests.nested exec "sudo snap wait system seed.loaded" # wait up to two minutes for serial registration retry -n 60 --wait 2 tests.nested exec snap model --serial @@ -59,8 +62,8 @@ execute: | test "$old_ubuntu_seed" = "$new_ubuntu_seed" # check ubuntu-save - old_ubuntu_save="$(grep LABEL=\"ubuntu-save\" < initial-disk)" - new_ubuntu_save="$(grep LABEL=\"ubuntu-save\" < current-disk)" + old_ubuntu_save="$(grep ' LABEL="ubuntu-save-enc"' < initial-disk)" + new_ubuntu_save="$(grep ' LABEL="ubuntu-save-enc"' < current-disk)" # ubuntu save is identical test "$old_ubuntu_save" = "$new_ubuntu_save" @@ -73,8 +76,8 @@ execute: | test "$old_ubuntu_boot" != "$new_ubuntu_boot" # check ubuntu-data - old_ubuntu_data="$(grep LABEL=\"ubuntu-data\" < initial-disk)" - new_ubuntu_data="$(grep LABEL=\"ubuntu-data\" < current-disk)" + old_ubuntu_data="$(grep ' LABEL="ubuntu-data-enc"' < initial-disk)" + new_ubuntu_data="$(grep ' LABEL="ubuntu-data-enc"' < current-disk)" # again same device test "$(echo "$old_ubuntu_data" | cut -f1 -d:)" = "$(echo "$new_ubuntu_data" | cut -f1 -d:)" # again, the UUIDs are different @@ -87,5 +90,20 @@ execute: | tests.nested exec test -e /run/mnt/ubuntu-save/marker tests.nested exec test -e /run/mnt/ubuntu-seed/marker + # the temp factory-reset key is gone + # TODO enable those checks one cleanup lands + # TODO this is a very weak check + # tests.nested exec test ! -e /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset + # no factory reset marker + # tests.nested exec test ! -e /var/lib/snapd/device/factory-reset + # verify that the factory-reset log was collected tests.nested exec "zcat /var/log/factory-reset-mode.log.gz" | MATCH 'performing factory reset on an installed system' + + # TODO enable checks once the save fallback key is rotated + #tests.nested exec cat /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key > post-reset-save-fallback-key + # not a great check as the fallback key may have been resealed, but it + # should be different nonetheless + #not cmp pre-reset-save-fallback-key post-reset-save-fallback-key + + # TODO perform subsequent factory reset once post-factory reset cleanup lands From bb5bf047f44b9c1708633dab6fba9c9947012214 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 13 Jun 2022 12:50:36 +0200 Subject: [PATCH 073/153] tests: core20-4k-sector-size tests needs sudo on uc22 --- tests/nested/manual/core20-4k-sector-size/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nested/manual/core20-4k-sector-size/task.yaml b/tests/nested/manual/core20-4k-sector-size/task.yaml index e10521b26e5..73b68ed4933 100644 --- a/tests/nested/manual/core20-4k-sector-size/task.yaml +++ b/tests/nested/manual/core20-4k-sector-size/task.yaml @@ -36,7 +36,7 @@ execute: | tests.nested exec "! snap list test-snapd-sh" echo "Verifying tpm working on the nested vm" - tests.nested exec "dmesg | grep -i tpm" | MATCH "efi: +SMBIOS=.* +TPMFinalLog=.*" + tests.nested exec "sudo dmesg | grep -i tpm" | MATCH "efi: +SMBIOS=.* +TPMFinalLog=.*" tests.nested exec "test -e /sys/kernel/security/tpm0/binary_bios_measurements" echo "and secure boot is enabled on the nested vm" From 5914caee279857132ee4d7d6a3a7e38cfd970dce Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 10 Jun 2022 10:31:11 +0200 Subject: [PATCH 074/153] boot: helpers to obtain the name of fallback ubuntu-save sealed keys Signed-off-by: Maciej Borzecki --- boot/seal.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/boot/seal.go b/boot/seal.go index 391811f3872..f00c0501fdb 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -147,14 +147,25 @@ func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest { } } +// FallbackSaveSealedKeyUnder returns the name of a fallback ubuntu save key. +func FallbackSaveSealedKeyUnder(dir string) string { + return filepath.Join(dir, "ubuntu-save.recovery.sealed-key") +} + +// FactoryResetFallbackSaveSealedKeyUnder returns the name of a fallback ubuntu +// save key object generated during factory reset. +func FactoryResetFallbackSaveSealedKeyUnder(dir string) string { + return filepath.Join(dir, "ubuntu-save.recovery.sealed-key.factory-reset") +} + func fallbackKeySealRequests(key, saveKey keys.EncryptionKey, factoryReset bool) []secboot.SealKeyRequest { - saveFallbackKey := filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key") + saveFallbackKey := FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir) if factoryReset { // factory reset uses alternative sealed key location, such that // until we boot into the run mode, both sealed keys are present // on disk - saveFallbackKey = filepath.Join(InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset") + saveFallbackKey = FactoryResetFallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir) } return []secboot.SealKeyRequest{ { From 5dce1f31a755c0a1346e2f3d0c179d2bcd59eb2c Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 1 Jun 2022 15:44:21 +0200 Subject: [PATCH 075/153] boot: post factory reset cleanup Signed-off-by: Maciej Borzecki --- boot/boot.go | 13 +++++ boot/export_test.go | 6 +++ boot/seal.go | 58 +++++++++++++++++++++-- boot/seal_test.go | 113 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 5 deletions(-) diff --git a/boot/boot.go b/boot/boot.go index aaf21c137ca..2e10e566183 100644 --- a/boot/boot.go +++ b/boot/boot.go @@ -473,3 +473,16 @@ func UpdateCommandLineForGadgetComponent(dev snap.Device, gadgetSnapOrDir string } return cmdlineChange, nil } + +// CompleteFactoryReset runs a series of steps in a run system that complete a +// factory reset process. +func CompleteFactoryReset(encrypted bool) error { + if !encrypted { + // there is nothing to do on an unencrypted system + return nil + } + if err := postFactoryResetCleanup(); err != nil { + return fmt.Errorf("cannot perform boot cleanup: %v", err) + } + return nil +} diff --git a/boot/export_test.go b/boot/export_test.go index 3e65e7d1ca9..1d7494abe86 100644 --- a/boot/export_test.go +++ b/boot/export_test.go @@ -141,6 +141,12 @@ func MockSecbootPCRHandleOfSealedKey(f func(p string) (uint32, error)) (restore return restore } +func MockSecbootReleasePCRResourceHandles(f func(handles ...uint32) error) (restore func()) { + restore = testutil.Backup(&secbootReleasePCRResourceHandles) + secbootReleasePCRResourceHandles = f + return restore +} + func (o *TrustedAssetsUpdateObserver) InjectChangedAsset(blName, assetName, hash string, recovery bool) { ta := &trackedAsset{ blName: blName, diff --git a/boot/seal.go b/boot/seal.go index f00c0501fdb..56fa5c748ca 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -45,11 +45,12 @@ import ( ) var ( - secbootProvisionTPM = secboot.ProvisionTPM - secbootSealKeys = secboot.SealKeys - secbootSealKeysWithFDESetupHook = secboot.SealKeysWithFDESetupHook - secbootResealKeys = secboot.ResealKeys - secbootPCRHandleOfSealedKey = secboot.PCRHandleOfSealedKey + secbootProvisionTPM = secboot.ProvisionTPM + secbootSealKeys = secboot.SealKeys + secbootSealKeysWithFDESetupHook = secboot.SealKeysWithFDESetupHook + secbootResealKeys = secboot.ResealKeys + secbootPCRHandleOfSealedKey = secboot.PCRHandleOfSealedKey + secbootReleasePCRResourceHandles = secboot.ReleasePCRResourceHandles seedReadSystemEssential = seed.ReadSystemEssential ) @@ -894,3 +895,50 @@ func isResealNeeded(pbc predictableBootChains, bootChainsFile string, expectRese } return true, c + 1, nil } + +func postFactoryResetCleanupSecboot() error { + // we are inspecting a key which was generated during factory reset, in + // the simplest case the sealed key generated previously used the main + // handles, while the current key uses alt handles, hence we need to + // release the main handles corresponding to the old key + handles := []uint32{secboot.RunObjectPCRPolicyCounterHandle, secboot.FallbackObjectPCRPolicyCounterHandle} + usesAlt, err := usesAltPCRHandles() + if err != nil { + return fmt.Errorf("cannot inspect fallback key: %v", err) + } + if !usesAlt { + // current fallback key using the main handles, which is + // possible of there were subsequent factory reset steps, + // release the alt handles associated with the old key + handles = []uint32{secboot.AltRunObjectPCRPolicyCounterHandle, secboot.AltFallbackObjectPCRPolicyCounterHandle} + } + return secbootReleasePCRResourceHandles(handles...) +} + +func postFactoryResetCleanup() error { + hasHook, err := HasFDESetupHook() + if err != nil { + return fmt.Errorf("cannot check for fde-setup hook %v", err) + } + + saveFallbackKeyFactory := FactoryResetFallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir) + saveFallbackKey := FallbackSaveSealedKeyUnder(InitramfsSeedEncryptionKeyDir) + if err := os.Rename(saveFallbackKeyFactory, saveFallbackKey); err != nil { + // it is possible that the key file was already renamed if we + // came back here after an unexpected reboot + if !os.IsNotExist(err) { + return fmt.Errorf("cannot rotate fallback key: %v", err) + } + } + + if hasHook { + // TODO: do we need to invoke FDE hook? + return nil + } + + if err := postFactoryResetCleanupSecboot(); err != nil { + return fmt.Errorf("cannot cleanup secboot state: %v", err) + } + + return nil +} diff --git a/boot/seal_test.go b/boot/seal_test.go index 73fb93daa99..c3f53b4a567 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -2083,3 +2083,116 @@ func (s *sealSuite) TestResealKeyToModeenvWithTryModel(c *C) { }, }) } + +func (s *sealSuite) TestCompleteFactoryReset(c *C) { + + for i, tc := range []struct { + encrypted bool + factoryKeyAlreadyMigrated bool + pcrHandleOfKey uint32 + pcrHandleOfKeyErr error + pcrHandleOfKeyCalls int + releasePCRHandlesErr error + releasePCRHandleCalls int + hasFDEHook bool + err string + }{ + { + // unencrypted is a nop + encrypted: false, + }, { + // the old fallback key uses the main handle + encrypted: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, + factoryKeyAlreadyMigrated: true, pcrHandleOfKeyCalls: 1, releasePCRHandleCalls: 1, + }, { + // the old fallback key uses the alt handle + encrypted: true, pcrHandleOfKey: secboot.AltFallbackObjectPCRPolicyCounterHandle, + factoryKeyAlreadyMigrated: true, pcrHandleOfKeyCalls: 1, releasePCRHandleCalls: 1, + }, { + // unexpected reboot, the key file was already moved + encrypted: true, pcrHandleOfKey: secboot.AltFallbackObjectPCRPolicyCounterHandle, + pcrHandleOfKeyCalls: 1, releasePCRHandleCalls: 1, + }, { + // do nothing if we have the FDE hook + encrypted: true, pcrHandleOfKeyErr: errors.New("unexpected call"), + hasFDEHook: true, + }, + // error cases + { + encrypted: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, + factoryKeyAlreadyMigrated: true, + pcrHandleOfKeyCalls: 1, + pcrHandleOfKeyErr: errors.New("handle error"), + err: "cannot perform boot cleanup: cannot cleanup secboot state: cannot inspect fallback key: handle error", + }, { + encrypted: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, + factoryKeyAlreadyMigrated: true, + pcrHandleOfKeyCalls: 1, releasePCRHandleCalls: 1, + releasePCRHandlesErr: errors.New("release error"), + err: "cannot perform boot cleanup: cannot cleanup secboot state: release error", + }, + } { + c.Logf("tc %v", i) + + saveSealedKey := filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key") + saveSealedKeyByFactoryReset := filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset") + + if tc.encrypted { + c.Assert(os.MkdirAll(boot.InitramfsSeedEncryptionKeyDir, 0755), IsNil) + if tc.factoryKeyAlreadyMigrated { + c.Assert(ioutil.WriteFile(saveSealedKey, []byte{'o', 'l', 'd'}, 0644), IsNil) + c.Assert(ioutil.WriteFile(saveSealedKeyByFactoryReset, []byte{'n', 'e', 'w'}, 0644), IsNil) + } else { + c.Assert(ioutil.WriteFile(saveSealedKey, []byte{'n', 'e', 'w'}, 0644), IsNil) + } + } + + restore := boot.MockHasFDESetupHook(func() (bool, error) { + return tc.hasFDEHook, nil + }) + defer restore() + + pcrHandleOfKeyCalls := 0 + restore = boot.MockSecbootPCRHandleOfSealedKey(func(p string) (uint32, error) { + pcrHandleOfKeyCalls++ + // XXX we're inspecting the current key after it got rotated + c.Check(p, Equals, filepath.Join(dirs.GlobalRootDir, "/run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key")) + return tc.pcrHandleOfKey, tc.pcrHandleOfKeyErr + }) + defer restore() + + releasePCRHandleCalls := 0 + restore = boot.MockSecbootReleasePCRResourceHandles(func(handles ...uint32) error { + releasePCRHandleCalls++ + if tc.pcrHandleOfKey == secboot.FallbackObjectPCRPolicyCounterHandle { + c.Check(handles, DeepEquals, []uint32{ + secboot.AltRunObjectPCRPolicyCounterHandle, + secboot.AltFallbackObjectPCRPolicyCounterHandle, + }) + } else { + c.Check(handles, DeepEquals, []uint32{ + secboot.RunObjectPCRPolicyCounterHandle, + secboot.FallbackObjectPCRPolicyCounterHandle, + }) + } + return tc.releasePCRHandlesErr + }) + defer restore() + + err := boot.CompleteFactoryReset(tc.encrypted) + if tc.err != "" { + c.Assert(err, ErrorMatches, tc.err) + } else { + c.Assert(err, IsNil) + } + c.Check(pcrHandleOfKeyCalls, Equals, tc.pcrHandleOfKeyCalls) + c.Check(releasePCRHandleCalls, Equals, tc.releasePCRHandleCalls) + if tc.encrypted { + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + testutil.FileEquals, []byte{'n', 'e', 'w'}) + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + testutil.FileAbsent) + } + } + +} From e77ec130757f843b35ac06248f9744010935a0c3 Mon Sep 17 00:00:00 2001 From: Valentin David Date: Thu, 9 Jun 2022 10:52:08 +0200 Subject: [PATCH 076/153] gadget/install: do not assume dm device has same block size as disk --- gadget/install/install.go | 10 +++++++++- gadget/install/install_test.go | 22 +++++++++++++++++++--- osutil/disks/disks_darwin.go | 4 ++++ osutil/disks/disks_linux.go | 4 ++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/gadget/install/install.go b/gadget/install/install.go index 07583135cb9..be4ee4819c4 100644 --- a/gadget/install/install.go +++ b/gadget/install/install.go @@ -30,6 +30,7 @@ import ( "github.com/snapcore/snapd/boot" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget" + "github.com/snapcore/snapd/gadget/quantity" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil/disks" "github.com/snapcore/snapd/secboot" @@ -164,6 +165,8 @@ func Run(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options for _, part := range created { roleFmt := "" + fsSectorSize := diskLayout.SectorSize + if part.Role != "" { roleFmt = fmt.Sprintf("role %v", part.Role) } @@ -223,6 +226,11 @@ func Run(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options } keyForRole[part.Role] = encryptionKey logger.Noticef("encrypted device %v", part.Node) + fsSectorSizeInt, err := disks.SectorSize(part.Node) + if err != nil { + return nil, err + } + fsSectorSize = quantity.Size(fsSectorSizeInt) } // use the diskLayout.SectorSize here instead of lv.SectorSize, we check @@ -231,7 +239,7 @@ func Run(model gadget.Model, gadgetRoot, kernelRoot, bootDevice string, options // size specified in the gadget.yaml, but we will always have the sector // size from the physical disk device timings.Run(perfTimings, fmt.Sprintf("make-filesystem[%s]", roleOrLabelOrName(part)), fmt.Sprintf("Create filesystem for %s", part.Node), func(timings.Measurer) { - err = makeFilesystem(&part, diskLayout.SectorSize) + err = makeFilesystem(&part, fsSectorSize) }) if err != nil { return nil, fmt.Errorf("cannot make filesystem for partition %s: %v", roleOrLabelOrName(part), err) diff --git a/gadget/install/install_test.go b/gadget/install/install_test.go index 19e93eb77cb..826964f611f 100644 --- a/gadget/install/install_test.go +++ b/gadget/install/install_test.go @@ -153,6 +153,11 @@ func (s *installSuite) testInstall(c *C, opts installOpts) { mockCryptsetup := testutil.MockCommand(c, "cryptsetup", "") defer mockCryptsetup.Restore() + if opts.encryption { + mockBlockdev := testutil.MockCommand(c, "blockdev", "case ${1} in --getss) echo 4096; exit 0;; esac; exit 1") + defer mockBlockdev.Restore() + } + restore = install.MockEnsureNodesExist(func(dss []gadget.OnDiskStructure, timeout time.Duration) error { c.Assert(timeout, Equals, 5*time.Second) c.Assert(dss, DeepEquals, []gadget.OnDiskStructure{ @@ -257,22 +262,24 @@ func (s *installSuite) testInstall(c *C, opts installOpts) { c.Assert(typ, Equals, "ext4") if opts.encryption { c.Assert(img, Equals, "/dev/mapper/ubuntu-save") + c.Assert(sectorSize, Equals, quantity.Size(4096)) } else { c.Assert(img, Equals, "/dev/mmcblk0p3") + c.Assert(sectorSize, Equals, quantity.Size(512)) } c.Assert(label, Equals, "ubuntu-save") c.Assert(devSize, Equals, 16*quantity.SizeMiB) - c.Assert(sectorSize, Equals, quantity.Size(512)) case 3: c.Assert(typ, Equals, "ext4") if opts.encryption { c.Assert(img, Equals, "/dev/mapper/ubuntu-data") + c.Assert(sectorSize, Equals, quantity.Size(4096)) } else { c.Assert(img, Equals, "/dev/mmcblk0p4") + c.Assert(sectorSize, Equals, quantity.Size(512)) } c.Assert(label, Equals, "ubuntu-data") c.Assert(devSize, Equals, (30528-(1+1200+750+16))*quantity.SizeMiB) - c.Assert(sectorSize, Equals, quantity.Size(512)) default: c.Errorf("unexpected call (%d) to mkfs.Make()", mkfsCall) return fmt.Errorf("test broken") @@ -620,6 +627,11 @@ func (s *installSuite) testFactoryReset(c *C, opts factoryResetOpts) { mockCryptsetup := testutil.MockCommand(c, "cryptsetup", "") defer mockCryptsetup.Restore() + if opts.encryption { + mockBlockdev := testutil.MockCommand(c, "blockdev", "case ${1} in --getss) echo 4096; exit 0;; esac; exit 1") + defer mockBlockdev.Restore() + } + dataDev := "/dev/mmcblk0p4" if opts.noSave { dataDev = "/dev/mmcblk0p3" @@ -739,7 +751,11 @@ func (s *installSuite) testFactoryReset(c *C, opts factoryResetOpts) { } else { c.Assert(devSize, Equals, (30528-(1+1200+750+16))*quantity.SizeMiB) } - c.Assert(sectorSize, Equals, quantity.Size(512)) + if opts.encryption { + c.Assert(sectorSize, Equals, quantity.Size(4096)) + } else { + c.Assert(sectorSize, Equals, quantity.Size(512)) + } default: c.Errorf("unexpected call (%d) to mkfs.Make()", mkfsCall) return fmt.Errorf("test broken") diff --git a/osutil/disks/disks_darwin.go b/osutil/disks/disks_darwin.go index ed317aebb82..485440ebe30 100644 --- a/osutil/disks/disks_darwin.go +++ b/osutil/disks/disks_darwin.go @@ -74,3 +74,7 @@ func PartitionUUIDFromMountPoint(mountpoint string, opts *Options) (string, erro func PartitionUUID(node string) (string, error) { return "", osutil.ErrDarwin } + +func SectorSize(devname string) (uint64, error) { + return 0, osutil.ErrDarwin +} diff --git a/osutil/disks/disks_linux.go b/osutil/disks/disks_linux.go index d767108452c..40703c567b2 100644 --- a/osutil/disks/disks_linux.go +++ b/osutil/disks/disks_linux.go @@ -977,3 +977,7 @@ func PartitionUUID(node string) (string, error) { } return partUUID, nil } + +func SectorSize(devname string) (uint64, error) { + return blockDeviceSectorSize(devname) +} From 94c5f4e167552643aa306977b6099cc87e10f21c Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 13 Jun 2022 15:28:31 +0200 Subject: [PATCH 077/153] spread.yaml: add ubuntu-22.04-06 to qemu-nested --- spread.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spread.yaml b/spread.yaml index 0df75487aff..8ae704fa638 100644 --- a/spread.yaml +++ b/spread.yaml @@ -232,6 +232,9 @@ backends: - ubuntu-20.04-64: username: ubuntu password: ubuntu + - ubuntu-22.04-64: + username: ubuntu + password: ubuntu qemu: systems: From 8f2f4eca9b635c4aadc9903468cc9ef13f4682dd Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Mon, 13 Jun 2022 16:30:17 -0300 Subject: [PATCH 078/153] Skip interfaces-network-control test on i386 This is needed because network-control-consumer snap is core20 based now --- tests/main/interfaces-network-control/task.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/main/interfaces-network-control/task.yaml b/tests/main/interfaces-network-control/task.yaml index 7b4cd7673d0..cd0de72493d 100644 --- a/tests/main/interfaces-network-control/task.yaml +++ b/tests/main/interfaces-network-control/task.yaml @@ -12,7 +12,8 @@ details: | capability) and creates an arp entry (write capability). # ubuntu-14.04: systemd-run not supported -systems: [-fedora-*, -opensuse-*, -ubuntu-14.04*] +# ubuntu-18.04-32: network-control-consumer snap not available on i386 +systems: [-fedora-*, -opensuse-*, -ubuntu-14.04*, -ubuntu-18.04-32] environment: PORT: 8081 From 2f22f033be96b49c435a3ed7aaaa3b1b79ce7848 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 14 Jun 2022 11:09:33 +0200 Subject: [PATCH 079/153] o/devicestate: tweak factory reset marker field name Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicestate_install_mode_test.go | 2 +- overlord/devicestate/handlers_install.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go index 64c22b09539..2bfae3afdd6 100644 --- a/overlord/devicestate/devicestate_install_mode_test.go +++ b/overlord/devicestate/devicestate_install_mode_test.go @@ -2461,7 +2461,7 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts // sha3-384 of the mocked ubuntu-save sealed key c.Check(filepath.Join(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), "factory-reset"), testutil.FileEquals, - `{"fallback-save-key-hash":"d192153f0a50e826c6eb400c8711750ed0466571df1d151aaecc8c73095da7ec104318e7bf74d5e5ae2940827bf8402b"} + `{"fallback-save-key-sha3-384":"d192153f0a50e826c6eb400c8711750ed0466571df1d151aaecc8c73095da7ec104318e7bf74d5e5ae2940827bf8402b"} `) } else { c.Check(filepath.Join(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), "factory-reset"), diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index f3a952b8484..9638f26f427 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -1190,7 +1190,7 @@ func restoreDeviceSerialFromSave(model *asserts.Model) error { } type factoryResetMarker struct { - FallbackSaveKeyHash string `json:"fallback-save-key-hash,omitempty"` + FallbackSaveKeyHash string `json:"fallback-save-key-sha3-384,omitempty"` } func fileDigest(p string) (string, error) { From c2aedef2397c6d78a79fab02f57b6b5186a165f5 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 14 Jun 2022 11:11:43 +0200 Subject: [PATCH 080/153] o/devicestate: split and reuse some of the install code Signed-off-by: Maciej Borzecki --- overlord/devicestate/handlers_install.go | 162 +++++++++-------------- 1 file changed, 62 insertions(+), 100 deletions(-) diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index 9638f26f427..bb7be7171fb 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -337,31 +337,68 @@ func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error { } if trustedInstallObserver != nil { - // validity check - if len(installedSystem.KeyForRole) == 0 || installedSystem.KeyForRole[gadget.SystemData] == nil || installedSystem.KeyForRole[gadget.SystemSave] == nil { - return fmt.Errorf("internal error: system encryption keys are unset") + if err := prepareEncryptedSystemData(installedSystem.KeyForRole, trustedInstallObserver); err != nil { + return err } - dataEncryptionKey := installedSystem.KeyForRole[gadget.SystemData] - saveEncryptionKey := installedSystem.KeyForRole[gadget.SystemSave] + } - // make note of the encryption keys - trustedInstallObserver.ChosenEncryptionKeys(dataEncryptionKey, saveEncryptionKey) + if err := prepareRunSystemData(model, gadgetDir, perfTimings); err != nil { + return err + } - // keep track of recovery assets - if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil { - return fmt.Errorf("cannot observe existing trusted recovery assets: err") - } - if err := saveKeys(installedSystem.KeyForRole); err != nil { - return err - } - // write markers containing a secret to pair data and save - if err := writeMarkers(); err != nil { - return err - } + // make it bootable, which should be the final step in the process, as + // it effectively makes it possible to boot into run mode + logger.Noticef("make system runnable") + bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx) + if err != nil { + return fmt.Errorf("cannot get boot base info: %v", err) + } + recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem) + bootWith := &boot.BootableSet{ + Base: bootBaseInfo, + BasePath: bootBaseInfo.MountFile(), + Kernel: kernelInfo, + KernelPath: kernelInfo.MountFile(), + RecoverySystemDir: recoverySystemDir, + UnpackedGadgetDir: gadgetDir, + } + timings.Run(perfTimings, "boot-make-runnable", "Make target system runnable", func(timings.Measurer) { + err = bootMakeRunnable(deviceCtx.Model(), bootWith, trustedInstallObserver) + }) + if err != nil { + return fmt.Errorf("cannot make system runnable: %v", err) + } + return nil +} + +func prepareEncryptedSystemData(keyForRole map[string]keys.EncryptionKey, trustedInstallObserver *boot.TrustedAssetsInstallObserver) error { + // validity check + if len(keyForRole) == 0 || keyForRole[gadget.SystemData] == nil || keyForRole[gadget.SystemSave] == nil { + return fmt.Errorf("internal error: system encryption keys are unset") } + dataEncryptionKey := keyForRole[gadget.SystemData] + saveEncryptionKey := keyForRole[gadget.SystemSave] + + // make note of the encryption keys + trustedInstallObserver.ChosenEncryptionKeys(dataEncryptionKey, saveEncryptionKey) + // keep track of recovery assets + if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil { + return fmt.Errorf("cannot observe existing trusted recovery assets: err") + } + if err := saveKeys(keyForRole); err != nil { + return err + } + // write markers containing a secret to pair data and save + if err := writeMarkers(); err != nil { + return err + } + return nil +} + +func prepareRunSystemData(model *asserts.Model, gadgetDir string, perfTimings timings.Measurer) error { // keep track of the model we installed - err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755) + err := os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755) if err != nil { return fmt.Errorf("cannot store the model: %v", err) } @@ -399,29 +436,6 @@ func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error { if err := fixupWritableDefaultDirs(boot.InstallHostWritableDir); err != nil { return err } - - // make it bootable, which should be the final step in the process, as - // it effectively makes it possible to boot into run mode - logger.Noticef("make system runnable") - bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx) - if err != nil { - return fmt.Errorf("cannot get boot base info: %v", err) - } - recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem) - bootWith := &boot.BootableSet{ - Base: bootBaseInfo, - BasePath: bootBaseInfo.MountFile(), - Kernel: kernelInfo, - KernelPath: kernelInfo.MountFile(), - RecoverySystemDir: recoverySystemDir, - UnpackedGadgetDir: gadgetDir, - } - timings.Run(perfTimings, "boot-make-runnable", "Make target system runnable", func(timings.Measurer) { - err = bootMakeRunnable(deviceCtx.Model(), bootWith, trustedInstallObserver) - }) - if err != nil { - return fmt.Errorf("cannot make system runnable: %v", err) - } return nil } @@ -973,7 +987,7 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err } // new encryption key for save - key, err := keys.NewEncryptionKey() + saveEncryptionKey, err := keys.NewEncryptionKey() if err != nil { return fmt.Errorf("cannot create encryption key: %v", err) } @@ -983,61 +997,18 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err return fmt.Errorf("internal error: no system-save device") } - if err := secbootStageEncryptionKeyChange(saveNode, key); err != nil { + if err := secbootStageEncryptionKeyChange(saveNode, saveEncryptionKey); err != nil { return fmt.Errorf("cannot change encryption keys: %v", err) } + // keep track of the new ubuntu-save encryption key + installedSystem.KeyForRole[gadget.SystemSave] = saveEncryptionKey - installedSystem.KeyForRole[gadget.SystemSave] = key - - // sanity check - if len(installedSystem.KeyForRole) == 0 || installedSystem.KeyForRole[gadget.SystemData] == nil || installedSystem.KeyForRole[gadget.SystemSave] == nil { - return fmt.Errorf("internal error: system encryption keys are unset") - } - dataEncryptionKey := installedSystem.KeyForRole[gadget.SystemData] - saveEncryptionKey := installedSystem.KeyForRole[gadget.SystemSave] - - // make note of the encryption keys - trustedInstallObserver.ChosenEncryptionKeys(dataEncryptionKey, saveEncryptionKey) - - // keep track of recovery assets - if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil { - return fmt.Errorf("cannot observe existing trusted recovery assets: err") - } - if err := saveKeys(installedSystem.KeyForRole); err != nil { - return err - } - // write markers containing a secret to pair data and save - if err := writeMarkers(); err != nil { + if err := prepareEncryptedSystemData(installedSystem.KeyForRole, trustedInstallObserver); err != nil { return err } } - // TODO: splice into a common install/reset helper? - - // keep track of the model we installed - err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755) - if err != nil { - return fmt.Errorf("cannot store the model: %v", err) - } - err = writeModel(model, filepath.Join(boot.InitramfsUbuntuBootDir, "device/model")) - if err != nil { - return fmt.Errorf("cannot store the model: %v", err) - } - - // preserve systemd-timesyncd clock timestamp, so that RTC-less devices - // can start with a more recent time on the next boot - if err := writeTimesyncdClock(dirs.GlobalRootDir, boot.InstallHostWritableDir); err != nil { - return fmt.Errorf("cannot seed timesyncd clock: %v", err) - } - - // configure the run system - opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir} - // configure cloud init - setSysconfigCloudOptions(opts, gadgetDir, model) - timings.Run(perfTimings, "sysconfig-configure-target-system", "Configure target system", func(timings.Measurer) { - err = sysconfigConfigureTargetSystem(model, opts) - }) - if err != nil { + if err := prepareRunSystemData(model, gadgetDir, perfTimings); err != nil { return err } @@ -1045,15 +1016,6 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err return fmt.Errorf("cannot restore data from save: %v", err) } - // on some specific devices, we need to create these directories in - // _writable_defaults in order to allow the install-device hook to install - // some files there, this eventually will go away when we introduce a proper - // mechanism not using system-files to install files onto the root - // filesystem from the install-device hook - if err := fixupWritableDefaultDirs(boot.InstallHostWritableDir); err != nil { - return err - } - // make it bootable logger.Noticef("make system runnable") bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx) From 6baaae47a7426924afb9f68f59cec8707a523c7d Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 14 Jun 2022 12:12:56 +0200 Subject: [PATCH 081/153] boot: tweak error message Signed-off-by: Maciej Borzecki --- boot/boot.go | 2 +- boot/seal_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/boot/boot.go b/boot/boot.go index 2e10e566183..836f9c41401 100644 --- a/boot/boot.go +++ b/boot/boot.go @@ -482,7 +482,7 @@ func CompleteFactoryReset(encrypted bool) error { return nil } if err := postFactoryResetCleanup(); err != nil { - return fmt.Errorf("cannot perform boot cleanup: %v", err) + return fmt.Errorf("cannot perform post factory reset boot cleanup: %v", err) } return nil } diff --git a/boot/seal_test.go b/boot/seal_test.go index c3f53b4a567..eda740d8add 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -2123,13 +2123,13 @@ func (s *sealSuite) TestCompleteFactoryReset(c *C) { factoryKeyAlreadyMigrated: true, pcrHandleOfKeyCalls: 1, pcrHandleOfKeyErr: errors.New("handle error"), - err: "cannot perform boot cleanup: cannot cleanup secboot state: cannot inspect fallback key: handle error", + err: "cannot perform post factory reset boot cleanup: cannot cleanup secboot state: cannot inspect fallback key: handle error", }, { encrypted: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, factoryKeyAlreadyMigrated: true, pcrHandleOfKeyCalls: 1, releasePCRHandleCalls: 1, releasePCRHandlesErr: errors.New("release error"), - err: "cannot perform boot cleanup: cannot cleanup secboot state: release error", + err: "cannot perform post factory reset boot cleanup: cannot cleanup secboot state: release error", }, } { c.Logf("tc %v", i) From 0daafce9da3615971c5a768d957311b6d5dd9449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Tue, 14 Jun 2022 13:58:28 +0200 Subject: [PATCH 082/153] Support multiple extra validation sets in EnforcedValidationSets helper. --- .../assertstate/validation_set_tracking.go | 14 +++-- .../validation_set_tracking_test.go | 60 ++++++++++++++++++- overlord/hookstate/ctlcmd/services_test.go | 2 +- overlord/snapshotstate/snapshotstate_test.go | 2 +- overlord/snapstate/autorefresh_gating_test.go | 4 +- overlord/snapstate/autorefresh_test.go | 2 +- overlord/snapstate/handlers.go | 4 +- overlord/snapstate/handlers_download_test.go | 2 +- overlord/snapstate/handlers_prereq_test.go | 2 +- overlord/snapstate/handlers_rerefresh_test.go | 10 ++-- overlord/snapstate/refreshhints_test.go | 2 +- overlord/snapstate/snapstate.go | 4 +- overlord/snapstate/snapstate_install_test.go | 6 +- overlord/snapstate/snapstate_remove_test.go | 6 +- overlord/snapstate/snapstate_test.go | 2 +- overlord/snapstate/snapstate_update_test.go | 26 ++++---- overlord/snapstate/storehelpers.go | 12 ++-- 17 files changed, 110 insertions(+), 50 deletions(-) diff --git a/overlord/assertstate/validation_set_tracking.go b/overlord/assertstate/validation_set_tracking.go index 746649e86b8..682256e02dc 100644 --- a/overlord/assertstate/validation_set_tracking.go +++ b/overlord/assertstate/validation_set_tracking.go @@ -147,10 +147,10 @@ func ValidationSets(st *state.State) (map[string]*ValidationSetTracking, error) } // EnforcedValidationSets returns ValidationSets object with all currently tracked -// validation sets that are in enforcing mode. If extraVs is not nil then it is -// added to the returned set and replaces a validation set with same account/name -// in case one was tracked already. -func EnforcedValidationSets(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { +// validation sets that are in enforcing mode. If extraVss is not nil then they are +// added to the returned set and replaces validation sets with same account/name +// in case they were tracked already. +func EnforcedValidationSets(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { valsets, err := ValidationSets(st) if err != nil { return nil, err @@ -159,8 +159,10 @@ func EnforcedValidationSets(st *state.State, extraVs *asserts.ValidationSet) (*s db := DB(st) sets := snapasserts.NewValidationSets() - if extraVs != nil { + skip := make(map[string]bool, len(extraVss)) + for _, extraVs := range extraVss { sets.Add(extraVs) + skip[fmt.Sprintf("%s:%s", extraVs.AccountID(), extraVs.Name())] = true } for _, vs := range valsets { @@ -170,7 +172,7 @@ func EnforcedValidationSets(st *state.State, extraVs *asserts.ValidationSet) (*s // if extraVs matches an already enforced validation set, then skip that one, extraVs has been added // before the loop. - if extraVs != nil && extraVs.AccountID() == vs.AccountID && extraVs.Name() == vs.Name { + if skip[fmt.Sprintf("%s:%s", vs.AccountID, vs.Name)] { continue } diff --git a/overlord/assertstate/validation_set_tracking_test.go b/overlord/assertstate/validation_set_tracking_test.go index 87f77e638b8..7b1eb8e6c1d 100644 --- a/overlord/assertstate/validation_set_tracking_test.go +++ b/overlord/assertstate/validation_set_tracking_test.go @@ -247,7 +247,7 @@ func (s *validationSetTrackingSuite) TestEnforcedValidationSets(c *C) { vs3 := s.mockAssert(c, "baz", "5", "invalid") c.Assert(assertstate.Add(s.st, vs3), IsNil) - valsets, err := assertstate.EnforcedValidationSets(s.st, nil) + valsets, err := assertstate.EnforcedValidationSets(s.st) c.Assert(err, IsNil) // foo and bar are in conflict, use this as an indirect way of checking @@ -257,6 +257,64 @@ func (s *validationSetTrackingSuite) TestEnforcedValidationSets(c *C) { c.Check(err, ErrorMatches, `validation sets are in conflict:\n- cannot constrain snap "snap-b" as both invalid \(.*/bar\) and required at any revision \(.*/foo\)`) } +func (s *validationSetTrackingSuite) TestEnforcedValidationSetsWithExtraSets(c *C) { + s.st.Lock() + defer s.st.Unlock() + + tr := assertstate.ValidationSetTracking{ + AccountID: s.dev1acct.AccountID(), + Name: "foo", + Mode: assertstate.Enforce, + Current: 2, + } + assertstate.UpdateValidationSet(s.st, &tr) + + tr = assertstate.ValidationSetTracking{ + AccountID: s.dev1acct.AccountID(), + Name: "bar", + Mode: assertstate.Enforce, + PinnedAt: 1, + Current: 3, + } + assertstate.UpdateValidationSet(s.st, &tr) + + vs1 := s.mockAssert(c, "foo", "2", "invalid") + c.Assert(assertstate.Add(s.st, vs1), IsNil) + + vs2 := s.mockAssert(c, "bar", "1", "required") + c.Assert(assertstate.Add(s.st, vs2), IsNil) + + valsets, err := assertstate.EnforcedValidationSets(s.st) + c.Assert(err, IsNil) + + // foo and bar are in conflict, use this as an indirect way of checking that extra validation sets are considered by validation sets and + // resolve the conflict. + err = valsets.Conflict() + c.Check(err, ErrorMatches, `validation sets are in conflict:\n- cannot constrain snap "snap-b" as both invalid \(.*/foo\) and required at any revision \(.*/bar\)`) + + // extra validation set "foo" replaces vs from the state + extra1 := s.mockAssert(c, "foo", "9", "required") + valsets, err = assertstate.EnforcedValidationSets(s.st, extra1.(*asserts.ValidationSet)) + c.Assert(err, IsNil) + + err = valsets.Conflict() + c.Assert(err, IsNil) + + // extra validations set "baz" is not tracked, it augments computed validation sets (and creates a conflict) + extra2 := s.mockAssert(c, "baz", "9", "invalid") + valsets, err = assertstate.EnforcedValidationSets(s.st, extra1.(*asserts.ValidationSet), extra2.(*asserts.ValidationSet)) + c.Assert(err, IsNil) + err = valsets.Conflict() + c.Check(err, ErrorMatches, `validation sets are in conflict:\n- cannot constrain snap "snap-b" as both invalid \(.*/baz\) and required at any revision \(.*/foo\)`) + + // extra validations set "baz" is not tracked, it augments computed validation sets (no conflict this time) + extra2 = s.mockAssert(c, "baz", "9", "optional") + valsets, err = assertstate.EnforcedValidationSets(s.st, extra1.(*asserts.ValidationSet), extra2.(*asserts.ValidationSet)) + c.Assert(err, IsNil) + err = valsets.Conflict() + c.Assert(err, IsNil) +} + func (s *validationSetTrackingSuite) TestAddToValidationSetsHistory(c *C) { s.st.Lock() defer s.st.Unlock() diff --git a/overlord/hookstate/ctlcmd/services_test.go b/overlord/hookstate/ctlcmd/services_test.go index 0bddbc62d9b..8eb302ae065 100644 --- a/overlord/hookstate/ctlcmd/services_test.go +++ b/overlord/hookstate/ctlcmd/services_test.go @@ -203,7 +203,7 @@ func (s *servicectlSuite) SetUpTest(c *C) { s.st.Set("refresh-privacy-key", "privacy-key") s.AddCleanup(snapstatetest.UseFallbackDeviceModel()) - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restore) diff --git a/overlord/snapshotstate/snapshotstate_test.go b/overlord/snapshotstate/snapshotstate_test.go index 8836c2f2128..5a532d95215 100644 --- a/overlord/snapshotstate/snapshotstate_test.go +++ b/overlord/snapshotstate/snapshotstate_test.go @@ -70,7 +70,7 @@ func (s *snapshotSuite) SetUpTest(c *check.C) { dirs.SetRootDir(c.MkDir()) os.MkdirAll(dirs.SnapshotsDir, os.ModePerm) - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restore) diff --git a/overlord/snapstate/autorefresh_gating_test.go b/overlord/snapstate/autorefresh_gating_test.go index ded53146c67..a4cd88a77fa 100644 --- a/overlord/snapstate/autorefresh_gating_test.go +++ b/overlord/snapstate/autorefresh_gating_test.go @@ -86,7 +86,7 @@ func (s *autorefreshGatingSuite) SetUpTest(c *C) { snapstate.ReplaceStore(s.state, s.store) s.state.Set("refresh-privacy-key", "privacy-key") - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restore) @@ -2651,7 +2651,7 @@ func verifyPhasedAutorefreshTasks(c *C, tasks []*state.Task, expected []string) func (s *validationSetsSuite) TestAutoRefreshPhase1WithValidationSets(c *C) { var requiredRevision string - restoreEnforcedValidationSets := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restoreEnforcedValidationSets := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", diff --git a/overlord/snapstate/autorefresh_test.go b/overlord/snapstate/autorefresh_test.go index 9fef693ce35..d18da2b1a07 100644 --- a/overlord/snapstate/autorefresh_test.go +++ b/overlord/snapstate/autorefresh_test.go @@ -137,7 +137,7 @@ func (s *autoRefreshTestSuite) SetUpTest(c *C) { s.state.Set("refresh-privacy-key", "privacy-key") s.AddCleanup(snapstatetest.MockDeviceModel(DefaultModel())) - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restore) diff --git a/overlord/snapstate/handlers.go b/overlord/snapstate/handlers.go index c3d9885ac80..bac43ef861d 100644 --- a/overlord/snapstate/handlers.go +++ b/overlord/snapstate/handlers.go @@ -3666,7 +3666,7 @@ func (m *SnapManager) doConditionalAutoRefresh(t *state.Task, tomb *tomb.Tomb) e // of the refreshed snaps to their previous revisions to satisfy the restored // validation sets tracking. var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSnaps []string, fromChange string) ([]*state.TaskSet, error) { - enforcedSets, err := EnforcedValidationSets(st, nil) + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return nil, err } @@ -3700,7 +3700,7 @@ var maybeRestoreValidationSetsAndRevertSnaps = func(st *state.State, refreshedSn } // we need to fetch enforced sets again because of RestoreValidationSetsTracking. - enforcedSets, err = EnforcedValidationSets(st, nil) + enforcedSets, err = EnforcedValidationSets(st) if err != nil { return nil, err } diff --git a/overlord/snapstate/handlers_download_test.go b/overlord/snapstate/handlers_download_test.go index cc9e0c61d67..5505ac4e807 100644 --- a/overlord/snapstate/handlers_download_test.go +++ b/overlord/snapstate/handlers_download_test.go @@ -58,7 +58,7 @@ func (s *downloadSnapSuite) SetUpTest(c *C) { s.AddCleanup(snapstatetest.UseFallbackDeviceModel()) - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restore) diff --git a/overlord/snapstate/handlers_prereq_test.go b/overlord/snapstate/handlers_prereq_test.go index abb7a59044e..279fae7ef33 100644 --- a/overlord/snapstate/handlers_prereq_test.go +++ b/overlord/snapstate/handlers_prereq_test.go @@ -75,7 +75,7 @@ func (s *prereqSuite) SetUpTest(c *C) { }) s.AddCleanup(restoreInstallSize) - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restore) diff --git a/overlord/snapstate/handlers_rerefresh_test.go b/overlord/snapstate/handlers_rerefresh_test.go index 318f9846cc9..808be965fd5 100644 --- a/overlord/snapstate/handlers_rerefresh_test.go +++ b/overlord/snapstate/handlers_rerefresh_test.go @@ -376,7 +376,7 @@ func (s *reRefreshSuite) TestFilterReturnsFalseIfEpochEqualZero(c *C) { // validation-sets related tests func (s *refreshSuite) TestMaybeRestoreValidationSetsAndRevertSnaps(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) defer restore() @@ -394,7 +394,7 @@ func (s *refreshSuite) TestMaybeRestoreValidationSetsAndRevertSnaps(c *C) { func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertSnapsOneRevert(c *C) { var enforcedValidationSetsCalled int - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { enforcedValidationSetsCalled++ vs := snapasserts.NewValidationSets() @@ -531,7 +531,7 @@ func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertSnapsOneRev func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertNoSnapsRefreshed(c *C) { var enforcedValidationSetsCalled int - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { enforcedValidationSetsCalled++ vs := snapasserts.NewValidationSets() @@ -582,7 +582,7 @@ func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertNoSnapsRefr func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertJustValidationSetsRestore(c *C) { var enforcedValidationSetsCalled int - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { enforcedValidationSetsCalled++ vs := snapasserts.NewValidationSets() @@ -662,7 +662,7 @@ func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertJustValidat func (s *validationSetsSuite) TestMaybeRestoreValidationSetsAndRevertStillValid(c *C) { var enforcedValidationSetsCalled int - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { enforcedValidationSetsCalled++ vs := snapasserts.NewValidationSets() diff --git a/overlord/snapstate/refreshhints_test.go b/overlord/snapstate/refreshhints_test.go index f57e81064c2..d5a9a3097b0 100644 --- a/overlord/snapstate/refreshhints_test.go +++ b/overlord/snapstate/refreshhints_test.go @@ -115,7 +115,7 @@ func (s *refreshHintsTestSuite) SetUpTest(c *C) { restoreModel := snapstatetest.MockDeviceModel(DefaultModel()) s.AddCleanup(restoreModel) - restoreEnforcedValidationSets := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restoreEnforcedValidationSets := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restoreEnforcedValidationSets) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 1c553935631..e983212847e 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -2757,7 +2757,7 @@ func canRemove(st *state.State, si *snap.Info, snapst *SnapState, removeAll bool } // check if this snap is required by any validation set in enforcing mode - enforcedSets, err := EnforcedValidationSets(st, nil) + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return err } @@ -3593,7 +3593,7 @@ func MockOsutilCheckFreeSpace(mock func(path string, minSize uint64) error) (res return func() { osutilCheckFreeSpace = old } } -func MockEnforcedValidationSets(f func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error)) func() { +func MockEnforcedValidationSets(f func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error)) func() { osutil.MustBeTestBinary("mocking can be done only in tests") old := EnforcedValidationSets diff --git a/overlord/snapstate/snapstate_install_test.go b/overlord/snapstate/snapstate_install_test.go index bcca18bf82e..d2ba1cc87c1 100644 --- a/overlord/snapstate/snapstate_install_test.go +++ b/overlord/snapstate/snapstate_install_test.go @@ -3947,7 +3947,7 @@ func (s *validationSetsSuite) installSnapReferencedByValidationSet(c *C, presenc flags = &snapstate.Flags{} } - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -4052,7 +4052,7 @@ func (s *validationSetsSuite) TestInstallSnapReferencedByValidationSetWrongRevis } func (s *validationSetsSuite) installManySnapReferencedByValidationSet(c *C, snapOnePresence, snapOneRequiredRev, snapTwoPresence, snapTwoRequiredRev string) error { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() snapOne := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -4151,7 +4151,7 @@ func (s *validationSetsSuite) TestInstallManyRequiredRevisionForValidationSetOK( } func (s *validationSetsSuite) testInstallSnapRequiredByValidationSetWithBase(c *C, presenceForBase string) error { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", diff --git a/overlord/snapstate/snapstate_remove_test.go b/overlord/snapstate/snapstate_remove_test.go index c890def1bdb..72d0d930397 100644 --- a/overlord/snapstate/snapstate_remove_test.go +++ b/overlord/snapstate/snapstate_remove_test.go @@ -1765,7 +1765,7 @@ func (s *snapmgrTestSuite) TestRemoveKeepsGatingDataIfNotLastRevision(c *C) { } func (s *validationSetsSuite) removeSnapReferencedByValidationSet(c *C, presence string) error { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -1822,7 +1822,7 @@ func (s *validationSetsSuite) TestRemoveInvalidSnapOK(c *C) { } func (s *validationSetsSuite) TestRemoveSnapRequiredByValidationSetAtSpecificRevisionRefused(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -1875,7 +1875,7 @@ func (s *validationSetsSuite) TestRemoveSnapRequiredByValidationSetAtSpecificRev } func (s *validationSetsSuite) TestRemoveSnapRequiredByValidationSetAtSpecificRevisionNotActive(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index b222c412ce9..ab1141d9251 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -156,7 +156,7 @@ func (s *snapmgrBaseTest) SetUpTest(c *C) { snapstate.SnapServiceOptions = servicestate.SnapServiceOptions snapstate.EnsureSnapAbsentFromQuotaGroup = servicestate.EnsureSnapAbsentFromQuota - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { return nil, nil }) s.AddCleanup(restore) diff --git a/overlord/snapstate/snapstate_update_test.go b/overlord/snapstate/snapstate_update_test.go index 12bb6c28051..8b1b8c745b2 100644 --- a/overlord/snapstate/snapstate_update_test.go +++ b/overlord/snapstate/snapstate_update_test.go @@ -6340,7 +6340,7 @@ func findStrictlyOnePrereqTask(c *C, chg *state.Change) *state.Task { } func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationSetAlreadyAtRequiredRevision(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6379,7 +6379,7 @@ func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationSetAlreadyAtRequ } func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationRefreshToRequiredRevision(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6449,7 +6449,7 @@ func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationRefreshToRequire } func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationSetAnyRevision(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() // no revision specified someSnap := map[string]interface{}{ @@ -6518,7 +6518,7 @@ func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationSetAnyRevision(c } func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationSetAnyRevision(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() // no revision specified someSnap := map[string]interface{}{ @@ -6588,7 +6588,7 @@ func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationSetAny } func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationWithMatchingRevision(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6656,7 +6656,7 @@ func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationWithMa } func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationAlreadyAtRevisionNoop(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6702,7 +6702,7 @@ func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationAlread } func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationWrongRevisionError(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6742,7 +6742,7 @@ func (s *validationSetsSuite) TestUpdateToRevisionSnapRequiredByValidationWrongR // test that updating to a revision that is different than the revision required // by a validation set is possible if --ignore-validation flag is passed. func (s *validationSetsSuite) TestUpdateToWrongRevisionIgnoreValidation(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6817,7 +6817,7 @@ func (s *validationSetsSuite) TestUpdateToWrongRevisionIgnoreValidation(c *C) { } func (s *validationSetsSuite) TestUpdateManyRequiredByValidationSetAlreadyAtCorrectRevisionNoop(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6857,7 +6857,7 @@ func (s *validationSetsSuite) TestUpdateManyRequiredByValidationSetAlreadyAtCorr } func (s *validationSetsSuite) TestUpdateManyRequiredByValidationSetsCohortIgnored(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6923,7 +6923,7 @@ func (s *validationSetsSuite) TestUpdateManyRequiredByValidationSetsCohortIgnore } func (s *validationSetsSuite) TestUpdateManyRequiredByValidationSetIgnoreValidation(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -6989,7 +6989,7 @@ func (s *validationSetsSuite) TestUpdateManyRequiredByValidationSetIgnoreValidat } func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationSetAlreadyAtRequiredRevisionIgnoreValidationOK(c *C) { - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() someSnap := map[string]interface{}{ "id": "yOqKhntON3vR7kwEbVPsILm7bUViPDzx", @@ -7101,7 +7101,7 @@ func (s *validationSetsSuite) testUpdateManyValidationSetsPartialFailure(c *C) * } var enforcedValidationSetsCalls int - restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { vs := snapasserts.NewValidationSets() snap1 := map[string]interface{}{ "id": "aaqKhntON3vR7kwEbVPsILm7bUViPDzx", diff --git a/overlord/snapstate/storehelpers.go b/overlord/snapstate/storehelpers.go index 1d8a3902814..515d090c079 100644 --- a/overlord/snapstate/storehelpers.go +++ b/overlord/snapstate/storehelpers.go @@ -42,7 +42,7 @@ var currentSnaps = currentSnapsImpl // EnforcedValidationSets allows to hook getting of validation sets in enforce // mode into installation/refresh/removal of snaps. It gets hooked from // assertstate. -var EnforcedValidationSets func(st *state.State, extraVs *asserts.ValidationSet) (*snapasserts.ValidationSets, error) +var EnforcedValidationSets func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) func userIDForSnap(st *state.State, snapst *SnapState, fallbackUserID int) (int, error) { userID := snapst.UserID @@ -253,7 +253,7 @@ func installInfo(ctx context.Context, st *state.State, name string, revOpts *Rev var requiredValSets []string if !flags.IgnoreValidation { - enforcedSets, err := EnforcedValidationSets(st, nil) + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return store.SnapActionResult{}, err } @@ -339,7 +339,7 @@ func updateInfo(st *state.State, snapst *SnapState, opts *RevisionOptions, userI } if !flags.IgnoreValidation { - enforcedSets, err := EnforcedValidationSets(st, nil) + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return nil, err } @@ -468,7 +468,7 @@ func updateToRevisionInfo(st *state.State, snapst *SnapState, revision snap.Revi var storeFlags store.SnapActionFlags if !flags.IgnoreValidation { - enforcedSets, err := EnforcedValidationSets(st, nil) + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return nil, err } @@ -604,7 +604,7 @@ func refreshCandidates(ctx context.Context, st *state.State, names []string, use ignoreValidationByInstanceName := make(map[string]bool) nCands := 0 - enforcedSets, err := EnforcedValidationSets(st, nil) + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return nil, nil, nil, err } @@ -742,7 +742,7 @@ func installCandidates(st *state.State, names []string, channel string, user *au return nil, err } - enforcedSets, err := EnforcedValidationSets(st, nil) + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return nil, err } From e342c09115d9b30a5f675bbed9faf6857d94d17f Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Tue, 14 Jun 2022 13:52:24 -0300 Subject: [PATCH 083/153] Add support for uc22 in listing test This to make the test work on external backend --- tests/main/listing/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/listing/task.yaml b/tests/main/listing/task.yaml index 2e4a14f43ed..0cc05f4dbe8 100644 --- a/tests/main/listing/task.yaml +++ b/tests/main/listing/task.yaml @@ -54,7 +54,7 @@ execute: | echo "On the external device the core snap tested could be in any track" TRACKING="(latest/)?(edge|beta|candidate|stable)" - elif [ "$SPREAD_BACKEND" = "external" ] && { os.query is-core18 || os.query is-core20; }; then + elif [ "$SPREAD_BACKEND" = "external" ] && { os.query is-core18 || os.query is-core20 || os.query is-core22; }; then echo "On the external device the snapd snap tested could be in any track" NAME=snapd VERSION=$SNAPD_GIT_VERSION From e849f1045ab9a36834f200ab332ce10690884d34 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Tue, 14 Jun 2022 17:55:47 -0300 Subject: [PATCH 084/153] Enable mount-order-regression test for arm devices This change includes: . new snaps for arm64 and armhf . updated the test to support the new architectures --- .../mount-order-regression/task.yaml | 52 ++++++++++++++----- .../bin/cmd | 0 .../meta/snap.yaml | 2 +- .../bin/cmd | 2 + .../meta/snap.yaml | 37 +++++++++++++ .../bin/cmd | 2 + .../meta/snap.yaml | 37 +++++++++++++ 7 files changed, 118 insertions(+), 14 deletions(-) rename tests/regression/mount-order-regression/{test-content-layout-consumer => test-content-layout-consumer-amd64}/bin/cmd (100%) rename tests/regression/mount-order-regression/{test-content-layout-consumer => test-content-layout-consumer-amd64}/meta/snap.yaml (95%) create mode 100755 tests/regression/mount-order-regression/test-content-layout-consumer-arm64/bin/cmd create mode 100644 tests/regression/mount-order-regression/test-content-layout-consumer-arm64/meta/snap.yaml create mode 100755 tests/regression/mount-order-regression/test-content-layout-consumer-armhf/bin/cmd create mode 100644 tests/regression/mount-order-regression/test-content-layout-consumer-armhf/meta/snap.yaml diff --git a/tests/regression/mount-order-regression/task.yaml b/tests/regression/mount-order-regression/task.yaml index 8e4594d46f6..ae8007f0673 100644 --- a/tests/regression/mount-order-regression/task.yaml +++ b/tests/regression/mount-order-regression/task.yaml @@ -11,17 +11,23 @@ systems: - -ubuntu-core-16-32 - -ubuntu-core-18-32 -environment: - SNAP_NAME: test-content-layout-consumer - prepare: | + if os.query is-arm64; then + SNAP_NAME=test-content-layout-consumer-arm64 + elif os.query is-armhf; then + SNAP_NAME=test-content-layout-consumer-armhf + else + SNAP_NAME=test-content-layout-consumer-amd64 + fi + # Create mount points for the content interface (we would like to have them # in the repository, but git does not allow storing empty directories) mkdir -p "$SNAP_NAME/gnome-platform" mkdir -p "$SNAP_NAME/data-dir/themes" mkdir -p "$SNAP_NAME/data-dir/icons" mkdir -p "$SNAP_NAME/data-dir/sounds" - "$TESTSTOOLS"/snaps-state install-local $SNAP_NAME + + "$TESTSTOOLS"/snaps-state install-local "$SNAP_NAME" execute: | check_non_empty() { @@ -30,6 +36,17 @@ execute: | test "$($SNAP_NAME.cmd ls -1 "$DIR" 2> /dev/null | wc -l)" -gt 0 } + if os.query is-arm64; then + SNAP_NAME=test-content-layout-consumer-arm64 + LINUX_GNU_NAME=aarch64-linux-gnu + elif os.query is-armhf; then + SNAP_NAME=test-content-layout-consumer-armhf + LINUX_GNU_NAME=arm-linux-gnueabihf + else + SNAP_NAME=test-content-layout-consumer-amd64 + LINUX_GNU_NAME=x86_64-linux-gnu + fi + # Check a handful of directories that should have been bind-mounted from # the content snap (all those starting with "$SNAP/"), plus the directories # that have been added via the layouts. @@ -40,12 +57,12 @@ execute: | done <<-EOF $SNAP/gnome-platform/etc/X11/Xsession.d $SNAP/gnome-platform/lib/udev/rules.d - $SNAP/gnome-platform/lib/x86_64-linux-gnu + $SNAP/gnome-platform/lib/$LINUX_GNU_NAME $SNAP/gnome-platform/usr/lib/python3/dist-packages/gi/overrides $SNAP/gnome-platform/usr/lib/systemd/user - $SNAP/gnome-platform/usr/lib/x86_64-linux-gnu/gtk-3.0/3.0.0/immodules - $SNAP/gnome-platform/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0 - $SNAP/gnome-platform/usr/lib/x86_64-linux-gnu/webkit2gtk-4.0/injected-bundle + $SNAP/gnome-platform/usr/lib/$LINUX_GNU_NAME/gtk-3.0/3.0.0/immodules + $SNAP/gnome-platform/usr/lib/$LINUX_GNU_NAME/webkit2gtk-4.0 + $SNAP/gnome-platform/usr/lib/$LINUX_GNU_NAME/webkit2gtk-4.0/injected-bundle $SNAP/gnome-platform/usr/share/X11/locale/C $SNAP/gnome-platform/usr/share/glib-2.0/schemas $SNAP/gnome-platform/usr/share/gnome-control-center @@ -54,12 +71,21 @@ execute: | $SNAP/data-dir/themes/Yaru/gtk-3.0 $SNAP/data-dir/icons $SNAP/data-dir/sounds - /usr/lib/x86_64-linux-gnu/webkit2gtk-4.0 - /usr/lib/x86_64-linux-gnu/webkit2gtk-4.0/injected-bundle - /usr/share/xml/iso-codes EOF - # We already know that /lib/x86_64-linux-gnu is not empty, since it comes + if os.query is-amd64; then + check_non_empty /usr/lib/$LINUX_GNU_NAME/webkit2gtk-4.0 + check_non_empty/usr/lib/$LINUX_GNU_NAME/webkit2gtk-4.0/injected-bundle + check_non_empty /usr/share/xml/iso-codes + fi + + # We already know that /lib/$LINUX_GNU_NAME is not empty, since it comes # from the base snap; so, here we check the existence of a specific file we # bind-mounted using the layout directive. - $SNAP_NAME.cmd test -f /lib/x86_64-linux-gnu/bindtextdomain.so + if os.query is-arm64; then + $SNAP_NAME.cmd test -f /lib/$LINUX_GNU_NAME/libSegFault.so + elif os.query is-armhf; then + $SNAP_NAME.cmd test -f /lib/$LINUX_GNU_NAME/libSegFault.so + else + $SNAP_NAME.cmd test -f /lib/$LINUX_GNU_NAME/bindtextdomain.so + fi diff --git a/tests/regression/mount-order-regression/test-content-layout-consumer/bin/cmd b/tests/regression/mount-order-regression/test-content-layout-consumer-amd64/bin/cmd similarity index 100% rename from tests/regression/mount-order-regression/test-content-layout-consumer/bin/cmd rename to tests/regression/mount-order-regression/test-content-layout-consumer-amd64/bin/cmd diff --git a/tests/regression/mount-order-regression/test-content-layout-consumer/meta/snap.yaml b/tests/regression/mount-order-regression/test-content-layout-consumer-amd64/meta/snap.yaml similarity index 95% rename from tests/regression/mount-order-regression/test-content-layout-consumer/meta/snap.yaml rename to tests/regression/mount-order-regression/test-content-layout-consumer-amd64/meta/snap.yaml index 70b4b5d98ec..44321ff7aa9 100644 --- a/tests/regression/mount-order-regression/test-content-layout-consumer/meta/snap.yaml +++ b/tests/regression/mount-order-regression/test-content-layout-consumer-amd64/meta/snap.yaml @@ -1,4 +1,4 @@ -name: test-content-layout-consumer +name: test-content-layout-consumer-amd64 version: 1.0 apps: cmd: diff --git a/tests/regression/mount-order-regression/test-content-layout-consumer-arm64/bin/cmd b/tests/regression/mount-order-regression/test-content-layout-consumer-arm64/bin/cmd new file mode 100755 index 00000000000..ee708187ee6 --- /dev/null +++ b/tests/regression/mount-order-regression/test-content-layout-consumer-arm64/bin/cmd @@ -0,0 +1,2 @@ +#! /bin/sh +exec "$@" diff --git a/tests/regression/mount-order-regression/test-content-layout-consumer-arm64/meta/snap.yaml b/tests/regression/mount-order-regression/test-content-layout-consumer-arm64/meta/snap.yaml new file mode 100644 index 00000000000..c85e5c7db29 --- /dev/null +++ b/tests/regression/mount-order-regression/test-content-layout-consumer-arm64/meta/snap.yaml @@ -0,0 +1,37 @@ +name: test-content-layout-consumer-arm64 +version: 1.0 +apps: + cmd: + command: bin/cmd + plugs: + - desktop + - desktop-legacy + - gsettings + - opengl + - wayland + - x11 + - home +base: core18 + +layout: + /lib/aarch64-linux-gnu/libSegFault.so: + bind-file: $SNAP/gnome-platform/lib/aarch64-linux-gnu/libSegFault.so + /usr/lib/x86_64-linux-gnu/libSegFault.so: + bind: $SNAP/gnome-platform/usr/lib/aarch64-linux-gnu/libSegFault.so +plugs: + gnome-3-38-2004: + interface: content + target: $SNAP/gnome-platform + default-provider: gnome-3-38-2004 + gtk-3-themes: + interface: content + target: $SNAP/data-dir/themes + default-provider: gtk-common-themes + icon-themes: + interface: content + target: $SNAP/data-dir/icons + default-provider: gtk-common-themes + sound-themes: + interface: content + target: $SNAP/data-dir/sounds + default-provider: gtk-common-themes diff --git a/tests/regression/mount-order-regression/test-content-layout-consumer-armhf/bin/cmd b/tests/regression/mount-order-regression/test-content-layout-consumer-armhf/bin/cmd new file mode 100755 index 00000000000..ee708187ee6 --- /dev/null +++ b/tests/regression/mount-order-regression/test-content-layout-consumer-armhf/bin/cmd @@ -0,0 +1,2 @@ +#! /bin/sh +exec "$@" diff --git a/tests/regression/mount-order-regression/test-content-layout-consumer-armhf/meta/snap.yaml b/tests/regression/mount-order-regression/test-content-layout-consumer-armhf/meta/snap.yaml new file mode 100644 index 00000000000..33aed83f681 --- /dev/null +++ b/tests/regression/mount-order-regression/test-content-layout-consumer-armhf/meta/snap.yaml @@ -0,0 +1,37 @@ +name: test-content-layout-consumer-armhf +version: 1.0 +apps: + cmd: + command: bin/cmd + plugs: + - desktop + - desktop-legacy + - gsettings + - opengl + - wayland + - x11 + - home +base: core18 + +layout: + /lib/arm-linux-gnueabihf/libSegFault.so: + bind-file: $SNAP/gnome-platform/lib/arm-linux-gnueabihf/libSegFault.so + /usr/lib/arm-linux-gnueabihf/libSegFault.so: + bind: $SNAP/gnome-platform/usr/lib/arm-linux-gnueabihf/libSegFault.so +plugs: + gnome-3-38-2004: + interface: content + target: $SNAP/gnome-platform + default-provider: gnome-3-38-2004 + gtk-3-themes: + interface: content + target: $SNAP/data-dir/themes + default-provider: gtk-common-themes + icon-themes: + interface: content + target: $SNAP/data-dir/icons + default-provider: gtk-common-themes + sound-themes: + interface: content + target: $SNAP/data-dir/sounds + default-provider: gtk-common-themes From 69660dedce1ba0e51d6ae7b5d6590cabf6494532 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 15 Jun 2022 14:14:44 +0930 Subject: [PATCH 085/153] cmd/snapd-apparmor: Simplify logic in isContainerWithInternalPolicy() Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index 4f34c50457a..d90915be79b 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -84,12 +84,6 @@ func isContainerWithInternalPolicy() bool { return true } - for _, path := range []string{nsStackedPath, nsNamePath} { - if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { - return false - } - } - contents, err := ioutil.ReadFile(nsStackedPath) if err != nil { return false From 48b68d4ffd7deabf59680272a26ecebd7d564697 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 15 Jun 2022 14:14:59 +0930 Subject: [PATCH 086/153] cmd/snapd-apparmor: Log errors in isContainerWithInternalPolicy() Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index d90915be79b..1bbb74fb60e 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -85,7 +85,8 @@ func isContainerWithInternalPolicy() bool { } contents, err := ioutil.ReadFile(nsStackedPath) - if err != nil { + if err != nil && !errors.Is(err, os.ErrNotExist) { + logger.Noticef("Failed to read %s: %v", nsStackedPath, err) return false } @@ -94,7 +95,8 @@ func isContainerWithInternalPolicy() bool { } contents, err = ioutil.ReadFile(nsNamePath) - if err != nil { + if err != nil && !errors.Is(err, os.ErrNotExist) { + logger.Noticef("Failed to read %s: %v", nsNamePath, err) return false } From d4eb44729700b02090d14df80e1eeae3f73b0d19 Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 15 Jun 2022 14:16:52 +0930 Subject: [PATCH 087/153] cmd/snapd-apparmor: Remove redundant error checks Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main.go | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index 1bbb74fb60e..389da2cbdb1 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -130,17 +130,20 @@ func loadAppArmorProfiles() error { return nil } logger.Noticef("Loading profiles %v", profiles) - err = apparmor_sandbox.LoadProfiles(profiles, apparmor_sandbox.SystemCacheDir, 0) - if err != nil { - return err - } - return nil + return apparmor_sandbox.LoadProfiles(profiles, apparmor_sandbox.SystemCacheDir, 0) } func isContainer() bool { return (exec.Command("systemd-detect-virt", "--quiet", "--container").Run() == nil) } +func validateArgs(args []string) error { + if len(args) != 1 || args[0] != "start" { + return errors.New("Expected to be called with a single 'start' argument.") + } + return nil +} + func main() { if err := run(); err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) @@ -166,17 +169,5 @@ func run() error { } } - err := loadAppArmorProfiles() - if err != nil { - return err - } - - return nil -} - -func validateArgs(args []string) error { - if len(args) != 1 || args[0] != "start" { - return errors.New("Expected to be called with a single 'start' argument.") - } - return nil + return loadAppArmorProfiles() } From 09473d4fa37f0deff068707e89e880f2b6ba543c Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 15 Jun 2022 14:17:37 +0930 Subject: [PATCH 088/153] cmd/snapd-apparmor/tests: Simplify some tests via ioutil.WriteFile() Also test a few more error conditions and different possible behaviours of systemd-detect-virt Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main_test.go | 45 ++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index 0d8b09aa043..217e647ee80 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -21,6 +21,7 @@ package main_test import ( "fmt" + "io/ioutil" "os" "path/filepath" "testing" @@ -43,7 +44,6 @@ var _ = Suite(&mainSuite{}) func (s *mainSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) - } func (s *mainSuite) TearDownTest(c *C) { @@ -51,6 +51,8 @@ func (s *mainSuite) TearDownTest(c *C) { } func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { + // since "apparmorfs" is not present within our test root dir setup + // we expect this to return false c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) appArmorSecurityFSPath := filepath.Join(dirs.GlobalRootDir, "/sys/kernel/security/apparmor/") @@ -67,22 +69,20 @@ func (s *mainSuite) TestIsContainerWithInternalPolicy(c *C) { testutil.MockCommand(c, "systemd-detect-virt", "echo lxc") c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) - f, err := os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_stacked")) + err = ioutil.WriteFile(filepath.Join(appArmorSecurityFSPath, ".ns_stacked"), []byte("yes"), 0644) c.Assert(err, IsNil) - f.WriteString("yes") - f.Close() c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) - f, err = os.Create(filepath.Join(appArmorSecurityFSPath, ".ns_name")) + err = ioutil.WriteFile(filepath.Join(appArmorSecurityFSPath, ".ns_name"), nil, 0644) c.Assert(err, IsNil) - defer f.Close() c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) - f.WriteString("foo") + err = ioutil.WriteFile(filepath.Join(appArmorSecurityFSPath, ".ns_name"), []byte("foo"), 0644) + c.Assert(err, IsNil) c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, false) // lxc/lxd name should result in a container with internal policy - f.Seek(0, 0) - f.WriteString("lxc-foo") + err = ioutil.WriteFile(filepath.Join(appArmorSecurityFSPath, ".ns_name"), []byte("lxc-foo"), 0644) + c.Assert(err, IsNil) c.Assert(snapd_apparmor.IsContainerWithInternalPolicy(), Equals, true) } @@ -92,16 +92,15 @@ func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { err := snapd_apparmor.LoadAppArmorProfiles() c.Assert(err, IsNil) // since no profiles to load the parser should not have been called - c.Assert(parserCmd.Calls(), DeepEquals, [][]string(nil)) + c.Assert(parserCmd.Calls(), HasLen, 0) // mock a profile err = os.MkdirAll(dirs.SnapAppArmorDir, 0755) c.Assert(err, IsNil) profile := filepath.Join(dirs.SnapAppArmorDir, "foo") - f, err := os.Create(profile) + err = ioutil.WriteFile(profile, nil, 0644) c.Assert(err, IsNil) - f.Close() // ensure SNAPD_DEBUG is set in the environment so then --quiet // will *not* be included in the apparmor_parser arguments (since @@ -118,26 +117,38 @@ func (s *mainSuite) TestLoadAppArmorProfiles(c *C) { profile}}) // test error case - testutil.MockCommand(c, "apparmor_parser", "exit 1") + testutil.MockCommand(c, "apparmor_parser", "echo mocked parser failed > /dev/stderr; exit 1") err = snapd_apparmor.LoadAppArmorProfiles() - c.Check(err.Error(), Equals, fmt.Sprintf("cannot load apparmor profiles: exit status 1\napparmor_parser output:\n")) + c.Check(err.Error(), Equals, fmt.Sprintf("cannot load apparmor profiles: exit status 1\napparmor_parser output:\nmocked parser failed\n")) // rename so file is ignored err = os.Rename(profile, profile+"~") c.Assert(err, IsNil) + // forget previous calls so we can check below that as a result of + // having no profiles again that no invocation of the parser occurs + parserCmd.ForgetCalls() err = snapd_apparmor.LoadAppArmorProfiles() c.Assert(err, IsNil) + c.Assert(parserCmd.Calls(), HasLen, 0) } func (s *mainSuite) TestIsContainer(c *C) { - c.Check(snapd_apparmor.IsContainer(), Equals, false) - - detectCmd := testutil.MockCommand(c, "systemd-detect-virt", "") + detectCmd := testutil.MockCommand(c, "systemd-detect-virt", "exit 1") defer detectCmd.Restore() + c.Check(snapd_apparmor.IsContainer(), Equals, false) + c.Assert(detectCmd.Calls(), DeepEquals, [][]string{ + {"systemd-detect-virt", "--quiet", "--container"}}) + detectCmd = testutil.MockCommand(c, "systemd-detect-virt", "") c.Check(snapd_apparmor.IsContainer(), Equals, true) c.Assert(detectCmd.Calls(), DeepEquals, [][]string{ {"systemd-detect-virt", "--quiet", "--container"}}) + + // test error cases too + detectCmd = testutil.MockCommand(c, "systemd-detect-virt", "echo failed > /dev/stderr; exit 1") + c.Check(snapd_apparmor.IsContainer(), Equals, false) + c.Assert(detectCmd.Calls(), DeepEquals, [][]string{ + {"systemd-detect-virt", "--quiet", "--container"}}) } func (s *mainSuite) TestValidateArgs(c *C) { From 11187577a17d502f13416ae397a3f4d6edd8e48e Mon Sep 17 00:00:00 2001 From: Alex Murray Date: Wed, 15 Jun 2022 14:33:30 +0930 Subject: [PATCH 089/153] cmd/snapd-apparmor: Annotate any err from Glob() of SnapAppArmorDir Signed-off-by: Alex Murray --- cmd/snapd-apparmor/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index 389da2cbdb1..bb19138193d 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -113,6 +113,7 @@ func isContainerWithInternalPolicy() bool { func loadAppArmorProfiles() error { candidates, err := filepath.Glob(dirs.SnapAppArmorDir + "/*") if err != nil { + err = fmt.Errorf("Failed to glob profiles from snap apparmor dir %s: %v", dirs.SnapAppArmorDir, err) return err } From e593302bd604e59c1065e62b588358a12b072c6c Mon Sep 17 00:00:00 2001 From: alfonsosanchezbeato Date: Wed, 15 Jun 2022 09:09:02 +0200 Subject: [PATCH 090/153] gadget: check also mbr type when testing for implicit data partition * gadget: check also mbr type when testing for implicit data partition When testing if a gadget defines implicitly the system data partition (which can happen for UC16 and UC18), we were checking the type of the on-disk partition to determine if it was the system data partition. However, we were testing only if it matched the GPT UUID partition type, and we were not considering the MBR disk case, which uses different numbers. Fixes LP:#1978127. * gadget/update_test.go: remove left-over comment --- gadget/gadgettest/examples.go | 82 +++++++++++++++++++++++++++++++++++ gadget/update.go | 2 +- gadget/update_test.go | 25 +++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/gadget/gadgettest/examples.go b/gadget/gadgettest/examples.go index cb68186ee3f..8a52f6a4a49 100644 --- a/gadget/gadgettest/examples.go +++ b/gadget/gadgettest/examples.go @@ -85,6 +85,19 @@ volumes: type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 ` +// from UC18 image, for testing the implicit system data partition case +const RaspiUC18SimplifiedYaml = ` +volumes: + pi: + schema: mbr + bootloader: u-boot + structure: + - type: 0C + filesystem: vfat + filesystem-label: system-boot + size: 256M +` + var expPiSeedStructureTraits = gadget.DiskStructureDeviceTraits{ OriginalDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p1", OriginalKernelPath: "/dev/mmcblk0p1", @@ -225,6 +238,33 @@ var ExpectedLUKSEncryptedRaspiDiskVolumeDeviceTraits = gadget.DiskVolumeDeviceTr }, } +// ExpectedRaspiUC18DiskVolumeDeviceTraits, for testing the implicit system +// data partition case +var ExpectedRaspiUC18DiskVolumeDeviceTraits = gadget.DiskVolumeDeviceTraits{ + OriginalDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0", + OriginalKernelPath: "/dev/mmcblk0", + DiskID: "7c301cbd", + Size: 32010928128, + SectorSize: 512, + Schema: "dos", + Structure: []gadget.DiskStructureDeviceTraits{ + { + OriginalDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p1", + OriginalKernelPath: "/dev/mmcblk0p1", + PartitionUUID: "7c301cbd-01", + PartitionType: "0C", + PartitionLabel: "", + FilesystemUUID: "23F9-881F", + FilesystemLabel: "system-boot", + FilesystemType: "vfat", + Offset: quantity.OffsetMiB, + Size: 256 * quantity.SizeMiB, + }, + // note no writable structure here - since it's not in the YAML, we + // don't save it in the traits either + }, +} + var mockSeedPartition = disks.Partition{ PartitionUUID: "7c301cbd-01", PartitionType: "0C", @@ -393,6 +433,48 @@ var ExpectedRaspiMockDiskInstallModeMapping = &disks.MockDiskMapping{ }, } +// ExpectedRaspiUC18MockDiskMapping, for testing the implicit system data partition case +var ExpectedRaspiUC18MockDiskMapping = &disks.MockDiskMapping{ + DevNode: "/dev/mmcblk0", + DevPath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0", + DevNum: "179:0", + DiskUsableSectorEnd: 30528 * oneMeg / 512, + DiskSizeInBytes: 30528 * oneMeg, + SectorSizeBytes: 512, + DiskSchema: "dos", + ID: "7c301cbd", + Structure: []disks.Partition{ + { + PartitionUUID: "7c301cbd-01", + PartitionType: "0C", + FilesystemLabel: "system-boot", + FilesystemUUID: "23F9-881F", + FilesystemType: "vfat", + Major: 179, + Minor: 1, + KernelDeviceNode: "/dev/mmcblk0p1", + KernelDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p1", + DiskIndex: 1, + StartInBytes: oneMeg, + SizeInBytes: 256 * oneMeg, + }, + { + PartitionUUID: "7c301cbd-02", + PartitionType: "83", + FilesystemLabel: "writable", + FilesystemUUID: "cba2b8b3-c2e4-4e51-9a57-d35041b7bf9a", + FilesystemType: "ext4", + Major: 179, + Minor: 2, + KernelDeviceNode: "/dev/mmcblk0p2", + KernelDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p2", + DiskIndex: 2, + StartInBytes: (1 + 256) * oneMeg, + SizeInBytes: 32270 * oneMeg, + }, + }, +} + const ExpectedRaspiDiskVolumeDeviceTraitsJSON = ` { "pi": { diff --git a/gadget/update.go b/gadget/update.go index 3e16940f74d..c01e47a2fff 100644 --- a/gadget/update.go +++ b/gadget/update.go @@ -294,7 +294,7 @@ func onDiskStructureIsLikelyImplicitSystemDataRole(gadgetLayout *LaidOutVolume, numPartsOnDisk := len(diskLayout.Structure) return s.Filesystem == "ext4" && - s.Type == "0FC63DAF-8483-4772-8E79-3D69D8477DE4" && // TODO: check hybrid and on MBR/DOS too + (s.Type == "0FC63DAF-8483-4772-8E79-3D69D8477DE4" || s.Type == "83") && s.Label == "writable" && // DiskIndex is 1-based s.DiskIndex == numPartsOnDisk && diff --git a/gadget/update_test.go b/gadget/update_test.go index dc795e475a0..48ad444d6b1 100644 --- a/gadget/update_test.go +++ b/gadget/update_test.go @@ -3926,6 +3926,31 @@ func (s *updateTestSuite) TestDiskTraitsFromDeviceAndValidateImplicitSystemDataH c.Assert(traits, DeepEquals, gadgettest.UC16ImplicitSystemDataDeviceTraits) } +func (s *updateTestSuite) TestDiskTraitsFromDeviceAndValidateImplicitSystemDataRaspiHappy(c *C) { + // mock the device name + restore := disks.MockDeviceNameToDiskMapping(map[string]*disks.MockDiskMapping{ + "/dev/mmcblk0": gadgettest.ExpectedRaspiUC18MockDiskMapping, + }) + defer restore() + + lvol, err := gadgettest.LayoutFromYaml(c.MkDir(), gadgettest.RaspiUC18SimplifiedYaml, nil) + c.Assert(err, IsNil) + + // the volume cannot be found with no opts set + _, err = gadget.DiskTraitsFromDeviceAndValidate(lvol, "/dev/mmcblk0", nil) + c.Assert(err, ErrorMatches, `volume pi is not compatible with disk /dev/mmcblk0: cannot find disk partition /dev/mmcblk0p2 \(starting at 269484032\) in gadget: start offsets do not match \(disk: 269484032 \(257 MiB\) and gadget: 1048576 \(1 MiB\)\)`) + + // with opts for pc then it can be found + opts := &gadget.DiskVolumeValidationOptions{ + AllowImplicitSystemData: true, + } + + traits, err := gadget.DiskTraitsFromDeviceAndValidate(lvol, "/dev/mmcblk0", opts) + c.Assert(err, IsNil) + + c.Assert(traits, DeepEquals, gadgettest.ExpectedRaspiUC18DiskVolumeDeviceTraits) +} + func (s *updateTestSuite) TestSearchForVolumeWithTraitsImplicitSystemData(c *C) { allowImplicitDataOpts := &gadget.DiskVolumeValidationOptions{ AllowImplicitSystemData: true, From 43c6272cac9a89c9593bc4f0388db86552f1c2f8 Mon Sep 17 00:00:00 2001 From: alfonsosanchezbeato Date: Wed, 15 Jun 2022 09:09:02 +0200 Subject: [PATCH 091/153] gadget: check also mbr type when testing for implicit data partition * gadget: check also mbr type when testing for implicit data partition When testing if a gadget defines implicitly the system data partition (which can happen for UC16 and UC18), we were checking the type of the on-disk partition to determine if it was the system data partition. However, we were testing only if it matched the GPT UUID partition type, and we were not considering the MBR disk case, which uses different numbers. Fixes LP:#1978127. * gadget/update_test.go: remove left-over comment --- gadget/gadgettest/examples.go | 82 +++++++++++++++++++++++++++++++++++ gadget/update.go | 2 +- gadget/update_test.go | 25 +++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/gadget/gadgettest/examples.go b/gadget/gadgettest/examples.go index cb68186ee3f..8a52f6a4a49 100644 --- a/gadget/gadgettest/examples.go +++ b/gadget/gadgettest/examples.go @@ -85,6 +85,19 @@ volumes: type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 ` +// from UC18 image, for testing the implicit system data partition case +const RaspiUC18SimplifiedYaml = ` +volumes: + pi: + schema: mbr + bootloader: u-boot + structure: + - type: 0C + filesystem: vfat + filesystem-label: system-boot + size: 256M +` + var expPiSeedStructureTraits = gadget.DiskStructureDeviceTraits{ OriginalDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p1", OriginalKernelPath: "/dev/mmcblk0p1", @@ -225,6 +238,33 @@ var ExpectedLUKSEncryptedRaspiDiskVolumeDeviceTraits = gadget.DiskVolumeDeviceTr }, } +// ExpectedRaspiUC18DiskVolumeDeviceTraits, for testing the implicit system +// data partition case +var ExpectedRaspiUC18DiskVolumeDeviceTraits = gadget.DiskVolumeDeviceTraits{ + OriginalDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0", + OriginalKernelPath: "/dev/mmcblk0", + DiskID: "7c301cbd", + Size: 32010928128, + SectorSize: 512, + Schema: "dos", + Structure: []gadget.DiskStructureDeviceTraits{ + { + OriginalDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p1", + OriginalKernelPath: "/dev/mmcblk0p1", + PartitionUUID: "7c301cbd-01", + PartitionType: "0C", + PartitionLabel: "", + FilesystemUUID: "23F9-881F", + FilesystemLabel: "system-boot", + FilesystemType: "vfat", + Offset: quantity.OffsetMiB, + Size: 256 * quantity.SizeMiB, + }, + // note no writable structure here - since it's not in the YAML, we + // don't save it in the traits either + }, +} + var mockSeedPartition = disks.Partition{ PartitionUUID: "7c301cbd-01", PartitionType: "0C", @@ -393,6 +433,48 @@ var ExpectedRaspiMockDiskInstallModeMapping = &disks.MockDiskMapping{ }, } +// ExpectedRaspiUC18MockDiskMapping, for testing the implicit system data partition case +var ExpectedRaspiUC18MockDiskMapping = &disks.MockDiskMapping{ + DevNode: "/dev/mmcblk0", + DevPath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0", + DevNum: "179:0", + DiskUsableSectorEnd: 30528 * oneMeg / 512, + DiskSizeInBytes: 30528 * oneMeg, + SectorSizeBytes: 512, + DiskSchema: "dos", + ID: "7c301cbd", + Structure: []disks.Partition{ + { + PartitionUUID: "7c301cbd-01", + PartitionType: "0C", + FilesystemLabel: "system-boot", + FilesystemUUID: "23F9-881F", + FilesystemType: "vfat", + Major: 179, + Minor: 1, + KernelDeviceNode: "/dev/mmcblk0p1", + KernelDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p1", + DiskIndex: 1, + StartInBytes: oneMeg, + SizeInBytes: 256 * oneMeg, + }, + { + PartitionUUID: "7c301cbd-02", + PartitionType: "83", + FilesystemLabel: "writable", + FilesystemUUID: "cba2b8b3-c2e4-4e51-9a57-d35041b7bf9a", + FilesystemType: "ext4", + Major: 179, + Minor: 2, + KernelDeviceNode: "/dev/mmcblk0p2", + KernelDevicePath: "/sys/devices/platform/emmc2bus/fe340000.emmc2/mmc_host/mmc0/mmc0:0001/block/mmcblk0/mmcblk0p2", + DiskIndex: 2, + StartInBytes: (1 + 256) * oneMeg, + SizeInBytes: 32270 * oneMeg, + }, + }, +} + const ExpectedRaspiDiskVolumeDeviceTraitsJSON = ` { "pi": { diff --git a/gadget/update.go b/gadget/update.go index 3e16940f74d..c01e47a2fff 100644 --- a/gadget/update.go +++ b/gadget/update.go @@ -294,7 +294,7 @@ func onDiskStructureIsLikelyImplicitSystemDataRole(gadgetLayout *LaidOutVolume, numPartsOnDisk := len(diskLayout.Structure) return s.Filesystem == "ext4" && - s.Type == "0FC63DAF-8483-4772-8E79-3D69D8477DE4" && // TODO: check hybrid and on MBR/DOS too + (s.Type == "0FC63DAF-8483-4772-8E79-3D69D8477DE4" || s.Type == "83") && s.Label == "writable" && // DiskIndex is 1-based s.DiskIndex == numPartsOnDisk && diff --git a/gadget/update_test.go b/gadget/update_test.go index dc795e475a0..48ad444d6b1 100644 --- a/gadget/update_test.go +++ b/gadget/update_test.go @@ -3926,6 +3926,31 @@ func (s *updateTestSuite) TestDiskTraitsFromDeviceAndValidateImplicitSystemDataH c.Assert(traits, DeepEquals, gadgettest.UC16ImplicitSystemDataDeviceTraits) } +func (s *updateTestSuite) TestDiskTraitsFromDeviceAndValidateImplicitSystemDataRaspiHappy(c *C) { + // mock the device name + restore := disks.MockDeviceNameToDiskMapping(map[string]*disks.MockDiskMapping{ + "/dev/mmcblk0": gadgettest.ExpectedRaspiUC18MockDiskMapping, + }) + defer restore() + + lvol, err := gadgettest.LayoutFromYaml(c.MkDir(), gadgettest.RaspiUC18SimplifiedYaml, nil) + c.Assert(err, IsNil) + + // the volume cannot be found with no opts set + _, err = gadget.DiskTraitsFromDeviceAndValidate(lvol, "/dev/mmcblk0", nil) + c.Assert(err, ErrorMatches, `volume pi is not compatible with disk /dev/mmcblk0: cannot find disk partition /dev/mmcblk0p2 \(starting at 269484032\) in gadget: start offsets do not match \(disk: 269484032 \(257 MiB\) and gadget: 1048576 \(1 MiB\)\)`) + + // with opts for pc then it can be found + opts := &gadget.DiskVolumeValidationOptions{ + AllowImplicitSystemData: true, + } + + traits, err := gadget.DiskTraitsFromDeviceAndValidate(lvol, "/dev/mmcblk0", opts) + c.Assert(err, IsNil) + + c.Assert(traits, DeepEquals, gadgettest.ExpectedRaspiUC18DiskVolumeDeviceTraits) +} + func (s *updateTestSuite) TestSearchForVolumeWithTraitsImplicitSystemData(c *C) { allowImplicitDataOpts := &gadget.DiskVolumeValidationOptions{ AllowImplicitSystemData: true, From 35960ec2554f74b2340392b18269768b854dbc61 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 15 Jun 2022 09:04:06 +0200 Subject: [PATCH 092/153] boot: helper to obtain the name of the fallback ubuntu-data key Signed-off-by: Maciej Borzecki --- boot/seal.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/boot/seal.go b/boot/seal.go index f00c0501fdb..22d7b2836f4 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -147,6 +147,11 @@ func runKeySealRequests(key keys.EncryptionKey) []secboot.SealKeyRequest { } } +// FallbackDataSealedKeyUnder returns the name of a fallback ubuntu data key. +func FallbackDataSealedKeyUnder(dir string) string { + return filepath.Join(dir, "ubuntu-data.recovery.sealed-key") +} + // FallbackSaveSealedKeyUnder returns the name of a fallback ubuntu save key. func FallbackSaveSealedKeyUnder(dir string) string { return filepath.Join(dir, "ubuntu-save.recovery.sealed-key") From 485f6bcee71762201b0811e4ef38d8a7d5261be7 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 14 Jun 2022 16:39:21 +0200 Subject: [PATCH 093/153] o/devicestate: make sure to remove old factory reset fallback key Signed-off-by: Maciej Borzecki --- .../devicestate_install_mode_test.go | 92 ++++++++++++++++++- overlord/devicestate/handlers_install.go | 13 ++- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go index 2bfae3afdd6..78e66425a63 100644 --- a/overlord/devicestate/devicestate_install_mode_test.go +++ b/overlord/devicestate/devicestate_install_mode_test.go @@ -2378,10 +2378,24 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts } bootMakeBootableCalled++ + if tc.encrypt { + // those 2 keys are removed + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), + testutil.FileAbsent) + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + testutil.FileAbsent) + // but the original ubuntu-save key remains + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + testutil.FilePresent) + } + // this would be done by boot if tc.encrypt { err := ioutil.WriteFile(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), - []byte{'s', 'a', 'v', 'e'}, 0644) + []byte("save"), 0644) + c.Check(err, IsNil) + err = ioutil.WriteFile(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), + []byte("new-data"), 0644) c.Check(err, IsNil) } return nil @@ -2457,7 +2471,7 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts c.Assert(saveKey, NotNil) c.Check(recoveryKeyRemoved, Equals, true) c.Check(filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey)) - c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), testutil.FileAbsent) + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), testutil.FileEquals, "new-data") // sha3-384 of the mocked ubuntu-save sealed key c.Check(filepath.Join(dirs.SnapDeviceDirUnder(boot.InstallHostWritableDir), "factory-reset"), testutil.FileEquals, @@ -2593,7 +2607,72 @@ echo "mock output of: $(basename "$0") $*" err := os.MkdirAll(boot.InitramfsUbuntuSaveDir, 0755) c.Assert(err, IsNil) snaptest.PopulateDir(boot.InitramfsSeedEncryptionKeyDir, [][]string{ - {"ubuntu-data.recovery.sealed-key", ""}, + {"ubuntu-data.recovery.sealed-key", "old-data"}, + {"ubuntu-save.recovery.sealed-key", "old-save"}, + }) + + // and it has some content + serial := makeDeviceSerialAssertionInDir(c, boot.InstallHostDeviceSaveDir, s.storeSigning, s.brands, + model, devKey, "serial-1234") + + err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuSaveDir, "device/fde"), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSaveDir, "device/fde/marker"), nil, 0644) + c.Assert(err, IsNil) + + logbuf, restore := logger.MockLogger() + defer restore() + + err = s.doRunFactoryResetChange(c, model, resetTestCase{ + tpm: true, encrypt: true, trustedBootloader: true, + }) + c.Logf("logs:\n%v", logbuf.String()) + c.Assert(err, IsNil) + + // verify that the serial assertion has been restored + assertsInResetSystem := filepath.Join(boot.InstallHostWritableDir, "var/lib/snapd/assertions") + bs, err := asserts.OpenFSBackstore(assertsInResetSystem) + c.Assert(err, IsNil) + db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ + Backstore: bs, + Trusted: s.storeSigning.Trusted, + OtherPredefined: s.storeSigning.Generic, + }) + c.Assert(err, IsNil) + ass, err := db.FindMany(asserts.SerialType, map[string]string{ + "brand-id": serial.BrandID(), + "model": serial.Model(), + "device-key-sha3-384": serial.DeviceKey().ID(), + }) + c.Assert(err, IsNil) + c.Assert(ass, HasLen, 1) + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), + testutil.FileEquals, "new-data") + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + testutil.FileEquals, "old-save") + // new key was written + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + testutil.FileEquals, "save") +} + +func (s *deviceMgrInstallModeSuite) TestFactoryResetEncryptionHappyAfterReboot(c *C) { + s.state.Lock() + model := s.makeMockInstallModel(c, "dangerous") + s.state.Unlock() + + // for debug timinigs + mockedSnapCmd := testutil.MockCommand(c, "snap", ` +echo "mock output of: $(basename "$0") $*" +`) + defer mockedSnapCmd.Restore() + + // pretend snap-bootstrap mounted ubuntu-save + err := os.MkdirAll(boot.InitramfsUbuntuSaveDir, 0755) + c.Assert(err, IsNil) + snaptest.PopulateDir(boot.InitramfsSeedEncryptionKeyDir, [][]string{ + {"ubuntu-data.recovery.sealed-key", "old-data"}, + {"ubuntu-save.recovery.sealed-key", "old-save"}, + {"ubuntu-save.recovery.sealed-key.factory-reset", "old-factory-reset"}, }) // and it has some content @@ -2631,6 +2710,13 @@ echo "mock output of: $(basename "$0") $*" }) c.Assert(err, IsNil) c.Assert(ass, HasLen, 1) + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"), + testutil.FileEquals, "new-data") + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + testutil.FileEquals, "old-save") + // key was replaced + c.Check(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + testutil.FileEquals, "save") } func (s *deviceMgrInstallModeSuite) TestFactoryResetSerialsWithoutKey(c *C) { diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index bb7be7171fb..deb88b76d2a 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -969,9 +969,16 @@ func (m *DeviceManager) doFactoryResetRunSystem(t *state.Task, _ *tomb.Tomb) err logger.Noticef("devs: %+v", installedSystem.DeviceForRole) if trustedInstallObserver != nil { - // at this point we removed boot and data. sealed keys are becoming - // useless - err := os.Remove(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key")) + // at this point we removed boot and data. sealed fallback key + // for ubuntu-data is becoming useless + err := os.Remove(boot.FallbackDataSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir)) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("cannot cleanup obsolete key file: %v", err) + } + + // it is possible that we reached this place again where a + // previously running factory reset was interrupted by a reboot + err = os.Remove(boot.FactoryResetFallbackSaveSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir)) if err != nil && !os.IsNotExist(err) { return fmt.Errorf("cannot cleanup obsolete key file: %v", err) } From a303865ee669aa6af8c2578d7aecd358e274c6af Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 15 Jun 2022 10:36:00 +0200 Subject: [PATCH 094/153] boot: tweak naming Signed-off-by: Maciej Borzecki --- boot/boot.go | 4 ++-- boot/seal_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/boot/boot.go b/boot/boot.go index 836f9c41401..1a342b222cc 100644 --- a/boot/boot.go +++ b/boot/boot.go @@ -474,9 +474,9 @@ func UpdateCommandLineForGadgetComponent(dev snap.Device, gadgetSnapOrDir string return cmdlineChange, nil } -// CompleteFactoryReset runs a series of steps in a run system that complete a +// MarkFactoryResetComplete runs a series of steps in a run system that complete a // factory reset process. -func CompleteFactoryReset(encrypted bool) error { +func MarkFactoryResetComplete(encrypted bool) error { if !encrypted { // there is nothing to do on an unencrypted system return nil diff --git a/boot/seal_test.go b/boot/seal_test.go index eda740d8add..a0537496af7 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -2084,7 +2084,7 @@ func (s *sealSuite) TestResealKeyToModeenvWithTryModel(c *C) { }) } -func (s *sealSuite) TestCompleteFactoryReset(c *C) { +func (s *sealSuite) TestMarkFactoryResetComplete(c *C) { for i, tc := range []struct { encrypted bool @@ -2179,7 +2179,7 @@ func (s *sealSuite) TestCompleteFactoryReset(c *C) { }) defer restore() - err := boot.CompleteFactoryReset(tc.encrypted) + err := boot.MarkFactoryResetComplete(tc.encrypted) if tc.err != "" { c.Assert(err, ErrorMatches, tc.err) } else { From 8fcffdde6f53eb2d2a8d88743521d74901813cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 15 Jun 2022 11:07:26 +0200 Subject: [PATCH 095/153] Also test with two extra validation sets that replace both tracked sets. --- .../validation_set_tracking_test.go | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/overlord/assertstate/validation_set_tracking_test.go b/overlord/assertstate/validation_set_tracking_test.go index 7b1eb8e6c1d..ba1e77a2a75 100644 --- a/overlord/assertstate/validation_set_tracking_test.go +++ b/overlord/assertstate/validation_set_tracking_test.go @@ -278,7 +278,7 @@ func (s *validationSetTrackingSuite) TestEnforcedValidationSetsWithExtraSets(c * } assertstate.UpdateValidationSet(s.st, &tr) - vs1 := s.mockAssert(c, "foo", "2", "invalid") + vs1 := s.mockAssert(c, "foo", "2", "optional") c.Assert(assertstate.Add(s.st, vs1), IsNil) vs2 := s.mockAssert(c, "bar", "1", "required") @@ -287,10 +287,11 @@ func (s *validationSetTrackingSuite) TestEnforcedValidationSetsWithExtraSets(c * valsets, err := assertstate.EnforcedValidationSets(s.st) c.Assert(err, IsNil) - // foo and bar are in conflict, use this as an indirect way of checking that extra validation sets are considered by validation sets and - // resolve the conflict. err = valsets.Conflict() - c.Check(err, ErrorMatches, `validation sets are in conflict:\n- cannot constrain snap "snap-b" as both invalid \(.*/foo\) and required at any revision \(.*/bar\)`) + c.Assert(err, IsNil) + + // use extra validation sets that trigger conflicts to verify they are + // considered by EnforcedValidationSets. // extra validation set "foo" replaces vs from the state extra1 := s.mockAssert(c, "foo", "9", "required") @@ -313,6 +314,21 @@ func (s *validationSetTrackingSuite) TestEnforcedValidationSetsWithExtraSets(c * c.Assert(err, IsNil) err = valsets.Conflict() c.Assert(err, IsNil) + + // extra validations set replace both foo and bar vs from the state + extra1 = s.mockAssert(c, "foo", "9", "required") + extra2 = s.mockAssert(c, "bar", "9", "invalid") + valsets, err = assertstate.EnforcedValidationSets(s.st, extra1.(*asserts.ValidationSet), extra2.(*asserts.ValidationSet)) + c.Assert(err, IsNil) + err = valsets.Conflict() + c.Check(err, ErrorMatches, `validation sets are in conflict:\n- cannot constrain snap "snap-b" as both invalid \(.*/bar\) and required at any revision \(.*/foo\)`) + + // no conflict once both are invalid + extra1 = s.mockAssert(c, "foo", "9", "invalid") + valsets, err = assertstate.EnforcedValidationSets(s.st, extra1.(*asserts.ValidationSet), extra2.(*asserts.ValidationSet)) + c.Assert(err, IsNil) + err = valsets.Conflict() + c.Check(err, IsNil) } func (s *validationSetTrackingSuite) TestAddToValidationSetsHistory(c *C) { From 37bed2a6d61ddee0de4c8dc9ed219c97201d9a99 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 15 Jun 2022 12:22:39 +0200 Subject: [PATCH 096/153] tests/regression/mount-order-regression: shellcheck In - line 5: test "$($SNAP_NAME.cmd ls -1 "$DIR" 2> /dev/null | wc -l)" -gt 0 ^--------^ SC2086 (info): Double quote to prevent globbing and word splitting. Signed-off-by: Maciej Borzecki --- tests/regression/mount-order-regression/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression/mount-order-regression/task.yaml b/tests/regression/mount-order-regression/task.yaml index ae8007f0673..c4080eb38c4 100644 --- a/tests/regression/mount-order-regression/task.yaml +++ b/tests/regression/mount-order-regression/task.yaml @@ -33,7 +33,7 @@ execute: | check_non_empty() { local DIR="$1" echo "Checking $DIR" - test "$($SNAP_NAME.cmd ls -1 "$DIR" 2> /dev/null | wc -l)" -gt 0 + test "$("$SNAP_NAME.cmd" ls -1 "$DIR" 2> /dev/null | wc -l)" -gt 0 } if os.query is-arm64; then From 6ebf9c09e810812f757284357ced3d4a5a44d5bd Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 15 Jun 2022 13:12:35 +0200 Subject: [PATCH 097/153] release: 2.56.1 (#11878) --- packaging/arch/PKGBUILD | 2 +- packaging/debian-sid/changelog | 20 ++++++++++++++++++++ packaging/fedora/snapd.spec | 19 ++++++++++++++++++- packaging/opensuse/snapd.changes | 5 +++++ packaging/opensuse/snapd.spec | 2 +- packaging/ubuntu-14.04/changelog | 20 ++++++++++++++++++++ packaging/ubuntu-16.04/changelog | 20 ++++++++++++++++++++ 7 files changed, 85 insertions(+), 3 deletions(-) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 8905fb72061..f71c6f3df57 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -11,7 +11,7 @@ pkgdesc="Service and tools for management of snap packages." depends=('squashfs-tools' 'libseccomp' 'libsystemd' 'apparmor') optdepends=('bash-completion: bash completion support' 'xdg-desktop-portal: desktop integration') -pkgver=2.56 +pkgver=2.56.1 pkgrel=1 arch=('x86_64' 'i686' 'armv7h' 'aarch64') url="https://github.com/snapcore/snapd" diff --git a/packaging/debian-sid/changelog b/packaging/debian-sid/changelog index 1cc8a70880e..248944fd2e0 100644 --- a/packaging/debian-sid/changelog +++ b/packaging/debian-sid/changelog @@ -1,3 +1,23 @@ +snapd (2.56.1-1) unstable; urgency=medium + + * New upstream release, LP: #1974147 + - gadget/install: do not assume dm device has same block size as + disk + - gadget: check also mbr type when testing for implicit data + partition + - interfaces: update network-control interface with permissions + required by resolvectl + - interfaces/builtin: remove the name=org.freedesktop.DBus + restriction in cups-control AppArmor rules + - many: print valid/invalid status on snap validate --monitor ... + - o/snapstate: fix validation sets restoring and snap revert on + failed refresh + - interfaces/opengl: update allowed PCI accesses for RPi + - interfaces/shared-memory: Update AppArmor permissions for + mmap+linkpaths + + -- Michael Vogt Wed, 15 Jun 2022 09:57:54 +0200 + snapd (2.56-1) unstable; urgency=medium * New upstream release, LP: #1974147 diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec index 7c7820af645..a0d5d97cfcf 100644 --- a/packaging/fedora/snapd.spec +++ b/packaging/fedora/snapd.spec @@ -102,7 +102,7 @@ %endif Name: snapd -Version: 2.56 +Version: 2.56.1 Release: 0%{?dist} Summary: A transactional software package manager License: GPLv3 @@ -981,6 +981,23 @@ fi %changelog +* Wed Jun 15 2022 Michael Vogt +- New upstream release 2.56.1 + - gadget/install: do not assume dm device has same block size as + disk + - gadget: check also mbr type when testing for implicit data + partition + - interfaces: update network-control interface with permissions + required by resolvectl + - interfaces/builtin: remove the name=org.freedesktop.DBus + restriction in cups-control AppArmor rules + - many: print valid/invalid status on snap validate --monitor ... + - o/snapstate: fix validation sets restoring and snap revert on + failed refresh + - interfaces/opengl: update allowed PCI accesses for RPi + - interfaces/shared-memory: Update AppArmor permissions for + mmap+linkpaths + * Thu May 19 2022 Michael Vogt - New upstream release 2.56 - portal-info: Add CommonID Field diff --git a/packaging/opensuse/snapd.changes b/packaging/opensuse/snapd.changes index 6f0af82874d..6cce19bf427 100644 --- a/packaging/opensuse/snapd.changes +++ b/packaging/opensuse/snapd.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Wed Jun 15 07:57:54 UTC 2022 - michael.vogt@ubuntu.com + +- Update to upstream release 2.56.1 + ------------------------------------------------------------------- Thu May 19 07:57:33 UTC 2022 - michael.vogt@ubuntu.com diff --git a/packaging/opensuse/snapd.spec b/packaging/opensuse/snapd.spec index db055bccfdf..d127b5a9986 100644 --- a/packaging/opensuse/snapd.spec +++ b/packaging/opensuse/snapd.spec @@ -81,7 +81,7 @@ Name: snapd -Version: 2.56 +Version: 2.56.1 Release: 0 Summary: Tools enabling systems to work with .snap files License: GPL-3.0 diff --git a/packaging/ubuntu-14.04/changelog b/packaging/ubuntu-14.04/changelog index 00b9a039b9a..19af7228e0b 100644 --- a/packaging/ubuntu-14.04/changelog +++ b/packaging/ubuntu-14.04/changelog @@ -1,3 +1,23 @@ +snapd (2.56.1~14.04) trusty; urgency=medium + + * New upstream release, LP: #1974147 + - gadget/install: do not assume dm device has same block size as + disk + - gadget: check also mbr type when testing for implicit data + partition + - interfaces: update network-control interface with permissions + required by resolvectl + - interfaces/builtin: remove the name=org.freedesktop.DBus + restriction in cups-control AppArmor rules + - many: print valid/invalid status on snap validate --monitor ... + - o/snapstate: fix validation sets restoring and snap revert on + failed refresh + - interfaces/opengl: update allowed PCI accesses for RPi + - interfaces/shared-memory: Update AppArmor permissions for + mmap+linkpaths + + -- Michael Vogt Wed, 15 Jun 2022 09:57:54 +0200 + snapd (2.56~14.04) trusty; urgency=medium * New upstream release, LP: #1974147 diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog index 1441c24f267..8bbe4130482 100644 --- a/packaging/ubuntu-16.04/changelog +++ b/packaging/ubuntu-16.04/changelog @@ -1,3 +1,23 @@ +snapd (2.56.1) xenial; urgency=medium + + * New upstream release, LP: #1974147 + - gadget/install: do not assume dm device has same block size as + disk + - gadget: check also mbr type when testing for implicit data + partition + - interfaces: update network-control interface with permissions + required by resolvectl + - interfaces/builtin: remove the name=org.freedesktop.DBus + restriction in cups-control AppArmor rules + - many: print valid/invalid status on snap validate --monitor ... + - o/snapstate: fix validation sets restoring and snap revert on + failed refresh + - interfaces/opengl: update allowed PCI accesses for RPi + - interfaces/shared-memory: Update AppArmor permissions for + mmap+linkpaths + + -- Michael Vogt Wed, 15 Jun 2022 09:57:54 +0200 + snapd (2.56) xenial; urgency=medium * New upstream release, LP: #1974147 From 90b1681d16af4d0fd9e8e98a1b3bb4d511baa4e3 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Wed, 15 Jun 2022 09:07:11 -0300 Subject: [PATCH 098/153] Add suport fof uc22 in test uboot-unpacked-assets --- tests/core/uboot-unpacked-assets/task.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/uboot-unpacked-assets/task.yaml b/tests/core/uboot-unpacked-assets/task.yaml index 323f63e1a42..d3aa12b7e45 100644 --- a/tests/core/uboot-unpacked-assets/task.yaml +++ b/tests/core/uboot-unpacked-assets/task.yaml @@ -7,8 +7,8 @@ environment: NAME/kernelimg: kernel.img execute: | - if os.query is-core20; then - echo "Check that on UC20, the kernel snap is extracted onto ubuntu-seed, not on ubuntu-boot" + if os.query is-core20 || os.query is-core22; then + echo "Check that on UC20 and UC22, the kernel snap is extracted onto ubuntu-seed, not on ubuntu-boot" output=$(find /run/mnt/ubuntu-seed/systems/*/kernel/ -name "$NAME" ) if [ -z "$output" ]; then echo "Not found expected file $NAME in /run/mnt/ubuntu-seed/systems/*/kernel/" From 5a5a61225170ff8493446a437447d1ad4d8c1496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Tue, 31 May 2022 15:57:33 +0200 Subject: [PATCH 099/153] Exclude services from refresh app awareness hard running check. --- overlord/snapstate/refresh.go | 3 +-- overlord/snapstate/refresh_test.go | 7 ++----- tests/main/services-refresh-mode/task.yaml | 4 +--- tests/main/services-snapctl/task.yaml | 3 +-- tests/main/services-stop-mode-sigkill/task.yaml | 4 +--- tests/main/services-stop-mode/task.yaml | 4 +--- 6 files changed, 7 insertions(+), 18 deletions(-) diff --git a/overlord/snapstate/refresh.go b/overlord/snapstate/refresh.go index bb800074621..723dee9556d 100644 --- a/overlord/snapstate/refresh.go +++ b/overlord/snapstate/refresh.go @@ -119,8 +119,7 @@ func SoftNothingRunningRefreshCheck(info *snap.Info) error { // refreshed and the refresh process is aborted. func HardNothingRunningRefreshCheck(info *snap.Info) error { return genericRefreshCheck(info, func(app *snap.AppInfo) bool { - // TODO: use a constant instead of "endure" - return app.IsService() && app.RefreshMode == "endure" + return app.IsService() }) } diff --git a/overlord/snapstate/refresh_test.go b/overlord/snapstate/refresh_test.go index 1f0f4574c59..6f7974db90b 100644 --- a/overlord/snapstate/refresh_test.go +++ b/overlord/snapstate/refresh_test.go @@ -112,15 +112,12 @@ func (s *refreshSuite) TestSoftNothingRunningRefreshCheck(c *C) { } func (s *refreshSuite) TestHardNothingRunningRefreshCheck(c *C) { - // Regular services are blocking hard refresh check. - // We were expecting them to be gone by now. + // Services are ignored by hard refresh check. s.pids = map[string][]int{ "snap.pkg.daemon": {100}, } err := snapstate.HardNothingRunningRefreshCheck(s.info) - c.Assert(err, NotNil) - c.Check(err.Error(), Equals, `snap "pkg" has running apps (daemon), pids: 100`) - c.Check(err.(*snapstate.BusySnapError).Pids(), DeepEquals, []int{100}) + c.Assert(err, IsNil) // When the service is supposed to endure refreshes it will not be // stopped. As such such services cannot block refresh. diff --git a/tests/main/services-refresh-mode/task.yaml b/tests/main/services-refresh-mode/task.yaml index 310f088e1ef..cf1238de87d 100644 --- a/tests/main/services-refresh-mode/task.yaml +++ b/tests/main/services-refresh-mode/task.yaml @@ -18,9 +18,7 @@ execute: | systemctl show -p MainPID snap.test-snapd-service.test-snapd-endure-service > old-main.pid echo "When it is re-installed" - # Due to refresh-app-awareness we need --ignore-running to ignore the service that - # keeps running due to "endure" mode. - "$TESTSTOOLS"/snaps-state install-local test-snapd-service --ignore-running + "$TESTSTOOLS"/snaps-state install-local test-snapd-service echo "We can still see it running with the same PID" systemctl show -p MainPID snap.test-snapd-service.test-snapd-endure-service > new-main.pid diff --git a/tests/main/services-snapctl/task.yaml b/tests/main/services-snapctl/task.yaml index afc472b6418..42968060cb2 100644 --- a/tests/main/services-snapctl/task.yaml +++ b/tests/main/services-snapctl/task.yaml @@ -76,8 +76,7 @@ execute: | echo "Reinstalling the snap with configure hook calling snapctl restart works" snap set test-snapd-service command=restart - # Due to refresh-app-awareness we need --ignore-running as test-snapd-sigterm-service keeps running after sigterm. - "$TESTSTOOLS"/snaps-state install-local test-snapd-service --ignore-running + "$TESTSTOOLS"/snaps-state install-local test-snapd-service # shellcheck disable=SC2119 if "$TESTSTOOLS"/journal-state get-log | MATCH "error running snapctl"; then echo "snapctl should not report errors" diff --git a/tests/main/services-stop-mode-sigkill/task.yaml b/tests/main/services-stop-mode-sigkill/task.yaml index 3a0d5a04ed6..3858b34383d 100644 --- a/tests/main/services-stop-mode-sigkill/task.yaml +++ b/tests/main/services-stop-mode-sigkill/task.yaml @@ -34,9 +34,7 @@ execute: | [ "$n" = "2" ] echo "When it is re-installed one process uses sigterm, the other sigterm-all" - # due to refresh-app-awareness we need to ignore running processes since the - # sigterm service keeps a sleep process running. - "$TESTSTOOLS"/snaps-state install-local test-snapd-service --ignore-running + "$TESTSTOOLS"/snaps-state install-local test-snapd-service retry -n 20 --wait 1 sh -c 'test -f /var/snap/test-snapd-service/common/ready' echo "After reinstall the sigterm-all service and all children got killed" diff --git a/tests/main/services-stop-mode/task.yaml b/tests/main/services-stop-mode/task.yaml index 4a62c85f4ae..4b3ec18d45d 100644 --- a/tests/main/services-stop-mode/task.yaml +++ b/tests/main/services-stop-mode/task.yaml @@ -37,9 +37,7 @@ execute: | done echo "When it is re-installed" - # Due to refresh-app-awareness we need to --ignore-running to ignore the running test-snapd-sigterm-service (it - # keeps running after sigterm). - "$TESTSTOOLS"/snaps-state install-local test-snapd-service --ignore-running + "$TESTSTOOLS"/snaps-state install-local test-snapd-service retry -n 20 --wait 1 sh -c 'test -f /var/snap/test-snapd-service/common/ready' # note that sigterm{,-all} is tested separately From 1f3a29c5ad3eb85b865aa8dbd3cbdf9f5839fc56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Mon, 30 May 2022 11:33:58 +0200 Subject: [PATCH 100/153] Support custom apparmor features dir with snap prepare-image. --- cmd/snap/cmd_prepare_image.go | 9 +++++++-- cmd/snap/cmd_prepare_image_test.go | 11 ++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/cmd/snap/cmd_prepare_image.go b/cmd/snap/cmd_prepare_image.go index d868483cb10..25b05747af1 100644 --- a/cmd/snap/cmd_prepare_image.go +++ b/cmd/snap/cmd_prepare_image.go @@ -35,7 +35,9 @@ type cmdPrepareImage struct { Classic bool `long:"classic"` Preseed bool `long:"preseed"` PreseedSignKey string `long:"preseed-sign-key"` - Architecture string `long:"arch"` + // optional path to AppArmor kernel features directory + AppArmorKernelFeaturesDir string `long:"apparmor-features-dir"` + Architecture string `long:"arch"` Positional struct { ModelAssertionFn string @@ -67,10 +69,12 @@ For preparing classic images it supports a --classic mode`), // TRANSLATORS: This should not start with a lowercase letter. "classic": i18n.G("Enable classic mode to prepare a classic model image"), // TRANSLATORS: This should not start with a lowercase letter. - "preseed": i18n.G("Preseed (UC20 only)"), + "preseed": i18n.G("Preseed (UC20+ only)"), // TRANSLATORS: This should not start with a lowercase letter. "preseed-sign-key": i18n.G("Name of the key to use to sign preseed assertion, otherwise use the default key"), // TRANSLATORS: This should not start with a lowercase letter. + "apparmor-features-dir": i18n.G("Optional path to apparmor kernel features directory (UC20+ only)"), + // TRANSLATORS: This should not start with a lowercase letter. "arch": i18n.G("Specify an architecture for snaps for --classic when the model does not"), // TRANSLATORS: This should not start with a lowercase letter. "snap": i18n.G("Include the given snap from the store or a local file and/or specify the channel to track for the given snap"), @@ -143,6 +147,7 @@ func (x *cmdPrepareImage) Execute(args []string) error { } opts.Preseed = x.Preseed opts.PreseedSignKey = x.PreseedSignKey + opts.AppArmorKernelFeaturesDir = x.AppArmorKernelFeaturesDir return imagePrepare(opts) } diff --git a/cmd/snap/cmd_prepare_image_test.go b/cmd/snap/cmd_prepare_image_test.go index 95d402fb79b..09e2b3c1aec 100644 --- a/cmd/snap/cmd_prepare_image_test.go +++ b/cmd/snap/cmd_prepare_image_test.go @@ -188,14 +188,15 @@ func (s *SnapPrepareImageSuite) TestPrepareImagePreseed(c *C) { r := snap.MockImagePrepare(prep) defer r() - rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "--preseed", "--preseed-sign-key", "key", "model", "prepare-dir"}) + rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"prepare-image", "--preseed", "--preseed-sign-key", "key", "--apparmor-features-dir", "aafeatures", "model", "prepare-dir"}) c.Assert(err, IsNil) c.Assert(rest, DeepEquals, []string{}) c.Check(opts, DeepEquals, &image.Options{ - ModelFile: "model", - PrepareDir: "prepare-dir", - Preseed: true, - PreseedSignKey: "key", + ModelFile: "model", + PrepareDir: "prepare-dir", + Preseed: true, + PreseedSignKey: "key", + AppArmorKernelFeaturesDir: "aafeatures", }) } From 9b6cf7219e91f613be411efb3405827f9b969c70 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 15 Jun 2022 14:46:37 +0200 Subject: [PATCH 101/153] release: 2.56.2 (#11883) --- packaging/arch/PKGBUILD | 2 +- packaging/debian-sid/changelog | 10 ++++++++++ packaging/fedora/snapd.spec | 9 ++++++++- packaging/opensuse/snapd.changes | 5 +++++ packaging/opensuse/snapd.spec | 2 +- packaging/ubuntu-14.04/changelog | 10 ++++++++++ packaging/ubuntu-16.04/changelog | 10 ++++++++++ 7 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index f71c6f3df57..924220bac40 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -11,7 +11,7 @@ pkgdesc="Service and tools for management of snap packages." depends=('squashfs-tools' 'libseccomp' 'libsystemd' 'apparmor') optdepends=('bash-completion: bash completion support' 'xdg-desktop-portal: desktop integration') -pkgver=2.56.1 +pkgver=2.56.2 pkgrel=1 arch=('x86_64' 'i686' 'armv7h' 'aarch64') url="https://github.com/snapcore/snapd" diff --git a/packaging/debian-sid/changelog b/packaging/debian-sid/changelog index 248944fd2e0..cd20924fd37 100644 --- a/packaging/debian-sid/changelog +++ b/packaging/debian-sid/changelog @@ -1,3 +1,13 @@ +snapd (2.56.2-1) unstable; urgency=medium + + * New upstream release, LP: #1974147 + - o/snapstate: exclude services from refresh app awareness hard + running check + - cmd/snap: support custom apparmor features dir with snap + prepare-image + + -- Michael Vogt Wed, 15 Jun 2022 14:22:31 +0200 + snapd (2.56.1-1) unstable; urgency=medium * New upstream release, LP: #1974147 diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec index a0d5d97cfcf..fe46d4c9a44 100644 --- a/packaging/fedora/snapd.spec +++ b/packaging/fedora/snapd.spec @@ -102,7 +102,7 @@ %endif Name: snapd -Version: 2.56.1 +Version: 2.56.2 Release: 0%{?dist} Summary: A transactional software package manager License: GPLv3 @@ -981,6 +981,13 @@ fi %changelog +* Wed Jun 15 2022 Michael Vogt +- New upstream release 2.56.2 + - o/snapstate: exclude services from refresh app awareness hard + running check + - cmd/snap: support custom apparmor features dir with snap + prepare-image + * Wed Jun 15 2022 Michael Vogt - New upstream release 2.56.1 - gadget/install: do not assume dm device has same block size as diff --git a/packaging/opensuse/snapd.changes b/packaging/opensuse/snapd.changes index 6cce19bf427..9cdd4177881 100644 --- a/packaging/opensuse/snapd.changes +++ b/packaging/opensuse/snapd.changes @@ -1,3 +1,8 @@ +------------------------------------------------------------------- +Wed Jun 15 12:22:31 UTC 2022 - michael.vogt@ubuntu.com + +- Update to upstream release 2.56.2 + ------------------------------------------------------------------- Wed Jun 15 07:57:54 UTC 2022 - michael.vogt@ubuntu.com diff --git a/packaging/opensuse/snapd.spec b/packaging/opensuse/snapd.spec index d127b5a9986..f65a4e5af5a 100644 --- a/packaging/opensuse/snapd.spec +++ b/packaging/opensuse/snapd.spec @@ -81,7 +81,7 @@ Name: snapd -Version: 2.56.1 +Version: 2.56.2 Release: 0 Summary: Tools enabling systems to work with .snap files License: GPL-3.0 diff --git a/packaging/ubuntu-14.04/changelog b/packaging/ubuntu-14.04/changelog index 19af7228e0b..ddce2cf0bd7 100644 --- a/packaging/ubuntu-14.04/changelog +++ b/packaging/ubuntu-14.04/changelog @@ -1,3 +1,13 @@ +snapd (2.56.2~14.04) trusty; urgency=medium + + * New upstream release, LP: #1974147 + - o/snapstate: exclude services from refresh app awareness hard + running check + - cmd/snap: support custom apparmor features dir with snap + prepare-image + + -- Michael Vogt Wed, 15 Jun 2022 14:22:31 +0200 + snapd (2.56.1~14.04) trusty; urgency=medium * New upstream release, LP: #1974147 diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog index 8bbe4130482..4b305969dd2 100644 --- a/packaging/ubuntu-16.04/changelog +++ b/packaging/ubuntu-16.04/changelog @@ -1,3 +1,13 @@ +snapd (2.56.2) xenial; urgency=medium + + * New upstream release, LP: #1974147 + - o/snapstate: exclude services from refresh app awareness hard + running check + - cmd/snap: support custom apparmor features dir with snap + prepare-image + + -- Michael Vogt Wed, 15 Jun 2022 14:22:31 +0200 + snapd (2.56.1) xenial; urgency=medium * New upstream release, LP: #1974147 From 095513cfbbd86476f282e8519798e0e65b8c9748 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 14 Jun 2022 16:22:22 +0200 Subject: [PATCH 102/153] boot: release the new PCR handles when sealing for factory reset It is possible that a factory reset attempt is interrupted by a reboot after sealing the keys, which means that the PCR handles may already have been used and have TPM resources allocated to them. In such case, we should attempt to release the handles before sealing for factory reset again. Signed-off-by: Maciej Borzecki --- boot/makebootable_test.go | 14 +++++++++++++ boot/seal.go | 10 +++++++++ boot/seal_test.go | 43 +++++++++++++++++++++++++++++---------- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/boot/makebootable_test.go b/boot/makebootable_test.go index ea62b15d2f4..cd584c15b5d 100644 --- a/boot/makebootable_test.go +++ b/boot/makebootable_test.go @@ -608,6 +608,18 @@ version: 5.0 }) defer restore() + releasePCRHandleCalls := 0 + restore = boot.MockSecbootReleasePCRResourceHandles(func(handles ...uint32) error { + c.Check(factoryReset, Equals, true) + releasePCRHandleCalls++ + c.Check(handles, DeepEquals, []uint32{ + secboot.AltRunObjectPCRPolicyCounterHandle, + secboot.AltFallbackObjectPCRPolicyCounterHandle, + }) + return nil + }) + defer restore() + // set mock key sealing sealKeysCalls := 0 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { @@ -783,8 +795,10 @@ current_kernel_command_lines=["snapd_recovery_mode=run console=ttyS0 console=tty // PCR handle checks if factoryReset { c.Check(pcrHandleOfKeyCalls, Equals, 1) + c.Check(releasePCRHandleCalls, Equals, 1) } else { c.Check(pcrHandleOfKeyCalls, Equals, 0) + c.Check(releasePCRHandleCalls, Equals, 0) } // make sure the marker file for sealed key was created diff --git a/boot/seal.go b/boot/seal.go index 56fa5c748ca..b1f54153a59 100644 --- a/boot/seal.go +++ b/boot/seal.go @@ -301,6 +301,16 @@ func sealKeyToModeenvUsingSecboot(key, saveKey keys.EncryptionKey, modeenv *Mode return err } + if flags.FactoryReset { + // it is possible that we are sealing the keys again, after a + // previously running factory reset was interrupted by a reboot, + // in which case the PCR handles of the new sealed keys might + // have already been used + if err := secbootReleasePCRResourceHandles(runObjectKeyPCRHandle, fallbackObjectKeyPCRHandle); err != nil { + return err + } + } + // TODO: refactor sealing functions to take a struct instead of so many // parameters err = sealRunObjectKeys(key, pbc, authKey, roleToBlName, runObjectKeyPCRHandle) diff --git a/boot/seal_test.go b/boot/seal_test.go index a0537496af7..ff27278f7f9 100644 --- a/boot/seal_test.go +++ b/boot/seal_test.go @@ -101,25 +101,26 @@ func mockGadgetSeedSnap(c *C, files [][]string) *seed.Snap { func (s *sealSuite) TestSealKeyToModeenv(c *C) { for idx, tc := range []struct { - sealErr error - provisionErr error - factoryReset bool - pcrHandleOfKey uint32 - pcrHandleOfKeyErr error - expErr string - expProvisionCalls int - expSealCalls int - expPCRHandleOfKeyCalls int + sealErr error + provisionErr error + factoryReset bool + pcrHandleOfKey uint32 + pcrHandleOfKeyErr error + expErr string + expProvisionCalls int + expSealCalls int + expReleasePCRHandleCalls int + expPCRHandleOfKeyCalls int }{ { sealErr: nil, expErr: "", expProvisionCalls: 1, expSealCalls: 2, }, { sealErr: nil, factoryReset: true, pcrHandleOfKey: secboot.FallbackObjectPCRPolicyCounterHandle, - expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, + expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, expReleasePCRHandleCalls: 1, }, { sealErr: nil, factoryReset: true, pcrHandleOfKey: secboot.AltFallbackObjectPCRPolicyCounterHandle, - expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, + expProvisionCalls: 1, expSealCalls: 2, expPCRHandleOfKeyCalls: 1, expReleasePCRHandleCalls: 1, }, { sealErr: nil, factoryReset: true, pcrHandleOfKeyErr: errors.New("PCR handle error"), expErr: "PCR handle error", @@ -213,6 +214,25 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { }) defer restore() + releasePCRHandleCalls := 0 + restore = boot.MockSecbootReleasePCRResourceHandles(func(handles ...uint32) error { + c.Check(tc.factoryReset, Equals, true) + releasePCRHandleCalls++ + if tc.pcrHandleOfKey == secboot.FallbackObjectPCRPolicyCounterHandle { + c.Check(handles, DeepEquals, []uint32{ + secboot.AltRunObjectPCRPolicyCounterHandle, + secboot.AltFallbackObjectPCRPolicyCounterHandle, + }) + } else { + c.Check(handles, DeepEquals, []uint32{ + secboot.RunObjectPCRPolicyCounterHandle, + secboot.FallbackObjectPCRPolicyCounterHandle, + }) + } + return nil + }) + defer restore() + // set mock key sealing sealKeysCalls := 0 restore = boot.MockSecbootSealKeys(func(keys []secboot.SealKeyRequest, params *secboot.SealKeysParams) error { @@ -302,6 +322,7 @@ func (s *sealSuite) TestSealKeyToModeenv(c *C) { c.Check(pcrHandleOfKeyCalls, Equals, tc.expPCRHandleOfKeyCalls) c.Check(provisionCalls, Equals, tc.expProvisionCalls) c.Check(sealKeysCalls, Equals, tc.expSealCalls) + c.Check(releasePCRHandleCalls, Equals, tc.expReleasePCRHandleCalls) if tc.expErr == "" { c.Assert(err, IsNil) } else { From db727242a2c95ffa4d09528d9c045b5d9f58a649 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Wed, 15 Jun 2022 15:07:58 +0200 Subject: [PATCH 103/153] overlord/ifacestate: fix path for journal redirect --- dirs/dirs.go | 2 ++ overlord/ifacestate/handlers.go | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dirs/dirs.go b/dirs/dirs.go index 2a83f879850..ada5d76c04f 100644 --- a/dirs/dirs.go +++ b/dirs/dirs.go @@ -103,6 +103,7 @@ var ( SnapDesktopIconsDir string SnapPolkitPolicyDir string SnapSystemdDir string + SnapSystemdRunDir string SnapDBusSessionPolicyDir string SnapDBusSystemPolicyDir string @@ -422,6 +423,7 @@ func SetRootDir(rootdir string) { SnapUserServicesDir = filepath.Join(rootdir, "/etc/systemd/user") SnapSystemdConfDir = SnapSystemdConfDirUnder(rootdir) SnapSystemdDir = filepath.Join(rootdir, "/etc/systemd") + SnapSystemdRunDir = filepath.Join(rootdir, "/run/systemd") SnapDBusSystemPolicyDir = filepath.Join(rootdir, "/etc/dbus-1/system.d") SnapDBusSessionPolicyDir = filepath.Join(rootdir, "/etc/dbus-1/session.d") diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go index dd2d01e123e..2e3298657c0 100644 --- a/overlord/ifacestate/handlers.go +++ b/overlord/ifacestate/handlers.go @@ -54,10 +54,10 @@ func journalQuotaLayout(quotaGroup *quota.Group) []snap.Layout { } // bind mount the journal namespace folder on top of the journal folder - // /etc/systemd/journal. -> /etc/systemd/journal + // /run/systemd/journal. -> /run/systemd/journal layouts := []snap.Layout{{ - Bind: path.Join(dirs.SnapSystemdDir, fmt.Sprintf("journal.snap-%s", quotaGroup.Name)), - Path: path.Join(dirs.SnapSystemdDir, "journal"), + Bind: path.Join(dirs.SnapSystemdRunDir, fmt.Sprintf("journal.snap-%s", quotaGroup.Name)), + Path: path.Join(dirs.SnapSystemdRunDir, "journal"), Mode: 0755, }} return layouts From efa99b189c0d87982bcac914aa19682c373c6c9b Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Wed, 15 Jun 2022 15:11:40 +0200 Subject: [PATCH 104/153] wrappers: fix LogNamespace being written to the wrong file --- wrappers/services.go | 17 ++++++++--------- wrappers/services_test.go | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/wrappers/services.go b/wrappers/services.go index 646695a97bd..5300676f9d8 100644 --- a/wrappers/services.go +++ b/wrappers/services.go @@ -138,13 +138,6 @@ TasksAccounting=true return buf.String() } -func formatLogNamespaceSlice(grp *quota.Group) string { - if grp.JournalLimit == nil { - return "" - } - return fmt.Sprintf("LogNamespace=snap-%s\n", grp.Name) -} - // generateGroupSliceFile generates a systemd slice unit definition for the // specified quota group. func generateGroupSliceFile(grp *quota.Group) []byte { @@ -153,7 +146,6 @@ func generateGroupSliceFile(grp *quota.Group) []byte { cpuOptions := formatCpuGroupSlice(grp) memoryOptions := formatMemoryGroupSlice(grp) taskOptions := formatTaskGroupSlice(grp) - logOptions := formatLogNamespaceSlice(grp) template := `[Unit] Description=Slice for snap quota group %[1]s Before=slices.target @@ -163,7 +155,7 @@ X-Snappy=yes ` fmt.Fprintf(&buf, template, grp.Name) - fmt.Fprint(&buf, cpuOptions, memoryOptions, taskOptions, logOptions) + fmt.Fprint(&buf, cpuOptions, memoryOptions, taskOptions) return buf.Bytes() } @@ -1207,6 +1199,9 @@ OOMScoreAdjust={{.OOMAdjustScore}} {{- if .SliceUnit}} Slice={{.SliceUnit}} {{- end}} +{{- if .LogNamespace}} +LogNamespace={{.LogNamespace}} +{{- end}} {{- if not (or .App.Sockets .App.Timer .App.ActivatesOn) }} [Install] @@ -1279,6 +1274,7 @@ WantedBy={{.ServicesTarget}} After []string InterfaceServiceSnippets string SliceUnit string + LogNamespace string Home string EnvVars string @@ -1323,6 +1319,9 @@ WantedBy={{.ServicesTarget}} // check the quota group slice if opts.QuotaGroup != nil { wrapperData.SliceUnit = opts.QuotaGroup.SliceFileName() + if opts.QuotaGroup.JournalLimit != nil { + wrapperData.LogNamespace = "snap-" + opts.QuotaGroup.Name + } } // Add extra "After" targets diff --git a/wrappers/services_test.go b/wrappers/services_test.go index ec24b4a2012..4d23914df21 100644 --- a/wrappers/services_test.go +++ b/wrappers/services_test.go @@ -550,6 +550,7 @@ ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 TimeoutStopSec=30 Type=forking Slice=snap.foogroup.slice +LogNamespace=snap-foogroup [Install] WantedBy=multi-user.target @@ -575,7 +576,6 @@ MemoryAccounting=true # Always enable task accounting in order to be able to count the processes/ # threads, etc for a slice TasksAccounting=true -LogNamespace=snap-foogroup ` jconfContent := fmt.Sprintf(jconfTempl, grp.Name) @@ -653,6 +653,7 @@ ExecStopPost=/usr/bin/snap run --command=post-stop hello-snap.svc1 TimeoutStopSec=30 Type=forking Slice=snap.foogroup.slice +LogNamespace=snap-foogroup [Install] WantedBy=multi-user.target @@ -682,7 +683,6 @@ MemoryAccounting=true # Always enable task accounting in order to be able to count the processes/ # threads, etc for a slice TasksAccounting=true -LogNamespace=snap-foogroup ` jconfContent := fmt.Sprintf(jconfTempl, grp.Name) From 14752a773e388fe9c0cb046b93935e5b8aa4eb08 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Wed, 15 Jun 2022 15:17:54 +0200 Subject: [PATCH 105/153] overlord/ifacestate: update unit test --- overlord/ifacestate/handlers_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/overlord/ifacestate/handlers_test.go b/overlord/ifacestate/handlers_test.go index 59c8869299b..b6eb54fab67 100644 --- a/overlord/ifacestate/handlers_test.go +++ b/overlord/ifacestate/handlers_test.go @@ -159,8 +159,8 @@ func (s *handlersSuite) TestBuildConfinementOptionsWithLogNamespace(c *C) { c.Check(err, IsNil) c.Assert(len(opts.ExtraLayouts), Equals, 1) - c.Check(opts.ExtraLayouts[0].Bind, Equals, path.Join(dirs.SnapSystemdDir, "journal.snap-foo")) - c.Check(opts.ExtraLayouts[0].Path, Equals, path.Join(dirs.SnapSystemdDir, "journal")) + c.Check(opts.ExtraLayouts[0].Bind, Equals, path.Join(dirs.SnapSystemdRunDir, "journal.snap-foo")) + c.Check(opts.ExtraLayouts[0].Path, Equals, path.Join(dirs.SnapSystemdRunDir, "journal")) c.Check(opts.Classic, Equals, flags.Classic) c.Check(opts.DevMode, Equals, flags.DevMode) c.Check(opts.JailMode, Equals, flags.JailMode) From 6ce25b0a5c64eb4d7205c6a44ca3f64b4c1d2444 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Wed, 15 Jun 2022 11:36:34 -0300 Subject: [PATCH 106/153] Enable snapd.apparmor service for all the opensuse systems In opensuse leap 13 I see the following error generated WARNING: There is 1 new warning. See 'snap warnings'. and then after run snap warning: last-occurrence: today at 14:01 UTC warning: | the snapd.apparmor service is disabled; snap applications will likely not start. Run "systemctl enable --now snapd.apparmor" to correct this. --- tests/lib/pkgdb.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/pkgdb.sh b/tests/lib/pkgdb.sh index 2ef6530b989..66766ea9b0d 100755 --- a/tests/lib/pkgdb.sh +++ b/tests/lib/pkgdb.sh @@ -479,7 +479,7 @@ distro_install_build_snapd(){ fi fi - if os.query is-opensuse-tumbleweed || os.query is-arch-linux; then + if os.query is-opensuse || os.query is-arch-linux; then # Package installation applies vendor presets only, which leaves # snapd.apparmor disabled. systemctl enable --now snapd.apparmor.service From 993ada31c1407736ceb6583004ad187aadaeb0f1 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Thu, 16 Jun 2022 09:25:07 +0200 Subject: [PATCH 107/153] overlord/servicestate: refresh security profiles when services get affected by quotas --- overlord/servicestate/export_test.go | 4 + overlord/servicestate/quota_control_test.go | 10 + overlord/servicestate/quota_handlers.go | 72 +++++- overlord/servicestate/quota_handlers_test.go | 241 +++++++++---------- overlord/servicestate/servicemgr.go | 1 + 5 files changed, 186 insertions(+), 142 deletions(-) diff --git a/overlord/servicestate/export_test.go b/overlord/servicestate/export_test.go index fea3ce6fd75..004c475c045 100644 --- a/overlord/servicestate/export_test.go +++ b/overlord/servicestate/export_test.go @@ -40,6 +40,10 @@ func (m *ServiceManager) DoQuotaControl(t *state.Task, to *tomb.Tomb) error { return m.doQuotaControl(t, to) } +func (m *ServiceManager) DoQuotaServiceRestart(t *state.Task, to *tomb.Tomb) error { + return m.doQuotaRestartServices(t, to) +} + func MockOsutilBootID(mockID string) (restore func()) { old := osutilBootID osutilBootID = func() (string, error) { diff --git a/overlord/servicestate/quota_control_test.go b/overlord/servicestate/quota_control_test.go index 8e0ba4655a5..208893dd8b4 100644 --- a/overlord/servicestate/quota_control_test.go +++ b/overlord/servicestate/quota_control_test.go @@ -25,6 +25,7 @@ import ( "time" . "gopkg.in/check.v1" + tomb "gopkg.in/tomb.v2" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget/quantity" @@ -69,6 +70,15 @@ func (s *quotaControlSuite) SetUpTest(c *C) { return nil }) s.AddCleanup(r) + + // Add fake handlers for tasks handled by interfaces manager + fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + task.State().Lock() + _, err := snapstate.TaskSnapSetup(task) + task.State().Unlock() + return err + } + s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) } type quotaGroupState struct { diff --git a/overlord/servicestate/quota_handlers.go b/overlord/servicestate/quota_handlers.go index f0213684516..f2b256241b7 100644 --- a/overlord/servicestate/quota_handlers.go +++ b/overlord/servicestate/quota_handlers.go @@ -26,6 +26,7 @@ import ( tomb "gopkg.in/tomb.v2" + "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/overlord/servicestate/internal" @@ -92,7 +93,7 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { qc := qcs[0] - updated, appsToRestartBySnap, err := quotaStateAlreadyUpdated(t) + updated, servicesAffected, err := quotaStateAlreadyUpdated(t) if err != nil { return err } @@ -123,14 +124,14 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { opts := &ensureSnapServicesForGroupOptions{ allGrps: allGrps, } - appsToRestartBySnap, err = ensureSnapServicesForGroup(st, t, grp, opts) + servicesAffected, err = ensureSnapServicesForGroup(st, t, grp, opts) if err != nil { return err } // All persistent modifications to disk are made and the // modifications to state will be committed by the - // unlocking in restartSnapServices. If snapd gets + // unlocking at the end of this task. If snapd gets // restarted before the end of this task, all the // modifications would be redone, and those // non-idempotent parts of the task would fail. @@ -138,22 +139,69 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { // in state the fact that the changes were made, // to avoid repeating them. // What remains for this task handler is just to - // restart services which will happen regardless if we - // get rebooted after unlocking the state - if we got - // rebooted before unlocking the state, none of the - // changes we made to state would be persisted and we - // would run through everything above here again, but - // the second time around EnsureSnapServices would end - // up doing nothing since it is idempotent. So in the - // rare case that snapd gets restarted but is not a + // refresh security profiles and restart services which + // will happen regardless if we get rebooted after + // unlocking the state - if we got rebooted before unlocking + // the state, none of the changes we made to state would + // be persisted and we would run through everything above + // here again, but the second time around EnsureSnapServices + // would end up doing nothing since it is idempotent. + // So in the rare case that snapd gets restarted but is not a // reboot also record which services do need // restarting. There is a small chance that services // will be restarted again but is preferable to the // quota not applying to them. - if err := rememberQuotaStateUpdated(t, appsToRestartBySnap); err != nil { + if err := rememberQuotaStateUpdated(t, servicesAffected); err != nil { return err } + } + + // As long as there are apps to restart, we also do a profile refresh for them. + // We want to ensure that their profile is up to date after the quota changes. As of + // this moment only journal quotas can affect the security profiles, but it would be hard + // to reliably detect when this is necessary if we want to be robust in case that snapd + // restarts in the middle of this task. So we schedule profile updates as long as there have + // been services affected + if len(servicesAffected) > 0 { + chg := t.Change() + prevTask := t + for info := range servicesAffected { + setupProfilesTask := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Update snap %q (%s) security profiles"), info.SnapName(), info.Revision)) + setupProfilesTask.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{ + RealName: info.SnapName(), + Revision: info.Revision, + }, + }) + setupProfilesTask.WaitFor(prevTask) + chg.AddTask(setupProfilesTask) + prevTask = setupProfilesTask + } + + restartTask := st.NewTask("quota-restart-services", fmt.Sprintf("Restarting services for quota %q", qc.QuotaName)) + if err := rememberQuotaStateUpdated(restartTask, servicesAffected); err != nil { + return err + } + restartTask.WaitFor(prevTask) + chg.AddTask(restartTask) + } + + t.SetStatus(state.DoneStatus) + return nil +} + +func (m *ServiceManager) doQuotaRestartServices(t *state.Task, _ *tomb.Tomb) error { + st := t.State() + st.Lock() + defer st.Unlock() + + perfTimings := state.TimingsForTask(t) + defer perfTimings.Save(st) + + _, appsToRestartBySnap, err := quotaStateAlreadyUpdated(t) + if err != nil { + return err } if err := restartSnapServices(st, t, appsToRestartBySnap, perfTimings); err != nil { diff --git a/overlord/servicestate/quota_handlers_test.go b/overlord/servicestate/quota_handlers_test.go index 706471847eb..6bf16a6bfd3 100644 --- a/overlord/servicestate/quota_handlers_test.go +++ b/overlord/servicestate/quota_handlers_test.go @@ -110,26 +110,15 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreate(c *C) { snapstate.Set(s.state, "test-snap", s.testSnapState) snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) - // make a fake task - t := st.NewTask("create-quota", "...") - - qcs := []servicestate.QuotaControlAction{ - { - Action: "create", - QuotaName: "foo-group", - ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), - AddSnaps: []string{"test-snap"}, - }, + qcs := servicestate.QuotaControlAction{ + Action: "create", + QuotaName: "foo-group", + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), + AddSnaps: []string{"test-snap"}, } - t.Set("quota-control-actions", &qcs) - - st.Unlock() - err := s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() - + err := s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) checkQuotaState(c, st, map[string]quotaGroupState{ "foo-group": { @@ -157,7 +146,8 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { snapstate.Set(s.state, "test-snap", s.testSnapState) snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) - // make a fake task + // make a fake change with a single task + chg := st.NewChange("test", "") t := st.NewTask("create-quota", "...") qcs := []servicestate.QuotaControlAction{ @@ -170,6 +160,7 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { } t.Set("quota-control-actions", &qcs) + chg.AddTask(t) expectedQuotaState := map[string]quotaGroupState{ "foo-group": { @@ -177,12 +168,19 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { Snaps: []string{"test-snap"}, }, } - st.Unlock() + err := s.o.ServiceManager().DoQuotaControl(t, nil) + st.Lock() c.Assert(err, IsNil) + restartTask := chg.Tasks()[len(chg.Tasks())-1] + st.Unlock() + err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + c.Assert(err, IsNil) + + st.Lock() c.Assert(t.Status(), Equals, state.DoneStatus) checkQuotaState(c, st, expectedQuotaState) @@ -191,11 +189,17 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { st.Unlock() err = s.o.ServiceManager().DoQuotaControl(t, nil) + st.Lock() c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) + st.Unlock() + err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + + st.Lock() + c.Assert(err, IsNil) + c.Assert(t.Status(), Equals, state.DoneStatus) checkQuotaState(c, st, expectedQuotaState) } @@ -215,7 +219,8 @@ func (s *quotaHandlersSuite) TestQuotaStateAlreadyUpdatedBehavior(c *C) { snapstate.Set(s.state, "test-snap", s.testSnapState) snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) - // make a fake task + // make a fake change with a task + chg := st.NewChange("test", "") t := st.NewTask("create-quota", "...") qcs := []servicestate.QuotaControlAction{ @@ -228,13 +233,22 @@ func (s *quotaHandlersSuite) TestQuotaStateAlreadyUpdatedBehavior(c *C) { } t.Set("quota-control-actions", &qcs) - + chg.AddTask(t) st.Unlock() + err := s.o.ServiceManager().DoQuotaControl(t, nil) + st.Lock() c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) + + restartTask := chg.Tasks()[len(chg.Tasks())-1] + st.Unlock() + + err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + + st.Lock() + c.Assert(err, IsNil) t.SetStatus(state.DoingStatus) updated, appsToRestart, err := servicestate.QuotaStateAlreadyUpdated(t) @@ -288,45 +302,25 @@ func (s *quotaHandlersSuite) TestDoQuotaControlUpdate(c *C) { snapstate.Set(s.state, "test-snap", s.testSnapState) snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) - // create a quota group - t := st.NewTask("create-quota", "...") - - qcs := []servicestate.QuotaControlAction{ - { - Action: "create", - QuotaName: "foo-group", - ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), - AddSnaps: []string{"test-snap"}, - }, + qcs := servicestate.QuotaControlAction{ + Action: "create", + QuotaName: "foo-group", + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), + AddSnaps: []string{"test-snap"}, } - t.Set("quota-control-actions", &qcs) - - st.Unlock() - err := s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() + err := s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - // create a task for updating the quota group - t = st.NewTask("update-quota", "...") - // update the memory limit to be double - qcs = []servicestate.QuotaControlAction{ - { - Action: "update", - QuotaName: "foo-group", - ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB * 2).Build(), - }, + qcs = servicestate.QuotaControlAction{ + Action: "update", + QuotaName: "foo-group", + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB * 2).Build(), } - t.Set("quota-control-actions", &qcs) - - st.Unlock() - err = s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() - + err = s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) checkQuotaState(c, st, map[string]quotaGroupState{ "foo-group": { @@ -355,39 +349,23 @@ func (s *quotaHandlersSuite) TestDoQuotaControlUpdateRestartOK(c *C) { snapstate.Set(s.state, "test-snap", s.testSnapState) snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) - // create a quota group - t := st.NewTask("create-quota", "...") - - qcs := []servicestate.QuotaControlAction{ - { - Action: "create", - QuotaName: "foo-group", - ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), - AddSnaps: []string{"test-snap"}, - }, + qcs := servicestate.QuotaControlAction{ + Action: "create", + QuotaName: "foo-group", + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), + AddSnaps: []string{"test-snap"}, } - t.Set("quota-control-actions", &qcs) - - st.Unlock() - err := s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() + err := s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - // create a task for updating the quota group - t = st.NewTask("update-quota", "...") - // update the memory limit to be double - qcs = []servicestate.QuotaControlAction{ - { - Action: "update", - QuotaName: "foo-group", - ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB * 2).Build(), - }, + qcs = servicestate.QuotaControlAction{ + Action: "update", + QuotaName: "foo-group", + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB * 2).Build(), } - t.Set("quota-control-actions", &qcs) - expectedQuotaState := map[string]quotaGroupState{ "foo-group": { ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB * 2).Build(), @@ -395,24 +373,14 @@ func (s *quotaHandlersSuite) TestDoQuotaControlUpdateRestartOK(c *C) { }, } - st.Unlock() - err = s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() + err = s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) - checkQuotaState(c, st, expectedQuotaState) - t.SetStatus(state.DoingStatus) - - st.Unlock() - err = s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() + err = s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) - checkQuotaState(c, st, expectedQuotaState) } @@ -437,44 +405,24 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemove(c *C) { snapstate.Set(s.state, "test-snap", s.testSnapState) snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) - // create a quota group - t := st.NewTask("create-quota", "...") - - qcs := []servicestate.QuotaControlAction{ - { - Action: "create", - QuotaName: "foo-group", - ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), - AddSnaps: []string{"test-snap"}, - }, + qcs := servicestate.QuotaControlAction{ + Action: "create", + QuotaName: "foo-group", + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), + AddSnaps: []string{"test-snap"}, } - t.Set("quota-control-actions", &qcs) - - st.Unlock() - err := s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() + err := s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - // create a task for removing the quota group - t = st.NewTask("remove-quota", "...") - // remove quota group - qcs = []servicestate.QuotaControlAction{ - { - Action: "remove", - QuotaName: "foo-group", - }, + qcs = servicestate.QuotaControlAction{ + Action: "remove", + QuotaName: "foo-group", } - t.Set("quota-control-actions", &qcs) - - st.Unlock() - err = s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() - + err = s.callDoQuotaControl(&qcs) c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) checkQuotaState(c, st, nil) } @@ -504,6 +452,7 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) // create a quota group + chg := st.NewChange("remove-quota", "...") t := st.NewTask("create-quota", "...") qcs := []servicestate.QuotaControlAction{ @@ -516,13 +465,22 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { } t.Set("quota-control-actions", &qcs) - + chg.AddTask(t) st.Unlock() + err := s.o.ServiceManager().DoQuotaControl(t, nil) + st.Lock() c.Assert(err, IsNil) + restartTask := chg.Tasks()[len(chg.Tasks())-1] + st.Unlock() + + err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + c.Assert(err, IsNil) - // create a task for removing the quota group + st.Lock() + // create a change and a task for removing the quota group + chg = st.NewChange("remove-quota", "...") t = st.NewTask("remove-quota", "...") // remove quota group @@ -534,23 +492,39 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { } t.Set("quota-control-actions", &qcs) - + chg.AddTask(t) st.Unlock() + err = s.o.ServiceManager().DoQuotaControl(t, nil) + st.Lock() c.Assert(err, IsNil) - c.Assert(t.Status(), Equals, state.DoneStatus) + restartTask = chg.Tasks()[len(chg.Tasks())-1] + st.Unlock() + + err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + c.Assert(err, IsNil) + + st.Lock() checkQuotaState(c, st, nil) t.SetStatus(state.DoingStatus) - st.Unlock() + err = s.o.ServiceManager().DoQuotaControl(t, nil) + st.Lock() c.Assert(err, IsNil) + c.Assert(t.Status(), Equals, state.DoneStatus) + st.Unlock() + + err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + c.Assert(err, IsNil) + st.Lock() + c.Assert(err, IsNil) c.Assert(t.Status(), Equals, state.DoneStatus) checkQuotaState(c, st, nil) @@ -559,14 +533,21 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { func (s *quotaHandlersSuite) callDoQuotaControl(action *servicestate.QuotaControlAction) error { st := s.state qcs := []*servicestate.QuotaControlAction{action} + chg := st.NewChange("quota-control", "...") t := st.NewTask("quota-task", "...") t.Set("quota-control-actions", &qcs) + chg.AddTask(t) st.Unlock() - err := s.o.ServiceManager().DoQuotaControl(t, nil) - st.Lock() + defer st.Lock() - return err + if err := s.o.ServiceManager().DoQuotaControl(t, nil); err != nil { + return err + } + st.Lock() + restartTask := chg.Tasks()[len(chg.Tasks())-1] + st.Unlock() + return s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) } func (s *quotaHandlersSuite) TestQuotaCreatePreseeding(c *C) { diff --git a/overlord/servicestate/servicemgr.go b/overlord/servicestate/servicemgr.go index a9bfe6c592d..e14ffb0ef71 100644 --- a/overlord/servicestate/servicemgr.go +++ b/overlord/servicestate/servicemgr.go @@ -61,6 +61,7 @@ func Manager(st *state.State, runner *state.TaskRunner) *ServiceManager { // TODO: undo handler runner.AddHandler("quota-control", m.doQuotaControl, nil) + runner.AddHandler("quota-restart-services", m.doQuotaRestartServices, nil) snapstate.AddAffectedSnapsByKind("quota-control", quotaControlAffectedSnaps) From 4e697a1abeec1350995b6f2625452dd2c79b8259 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Thu, 16 Jun 2022 10:51:53 +0200 Subject: [PATCH 108/153] overlord/servicestate: only refresh security profiles if a journal quota has changed --- overlord/servicestate/quota_handlers.go | 142 +++++++++++-------- overlord/servicestate/quota_handlers_test.go | 20 +-- 2 files changed, 93 insertions(+), 69 deletions(-) diff --git a/overlord/servicestate/quota_handlers.go b/overlord/servicestate/quota_handlers.go index f2b256241b7..a06244f98c6 100644 --- a/overlord/servicestate/quota_handlers.go +++ b/overlord/servicestate/quota_handlers.go @@ -93,7 +93,7 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { qc := qcs[0] - updated, servicesAffected, err := quotaStateAlreadyUpdated(t) + updated, servicesAffected, refreshProfiles, err := quotaStateAlreadyUpdated(t) if err != nil { return err } @@ -107,11 +107,11 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { var grp *quota.Group switch qc.Action { case "create": - grp, allGrps, err = quotaCreate(st, qc, allGrps) + grp, allGrps, refreshProfiles, err = quotaCreate(st, qc, allGrps) case "remove": - grp, allGrps, err = quotaRemove(st, qc, allGrps) + grp, allGrps, refreshProfiles, err = quotaRemove(st, qc, allGrps) case "update": - grp, allGrps, err = quotaUpdate(st, qc, allGrps) + grp, allGrps, refreshProfiles, err = quotaUpdate(st, qc, allGrps) default: return fmt.Errorf("unknown action %q requested", qc.Action) } @@ -151,46 +151,54 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { // restarting. There is a small chance that services // will be restarted again but is preferable to the // quota not applying to them. - if err := rememberQuotaStateUpdated(t, servicesAffected); err != nil { + if err := rememberQuotaStateUpdated(t, servicesAffected, refreshProfiles); err != nil { return err } } - // As long as there are apps to restart, we also do a profile refresh for them. - // We want to ensure that their profile is up to date after the quota changes. As of - // this moment only journal quotas can affect the security profiles, but it would be hard - // to reliably detect when this is necessary if we want to be robust in case that snapd - // restarts in the middle of this task. So we schedule profile updates as long as there have - // been services affected if len(servicesAffected) > 0 { - chg := t.Change() prevTask := t - for info := range servicesAffected { - setupProfilesTask := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Update snap %q (%s) security profiles"), info.SnapName(), info.Revision)) - setupProfilesTask.Set("snap-setup", &snapstate.SnapSetup{ - SideInfo: &snap.SideInfo{ - RealName: info.SnapName(), - Revision: info.Revision, - }, - }) - setupProfilesTask.WaitFor(prevTask) - chg.AddTask(setupProfilesTask) - prevTask = setupProfilesTask + if refreshProfiles { + prevTask = addRefreshProfileTasks(st, prevTask, servicesAffected) } - - restartTask := st.NewTask("quota-restart-services", fmt.Sprintf("Restarting services for quota %q", qc.QuotaName)) - if err := rememberQuotaStateUpdated(restartTask, servicesAffected); err != nil { + if err := addRestartServicesTask(st, prevTask, qc.QuotaName, servicesAffected); err != nil { return err } - restartTask.WaitFor(prevTask) - - chg.AddTask(restartTask) } t.SetStatus(state.DoneStatus) return nil } +func addRefreshProfileTasks(st *state.State, t *state.Task, servicesAffected map[*snap.Info][]*snap.AppInfo) *state.Task { + chg := t.Change() + prevTask := t + for info := range servicesAffected { + setupProfilesTask := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Update snap %q (%s) security profiles"), info.SnapName(), info.Revision)) + setupProfilesTask.Set("snap-setup", &snapstate.SnapSetup{ + SideInfo: &snap.SideInfo{ + RealName: info.SnapName(), + Revision: info.Revision, + }, + }) + setupProfilesTask.WaitFor(prevTask) + chg.AddTask(setupProfilesTask) + prevTask = setupProfilesTask + } + return prevTask +} + +func addRestartServicesTask(st *state.State, t *state.Task, grpName string, servicesAffected map[*snap.Info][]*snap.AppInfo) error { + chg := t.Change() + restartTask := st.NewTask("quota-restart-services", fmt.Sprintf("Restarting services for quota %q", grpName)) + if err := rememberQuotaStateUpdated(restartTask, servicesAffected, false); err != nil { + return err + } + restartTask.WaitFor(t) + chg.AddTask(restartTask) + return nil +} + func (m *ServiceManager) doQuotaRestartServices(t *state.Task, _ *tomb.Tomb) error { st := t.State() st.Lock() @@ -199,7 +207,7 @@ func (m *ServiceManager) doQuotaRestartServices(t *state.Task, _ *tomb.Tomb) err perfTimings := state.TimingsForTask(t) defer perfTimings.Save(st) - _, appsToRestartBySnap, err := quotaStateAlreadyUpdated(t) + _, appsToRestartBySnap, _, err := quotaStateAlreadyUpdated(t) if err != nil { return err } @@ -216,9 +224,10 @@ var osutilBootID = osutil.BootID type quotaStateUpdated struct { BootID string `json:"boot-id"` AppsToRestartBySnap map[string][]string `json:"apps-to-restart,omitempty"` + RefreshProfiles bool `json:"refresh-profiles,omitempty"` } -func rememberQuotaStateUpdated(t *state.Task, appsToRestartBySnap map[*snap.Info][]*snap.AppInfo) error { +func rememberQuotaStateUpdated(t *state.Task, appsToRestartBySnap map[*snap.Info][]*snap.AppInfo, refreshProfiles bool) error { bootID, err := osutilBootID() if err != nil { return err @@ -234,26 +243,27 @@ func rememberQuotaStateUpdated(t *state.Task, appsToRestartBySnap map[*snap.Info t.Set("state-updated", quotaStateUpdated{ BootID: bootID, AppsToRestartBySnap: appNamesBySnapName, + RefreshProfiles: refreshProfiles, }) return nil } -func quotaStateAlreadyUpdated(t *state.Task) (ok bool, appsToRestartBySnap map[*snap.Info][]*snap.AppInfo, err error) { +func quotaStateAlreadyUpdated(t *state.Task) (ok bool, appsToRestartBySnap map[*snap.Info][]*snap.AppInfo, refreshProfiles bool, err error) { var updated quotaStateUpdated if err := t.Get("state-updated", &updated); err != nil { if errors.Is(err, state.ErrNoState) { - return false, nil, nil + return false, nil, false, nil } - return false, nil, err + return false, nil, false, err } bootID, err := osutilBootID() if err != nil { - return false, nil, err + return false, nil, false, err } if bootID != updated.BootID { // rebooted => nothing to restart - return true, nil, nil + return true, nil, false, nil } appsToRestartBySnap = make(map[*snap.Info][]*snap.AppInfo, len(updated.AppsToRestartBySnap)) @@ -266,7 +276,7 @@ func quotaStateAlreadyUpdated(t *state.Task) (ok bool, appsToRestartBySnap map[* t.Logf("after snapd restart, snap %q went missing", instanceName) continue } - return false, nil, err + return false, nil, false, err } apps := make([]*snap.AppInfo, 0, len(appNames)) for _, appName := range appNames { @@ -278,13 +288,13 @@ func quotaStateAlreadyUpdated(t *state.Task) (ok bool, appsToRestartBySnap map[* } appsToRestartBySnap[info] = apps } - return true, appsToRestartBySnap, nil + return true, appsToRestartBySnap, updated.RefreshProfiles, nil } -func quotaCreate(st *state.State, action QuotaControlAction, allGrps map[string]*quota.Group) (*quota.Group, map[string]*quota.Group, error) { +func quotaCreate(st *state.State, action QuotaControlAction, allGrps map[string]*quota.Group) (*quota.Group, map[string]*quota.Group, bool, error) { // make sure the group does not exist yet if _, ok := allGrps[action.QuotaName]; ok { - return nil, nil, fmt.Errorf("group %q already exists", action.QuotaName) + return nil, nil, false, fmt.Errorf("group %q already exists", action.QuotaName) } // make sure that the parent group exists if we are creating a sub-group @@ -293,47 +303,52 @@ func quotaCreate(st *state.State, action QuotaControlAction, allGrps map[string] var ok bool parentGrp, ok = allGrps[action.ParentName] if !ok { - return nil, nil, fmt.Errorf("cannot create group under non-existent parent group %q", action.ParentName) + return nil, nil, false, fmt.Errorf("cannot create group under non-existent parent group %q", action.ParentName) } } // make sure the resource limits for the group are valid if err := action.ResourceLimits.Validate(); err != nil { - return nil, nil, fmt.Errorf("cannot create quota group %q: %v", action.QuotaName, err) + return nil, nil, false, fmt.Errorf("cannot create quota group %q: %v", action.QuotaName, err) } // make sure the specified snaps exist and aren't currently in another group if err := validateSnapForAddingToGroup(st, action.AddSnaps, action.QuotaName, allGrps); err != nil { - return nil, nil, err + return nil, nil, false, err } - return internal.CreateQuotaInState(st, action.QuotaName, parentGrp, action.AddSnaps, action.ResourceLimits, allGrps) + grp, allGrps, err := internal.CreateQuotaInState(st, action.QuotaName, parentGrp, action.AddSnaps, action.ResourceLimits, allGrps) + if err != nil { + return nil, nil, false, err + } + refreshProfiles := grp.JournalLimit != nil + return grp, allGrps, refreshProfiles, nil } -func quotaRemove(st *state.State, action QuotaControlAction, allGrps map[string]*quota.Group) (*quota.Group, map[string]*quota.Group, error) { +func quotaRemove(st *state.State, action QuotaControlAction, allGrps map[string]*quota.Group) (*quota.Group, map[string]*quota.Group, bool, error) { // make sure the group exists grp, ok := allGrps[action.QuotaName] if !ok { - return nil, nil, fmt.Errorf("cannot remove non-existent quota group %q", action.QuotaName) + return nil, nil, false, fmt.Errorf("cannot remove non-existent quota group %q", action.QuotaName) } // make sure some of the options are not set, it's an internal error if // anything other than the name and action are set for a removal if action.ParentName != "" { - return nil, nil, fmt.Errorf("internal error, ParentName option cannot be used with remove action") + return nil, nil, false, fmt.Errorf("internal error, ParentName option cannot be used with remove action") } if len(action.AddSnaps) != 0 { - return nil, nil, fmt.Errorf("internal error, AddSnaps option cannot be used with remove action") + return nil, nil, false, fmt.Errorf("internal error, AddSnaps option cannot be used with remove action") } if action.ResourceLimits.Memory != nil { - return nil, nil, fmt.Errorf("internal error, MemoryLimit option cannot be used with remove action") + return nil, nil, false, fmt.Errorf("internal error, MemoryLimit option cannot be used with remove action") } // XXX: remove this limitation eventually if len(grp.SubGroups) != 0 { - return nil, nil, fmt.Errorf("cannot remove quota group with sub-groups, remove the sub-groups first") + return nil, nil, false, fmt.Errorf("cannot remove quota group with sub-groups, remove the sub-groups first") } // if this group has a parent, we need to remove the linkage to this @@ -372,13 +387,14 @@ func quotaRemove(st *state.State, action QuotaControlAction, allGrps map[string] // make sure that the group set is consistent before saving it - we may need // to delete old links from this group's parent to the child if err := quota.ResolveCrossReferences(allGrps); err != nil { - return nil, nil, fmt.Errorf("cannot remove quota group %q: %v", action.QuotaName, err) + return nil, nil, false, fmt.Errorf("cannot remove quota group %q: %v", action.QuotaName, err) } // now set it in state st.Set("quotas", allGrps) - return grp, allGrps, nil + refreshProfiles := grp.JournalLimit != nil + return grp, allGrps, refreshProfiles, nil } func quotaUpdateGroupLimits(grp *quota.Group, limits quota.Resources) error { @@ -389,17 +405,17 @@ func quotaUpdateGroupLimits(grp *quota.Group, limits quota.Resources) error { return grp.UpdateQuotaLimits(currentQuotas) } -func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string]*quota.Group) (*quota.Group, map[string]*quota.Group, error) { +func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string]*quota.Group) (*quota.Group, map[string]*quota.Group, bool, error) { // make sure the group exists grp, ok := allGrps[action.QuotaName] if !ok { - return nil, nil, fmt.Errorf("group %q does not exist", action.QuotaName) + return nil, nil, false, fmt.Errorf("group %q does not exist", action.QuotaName) } // check that ParentName is not set, since we don't currently support // re-parenting if action.ParentName != "" { - return nil, nil, fmt.Errorf("group %q cannot be moved to a different parent (re-parenting not yet supported)", action.QuotaName) + return nil, nil, false, fmt.Errorf("group %q cannot be moved to a different parent (re-parenting not yet supported)", action.QuotaName) } modifiedGrps := []*quota.Group{grp} @@ -407,29 +423,35 @@ func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string] // ensure that the group we are modifying does not contain a mix of snaps and sub-groups // as we no longer support this, and existing quota groups might have this if err := ensureGroupIsNotMixed(action.QuotaName, allGrps); err != nil { - return nil, nil, err + return nil, nil, false, err } // now ensure that all of the snaps mentioned in AddSnaps exist as snaps and // that they aren't already in an existing quota group if err := validateSnapForAddingToGroup(st, action.AddSnaps, action.QuotaName, allGrps); err != nil { - return nil, nil, err + return nil, nil, false, err } // append the snaps list in the group grp.Snaps = append(grp.Snaps, action.AddSnaps...) + // store the current status of journal quota, if it changes we need + // to refresh the profiles for the snaps in the groups + refreshProfiles := grp.JournalLimit != nil + // update resource limits for the group if err := quotaUpdateGroupLimits(grp, action.ResourceLimits); err != nil { - return nil, nil, err + return nil, nil, false, err } // update the quota group state allGrps, err := internal.PatchQuotas(st, modifiedGrps...) if err != nil { - return nil, nil, err + return nil, nil, false, err } - return grp, allGrps, nil + + refreshProfiles = refreshProfiles != (grp.JournalLimit != nil) + return grp, allGrps, refreshProfiles, nil } type ensureSnapServicesForGroupOptions struct { diff --git a/overlord/servicestate/quota_handlers_test.go b/overlord/servicestate/quota_handlers_test.go index 6bf16a6bfd3..53ab50c8183 100644 --- a/overlord/servicestate/quota_handlers_test.go +++ b/overlord/servicestate/quota_handlers_test.go @@ -174,6 +174,9 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { st.Lock() c.Assert(err, IsNil) + c.Assert(t.Status(), Equals, state.DoneStatus) + + t.SetStatus(state.DoingStatus) restartTask := chg.Tasks()[len(chg.Tasks())-1] st.Unlock() @@ -181,12 +184,9 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { c.Assert(err, IsNil) st.Lock() - c.Assert(t.Status(), Equals, state.DoneStatus) checkQuotaState(c, st, expectedQuotaState) - t.SetStatus(state.DoingStatus) - st.Unlock() err = s.o.ServiceManager().DoQuotaControl(t, nil) @@ -241,7 +241,9 @@ func (s *quotaHandlersSuite) TestQuotaStateAlreadyUpdatedBehavior(c *C) { st.Lock() c.Assert(err, IsNil) c.Assert(t.Status(), Equals, state.DoneStatus) + c.Assert(len(chg.Tasks()), Equals, 2) + t.SetStatus(state.DoingStatus) restartTask := chg.Tasks()[len(chg.Tasks())-1] st.Unlock() @@ -249,9 +251,8 @@ func (s *quotaHandlersSuite) TestQuotaStateAlreadyUpdatedBehavior(c *C) { st.Lock() c.Assert(err, IsNil) - t.SetStatus(state.DoingStatus) - updated, appsToRestart, err := servicestate.QuotaStateAlreadyUpdated(t) + updated, appsToRestart, _, err := servicestate.QuotaStateAlreadyUpdated(t) c.Assert(err, IsNil) c.Check(updated, Equals, true) c.Assert(appsToRestart, HasLen, 1) @@ -265,20 +266,20 @@ func (s *quotaHandlersSuite) TestQuotaStateAlreadyUpdatedBehavior(c *C) { r = servicestate.MockOsutilBootID("other-boot") defer r() - updated, appsToRestart, err = servicestate.QuotaStateAlreadyUpdated(t) + updated, appsToRestart, _, err = servicestate.QuotaStateAlreadyUpdated(t) c.Assert(err, IsNil) c.Check(updated, Equals, true) c.Check(appsToRestart, HasLen, 0) r() // restored - _, appsToRestart, err = servicestate.QuotaStateAlreadyUpdated(t) + _, appsToRestart, _, err = servicestate.QuotaStateAlreadyUpdated(t) c.Assert(err, IsNil) c.Check(appsToRestart, HasLen, 1) // snap went missing snapstate.Set(s.state, "test-snap", nil) - updated, appsToRestart, err = servicestate.QuotaStateAlreadyUpdated(t) + updated, appsToRestart, _, err = servicestate.QuotaStateAlreadyUpdated(t) c.Assert(err, IsNil) c.Check(updated, Equals, true) c.Check(appsToRestart, HasLen, 0) @@ -500,6 +501,8 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { st.Lock() c.Assert(err, IsNil) c.Assert(t.Status(), Equals, state.DoneStatus) + + t.SetStatus(state.DoingStatus) restartTask = chg.Tasks()[len(chg.Tasks())-1] st.Unlock() @@ -510,7 +513,6 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { checkQuotaState(c, st, nil) - t.SetStatus(state.DoingStatus) st.Unlock() err = s.o.ServiceManager().DoQuotaControl(t, nil) From be5a37d6b5bce6a9589ed896efce89707c3e856b Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Thu, 16 Jun 2022 14:07:29 +0200 Subject: [PATCH 109/153] cmd/snap-update-ns: add /run/systemd to unrestricted paths to allow systemd journal bind mount --- cmd/snap-update-ns/system.go | 2 +- cmd/snap-update-ns/system_test.go | 4 ++-- cmd/snap-update-ns/trespassing_test.go | 29 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cmd/snap-update-ns/system.go b/cmd/snap-update-ns/system.go index bc7acd1c628..29015bbd9b3 100644 --- a/cmd/snap-update-ns/system.go +++ b/cmd/snap-update-ns/system.go @@ -69,7 +69,7 @@ func (upCtx *SystemProfileUpdateContext) Assumptions() *Assumptions { // remapping for parallel installs only when the snap has an instance key as := &Assumptions{} instanceName := upCtx.InstanceName() - as.AddUnrestrictedPaths("/tmp", "/var/snap", "/snap/"+instanceName, "/dev/shm") + as.AddUnrestrictedPaths("/tmp", "/var/snap", "/snap/"+instanceName, "/dev/shm", "/run/systemd") if snapName := snap.InstanceSnap(instanceName); snapName != instanceName { as.AddUnrestrictedPaths("/snap/" + snapName) } diff --git a/cmd/snap-update-ns/system_test.go b/cmd/snap-update-ns/system_test.go index 2d0f60a609e..cb53e7dc13a 100644 --- a/cmd/snap-update-ns/system_test.go +++ b/cmd/snap-update-ns/system_test.go @@ -74,7 +74,7 @@ func (s *systemSuite) TestAssumptions(c *C) { // Non-instances can access /tmp, /var/snap and /snap/$SNAP_NAME upCtx := update.NewSystemProfileUpdateContext("foo", false) as := upCtx.Assumptions() - c.Check(as.UnrestrictedPaths(), DeepEquals, []string{"/tmp", "/var/snap", "/snap/foo", "/dev/shm", "/var/lib/snapd/hostfs/tmp"}) + c.Check(as.UnrestrictedPaths(), DeepEquals, []string{"/tmp", "/var/snap", "/snap/foo", "/dev/shm", "/run/systemd", "/var/lib/snapd/hostfs/tmp"}) c.Check(as.ModeForPath("/stuff"), Equals, os.FileMode(0755)) c.Check(as.ModeForPath("/tmp"), Equals, os.FileMode(0755)) c.Check(as.ModeForPath("/var/lib/snapd/hostfs/tmp"), Equals, os.FileMode(0755)) @@ -87,7 +87,7 @@ func (s *systemSuite) TestAssumptions(c *C) { // Instances can, in addition, access /snap/$SNAP_INSTANCE_NAME upCtx = update.NewSystemProfileUpdateContext("foo_instance", false) as = upCtx.Assumptions() - c.Check(as.UnrestrictedPaths(), DeepEquals, []string{"/tmp", "/var/snap", "/snap/foo_instance", "/dev/shm", "/snap/foo", "/var/lib/snapd/hostfs/tmp"}) + c.Check(as.UnrestrictedPaths(), DeepEquals, []string{"/tmp", "/var/snap", "/snap/foo_instance", "/dev/shm", "/run/systemd", "/snap/foo", "/var/lib/snapd/hostfs/tmp"}) } func (s *systemSuite) TestLoadDesiredProfile(c *C) { diff --git a/cmd/snap-update-ns/trespassing_test.go b/cmd/snap-update-ns/trespassing_test.go index aac2abe36b1..fe55853a605 100644 --- a/cmd/snap-update-ns/trespassing_test.go +++ b/cmd/snap-update-ns/trespassing_test.go @@ -347,6 +347,35 @@ func (s *trespassingSuite) TestRestrictionsForVarSnap(c *C) { rs.Lift() } +func (s *trespassingSuite) TestRestrictionsForRunSystemd(c *C) { + a := &update.Assumptions{} + a.AddUnrestrictedPaths("/run/systemd") + + // There should be no restrictions under /run/systemd + rs := a.RestrictionsFor("/run/systemd/journal") + c.Assert(rs, IsNil) + rs = a.RestrictionsFor("/run/systemd/journal.namespace") + c.Assert(rs, IsNil) + + // however we should still disallow anything else under /run + rs = a.RestrictionsFor("/run/test.txt") + c.Assert(rs, NotNil) + + fd, err := s.sys.Open("/run", syscall.O_DIRECTORY, 0) + c.Assert(err, IsNil) + defer s.sys.Close(fd) + s.sys.InsertFstatfsResult(`fstatfs 3 `, syscall.Statfs_t{Type: update.Ext4Magic}) + s.sys.InsertFstatResult(`fstat 3 `, syscall.Stat_t{}) + + err = rs.Check(fd, "/run") + c.Assert(err, ErrorMatches, `cannot write to "/run/test.txt" because it would affect the host in "/run"`) + c.Assert(err.(*update.TrespassingError).ViolatedPath, Equals, "/run") + c.Assert(err.(*update.TrespassingError).DesiredPath, Equals, "/run/test.txt") + + rs.Lift() + c.Assert(rs.Check(fd, "/run"), IsNil) +} + func (s *trespassingSuite) TestRestrictionsForRootfsEntries(c *C) { a := &update.Assumptions{} From 11d31b996c73945a3c5c3168ff26a80454a70257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bu=C4=9Fra=20Aydo=C4=9Far?= Date: Thu, 16 Jun 2022 16:16:16 +0300 Subject: [PATCH 110/153] interfaces: update AppArmor template to allow reading snap's memory statistics --- interfaces/apparmor/template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index 0e9d0dc63b1..bcbbd314aba 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -285,6 +285,7 @@ var templateCommon = ` /sys/devices/virtual/tty/{console,tty*}/active r, /sys/fs/cgroup/memory/{,user.slice/}memory.limit_in_bytes r, /sys/fs/cgroup/memory/{,**/}snap.@{SNAP_INSTANCE_NAME}{,.*}/memory.limit_in_bytes r, + /sys/fs/cgroup/memory/{,**/}snap.@{SNAP_INSTANCE_NAME}{,.*}/memory.stat r, /sys/fs/cgroup/cpu,cpuacct/{,user.slice/}cpu.cfs_{period,quota}_us r, /sys/fs/cgroup/cpu,cpuacct/{,**/}snap.@{SNAP_INSTANCE_NAME}{,.*}/cpu.cfs_{period,quota}_us r, /sys/fs/cgroup/cpu,cpuacct/{,user.slice/}cpu.shares r, From ac945e631f37c984c0729a6ade257f6f5ed6e8fa Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Fri, 17 Jun 2022 08:44:01 +0200 Subject: [PATCH 111/153] snap: review feedback, add additional validation tests --- snap/validate_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/snap/validate_test.go b/snap/validate_test.go index 558b88d1e00..e520cb34006 100644 --- a/snap/validate_test.go +++ b/snap/validate_test.go @@ -945,6 +945,10 @@ func (s *ValidateSuite) TestValidateLayout(c *C) { ErrorMatches, `layout "/sys" in an off-limits area`) c.Check(ValidateLayout(&Layout{Snap: si, Path: "/run", Type: "tmpfs"}, nil), ErrorMatches, `layout "/run" in an off-limits area`) + c.Check(ValidateLayout(&Layout{Snap: si, Path: "/run/foo", Type: "tmpfs"}, nil), + ErrorMatches, `layout "/run/foo" in an off-limits area`) + c.Check(ValidateLayout(&Layout{Snap: si, Path: "/run/systemd", Type: "tmpfs"}, nil), + ErrorMatches, `layout "/run/systemd" in an off-limits area`) c.Check(ValidateLayout(&Layout{Snap: si, Path: "/boot", Type: "tmpfs"}, nil), ErrorMatches, `layout "/boot" in an off-limits area`) c.Check(ValidateLayout(&Layout{Snap: si, Path: "/lost+found", Type: "tmpfs"}, nil), From 6413f18c19b1581e8b6bd9efb4c0eb161616dac4 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Fri, 17 Jun 2022 09:20:31 +0200 Subject: [PATCH 112/153] overlord/servicestate: use builtin service-control task instead of having an additional --- overlord/servicestate/export_test.go | 4 +- overlord/servicestate/quota_handlers.go | 52 ++++++++++---------- overlord/servicestate/quota_handlers_test.go | 43 +++++++++++----- overlord/servicestate/servicemgr.go | 1 - 4 files changed, 59 insertions(+), 41 deletions(-) diff --git a/overlord/servicestate/export_test.go b/overlord/servicestate/export_test.go index 004c475c045..8543db6c49c 100644 --- a/overlord/servicestate/export_test.go +++ b/overlord/servicestate/export_test.go @@ -40,8 +40,8 @@ func (m *ServiceManager) DoQuotaControl(t *state.Task, to *tomb.Tomb) error { return m.doQuotaControl(t, to) } -func (m *ServiceManager) DoQuotaServiceRestart(t *state.Task, to *tomb.Tomb) error { - return m.doQuotaRestartServices(t, to) +func (m *ServiceManager) DoServiceControl(t *state.Task, to *tomb.Tomb) error { + return m.doServiceControl(t, to) } func MockOsutilBootID(mockID string) (restore func()) { diff --git a/overlord/servicestate/quota_handlers.go b/overlord/servicestate/quota_handlers.go index a06244f98c6..4700655b16c 100644 --- a/overlord/servicestate/quota_handlers.go +++ b/overlord/servicestate/quota_handlers.go @@ -161,9 +161,7 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { if refreshProfiles { prevTask = addRefreshProfileTasks(st, prevTask, servicesAffected) } - if err := addRestartServicesTask(st, prevTask, qc.QuotaName, servicesAffected); err != nil { - return err - } + addRestartServicesTasks(st, prevTask, qc.QuotaName, servicesAffected) } t.SetStatus(state.DoneStatus) @@ -188,35 +186,37 @@ func addRefreshProfileTasks(st *state.State, t *state.Task, servicesAffected map return prevTask } -func addRestartServicesTask(st *state.State, t *state.Task, grpName string, servicesAffected map[*snap.Info][]*snap.AppInfo) error { +func addRestartServicesTasks(st *state.State, t *state.Task, grpName string, servicesAffected map[*snap.Info][]*snap.AppInfo) { chg := t.Change() - restartTask := st.NewTask("quota-restart-services", fmt.Sprintf("Restarting services for quota %q", grpName)) - if err := rememberQuotaStateUpdated(restartTask, servicesAffected, false); err != nil { - return err - } - restartTask.WaitFor(t) - chg.AddTask(restartTask) - return nil -} -func (m *ServiceManager) doQuotaRestartServices(t *state.Task, _ *tomb.Tomb) error { - st := t.State() - st.Lock() - defer st.Unlock() - - perfTimings := state.TimingsForTask(t) - defer perfTimings.Save(st) + getServiceNames := func(services []*snap.AppInfo) []string { + var names []string + for _, svc := range services { + names = append(names, svc.Name) + } + return names + } - _, appsToRestartBySnap, _, err := quotaStateAlreadyUpdated(t) - if err != nil { - return err + sortedInfos := make([]*snap.Info, 0, len(servicesAffected)) + for info := range servicesAffected { + sortedInfos = append(sortedInfos, info) } + sort.Slice(sortedInfos, func(i, j int) bool { + return sortedInfos[i].InstanceName() < sortedInfos[j].InstanceName() + }) - if err := restartSnapServices(st, t, appsToRestartBySnap, perfTimings); err != nil { - return err + prevTask := t + for _, info := range sortedInfos { + restartTask := st.NewTask("service-control", fmt.Sprintf("Restarting services for snap %q", info.InstanceName())) + restartTask.Set("service-action", ServiceAction{ + Action: "restart", + SnapName: info.InstanceName(), + Services: getServiceNames(servicesAffected[info]), + }) + restartTask.WaitFor(prevTask) + chg.AddTask(restartTask) + prevTask = restartTask } - t.SetStatus(state.DoneStatus) - return nil } var osutilBootID = osutil.BootID diff --git a/overlord/servicestate/quota_handlers_test.go b/overlord/servicestate/quota_handlers_test.go index 53ab50c8183..4e249e62466 100644 --- a/overlord/servicestate/quota_handlers_test.go +++ b/overlord/servicestate/quota_handlers_test.go @@ -128,6 +128,25 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreate(c *C) { }) } +func (s *quotaHandlersSuite) getRestartTasks(chg *state.Change) []*state.Task { + var tasks []*state.Task + for _, t := range chg.Tasks() { + if t.Kind() == "service-control" { + tasks = append(tasks, t) + } + } + return tasks +} + +func (s *quotaHandlersSuite) runRestartTasks(tasks []*state.Task) error { + for _, t := range tasks { + if err := s.o.ServiceManager().DoServiceControl(t, nil); err != nil { + return err + } + } + return nil +} + func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { // test a situation where because of restart the task is reentered r := s.mockSystemctlCalls(c, join( @@ -177,10 +196,10 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { c.Assert(t.Status(), Equals, state.DoneStatus) t.SetStatus(state.DoingStatus) - restartTask := chg.Tasks()[len(chg.Tasks())-1] + restartTasks := s.getRestartTasks(chg) st.Unlock() - err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + err = s.runRestartTasks(restartTasks) c.Assert(err, IsNil) st.Lock() @@ -195,7 +214,7 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { c.Assert(t.Status(), Equals, state.DoneStatus) st.Unlock() - err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + err = s.runRestartTasks(restartTasks) st.Lock() c.Assert(err, IsNil) @@ -244,10 +263,10 @@ func (s *quotaHandlersSuite) TestQuotaStateAlreadyUpdatedBehavior(c *C) { c.Assert(len(chg.Tasks()), Equals, 2) t.SetStatus(state.DoingStatus) - restartTask := chg.Tasks()[len(chg.Tasks())-1] + restartTasks := s.getRestartTasks(chg) st.Unlock() - err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + err = s.runRestartTasks(restartTasks) st.Lock() c.Assert(err, IsNil) @@ -473,10 +492,10 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { st.Lock() c.Assert(err, IsNil) - restartTask := chg.Tasks()[len(chg.Tasks())-1] + restartTasks := s.getRestartTasks(chg) st.Unlock() - err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + err = s.runRestartTasks(restartTasks) c.Assert(err, IsNil) st.Lock() @@ -503,10 +522,10 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { c.Assert(t.Status(), Equals, state.DoneStatus) t.SetStatus(state.DoingStatus) - restartTask = chg.Tasks()[len(chg.Tasks())-1] + restartTasks = s.getRestartTasks(chg) st.Unlock() - err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + err = s.runRestartTasks(restartTasks) c.Assert(err, IsNil) st.Lock() @@ -522,7 +541,7 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { c.Assert(t.Status(), Equals, state.DoneStatus) st.Unlock() - err = s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + err = s.runRestartTasks(restartTasks) c.Assert(err, IsNil) st.Lock() @@ -547,9 +566,9 @@ func (s *quotaHandlersSuite) callDoQuotaControl(action *servicestate.QuotaContro return err } st.Lock() - restartTask := chg.Tasks()[len(chg.Tasks())-1] + restartTasks := s.getRestartTasks(chg) st.Unlock() - return s.o.ServiceManager().DoQuotaServiceRestart(restartTask, nil) + return s.runRestartTasks(restartTasks) } func (s *quotaHandlersSuite) TestQuotaCreatePreseeding(c *C) { diff --git a/overlord/servicestate/servicemgr.go b/overlord/servicestate/servicemgr.go index e14ffb0ef71..a9bfe6c592d 100644 --- a/overlord/servicestate/servicemgr.go +++ b/overlord/servicestate/servicemgr.go @@ -61,7 +61,6 @@ func Manager(st *state.State, runner *state.TaskRunner) *ServiceManager { // TODO: undo handler runner.AddHandler("quota-control", m.doQuotaControl, nil) - runner.AddHandler("quota-restart-services", m.doQuotaRestartServices, nil) snapstate.AddAffectedSnapsByKind("quota-control", quotaControlAffectedSnaps) From a1321d9c4ee0ee2ccce82e93616e328025b1e6db Mon Sep 17 00:00:00 2001 From: Valentin David Date: Fri, 17 Jun 2022 12:12:34 +0200 Subject: [PATCH 113/153] tests/core/basic20: Enable on uc22 and rename --- tests/core/{basic20 => basic20plus}/task.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) rename tests/core/{basic20 => basic20plus}/task.yaml (96%) diff --git a/tests/core/basic20/task.yaml b/tests/core/basic20plus/task.yaml similarity index 96% rename from tests/core/basic20/task.yaml rename to tests/core/basic20plus/task.yaml index cf5d05821bc..1eb054b7937 100644 --- a/tests/core/basic20/task.yaml +++ b/tests/core/basic20plus/task.yaml @@ -1,10 +1,8 @@ -summary: Check basic core20 system functionality +summary: Check basic core20 and later system functionality systems: - ubuntu-core-20-* - # TODO: - # depends on https://github.com/snapcore/core-base/issues/37 - # - ubuntu-core-22-* + - ubuntu-core-22-* execute: | case "$SPREAD_SYSTEM" in From 60065ef5afe9a4f99be417c3eea60e95e1112e24 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Mon, 20 Jun 2022 12:57:34 +0200 Subject: [PATCH 114/153] overlord/servicestate: review feedback update scheduling of tasks, update some docs for ensureSnapServicesStateForGroup, add more unit tests for journal quota handlers --- overlord/servicestate/quota_control_test.go | 45 +++- overlord/servicestate/quota_handlers.go | 96 ++++--- overlord/servicestate/quota_handlers_test.go | 270 ++++++++++++++++++- 3 files changed, 354 insertions(+), 57 deletions(-) diff --git a/overlord/servicestate/quota_control_test.go b/overlord/servicestate/quota_control_test.go index 208893dd8b4..1824a5b5845 100644 --- a/overlord/servicestate/quota_control_test.go +++ b/overlord/servicestate/quota_control_test.go @@ -38,6 +38,7 @@ import ( "github.com/snapcore/snapd/snap/quota" "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/snapdenv" + "github.com/snapcore/snapd/strutil" "github.com/snapcore/snapd/systemd" "github.com/snapcore/snapd/testutil" ) @@ -95,8 +96,6 @@ func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { for name, grp := range m { expGrp, ok := exp[name] c.Assert(ok, Equals, true, Commentf("unexpected group %q in state", name)) - c.Assert(expGrp.ResourceLimits.Memory, NotNil) - c.Assert(grp.MemoryLimit, Equals, expGrp.ResourceLimits.Memory.Limit) c.Assert(grp.ParentGroup, Equals, expGrp.ParentGroup) c.Assert(grp.Snaps, HasLen, len(expGrp.Snaps)) @@ -110,7 +109,7 @@ func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { if grp.ParentGroup != "" { slicePath = grp.ParentGroup + "/" + name } - checkSvcAndSliceState(c, sn+".svc1", slicePath, grp.MemoryLimit) + checkSvcAndSliceState(c, sn+".svc1", slicePath, grp.GetQuotaResources()) } } @@ -121,28 +120,50 @@ func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { } } -func checkSvcAndSliceState(c *C, snapSvc string, slicePath string, sliceMem quantity.Size) { +func shouldMentionSlice(resources quota.Resources) bool { + // If no quota is set, then Validate returns an error. And only + // valid quotas will get written to the slice file. + if err := resources.Validate(); err != nil { + return false + } + return true +} + +func checkSvcAndSliceState(c *C, snapSvc string, slicePath string, resources quota.Resources) { slicePath = systemd.EscapeUnitNamePath(slicePath) // make sure the service file exists svcFileName := filepath.Join(dirs.SnapServicesDir, "snap."+snapSvc+".service") c.Assert(svcFileName, testutil.FilePresent) - if sliceMem != 0 { + if shouldMentionSlice(resources) { // the service file should mention this slice c.Assert(svcFileName, testutil.FileContains, fmt.Sprintf("\nSlice=snap.%s.slice\n", slicePath)) } else { c.Assert(svcFileName, Not(testutil.FileContains), fmt.Sprintf("Slice=snap.%s.slice", slicePath)) } - checkSliceState(c, slicePath, sliceMem) + checkSliceState(c, slicePath, resources) } -func checkSliceState(c *C, sliceName string, sliceMem quantity.Size) { +func checkSliceState(c *C, sliceName string, resources quota.Resources) { sliceFileName := filepath.Join(dirs.SnapServicesDir, "snap."+sliceName+".slice") - if sliceMem != 0 { - c.Assert(sliceFileName, testutil.FilePresent) - c.Assert(sliceFileName, testutil.FileContains, fmt.Sprintf("\nMemoryMax=%s\n", sliceMem.String())) - } else { - c.Assert(sliceFileName, testutil.FileAbsent) + if !shouldMentionSlice(resources) { + c.Assert(sliceFileName, Not(testutil.FilePresent)) + return + } + + c.Assert(sliceFileName, testutil.FilePresent) + if resources.Memory != nil { + c.Assert(sliceFileName, testutil.FileContains, fmt.Sprintf("\nMemoryMax=%s\n", resources.Memory.Limit.String())) + } + if resources.CPU != nil { + c.Assert(sliceFileName, testutil.FileContains, fmt.Sprintf("\nCPUQuota=%d%%\n", resources.CPU.Count*resources.CPU.Percentage)) + } + if resources.CPUSet != nil { + allowedCpusValue := strutil.IntsToCommaSeparated(resources.CPUSet.CPUs) + c.Assert(sliceFileName, testutil.FileContains, fmt.Sprintf("\nAllowedCPUs=%s\n", allowedCpusValue)) + } + if resources.Threads != nil { + c.Assert(sliceFileName, testutil.FileContains, fmt.Sprintf("\nThreadsMax=%d\n", resources.Threads.Limit)) } } diff --git a/overlord/servicestate/quota_handlers.go b/overlord/servicestate/quota_handlers.go index 4700655b16c..2135d8c9808 100644 --- a/overlord/servicestate/quota_handlers.go +++ b/overlord/servicestate/quota_handlers.go @@ -22,6 +22,7 @@ package servicestate import ( "errors" "fmt" + "log" "sort" tomb "gopkg.in/tomb.v2" @@ -156,21 +157,32 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { } } + log.Println("[QUOTA]", "quota-control", len(servicesAffected), refreshProfiles) if len(servicesAffected) > 0 { - prevTask := t + var prevTaskSet *state.TaskSet + chg := t.Change() + queueTasks := func(ts *state.TaskSet) { + if prevTaskSet != nil { + prevTaskSet.WaitAll(ts) + } else { + ts.WaitFor(t) + } + chg.AddAll(ts) + prevTaskSet = ts + } + if refreshProfiles { - prevTask = addRefreshProfileTasks(st, prevTask, servicesAffected) + queueTasks(addRefreshProfileTasks(st, servicesAffected)) } - addRestartServicesTasks(st, prevTask, qc.QuotaName, servicesAffected) + queueTasks(addRestartServicesTasks(st, qc.QuotaName, servicesAffected)) } t.SetStatus(state.DoneStatus) return nil } -func addRefreshProfileTasks(st *state.State, t *state.Task, servicesAffected map[*snap.Info][]*snap.AppInfo) *state.Task { - chg := t.Change() - prevTask := t +func addRefreshProfileTasks(st *state.State, servicesAffected map[*snap.Info][]*snap.AppInfo) *state.TaskSet { + ts := state.NewTaskSet() for info := range servicesAffected { setupProfilesTask := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Update snap %q (%s) security profiles"), info.SnapName(), info.Revision)) setupProfilesTask.Set("snap-setup", &snapstate.SnapSetup{ @@ -179,15 +191,14 @@ func addRefreshProfileTasks(st *state.State, t *state.Task, servicesAffected map Revision: info.Revision, }, }) - setupProfilesTask.WaitFor(prevTask) - chg.AddTask(setupProfilesTask) - prevTask = setupProfilesTask + setupProfilesTask.WaitAll(ts) + ts.AddTask(setupProfilesTask) } - return prevTask + return ts } -func addRestartServicesTasks(st *state.State, t *state.Task, grpName string, servicesAffected map[*snap.Info][]*snap.AppInfo) { - chg := t.Change() +func addRestartServicesTasks(st *state.State, grpName string, servicesAffected map[*snap.Info][]*snap.AppInfo) *state.TaskSet { + ts := state.NewTaskSet() getServiceNames := func(services []*snap.AppInfo) []string { var names []string @@ -205,7 +216,6 @@ func addRestartServicesTasks(st *state.State, t *state.Task, grpName string, ser return sortedInfos[i].InstanceName() < sortedInfos[j].InstanceName() }) - prevTask := t for _, info := range sortedInfos { restartTask := st.NewTask("service-control", fmt.Sprintf("Restarting services for snap %q", info.InstanceName())) restartTask.Set("service-action", ServiceAction{ @@ -213,10 +223,10 @@ func addRestartServicesTasks(st *state.State, t *state.Task, grpName string, ser SnapName: info.InstanceName(), Services: getServiceNames(servicesAffected[info]), }) - restartTask.WaitFor(prevTask) - chg.AddTask(restartTask) - prevTask = restartTask + restartTask.WaitAll(ts) + ts.AddTask(restartTask) } + return ts } var osutilBootID = osutil.BootID @@ -438,6 +448,7 @@ func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string] // store the current status of journal quota, if it changes we need // to refresh the profiles for the snaps in the groups refreshProfiles := grp.JournalLimit != nil + log.Println("[DEBUG] quotaUpdate: refreshProfiles", refreshProfiles) // update resource limits for the group if err := quotaUpdateGroupLimits(grp, action.ResourceLimits); err != nil { @@ -451,6 +462,7 @@ func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string] } refreshProfiles = refreshProfiles != (grp.JournalLimit != nil) + log.Println("[DEBUG] quotaUpdate: refreshProfiles", refreshProfiles) return grp, allGrps, refreshProfiles, nil } @@ -522,6 +534,15 @@ func ensureSnapServicesForGroup(st *state.State, t *state.Task, grp *quota.Group grpsToStart := []*quota.Group{} appsToRestartBySnap = map[*snap.Info][]*snap.AppInfo{} + markAppForRestart := func(info *snap.Info, app *snap.AppInfo) { + // make sure it is not already in the list + for _, a := range appsToRestartBySnap[info] { + if a.Name == app.Name { + return + } + } + appsToRestartBySnap[info] = append(appsToRestartBySnap[info], app) + } collectModifiedUnits := func(app *snap.AppInfo, grp *quota.Group, unitType string, name, old, new string) { switch unitType { @@ -564,12 +585,22 @@ func ensureSnapServicesForGroup(st *state.State, t *state.Task, grp *quota.Group // in this case, the only way that a service could have been changed // was if it was moved into or out of a slice, in both cases we need // to restart the service - sn := app.Snap - appsToRestartBySnap[sn] = append(appsToRestartBySnap[sn], app) + markAppForRestart(app.Snap, app) // TODO: what about sockets and timers? activation units just start // the full unit, so as long as the full unit is restarted we should // be okay? + + case "journald": + // this happens when a journal quota is either added, modified or removed, and + // in this case we need to restart all services in the quota group + for info := range snapSvcMap { + for _, app := range info.Apps { + if app.IsService() { + markAppForRestart(info, app) + } + } + } } } if err := wrappers.EnsureSnapServices(snapSvcMap, ensureOpts, collectModifiedUnits, meterLocked); err != nil { @@ -616,25 +647,14 @@ func ensureSnapServicesForGroup(st *state.State, t *state.Task, grp *quota.Group return appsToRestartBySnap, nil } -// restartSnapServices is used to restart the services for each snap -// that was newly moved into a quota group iterate in a sorted order -// over the snaps to restart their apps for easy tests. -func restartSnapServices(st *state.State, t *state.Task, appsToRestartBySnap map[*snap.Info][]*snap.AppInfo, perfTimings *timings.Timings) error { +// restartSnapServices is used to restart the services for snaps that +// have been modified. Snaps and services are sorted before they are +// restarted to provide a consistent ordering of restarts to be testable. +func restartSnapServices(st *state.State, appsToRestartBySnap map[*snap.Info][]*snap.AppInfo) error { if len(appsToRestartBySnap) == 0 { return nil } - var meterUnlocked progress.Meter - if t == nil { - meterUnlocked = progress.Null - } else { - meterUnlocked = snapstate.NewTaskProgressAdapterUnlocked(t) - } - - if perfTimings == nil { - perfTimings = &timings.Timings{} - } - st.Unlock() defer st.Lock() @@ -653,7 +673,7 @@ func restartSnapServices(st *state.State, t *state.Task, appsToRestartBySnap map return err } - err = wrappers.RestartServices(startupOrdered, nil, nil, meterUnlocked, perfTimings) + err = wrappers.RestartServices(startupOrdered, nil, nil, progress.Null, &timings.Timings{}) if err != nil { return err } @@ -661,13 +681,17 @@ func restartSnapServices(st *state.State, t *state.Task, appsToRestartBySnap map return nil } -// ensureSnapServicesStateForGroup combines ensureSnapServicesForGroup and restartSnapServices +// ensureSnapServicesStateForGroup combines ensureSnapServicesForGroup and restartSnapServices. +// This does not refresh security profiles for snaps in the quota group, which is required +// for modifications to a journal quota. Currently this function is used when removing a +// snap from the system which will cause an update(removal) of security profiles, +// and thus won't should not cause a conflict. func ensureSnapServicesStateForGroup(st *state.State, grp *quota.Group, opts *ensureSnapServicesForGroupOptions) error { appsToRestartBySnap, err := ensureSnapServicesForGroup(st, nil, grp, opts) if err != nil { return err } - return restartSnapServices(st, nil, appsToRestartBySnap, nil) + return restartSnapServices(st, appsToRestartBySnap) } func ensureGroupIsNotMixed(group string, allGrps map[string]*quota.Group) error { diff --git a/overlord/servicestate/quota_handlers_test.go b/overlord/servicestate/quota_handlers_test.go index 4e249e62466..ada182b3ac2 100644 --- a/overlord/servicestate/quota_handlers_test.go +++ b/overlord/servicestate/quota_handlers_test.go @@ -21,12 +21,16 @@ package servicestate_test import ( "errors" + "time" + "gopkg.in/check.v1" . "gopkg.in/check.v1" + tomb "gopkg.in/tomb.v2" "github.com/snapcore/snapd/gadget/quantity" "github.com/snapcore/snapd/overlord/configstate/config" "github.com/snapcore/snapd/overlord/servicestate" + "github.com/snapcore/snapd/overlord/servicestate/servicestatetest" "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" @@ -128,10 +132,10 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreate(c *C) { }) } -func (s *quotaHandlersSuite) getRestartTasks(chg *state.Change) []*state.Task { +func (s *quotaHandlersSuite) getTasksOfKind(chg *state.Change, kind string) []*state.Task { var tasks []*state.Task for _, t := range chg.Tasks() { - if t.Kind() == "service-control" { + if t.Kind() == kind { tasks = append(tasks, t) } } @@ -196,7 +200,7 @@ func (s *quotaHandlersSuite) TestDoQuotaControlCreateRestartOK(c *C) { c.Assert(t.Status(), Equals, state.DoneStatus) t.SetStatus(state.DoingStatus) - restartTasks := s.getRestartTasks(chg) + restartTasks := s.getTasksOfKind(chg, "service-control") st.Unlock() err = s.runRestartTasks(restartTasks) @@ -263,7 +267,7 @@ func (s *quotaHandlersSuite) TestQuotaStateAlreadyUpdatedBehavior(c *C) { c.Assert(len(chg.Tasks()), Equals, 2) t.SetStatus(state.DoingStatus) - restartTasks := s.getRestartTasks(chg) + restartTasks := s.getTasksOfKind(chg, "service-control") st.Unlock() err = s.runRestartTasks(restartTasks) @@ -492,7 +496,7 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { st.Lock() c.Assert(err, IsNil) - restartTasks := s.getRestartTasks(chg) + restartTasks := s.getTasksOfKind(chg, "service-control") st.Unlock() err = s.runRestartTasks(restartTasks) @@ -522,7 +526,7 @@ func (s *quotaHandlersSuite) TestDoQuotaControlRemoveRestartOK(c *C) { c.Assert(t.Status(), Equals, state.DoneStatus) t.SetStatus(state.DoingStatus) - restartTasks = s.getRestartTasks(chg) + restartTasks = s.getTasksOfKind(chg, "service-control") st.Unlock() err = s.runRestartTasks(restartTasks) @@ -566,7 +570,7 @@ func (s *quotaHandlersSuite) callDoQuotaControl(action *servicestate.QuotaContro return err } st.Lock() - restartTasks := s.getRestartTasks(chg) + restartTasks := s.getTasksOfKind(chg, "service-control") st.Unlock() return s.runRestartTasks(restartTasks) } @@ -751,7 +755,8 @@ func (s *quotaHandlersSuite) TestDoCreateSubGroupQuota(c *C) { }) // foo-group exists as a slice too, but has no snap services in the slice - checkSliceState(c, systemd.EscapeUnitNamePath("foo-group"), quantity.SizeGiB) + checkSliceState(c, systemd.EscapeUnitNamePath("foo-group"), + quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build()) } func (s *quotaHandlersSuite) TestQuotaRemove(c *C) { @@ -904,7 +909,7 @@ func (s *quotaHandlersSuite) TestQuotaRemove(c *C) { checkQuotaState(c, st, nil) // foo is not mentioned in the service and doesn't exist - checkSvcAndSliceState(c, "test-snap.svc1", "foo", 0) + checkSvcAndSliceState(c, "test-snap.svc1", "foo", quota.NewResourcesBuilder().Build()) } func (s *quotaHandlersSuite) TestQuotaSnapModifyExistingMixable(c *C) { @@ -1262,6 +1267,253 @@ func (s *quotaHandlersSuite) TestQuotaUpdateChangeMemLimit(c *C) { c.Assert(err, ErrorMatches, "cannot update limits for group \"foo\": cannot decrease memory limit, remove and re-create it to decrease the limit") } +func (s *quotaHandlersSuite) TestCreateJournalQuota(c *C) { + r := s.mockSystemctlCalls(c, join( + // CreateQuota for foo + systemctlCallsForCreateQuota("foo", "test-snap"), + )) + defer r() + + // Add fake handlers for the setup-profiles task which should be invoked + // when creating the journal quota. + var setupProfilesCalled int + fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + task.State().Lock() + _, err := snapstate.TaskSnapSetup(task) + task.State().Unlock() + setupProfilesCalled++ + return err + } + s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) + + st := s.state + st.Lock() + defer st.Unlock() + + // setup the snap so it exists + snapstate.Set(s.state, "test-snap", s.testSnapState) + snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) + + qc := servicestate.QuotaControlAction{ + Action: "create", + QuotaName: "foo", + ResourceLimits: quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB * 64).Build(), + AddSnaps: []string{"test-snap"}, + } + qcs := []*servicestate.QuotaControlAction{&qc} + + chg := st.NewChange("quota-control-tasks", "...") + t := st.NewTask("quota-control", "...") + t.Set("quota-control-actions", &qcs) + chg.AddTask(t) + + st.Unlock() + defer s.se.Stop() + err := s.o.Settle(5 * time.Second) + st.Lock() + c.Check(err, IsNil) + c.Check(setupProfilesCalled, Equals, 1) + checkQuotaState(c, st, map[string]quotaGroupState{ + "foo": { + ResourceLimits: quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB * 64).Build(), + Snaps: []string{"test-snap"}, + }, + }) +} + +func (s *quotaHandlersSuite) TestAddJournalQuota(c *C) { + r := s.mockSystemctlCalls(c, join( + // CreateQuota for foo + systemctlCallsForCreateQuota("foo", "test-snap"), + + // UpdateQuota for foo + []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, + systemctlCallsForServiceRestart("test-snap"), + )) + defer r() + + // Add fake handlers for the setup-profiles task which should be invoked + // when creating the journal quota. + var setupProfilesCalled int + fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + task.State().Lock() + _, err := snapstate.TaskSnapSetup(task) + task.State().Unlock() + setupProfilesCalled++ + return err + } + s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) + + st := s.state + st.Lock() + defer st.Unlock() + + // setup the snap so it exists + snapstate.Set(s.state, "test-snap", s.testSnapState) + snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) + + qc := servicestate.QuotaControlAction{ + Action: "create", + QuotaName: "foo", + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), + AddSnaps: []string{"test-snap"}, + } + qcs := []*servicestate.QuotaControlAction{&qc} + + chg := st.NewChange("quota-control-tasks", "...") + t := st.NewTask("quota-control", "...") + t.Set("quota-control-actions", &qcs) + chg.AddTask(t) + + st.Unlock() + defer s.se.Stop() + err := s.o.Settle(5 * time.Second) + st.Lock() + c.Check(err, IsNil) + c.Check(setupProfilesCalled, Equals, 0) + checkQuotaState(c, st, map[string]quotaGroupState{ + "foo": { + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(), + Snaps: []string{"test-snap"}, + }, + }) + + qc = servicestate.QuotaControlAction{ + Action: "update", + QuotaName: "foo", + ResourceLimits: quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB * 64).Build(), + } + qcs = []*servicestate.QuotaControlAction{&qc} + + chg = st.NewChange("quota-control-tasks", "...") + t = st.NewTask("quota-control", "...") + t.Set("quota-control-actions", &qcs) + chg.AddTask(t) + + st.Unlock() + defer s.se.Stop() + err = s.o.Settle(5 * time.Second) + st.Lock() + c.Check(err, IsNil) + c.Check(setupProfilesCalled, Equals, 1) + checkQuotaState(c, st, map[string]quotaGroupState{ + "foo": { + ResourceLimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).WithJournalSize(quantity.SizeMiB * 64).Build(), + Snaps: []string{"test-snap"}, + }, + }) +} + +func (s *quotaHandlersSuite) TestUpdateJournalQuota(c *C) { + r := s.mockSystemctlCalls(c, join( + []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, + systemctlCallsForSliceStart("foo"), + systemctlCallsForServiceRestart("test-snap"), + )) + defer r() + + // Add fake handlers for the setup-profiles task which should be invoked + // when creating the journal quota. + var setupProfilesCalled int + fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + task.State().Lock() + _, err := snapstate.TaskSnapSetup(task) + task.State().Unlock() + setupProfilesCalled++ + return err + } + s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) + + st := s.state + st.Lock() + defer st.Unlock() + + // setup the snap so it exists + snapstate.Set(s.state, "test-snap", s.testSnapState) + snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) + + // setup an existing quota group we can update it + err := servicestatetest.MockQuotaInState(st, "foo", "", []string{"test-snap"}, quota.NewResourcesBuilder().WithJournalSize(16*quantity.SizeMiB).Build()) + c.Assert(err, check.IsNil) + + qc := servicestate.QuotaControlAction{ + Action: "update", + QuotaName: "foo", + ResourceLimits: quota.NewResourcesBuilder().WithJournalRate(150, time.Millisecond*10).Build(), + } + qcs := []*servicestate.QuotaControlAction{&qc} + + chg := st.NewChange("quota-control-tasks", "...") + t := st.NewTask("quota-control", "...") + t.Set("quota-control-actions", &qcs) + chg.AddTask(t) + + st.Unlock() + defer s.se.Stop() + err = s.o.Settle(5 * time.Second) + st.Lock() + c.Check(err, IsNil) + c.Check(setupProfilesCalled, Equals, 0) + checkQuotaState(c, st, map[string]quotaGroupState{ + "foo": { + ResourceLimits: quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB*16).WithJournalRate(150, time.Millisecond*10).Build(), + Snaps: []string{"test-snap"}, + }, + }) +} + +func (s *quotaHandlersSuite) TestRemoveJournalQuota(c *C) { + r := s.mockSystemctlCalls(c, join( + // RemoveQuota for foo + []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, + systemctlCallsForSliceStop("foo"), + systemctlCallsForServiceRestart("test-snap"), + )) + defer r() + + // Add fake handlers for the setup-profiles task which should be invoked + // when creating the journal quota. + var setupProfilesCalled int + fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + task.State().Lock() + _, err := snapstate.TaskSnapSetup(task) + task.State().Unlock() + setupProfilesCalled++ + return err + } + s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) + + st := s.state + st.Lock() + defer st.Unlock() + + // setup the snap so it exists + snapstate.Set(s.state, "test-snap", s.testSnapState) + snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) + + // setup an existing quota group we can remove + err := servicestatetest.MockQuotaInState(st, "foo", "", []string{"test-snap"}, quota.NewResourcesBuilder().WithJournalSize(16*quantity.SizeMiB).Build()) + c.Assert(err, check.IsNil) + + qc := servicestate.QuotaControlAction{ + Action: "remove", + QuotaName: "foo", + } + qcs := []*servicestate.QuotaControlAction{&qc} + + chg := st.NewChange("quota-control-tasks", "...") + t := st.NewTask("quota-control", "...") + t.Set("quota-control-actions", &qcs) + chg.AddTask(t) + + st.Unlock() + defer s.se.Stop() + err = s.o.Settle(5 * time.Second) + st.Lock() + c.Check(err, IsNil) + c.Check(setupProfilesCalled, Equals, 1) +} + func (s *quotaHandlersSuite) TestQuotaUpdateAddSnap(c *C) { r := s.mockSystemctlCalls(c, join( // CreateQuota for foo From adf7e8ba6afc327ebb2d53eda3b27fc2c4120a0d Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Mon, 20 Jun 2022 13:03:03 +0200 Subject: [PATCH 115/153] overlord/servicestate: remove debug prints --- overlord/servicestate/quota_handlers.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/overlord/servicestate/quota_handlers.go b/overlord/servicestate/quota_handlers.go index 2135d8c9808..5ac0c40498e 100644 --- a/overlord/servicestate/quota_handlers.go +++ b/overlord/servicestate/quota_handlers.go @@ -22,7 +22,6 @@ package servicestate import ( "errors" "fmt" - "log" "sort" tomb "gopkg.in/tomb.v2" @@ -157,7 +156,6 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { } } - log.Println("[QUOTA]", "quota-control", len(servicesAffected), refreshProfiles) if len(servicesAffected) > 0 { var prevTaskSet *state.TaskSet chg := t.Change() @@ -448,7 +446,6 @@ func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string] // store the current status of journal quota, if it changes we need // to refresh the profiles for the snaps in the groups refreshProfiles := grp.JournalLimit != nil - log.Println("[DEBUG] quotaUpdate: refreshProfiles", refreshProfiles) // update resource limits for the group if err := quotaUpdateGroupLimits(grp, action.ResourceLimits); err != nil { @@ -462,7 +459,6 @@ func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string] } refreshProfiles = refreshProfiles != (grp.JournalLimit != nil) - log.Println("[DEBUG] quotaUpdate: refreshProfiles", refreshProfiles) return grp, allGrps, refreshProfiles, nil } From aa6f3dcecdbc34324d2e5885d7e5e80f3f202c2b Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Mon, 20 Jun 2022 13:17:56 +0200 Subject: [PATCH 116/153] overlord/servicestate: add new checkers for resource states --- overlord/servicestate/quota_control_test.go | 39 +++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/overlord/servicestate/quota_control_test.go b/overlord/servicestate/quota_control_test.go index 1824a5b5845..62ff420a4be 100644 --- a/overlord/servicestate/quota_control_test.go +++ b/overlord/servicestate/quota_control_test.go @@ -89,6 +89,44 @@ type quotaGroupState struct { Snaps []string } +func assertQuotaResources(c *C, grp *quota.Group, expected quota.Resources) { + if grp.MemoryLimit != 0 || expected.Memory != nil { + c.Assert(expected.Memory, NotNil) + c.Assert(grp.MemoryLimit > 0, Equals, true) + c.Check(grp.MemoryLimit, Equals, expected.Memory.Limit) + } + if grp.CPULimit != nil || expected.CPU != nil || expected.CPUSet != nil { + c.Assert(grp.CPULimit, NotNil) + + if grp.CPULimit.Count != 0 || grp.CPULimit.Percentage != 0 || expected.CPU != nil { + c.Assert(expected.CPU, NotNil) + c.Check(grp.CPULimit.Count, Equals, expected.CPU.Count) + c.Check(grp.CPULimit.Percentage, Equals, expected.CPU.Percentage) + } + if len(grp.CPULimit.AllowedCPUs) > 0 || expected.CPUSet != nil { + c.Assert(expected.CPUSet, NotNil) + c.Check(grp.CPULimit.AllowedCPUs, DeepEquals, expected.CPUSet.CPUs) + } + } + if grp.TaskLimit != 0 || expected.Threads != nil { + c.Assert(expected.Threads, NotNil) + c.Check(grp.TaskLimit, Equals, expected.Threads.Limit) + } + if grp.JournalLimit != nil || expected.Journal != nil { + c.Assert(grp.JournalLimit, NotNil) + c.Assert(expected.Journal, NotNil) + if grp.JournalLimit.Size != 0 { + c.Assert(expected.Journal.Size, NotNil) + c.Check(grp.JournalLimit.Size, Equals, expected.Journal.Size.Limit) + } + if grp.JournalLimit.RateCount != 0 && grp.JournalLimit.RatePeriod != 0 { + c.Assert(expected.Journal.Rate, NotNil) + c.Check(grp.JournalLimit.RateCount, Equals, expected.Journal.Rate.Count) + c.Check(grp.JournalLimit.RatePeriod, Equals, expected.Journal.Rate.Period) + } + } +} + func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { m, err := servicestate.AllQuotas(st) c.Assert(err, IsNil) @@ -97,6 +135,7 @@ func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { expGrp, ok := exp[name] c.Assert(ok, Equals, true, Commentf("unexpected group %q in state", name)) c.Assert(grp.ParentGroup, Equals, expGrp.ParentGroup) + assertQuotaResources(c, grp, expGrp.ResourceLimits) c.Assert(grp.Snaps, HasLen, len(expGrp.Snaps)) if len(expGrp.Snaps) != 0 { From be2ef2c7dadb7c90b2b60aade840d323ba6f8279 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 15 Jun 2022 10:35:08 +0200 Subject: [PATCH 117/153] Determine required revisions for missing snaps. --- asserts/snapasserts/validation_sets.go | 51 +++++++++++++--- asserts/snapasserts/validation_sets_test.go | 66 ++++++++++++++------- 2 files changed, 85 insertions(+), 32 deletions(-) diff --git a/asserts/snapasserts/validation_sets.go b/asserts/snapasserts/validation_sets.go index 58e6aedc45a..c2d073311e7 100644 --- a/asserts/snapasserts/validation_sets.go +++ b/asserts/snapasserts/validation_sets.go @@ -63,8 +63,9 @@ func (e *ValidationSetsConflictError) Error() string { // ValidationSetsValidationError describes an error arising // from validation of snaps against ValidationSets. type ValidationSetsValidationError struct { - // MissingSnaps maps missing snap names to the validation sets requiring them. - MissingSnaps map[string][]string + // MissingSnaps maps missing snap names to the expected revisions and respective validation sets requiring them. + // Revisions may be unset if no specific revision is required + MissingSnaps map[string]map[snap.Revision][]string // InvalidSnaps maps snap names to the validation sets declaring them invalid. InvalidSnaps map[string][]string // WronRevisionSnaps maps snap names to the expected revisions and respective @@ -94,9 +95,27 @@ func (e *ValidationSetsValidationError) Error() string { } } - printDetails("missing required snaps", e.MissingSnaps, func(snapName string, validationSetKeys []string) string { - return fmt.Sprintf("%s (required by sets %s)", snapName, strings.Join(validationSetKeys, ",")) - }) + if len(e.MissingSnaps) > 0 { + fmt.Fprintf(buf, "\n- missing required snaps:") + for snapName, revisions := range e.MissingSnaps { + revisionsSorted := make([]snap.Revision, 0, len(revisions)) + for rev := range revisions { + revisionsSorted = append(revisionsSorted, rev) + } + sort.Sort(byRevision(revisionsSorted)) + t := make([]string, 0, len(revisionsSorted)) + for _, rev := range revisionsSorted { + keys := revisions[rev] + if rev.Unset() { + t = append(t, fmt.Sprintf("at any revision by sets %s", strings.Join(keys, ","))) + } else { + t = append(t, fmt.Sprintf("at revision %s by sets %s", rev, strings.Join(keys, ","))) + } + } + fmt.Fprintf(buf, "\n - %s (required %s)", snapName, strings.Join(t, ", ")) + } + } + printDetails("invalid snaps", e.InvalidSnaps, func(snapName string, validationSetKeys []string) string { return fmt.Sprintf("%s (invalid for sets %s)", snapName, strings.Join(validationSetKeys, ",")) }) @@ -367,7 +386,7 @@ func (v *ValidationSets) CheckInstalledSnaps(snaps []*InstalledSnap, ignoreValid // snapName -> validationSet key -> validation set invalid := make(map[string]map[string]bool) - missing := make(map[string]map[string]bool) + missing := make(map[string]map[snap.Revision]map[string]bool) wrongrev := make(map[string]map[snap.Revision]map[string]bool) sets := make(map[string]*asserts.ValidationSet) @@ -411,9 +430,12 @@ func (v *ValidationSets) CheckInstalledSnaps(snaps []*InstalledSnap, ignoreValid // is only possible to have it with a wrong revision, or installed while invalid, in both // cases through --ignore-validation flag). if missing[rc.Name] == nil { - missing[rc.Name] = make(map[string]bool) + missing[rc.Name] = make(map[snap.Revision]map[string]bool) + } + if missing[rc.Name][rev] == nil { + missing[rc.Name][rev] = make(map[string]bool) } - missing[rc.Name][rc.validationSetKey] = true + missing[rc.Name][rev][rc.validationSetKey] = true sets[rc.validationSetKey] = v.sets[rc.validationSetKey] } } @@ -438,9 +460,20 @@ func (v *ValidationSets) CheckInstalledSnaps(snaps []*InstalledSnap, ignoreValid if len(invalid) > 0 || len(missing) > 0 || len(wrongrev) > 0 { verr := &ValidationSetsValidationError{ InvalidSnaps: setsToLists(invalid), - MissingSnaps: setsToLists(missing), Sets: sets, } + if len(missing) > 0 { + verr.MissingSnaps = make(map[string]map[snap.Revision][]string) + for snapName, revs := range missing { + verr.MissingSnaps[snapName] = make(map[snap.Revision][]string) + for rev, keys := range revs { + for key := range keys { + verr.MissingSnaps[snapName][rev] = append(verr.MissingSnaps[snapName][rev], key) + } + sort.Strings(verr.MissingSnaps[snapName][rev]) + } + } + } if len(wrongrev) > 0 { verr.WrongRevisionSnaps = make(map[string]map[snap.Revision][]string) for snapName, revs := range wrongrev { diff --git a/asserts/snapasserts/validation_sets_test.go b/asserts/snapasserts/validation_sets_test.go index 72102218de2..abefbbf6f52 100644 --- a/asserts/snapasserts/validation_sets_test.go +++ b/asserts/snapasserts/validation_sets_test.go @@ -389,15 +389,19 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { tests := []struct { snaps []*snapasserts.InstalledSnap expectedInvalid map[string][]string - expectedMissing map[string][]string + expectedMissing map[string]map[snap.Revision][]string expectedWrongRev map[string]map[snap.Revision][]string }{ { // required snaps not installed snaps: nil, - expectedMissing: map[string][]string{ - "snap-b": {"acme/fooname"}, - "snap-d": {"acme/barname"}, + expectedMissing: map[string]map[snap.Revision][]string{ + "snap-b": { + snap.R(3): {"acme/fooname"}, + }, + "snap-d": { + snap.R(0): {"acme/barname"}, + }, }, }, { @@ -405,9 +409,13 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snaps: []*snapasserts.InstalledSnap{ snapZ, }, - expectedMissing: map[string][]string{ - "snap-b": {"acme/fooname"}, - "snap-d": {"acme/barname"}, + expectedMissing: map[string]map[snap.Revision][]string{ + "snap-b": { + snap.R(3): {"acme/fooname"}, + }, + "snap-d": { + snap.R(0): {"acme/barname"}, + }, }, }, { @@ -438,8 +446,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { expectedInvalid: map[string][]string{ "snap-a": {"acme/booname", "acme/fooname"}, }, - expectedMissing: map[string][]string{ - "snap-b": {"acme/fooname"}, + expectedMissing: map[string]map[snap.Revision][]string{ + "snap-b": { + snap.R(3): {"acme/fooname"}, + }, }, }, { @@ -482,8 +492,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snapB, // covered by acme/barname validation-set. snap-d not installed. snapE}, - expectedMissing: map[string][]string{ - "snap-d": {"acme/barname"}, + expectedMissing: map[string]map[snap.Revision][]string{ + "snap-d": { + snap.R(0): {"acme/barname"}, + }, }, }, { @@ -492,8 +504,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/barname validation-set snapDrev99, snapE}, - expectedMissing: map[string][]string{ - "snap-b": {"acme/fooname"}, + expectedMissing: map[string]map[snap.Revision][]string{ + "snap-b": { + snap.R(3): {"acme/fooname"}, + }, }, }, { @@ -502,9 +516,13 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snapC, // covered by acme/barname validation-set, required missing. snapE}, - expectedMissing: map[string][]string{ - "snap-b": {"acme/fooname"}, - "snap-d": {"acme/barname"}, + expectedMissing: map[string]map[snap.Revision][]string{ + "snap-b": { + snap.R(3): {"acme/fooname"}, + }, + "snap-d": { + snap.R(0): {"acme/barname"}, + }, }, }, // local snaps @@ -561,9 +579,9 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { } verr, ok := err.(*snapasserts.ValidationSetsValidationError) c.Assert(ok, Equals, true, Commentf("#%d", i)) - c.Assert(tc.expectedInvalid, DeepEquals, verr.InvalidSnaps, Commentf("#%d", i)) - c.Assert(tc.expectedMissing, DeepEquals, verr.MissingSnaps, Commentf("#%d", i)) - c.Assert(tc.expectedWrongRev, DeepEquals, verr.WrongRevisionSnaps, Commentf("#%d", i)) + c.Assert(verr.InvalidSnaps, DeepEquals, tc.expectedInvalid, Commentf("#%d", i)) + c.Assert(verr.MissingSnaps, DeepEquals, tc.expectedMissing, Commentf("#%d", i)) + c.Assert(verr.WrongRevisionSnaps, DeepEquals, tc.expectedWrongRev, Commentf("#%d", i)) checkSets(verr.InvalidSnaps, verr.Sets) } } @@ -654,7 +672,6 @@ func (s *validationSetsSuite) TestCheckInstalledSnapsErrorFormat(c *C) { map[string]interface{}{ "name": "snap-b", "id": "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb", - "revision": "5", "presence": "required", }, }, @@ -664,6 +681,9 @@ func (s *validationSetsSuite) TestCheckInstalledSnapsErrorFormat(c *C) { c.Assert(valsets.Add(vs1), IsNil) c.Assert(valsets.Add(vs2), IsNil) + // not strictly important, but ensures test data makes sense and avoids confusing results + c.Assert(valsets.Conflict(), IsNil) + snapA := snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1)) snapBlocal := snapasserts.NewInstalledSnap("snap-b", "", snap.R("x3")) @@ -675,13 +695,13 @@ func (s *validationSetsSuite) TestCheckInstalledSnapsErrorFormat(c *C) { nil, "validation sets assertions are not met:\n" + "- missing required snaps:\n" + - " - snap-b \\(required by sets acme/barname,acme/fooname\\)", + " - snap-b \\(required at any revision by sets acme/barname, at revision 3 by sets acme/fooname\\)", }, { []*snapasserts.InstalledSnap{snapA}, "validation sets assertions are not met:\n" + "- missing required snaps:\n" + - " - snap-b \\(required by sets acme/barname,acme/fooname\\)\n" + + " - snap-b \\(required at any revision by sets acme/barname, at revision 3 by sets acme/fooname\\)\n" + "- invalid snaps:\n" + " - snap-a \\(invalid for sets acme/fooname\\)", }, @@ -689,7 +709,7 @@ func (s *validationSetsSuite) TestCheckInstalledSnapsErrorFormat(c *C) { []*snapasserts.InstalledSnap{snapBlocal}, "validation sets assertions are not met:\n" + "- snaps at wrong revisions:\n" + - " - snap-b \\(required at revision 3 by sets acme/fooname, at revision 5 by sets acme/barname\\)", + " - snap-b \\(required at revision 3 by sets acme/fooname\\)", }, } From 3ececd100f459d8889e91a3ae9b3e638d2af8f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 15 Jun 2022 12:05:10 +0200 Subject: [PATCH 118/153] Update assertstate test for MissingSnaps change. --- overlord/assertstate/assertstate_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/overlord/assertstate/assertstate_test.go b/overlord/assertstate/assertstate_test.go index d1acfc89653..a52a5817d4e 100644 --- a/overlord/assertstate/assertstate_test.go +++ b/overlord/assertstate/assertstate_test.go @@ -2849,7 +2849,7 @@ func (s *assertMgrSuite) TestRefreshValidationSetAssertionsEnforcingModeMissingS assertstate.UpdateValidationSet(s.state, &tr) c.Assert(assertstate.RefreshValidationSetAssertions(s.state, 0, nil), IsNil) - c.Assert(logbuf.String(), Matches, `.*cannot refresh to validation set assertions that do not satisfy installed snaps: validation sets assertions are not met:\n- missing required snaps:\n - foo \(required by sets .*/foo\)\n`) + c.Assert(logbuf.String(), Matches, `.*cannot refresh to validation set assertions that do not satisfy installed snaps: validation sets assertions are not met:\n- missing required snaps:\n - foo \(required at any revision by sets .*/foo\)\n`) a, err := assertstate.DB(s.state).Find(asserts.ValidationSetType, map[string]string{ "series": "16", @@ -3133,8 +3133,10 @@ func (s *assertMgrSuite) TestValidationSetAssertionForEnforceNotPinnedUnhappyMis c.Assert(err, NotNil) verr, ok := err.(*snapasserts.ValidationSetsValidationError) c.Assert(ok, Equals, true) - c.Check(verr.MissingSnaps, DeepEquals, map[string][]string{ - "foo": {fmt.Sprintf("%s/bar", s.dev1Acct.AccountID())}, + c.Check(verr.MissingSnaps, DeepEquals, map[string]map[snap.Revision][]string{ + "foo": { + snap.R(1): []string{fmt.Sprintf("%s/bar", s.dev1Acct.AccountID())}, + }, }) // and it hasn't been committed From 387c216beef0eb46bce53250a8568a6a99ec1bcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 15 Jun 2022 18:06:53 +0200 Subject: [PATCH 119/153] Cover the case where same revision of a snap is required by 2 validation sets. --- asserts/snapasserts/validation_sets_test.go | 101 +++++++++++++++++--- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/asserts/snapasserts/validation_sets_test.go b/asserts/snapasserts/validation_sets_test.go index abefbbf6f52..76375ddf68d 100644 --- a/asserts/snapasserts/validation_sets_test.go +++ b/asserts/snapasserts/validation_sets_test.go @@ -366,11 +366,47 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { }, }).(*asserts.ValidationSet) + vs5 := assertstest.FakeAssertion(map[string]interface{}{ + "type": "validation-set", + "authority-id": "acme", + "series": "16", + "account-id": "acme", + "name": "huhname", + "sequence": "1", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "snap-f", + "id": "mysnapffffffffffffffffffffffffff", + "revision": "4", + "presence": "required", + }, + }, + }).(*asserts.ValidationSet) + + vs6 := assertstest.FakeAssertion(map[string]interface{}{ + "type": "validation-set", + "authority-id": "acme", + "series": "16", + "account-id": "acme", + "name": "duhname", + "sequence": "1", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "snap-f", + "id": "mysnapffffffffffffffffffffffffff", + "revision": "4", + "presence": "required", + }, + }, + }).(*asserts.ValidationSet) + valsets := snapasserts.NewValidationSets() c.Assert(valsets.Add(vs1), IsNil) c.Assert(valsets.Add(vs2), IsNil) c.Assert(valsets.Add(vs3), IsNil) c.Assert(valsets.Add(vs4), IsNil) + c.Assert(valsets.Add(vs5), IsNil) + c.Assert(valsets.Add(vs6), IsNil) snapA := snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1)) snapAlocal := snapasserts.NewInstalledSnap("snap-a", "", snap.R("x2")) @@ -383,6 +419,7 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snapDrev99 := snapasserts.NewInstalledSnap("snap-d", "mysnapdddddddddddddddddddddddddd", snap.R(99)) snapDlocal := snapasserts.NewInstalledSnap("snap-d", "", snap.R("x3")) snapE := snapasserts.NewInstalledSnap("snap-e", "mysnapeeeeeeeeeeeeeeeeeeeeeeeeee", snap.R(2)) + snapF := snapasserts.NewInstalledSnap("snap-f", "mysnapffffffffffffffffffffffffff", snap.R(4)) // extra snap, not referenced by any validation set snapZ := snapasserts.NewInstalledSnap("snap-z", "mysnapzzzzzzzzzzzzzzzzzzzzzzzzzz", snap.R(1)) @@ -402,6 +439,9 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { "snap-d": { snap.R(0): {"acme/barname"}, }, + "snap-f": { + snap.R(4): {"acme/duhname", "acme/huhname"}, + }, }, }, { @@ -416,6 +456,9 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { "snap-d": { snap.R(0): {"acme/barname"}, }, + "snap-f": { + snap.R(4): {"acme/duhname", "acme/huhname"}, + }, }, }, { @@ -423,7 +466,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/fooname validation-set snapB, // covered by acme/barname validation-set. snap-e not installed but optional - snapDrev99}, + snapDrev99, + // covered by acme/duhname and acme/huhname + snapF, + }, // ale fine }, { @@ -432,7 +478,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snapA, snapB, // covered by acme/barname validation-set. snap-e not installed but optional - snapDrev99}, + snapDrev99, + // covered by acme/duhname and acme/huhname + snapF, + }, expectedInvalid: map[string][]string{ "snap-a": {"acme/booname", "acme/fooname"}, }, @@ -442,7 +491,9 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/fooname and acme/booname validation-sets, snapB missing, snap-a presence is invalid snapA, // covered by acme/barname validation-set. snap-e not installed but optional - snapDrev99}, + snapDrev99, + snapF, + }, expectedInvalid: map[string][]string{ "snap-a": {"acme/booname", "acme/fooname"}, }, @@ -458,7 +509,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snapB, snapC, // covered by acme/barname validation-set. snap-e not installed but optional - snapD}, + snapD, + // covered by acme/duhname and acme/huhname + snapF, + }, // ale fine }, { @@ -467,7 +521,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snapB, snapCinvRev, // covered by acme/barname validation-set. snap-e not installed but optional - snapD}, + snapD, + // covered by acme/duhname and acme/huhname + snapF, + }, expectedWrongRev: map[string]map[snap.Revision][]string{ "snap-c": { snap.R(2): {"acme/fooname"}, @@ -479,7 +536,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/fooname validation-set but wrong revision snapBinvRev, // covered by acme/barname validation-set. - snapD}, + snapD, + // covered by acme/duhname and acme/huhname + snapF, + }, expectedWrongRev: map[string]map[snap.Revision][]string{ "snap-b": { snap.R(3): {"acme/fooname"}, @@ -491,7 +551,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/fooname validation-set snapB, // covered by acme/barname validation-set. snap-d not installed. - snapE}, + snapE, + // covered by acme/duhname and acme/huhname + snapF, + }, expectedMissing: map[string]map[snap.Revision][]string{ "snap-d": { snap.R(0): {"acme/barname"}, @@ -503,7 +566,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // required snaps from acme/fooname are not installed. // covered by acme/barname validation-set snapDrev99, - snapE}, + snapE, + // covered by acme/duhname and acme/huhname + snapF, + }, expectedMissing: map[string]map[snap.Revision][]string{ "snap-b": { snap.R(3): {"acme/fooname"}, @@ -515,7 +581,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/fooname validation-set, required missing. snapC, // covered by acme/barname validation-set, required missing. - snapE}, + snapE, + // covered by acme/duhname and acme/huhname + snapF, + }, expectedMissing: map[string]map[snap.Revision][]string{ "snap-b": { snap.R(3): {"acme/fooname"}, @@ -531,7 +600,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/fooname validation-set. snapB, // covered by acme/barname validation-set, local snap-d. - snapDlocal}, + snapDlocal, + // covered by acme/duhname and acme/huhname + snapF, + }, // all fine }, { @@ -540,7 +612,9 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snapAlocal, snapB, // covered by acme/barname validation-set. - snapD}, + snapD, + snapF, + }, expectedInvalid: map[string][]string{ "snap-a": {"acme/booname", "acme/fooname"}, }, @@ -550,7 +624,10 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { // covered by acme/fooname validation-set, snap-b is wrong rev (local). snapBlocal, // covered by acme/barname validation-set. - snapD}, + snapD, + // covered by acme/duhname and acme/huhname + snapF, + }, expectedWrongRev: map[string]map[snap.Revision][]string{ "snap-b": { snap.R(3): {"acme/fooname"}, From 386ebcd978fc2c505b86ddd98514ab54651b6efe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 15 Jun 2022 18:09:27 +0200 Subject: [PATCH 120/153] Also add a validation set that doesn't require a specific revision. --- asserts/snapasserts/validation_sets_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/asserts/snapasserts/validation_sets_test.go b/asserts/snapasserts/validation_sets_test.go index 76375ddf68d..62ea0f9a7c0 100644 --- a/asserts/snapasserts/validation_sets_test.go +++ b/asserts/snapasserts/validation_sets_test.go @@ -400,6 +400,22 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { }, }).(*asserts.ValidationSet) + vs7 := assertstest.FakeAssertion(map[string]interface{}{ + "type": "validation-set", + "authority-id": "acme", + "series": "16", + "account-id": "acme", + "name": "bahname", + "sequence": "1", + "snaps": []interface{}{ + map[string]interface{}{ + "name": "snap-f", + "id": "mysnapffffffffffffffffffffffffff", + "presence": "required", + }, + }, + }).(*asserts.ValidationSet) + valsets := snapasserts.NewValidationSets() c.Assert(valsets.Add(vs1), IsNil) c.Assert(valsets.Add(vs2), IsNil) @@ -407,6 +423,7 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { c.Assert(valsets.Add(vs4), IsNil) c.Assert(valsets.Add(vs5), IsNil) c.Assert(valsets.Add(vs6), IsNil) + c.Assert(valsets.Add(vs7), IsNil) snapA := snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1)) snapAlocal := snapasserts.NewInstalledSnap("snap-a", "", snap.R("x2")) @@ -440,6 +457,7 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snap.R(0): {"acme/barname"}, }, "snap-f": { + snap.R(0): {"acme/bahname"}, snap.R(4): {"acme/duhname", "acme/huhname"}, }, }, @@ -457,6 +475,7 @@ func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) { snap.R(0): {"acme/barname"}, }, "snap-f": { + snap.R(0): {"acme/bahname"}, snap.R(4): {"acme/duhname", "acme/huhname"}, }, }, From dcb467b2d6b6e0f7c6d50d7905174c3f5ab69fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Mon, 20 Jun 2022 17:18:56 +0200 Subject: [PATCH 121/153] Fix expected error message in the spread test. --- tests/main/snap-validate-enforce/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/snap-validate-enforce/task.yaml b/tests/main/snap-validate-enforce/task.yaml index 971bb9ca70b..6d77c17bdba 100644 --- a/tests/main/snap-validate-enforce/task.yaml +++ b/tests/main/snap-validate-enforce/task.yaml @@ -32,7 +32,7 @@ execute: | fi MATCH "error: cannot apply validation set: cannot enforce validation set: validation sets assertions are not met:" < log.txt MATCH "missing required snaps:" < log.txt - MATCH "test-snapd-validation-set-enforcing \(required by sets xSfWKGdLoQBoQx88vIM1MpbFNMq53t1f/testenforce1\)" < log.txt + MATCH "test-snapd-validation-set-enforcing \(required at any revision by sets xSfWKGdLoQBoQx88vIM1MpbFNMq53t1f/testenforce1\)" < log.txt echo "Install the required snap and enable enforcing mode, pinned at sequence point 1" snap install --beta test-snapd-validation-set-enforcing From 8f4e2f77b7b0d6236be00fca5135f82c34ae326d Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 20 Jun 2022 17:40:17 +0200 Subject: [PATCH 122/153] snapd-apparmor: add more integration-ish tests This is a small followup for PR#11129 that tests the "main()" function a bit more. --- cmd/snapd-apparmor/main.go | 3 +- cmd/snapd-apparmor/main_test.go | 58 ++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/cmd/snapd-apparmor/main.go b/cmd/snapd-apparmor/main.go index bb19138193d..ad12432d486 100644 --- a/cmd/snapd-apparmor/main.go +++ b/cmd/snapd-apparmor/main.go @@ -113,8 +113,7 @@ func isContainerWithInternalPolicy() bool { func loadAppArmorProfiles() error { candidates, err := filepath.Glob(dirs.SnapAppArmorDir + "/*") if err != nil { - err = fmt.Errorf("Failed to glob profiles from snap apparmor dir %s: %v", dirs.SnapAppArmorDir, err) - return err + return fmt.Errorf("Failed to glob profiles from snap apparmor dir %s: %v", dirs.SnapAppArmorDir, err) } profiles := make([]string, 0, len(candidates)) diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index 217e647ee80..59ff8865663 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -20,6 +20,7 @@ package main_test import ( + "bytes" "fmt" "io/ioutil" "os" @@ -30,6 +31,7 @@ import ( snapd_apparmor "github.com/snapcore/snapd/cmd/snapd-apparmor" "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/logger" "github.com/snapcore/snapd/testutil" ) @@ -179,15 +181,63 @@ func (s *mainSuite) TestValidateArgs(c *C) { } } -func (s *mainSuite) TestRun(c *C) { +type integrationSuite struct { + testutil.BaseTest + + logBuf *bytes.Buffer + parserCmd *testutil.MockCmd +} + +var _ = Suite(&integrationSuite{}) + +func (s *integrationSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) + s.AddCleanup(func() { dirs.SetRootDir("/") }) + + logBuf, r := logger.MockLogger() + s.AddCleanup(r) + s.logBuf = logBuf + + // simulate a single profile to load + s.parserCmd = testutil.MockCommand(c, "apparmor_parser", "") + s.AddCleanup(s.parserCmd.Restore) + err := os.MkdirAll(dirs.SnapAppArmorDir, 0755) + c.Assert(err, IsNil) + profile := filepath.Join(dirs.SnapAppArmorDir, "foo") + err = ioutil.WriteFile(profile, nil, 0644) + c.Assert(err, IsNil) + os.Args = []string{"snapd-apparmor", "start"} +} + +func (s *integrationSuite) TestRunInContainerSkipsLoading(c *C) { + testutil.MockCommand(c, "systemd-detect-virt", "exit 0") + err := snapd_apparmor.Run() c.Assert(err, IsNil) + c.Check(s.logBuf.String(), testutil.Contains, "DEBUG: inside container environment") + c.Check(s.logBuf.String(), testutil.Contains, "Inside container environment without internal policy") + c.Assert(s.parserCmd.Calls(), HasLen, 0) +} - // simulate being inside a container environment - detectCmd := testutil.MockCommand(c, "systemd-detect-virt", "echo wsl") +func (s *integrationSuite) TestRunInContainerWithInternalPolicyLoadsProfiles(c *C) { + testutil.MockCommand(c, "systemd-detect-virt", "echo wsl") + + err := snapd_apparmor.Run() + c.Assert(err, IsNil) + c.Check(s.logBuf.String(), testutil.Contains, "DEBUG: inside container environment") + c.Check(s.logBuf.String(), Not(testutil.Contains), "Inside container environment without internal policy") + c.Assert(s.parserCmd.Calls(), HasLen, 1) +} + +func (s *integrationSuite) TestRunNormalLoadsProfiles(c *C) { + // simulate a normal system (not a container) + testutil.MockCommand(c, "systemd-detect-virt", "exit 1") + + detectCmd := testutil.MockCommand(c, "systemd-detect-virt", "exit 1") defer detectCmd.Restore() - err = snapd_apparmor.Run() + err := snapd_apparmor.Run() c.Assert(err, IsNil) + c.Assert(s.parserCmd.Calls(), HasLen, 1) } From 4288dc0e2819fd4ff8b3c3afefdc5f7fda986d5c Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 21 Jun 2022 09:13:40 +0200 Subject: [PATCH 123/153] snapd-apparmor: add logBuf output check in TestRunNormalLoadsProfiles --- cmd/snapd-apparmor/main_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index 59ff8865663..6a3c0be6035 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -25,6 +25,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "testing" . "gopkg.in/check.v1" @@ -240,4 +241,7 @@ func (s *integrationSuite) TestRunNormalLoadsProfiles(c *C) { err := snapd_apparmor.Run() c.Assert(err, IsNil) c.Assert(s.parserCmd.Calls(), HasLen, 1) + logLines := strings.Split(strings.TrimSpace(s.logBuf.String()), "\n") + c.Check(logLines, HasLen, 1) + c.Check(logLines[0], Matches, `.* main.go:[0-9]+: Loading profiles \[.*/var/lib/snapd/apparmor/profiles/foo\]`) } From b9ffd8189864b1853347186842b50466862b4610 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Thu, 16 Jun 2022 14:05:42 +0200 Subject: [PATCH 124/153] interfaces/apparmor: add missing apparmor rules for journal namespaces --- interfaces/apparmor/template.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index bcbbd314aba..b2e2b94090c 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -191,8 +191,8 @@ var templateCommon = ` # systemd native journal API (see sd_journal_print(4)). This should be in # AppArmor's base abstraction, but until it is, include here. - /run/systemd/journal/socket w, - /run/systemd/journal/stdout rw, # 'r' shouldn't be needed, but journald + /run/systemd/journal{,.*}/socket w, + /run/systemd/journal{,.*}/stdout rw, # 'r' shouldn't be needed, but journald # doesn't leak anything so allow # snapctl and its requirements From 82aa2e1ae3acb8253860ec8a7a459f99981ff25a Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 21 Jun 2022 09:56:00 +0200 Subject: [PATCH 125/153] overlord/servicestate: go back to a linear approach for queueing the quota tasks --- overlord/servicestate/quota_control_test.go | 9 ++++-- overlord/servicestate/quota_handlers.go | 34 +++++++++------------ 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/overlord/servicestate/quota_control_test.go b/overlord/servicestate/quota_control_test.go index 62ff420a4be..28e964397a0 100644 --- a/overlord/servicestate/quota_control_test.go +++ b/overlord/servicestate/quota_control_test.go @@ -159,10 +159,13 @@ func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { } } +// shouldMentionSlice returns whether or not a slice file +// should be mentioned in the service unit file. It does in the case +// when a quota is set. func shouldMentionSlice(resources quota.Resources) bool { - // If no quota is set, then Validate returns an error. And only - // valid quotas will get written to the slice file. - if err := resources.Validate(); err != nil { + if resources.Memory == nil && resources.CPU == nil && + resources.CPUSet == nil && resources.Threads == nil && + resources.Journal == nil { return false } return true diff --git a/overlord/servicestate/quota_handlers.go b/overlord/servicestate/quota_handlers.go index 5ac0c40498e..c6b87695cf5 100644 --- a/overlord/servicestate/quota_handlers.go +++ b/overlord/servicestate/quota_handlers.go @@ -157,29 +157,28 @@ func (m *ServiceManager) doQuotaControl(t *state.Task, _ *tomb.Tomb) error { } if len(servicesAffected) > 0 { - var prevTaskSet *state.TaskSet - chg := t.Change() - queueTasks := func(ts *state.TaskSet) { - if prevTaskSet != nil { - prevTaskSet.WaitAll(ts) - } else { - ts.WaitFor(t) + ts := state.NewTaskSet() + var prevTask *state.Task + queueTask := func(task *state.Task) { + if prevTask != nil { + task.WaitFor(prevTask) } - chg.AddAll(ts) - prevTaskSet = ts + ts.AddTask(task) + prevTask = task } if refreshProfiles { - queueTasks(addRefreshProfileTasks(st, servicesAffected)) + addRefreshProfileTasks(st, queueTask, servicesAffected) } - queueTasks(addRestartServicesTasks(st, qc.QuotaName, servicesAffected)) + addRestartServicesTasks(st, queueTask, qc.QuotaName, servicesAffected) + snapstate.InjectTasks(t, ts) } t.SetStatus(state.DoneStatus) return nil } -func addRefreshProfileTasks(st *state.State, servicesAffected map[*snap.Info][]*snap.AppInfo) *state.TaskSet { +func addRefreshProfileTasks(st *state.State, queueTask func(task *state.Task), servicesAffected map[*snap.Info][]*snap.AppInfo) *state.TaskSet { ts := state.NewTaskSet() for info := range servicesAffected { setupProfilesTask := st.NewTask("setup-profiles", fmt.Sprintf(i18n.G("Update snap %q (%s) security profiles"), info.SnapName(), info.Revision)) @@ -189,15 +188,12 @@ func addRefreshProfileTasks(st *state.State, servicesAffected map[*snap.Info][]* Revision: info.Revision, }, }) - setupProfilesTask.WaitAll(ts) - ts.AddTask(setupProfilesTask) + queueTask(setupProfilesTask) } return ts } -func addRestartServicesTasks(st *state.State, grpName string, servicesAffected map[*snap.Info][]*snap.AppInfo) *state.TaskSet { - ts := state.NewTaskSet() - +func addRestartServicesTasks(st *state.State, queueTask func(task *state.Task), grpName string, servicesAffected map[*snap.Info][]*snap.AppInfo) { getServiceNames := func(services []*snap.AppInfo) []string { var names []string for _, svc := range services { @@ -221,10 +217,8 @@ func addRestartServicesTasks(st *state.State, grpName string, servicesAffected m SnapName: info.InstanceName(), Services: getServiceNames(servicesAffected[info]), }) - restartTask.WaitAll(ts) - ts.AddTask(restartTask) + queueTask(restartTask) } - return ts } var osutilBootID = osutil.BootID From ab609c50639235652c2904ef371d7127052fb739 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Fri, 27 May 2022 12:02:43 +0200 Subject: [PATCH 126/153] o/devicestate: post factory reset ensure Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicemgr.go | 70 +++++++- .../devicestate_install_mode_test.go | 5 + overlord/devicestate/devicestate_test.go | 165 ++++++++++++++++++ overlord/devicestate/export_test.go | 16 ++ overlord/devicestate/handlers_install.go | 66 ++++++- 5 files changed, 314 insertions(+), 8 deletions(-) diff --git a/overlord/devicestate/devicemgr.go b/overlord/devicestate/devicemgr.go index 98690d558e5..e48a324f231 100644 --- a/overlord/devicestate/devicemgr.go +++ b/overlord/devicestate/devicemgr.go @@ -101,8 +101,9 @@ type DeviceManager struct { ensureSeedInConfigRan bool - ensureInstalledRan bool - ensureFactoryResetRan bool + ensureInstalledRan bool + ensureFactoryResetRan bool + ensurePostFactoryResetRan bool ensureTriedRecoverySystemRan bool @@ -1348,6 +1349,67 @@ func (m *DeviceManager) ensureTriedRecoverySystem() error { return nil } +var bootMarkFactoryResetComplete = boot.MarkFactoryResetComplete + +func (m *DeviceManager) ensurePostFactoryReset() error { + m.state.Lock() + defer m.state.Unlock() + + if release.OnClassic { + return nil + } + + if m.ensurePostFactoryResetRan { + return nil + } + + mode := m.SystemMode(SysHasModeenv) + if mode != "run" { + return nil + } + + var seeded bool + err := m.state.Get("seeded", &seeded) + if err != nil && !errors.Is(err, state.ErrNoState) { + return err + } + if !seeded { + return nil + } + + m.ensurePostFactoryResetRan = true + + factoryResetMarker := filepath.Join(dirs.SnapDeviceDir, "factory-reset") + if !osutil.FileExists(factoryResetMarker) { + // marker is gone already + return nil + } + + encrypted := true + // XXX have a helper somewhere for this? + if !osutil.FileExists(filepath.Join(dirs.SnapFDEDir, "marker")) { + encrypted = false + } + + // verify the marker + if err := verifyFactoryResetMarkerInRun(factoryResetMarker, encrypted); err != nil { + return fmt.Errorf("cannot verify factory reset marker: %v", err) + } + + // if encrypted, rotates the fallback keys on disk + if err := bootMarkFactoryResetComplete(encrypted); err != nil { + return fmt.Errorf("cannot complete factory reset: %v", err) + } + + if encrypted { + if err := rotateEncryptionKeys(); err != nil { + return fmt.Errorf("cannot transition encryption keys: %v", err) + } + } + + return os.Remove(factoryResetMarker) +} + type ensureError struct { errs []error } @@ -1405,6 +1467,10 @@ func (m *DeviceManager) Ensure() error { if err := m.ensureFactoryReset(); err != nil { errs = append(errs, err) } + + if err := m.ensurePostFactoryReset(); err != nil { + errs = append(errs, err) + } } if len(errs) > 0 { diff --git a/overlord/devicestate/devicestate_install_mode_test.go b/overlord/devicestate/devicestate_install_mode_test.go index 78e66425a63..3ad6f14017a 100644 --- a/overlord/devicestate/devicestate_install_mode_test.go +++ b/overlord/devicestate/devicestate_install_mode_test.go @@ -2340,6 +2340,11 @@ func (s *deviceMgrInstallModeSuite) doRunFactoryResetChange(c *C, model *asserts s.state.Unlock() var saveKey keys.EncryptionKey + restore = devicestate.MockSecbootTransitionEncryptionKeyChange(func(node string, key keys.EncryptionKey) error { + c.Errorf("unexpected call") + return fmt.Errorf("unexpected call") + }) + defer restore() restore = devicestate.MockSecbootStageEncryptionKeyChange(func(node string, key keys.EncryptionKey) error { if tc.encrypt { c.Check(node, Equals, "/dev/foo-save") diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go index ef2dc7eae0c..14ad7199ad2 100644 --- a/overlord/devicestate/devicestate_test.go +++ b/overlord/devicestate/devicestate_test.go @@ -22,6 +22,7 @@ package devicestate_test import ( "errors" "fmt" + "io/ioutil" "os" "path/filepath" "strings" @@ -1899,3 +1900,167 @@ func (s *deviceMgrSuite) TestVoidDirPermissionsGetFixed(c *C) { c.Check(msgs, Matches, "(?sm).*fixing permissions of .*/var/lib/snapd/void to 0111") c.Check(strings.Split(msgs, "\n"), HasLen, 1) } + +func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetEncrypted(c *C) { + defer release.MockOnClassic(false) + + s.state.Lock() + s.state.Set("seeded", true) + s.state.Unlock() + devicestate.SetBootOkRan(s.mgr, false) + devicestate.SetSystemMode(s.mgr, "run") + + // encrypted system + mockSnapFDEFile(c, "marker", nil) + err := ioutil.WriteFile(filepath.Join(dirs.SnapFDEDir, "ubuntu-save.key"), + []byte("save-key"), 0644) + c.Assert(err, IsNil) + c.Assert(os.MkdirAll(boot.InitramfsSeedEncryptionKeyDir, 0755), IsNil) + err = ioutil.WriteFile(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + []byte("old"), 0644) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + []byte("save"), 0644) + c.Assert(err, IsNil) + // matches the .factory key + factoryResetMarkercontent := []byte(`{"fallback-save-key-sha3-384":"d192153f0a50e826c6eb400c8711750ed0466571df1d151aaecc8c73095da7ec104318e7bf74d5e5ae2940827bf8402b"} +`) + c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), factoryResetMarkercontent, 0644), IsNil) + + completeCalls := 0 + restore := devicestate.MockMarkFactoryResetComplete(func(encrypted bool) error { + completeCalls++ + c.Check(encrypted, Equals, true) + return nil + }) + defer restore() + transitionCalls := 0 + restore = devicestate.MockSecbootTransitionEncryptionKeyChange(func(mountpoint string, key keys.EncryptionKey) error { + transitionCalls++ + c.Check(mountpoint, Equals, boot.InitramfsUbuntuSaveDir) + c.Check(key, DeepEquals, keys.EncryptionKey([]byte("save-key"))) + return nil + }) + defer restore() + + err = s.mgr.Ensure() + c.Assert(err, IsNil) + + c.Check(completeCalls, Equals, 1) + c.Check(transitionCalls, Equals, 1) + // factory reset marker is gone, the key was verified successfully + c.Check(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), testutil.FileAbsent) + c.Check(filepath.Join(dirs.SnapFDEDir, "marker"), testutil.FilePresent) + + completeCalls = 0 + transitionCalls = 0 + // try again, no marker, nothing should happen + devicestate.SetPostFactoryResetRan(s.mgr, false) + err = s.mgr.Ensure() + c.Assert(err, IsNil) + // nothing was called + c.Check(completeCalls, Equals, 0) + c.Check(transitionCalls, Equals, 0) + + // have the marker, but migrate the key as if boot code would do it and + // try again, in this setup the marker hash matches the migrated key + c.Check(os.Rename(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key")), + IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), factoryResetMarkercontent, 0644), IsNil) + + devicestate.SetPostFactoryResetRan(s.mgr, false) + err = s.mgr.Ensure() + c.Assert(err, IsNil) + c.Check(completeCalls, Equals, 1) + c.Check(transitionCalls, Equals, 1) + // the marker was again removed + c.Check(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), testutil.FileAbsent) +} + +func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetEncryptedError(c *C) { + defer release.MockOnClassic(false) + + s.state.Lock() + s.state.Set("seeded", true) + s.state.Unlock() + devicestate.SetBootOkRan(s.mgr, false) + devicestate.SetSystemMode(s.mgr, "run") + + // encrypted system + mockSnapFDEFile(c, "marker", nil) + c.Assert(os.MkdirAll(boot.InitramfsSeedEncryptionKeyDir, 0755), IsNil) + err := ioutil.WriteFile(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"), + []byte("old"), 0644) + c.Check(err, IsNil) + err = ioutil.WriteFile(filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key.factory-reset"), + []byte("save"), 0644) + c.Check(err, IsNil) + // does not match the save key + factoryResetMarkercontent := []byte(`{"fallback-save-key-sha3-384":"uh-oh"} +`) + c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), factoryResetMarkercontent, 0644), IsNil) + + completeCalls := 0 + restore := devicestate.MockMarkFactoryResetComplete(func(encrypted bool) error { + completeCalls++ + c.Check(encrypted, Equals, true) + return nil + }) + defer restore() + + err = s.mgr.Ensure() + c.Assert(err, ErrorMatches, "devicemgr: cannot verify factory reset marker: fallback sealed key digest mismatch, got d192153f0a50e826c6eb400c8711750ed0466571df1d151aaecc8c73095da7ec104318e7bf74d5e5ae2940827bf8402b expected uh-oh") + + c.Check(completeCalls, Equals, 0) + // factory reset marker is gone, the key was verified successfully + c.Check(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), testutil.FilePresent) + c.Check(filepath.Join(dirs.SnapFDEDir, "marker"), testutil.FilePresent) + + // try again, no marker, hit the same error + devicestate.SetPostFactoryResetRan(s.mgr, false) + err = s.mgr.Ensure() + c.Assert(err, ErrorMatches, "devicemgr: cannot verify factory reset marker: fallback sealed key digest mismatch, got d192153f0a50e826c6eb400c8711750ed0466571df1d151aaecc8c73095da7ec104318e7bf74d5e5ae2940827bf8402b expected uh-oh") + c.Check(completeCalls, Equals, 0) + + // and again, but not resetting the 'ran' check, so nothing is checked or called + err = s.mgr.Ensure() + c.Assert(err, IsNil) + c.Check(completeCalls, Equals, 0) +} + +func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetUnencrypted(c *C) { + defer release.MockOnClassic(false) + + s.state.Lock() + s.state.Set("seeded", true) + s.state.Unlock() + devicestate.SetBootOkRan(s.mgr, false) + devicestate.SetSystemMode(s.mgr, "run") + + // encrypted system + c.Assert(os.MkdirAll(dirs.SnapDeviceDir, 0755), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), []byte("{}"), 0644), IsNil) + + completeCalls := 0 + restore := devicestate.MockMarkFactoryResetComplete(func(encrypted bool) error { + completeCalls++ + c.Check(encrypted, Equals, false) + return nil + }) + defer restore() + + err := s.mgr.Ensure() + c.Assert(err, IsNil) + + c.Check(completeCalls, Equals, 1) + // factory reset marker is gone + c.Check(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), testutil.FileAbsent) + + // try again, no marker, nothing should happen + devicestate.SetPostFactoryResetRan(s.mgr, false) + err = s.mgr.Ensure() + c.Assert(err, IsNil) + // nothing was called + c.Check(completeCalls, Equals, 1) +} diff --git a/overlord/devicestate/export_test.go b/overlord/devicestate/export_test.go index 85a526dbdcb..d606af8805e 100644 --- a/overlord/devicestate/export_test.go +++ b/overlord/devicestate/export_test.go @@ -207,6 +207,10 @@ func SetTriedSystemsRan(m *DeviceManager, b bool) { m.ensureTriedRecoverySystemRan = b } +func SetPostFactoryResetRan(m *DeviceManager, b bool) { + m.ensurePostFactoryResetRan = b +} + func StartTime() time.Time { return startTime } @@ -360,6 +364,12 @@ func MockSecbootStageEncryptionKeyChange(f func(node string, key keys.Encryption return restore } +func MockSecbootTransitionEncryptionKeyChange(f func(mountpoint string, key keys.EncryptionKey) error) (restore func()) { + restore = testutil.Backup(&secbootTransitionEncryptionKeyChange) + secbootTransitionEncryptionKeyChange = f + return restore +} + func MockCloudInitStatus(f func() (sysconfig.CloudInitState, error)) (restore func()) { old := cloudInitStatus cloudInitStatus = f @@ -423,3 +433,9 @@ func MockSecbootRemoveRecoveryKeys(f func(rkeyDevToKey map[secboot.RecoveryKeyDe secbootRemoveRecoveryKeys = f return restore } + +func MockMarkFactoryResetComplete(f func(encrypted bool) error) (restore func()) { + restore = testutil.Backup(&bootMarkFactoryResetComplete) + bootMarkFactoryResetComplete = f + return restore +} diff --git a/overlord/devicestate/handlers_install.go b/overlord/devicestate/handlers_install.go index deb88b76d2a..bada5fa4de3 100644 --- a/overlord/devicestate/handlers_install.go +++ b/overlord/devicestate/handlers_install.go @@ -29,6 +29,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -59,12 +60,13 @@ import ( ) var ( - bootMakeRunnable = boot.MakeRunnableSystem - bootMakeRunnableAfterDataReset = boot.MakeRunnableSystemAfterDataReset - bootEnsureNextBootToRunMode = boot.EnsureNextBootToRunMode - installRun = install.Run - installFactoryReset = install.FactoryReset - secbootStageEncryptionKeyChange = secboot.StageEncryptionKeyChange + bootMakeRunnable = boot.MakeRunnableSystem + bootMakeRunnableAfterDataReset = boot.MakeRunnableSystemAfterDataReset + bootEnsureNextBootToRunMode = boot.EnsureNextBootToRunMode + installRun = install.Run + installFactoryReset = install.FactoryReset + secbootStageEncryptionKeyChange = secboot.StageEncryptionKeyChange + secbootTransitionEncryptionKeyChange = secboot.TransitionEncryptionKeyChange sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem ) @@ -1194,3 +1196,55 @@ func writeFactoryResetMarker(marker string, hasEncryption bool) error { } return osutil.AtomicWriteFile(marker, buf.Bytes(), 0644, 0) } + +func verifyFactoryResetMarkerInRun(marker string, hasEncryption bool) error { + f, err := os.Open(marker) + if err != nil { + return err + } + defer f.Close() + var frm factoryResetMarker + if err := json.NewDecoder(f).Decode(&frm); err != nil { + return err + } + if hasEncryption { + saveFallbackKeyFactory := boot.FactoryResetFallbackSaveSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir) + d, err := fileDigest(saveFallbackKeyFactory) + if err != nil { + // possible that there was unexpected reboot + // before, after the key was moved, but before + // the marker was removed, in which case the + // actual fallback key should have the right + // digest + if !os.IsNotExist(err) { + // unless it's a different error + return err + } + saveFallbackKeyFactory := boot.FallbackSaveSealedKeyUnder(boot.InitramfsSeedEncryptionKeyDir) + d, err = fileDigest(saveFallbackKeyFactory) + if err != nil { + return err + } + } + if d != frm.FallbackSaveKeyHash { + return fmt.Errorf("fallback sealed key digest mismatch, got %v expected %v", d, frm.FallbackSaveKeyHash) + } + } else { + if frm.FallbackSaveKeyHash != "" { + return fmt.Errorf("unexpected non-empty fallback key digest") + } + } + return nil +} + +func rotateEncryptionKeys() error { + kd, err := ioutil.ReadFile(filepath.Join(dirs.SnapFDEDir, "ubuntu-save.key")) + if err != nil { + return fmt.Errorf("cannot open encryption key file: %v", err) + } + // does the right thing if the key has already been transitioned + if err := secbootTransitionEncryptionKeyChange(boot.InitramfsUbuntuSaveDir, keys.EncryptionKey(kd)); err != nil { + return fmt.Errorf("cannot transition the encryption key: %v", err) + } + return nil +} From 753637405bb25fa386d9e5dc62113cdaf9b37570 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 4 May 2022 14:03:13 +0200 Subject: [PATCH 127/153] tests/nested/core/core20-factory-reset: subsequent factory reset, verify key rotation Signed-off-by: Maciej Borzecki --- .../core/core20-factory-reset/task.yaml | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/nested/core/core20-factory-reset/task.yaml b/tests/nested/core/core20-factory-reset/task.yaml index 37b3d0cd7cc..f731507d3b4 100644 --- a/tests/nested/core/core20-factory-reset/task.yaml +++ b/tests/nested/core/core20-factory-reset/task.yaml @@ -91,19 +91,36 @@ execute: | tests.nested exec test -e /run/mnt/ubuntu-seed/marker # the temp factory-reset key is gone - # TODO enable those checks one cleanup lands # TODO this is a very weak check - # tests.nested exec test ! -e /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset + tests.nested exec test ! -e /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key.factory-reset # no factory reset marker - # tests.nested exec test ! -e /var/lib/snapd/device/factory-reset + tests.nested exec test ! -e /var/lib/snapd/device/factory-reset # verify that the factory-reset log was collected tests.nested exec "zcat /var/log/factory-reset-mode.log.gz" | MATCH 'performing factory reset on an installed system' - # TODO enable checks once the save fallback key is rotated - #tests.nested exec cat /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key > post-reset-save-fallback-key + tests.nested exec cat /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key > post-reset-save-fallback-key # not a great check as the fallback key may have been resealed, but it # should be different nonetheless - #not cmp pre-reset-save-fallback-key post-reset-save-fallback-key + not cmp pre-reset-save-fallback-key post-reset-save-fallback-key - # TODO perform subsequent factory reset once post-factory reset cleanup lands + echo "Perform subsequent factory reset" + tests.nested exec "sudo snap reboot --factory-reset" || true + tests.nested wait-for reboot "${boot_id}" + tests.nested exec cat /proc/cmdline | MATCH 'snapd_recovery_mode=run' + tests.nested wait-for snap-command + # TODO investigate why does this have to be much longer than what is needed for the + # initial wait and one after the first reset? + retry -n 60 --wait 2 tests.nested exec "sudo snap wait system seed.loaded" + retry -n 60 --wait 2 tests.nested exec snap model --serial + tests.nested exec snap model --serial > subsequent-serial + # still the same serial + diff -u initial-serial subsequent-serial + + # the markers are still there + tests.nested exec test -e /run/mnt/ubuntu-save/marker + tests.nested exec test -e /run/mnt/ubuntu-seed/marker + # get the key + tests.nested exec cat /run/mnt/ubuntu-seed/device/fde/ubuntu-save.recovery.sealed-key > subsequent-reset-save-fallback-key + # and the key is different again + not cmp post-reset-save-fallback-key subsequent-reset-save-fallback-key From 9169ce05ffad1b80d64c44baa8d4e16f7dcccb30 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Tue, 21 Jun 2022 15:28:49 +0200 Subject: [PATCH 128/153] overlord/servicestate: initial review feedback --- overlord/servicestate/quota_control_test.go | 43 ++------------------- overlord/servicestate/quota_handlers.go | 5 ++- 2 files changed, 6 insertions(+), 42 deletions(-) diff --git a/overlord/servicestate/quota_control_test.go b/overlord/servicestate/quota_control_test.go index 28e964397a0..e43e53ed4f8 100644 --- a/overlord/servicestate/quota_control_test.go +++ b/overlord/servicestate/quota_control_test.go @@ -89,44 +89,6 @@ type quotaGroupState struct { Snaps []string } -func assertQuotaResources(c *C, grp *quota.Group, expected quota.Resources) { - if grp.MemoryLimit != 0 || expected.Memory != nil { - c.Assert(expected.Memory, NotNil) - c.Assert(grp.MemoryLimit > 0, Equals, true) - c.Check(grp.MemoryLimit, Equals, expected.Memory.Limit) - } - if grp.CPULimit != nil || expected.CPU != nil || expected.CPUSet != nil { - c.Assert(grp.CPULimit, NotNil) - - if grp.CPULimit.Count != 0 || grp.CPULimit.Percentage != 0 || expected.CPU != nil { - c.Assert(expected.CPU, NotNil) - c.Check(grp.CPULimit.Count, Equals, expected.CPU.Count) - c.Check(grp.CPULimit.Percentage, Equals, expected.CPU.Percentage) - } - if len(grp.CPULimit.AllowedCPUs) > 0 || expected.CPUSet != nil { - c.Assert(expected.CPUSet, NotNil) - c.Check(grp.CPULimit.AllowedCPUs, DeepEquals, expected.CPUSet.CPUs) - } - } - if grp.TaskLimit != 0 || expected.Threads != nil { - c.Assert(expected.Threads, NotNil) - c.Check(grp.TaskLimit, Equals, expected.Threads.Limit) - } - if grp.JournalLimit != nil || expected.Journal != nil { - c.Assert(grp.JournalLimit, NotNil) - c.Assert(expected.Journal, NotNil) - if grp.JournalLimit.Size != 0 { - c.Assert(expected.Journal.Size, NotNil) - c.Check(grp.JournalLimit.Size, Equals, expected.Journal.Size.Limit) - } - if grp.JournalLimit.RateCount != 0 && grp.JournalLimit.RatePeriod != 0 { - c.Assert(expected.Journal.Rate, NotNil) - c.Check(grp.JournalLimit.RateCount, Equals, expected.Journal.Rate.Count) - c.Check(grp.JournalLimit.RatePeriod, Equals, expected.Journal.Rate.Period) - } - } -} - func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { m, err := servicestate.AllQuotas(st) c.Assert(err, IsNil) @@ -135,7 +97,8 @@ func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { expGrp, ok := exp[name] c.Assert(ok, Equals, true, Commentf("unexpected group %q in state", name)) c.Assert(grp.ParentGroup, Equals, expGrp.ParentGroup) - assertQuotaResources(c, grp, expGrp.ResourceLimits) + groupResources := grp.GetQuotaResources() + c.Assert(groupResources, DeepEquals, expGrp.ResourceLimits) c.Assert(grp.Snaps, HasLen, len(expGrp.Snaps)) if len(expGrp.Snaps) != 0 { @@ -148,7 +111,7 @@ func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { if grp.ParentGroup != "" { slicePath = grp.ParentGroup + "/" + name } - checkSvcAndSliceState(c, sn+".svc1", slicePath, grp.GetQuotaResources()) + checkSvcAndSliceState(c, sn+".svc1", slicePath, groupResources) } } diff --git a/overlord/servicestate/quota_handlers.go b/overlord/servicestate/quota_handlers.go index c6b87695cf5..0bf0bdc50a0 100644 --- a/overlord/servicestate/quota_handlers.go +++ b/overlord/servicestate/quota_handlers.go @@ -439,7 +439,7 @@ func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string] // store the current status of journal quota, if it changes we need // to refresh the profiles for the snaps in the groups - refreshProfiles := grp.JournalLimit != nil + hadJournalLimit := grp.JournalLimit != nil // update resource limits for the group if err := quotaUpdateGroupLimits(grp, action.ResourceLimits); err != nil { @@ -452,7 +452,8 @@ func quotaUpdate(st *state.State, action QuotaControlAction, allGrps map[string] return nil, nil, false, err } - refreshProfiles = refreshProfiles != (grp.JournalLimit != nil) + hasJournalLimit := (grp.JournalLimit != nil) + refreshProfiles := hadJournalLimit != hasJournalLimit return grp, allGrps, refreshProfiles, nil } From 15f4ef65981b3f30ac770fb41c1a5ad56c0d3b14 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 21 Jun 2022 15:45:15 +0200 Subject: [PATCH 129/153] packaging/arch: install snapd-apparmor The snapd-apparmor rewrite branch landed, but Arch packaging was not updated. Signed-off-by: Maciej Borzecki --- packaging/arch/PKGBUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD index 924220bac40..215cd861449 100644 --- a/packaging/arch/PKGBUILD +++ b/packaging/arch/PKGBUILD @@ -77,6 +77,7 @@ build() { go build "${flags[@]}" -o "$srcdir/go/bin/snapd" $GOFLAGS "${_gourl}/cmd/snapd" go build "${flags[@]}" -o "$srcdir/go/bin/snap-seccomp" $GOFLAGS "${_gourl}/cmd/snap-seccomp" go build "${flags[@]}" -o "$srcdir/go/bin/snap-failure" $GOFLAGS "${_gourl}/cmd/snap-failure" + go build "${flags[@]}" -o "$srcdir/go/bin/snapd-apparmor" $GOFLAGS "${_gourl}/cmd/snapd-apparmor" # build snap-exec and snap-update-ns completely static for base snaps go build "${staticflags[@]}" -o "$srcdir/go/bin/snap-update-ns" $GOFLAGS "${_gourl}/cmd/snap-update-ns" go build "${staticflags[@]}" -o "$srcdir/go/bin/snap-exec" $GOFLAGS "${_gourl}/cmd/snap-exec" @@ -155,6 +156,7 @@ package() { install -Dm755 "$srcdir/go/bin/snapd" "$pkgdir/usr/lib/snapd/snapd" install -Dm755 "$srcdir/go/bin/snap-seccomp" "$pkgdir/usr/lib/snapd/snap-seccomp" install -Dm755 "$srcdir/go/bin/snap-failure" "$pkgdir/usr/lib/snapd/snap-failure" + install -Dm755 "$srcdir/go/bin/snapd-apparmor" "$pkgdir/usr/lib/snapd/snapd-apparmor" install -Dm755 "$srcdir/go/bin/snap-update-ns" "$pkgdir/usr/lib/snapd/snap-update-ns" install -Dm755 "$srcdir/go/bin/snap-exec" "$pkgdir/usr/lib/snapd/snap-exec" # Ensure /usr/bin/snapctl is a symlink to /usr/libexec/snapd/snapctl From 89fe520d5f0e761a41ead256718403d3958c0b4c Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Tue, 21 Jun 2022 16:38:55 +0200 Subject: [PATCH 130/153] preseed: suggest to install "qemu-user-static" When preseeding fails with an exec-format error this is most likely because the image is build from a system that has a different architecture than the target architecture. Here installing the `qemu-user-static` package is enough to preseed. This commit adds a slightly more helpful error message in this situation. --- image/preseed/export_test.go | 1 + image/preseed/preseed_linux.go | 6 ++++++ image/preseed/preseed_uc20_test.go | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/image/preseed/export_test.go b/image/preseed/export_test.go index 1a3e290715e..04ee926b07c 100644 --- a/image/preseed/export_test.go +++ b/image/preseed/export_test.go @@ -31,6 +31,7 @@ var ( SystemSnapFromSeed = systemSnapFromSeed ChooseTargetSnapdVersion = chooseTargetSnapdVersion CreatePreseedArtifact = createPreseedArtifact + RunUC20PreseedMode = runUC20PreseedMode ) type PreseedOpts = preseedOpts diff --git a/image/preseed/preseed_linux.go b/image/preseed/preseed_linux.go index e20ebde7e44..a70e6b91df8 100644 --- a/image/preseed/preseed_linux.go +++ b/image/preseed/preseed_linux.go @@ -22,6 +22,7 @@ package preseed import ( "crypto" "encoding/json" + "errors" "fmt" "io/ioutil" "os" @@ -539,6 +540,11 @@ func runUC20PreseedMode(opts *preseedOpts) error { fmt.Fprintf(Stdout, "starting to preseed UC20+ system: %s\n", opts.PreseedChrootDir) if err := cmd.Run(); err != nil { + var errno syscall.Errno + if errors.As(err, &errno) && errno == syscall.ENOEXEC { + return fmt.Errorf(`error running snapd, please try installing the "qemu-user-static" package: %v`, err) + } + return fmt.Errorf("error running snapd in preseed mode: %v\n", err) } diff --git a/image/preseed/preseed_uc20_test.go b/image/preseed/preseed_uc20_test.go index 023be6828b0..a170aa1d574 100644 --- a/image/preseed/preseed_uc20_test.go +++ b/image/preseed/preseed_uc20_test.go @@ -333,3 +333,21 @@ func (s *preseedSuite) TestRunPreseedUC20Happy(c *C) { func (s *preseedSuite) TestRunPreseedUC20HappyCustomApparmorFeaturesDir(c *C) { s.testRunPreseedUC20Happy(c, "/custom-aa-features") } + +func (s *preseedSuite) TestRunPreseedUC20ExecFormatError(c *C) { + tmpdir := c.MkDir() + + // Mock an exec-format error - the first thing that runUC20PreseedMode + // does is start snapd in a chroot. So we can override the "chroot" + // call with a simulated exec format error to simulate the error a + // user would get when running preseeding on a architecture that is + // not the image target architecture. + mockChrootCmd := testutil.MockCommand(c, "chroot", "") + defer mockChrootCmd.Restore() + err := ioutil.WriteFile(mockChrootCmd.Exe(), []byte("invalid-exe"), 0755) + c.Check(err, IsNil) + + opts := &preseed.PreseedOpts{PreseedChrootDir: tmpdir} + err = preseed.RunUC20PreseedMode(opts) + c.Check(err, ErrorMatches, `error running snapd, please try installing the "qemu-user-static" package: fork/exec .* exec format error`) +} From 06d4688ffd57695ddba16a716036364d0417367c Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Fri, 10 Jun 2022 12:10:44 +0100 Subject: [PATCH 131/153] daemon: add migrate-home action to debug/ API endpoint Signed-off-by: Miguel Pires --- daemon/api_debug.go | 3 ++ daemon/api_debug_migrate.go | 55 +++++++++++++++++++++ daemon/api_debug_test.go | 98 +++++++++++++++++++++++++++++++++++++ daemon/export_test.go | 8 +++ 4 files changed, 164 insertions(+) create mode 100644 daemon/api_debug_migrate.go diff --git a/daemon/api_debug.go b/daemon/api_debug.go index 2deb455abe0..2f3891e5107 100644 --- a/daemon/api_debug.go +++ b/daemon/api_debug.go @@ -53,6 +53,7 @@ type debugAction struct { RecoverySystemLabel string `json:"recovery-system-label"` } `json:"params"` + Snaps []string `json:"snaps"` } type connectivityStatus struct { @@ -411,6 +412,8 @@ func postDebug(c *Command, r *http.Request, user *auth.UserState) Response { return getStacktraces() case "create-recovery-system": return createRecovery(st, a.Params.RecoverySystemLabel) + case "migrate-home": + return migrateHome(st, a.Snaps) default: return BadRequest("unknown debug action: %v", a.Action) } diff --git a/daemon/api_debug_migrate.go b/daemon/api_debug_migrate.go new file mode 100644 index 00000000000..76405b833e4 --- /dev/null +++ b/daemon/api_debug_migrate.go @@ -0,0 +1,55 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2022 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package daemon + +import ( + "fmt" + + "github.com/snapcore/snapd/overlord/snapstate" + "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/strutil" +) + +var snapstateMigrateHome = snapstate.MigrateHome + +func migrateHome(st *state.State, snaps []string) Response { + if len(snaps) == 0 { + return BadRequest("no snaps were provided") + } + + tss, err := snapstateMigrateHome(st, snaps) + if err != nil { + if terr, ok := err.(snap.NotInstalledError); ok { + return SnapNotFound(terr.Snap, err) + } + + return InternalError(err.Error()) + } + + chg := st.NewChange("migrate-home", fmt.Sprintf("Migrate snap homes to ~/Snap for snaps %s", strutil.Quoted(snaps))) + for _, ts := range tss { + chg.AddAll(ts) + } + chg.Set("api-data", map[string][]string{"snap-names": snaps}) + + ensureStateSoon(st) + return AsyncResponse(nil, chg.ID()) +} diff --git a/daemon/api_debug_test.go b/daemon/api_debug_test.go index 1aed0aa6f33..3c9f7d1c9ee 100644 --- a/daemon/api_debug_test.go +++ b/daemon/api_debug_test.go @@ -22,12 +22,16 @@ package daemon_test import ( "bytes" "encoding/json" + "errors" "net/http" + "strings" "gopkg.in/check.v1" + "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/daemon" "github.com/snapcore/snapd/overlord/state" + "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/testutil" "github.com/snapcore/snapd/timings" ) @@ -238,3 +242,97 @@ func (s *postDebugSuite) TestMinLane(c *check.C) { // validity c.Check(t.Lanes(), check.DeepEquals, []int{lane1, lane2}) } + +func (s *postDebugSuite) TestMigrateHome(c *check.C) { + d := s.daemonWithOverlordMock() + s.expectRootAccess() + + restore := daemon.MockSnapstateMigrate(func(*state.State, []string) ([]*state.TaskSet, error) { + st := state.New(nil) + st.Lock() + defer st.Unlock() + + var ts state.TaskSet + ts.AddTask(st.NewTask("bar", "")) + return []*state.TaskSet{&ts}, nil + }) + defer restore() + + body := strings.NewReader(`{"action": "migrate-home", "snaps": ["foo", "bar"]}`) + req, err := http.NewRequest("POST", "/v2/debug", body) + c.Assert(err, check.IsNil) + + rsp := s.req(c, req, nil) + c.Assert(rsp, check.FitsTypeOf, &daemon.RespJSON{}) + + rspJSON := rsp.(*daemon.RespJSON) + + st := d.Overlord().State() + st.Lock() + defer st.Unlock() + + chg := st.Change(rspJSON.Change) + var snaps map[string][]string + c.Assert(chg.Get("api-data", &snaps), check.IsNil) + c.Assert(snaps["snap-names"], check.DeepEquals, []string{"foo", "bar"}) +} + +func (s *postDebugSuite) TestMigrateHomeNoSnaps(c *check.C) { + s.daemonWithOverlordMock() + s.expectRootAccess() + + body := strings.NewReader(`{"action": "migrate-home"}`) + req, err := http.NewRequest("POST", "/v2/debug", body) + c.Assert(err, check.IsNil) + + rsp := s.req(c, req, nil) + c.Assert(rsp, check.FitsTypeOf, &daemon.APIError{}) + apiErr := rsp.(*daemon.APIError) + + c.Check(apiErr.Status, check.Equals, 400) + c.Check(apiErr.Message, check.Equals, "no snaps were provided") +} + +func (s *postDebugSuite) TestMigrateHomeNotInstalled(c *check.C) { + s.daemonWithOverlordMock() + s.expectRootAccess() + + restore := daemon.MockSnapstateMigrate(func(*state.State, []string) ([]*state.TaskSet, error) { + return nil, snap.NotInstalledError{Snap: "some-snap"} + }) + defer restore() + + body := strings.NewReader(`{"action": "migrate-home", "snaps": ["some-snap"]}`) + req, err := http.NewRequest("POST", "/v2/debug", body) + c.Assert(err, check.IsNil) + + rsp := s.req(c, req, nil) + c.Assert(rsp, check.FitsTypeOf, &daemon.APIError{}) + apiErr := rsp.(*daemon.APIError) + + c.Check(apiErr.Status, check.Equals, 404) + c.Check(apiErr.Message, check.Equals, `snap "some-snap" is not installed`) + c.Check(apiErr.Kind, check.Equals, client.ErrorKindSnapNotFound) + c.Check(apiErr.Value, check.Equals, "some-snap") +} + +func (s *postDebugSuite) TestMigrateHomeInternalError(c *check.C) { + s.daemonWithOverlordMock() + s.expectRootAccess() + + restore := daemon.MockSnapstateMigrate(func(*state.State, []string) ([]*state.TaskSet, error) { + return nil, errors.New("boom") + }) + defer restore() + + body := strings.NewReader(`{"action": "migrate-home", "snaps": ["some-snap"]}`) + req, err := http.NewRequest("POST", "/v2/debug", body) + c.Assert(err, check.IsNil) + + rsp := s.req(c, req, nil) + c.Assert(rsp, check.FitsTypeOf, &daemon.APIError{}) + apiErr := rsp.(*daemon.APIError) + + c.Check(apiErr.Status, check.Equals, 500) + c.Check(apiErr.Message, check.Equals, `boom`) +} diff --git a/daemon/export_test.go b/daemon/export_test.go index 6ba228b384b..b7dc30aae03 100644 --- a/daemon/export_test.go +++ b/daemon/export_test.go @@ -204,6 +204,14 @@ func MockSnapstateInstallPathMany(f func(context.Context, *state.State, []*snap. } } +func MockSnapstateMigrate(mock func(*state.State, []string) ([]*state.TaskSet, error)) (restore func()) { + oldSnapstateMigrate := snapstateMigrateHome + snapstateMigrateHome = mock + return func() { + snapstateMigrateHome = oldSnapstateMigrate + } +} + func MockReboot(f func(boot.RebootAction, time.Duration, *boot.RebootInfo) error) func() { reboot = f return func() { reboot = boot.Reboot } From e5a00935b1e8024f5a6f8f72ece1c2af7a236dcd Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Fri, 10 Jun 2022 12:11:22 +0100 Subject: [PATCH 132/153] cmd/snap: add 'snap debug migrate-home' command Signed-off-by: Miguel Pires --- client/client.go | 15 +++ client/client_test.go | 17 +++ cmd/snap/cmd_debug_migrate.go | 80 +++++++++++++++ cmd/snap/cmd_debug_migrate_test.go | 160 +++++++++++++++++++++++++++++ cmd/snap/cmd_snap_op_test.go | 1 + 5 files changed, 273 insertions(+) create mode 100644 cmd/snap/cmd_debug_migrate.go create mode 100644 cmd/snap/cmd_debug_migrate_test.go diff --git a/client/client.go b/client/client.go index a22e8256b2c..1d8eab34c4f 100644 --- a/client/client.go +++ b/client/client.go @@ -769,3 +769,18 @@ func (client *Client) SystemRecoveryKeys(result interface{}) error { _, err := client.doSync("GET", "/v2/system-recovery-keys", nil, nil, nil, &result) return err } + +func (c *Client) MigrateSnapHome(snaps []string) (changeID string, err error) { + body, err := json.Marshal(struct { + Action string `json:"action"` + Snaps []string `json:"snaps"` + }{ + Action: "migrate-home", + Snaps: snaps, + }) + if err != nil { + return "", err + } + + return c.doAsync("POST", "/v2/debug", nil, nil, bytes.NewReader(body)) +} diff --git a/client/client_test.go b/client/client_test.go index 284e23d02c6..59ff3db1a7c 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -629,6 +629,23 @@ func (cs *clientSuite) TestDebugGet(c *C) { c.Check(cs.reqs[0].URL.Query(), DeepEquals, url.Values{"aspect": []string{"do-something"}, "foo": []string{"bar"}}) } +func (cs *clientSuite) TestDebugMigrateHome(c *C) { + cs.status = 202 + cs.rsp = `{"type": "async", "status-code": 202, "change": "123"}` + + snaps := []string{"foo", "bar"} + changeID, err := cs.cli.MigrateSnapHome(snaps) + c.Check(err, IsNil) + c.Check(changeID, Equals, "123") + + c.Check(cs.reqs, HasLen, 1) + c.Check(cs.reqs[0].Method, Equals, "POST") + c.Check(cs.reqs[0].URL.Path, Equals, "/v2/debug") + data, err := ioutil.ReadAll(cs.reqs[0].Body) + c.Assert(err, IsNil) + c.Check(string(data), Equals, `{"action":"migrate-home","snaps":["foo","bar"]}`) +} + type integrationSuite struct{} var _ = Suite(&integrationSuite{}) diff --git a/cmd/snap/cmd_debug_migrate.go b/cmd/snap/cmd_debug_migrate.go new file mode 100644 index 00000000000..4c7bab6f7a1 --- /dev/null +++ b/cmd/snap/cmd_debug_migrate.go @@ -0,0 +1,80 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2022 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main + +import ( + "errors" + "fmt" + + "github.com/jessevdk/go-flags" + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/strutil" +) + +type cmdMigrateHome struct { + waitMixin + + Positional struct { + Snaps []string `positional-arg-name:"" required:"1"` + } `positional-args:"yes" required:"yes"` +} + +func init() { + addDebugCommand("migrate-home", + "Migrate snaps' directory to ~/Snap.", + "Migrate snaps' directory to ~/Snap.", + func() flags.Commander { + return &cmdMigrateHome{} + }, nil, nil) +} + +func (x *cmdMigrateHome) Execute(args []string) error { + chgID, err := x.client.MigrateSnapHome(x.Positional.Snaps) + if err != nil { + msg, err := errorToCmdMessage("", "migrate-home", err, nil) + if err != nil { + return err + } + fmt.Fprintln(Stderr, msg) + return nil + } + + chg, err := x.wait(chgID) + if err != nil { + return err + } + + var snaps []string + if err := chg.Get("snap-names", &snaps); err != nil { + return errors.New(`cannot get "snap-names" from change`) + } + + if len(snaps) == 0 { + return errors.New(`expected "migrate-home" change to have non-empty "snap-names"`) + } + + msg := fmt.Sprintf("%s's home directory was migrated to ~/Snap\n", snaps[0]) + if len(snaps) > 1 { + msg = fmt.Sprintf(i18n.G("%s migrated their home directories to ~/Snap\n"), strutil.Quoted(snaps)) + } + + fmt.Fprintf(Stdout, msg) + return nil +} diff --git a/cmd/snap/cmd_debug_migrate_test.go b/cmd/snap/cmd_debug_migrate_test.go new file mode 100644 index 00000000000..c09210b8346 --- /dev/null +++ b/cmd/snap/cmd_debug_migrate_test.go @@ -0,0 +1,160 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2022 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package main_test + +import ( + "fmt" + "net/http" + + snap "github.com/snapcore/snapd/cmd/snap" + "gopkg.in/check.v1" + . "gopkg.in/check.v1" +) + +type MigrateHomeSuite struct { + BaseSnapSuite +} + +var _ = check.Suite(&MigrateHomeSuite{}) + +// failRequest logs an error message, fails the test and returns a proper error +// to the client. Use this instead of panic() or c.Fatal() because those crash +// the server and leave the client hanging/retrying. +func failRequest(msg string, w http.ResponseWriter, c *C) { + c.Error(msg) + w.WriteHeader(400) + fmt.Fprintf(w, `{"type": "error", "status-code": 400, "result": {"message": %q}}`, msg) +} + +func serverWithChange(chgRsp string, c *C) func(w http.ResponseWriter, r *http.Request) { + var n int + return func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.Method, check.Equals, "POST") + c.Check(r.URL.Path, check.Equals, "/v2/debug") + c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ + "action": "migrate-home", + "snaps": []interface{}{"foo"}, + }) + w.WriteHeader(202) + fmt.Fprintln(w, `{"type": "async", "status-code": 202, "result": {}, "change": "12"}`) + + case 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/changes/12") + fmt.Fprintf(w, chgRsp) + + default: + failRequest(fmt.Sprintf("server expected to get 2 requests, now on %d", n+1), w, c) + } + + n++ + } +} + +func (s *MigrateHomeSuite) TestMigrateHome(c *C) { + rsp := serverWithChange(`{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["foo"]}}}\n`, c) + s.RedirectClientToTestServer(rsp) + + rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "migrate-home", "foo"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Equals, "foo's home directory was migrated to ~/Snap\n") + c.Check(s.Stderr(), check.Equals, "") +} + +func (s *MigrateHomeSuite) TestMigrateHomeManySnaps(c *C) { + n := 0 + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + switch n { + case 0: + c.Check(r.Method, check.Equals, "POST") + c.Check(r.URL.Path, check.Equals, "/v2/debug") + c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{ + "action": "migrate-home", + "snaps": []interface{}{"foo", "bar"}, + }) + w.WriteHeader(202) + fmt.Fprintln(w, `{"type": "async", "status-code": 202, "result": {}, "change": "12"}`) + + case 1: + c.Check(r.Method, check.Equals, "GET") + c.Check(r.URL.Path, check.Equals, "/v2/changes/12") + fmt.Fprintf(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["foo", "bar"]}}}\n`) + + default: + failRequest(fmt.Sprintf("server expected to get 2 requests, now on %d", n+1), w, c) + } + + n++ + }) + + rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "migrate-home", "foo", "bar"}) + c.Assert(err, check.IsNil) + c.Assert(rest, check.DeepEquals, []string{}) + c.Check(s.Stdout(), check.Equals, "\"foo\", \"bar\" migrated their home directories to ~/Snap\n") + c.Check(s.Stderr(), check.Equals, "") +} + +func (s *MigrateHomeSuite) TestMigrateHomeNoSnaps(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + failRequest("unexpected request on server", w, c) + }) + + _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "migrate-home"}) + c.Assert(err, check.ErrorMatches, "the required argument .* was not provided") + c.Check(s.Stdout(), check.Equals, "") + c.Check(s.Stderr(), check.Equals, "") +} + +func (s *MigrateHomeSuite) TestMigrateHomeServerError(c *C) { + s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(500) + fmt.Fprintf(w, `{"type": "error", "status-code": 500, "result": {"message": "boom"}}`) + }) + + _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "migrate-home", "foo"}) + c.Assert(err, check.ErrorMatches, "boom") + c.Check(s.Stdout(), check.Equals, "") + c.Check(s.Stderr(), check.Equals, "") +} + +func (s *MigrateHomeSuite) TestMigrateHomeBadChangeNoSnaps(c *C) { + // broken change response: missing required "snap-names" + srv := serverWithChange(`{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": []}}}\n`, c) + s.RedirectClientToTestServer(srv) + + _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "migrate-home", "foo"}) + c.Assert(err, check.ErrorMatches, `expected "migrate-home" change to have non-empty "snap-names"`) + c.Check(s.Stdout(), check.Equals, "") + c.Check(s.Stderr(), check.Equals, "") +} + +func (s *MigrateHomeSuite) TestMigrateHomeBadChangeNoData(c *C) { + // broken change response: missing data + srv := serverWithChange(`{"type": "sync", "result": {"ready": true, "status": "Done"}}\n`, c) + s.RedirectClientToTestServer(srv) + + _, err := snap.Parser(snap.Client()).ParseArgs([]string{"debug", "migrate-home", "foo"}) + c.Assert(err, check.ErrorMatches, `cannot get "snap-names" from change`) + c.Check(s.Stdout(), check.Equals, "") + c.Check(s.Stderr(), check.Equals, "") +} diff --git a/cmd/snap/cmd_snap_op_test.go b/cmd/snap/cmd_snap_op_test.go index d83991f65f2..2eadc4e691d 100644 --- a/cmd/snap/cmd_snap_op_test.go +++ b/cmd/snap/cmd_snap_op_test.go @@ -2288,6 +2288,7 @@ func (s *SnapOpSuite) TestWaitServerError(c *check.C) { {"disable", "foo"}, {"try", "."}, {"switch", "--channel=foo", "bar"}, + {"debug", "migrate-home", "foo"}, // commands that use waitMixin from elsewhere {"start", "foo"}, {"stop", "foo"}, From b6a07bbc94327bca7bd60a48bb8118dcfa0f359d Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Wed, 22 Jun 2022 12:20:04 +0800 Subject: [PATCH 133/153] build-aux/snap: build against the snappy-dev/image PPA --- build-aux/snap/snapcraft.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build-aux/snap/snapcraft.yaml b/build-aux/snap/snapcraft.yaml index ce3b75a6c6e..cdd9baecfe4 100644 --- a/build-aux/snap/snapcraft.yaml +++ b/build-aux/snap/snapcraft.yaml @@ -12,6 +12,9 @@ adopt-info: snapd-deb # build-base is needed here for snapcraft to build this snap as with "modern" # snapcraft build-base: core +package-repositories: + - type: apt + ppa: snappy-dev/image grade: stable license: GPL-3.0 From 3e6322ca927e28ee36475c4ecd2df2d097f30016 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 22 Jun 2022 09:29:06 +0200 Subject: [PATCH 134/153] o/devicestate: tweak comments Signed-off-by: Maciej Borzecki --- overlord/devicestate/devicestate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go index 14ad7199ad2..5ed80721ef3 100644 --- a/overlord/devicestate/devicestate_test.go +++ b/overlord/devicestate/devicestate_test.go @@ -2038,7 +2038,7 @@ func (s *deviceMgrSuite) TestDeviceManagerEnsurePostFactoryResetUnencrypted(c *C devicestate.SetBootOkRan(s.mgr, false) devicestate.SetSystemMode(s.mgr, "run") - // encrypted system + // mock the factory reset marker of a system that isn't encrypted c.Assert(os.MkdirAll(dirs.SnapDeviceDir, 0755), IsNil) c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapDeviceDir, "factory-reset"), []byte("{}"), 0644), IsNil) From dd01b31a8b7869f8a37396751f95ebeceefa87ea Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Wed, 22 Jun 2022 09:53:58 +0200 Subject: [PATCH 135/153] interfaces/apparmor: revert approach, but make it a tad more specific by including snap prefix --- interfaces/apparmor/template.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index b2e2b94090c..2c8ecec513b 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -191,9 +191,9 @@ var templateCommon = ` # systemd native journal API (see sd_journal_print(4)). This should be in # AppArmor's base abstraction, but until it is, include here. - /run/systemd/journal{,.*}/socket w, - /run/systemd/journal{,.*}/stdout rw, # 'r' shouldn't be needed, but journald - # doesn't leak anything so allow + /run/systemd/journal{,.snap-*}/socket w, + /run/systemd/journal{,.snap-*}/stdout rw, # 'r' shouldn't be needed, but journald + # doesn't leak anything so allow # snapctl and its requirements /usr/bin/snapctl ixr, From afe17c60737406894dc4d733e56c64ffca109b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 22 Jun 2022 11:33:25 +0200 Subject: [PATCH 136/153] Support requesting validation sets with RevisionOptions and handle it in installInfo and updateToRevisionInfo store helpers. This is to allow enforcing validation sets and install/refresh required snaps to satisfy their constraints. --- overlord/snapstate/snapstate.go | 11 +- overlord/snapstate/snapstate_install_test.go | 27 +++++ overlord/snapstate/snapstate_update_test.go | 53 +++++++++ overlord/snapstate/storehelpers.go | 117 +++++++++++-------- 4 files changed, 157 insertions(+), 51 deletions(-) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 953505ccbc4..b42a0c035c7 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1975,10 +1975,11 @@ func Switch(st *state.State, name string, opts *RevisionOptions) (*state.TaskSet // RevisionOptions control the selection of a snap revision. type RevisionOptions struct { - Channel string - Revision snap.Revision - CohortKey string - LeaveCohort bool + Channel string + Revision snap.Revision + CohortKey string + LeaveCohort bool + ValidationSets []string } // Update initiates a change updating a snap. @@ -2169,7 +2170,7 @@ func infoForUpdate(st *state.State, snapst *SnapState, name string, opts *Revisi } if sideInfo == nil { // refresh from given revision from store - return updateToRevisionInfo(st, snapst, opts.Revision, userID, flags, deviceCtx) + return updateToRevisionInfo(st, snapst, opts, userID, flags, deviceCtx) } // refresh-to-local, this assumes the snap revision is mounted diff --git a/overlord/snapstate/snapstate_install_test.go b/overlord/snapstate/snapstate_install_test.go index d2ba1cc87c1..c31ad51f19f 100644 --- a/overlord/snapstate/snapstate_install_test.go +++ b/overlord/snapstate/snapstate_install_test.go @@ -4397,6 +4397,33 @@ func (s *validationSetsSuite) TestInstallSnapInvalidByValidationSetIgnoreValidat c.Assert(s.fakeBackend.ops[1], DeepEquals, expectedOp) } +func (s *validationSetsSuite) TestInstallSnapWithValidationSets(c *C) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + return nil, fmt.Errorf("unexpected") + }) + defer restore() + + s.state.Lock() + defer s.state.Unlock() + + opts := &snapstate.RevisionOptions{Revision: snap.R(11), ValidationSets: []string{"16/foo/bar", "16/foo/baz"}} + _, err := snapstate.Install(context.Background(), s.state, "some-snap", opts, 0, snapstate.Flags{}) + c.Assert(err, IsNil) + + // validation sets are not set on the action + expectedOp := fakeOp{ + op: "storesvc-snap-action:action", + action: store.SnapAction{ + Action: "install", + InstanceName: "some-snap", + ValidationSets: [][]string{{"16", "foo", "bar"}, {"16", "foo", "baz"}}, + Revision: snap.R(11), + }, + revno: snap.R(11), + } + c.Assert(s.fakeBackend.ops[1], DeepEquals, expectedOp) +} + func (s *snapmgrTestSuite) TestInstallPrerequisiteWithSameDeviceContext(c *C) { s.state.Lock() defer s.state.Unlock() diff --git a/overlord/snapstate/snapstate_update_test.go b/overlord/snapstate/snapstate_update_test.go index 8b1b8c745b2..5abda387651 100644 --- a/overlord/snapstate/snapstate_update_test.go +++ b/overlord/snapstate/snapstate_update_test.go @@ -7041,6 +7041,59 @@ func (s *validationSetsSuite) TestUpdateSnapRequiredByValidationSetAlreadyAtRequ c.Assert(s.fakeBackend.ops[1], DeepEquals, expectedOp) } +func (s *validationSetsSuite) TestUpdateToRevisionWithValidationSets(c *C) { + restore := snapstate.MockEnforcedValidationSets(func(st *state.State, extraVss ...*asserts.ValidationSet) (*snapasserts.ValidationSets, error) { + return nil, fmt.Errorf("unexpected") + }) + defer restore() + + s.state.Lock() + defer s.state.Unlock() + + si := &snap.SideInfo{RealName: "some-snap", SnapID: "some-snap-id", Revision: snap.R(1)} + snapstate.Set(s.state, "some-snap", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{si}, + Current: snap.R(1), + SnapType: "app", + }) + snaptest.MockSnap(c, `name: some-snap`, si) + + refreshedDate := fakeRevDateEpoch.AddDate(0, 0, 1) + + ts, err := snapstate.Update(s.state, "some-snap", &snapstate.RevisionOptions{Revision: snap.R(11), ValidationSets: []string{"16/foo/bar", "16/foo/baz"}}, 0, snapstate.Flags{}) + c.Assert(err, IsNil) + + var snapsup snapstate.SnapSetup + err = ts.Tasks()[0].Get("snap-setup", &snapsup) + c.Assert(err, IsNil) + + // new snap revision from the store + c.Check(snapsup.Revision(), Equals, snap.R(11)) + + c.Assert(s.fakeBackend.ops, HasLen, 2) + expectedOps := fakeOps{{ + op: "storesvc-snap-action", + curSnaps: []store.CurrentSnap{{ + InstanceName: "some-snap", + SnapID: "some-snap-id", + Revision: snap.R(1), + Epoch: snap.E("1*"), + RefreshedDate: refreshedDate, + }}}, { + op: "storesvc-snap-action:action", + action: store.SnapAction{ + Action: "refresh", + InstanceName: "some-snap", + SnapID: "some-snap-id", + Revision: snap.R(11), + ValidationSets: [][]string{{"16", "foo", "bar"}, {"16", "foo", "baz"}}, + }, + revno: snap.R(11), + }} + c.Assert(s.fakeBackend.ops, DeepEquals, expectedOps) +} + func (s *snapmgrTestSuite) TestUpdatePrerequisiteWithSameDeviceContext(c *C) { s.state.Lock() defer s.state.Unlock() diff --git a/overlord/snapstate/storehelpers.go b/overlord/snapstate/storehelpers.go index 515d090c079..8190c11836d 100644 --- a/overlord/snapstate/storehelpers.go +++ b/overlord/snapstate/storehelpers.go @@ -253,35 +253,40 @@ func installInfo(ctx context.Context, st *state.State, name string, revOpts *Rev var requiredValSets []string if !flags.IgnoreValidation { - enforcedSets, err := EnforcedValidationSets(st) - if err != nil { - return store.SnapActionResult{}, err - } - - if enforcedSets != nil { - // check for invalid presence first to have a list of sets where it's invalid - invalidForValSets, err := enforcedSets.CheckPresenceInvalid(naming.Snap(name)) + if len(revOpts.ValidationSets) > 0 { + requiredRevision = revOpts.Revision + requiredValSets = revOpts.ValidationSets + } else { + enforcedSets, err := EnforcedValidationSets(st) if err != nil { - if _, ok := err.(*snapasserts.PresenceConstraintError); !ok { - return store.SnapActionResult{}, err - } // else presence is optional or required, carry on + return store.SnapActionResult{}, err } - if len(invalidForValSets) > 0 { - return store.SnapActionResult{}, fmt.Errorf("cannot install snap %q due to enforcing rules of validation set %s", name, strings.Join(invalidForValSets, ",")) + + if enforcedSets != nil { + // check for invalid presence first to have a list of sets where it's invalid + invalidForValSets, err := enforcedSets.CheckPresenceInvalid(naming.Snap(name)) + if err != nil { + if _, ok := err.(*snapasserts.PresenceConstraintError); !ok { + return store.SnapActionResult{}, err + } // else presence is optional or required, carry on + } + if len(invalidForValSets) > 0 { + return store.SnapActionResult{}, fmt.Errorf("cannot install snap %q due to enforcing rules of validation set %s", name, strings.Join(invalidForValSets, ",")) + } + requiredValSets, requiredRevision, err = enforcedSets.CheckPresenceRequired(naming.Snap(name)) + if err != nil { + return store.SnapActionResult{}, err + } } - requiredValSets, requiredRevision, err = enforcedSets.CheckPresenceRequired(naming.Snap(name)) - if err != nil { - return store.SnapActionResult{}, err + + // check if desired revision matches the revision required by validation sets + if !requiredRevision.Unset() && !revOpts.Revision.Unset() && revOpts.Revision.N != requiredRevision.N { + return store.SnapActionResult{}, fmt.Errorf("cannot install snap %q at requested revision %s without --ignore-validation, revision %s required by validation sets: %s", + name, revOpts.Revision, requiredRevision, strings.Join(requiredValSets, ",")) } } } - // check if desired revision matches the revision required by validation sets - if !requiredRevision.Unset() && !revOpts.Revision.Unset() && revOpts.Revision.N != requiredRevision.N { - return store.SnapActionResult{}, fmt.Errorf("cannot install snap %q at requested revision %s without --ignore-validation, revision %s required by validation sets: %s", - name, revOpts.Revision, requiredRevision, strings.Join(requiredValSets, ",")) - } - if len(requiredValSets) > 0 { setActionValidationSetsAndRequiredRevision(action, requiredValSets, requiredRevision) } @@ -338,13 +343,24 @@ func updateInfo(st *state.State, snapst *SnapState, opts *RevisionOptions, userI Flags: storeFlags, } + if len(opts.ValidationSets) > 0 { + // update to a specific revision is handled by updateToRevisionInfo. + // updating without a revision while enforcing validation sets is not a + // viable scenario (although we could handle it if desired), we only install/refresh + // what's missing and explicitly required by requested validation sets. + return nil, fmt.Errorf("internal error: list of validation sets is not expected for update without revision") + } + + var requiredRevision snap.Revision + var requiredValsets []string + if !flags.IgnoreValidation { enforcedSets, err := EnforcedValidationSets(st) if err != nil { return nil, err } if enforcedSets != nil { - requiredValsets, requiredRevision, err := enforcedSets.CheckPresenceRequired(naming.Snap(curInfo.InstanceName())) + requiredValsets, requiredRevision, err = enforcedSets.CheckPresenceRequired(naming.Snap(curInfo.InstanceName())) if err != nil { return nil, err } @@ -442,7 +458,7 @@ func singleActionResult(name, action string, results []store.SnapActionResult, e return store.SnapActionResult{}, e } -func updateToRevisionInfo(st *state.State, snapst *SnapState, revision snap.Revision, userID int, flags Flags, deviceCtx DeviceContext) (*snap.Info, error) { +func updateToRevisionInfo(st *state.State, snapst *SnapState, revOpts *RevisionOptions, userID int, flags Flags, deviceCtx DeviceContext) (*snap.Info, error) { curSnaps, err := currentSnaps(st) if err != nil { return nil, err @@ -463,44 +479,53 @@ func updateToRevisionInfo(st *state.State, snapst *SnapState, revision snap.Revi SnapID: curInfo.SnapID, InstanceName: curInfo.InstanceName(), // the desired revision - Revision: revision, + Revision: revOpts.Revision, } + var requiredRevision snap.Revision + var requiredValsets []string + var storeFlags store.SnapActionFlags if !flags.IgnoreValidation { - enforcedSets, err := EnforcedValidationSets(st) - if err != nil { - return nil, err - } - if enforcedSets != nil { - requiredValsets, requiredRevision, err := enforcedSets.CheckPresenceRequired(naming.Snap(curInfo.InstanceName())) + if len(revOpts.ValidationSets) > 0 { + requiredRevision = revOpts.Revision + requiredValsets = revOpts.ValidationSets + } else { + enforcedSets, err := EnforcedValidationSets(st) if err != nil { return nil, err } - if !requiredRevision.Unset() { - if revision != requiredRevision { - return nil, fmt.Errorf("cannot update snap %q to revision %s without --ignore-validation, revision %s is required by validation sets: %s", - curInfo.InstanceName(), revision, requiredRevision, strings.Join(requiredValsets, ",")) + if enforcedSets != nil { + requiredValsets, requiredRevision, err = enforcedSets.CheckPresenceRequired(naming.Snap(curInfo.InstanceName())) + if err != nil { + return nil, err } - // note, not checking if required revision matches snapst.Current because - // this is already indirectly prevented by infoForUpdate(). - - // specific revision is required, reset cohort in current snaps - for _, sn := range curSnaps { - if sn.InstanceName == curInfo.InstanceName() { - sn.CohortKey = "" - break + if !requiredRevision.Unset() { + if revOpts.Revision != requiredRevision { + return nil, fmt.Errorf("cannot update snap %q to revision %s without --ignore-validation, revision %s is required by validation sets: %s", + curInfo.InstanceName(), revOpts.Revision, requiredRevision, strings.Join(requiredValsets, ",")) + } + // note, not checking if required revision matches snapst.Current because + // this is already indirectly prevented by infoForUpdate(). + + // specific revision is required, reset cohort in current snaps + for _, sn := range curSnaps { + if sn.InstanceName == curInfo.InstanceName() { + sn.CohortKey = "" + break + } } } } - if len(requiredValsets) > 0 { - setActionValidationSetsAndRequiredRevision(action, requiredValsets, requiredRevision) - } } } else { storeFlags = store.SnapActionIgnoreValidation } + if len(requiredValsets) > 0 { + setActionValidationSetsAndRequiredRevision(action, requiredValsets, requiredRevision) + } + action.Flags = storeFlags theStore := Store(st, deviceCtx) From bf86a081d792f31cddadf319fbc884c3515254c1 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 22 Jun 2022 14:57:12 +0200 Subject: [PATCH 137/153] cmd/snapd-apparmor: fix unit tests on distros which do not support reexec Distros that do not support reexec have an additional line logged in the output, such that the overall log looks like this: ... value string = "" + ... "2022/06/22 14:56:34.246323 tool_linux.go:68: DEBUG: re-exec not supported on distro \"arch\" yet\n" + ... "2022/06/22 14:56:34.250114 main.go:132: Loading profiles [/tmp/check-228349711/6/var/lib/snapd/apparmor/profiles/foo]\n" Fix the unit test such that it runs correctly everywhere. Signed-off-by: Maciej Borzecki --- cmd/snapd-apparmor/main_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/snapd-apparmor/main_test.go b/cmd/snapd-apparmor/main_test.go index 6a3c0be6035..f29af70a158 100644 --- a/cmd/snapd-apparmor/main_test.go +++ b/cmd/snapd-apparmor/main_test.go @@ -25,7 +25,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" "testing" . "gopkg.in/check.v1" @@ -241,7 +240,5 @@ func (s *integrationSuite) TestRunNormalLoadsProfiles(c *C) { err := snapd_apparmor.Run() c.Assert(err, IsNil) c.Assert(s.parserCmd.Calls(), HasLen, 1) - logLines := strings.Split(strings.TrimSpace(s.logBuf.String()), "\n") - c.Check(logLines, HasLen, 1) - c.Check(logLines[0], Matches, `.* main.go:[0-9]+: Loading profiles \[.*/var/lib/snapd/apparmor/profiles/foo\]`) + c.Check(s.logBuf.String(), Matches, `(?s).* main.go:[0-9]+: Loading profiles \[.*/var/lib/snapd/apparmor/profiles/foo\].*`) } From 3a871f56a3cd1220190f5eb6f25af7197a12e68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 22 Jun 2022 17:47:29 +0200 Subject: [PATCH 138/153] Fix test comment. Move ValidationSets field after Revision. --- overlord/snapstate/snapstate.go | 2 +- overlord/snapstate/snapstate_install_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index b42a0c035c7..8b0511cd9d8 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1977,9 +1977,9 @@ func Switch(st *state.State, name string, opts *RevisionOptions) (*state.TaskSet type RevisionOptions struct { Channel string Revision snap.Revision + ValidationSets []string CohortKey string LeaveCohort bool - ValidationSets []string } // Update initiates a change updating a snap. diff --git a/overlord/snapstate/snapstate_install_test.go b/overlord/snapstate/snapstate_install_test.go index c31ad51f19f..0aac5f2a0b5 100644 --- a/overlord/snapstate/snapstate_install_test.go +++ b/overlord/snapstate/snapstate_install_test.go @@ -4410,7 +4410,7 @@ func (s *validationSetsSuite) TestInstallSnapWithValidationSets(c *C) { _, err := snapstate.Install(context.Background(), s.state, "some-snap", opts, 0, snapstate.Flags{}) c.Assert(err, IsNil) - // validation sets are not set on the action + // validation sets are set on the action expectedOp := fakeOp{ op: "storesvc-snap-action:action", action: store.SnapAction{ From 51f2364751a12d854d7bd0255ea2bc961e4f3978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 22 Jun 2022 12:57:23 +0200 Subject: [PATCH 139/153] Implement api handler for refresh with validation sets (i.e. for `snap validate --enforce --refresh ...`. The two critical functions (snapstate.EnforceSnaps and assertstate.TryEnforceValidationSets are still stubs). --- daemon/api.go | 1 + daemon/api_snaps.go | 43 ++++++++++- daemon/api_snaps_test.go | 112 ++++++++++++++++++++++++++++ daemon/api_validate.go | 1 + daemon/export_test.go | 14 ++++ overlord/assertstate/assertstate.go | 8 ++ overlord/snapstate/snapstate.go | 7 ++ 7 files changed, 185 insertions(+), 1 deletion(-) diff --git a/daemon/api.go b/daemon/api.go index d94d7e5996d..7d321abc8f6 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -140,6 +140,7 @@ var ( snapstateUpdateMany = snapstate.UpdateMany snapstateInstallMany = snapstate.InstallMany snapstateRemoveMany = snapstate.RemoveMany + snapstateEnforceSnaps = snapstate.EnforceSnaps snapstateRevert = snapstate.Revert snapstateRevertToRevision = snapstate.RevertToRevision snapstateSwitch = snapstate.Switch diff --git a/daemon/api_snaps.go b/daemon/api_snaps.go index db78db24415..05309c52ccd 100644 --- a/daemon/api_snaps.go +++ b/daemon/api_snaps.go @@ -28,6 +28,7 @@ import ( "net/http" "strings" + "github.com/snapcore/snapd/asserts/snapasserts" "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/logger" @@ -201,6 +202,7 @@ type snapInstruction struct { Transaction client.TransactionType `json:"transaction"` Snaps []string `json:"snaps"` Users []string `json:"users"` + ValidationSets []string `json:"validation-sets"` // The fields below should not be unmarshalled into. Do not export them. userID int @@ -560,7 +562,11 @@ type snapManyActionFunc func(*snapInstruction, *state.State) (*snapInstructionRe func (inst *snapInstruction) dispatchForMany() (op snapManyActionFunc) { switch inst.Action { case "refresh": - op = snapUpdateMany + if len(inst.ValidationSets) > 0 { + op = snapEnforceValidationSets + } else { + op = snapUpdateMany + } case "install": op = snapInstallMany case "remove": @@ -654,6 +660,41 @@ func snapUpdateMany(inst *snapInstruction, st *state.State) (*snapInstructionRes }, nil } +func snapEnforceValidationSets(inst *snapInstruction, st *state.State) (*snapInstructionResult, error) { + if len(inst.ValidationSets) > 0 && len(inst.Snaps) != 0 { + return nil, fmt.Errorf("snap names cannot be specified with validation sets to enforce") + } + + snaps, ignoreValidation, err := snapstate.InstalledSnaps(st) + if err != nil { + return nil, err + } + + if err := assertstateRefreshSnapAssertions(st, inst.userID, nil); err != nil { + return nil, err + } + + var validErr *snapasserts.ValidationSetsValidationError + err = assertstateTryEnforceValidationSets(st, inst.ValidationSets, inst.userID, snaps, ignoreValidation) + if err != nil { + var ok bool + validErr, ok = err.(*snapasserts.ValidationSetsValidationError) + if !ok { + return nil, err + } + } + tss, affected, err := snapstateEnforceSnaps(context.TODO(), st, inst.ValidationSets, validErr, inst.userID) + if err != nil { + return nil, err + } + + return &snapInstructionResult{ + Summary: fmt.Sprintf("Enforced validation sets: %s", strutil.Quoted(inst.ValidationSets)), + Affected: affected, + Tasksets: tss, + }, nil +} + func snapRemoveMany(inst *snapInstruction, st *state.State) (*snapInstructionResult, error) { removed, tasksets, err := snapstateRemoveMany(st, inst.Snaps) if err != nil { diff --git a/daemon/api_snaps_test.go b/daemon/api_snaps_test.go index 460fa5c0dab..42b93f9035b 100644 --- a/daemon/api_snaps_test.go +++ b/daemon/api_snaps_test.go @@ -36,6 +36,7 @@ import ( "gopkg.in/check.v1" "github.com/snapcore/snapd/arch" + "github.com/snapcore/snapd/asserts/snapasserts" "github.com/snapcore/snapd/client" "github.com/snapcore/snapd/daemon" "github.com/snapcore/snapd/dirs" @@ -2301,3 +2302,114 @@ func (s *snapsSuite) TestPostSnapWrongTransaction(c *check.C) { c.Check(rspe.Message, check.Equals, expectedErr, check.Commentf("%q", action)) } } + +func (s *snapsSuite) TestRefreshEnforce(c *check.C) { + var refreshSnapAssertions bool + + defer daemon.MockAssertstateRefreshSnapAssertions(func(s *state.State, userID int, opts *assertstate.RefreshAssertionsOptions) error { + refreshSnapAssertions = true + c.Check(opts, check.IsNil) + return nil + })() + + var tryEnforceValidationSets bool + defer daemon.MockAssertstateTryEnforceValidationSets(func(st *state.State, validationSets []string, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + tryEnforceValidationSets = true + return nil + })() + + defer daemon.MockSnapstateEnforceSnaps(func(ctx context.Context, st *state.State, validationSets []string, validErr *snapasserts.ValidationSetsValidationError, userID int) ([]*state.TaskSet, []string, error) { + c.Check(validationSets, check.DeepEquals, []string{"foo/bar=2", "foo/baz"}) + t := st.NewTask("fake-enforce-snaps", "...") + return []*state.TaskSet{state.NewTaskSet(t)}, []string{"some-snap", "other-snap"}, nil + })() + + d := s.daemon(c) + inst := &daemon.SnapInstruction{Action: "refresh", ValidationSets: []string{"foo/bar=2", "foo/baz"}} + + st := d.Overlord().State() + st.Lock() + defer st.Unlock() + + res, err := inst.DispatchForMany()(inst, st) + c.Assert(err, check.IsNil) + c.Check(res.Summary, check.Equals, `Enforced validation sets: "foo/bar=2", "foo/baz"`) + c.Check(res.Affected, check.DeepEquals, []string{"some-snap", "other-snap"}) + c.Check(refreshSnapAssertions, check.Equals, true) + c.Check(tryEnforceValidationSets, check.Equals, true) +} + +func (s *snapsSuite) TestRefreshEnforceTryEnforceValidationSetsError(c *check.C) { + var refreshSnapAssertions int + defer daemon.MockAssertstateRefreshSnapAssertions(func(s *state.State, userID int, opts *assertstate.RefreshAssertionsOptions) error { + refreshSnapAssertions++ + c.Check(opts, check.IsNil) + return nil + })() + + tryEnforceErr := fmt.Errorf("boom") + defer daemon.MockAssertstateTryEnforceValidationSets(func(st *state.State, validationSets []string, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + return tryEnforceErr + })() + + var snapstateEnforceSnaps int + defer daemon.MockSnapstateEnforceSnaps(func(ctx context.Context, st *state.State, validationSets []string, validErr *snapasserts.ValidationSetsValidationError, userID int) ([]*state.TaskSet, []string, error) { + snapstateEnforceSnaps++ + c.Check(validErr, check.NotNil) + return nil, nil, nil + })() + + d := s.daemon(c) + inst := &daemon.SnapInstruction{Action: "refresh", ValidationSets: []string{"foo/baz"}} + + st := d.Overlord().State() + st.Lock() + defer st.Unlock() + + _, err := inst.DispatchForMany()(inst, st) + c.Assert(err, check.ErrorMatches, `boom`) + c.Check(refreshSnapAssertions, check.Equals, 1) + c.Check(snapstateEnforceSnaps, check.Equals, 0) + + // ValidationSetsValidationError is expected and fine + tryEnforceErr = &snapasserts.ValidationSetsValidationError{} + + _, err = inst.DispatchForMany()(inst, st) + c.Assert(err, check.IsNil) + c.Check(refreshSnapAssertions, check.Equals, 2) + c.Check(snapstateEnforceSnaps, check.Equals, 1) +} + +func (s *snapsSuite) TestRefreshEnforceWithSnapsIsAnError(c *check.C) { + var refreshSnapAssertions bool + defer daemon.MockAssertstateRefreshSnapAssertions(func(s *state.State, userID int, opts *assertstate.RefreshAssertionsOptions) error { + refreshSnapAssertions = true + c.Check(opts, check.IsNil) + return fmt.Errorf("unexptected") + })() + + var tryEnforceValidationSets bool + defer daemon.MockAssertstateTryEnforceValidationSets(func(st *state.State, validationSets []string, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + tryEnforceValidationSets = true + return fmt.Errorf("unexpected") + })() + + var snapstateEnforceSnaps bool + defer daemon.MockSnapstateEnforceSnaps(func(ctx context.Context, st *state.State, validationSets []string, validErr *snapasserts.ValidationSetsValidationError, userID int) ([]*state.TaskSet, []string, error) { + snapstateEnforceSnaps = true + return nil, nil, fmt.Errorf("unexpected") + })() + + d := s.daemon(c) + inst := &daemon.SnapInstruction{Action: "refresh", Snaps: []string{"some-snap"}, ValidationSets: []string{"foo/baz"}} + + st := d.Overlord().State() + st.Lock() + defer st.Unlock() + + _, err := inst.DispatchForMany()(inst, st) + c.Assert(err, check.ErrorMatches, `snap names cannot be specified with validation sets to enforce`) + c.Check(refreshSnapAssertions, check.Equals, false) + c.Check(tryEnforceValidationSets, check.Equals, false) + c.Check(snapstateEnforceSnaps, check.Equals, false) +} diff --git a/daemon/api_validate.go b/daemon/api_validate.go index f07d0f6912d..7a6f95172fc 100644 --- a/daemon/api_validate.go +++ b/daemon/api_validate.go @@ -272,6 +272,7 @@ func applyValidationSet(c *Command, r *http.Request, user *auth.UserState) Respo var assertstateMonitorValidationSet = assertstate.MonitorValidationSet var assertstateEnforceValidationSet = assertstate.EnforceValidationSet +var assertstateTryEnforceValidationSets = assertstate.TryEnforceValidationSets // updateValidationSet handles snap validate --monitor and --enforce accountId/name[=sequence]. func updateValidationSet(st *state.State, accountID, name string, reqMode string, sequence int, user *auth.UserState) Response { diff --git a/daemon/export_test.go b/daemon/export_test.go index 6ba228b384b..43e81f1119d 100644 --- a/daemon/export_test.go +++ b/daemon/export_test.go @@ -26,6 +26,7 @@ import ( "github.com/gorilla/mux" + "github.com/snapcore/snapd/asserts/snapasserts" "github.com/snapcore/snapd/boot" "github.com/snapcore/snapd/overlord" "github.com/snapcore/snapd/overlord/assertstate" @@ -33,6 +34,7 @@ import ( "github.com/snapcore/snapd/overlord/snapstate" "github.com/snapcore/snapd/overlord/state" "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/testutil" ) func APICommands() []*Command { @@ -116,6 +118,12 @@ func MockAssertstateRefreshSnapAssertions(mock func(*state.State, int, *assertst } } +func MockAssertstateTryEnforceValidationSets(f func(st *state.State, validationSets []string, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error) (restore func()) { + r := testutil.Backup(&assertstateTryEnforceValidationSets) + assertstateTryEnforceValidationSets = f + return r +} + func MockSnapstateInstall(mock func(context.Context, *state.State, string, *snapstate.RevisionOptions, int, snapstate.Flags) (*state.TaskSet, error)) (restore func()) { oldSnapstateInstall := snapstateInstall snapstateInstall = mock @@ -204,6 +212,12 @@ func MockSnapstateInstallPathMany(f func(context.Context, *state.State, []*snap. } } +func MockSnapstateEnforceSnaps(f func(ctx context.Context, st *state.State, validationSets []string, validErr *snapasserts.ValidationSetsValidationError, userID int) ([]*state.TaskSet, []string, error)) func() { + r := testutil.Backup(&assertstateTryEnforceValidationSets) + snapstateEnforceSnaps = f + return r +} + func MockReboot(f func(boot.RebootAction, time.Duration, *boot.RebootInfo) error) func() { reboot = f return func() { reboot = boot.Reboot } diff --git a/overlord/assertstate/assertstate.go b/overlord/assertstate/assertstate.go index 5d54fa92128..3c7b7b7ea46 100644 --- a/overlord/assertstate/assertstate.go +++ b/overlord/assertstate/assertstate.go @@ -772,6 +772,14 @@ func validationSetAssertionForEnforce(st *state.State, accountID, name string, s return vs, latest, err } +// TryEnforceValidationSets tries to fetch the given validation sets and enforce them (together with currently tracked validation sets) against installed snaps, +// but doesn't update tracking information. It may return snapasserts.ValidationSetsValidationError which can be used to install/remove snaps as required +// to satisfy validation sets constraints. +func TryEnforceValidationSets(st *state.State, validationSets []string, userID int, snaps []*snapasserts.InstalledSnap, ignoreValidation map[string]bool) error { + // TODO + return fmt.Errorf("not implemented") +} + // EnforceValidationSet tries to fetch the given validation set and enforce it. // If all validation sets constrains are satisfied, the current validation sets // tracking state is saved in validation sets history. diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 953505ccbc4..d3f0c7e2216 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1349,6 +1349,13 @@ func UpdateMany(ctx context.Context, st *state.State, names []string, userID int return updateManyFiltered(ctx, st, names, userID, nil, flags, "") } +// EnforceSnaps installs/updates/removes snaps reported by validErr. validationSets is the list of sets passed by the user and it's used in the final +// stage to update validation-sets tracking in the state. +func EnforceSnaps(ctx context.Context, st *state.State, validationSets []string, validErr *snapasserts.ValidationSetsValidationError, userID int) (tasksets []*state.TaskSet, names []string, err error) { + // TODO + return nil, nil, fmt.Errorf("not implemented") +} + // updateFilter is the type of function that can be passed to // updateManyFromChange so it filters the updates. // From 6b85841cee15195d0cb5434c8b9c653af8ea3a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Wed, 22 Jun 2022 17:09:54 +0200 Subject: [PATCH 140/153] Naming tweaks. --- daemon/api_snaps.go | 10 +++++----- overlord/snapstate/snapstate.go | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/daemon/api_snaps.go b/daemon/api_snaps.go index 05309c52ccd..6b50c674b7b 100644 --- a/daemon/api_snaps.go +++ b/daemon/api_snaps.go @@ -665,7 +665,7 @@ func snapEnforceValidationSets(inst *snapInstruction, st *state.State) (*snapIns return nil, fmt.Errorf("snap names cannot be specified with validation sets to enforce") } - snaps, ignoreValidation, err := snapstate.InstalledSnaps(st) + snaps, ignoreValidationSnaps, err := snapstate.InstalledSnaps(st) if err != nil { return nil, err } @@ -674,16 +674,16 @@ func snapEnforceValidationSets(inst *snapInstruction, st *state.State) (*snapIns return nil, err } - var validErr *snapasserts.ValidationSetsValidationError - err = assertstateTryEnforceValidationSets(st, inst.ValidationSets, inst.userID, snaps, ignoreValidation) + var validationErr *snapasserts.ValidationSetsValidationError + err = assertstateTryEnforceValidationSets(st, inst.ValidationSets, inst.userID, snaps, ignoreValidationSnaps) if err != nil { var ok bool - validErr, ok = err.(*snapasserts.ValidationSetsValidationError) + validationErr, ok = err.(*snapasserts.ValidationSetsValidationError) if !ok { return nil, err } } - tss, affected, err := snapstateEnforceSnaps(context.TODO(), st, inst.ValidationSets, validErr, inst.userID) + tss, affected, err := snapstateEnforceSnaps(context.TODO(), st, inst.ValidationSets, validationErr, inst.userID) if err != nil { return nil, err } diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index d3f0c7e2216..6b7bbc9a327 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1349,8 +1349,10 @@ func UpdateMany(ctx context.Context, st *state.State, names []string, userID int return updateManyFiltered(ctx, st, names, userID, nil, flags, "") } -// EnforceSnaps installs/updates/removes snaps reported by validErr. validationSets is the list of sets passed by the user and it's used in the final -// stage to update validation-sets tracking in the state. +// EnforceSnaps installs/updates/removes snaps reported by validErr. +// validationSets is the list of sets passed by the user and it's used in the +// final stage to update validation-sets tracking in the state. +// userID is used for store auth. func EnforceSnaps(ctx context.Context, st *state.State, validationSets []string, validErr *snapasserts.ValidationSetsValidationError, userID int) (tasksets []*state.TaskSet, names []string, err error) { // TODO return nil, nil, fmt.Errorf("not implemented") From 1746c0fd97849ab6ba60a185f88405fe0d005941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Sto=C5=82owski?= Date: Thu, 23 Jun 2022 09:32:50 +0200 Subject: [PATCH 141/153] Naming/clarification tweaks. --- daemon/api_snaps.go | 5 +++++ overlord/snapstate/snapstate.go | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/daemon/api_snaps.go b/daemon/api_snaps.go index 6b50c674b7b..a10bdec442f 100644 --- a/daemon/api_snaps.go +++ b/daemon/api_snaps.go @@ -670,6 +670,11 @@ func snapEnforceValidationSets(inst *snapInstruction, st *state.State) (*snapIns return nil, err } + // we need refreshed snap-declarations, this ensures that snap-declarations + // and their prerequisite assertions are updated regularly; do not update all + // validation-set assertions (this is implied by passing nil opts) - only + // those requested via inst.ValidationSets will get updated by + // assertstateTryEnforceValidationSets below. if err := assertstateRefreshSnapAssertions(st, inst.userID, nil); err != nil { return nil, err } diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go index 6b7bbc9a327..ccf1b6958e6 100644 --- a/overlord/snapstate/snapstate.go +++ b/overlord/snapstate/snapstate.go @@ -1349,11 +1349,11 @@ func UpdateMany(ctx context.Context, st *state.State, names []string, userID int return updateManyFiltered(ctx, st, names, userID, nil, flags, "") } -// EnforceSnaps installs/updates/removes snaps reported by validErr. +// EnforceSnaps installs/updates/removes snaps reported by validationErrorToSolve. // validationSets is the list of sets passed by the user and it's used in the // final stage to update validation-sets tracking in the state. // userID is used for store auth. -func EnforceSnaps(ctx context.Context, st *state.State, validationSets []string, validErr *snapasserts.ValidationSetsValidationError, userID int) (tasksets []*state.TaskSet, names []string, err error) { +func EnforceSnaps(ctx context.Context, st *state.State, validationSets []string, validationErrorToSolve *snapasserts.ValidationSetsValidationError, userID int) (tasksets []*state.TaskSet, names []string, err error) { // TODO return nil, nil, fmt.Errorf("not implemented") } From 86aa92e632d2988cab1a315aacf838b5dd618a62 Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Wed, 8 Jun 2022 12:36:15 +0100 Subject: [PATCH 142/153] tests: add spread test for migrate-home cmd Signed-off-by: Miguel Pires --- tests/main/debug-migrate-home/task.yaml | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/main/debug-migrate-home/task.yaml diff --git a/tests/main/debug-migrate-home/task.yaml b/tests/main/debug-migrate-home/task.yaml new file mode 100644 index 00000000000..3b352a0e398 --- /dev/null +++ b/tests/main/debug-migrate-home/task.yaml @@ -0,0 +1,45 @@ +summary: Check the debug command migrate-home migrates snaps' homes + +environment: + NAME: test-snapd-tools + +prepare: | + snap pack "$TESTSLIB/snaps/$NAME" + "$TESTSTOOLS"/snaps-state install-local "$NAME" + + snap pack "$TESTSLIB"/snaps/basic + "$TESTSTOOLS"/snaps-state install-local basic + +restore: | + snap unset system experimental.move-snap-home-dir + snap remove --purge basic + snap remove --purge "$NAME" + +execute: | + echo "Check that migrate home doesn't work without setting the experimental flag" + if got=$(snap debug migrate-home "$NAME" 2>&1); then + echo 'Calling "snap debug migrate-home" without setting "experimental.move-snap-home-dir" should fail' + exit 1 + fi + #shellcheck disable=SC2086 + echo $got | MATCH 'error: cannot migrate to ~/Snap: flag "experimental.move-snap-home-dir" is not set' + + echo "Check that migrate home migrates the data under SNAP_USER_DATA" + snap set system experimental.move-snap-home-dir=true + + #shellcheck disable=SC2016 + "$NAME".cmd sh -c 'echo foo > $HOME/foo' + MATCH "foo" < "$HOME/snap/$NAME/current/foo" + not test -e "$HOME"/Snap + + snap debug migrate-home "$NAME" basic + + MATCH "foo" < "$HOME/Snap/$NAME/foo" + + echo "Check that the snap's environment variables and AppArmor rules are correct" + snapEnv=$("$NAME".env) + echo "$snapEnv" | MATCH "HOME=$HOME/Snap/$NAME" + + #shellcheck disable=SC2016 + "$NAME".cmd sh -c 'echo "bar" > $HOME/bar' + MATCH "bar" < "$HOME/Snap/$NAME/bar" From a73c5a368e3d4b927cea97863c83f9bec65a3360 Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Thu, 23 Jun 2022 19:00:01 +0100 Subject: [PATCH 143/153] tests: simplified migrate-home test Signed-off-by: Miguel Pires --- tests/main/debug-migrate-home/task.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/main/debug-migrate-home/task.yaml b/tests/main/debug-migrate-home/task.yaml index 3b352a0e398..5ab9ac11f42 100644 --- a/tests/main/debug-migrate-home/task.yaml +++ b/tests/main/debug-migrate-home/task.yaml @@ -4,10 +4,7 @@ environment: NAME: test-snapd-tools prepare: | - snap pack "$TESTSLIB/snaps/$NAME" "$TESTSTOOLS"/snaps-state install-local "$NAME" - - snap pack "$TESTSLIB"/snaps/basic "$TESTSTOOLS"/snaps-state install-local basic restore: | @@ -37,8 +34,7 @@ execute: | MATCH "foo" < "$HOME/Snap/$NAME/foo" echo "Check that the snap's environment variables and AppArmor rules are correct" - snapEnv=$("$NAME".env) - echo "$snapEnv" | MATCH "HOME=$HOME/Snap/$NAME" + "$NAME".env | MATCH "HOME=$HOME/Snap/$NAME" #shellcheck disable=SC2016 "$NAME".cmd sh -c 'echo "bar" > $HOME/bar' From 58d1cba59115e5c72cef6286d42966a5c1e0ab24 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Fri, 24 Jun 2022 11:36:30 +0200 Subject: [PATCH 144/153] interfaces/apparmor: update doc string to explain the new snap-* --- interfaces/apparmor/template.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go index 2c8ecec513b..c745448a931 100644 --- a/interfaces/apparmor/template.go +++ b/interfaces/apparmor/template.go @@ -190,7 +190,9 @@ var templateCommon = ` /usr/lib/os-release k, # systemd native journal API (see sd_journal_print(4)). This should be in - # AppArmor's base abstraction, but until it is, include here. + # AppArmor's base abstraction, but until it is, include here. We include + # the base journal path as well as the journal namespace pattern path. Each + # journal namespace for quota groups will be prefixed with 'snap-'. /run/systemd/journal{,.snap-*}/socket w, /run/systemd/journal{,.snap-*}/stdout rw, # 'r' shouldn't be needed, but journald # doesn't leak anything so allow From fbc4f6985fba96f8bb4587b0672c7444e0cd4fe5 Mon Sep 17 00:00:00 2001 From: Philip Meulengracht Date: Fri, 24 Jun 2022 11:57:17 +0200 Subject: [PATCH 145/153] overlord/servicestate: verify revision and snap on setup-profiles call --- overlord/servicestate/quota_handlers_test.go | 28 ++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/overlord/servicestate/quota_handlers_test.go b/overlord/servicestate/quota_handlers_test.go index ada182b3ac2..e89e7d3c1a1 100644 --- a/overlord/servicestate/quota_handlers_test.go +++ b/overlord/servicestate/quota_handlers_test.go @@ -1278,10 +1278,13 @@ func (s *quotaHandlersSuite) TestCreateJournalQuota(c *C) { // when creating the journal quota. var setupProfilesCalled int fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + setupProfilesCalled++ task.State().Lock() - _, err := snapstate.TaskSnapSetup(task) + snapInfo, err := snapstate.TaskSnapSetup(task) task.State().Unlock() - setupProfilesCalled++ + c.Assert(err, IsNil) + c.Check(snapInfo.InstanceName(), Equals, "test-snap") + c.Check(snapInfo.SideInfo.Revision, Equals, s.testSnapSideInfo.Revision) return err } s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) @@ -1336,10 +1339,13 @@ func (s *quotaHandlersSuite) TestAddJournalQuota(c *C) { // when creating the journal quota. var setupProfilesCalled int fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + setupProfilesCalled++ task.State().Lock() - _, err := snapstate.TaskSnapSetup(task) + snapInfo, err := snapstate.TaskSnapSetup(task) task.State().Unlock() - setupProfilesCalled++ + c.Assert(err, IsNil) + c.Check(snapInfo.InstanceName(), Equals, "test-snap") + c.Check(snapInfo.SideInfo.Revision, Equals, s.testSnapSideInfo.Revision) return err } s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) @@ -1416,10 +1422,13 @@ func (s *quotaHandlersSuite) TestUpdateJournalQuota(c *C) { // when creating the journal quota. var setupProfilesCalled int fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + setupProfilesCalled++ task.State().Lock() - _, err := snapstate.TaskSnapSetup(task) + snapInfo, err := snapstate.TaskSnapSetup(task) task.State().Unlock() - setupProfilesCalled++ + c.Assert(err, IsNil) + c.Check(snapInfo.InstanceName(), Equals, "test-snap") + c.Check(snapInfo.SideInfo.Revision, Equals, s.testSnapSideInfo.Revision) return err } s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) @@ -1475,10 +1484,13 @@ func (s *quotaHandlersSuite) TestRemoveJournalQuota(c *C) { // when creating the journal quota. var setupProfilesCalled int fakeHandler := func(task *state.Task, _ *tomb.Tomb) error { + setupProfilesCalled++ task.State().Lock() - _, err := snapstate.TaskSnapSetup(task) + snapInfo, err := snapstate.TaskSnapSetup(task) task.State().Unlock() - setupProfilesCalled++ + c.Assert(err, IsNil) + c.Check(snapInfo.InstanceName(), Equals, "test-snap") + c.Check(snapInfo.SideInfo.Revision, Equals, s.testSnapSideInfo.Revision) return err } s.o.TaskRunner().AddHandler("setup-profiles", fakeHandler, fakeHandler) From cd171a4ae4cf9324827a6d7ae2e5f67e6574abf4 Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Fri, 24 Jun 2022 14:04:14 +0100 Subject: [PATCH 146/153] tests: expanded test var Signed-off-by: Miguel Pires --- tests/main/debug-migrate-home/task.yaml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/main/debug-migrate-home/task.yaml b/tests/main/debug-migrate-home/task.yaml index 5ab9ac11f42..afc067f56f8 100644 --- a/tests/main/debug-migrate-home/task.yaml +++ b/tests/main/debug-migrate-home/task.yaml @@ -1,20 +1,17 @@ summary: Check the debug command migrate-home migrates snaps' homes -environment: - NAME: test-snapd-tools - prepare: | - "$TESTSTOOLS"/snaps-state install-local "$NAME" + "$TESTSTOOLS"/snaps-state install-local "test-snapd-tools" "$TESTSTOOLS"/snaps-state install-local basic restore: | snap unset system experimental.move-snap-home-dir snap remove --purge basic - snap remove --purge "$NAME" + snap remove --purge test-snapd-tools execute: | echo "Check that migrate home doesn't work without setting the experimental flag" - if got=$(snap debug migrate-home "$NAME" 2>&1); then + if got=$(snap debug migrate-home test-snapd-tools 2>&1); then echo 'Calling "snap debug migrate-home" without setting "experimental.move-snap-home-dir" should fail' exit 1 fi @@ -25,17 +22,17 @@ execute: | snap set system experimental.move-snap-home-dir=true #shellcheck disable=SC2016 - "$NAME".cmd sh -c 'echo foo > $HOME/foo' - MATCH "foo" < "$HOME/snap/$NAME/current/foo" + test-snapd-tools.cmd sh -c 'echo foo > $HOME/foo' + MATCH "foo" < "$HOME/snap/test-snapd-tools/current/foo" not test -e "$HOME"/Snap - snap debug migrate-home "$NAME" basic + snap debug migrate-home test-snapd-tools basic - MATCH "foo" < "$HOME/Snap/$NAME/foo" + MATCH "foo" < "$HOME/Snap/test-snapd-tools/foo" echo "Check that the snap's environment variables and AppArmor rules are correct" - "$NAME".env | MATCH "HOME=$HOME/Snap/$NAME" + test-snapd-tools.env | MATCH "HOME=$HOME/Snap/test-snapd-tools" #shellcheck disable=SC2016 - "$NAME".cmd sh -c 'echo "bar" > $HOME/bar' - MATCH "bar" < "$HOME/Snap/$NAME/bar" + test-snapd-tools.cmd sh -c 'echo "bar" > $HOME/bar' + MATCH "bar" < "$HOME/Snap/test-snapd-tools/bar" From 16683e87bd2c78cdbe37481c752bdcc380b1d072 Mon Sep 17 00:00:00 2001 From: Miguel Pires Date: Fri, 24 Jun 2022 15:44:52 +0100 Subject: [PATCH 147/153] cmd/snap: fix test failing due to timezone differences Signed-off-by: Miguel Pires --- cmd/snap/cmd_debug_state_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/snap/cmd_debug_state_test.go b/cmd/snap/cmd_debug_state_test.go index 37f64b29b81..7aeebb6b15b 100644 --- a/cmd/snap/cmd_debug_state_test.go +++ b/cmd/snap/cmd_debug_state_test.go @@ -22,6 +22,7 @@ package main_test import ( "io/ioutil" "path/filepath" + "time" . "gopkg.in/check.v1" @@ -271,6 +272,14 @@ func (s *SnapSuite) TestDebugTasksWithCycles(c *C) { } func (s *SnapSuite) TestDebugCheckForCycles(c *C) { + // we use local time when printing times in a human-friendly format, which can + // break the comparison below + oldLoc := time.Local + time.Local = time.UTC + defer func() { + time.Local = oldLoc + }() + dir := c.MkDir() stateFile := filepath.Join(dir, "test-state.json") c.Assert(ioutil.WriteFile(stateFile, stateCyclesJSON, 0644), IsNil) From d68ad2b109fbe9f55c4cf8a3df6039bdfb8069fc Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Fri, 24 Jun 2022 12:06:43 -0300 Subject: [PATCH 148/153] Remove references to opensuse 15.2 --- tests/main/system-usernames-illegal/task.yaml | 2 +- tests/main/system-usernames-install-twice/task.yaml | 2 +- tests/main/system-usernames-missing-user/task.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/main/system-usernames-illegal/task.yaml b/tests/main/system-usernames-illegal/task.yaml index 2f0a6326bb4..fe442cca81e 100644 --- a/tests/main/system-usernames-illegal/task.yaml +++ b/tests/main/system-usernames-illegal/task.yaml @@ -3,7 +3,7 @@ summary: ensure unapproved user cannot be used with system-usernames # List of expected snap install failures due to libseccomp/golang-seccomp being # too old. Since the illegal name check happens after verifying system support, # we can ignore these. -systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-10-*, -opensuse-15.2-*, -ubuntu-14.04-*] +systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-10-*, -ubuntu-14.04-*] execute: | snap_path=$("$TESTSTOOLS"/snaps-state pack-local test-snapd-illegal-system-username) diff --git a/tests/main/system-usernames-install-twice/task.yaml b/tests/main/system-usernames-install-twice/task.yaml index 4727a841cd7..061e1310cbf 100644 --- a/tests/main/system-usernames-install-twice/task.yaml +++ b/tests/main/system-usernames-install-twice/task.yaml @@ -4,7 +4,7 @@ summary: ensure snap can be installed twice (reusing the created groups) # too old. Since the illegal name check happens after verifying system support, # we can ignore these. Ignore ubuntu-core since groupdel doesn't support # --extrausers -systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-10-*, -opensuse-15.2-*, -ubuntu-14.04-*, -ubuntu-core-*] +systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-10-*, -ubuntu-14.04-*, -ubuntu-core-*] prepare: | snap install --edge test-snapd-daemon-user diff --git a/tests/main/system-usernames-missing-user/task.yaml b/tests/main/system-usernames-missing-user/task.yaml index 7af6903f5fa..8f35d568b7a 100644 --- a/tests/main/system-usernames-missing-user/task.yaml +++ b/tests/main/system-usernames-missing-user/task.yaml @@ -4,7 +4,7 @@ summary: ensure snap fails to install if one of user or group doesn't exist # too old. Since the illegal name check happens after verifying system support, # we can ignore these. Ignore ubuntu-core since groupdel doesn't support # --extrausers -systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-10-*, -opensuse-15.2-*, -ubuntu-14.04-*, -ubuntu-core-*] +systems: [-amazon-linux-2-*, -centos-7-*, -debian-9-*, -debian-10-*, -ubuntu-14.04-*, -ubuntu-core-*] prepare: | groupadd --system snap_daemon From d210f50c134f6639c8bb4953637509540e4810b2 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Fri, 24 Jun 2022 12:37:06 -0300 Subject: [PATCH 149/153] Adding packaging --- packaging/fedora-33 | 1 - packaging/opensuse-15.2 | 1 - packaging/opensuse-15.4 | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 120000 packaging/fedora-33 delete mode 120000 packaging/opensuse-15.2 create mode 120000 packaging/opensuse-15.4 diff --git a/packaging/fedora-33 b/packaging/fedora-33 deleted file mode 120000 index 100fe0cd7bb..00000000000 --- a/packaging/fedora-33 +++ /dev/null @@ -1 +0,0 @@ -fedora \ No newline at end of file diff --git a/packaging/opensuse-15.2 b/packaging/opensuse-15.2 deleted file mode 120000 index 2caee2bc4f5..00000000000 --- a/packaging/opensuse-15.2 +++ /dev/null @@ -1 +0,0 @@ -opensuse \ No newline at end of file diff --git a/packaging/opensuse-15.4 b/packaging/opensuse-15.4 new file mode 120000 index 00000000000..041e6ece8f7 --- /dev/null +++ b/packaging/opensuse-15.4 @@ -0,0 +1 @@ +opensuse/ \ No newline at end of file From 0f0e044827739b15e2077f08ceaf9f21105d7a7b Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Fri, 24 Jun 2022 17:42:09 -0300 Subject: [PATCH 150/153] Fixes for contacts and calendar services --- tests/main/interfaces-calendar-service/task.yaml | 1 + tests/main/interfaces-contacts-service/task.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/main/interfaces-calendar-service/task.yaml b/tests/main/interfaces-calendar-service/task.yaml index b5b67e8eb05..e0d747a58e6 100644 --- a/tests/main/interfaces-calendar-service/task.yaml +++ b/tests/main/interfaces-calendar-service/task.yaml @@ -25,6 +25,7 @@ systems: - -fedora-35-* # test-snapd-eds is incompatible with eds version shipped with the distro - -fedora-36-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-15.3-* # test-snapd-eds is incompatible with eds version shipped with the distro + - -opensuse-15.4-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-tumbleweed-* # test-snapd-eds is incompatible with eds version shipped with the distro - -ubuntu-14.04-* # no tests.session support, eds is too old - -ubuntu-2* # test-snapd-eds is incompatible with eds shipped with the distro diff --git a/tests/main/interfaces-contacts-service/task.yaml b/tests/main/interfaces-contacts-service/task.yaml index e144cc9e82c..23b242963da 100644 --- a/tests/main/interfaces-contacts-service/task.yaml +++ b/tests/main/interfaces-contacts-service/task.yaml @@ -20,6 +20,7 @@ systems: - -fedora-35-* # test-snapd-eds is incompatible with eds version shipped with the distro - -fedora-36-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-15.3-* # test-snapd-eds is incompatible with eds version shipped with the distro + - -opensuse-15.4-* # test-snapd-eds is incompatible with eds version shipped with the distro - -opensuse-tumbleweed-* # test-snapd-eds is incompatible with eds version shipped with the distro - -ubuntu-14.04-* # no tests.session support, eds is too old - -ubuntu-2* # test-snapd-eds is incompatible with eds shipped with the distro From 6700c040c39b08902d27cada6670ff5a40a53aec Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Mon, 27 Jun 2022 11:04:28 -0300 Subject: [PATCH 151/153] Properly restore for nested connections test The change is to teardown the fake store --- tests/nested/manual/connections/task.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/nested/manual/connections/task.yaml b/tests/nested/manual/connections/task.yaml index 2aab3dd64a2..9b299347ff2 100644 --- a/tests/nested/manual/connections/task.yaml +++ b/tests/nested/manual/connections/task.yaml @@ -46,5 +46,10 @@ prepare: | tests.nested build-image core tests.nested create-vm core +restore: | + #shellcheck source=tests/lib/store.sh + . "$TESTSLIB"/store.sh + teardown_fake_store "$NESTED_FAKESTORE_BLOB_DIR" + execute: | tests.nested exec "snap connections" | MATCH 'serial-port *connections:serial-1 *pc:serial-1' From 76adfdd41c3169fac8dd92958ba6a66034a887d4 Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Tue, 28 Jun 2022 10:14:46 -0300 Subject: [PATCH 152/153] fish on openSUSE 15.4 somehow magically glues the ENV_* from su --- tests/main/user-session-env/task.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/main/user-session-env/task.yaml b/tests/main/user-session-env/task.yaml index 25b85cac6ef..110b29d911d 100644 --- a/tests/main/user-session-env/task.yaml +++ b/tests/main/user-session-env/task.yaml @@ -79,6 +79,15 @@ execute: | NOMATCH 'XDG_DATA_DIRS=.*[:]?/var/lib/snapd/desktop[:]?.*' < "${user}-child-env" MATCH "PATH=.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-child-env" ;; + test-fish:opensuse-15.4*) + # fish on openSUSE 15.4 somehow magically glues the ENV_* from su and + # PATH, but fortunately it also displays ENV_ROOTPATH + NOMATCH "PATH=.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-profile-env" + MATCH "ENV_ROOTPATH\s+.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-profile-env" + MATCH 'XDG_DATA_DIRS=.*[:]?/var/lib/snapd/desktop[:]?.*' < "${user}-child-env" + NOMATCH "PATH=.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-child-env" + MATCH "ENV_ROOTPATH\s+.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-child-env" + ;; *) MATCH 'XDG_DATA_DIRS=.*[:]?/var/lib/snapd/desktop[:]?.*' < "${user}-profile-env" MATCH "PATH=.*[:]?${SNAP_MOUNT_DIR}/bin[:]?.*" < "${user}-profile-env" From f5a7a16a55b52d6197ef820b2f30fd9f4cf74c2a Mon Sep 17 00:00:00 2001 From: Sergio Cazzolato Date: Tue, 28 Jun 2022 10:32:42 -0300 Subject: [PATCH 153/153] Disable core20-to-core22 nested test This test is affected by this issue: https://bugs.launchpad.net/snapd/+bug/1979197 --- tests/nested/manual/core20-to-core22/task.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/nested/manual/core20-to-core22/task.yaml b/tests/nested/manual/core20-to-core22/task.yaml index 3d453cc0279..91d2adbab11 100644 --- a/tests/nested/manual/core20-to-core22/task.yaml +++ b/tests/nested/manual/core20-to-core22/task.yaml @@ -6,6 +6,9 @@ summary: verify a UC20 to UC22 remodel systems: [ubuntu-20.04-64] +# TODO: re-enable the test once lp:1979197 is fixed +manual: true + environment: NESTED_CUSTOM_MODEL: $TESTSLIB/assertions/valid-for-testing-pc-{VERSION}.model NESTED_IMAGE_ID: uc22-remodel-testing