Skip to content

Commit 4a6a91e

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

18 files changed

Lines changed: 1350 additions & 63 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: 171 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,173 @@ 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+
readSystemEssentialCalls := 0
2609+
restore = boot.MockSeedReadSystemEssential(func(seedDir, label string, essentialTypes []snap.Type, tm timings.Measurer) (*asserts.Model, []*seed.Snap, error) {
2610+
readSystemEssentialCalls++
2611+
c.Check(seedDir, Equals, dirs.SnapSeedDir)
2612+
c.Check(label, Equals, "test")
2613+
return model, []*seed.Snap{mockKernelSeedSnap(snap.R(1)), mockGadgetSeedSnap(c, nil)}, nil
2614+
})
2615+
defer restore()
2616+
2617+
kernel2, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_2.snap")
2618+
c.Assert(err, IsNil)
2619+
2620+
kernel3, err := snap.ParsePlaceInfoFromSnapFileName("pc-kernel_3.snap")
2621+
c.Assert(err, IsNil)
2622+
2623+
sealKeyForBootChainsCalled := 0
2624+
restore = boot.MockSealKeyForBootChains(func(method device.SealingMethod, key, saveKey secboot.BootstrappedContainer, primaryKey []byte, volumesAuth *device.VolumesAuthOptions, checkResult *secboot.PreinstallCheckResult, params *boot.SealKeyForBootChainsParams) error {
2625+
sealKeyForBootChainsCalled++
2626+
c.Check(method, Equals, device.SealingMethodTPM)
2627+
c.Check(key, Equals, myKey)
2628+
c.Check(saveKey, Equals, myKey2)
2629+
c.Check(primaryKey, DeepEquals, chosenPrimaryKey)
2630+
c.Check(volumesAuth, Equals, myVolumesAuth)
2631+
c.Check(checkResult, Equals, myCheckResult)
2632+
2633+
recoveryBootLoader, hasRecovery := params.RoleToBlName[bootloader.RoleRecovery]
2634+
c.Assert(hasRecovery, Equals, true)
2635+
c.Check(recoveryBootLoader, Equals, "grub")
2636+
runBootLoader, hasRun := params.RoleToBlName[bootloader.RoleRunMode]
2637+
c.Assert(hasRun, Equals, true)
2638+
c.Check(runBootLoader, Equals, "grub")
2639+
2640+
c.Assert(params.RunModeBootChains, HasLen, 2)
2641+
for n, runBootChain := range params.RunModeBootChains {
2642+
c.Check(runBootChain.Model, Equals, model.Model())
2643+
c.Check(runBootChain.KernelCmdlines, DeepEquals, []string{"foo", "bar"})
2644+
c.Check(runBootChain.KernelBootFile.Path, Equals, "kernel.efi")
2645+
switch n {
2646+
case 0:
2647+
c.Check(runBootChain.KernelBootFile.Snap, Equals, filepath.Join(dirs.SnapBlobDir, "pc-kernel_2.snap"))
2648+
case 1:
2649+
c.Check(runBootChain.KernelBootFile.Snap, Equals, filepath.Join(dirs.SnapBlobDir, "pc-kernel_3.snap"))
2650+
}
2651+
c.Check(runBootChain.KernelBootFile.Role, Equals, bootloader.RoleRunMode)
2652+
c.Assert(runBootChain.AssetChain, HasLen, 3)
2653+
runShim := runBootChain.AssetChain[0]
2654+
runGrub := runBootChain.AssetChain[1]
2655+
runGrubRun := runBootChain.AssetChain[2]
2656+
c.Check(runShim.Name, Equals, "bootx64.efi")
2657+
c.Check(runShim.Hashes, DeepEquals, []string{"shimhash1", "shimhash2"})
2658+
c.Check(runGrub.Name, Equals, "grubx64.efi")
2659+
c.Check(runGrub.Hashes, DeepEquals, []string{"recovery-hash1"})
2660+
c.Check(runGrubRun.Name, Equals, "grubx64.efi")
2661+
c.Check(runGrubRun.Hashes, DeepEquals, []string{"hash1", "hash2"})
2662+
}
2663+
2664+
c.Check(params.RecoveryBootChainsForRunKey, HasLen, 0)
2665+
c.Assert(params.RecoveryBootChains, HasLen, 1)
2666+
recoveryBootChain := params.RecoveryBootChains[0]
2667+
c.Check(recoveryBootChain.KernelBootFile.Path, Equals, "kernel.efi")
2668+
c.Check(recoveryBootChain.KernelBootFile.Snap, Equals, "/var/lib/snapd/seed/snaps/pc-kernel_1.snap")
2669+
c.Check(recoveryBootChain.KernelBootFile.Role, Equals, bootloader.RoleRecovery)
2670+
c.Check(recoveryBootChain.Model, Equals, model.Model())
2671+
c.Assert(recoveryBootChain.AssetChain, HasLen, 2)
2672+
recoveryShim := recoveryBootChain.AssetChain[0]
2673+
recoveryGrub := recoveryBootChain.AssetChain[1]
2674+
c.Check(recoveryShim.Name, Equals, "bootx64.efi")
2675+
c.Check(recoveryShim.Hashes, DeepEquals, []string{"shimhash1", "shimhash2"})
2676+
c.Check(recoveryGrub.Name, Equals, "grubx64.efi")
2677+
c.Check(recoveryGrub.Hashes, DeepEquals, []string{"recovery-hash1"})
2678+
2679+
c.Check(params.Reprovision, Equals, true)
2680+
c.Check(params.LegacyFactoryResetKeyPath, Equals, false)
2681+
c.Check(params.InstallHostWritableDir, Equals, filepath.Join(boot.InitramfsRunMntDir, "ubuntu-data"))
2682+
2683+
c.Check(params.UseTokens, Equals, true)
2684+
2685+
return nil
2686+
})
2687+
defer restore()
2688+
2689+
restore = boot.MockCryptsetupSupportsTokenReplace(true)
2690+
defer restore()
2691+
2692+
modeenv := &boot.Modeenv{
2693+
Mode: "run",
2694+
RecoverySystem: "test",
2695+
CurrentRecoverySystems: []string{"test", "other"},
2696+
GoodRecoverySystems: []string{"test", "other"},
2697+
2698+
CurrentTrustedBootAssets: boot.BootAssetsMap{
2699+
"grubx64.efi": []string{"hash1", "hash2"},
2700+
},
2701+
CurrentTrustedRecoveryBootAssets: boot.BootAssetsMap{
2702+
"bootx64.efi": []string{"shimhash1", "shimhash2"},
2703+
"grubx64.efi": []string{"recovery-hash1"},
2704+
},
2705+
CurrentKernelCommandLines: boot.BootCommandLines{
2706+
"foo", "bar",
2707+
},
2708+
2709+
CurrentKernels: []string{kernel2.Filename(), kernel3.Filename()},
2710+
2711+
Model: "my-model-uc20",
2712+
BrandID: "my-brand",
2713+
ModelSignKeyID: "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij",
2714+
Grade: "dangerous",
2715+
}
2716+
c.Assert(modeenv.WriteTo(""), IsNil)
2717+
2718+
var protector secboot.KeyProtectorFactory
2719+
2720+
err = boot.MakeRunnableSystemReprovision(model, protector, encryptionSetup)
2721+
c.Assert(err, IsNil)
2722+
2723+
c.Check(sealKeyForBootChainsCalled, Equals, 1)
2724+
}

boot/seal.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,15 +242,20 @@ 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+
modes := map[string][]string{}
248+
for _, system := range modeenv.GoodRecoverySystems {
249+
logger.Debugf("sealing for system %q", system)
250+
modes[system] = []string{ModeRecover, ModeFactoryReset}
251+
}
252+
for _, system := range modeenv.CurrentRecoverySystems {
253+
if _, has := modes[system]; !has {
254+
return fmt.Errorf("trying to install or reprovision with try system %q", system)
255+
}
251256
}
252257
var err error
253-
params.RecoveryBootChains, err = recoveryBootChainsForSystems(systems, modes, tbl, modeenv, includeTryModel, flags.SeedDir)
258+
params.RecoveryBootChains, err = recoveryBootChainsForSystems(modeenv.GoodRecoverySystems, modes, tbl, modeenv, includeTryModel, flags.SeedDir)
254259
if err != nil {
255260
return fmt.Errorf("cannot compose recovery boot chains: %v", err)
256261
}

0 commit comments

Comments
 (0)