Skip to content

Commit 4df1ded

Browse files
committed
many: add reprovision API
1 parent a004222 commit 4df1ded

18 files changed

Lines changed: 1364 additions & 62 deletions

File tree

boot/assets.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,14 @@ func (b *bootAssetsImpl) UpdateBootEntry() error {
329329
return doUpdateBootEntry(b.bootLoader, b.updatedAssets)
330330
}
331331

332+
func GetTrustedAssetsFromModeenv(m *Modeenv) BootAssets {
333+
return &bootAssetsImpl{
334+
trackedAssets: m.CurrentTrustedBootAssets,
335+
trackedRecoveryAssets: m.CurrentTrustedRecoveryBootAssets,
336+
// No update of boot entry for now
337+
}
338+
}
339+
332340
type trustedAssetsInstallObserverImpl struct {
333341
model *asserts.Model
334342
gadgetDir string

boot/makebootable.go

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,46 @@ func isSealModeenvLocked() bool {
468468
return atomic.LoadInt32(&sealModeenvLocked) == 1
469469
}
470470

471+
func makeRunnableSystemSeal(modeenv *Modeenv, model *asserts.Model, protector secboot.KeyProtectorFactory, encryption *EncryptionSetup, makeOpts makeRunnableOptions) error {
472+
tokens := UseTokens(model)
473+
if tokens {
474+
logger.Debugf("key data will be stored in tokens")
475+
} else {
476+
logger.Debugf("key data will be stored in files")
477+
}
478+
479+
flags := sealKeyToModeenvFlags{
480+
HookKeyProtectorFactory: protector,
481+
LegacyFactoryResetKeyPath: makeOpts.LegacyFactoryResetKeyPath,
482+
Reprovision: makeOpts.Reprovision,
483+
SeedDir: makeOpts.SeedDir,
484+
StateUnlocker: makeOpts.StateUnlocker,
485+
UseTokens: tokens,
486+
}
487+
488+
if makeOpts.Standalone {
489+
flags.SnapsDir = dirs.SnapBlobDirUnder(InstallHostWritableDir(model))
490+
}
491+
492+
// seal the encryption key to the parameters specified in
493+
// modeenv as well as optimum PCR configuration specified in the
494+
// check result (when available)
495+
if err := sealKeyToModeenv(
496+
encryption.dataBootstrappedContainer,
497+
encryption.saveBootstrappedContainer,
498+
encryption.primaryKey,
499+
encryption.volumesAuth,
500+
encryption.checkResult,
501+
model,
502+
modeenv,
503+
flags,
504+
); err != nil {
505+
return err
506+
}
507+
508+
return nil
509+
}
510+
471511
func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, bootAssets BootAssets, encryption *EncryptionSetup, makeOpts makeRunnableOptions) error {
472512
if model.Grade() == asserts.ModelGradeUnset {
473513
return fmt.Errorf("internal error: cannot make pre-UC20 system runnable")
@@ -659,37 +699,7 @@ func makeRunnableSystem(model *asserts.Model, bootWith *BootableSet, bootAssets
659699
return fmt.Errorf("cannot check for fde-setup hook key protector: %v", err)
660700
}
661701

662-
tokens := UseTokens(model)
663-
if tokens {
664-
logger.Debugf("key data will be stored in tokens")
665-
} else {
666-
logger.Debugf("key data will be stored in files")
667-
}
668-
669-
flags := sealKeyToModeenvFlags{
670-
HookKeyProtectorFactory: protector,
671-
LegacyFactoryResetKeyPath: makeOpts.LegacyFactoryResetKeyPath,
672-
Reprovision: makeOpts.Reprovision,
673-
SeedDir: makeOpts.SeedDir,
674-
StateUnlocker: makeOpts.StateUnlocker,
675-
UseTokens: tokens,
676-
}
677-
if makeOpts.Standalone {
678-
flags.SnapsDir = snapBlobDir
679-
}
680-
// seal the encryption key to the parameters specified in
681-
// modeenv as well as optimum PCR configuration specified in the
682-
// check result (when available)
683-
if err := sealKeyToModeenv(
684-
encryption.dataBootstrappedContainer,
685-
encryption.saveBootstrappedContainer,
686-
encryption.primaryKey,
687-
encryption.volumesAuth,
688-
encryption.checkResult,
689-
model,
690-
modeenv,
691-
flags,
692-
); err != nil {
702+
if err := makeRunnableSystemSeal(modeenv, model, protector, encryption, makeOpts); err != nil {
693703
return err
694704
}
695705
}
@@ -801,8 +811,16 @@ func MakeRunnableSystemAfterDataReset(model *asserts.Model, bootWith *BootableSe
801811
})
802812
}
803813

804-
func MakeRunnableSystemReprovision(model *asserts.Model, bootWith *BootableSet, bootAssets BootAssets, encryption *EncryptionSetup) error {
805-
return makeRunnableSystem(model, bootWith, bootAssets, encryption, makeRunnableOptions{
814+
func MakeRunnableSystemReprovision(model *asserts.Model, protector secboot.KeyProtectorFactory, encryption *EncryptionSetup) error {
815+
sealModeenvLock()
816+
defer sealModeenvUnlock()
817+
818+
modeenv, err := ReadModeenv("")
819+
if err != nil {
820+
return err
821+
}
822+
823+
return makeRunnableSystemSeal(modeenv, model, protector, encryption, makeRunnableOptions{
806824
Reprovision: true,
807825
SeedDir: dirs.SnapSeedDir,
808826
})

boot/makebootable_test.go

Lines changed: 181 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,6 @@ func (s *makeBootable20Suite) TestMakeSystemRunnableSealWithHookKeyProtector(c *
580580
type testMakeSystemRunnable20Opts struct {
581581
standalone bool
582582
factoryReset bool
583-
reprovision bool
584583
classic bool
585584
fromInitrd bool
586585
withKComps bool
@@ -822,7 +821,7 @@ version: 5.0
822821
c.Assert(recoveryGrub.Hashes, HasLen, 1)
823822
c.Check(recoveryGrub.Hashes[0], Equals, "aa3c1a83e74bf6dd40dd64e5c5bd1971d75cdf55515b23b9eb379f66bf43d4661d22c4b8cf7d7a982d2013ab65c1c4c5")
824823

825-
c.Check(params.Reprovision, Equals, opts.reprovision || opts.factoryReset)
824+
c.Check(params.Reprovision, Equals, opts.factoryReset)
826825
c.Check(params.LegacyFactoryResetKeyPath, Equals, opts.factoryReset)
827826
if opts.classic {
828827
c.Check(params.InstallHostWritableDir, Equals, filepath.Join(boot.InitramfsRunMntDir, "ubuntu-data"))
@@ -870,8 +869,6 @@ version: 5.0
870869
c.Check(u.unlocked, Equals, 1)
871870
case opts.factoryReset && !opts.fromInitrd:
872871
err = boot.MakeRunnableSystemAfterDataReset(model, bootWith, obs.BootAssets(), obs.EncryptionSetup())
873-
case opts.reprovision && !opts.fromInitrd:
874-
err = boot.MakeRunnableSystemReprovision(model, bootWith, obs.BootAssets(), obs.EncryptionSetup())
875872
default:
876873
err = boot.MakeRunnableSystem(model, bootWith, obs.BootAssets(), obs.EncryptionSetup())
877874
}
@@ -1050,16 +1047,6 @@ func (s *makeBootable20Suite) TestMakeSystemRunnable20FactoryResetOnClassic(c *C
10501047
})
10511048
}
10521049

1053-
func (s *makeBootable20Suite) TestMakeSystemRunnable20Reprovision(c *C) {
1054-
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
1055-
standalone: false,
1056-
reprovision: true,
1057-
classic: false,
1058-
fromInitrd: false,
1059-
withKComps: true,
1060-
})
1061-
}
1062-
10631050
func (s *makeBootable20Suite) TestMakeSystemRunnable20InstallFromInitrd(c *C) {
10641051
s.testMakeSystemRunnable20(c, testMakeSystemRunnable20Opts{
10651052
standalone: true,
@@ -2565,3 +2552,183 @@ func (s *makeBootable20Suite) TestMakeBootableImageOptionalKernelArgsSignedAndDa
25652552
// The option is ignored if non-dangerous model
25662553
s.testMakeBootableImageOptionalKernelArgs(c, model, options, "", "")
25672554
}
2555+
2556+
func (s *makeBootable20Suite) TestMakeSystemRunnableReprovision(c *C) {
2557+
/* baseName := "core26" */
2558+
fakeProc := c.MkDir()
2559+
fakeCmdline := filepath.Join(fakeProc, "cmdline")
2560+
defer kcmdline.MockProcCmdline(fakeCmdline)()
2561+
err := os.WriteFile(fakeCmdline, []byte(fmt.Sprintf("some ubuntu-core.force-experimental-tokens=1 args")), 0644)
2562+
c.Assert(err, IsNil)
2563+
2564+
restore := release.MockOnClassic(true)
2565+
defer restore()
2566+
dirs.SetRootDir(dirs.GlobalRootDir)
2567+
2568+
bootloader.Force(nil)
2569+
2570+
var model *asserts.Model
2571+
model = boottest.MakeMockUC20Model(map[string]any{
2572+
"classic": "true",
2573+
"distribution": "ubuntu",
2574+
})
2575+
/*seedSnapsDirs := filepath.Join(s.rootdir, "/snaps")
2576+
err = os.MkdirAll(seedSnapsDirs, 0755)
2577+
c.Assert(err, IsNil)*/
2578+
2579+
mockSeedGrubDir := filepath.Join(boot.InitramfsUbuntuSeedDir, "EFI", "ubuntu")
2580+
mockSeedGrubCfg := filepath.Join(mockSeedGrubDir, "grub.cfg")
2581+
err = os.MkdirAll(filepath.Dir(mockSeedGrubCfg), 0755)
2582+
c.Assert(err, IsNil)
2583+
err = os.WriteFile(mockSeedGrubCfg, []byte("# Snapd-Boot-Config-Edition: 1\n"), 0644)
2584+
c.Assert(err, IsNil)
2585+
genv := grubenv.NewEnv(filepath.Join(mockSeedGrubDir, "grubenv"))
2586+
c.Assert(genv.Save(), IsNil)
2587+
2588+
mockBootGrubDir := filepath.Join(boot.InitramfsUbuntuBootDir, "EFI", "ubuntu")
2589+
mockBootGrubCfg := filepath.Join(mockBootGrubDir, "grub.cfg")
2590+
err = os.MkdirAll(filepath.Dir(mockBootGrubCfg), 0755)
2591+
c.Assert(err, IsNil)
2592+
err = os.WriteFile(mockBootGrubCfg, nil, 0644)
2593+
c.Assert(err, IsNil)
2594+
2595+
myKey := secboot.CreateMockBootstrappedContainer()
2596+
myKey2 := secboot.CreateMockBootstrappedContainer()
2597+
chosenPrimaryKey := []byte("primarykey!")
2598+
myVolumesAuth := &device.VolumesAuthOptions{Mode: device.AuthModePassphrase, Passphrase: "test"}
2599+
myCheckResult := &secboot.PreinstallCheckResult{}
2600+
2601+
encryptionSetup := boot.NewEncryptionSetup(
2602+
myKey, myKey2,
2603+
chosenPrimaryKey,
2604+
myVolumesAuth,
2605+
myCheckResult,
2606+
)
2607+
2608+
var readSystemEssentialCalls []string
2609+
restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
2610+
readSystemEssentialCalls = append(readSystemEssentialCalls, label)
2611+
c.Check(seedDir, Equals, dirs.SnapSeedDir)
2612+
if label == "test" {
2613+
return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
2614+
} else {
2615+
return model, []*seed.Snap{mockKernelSeedSnap(snap.R(2)), mockGadgetSeedSnap(c, nil)}, nil
2616+
}
2617+
})
2618+
defer restore()
2619+
2620+
kernel2, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
2621+
c.Assert(err, IsNil)
2622+
2623+
kernel3, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_3.snap")
2624+
c.Assert(err, IsNil)
2625+
2626+
sealKeyForBootChainsCalled := 0
2627+
restore = boot.MockSealKeyForBootChains(func(method device.SealingMethod, key, saveKey secboot.BootstrappedContainer, primaryKey []byte, volumesAuth *device.VolumesAuthOptions, checkResult *secboot.PreinstallCheckResult, params *boot.SealKeyForBootChainsParams) error {
2628+
sealKeyForBootChainsCalled++
2629+
c.Check(method, Equals, device.SealingMethodTPM)
2630+
c.Check(key, Equals, myKey)
2631+
c.Check(saveKey, Equals, myKey2)
2632+
c.Check(primaryKey, DeepEquals, chosenPrimaryKey)
2633+
c.Check(volumesAuth, Equals, myVolumesAuth)
2634+
c.Check(checkResult, Equals, myCheckResult)
2635+
2636+
recoveryBootLoader, hasRecovery := params.RoleToBlName[bootloader.RoleRecovery]
2637+
c.Assert(hasRecovery, Equals, true)
2638+
c.Check(recoveryBootLoader, Equals, "grub")
2639+
runBootLoader, hasRun := params.RoleToBlName[bootloader.RoleRunMode]
2640+
c.Assert(hasRun, Equals, true)
2641+
c.Check(runBootLoader, Equals, "grub")
2642+
2643+
c.Assert(params.RunModeBootChains, HasLen, 2)
2644+
for n, runBootChain := range params.RunModeBootChains {
2645+
c.Check(runBootChain.Model, Equals, model.Model())
2646+
c.Check(runBootChain.KernelCmdlines, DeepEquals, []string{"foo", "bar"})
2647+
c.Check(runBootChain.KernelBootFile.Path, Equals, "kernel.efi")
2648+
switch n {
2649+
case 0:
2650+
c.Check(runBootChain.KernelBootFile.Snap, Equals, filepath.Join(dirs.SnapBlobDir, "pc-kernel_2.snap"))
2651+
case 1:
2652+
c.Check(runBootChain.KernelBootFile.Snap, Equals, filepath.Join(dirs.SnapBlobDir, "pc-kernel_3.snap"))
2653+
}
2654+
c.Check(runBootChain.KernelBootFile.Role, Equals, bootloader.RoleRunMode)
2655+
c.Assert(runBootChain.AssetChain, HasLen, 3)
2656+
runShim := runBootChain.AssetChain[0]
2657+
runGrub := runBootChain.AssetChain[1]
2658+
runGrubRun := runBootChain.AssetChain[2]
2659+
c.Check(runShim.Name, Equals, "bootx64.efi")
2660+
c.Check(runShim.Hashes, DeepEquals, []string{"shimhash1", "shimhash2"})
2661+
c.Check(runGrub.Name, Equals, "grubx64.efi")
2662+
c.Check(runGrub.Hashes, DeepEquals, []string{"recovery-hash1"})
2663+
c.Check(runGrubRun.Name, Equals, "grubx64.efi")
2664+
c.Check(runGrubRun.Hashes, DeepEquals, []string{"hash1", "hash2"})
2665+
}
2666+
2667+
c.Check(params.RecoveryBootChainsForRunKey, HasLen, 0)
2668+
c.Assert(params.RecoveryBootChains, HasLen, 2)
2669+
for n, recoveryBootChain := range params.RecoveryBootChains {
2670+
c.Check(recoveryBootChain.KernelBootFile.Path, Equals, "kernel.efi")
2671+
switch n {
2672+
case 0:
2673+
c.Check(recoveryBootChain.KernelBootFile.Snap, Equals, "/var/lib/snapd/seed/snaps/pc-kernel_1.snap")
2674+
case 1:
2675+
c.Check(recoveryBootChain.KernelBootFile.Snap, Equals, "/var/lib/snapd/seed/snaps/pc-kernel_2.snap")
2676+
}
2677+
c.Check(recoveryBootChain.KernelBootFile.Role, Equals, bootloader.RoleRecovery)
2678+
c.Check(recoveryBootChain.Model, Equals, model.Model())
2679+
c.Assert(recoveryBootChain.AssetChain, HasLen, 2)
2680+
recoveryShim := recoveryBootChain.AssetChain[0]
2681+
recoveryGrub := recoveryBootChain.AssetChain[1]
2682+
c.Check(recoveryShim.Name, Equals, "bootx64.efi")
2683+
c.Check(recoveryShim.Hashes, DeepEquals, []string{"shimhash1", "shimhash2"})
2684+
c.Check(recoveryGrub.Name, Equals, "grubx64.efi")
2685+
c.Check(recoveryGrub.Hashes, DeepEquals, []string{"recovery-hash1"})
2686+
}
2687+
2688+
c.Check(params.Reprovision, Equals, true)
2689+
c.Check(params.LegacyFactoryResetKeyPath, Equals, false)
2690+
c.Check(params.InstallHostWritableDir, Equals, filepath.Join(boot.InitramfsRunMntDir, "ubuntu-data"))
2691+
2692+
c.Check(params.UseTokens, Equals, true)
2693+
2694+
return nil
2695+
})
2696+
defer restore()
2697+
2698+
restore = boot.MockCryptsetupSupportsTokenReplace(true)
2699+
defer restore()
2700+
2701+
modeenv := &boot.Modeenv{
2702+
Mode: "run",
2703+
RecoverySystem: "test",
2704+
CurrentRecoverySystems: []string{"test", "other"},
2705+
GoodRecoverySystems: []string{"test", "other"},
2706+
2707+
CurrentTrustedBootAssets: boot.BootAssetsMap{
2708+
"grubx64.efi": []string{"hash1", "hash2"},
2709+
},
2710+
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
2711+
"bootx64.efi": []string{"shimhash1", "shimhash2"},
2712+
"grubx64.efi": []string{"recovery-hash1"},
2713+
},
2714+
CurrentKernelCommandLines: boot.BootCommandLines{
2715+
"foo", "bar",
2716+
},
2717+
2718+
CurrentKernels: []string{kernel2.Filename(), kernel3.Filename()},
2719+
2720+
Model: "my-model-uc20",
2721+
BrandID: "my-brand",
2722+
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
2723+
Grade: "dangerous",
2724+
}
2725+
c.Assert(modeenv.WriteTo(""), IsNil)
2726+
2727+
var protector secboot.KeyProtectorFactory
2728+
2729+
err = boot.MakeRunnableSystemReprovision(model, protector, encryptionSetup)
2730+
c.Assert(err, IsNil)
2731+
2732+
c.Check(sealKeyForBootChainsCalled, Equals, 1)
2733+
c.Check(readSystemEssentialCalls, DeepEquals, []string{"test", "other"})
2734+
}

boot/seal.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,23 @@ func sealKeyToModeenvForMethod(
242242
}
243243
}
244244

245+
// When installing or reprovsion, we expect there is no try system
245246
includeTryModel := false
246-
systems := []string{modeenv.RecoverySystem}
247-
modes := map[string][]string{
248-
// the system we are installing from is considered current and
249-
// tested, hence allow both recover and factory reset modes
250-
modeenv.RecoverySystem: {ModeRecover, ModeFactoryReset},
247+
systems := modeenv.GoodRecoverySystems
248+
if len(systems) == 0 {
249+
systems = []string{modeenv.RecoverySystem}
250+
}
251+
modes := map[string][]string{}
252+
for _, system := range systems {
253+
logger.Debugf("sealing for system %q", system)
254+
modes[system] = []string{ModeRecover, ModeFactoryReset}
255+
}
256+
for _, system := range modeenv.CurrentRecoverySystems {
257+
if _, has := modes[system]; !has {
258+
return fmt.Errorf("trying to install or reprovision with a try system %q", system)
259+
}
251260
}
261+
252262
var err error
253263
params.RecoveryBootChains, err = recoveryBootChainsForSystems(systems, modes, tbl, modeenv, includeTryModel, flags.SeedDir)
254264
if err != nil {

0 commit comments

Comments
 (0)