mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
We need to resolve the boot chains another place based on the trusted assets we encountered to be installed. At this point it could be any chain. We will need to discover later what the correct chain is. Also make TrustedAssets return an unsorted data structure to make sure we do not use the order like the comments claimed.
2114 lines
69 KiB
Go
2114 lines
69 KiB
Go
// -*- 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package boot_test
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
. "gopkg.in/check.v1"
|
|
|
|
"github.com/snapcore/snapd/asserts"
|
|
"github.com/snapcore/snapd/boot"
|
|
"github.com/snapcore/snapd/boot/boottest"
|
|
"github.com/snapcore/snapd/bootloader"
|
|
"github.com/snapcore/snapd/bootloader/bootloadertest"
|
|
"github.com/snapcore/snapd/secboot"
|
|
"github.com/snapcore/snapd/seed"
|
|
"github.com/snapcore/snapd/snap"
|
|
"github.com/snapcore/snapd/testutil"
|
|
"github.com/snapcore/snapd/timings"
|
|
)
|
|
|
|
type baseSystemsSuite struct {
|
|
baseBootenvSuite
|
|
}
|
|
|
|
func (s *baseSystemsSuite) SetUpTest(c *C) {
|
|
s.baseBootenvSuite.SetUpTest(c)
|
|
c.Assert(os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755), IsNil)
|
|
c.Assert(os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755), IsNil)
|
|
}
|
|
|
|
type systemsSuite struct {
|
|
baseSystemsSuite
|
|
|
|
uc20dev snap.Device
|
|
|
|
runKernelBf bootloader.BootFile
|
|
recoveryKernelBf bootloader.BootFile
|
|
seedKernelSnap *seed.Snap
|
|
seedGadgetSnap *seed.Snap
|
|
}
|
|
|
|
var _ = Suite(&systemsSuite{})
|
|
|
|
func (s *systemsSuite) mockTrustedBootloaderWithAssetAndChains(c *C, runKernelBf, recoveryKernelBf bootloader.BootFile) *bootloadertest.MockTrustedAssetsBootloader {
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
|
|
mtbl.TrustedAssetsMap = map[string]string{"asset": "asset"}
|
|
mtbl.StaticCommandLine = "static cmdline"
|
|
mtbl.BootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRunMode),
|
|
runKernelBf,
|
|
}
|
|
mtbl.RecoveryBootChainList = []bootloader.BootFile{
|
|
bootloader.NewBootFile("", "asset", bootloader.RoleRecovery),
|
|
recoveryKernelBf,
|
|
}
|
|
bootloader.Force(mtbl)
|
|
return mtbl
|
|
}
|
|
|
|
func (s *systemsSuite) SetUpTest(c *C) {
|
|
s.baseBootenvSuite.SetUpTest(c)
|
|
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error { return nil })
|
|
s.AddCleanup(restore)
|
|
|
|
s.uc20dev = boottest.MockUC20Device("", nil)
|
|
|
|
// run kernel
|
|
s.runKernelBf = bootloader.NewBootFile("/var/lib/snapd/snap/pc-kernel_500.snap",
|
|
"kernel.efi", bootloader.RoleRunMode)
|
|
// seed (recovery) kernel
|
|
s.recoveryKernelBf = bootloader.NewBootFile("/var/lib/snapd/seed/snaps/pc-kernel_1.snap",
|
|
"kernel.efi", bootloader.RoleRecovery)
|
|
|
|
s.seedKernelSnap = mockKernelSeedSnap(snap.R(1))
|
|
s.seedGadgetSnap = mockGadgetSeedSnap(c, nil)
|
|
}
|
|
|
|
func (s *systemsSuite) TestSetTryRecoverySystemEncrypted(c *C) {
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentKernels: []string{"pc-kernel_500.snap"},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
var readSeedSeenLabels []string
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
// the mock bootloader can only mock a single recovery boot
|
|
// chain, so pretend both seeds use the same kernel, but keep track of the labels
|
|
readSeedSeenLabels = append(readSeedSeenLabels, label)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
// bootloader variables have already been modified
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 1)
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
switch resealCalls {
|
|
case 1:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=run static cmdline",
|
|
})
|
|
return nil
|
|
case 2:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return nil
|
|
default:
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
|
|
c.Assert(err, IsNil)
|
|
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "1234",
|
|
"recovery_system_status": "try",
|
|
})
|
|
// run and recovery keys
|
|
c.Check(resealCalls, Equals, 2)
|
|
c.Check(readSeedSeenLabels, DeepEquals, []string{
|
|
"20200825", "1234", // current recovery systems for run key
|
|
"20200825", // good recovery systems for recovery keys
|
|
})
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(modeenvRead.DeepEqual(&boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825", "1234"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentKernels: []string{"pc-kernel_500.snap"},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}), Equals, true)
|
|
}
|
|
|
|
func (s *systemsSuite) TestSetTryRecoverySystemRemodelEncrypted(c *C) {
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
newModel := boottest.MakeMockUC20Model(map[string]interface{}{
|
|
"model": "my-new-model",
|
|
})
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentKernels: []string{"pc-kernel_500.snap"},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
var readSeedSeenLabels []string
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
// the mock bootloader can only mock a single recovery boot
|
|
// chain, so pretend both seeds use the same kernel, but keep track of the labels
|
|
readSeedSeenLabels = append(readSeedSeenLabels, label)
|
|
systemModel := model
|
|
if label == "1234" {
|
|
systemModel = newModel
|
|
}
|
|
return systemModel, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
// bootloader variables have already been modified
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 1)
|
|
c.Assert(params, NotNil)
|
|
switch resealCalls {
|
|
case 1:
|
|
c.Assert(params.ModelParams, HasLen, 2)
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=run static cmdline",
|
|
})
|
|
c.Assert(params.ModelParams[1].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=recover snapd_recovery_system=1234 static cmdline",
|
|
"snapd_recovery_mode=run static cmdline",
|
|
})
|
|
return nil
|
|
case 2:
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return nil
|
|
default:
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
// a remodel will pass the new device
|
|
newUC20Device := boottest.MockUC20Device("run", newModel)
|
|
err := boot.SetTryRecoverySystem(newUC20Device, "1234")
|
|
c.Assert(err, IsNil)
|
|
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "1234",
|
|
"recovery_system_status": "try",
|
|
})
|
|
// run and recovery keys
|
|
c.Check(resealCalls, Equals, 2)
|
|
c.Check(readSeedSeenLabels, DeepEquals, []string{
|
|
"20200825", "1234", // current recovery systems for run key and current model from modeenv
|
|
"20200825", "1234", // current recovery systems for run key and try model from modeenv
|
|
"20200825", // good recovery systems for recovery keys
|
|
})
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825", "1234"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
CurrentKernels: []string{"pc-kernel_500.snap"},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
|
|
TryModel: newModel.Model(),
|
|
TryBrandID: newModel.BrandID(),
|
|
TryGrade: string(newModel.Grade()),
|
|
TryModelSignKeyID: newModel.SignKeyID(),
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestSetTryRecoverySystemSimple(c *C) {
|
|
mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
model := s.uc20dev.Model()
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
s.AddCleanup(restore)
|
|
|
|
err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
|
|
c.Assert(err, IsNil)
|
|
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "1234",
|
|
"recovery_system_status": "try",
|
|
})
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825", "1234"},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestSetTryRecoverySystemSetBootVarsErr(c *C) {
|
|
mtbl := bootloadertest.Mock("trusted", s.bootdir).WithTrustedAssets()
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
restore := boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
s.AddCleanup(restore)
|
|
|
|
mtbl.BootVars = map[string]string{
|
|
"try_recovery_system": "mock",
|
|
"recovery_system_status": "mock",
|
|
}
|
|
mtbl.SetErrFunc = func() error {
|
|
switch mtbl.SetBootVarsCalls {
|
|
case 1:
|
|
return fmt.Errorf("set boot vars fails")
|
|
case 2:
|
|
// called during cleanup
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
}
|
|
|
|
err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
|
|
c.Assert(err, ErrorMatches, "set boot vars fails")
|
|
|
|
// cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchanged
|
|
c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
|
|
}
|
|
|
|
func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorBeforeReseal(c *C) {
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
readSeedCalls := 0
|
|
cleanupTriggered := false
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
readSeedCalls++
|
|
// this is the reseal cleanup path
|
|
switch readSeedCalls {
|
|
case 1:
|
|
// called for the first system
|
|
c.Assert(label, Equals, "20200825")
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 1)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
case 2:
|
|
// called for the 'try' system
|
|
c.Assert(label, Equals, "1234")
|
|
// modeenv is updated first
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
|
|
"20200825", "1234",
|
|
})
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 1)
|
|
// we are triggering the cleanup by returning an error now
|
|
cleanupTriggered = true
|
|
return nil, nil, fmt.Errorf("seed read essential fails")
|
|
case 3:
|
|
// (cleanup) recovery boot chains for run key, called
|
|
// for the first system only
|
|
fallthrough
|
|
case 4:
|
|
// (cleanup) recovery boot chains for recovery keys
|
|
c.Assert(label, Equals, "20200825")
|
|
// boot variables already updated
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 2)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
default:
|
|
return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
if cleanupTriggered {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
defer restore()
|
|
|
|
err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
|
|
c.Assert(err, ErrorMatches, ".*: seed read essential fails")
|
|
|
|
// failed after the call to read the 'try' system seed
|
|
c.Check(readSeedCalls, Equals, 4)
|
|
// called twice during cleanup for run and recovery keys
|
|
c.Check(resealCalls, Equals, 2)
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchanged
|
|
c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestSetTryRecoverySystemCleanupOnErrorAfterReseal(c *C) {
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
readSeedCalls := 0
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
readSeedCalls++
|
|
// this is the reseal cleanup path
|
|
|
|
switch readSeedCalls {
|
|
case 1:
|
|
// called for the first system
|
|
c.Assert(label, Equals, "20200825")
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 1)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
case 2:
|
|
// called for the 'try' system
|
|
c.Assert(label, Equals, "1234")
|
|
// modeenv is updated first
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
|
|
"20200825", "1234",
|
|
})
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 1)
|
|
// still good
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
case 3:
|
|
// recovery boot chains for a good recovery system
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 1)
|
|
fallthrough
|
|
case 4:
|
|
// (cleanup) recovery boot chains for run key, called
|
|
// for the first system only
|
|
fallthrough
|
|
case 5:
|
|
// (cleanup) recovery boot chains for recovery keys
|
|
c.Assert(label, Equals, "20200825")
|
|
// boot variables already updated
|
|
if readSeedCalls >= 4 {
|
|
c.Check(mtbl.SetBootVarsCalls, Equals, 2)
|
|
}
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
default:
|
|
return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
switch resealCalls {
|
|
case 1:
|
|
// attempt to reseal the run key
|
|
return fmt.Errorf("reseal fails")
|
|
case 2, 3:
|
|
// reseal of run and recovery keys
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("unexpected call")
|
|
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
|
|
c.Assert(err, ErrorMatches, "cannot reseal the encryption key: reseal fails")
|
|
|
|
// failed after the call to read the 'try' system seed
|
|
c.Check(readSeedCalls, Equals, 5)
|
|
// called 3 times, once when mocked failure occurs, twice during cleanup
|
|
// for run and recovery keys
|
|
c.Check(resealCalls, Equals, 3)
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchanged
|
|
c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestSetTryRecoverySystemCleanupError(c *C) {
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
readSeedCalls := 0
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
readSeedCalls++
|
|
// this is the reseal cleanup path
|
|
c.Logf("call %v label %v", readSeedCalls, label)
|
|
switch readSeedCalls {
|
|
case 1:
|
|
// called for the first system
|
|
c.Assert(label, Equals, "20200825")
|
|
return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
case 2:
|
|
// called for the 'try' system
|
|
c.Assert(label, Equals, "1234")
|
|
// still good
|
|
return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
case 3:
|
|
// recovery boot chains for a good recovery system
|
|
fallthrough
|
|
case 4:
|
|
// (cleanup) recovery boot chains for run key, called
|
|
// for the first system only
|
|
fallthrough
|
|
case 5:
|
|
// (cleanup) recovery boot chains for recovery keys
|
|
c.Check(label, Equals, "20200825")
|
|
return s.uc20dev.Model(), []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
default:
|
|
return nil, nil, fmt.Errorf("unexpected call %v", readSeedCalls)
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
switch resealCalls {
|
|
case 1:
|
|
return fmt.Errorf("reseal fails")
|
|
case 2, 3:
|
|
// reseal of run and recovery keys
|
|
return fmt.Errorf("reseal in cleanup fails too")
|
|
default:
|
|
return fmt.Errorf("unexpected call")
|
|
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
err := boot.SetTryRecoverySystem(s.uc20dev, "1234")
|
|
c.Assert(err, ErrorMatches, `cannot reseal the encryption key: reseal fails \(cleanup failed: cannot reseal the encryption key: reseal in cleanup fails too\)`)
|
|
|
|
// failed after the call to read the 'try' system seed
|
|
c.Check(readSeedCalls, Equals, 5)
|
|
// called twice, once when enabling the try system, once on cleanup
|
|
c.Check(resealCalls, Equals, 2)
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv is unchanged
|
|
c.Check(modeenvRead.DeepEqual(modeenv), Equals, true)
|
|
// bootloader variables have been cleared regardless of reseal failing
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) testInspectRecoverySystemOutcomeHappy(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, expectedOutcome boot.TryRecoverySystemOutcome, expectedErr string) {
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
return nil, nil, fmt.Errorf("unexpected call")
|
|
})
|
|
defer restore()
|
|
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
return fmt.Errorf("unexpected call")
|
|
})
|
|
defer restore()
|
|
|
|
outcome, label, err := boot.InspectTryRecoverySystemOutcome(s.uc20dev)
|
|
if expectedErr == "" {
|
|
c.Assert(err, IsNil)
|
|
} else {
|
|
c.Assert(err, ErrorMatches, expectedErr)
|
|
}
|
|
c.Check(outcome, Equals, expectedOutcome)
|
|
switch outcome {
|
|
case boot.TryRecoverySystemOutcomeSuccess, boot.TryRecoverySystemOutcomeFailure:
|
|
c.Check(label, Equals, "1234")
|
|
default:
|
|
c.Check(label, Equals, "")
|
|
}
|
|
}
|
|
|
|
func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappySuccess(c *C) {
|
|
triedVars := map[string]string{
|
|
"recovery_system_status": "tried",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(triedVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
m := boot.Modeenv{
|
|
Mode: boot.ModeRun,
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"29112019", "1234"},
|
|
}
|
|
err = m.WriteTo("")
|
|
c.Assert(err, IsNil)
|
|
|
|
s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeSuccess, "")
|
|
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, triedVars)
|
|
}
|
|
|
|
func (s *systemsSuite) TestInspectRecoverySystemOutcomeFailureMissingSystemInModeenv(c *C) {
|
|
triedVars := map[string]string{
|
|
"recovery_system_status": "tried",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(triedVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
m := boot.Modeenv{
|
|
Mode: boot.ModeRun,
|
|
// we don't have the tried recovery system in the modeenv
|
|
CurrentRecoverySystems: []string{"29112019"},
|
|
}
|
|
err = m.WriteTo("")
|
|
c.Assert(err, IsNil)
|
|
|
|
s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, `recovery system "1234" was tried, but is not present in the modeenv CurrentRecoverySystems`)
|
|
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, triedVars)
|
|
}
|
|
|
|
func (s *systemsSuite) TestInspectRecoverySystemOutcomeHappyFailure(c *C) {
|
|
tryVars := map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(tryVars)
|
|
c.Assert(err, IsNil)
|
|
s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeFailure, "")
|
|
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, tryVars)
|
|
}
|
|
|
|
func (s *systemsSuite) TestInspectRecoverySystemOutcomeNotTried(c *C) {
|
|
notTriedVars := map[string]string{
|
|
"recovery_system_status": "",
|
|
"try_recovery_system": "",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(notTriedVars)
|
|
c.Assert(err, IsNil)
|
|
s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeNoneTried, "")
|
|
}
|
|
|
|
func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBogusStatus(c *C) {
|
|
badVars := map[string]string{
|
|
"recovery_system_status": "foo",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(badVars)
|
|
c.Assert(err, IsNil)
|
|
s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status "foo"`)
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, badVars)
|
|
}
|
|
|
|
func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentBadLabel(c *C) {
|
|
badVars := map[string]string{
|
|
"recovery_system_status": "tried",
|
|
"try_recovery_system": "",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(badVars)
|
|
c.Assert(err, IsNil)
|
|
s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `try recovery system is unset but status is "tried"`)
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, badVars)
|
|
}
|
|
|
|
func (s *systemsSuite) TestInspectRecoverySystemOutcomeInconsistentUnexpectedLabel(c *C) {
|
|
badVars := map[string]string{
|
|
"recovery_system_status": "",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(badVars)
|
|
c.Assert(err, IsNil)
|
|
s.testInspectRecoverySystemOutcomeHappy(c, mtbl, boot.TryRecoverySystemOutcomeInconsistent, `unexpected recovery system status ""`)
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, badVars)
|
|
}
|
|
|
|
type clearRecoverySystemTestCase struct {
|
|
systemLabel string
|
|
tryModel *asserts.Model
|
|
resealErr error
|
|
expectedErr string
|
|
}
|
|
|
|
func (s *systemsSuite) testClearRecoverySystem(c *C, mtbl *bootloadertest.MockTrustedAssetsBootloader, tc clearRecoverySystemTestCase) {
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentKernels: []string{},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
if tc.systemLabel != "" {
|
|
modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, tc.systemLabel)
|
|
}
|
|
if tc.tryModel != nil {
|
|
modeenv.TryModel = tc.tryModel.Model()
|
|
modeenv.TryBrandID = tc.tryModel.BrandID()
|
|
modeenv.TryGrade = string(tc.tryModel.Grade())
|
|
modeenv.TryModelSignKeyID = tc.tryModel.SignKeyID()
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
var readSeedSeenLabels []string
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
// the mock bootloader can only mock a single recovery boot
|
|
// chain, so pretend both seeds use the same kernel, but keep track of the labels
|
|
readSeedSeenLabels = append(readSeedSeenLabels, label)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
switch resealCalls {
|
|
case 1:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
return tc.resealErr
|
|
case 2:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return nil
|
|
default:
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
err := boot.ClearTryRecoverySystem(s.uc20dev, tc.systemLabel)
|
|
if tc.expectedErr == "" {
|
|
c.Assert(err, IsNil)
|
|
} else {
|
|
c.Assert(err, ErrorMatches, tc.expectedErr)
|
|
}
|
|
|
|
// only one seed system accessed
|
|
c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
|
|
if tc.resealErr == nil {
|
|
// called twice, for run and recovery keys
|
|
c.Check(resealCalls, Equals, 2)
|
|
} else {
|
|
// fails on run key
|
|
c.Check(resealCalls, Equals, 1)
|
|
}
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv systems list has one entry only
|
|
c.Check(modeenvRead, testutil.JsonEquals, boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
|
|
// try model if set, has been cleared
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemHappy(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"})
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemTriedHappy(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "tried",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"})
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemInconsistentStateHappy(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "foo",
|
|
"try_recovery_system": "",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: "1234"})
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemInconsistentNoLabelHappy(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "this-will-be-gone",
|
|
"try_recovery_system": "this-too",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
// clear without passing the system label, just clears the relevant boot
|
|
// variables
|
|
const noLabel = ""
|
|
s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{systemLabel: noLabel})
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemRemodelHappy(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{
|
|
systemLabel: "1234",
|
|
tryModel: boottest.MakeMockUC20Model(map[string]interface{}{
|
|
"tryModelodel": "my-new-model",
|
|
}),
|
|
})
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemResealFails(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{
|
|
systemLabel: "1234",
|
|
resealErr: fmt.Errorf("reseal fails"),
|
|
expectedErr: "cannot reseal the encryption key: reseal fails",
|
|
})
|
|
// bootloader variables have been cleared
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
// variables were cleared
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemSetBootVarsFails(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
mtbl.SetErr = fmt.Errorf("set boot vars fails")
|
|
|
|
s.testClearRecoverySystem(c, mtbl, clearRecoverySystemTestCase{
|
|
systemLabel: "1234",
|
|
expectedErr: "set boot vars fails",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestClearRecoverySystemReboot(c *C) {
|
|
setVars := map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
}
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
err := mtbl.SetBootVars(setVars)
|
|
c.Assert(err, IsNil)
|
|
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825", "1234"},
|
|
CurrentKernels: []string{},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
var readSeedSeenLabels []string
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
// the mock bootloader can only mock a single recovery boot
|
|
// chain, so pretend both seeds use the same kernel, but keep track of the labels
|
|
readSeedSeenLabels = append(readSeedSeenLabels, label)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
switch resealCalls {
|
|
case 1:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
panic("reseal panic")
|
|
case 2:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
return nil
|
|
case 3:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return nil
|
|
default:
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
checkGoodState := func() {
|
|
// modeenv was already written
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// modeenv systems list has one entry only
|
|
c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, []string{
|
|
"20200825",
|
|
})
|
|
// bootloader variables have been cleared already
|
|
vars, err := mtbl.GetBootVars("try_recovery_system", "recovery_system_status")
|
|
c.Assert(err, IsNil)
|
|
// variables were cleared
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"try_recovery_system": "",
|
|
"recovery_system_status": "",
|
|
})
|
|
}
|
|
|
|
c.Assert(func() {
|
|
boot.ClearTryRecoverySystem(s.uc20dev, "1234")
|
|
}, PanicMatches, "reseal panic")
|
|
// only one seed system accessed
|
|
c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
|
|
// panicked on run key
|
|
c.Check(resealCalls, Equals, 1)
|
|
checkGoodState()
|
|
|
|
mtbl.SetErrFunc = func() error {
|
|
panic("set boot vars panic")
|
|
}
|
|
c.Assert(func() {
|
|
boot.ClearTryRecoverySystem(s.uc20dev, "1234")
|
|
}, PanicMatches, "set boot vars panic")
|
|
// we did not reach resealing yet
|
|
c.Check(resealCalls, Equals, 1)
|
|
checkGoodState()
|
|
|
|
mtbl.SetErrFunc = nil
|
|
err = boot.ClearTryRecoverySystem(s.uc20dev, "1234")
|
|
c.Assert(err, IsNil)
|
|
checkGoodState()
|
|
}
|
|
|
|
type recoverySystemGoodTestCase struct {
|
|
systemLabelAddToCurrent bool
|
|
systemLabelAddToGood bool
|
|
triedSystems []string
|
|
|
|
resealRecoveryKeyErr error
|
|
resealRecoveryKeyDuringCleanupErr error
|
|
resealCalls int
|
|
expectedErr string
|
|
|
|
readSeedSystems []string
|
|
expectedCurrentSystemsList []string
|
|
expectedGoodSystemsList []string
|
|
}
|
|
|
|
func (s *systemsSuite) testPromoteTriedRecoverySystem(c *C, systemLabel string, tc recoverySystemGoodTestCase) {
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentKernels: []string{},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
if tc.systemLabelAddToCurrent {
|
|
modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel)
|
|
}
|
|
if tc.systemLabelAddToGood {
|
|
modeenv.GoodRecoverySystems = append(modeenv.GoodRecoverySystems, systemLabel)
|
|
}
|
|
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
var readSeedSeenLabels []string
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
// the mock bootloader can only mock a single recovery boot
|
|
// chain, so pretend both seeds use the same kernel, but keep track of the labels
|
|
readSeedSeenLabels = append(readSeedSeenLabels, label)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
switch resealCalls {
|
|
case 1:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
fmt.Sprintf("snapd_recovery_mode=factory-reset snapd_recovery_system=%s static cmdline", systemLabel),
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
fmt.Sprintf("snapd_recovery_mode=recover snapd_recovery_system=%s static cmdline", systemLabel),
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return nil
|
|
case 2:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
fmt.Sprintf("snapd_recovery_mode=factory-reset snapd_recovery_system=%s static cmdline", systemLabel),
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
fmt.Sprintf("snapd_recovery_mode=recover snapd_recovery_system=%s static cmdline", systemLabel),
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return tc.resealRecoveryKeyErr
|
|
case 3:
|
|
// run key boot chain is unchanged, so only recovery key boot chain is resealed
|
|
if tc.resealRecoveryKeyErr == nil {
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return nil
|
|
case 4:
|
|
if tc.resealRecoveryKeyErr == nil {
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return tc.resealRecoveryKeyDuringCleanupErr
|
|
default:
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
err := boot.PromoteTriedRecoverySystem(s.uc20dev, systemLabel, tc.triedSystems)
|
|
if tc.expectedErr == "" {
|
|
c.Assert(err, IsNil)
|
|
} else {
|
|
c.Assert(err, ErrorMatches, tc.expectedErr)
|
|
}
|
|
c.Check(readSeedSeenLabels, DeepEquals, tc.readSeedSystems)
|
|
c.Check(resealCalls, Equals, tc.resealCalls)
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
c.Check(modeenvRead.GoodRecoverySystems, DeepEquals, tc.expectedGoodSystemsList)
|
|
c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, tc.expectedCurrentSystemsList)
|
|
}
|
|
|
|
func (s *systemsSuite) TestPromoteTriedRecoverySystemHappy(c *C) {
|
|
s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
|
|
triedSystems: []string{"1234"},
|
|
|
|
resealCalls: 2,
|
|
|
|
readSeedSystems: []string{
|
|
// run key
|
|
"20200825", "1234",
|
|
// recovery keys
|
|
"20200825", "1234",
|
|
},
|
|
|
|
expectedCurrentSystemsList: []string{"20200825", "1234"},
|
|
expectedGoodSystemsList: []string{"20200825", "1234"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestPromoteTriedRecoverySystemInCurrent(c *C) {
|
|
s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
|
|
triedSystems: []string{"1234"},
|
|
systemLabelAddToCurrent: true,
|
|
resealCalls: 2,
|
|
|
|
readSeedSystems: []string{
|
|
// run key
|
|
"20200825", "1234",
|
|
// recovery keys
|
|
"20200825", "1234",
|
|
},
|
|
expectedCurrentSystemsList: []string{"20200825", "1234"},
|
|
expectedGoodSystemsList: []string{"20200825", "1234"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestPromoteTriedRecoverySystemPresentEverywhere(c *C) {
|
|
s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
|
|
triedSystems: []string{"1234"},
|
|
systemLabelAddToCurrent: true,
|
|
systemLabelAddToGood: true,
|
|
|
|
resealCalls: 2,
|
|
|
|
readSeedSystems: []string{
|
|
// run key
|
|
"20200825", "1234",
|
|
// recovery keys
|
|
"20200825", "1234",
|
|
},
|
|
expectedCurrentSystemsList: []string{"20200825", "1234"},
|
|
expectedGoodSystemsList: []string{"20200825", "1234"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestPromoteTriedRecoverySystemResealFails(c *C) {
|
|
s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
|
|
triedSystems: []string{"1234"},
|
|
resealRecoveryKeyErr: fmt.Errorf("recovery key reseal mock failure"),
|
|
// no failure during cleanup
|
|
resealRecoveryKeyDuringCleanupErr: nil,
|
|
|
|
resealCalls: 4,
|
|
|
|
expectedErr: `cannot reseal the fallback encryption keys: recovery key reseal mock failure`,
|
|
|
|
readSeedSystems: []string{
|
|
// run key
|
|
"20200825", "1234",
|
|
// recovery keys
|
|
"20200825", "1234",
|
|
// cleanup run key reseal (the seed system is still in
|
|
// current-recovery-systems)
|
|
"20200825",
|
|
// cleanup recovery keys
|
|
"20200825",
|
|
},
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestPromoteTriedRecoverySystemResealUndoFails(c *C) {
|
|
s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
|
|
triedSystems: []string{"1234"},
|
|
resealRecoveryKeyErr: fmt.Errorf("recovery key reseal mock failure"),
|
|
resealRecoveryKeyDuringCleanupErr: fmt.Errorf("recovery key reseal mock fail in cleanup"),
|
|
|
|
resealCalls: 4,
|
|
|
|
expectedErr: `cannot reseal the fallback encryption keys: recovery key reseal mock failure \(cleanup failed: cannot reseal the fallback encryption keys: recovery key reseal mock fail in cleanup\)`,
|
|
|
|
readSeedSystems: []string{
|
|
// run key
|
|
"20200825", "1234",
|
|
// recovery keys
|
|
"20200825", "1234",
|
|
// cleanup run key reseal (the seed system is still in
|
|
// current-recovery-systems)
|
|
// cleanup run key
|
|
"20200825",
|
|
// cleanup recovery keys
|
|
"20200825",
|
|
},
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestPromoteTriedRecoverySystemNotTried(c *C) {
|
|
s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
|
|
triedSystems: []string{"not-here"},
|
|
|
|
expectedErr: `system has not been successfully tried`,
|
|
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
})
|
|
|
|
// also works if tried systems list is nil
|
|
s.testPromoteTriedRecoverySystem(c, "1234", recoverySystemGoodTestCase{
|
|
triedSystems: nil,
|
|
|
|
expectedErr: `system has not been successfully tried`,
|
|
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
})
|
|
}
|
|
|
|
type recoverySystemDropTestCase struct {
|
|
systemLabelAddToCurrent bool
|
|
systemLabelAddToGood bool
|
|
|
|
resealRecoveryKeyErr error
|
|
resealCalls int
|
|
expectedErr string
|
|
|
|
expectedCurrentSystemsList []string
|
|
expectedGoodSystemsList []string
|
|
}
|
|
|
|
func (s *systemsSuite) testDropRecoverySystem(c *C, systemLabel string, tc recoverySystemDropTestCase) {
|
|
mtbl := s.mockTrustedBootloaderWithAssetAndChains(c, s.runKernelBf, s.recoveryKernelBf)
|
|
mockAssetsCache(c, s.rootdir, "trusted", []string{
|
|
"asset-asset-hash-1",
|
|
})
|
|
|
|
bootloader.Force(mtbl)
|
|
defer bootloader.Force(nil)
|
|
|
|
// system is encrypted
|
|
s.stampSealedKeys(c, s.rootdir)
|
|
|
|
model := s.uc20dev.Model()
|
|
|
|
modeenv := &boot.Modeenv{
|
|
Mode: "run",
|
|
// keep this comment to make old gofmt happy
|
|
CurrentRecoverySystems: []string{"20200825"},
|
|
GoodRecoverySystems: []string{"20200825"},
|
|
CurrentKernels: []string{},
|
|
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
CurrentTrustedBootAssets: boot.BootAssetsMap{
|
|
"asset": []string{"asset-hash-1"},
|
|
},
|
|
|
|
Model: model.Model(),
|
|
BrandID: model.BrandID(),
|
|
Grade: string(model.Grade()),
|
|
ModelSignKeyID: model.SignKeyID(),
|
|
}
|
|
if tc.systemLabelAddToCurrent {
|
|
modeenv.CurrentRecoverySystems = append(modeenv.CurrentRecoverySystems, systemLabel)
|
|
}
|
|
if tc.systemLabelAddToGood {
|
|
modeenv.GoodRecoverySystems = append(modeenv.GoodRecoverySystems, systemLabel)
|
|
}
|
|
|
|
c.Assert(modeenv.WriteTo(""), IsNil)
|
|
|
|
var readSeedSeenLabels []string
|
|
restore := boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
|
|
// the mock bootloader can only mock a single recovery boot
|
|
// chain, so pretend both seeds use the same kernel, but keep track of the labels
|
|
readSeedSeenLabels = append(readSeedSeenLabels, label)
|
|
return model, []*seed.Snap{s.seedKernelSnap, s.seedGadgetSnap}, nil
|
|
})
|
|
defer restore()
|
|
|
|
resealCalls := 0
|
|
restore = boot.MockSecbootResealKeys(func(params *secboot.ResealKeysParams) error {
|
|
resealCalls++
|
|
c.Assert(params, NotNil)
|
|
c.Assert(params.ModelParams, HasLen, 1)
|
|
switch resealCalls {
|
|
case 1:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsBootEncryptionKeyDir, "ubuntu-data.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return nil
|
|
case 2:
|
|
c.Check(params.KeyFiles, DeepEquals, []string{
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-data.recovery.sealed-key"),
|
|
filepath.Join(boot.InitramfsSeedEncryptionKeyDir, "ubuntu-save.recovery.sealed-key"),
|
|
})
|
|
c.Assert(params.ModelParams[0].KernelCmdlines, DeepEquals, []string{
|
|
"snapd_recovery_mode=factory-reset snapd_recovery_system=20200825 static cmdline",
|
|
"snapd_recovery_mode=recover snapd_recovery_system=20200825 static cmdline",
|
|
})
|
|
return tc.resealRecoveryKeyErr
|
|
default:
|
|
c.Errorf("unexpected call to secboot.ResealKeys with count %v", resealCalls)
|
|
return fmt.Errorf("unexpected call")
|
|
}
|
|
})
|
|
defer restore()
|
|
|
|
err := boot.DropRecoverySystem(s.uc20dev, systemLabel)
|
|
if tc.expectedErr == "" {
|
|
c.Assert(err, IsNil)
|
|
} else {
|
|
c.Assert(err, ErrorMatches, tc.expectedErr)
|
|
}
|
|
c.Check(readSeedSeenLabels, DeepEquals, []string{"20200825", "20200825"})
|
|
c.Check(resealCalls, Equals, tc.resealCalls)
|
|
|
|
modeenvRead, err := boot.ReadModeenv("")
|
|
c.Assert(err, IsNil)
|
|
// current is unchanged
|
|
c.Check(modeenvRead.GoodRecoverySystems, DeepEquals, tc.expectedCurrentSystemsList)
|
|
c.Check(modeenvRead.CurrentRecoverySystems, DeepEquals, tc.expectedGoodSystemsList)
|
|
}
|
|
|
|
func (s *systemsSuite) TestDropRecoverySystemHappy(c *C) {
|
|
s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
|
|
systemLabelAddToCurrent: true,
|
|
systemLabelAddToGood: true,
|
|
resealCalls: 2,
|
|
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestDropRecoverySystemAlreadyGoneFromBoth(c *C) {
|
|
s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
|
|
systemLabelAddToCurrent: false,
|
|
systemLabelAddToGood: false,
|
|
resealCalls: 2,
|
|
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestDropRecoverySystemAlreadyGoneOne(c *C) {
|
|
s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
|
|
systemLabelAddToCurrent: true,
|
|
systemLabelAddToGood: false,
|
|
resealCalls: 2,
|
|
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestDropRecoverySystemResealErr(c *C) {
|
|
s.testDropRecoverySystem(c, "1234", recoverySystemDropTestCase{
|
|
systemLabelAddToCurrent: true,
|
|
systemLabelAddToGood: false,
|
|
resealCalls: 2,
|
|
resealRecoveryKeyErr: fmt.Errorf("mocked error"),
|
|
expectedErr: `cannot reseal the fallback encryption keys: mocked error`,
|
|
|
|
expectedGoodSystemsList: []string{"20200825"},
|
|
expectedCurrentSystemsList: []string{"20200825"},
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestMarkRecoveryCapableSystemHappy(c *C) {
|
|
rbl := bootloadertest.Mock("recovery", c.MkDir()).RecoveryAware()
|
|
bootloader.Force(rbl)
|
|
|
|
err := boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err := rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1234",
|
|
})
|
|
// try the same system again
|
|
err = boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
// still a single entry
|
|
"snapd_good_recovery_systems": "1234",
|
|
})
|
|
|
|
// try something new
|
|
err = boot.MarkRecoveryCapableSystem("4567")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
// entry added
|
|
"snapd_good_recovery_systems": "1234,4567",
|
|
})
|
|
|
|
// try adding the old one again
|
|
err = boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
// system got moved to the end of the list
|
|
"snapd_good_recovery_systems": "4567,1234",
|
|
})
|
|
|
|
// and the new one again
|
|
err = boot.MarkRecoveryCapableSystem("4567")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
// and it became the last entry
|
|
"snapd_good_recovery_systems": "1234,4567",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestMarkRecoveryCapableSystemAlwaysLast(c *C) {
|
|
rbl := bootloadertest.Mock("recovery", c.MkDir()).RecoveryAware()
|
|
bootloader.Force(rbl)
|
|
|
|
err := rbl.SetBootVars(map[string]string{
|
|
"snapd_good_recovery_systems": "1234,2222",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err := rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "2222,1234",
|
|
})
|
|
|
|
err = rbl.SetBootVars(map[string]string{
|
|
"snapd_good_recovery_systems": "1111,1234,2222",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
err = boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1111,2222,1234",
|
|
})
|
|
|
|
err = rbl.SetBootVars(map[string]string{
|
|
"snapd_good_recovery_systems": "1111,2222",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
err = boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1111,2222,1234",
|
|
})
|
|
}
|
|
|
|
func (s *systemsSuite) TestMarkRecoveryCapableSystemErr(c *C) {
|
|
rbl := bootloadertest.Mock("recovery", c.MkDir()).RecoveryAware()
|
|
bootloader.Force(rbl)
|
|
|
|
err := boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err := rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1234",
|
|
})
|
|
|
|
rbl.SetErr = fmt.Errorf("mocked error")
|
|
err = boot.MarkRecoveryCapableSystem("4567")
|
|
c.Assert(err, ErrorMatches, "mocked error")
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
// mocked error is returned after variable is set
|
|
"snapd_good_recovery_systems": "1234,4567",
|
|
})
|
|
|
|
// but mocked panic happens earlier
|
|
rbl.SetMockToPanic("SetBootVars")
|
|
c.Assert(func() { boot.MarkRecoveryCapableSystem("9999") },
|
|
PanicMatches, "mocked reboot panic in SetBootVars")
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1234,4567",
|
|
})
|
|
|
|
}
|
|
|
|
func (s *systemsSuite) TestMarkRecoveryCapableSystemNonRecoveryAware(c *C) {
|
|
bl := bootloadertest.Mock("recovery", c.MkDir())
|
|
bootloader.Force(bl)
|
|
|
|
err := boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
c.Check(bl.SetBootVarsCalls, Equals, 0)
|
|
}
|
|
|
|
type initramfsMarkTryRecoverySystemSuite struct {
|
|
baseSystemsSuite
|
|
|
|
bl *bootloadertest.MockBootloader
|
|
}
|
|
|
|
var _ = Suite(&initramfsMarkTryRecoverySystemSuite{})
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) SetUpTest(c *C) {
|
|
s.baseSystemsSuite.SetUpTest(c)
|
|
|
|
s.bl = bootloadertest.Mock("bootloader", s.bootdir)
|
|
bootloader.Force(s.bl)
|
|
s.AddCleanup(func() { bootloader.Force(nil) })
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) testMarkRecoverySystemForRun(c *C, outcome boot.TryRecoverySystemOutcome, expectingStatus string) {
|
|
err := s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome)
|
|
c.Assert(err, IsNil)
|
|
|
|
expectedVars := map[string]string{
|
|
"snapd_recovery_mode": "run",
|
|
"snapd_recovery_system": "",
|
|
|
|
"recovery_system_status": expectingStatus,
|
|
"try_recovery_system": "1234",
|
|
}
|
|
|
|
vars, err := s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system",
|
|
"recovery_system_status", "try_recovery_system")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, expectedVars)
|
|
|
|
err = s.bl.SetBootVars(map[string]string{
|
|
// the status is overwritten, even if it's completely bogus
|
|
"recovery_system_status": "foobar",
|
|
"try_recovery_system": "1234",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
|
|
err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(outcome)
|
|
c.Assert(err, IsNil)
|
|
|
|
vars, err = s.bl.GetBootVars("snapd_recovery_mode", "snapd_recovery_system",
|
|
"recovery_system_status", "try_recovery_system")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, expectedVars)
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestMarkTryRecoverySystemSuccess(c *C) {
|
|
s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeSuccess, "tried")
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemFailure(c *C) {
|
|
s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeFailure, "try")
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemBogus(c *C) {
|
|
s.testMarkRecoverySystemForRun(c, boot.TryRecoverySystemOutcomeInconsistent, "")
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestMarkRecoverySystemErr(c *C) {
|
|
s.bl.SetErr = fmt.Errorf("set fails")
|
|
err := boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeSuccess)
|
|
c.Assert(err, ErrorMatches, "set fails")
|
|
err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeFailure)
|
|
c.Assert(err, ErrorMatches, "set fails")
|
|
err = boot.EnsureNextBootToRunModeWithTryRecoverySystemOutcome(boot.TryRecoverySystemOutcomeInconsistent)
|
|
c.Assert(err, ErrorMatches, "set fails")
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemUnset(c *C) {
|
|
err := s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "try",
|
|
// system is unset
|
|
"try_recovery_system": "",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, ErrorMatches, `try recovery system is unset but status is "try"`)
|
|
c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
|
|
c.Check(isTry, Equals, false)
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemBogus(c *C) {
|
|
err := s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "foobar",
|
|
"try_recovery_system": "1234",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, ErrorMatches, `unexpected recovery system status "foobar"`)
|
|
c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
|
|
c.Check(isTry, Equals, false)
|
|
|
|
// errors out even if try recovery system label is unset
|
|
err = s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "no-label",
|
|
"try_recovery_system": "",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, ErrorMatches, `unexpected recovery system status "no-label"`)
|
|
c.Check(boot.IsInconsistentRecoverySystemState(err), Equals, true)
|
|
c.Check(isTry, Equals, false)
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemNoTryingStatus(c *C) {
|
|
err := s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "",
|
|
"try_recovery_system": "",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, IsNil)
|
|
c.Check(isTry, Equals, false)
|
|
|
|
err = s.bl.SetBootVars(map[string]string{
|
|
// status is checked first
|
|
"recovery_system_status": "",
|
|
"try_recovery_system": "1234",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, IsNil)
|
|
c.Check(isTry, Equals, false)
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestTryingRecoverySystemSameSystem(c *C) {
|
|
// the usual scenario
|
|
err := s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "1234",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, IsNil)
|
|
c.Check(isTry, Equals, true)
|
|
|
|
// pretend the system has already been tried
|
|
err = s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "tried",
|
|
"try_recovery_system": "1234",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, IsNil)
|
|
c.Check(isTry, Equals, true)
|
|
}
|
|
|
|
func (s *initramfsMarkTryRecoverySystemSuite) TestRecoverySystemSuccessDifferent(c *C) {
|
|
// other system
|
|
err := s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "try",
|
|
"try_recovery_system": "9999",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err := boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, IsNil)
|
|
c.Check(isTry, Equals, false)
|
|
|
|
// same when the other system has already been tried
|
|
err = s.bl.SetBootVars(map[string]string{
|
|
"recovery_system_status": "tried",
|
|
"try_recovery_system": "9999",
|
|
})
|
|
c.Assert(err, IsNil)
|
|
isTry, err = boot.InitramfsIsTryingRecoverySystem("1234")
|
|
c.Assert(err, IsNil)
|
|
c.Check(isTry, Equals, false)
|
|
}
|
|
|
|
func (s *systemsSuite) TestUnmarkRecoveryCapableSystemHappy(c *C) {
|
|
rbl := bootloadertest.Mock("recovery", c.MkDir()).RecoveryAware()
|
|
bootloader.Force(rbl)
|
|
|
|
// mark system
|
|
err := boot.MarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err := rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1234",
|
|
})
|
|
|
|
// mark system
|
|
err = boot.MarkRecoveryCapableSystem("4567")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1234,4567",
|
|
})
|
|
|
|
// unmark system that is not present, function is idempotent
|
|
err = boot.UnmarkRecoveryCapableSystem("not-here")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "1234,4567",
|
|
})
|
|
|
|
// unmark system
|
|
err = boot.UnmarkRecoveryCapableSystem("1234")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "4567",
|
|
})
|
|
|
|
// unmark system
|
|
err = boot.UnmarkRecoveryCapableSystem("4567")
|
|
c.Assert(err, IsNil)
|
|
vars, err = rbl.GetBootVars("snapd_good_recovery_systems")
|
|
c.Assert(err, IsNil)
|
|
c.Check(vars, DeepEquals, map[string]string{
|
|
"snapd_good_recovery_systems": "",
|
|
})
|
|
}
|