github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/overlord/devicestate/devicestate_install_mode_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package devicestate_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 29 . "gopkg.in/check.v1" 30 "gopkg.in/tomb.v2" 31 32 "github.com/snapcore/snapd/asserts" 33 "github.com/snapcore/snapd/boot" 34 "github.com/snapcore/snapd/bootloader" 35 "github.com/snapcore/snapd/bootloader/bootloadertest" 36 "github.com/snapcore/snapd/dirs" 37 "github.com/snapcore/snapd/gadget" 38 "github.com/snapcore/snapd/gadget/install" 39 "github.com/snapcore/snapd/logger" 40 "github.com/snapcore/snapd/overlord/auth" 41 "github.com/snapcore/snapd/overlord/devicestate" 42 "github.com/snapcore/snapd/overlord/devicestate/devicestatetest" 43 "github.com/snapcore/snapd/overlord/hookstate" 44 "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" 45 "github.com/snapcore/snapd/overlord/snapstate" 46 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 47 "github.com/snapcore/snapd/overlord/state" 48 "github.com/snapcore/snapd/release" 49 "github.com/snapcore/snapd/secboot" 50 "github.com/snapcore/snapd/snap" 51 "github.com/snapcore/snapd/snap/snaptest" 52 "github.com/snapcore/snapd/sysconfig" 53 "github.com/snapcore/snapd/testutil" 54 ) 55 56 type deviceMgrInstallModeSuite struct { 57 deviceMgrBaseSuite 58 59 ConfigureTargetSystemOptsPassed []*sysconfig.Options 60 ConfigureTargetSystemErr error 61 } 62 63 var _ = Suite(&deviceMgrInstallModeSuite{}) 64 65 func (s *deviceMgrInstallModeSuite) findInstallSystem() *state.Change { 66 for _, chg := range s.state.Changes() { 67 if chg.Kind() == "install-system" { 68 return chg 69 } 70 } 71 return nil 72 } 73 74 func (s *deviceMgrInstallModeSuite) SetUpTest(c *C) { 75 s.deviceMgrBaseSuite.SetUpTest(c) 76 77 s.ConfigureTargetSystemOptsPassed = nil 78 s.ConfigureTargetSystemErr = nil 79 restore := devicestate.MockSysconfigConfigureTargetSystem(func(opts *sysconfig.Options) error { 80 s.ConfigureTargetSystemOptsPassed = append(s.ConfigureTargetSystemOptsPassed, opts) 81 return s.ConfigureTargetSystemErr 82 }) 83 s.AddCleanup(restore) 84 85 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { 86 return fmt.Errorf("TPM not available") 87 }) 88 s.AddCleanup(restore) 89 90 s.state.Lock() 91 defer s.state.Unlock() 92 s.state.Set("seeded", true) 93 94 fakeJournalctl := testutil.MockCommand(c, "journalctl", "") 95 s.AddCleanup(fakeJournalctl.Restore) 96 } 97 98 const ( 99 pcSnapID = "pcididididididididididididididid" 100 pcKernelSnapID = "pckernelidididididididididididid" 101 core20SnapID = "core20ididididididididididididid" 102 ) 103 104 func (s *deviceMgrInstallModeSuite) makeMockInstalledPcGadget(c *C, grade, installDeviceHook string, gadgetDefaultsYaml string) *asserts.Model { 105 si := &snap.SideInfo{ 106 RealName: "pc-kernel", 107 Revision: snap.R(1), 108 SnapID: pcKernelSnapID, 109 } 110 snapstate.Set(s.state, "pc-kernel", &snapstate.SnapState{ 111 SnapType: "kernel", 112 Sequence: []*snap.SideInfo{si}, 113 Current: si.Revision, 114 Active: true, 115 }) 116 kernelInfo := snaptest.MockSnapWithFiles(c, "name: pc-kernel\ntype: kernel", si, nil) 117 kernelFn := snaptest.MakeTestSnapWithFiles(c, "name: pc-kernel\ntype: kernel\nversion: 1.0", nil) 118 err := os.Rename(kernelFn, kernelInfo.MountFile()) 119 c.Assert(err, IsNil) 120 121 si = &snap.SideInfo{ 122 RealName: "pc", 123 Revision: snap.R(1), 124 SnapID: pcSnapID, 125 } 126 snapstate.Set(s.state, "pc", &snapstate.SnapState{ 127 SnapType: "gadget", 128 Sequence: []*snap.SideInfo{si}, 129 Current: si.Revision, 130 Active: true, 131 }) 132 133 files := [][]string{ 134 {"meta/gadget.yaml", uc20gadgetYamlWithSave + gadgetDefaultsYaml}, 135 } 136 if installDeviceHook != "" { 137 files = append(files, []string{"meta/hooks/install-device", installDeviceHook}) 138 } 139 snaptest.MockSnapWithFiles(c, "name: pc\ntype: gadget", si, files) 140 141 si = &snap.SideInfo{ 142 RealName: "core20", 143 Revision: snap.R(2), 144 SnapID: core20SnapID, 145 } 146 snapstate.Set(s.state, "core20", &snapstate.SnapState{ 147 SnapType: "base", 148 Sequence: []*snap.SideInfo{si}, 149 Current: si.Revision, 150 Active: true, 151 }) 152 snaptest.MockSnapWithFiles(c, "name: core20\ntype: base", si, nil) 153 154 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 155 "display-name": "my model", 156 "architecture": "amd64", 157 "base": "core20", 158 "grade": grade, 159 "snaps": []interface{}{ 160 map[string]interface{}{ 161 "name": "pc-kernel", 162 "id": pcKernelSnapID, 163 "type": "kernel", 164 "default-channel": "20", 165 }, 166 map[string]interface{}{ 167 "name": "pc", 168 "id": pcSnapID, 169 "type": "gadget", 170 "default-channel": "20", 171 }}, 172 }) 173 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 174 Brand: "my-brand", 175 Model: "my-model", 176 // no serial in install mode 177 }) 178 179 return mockModel 180 } 181 182 type encTestCase struct { 183 tpm bool 184 bypass bool 185 encrypt bool 186 trustedBootloader bool 187 } 188 189 var ( 190 dataEncryptionKey = secboot.EncryptionKey{'d', 'a', 't', 'a', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 191 dataRecoveryKey = secboot.RecoveryKey{'r', 'e', 'c', 'o', 'v', 'e', 'r', 'y', 10, 11, 12, 13, 14, 15, 16, 17} 192 193 saveKey = secboot.EncryptionKey{'s', 'a', 'v', 'e', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 194 reinstallKey = secboot.RecoveryKey{'r', 'e', 'i', 'n', 's', 't', 'a', 'l', 'l', 11, 12, 13, 14, 15, 16, 17} 195 ) 196 197 func (s *deviceMgrInstallModeSuite) doRunChangeTestWithEncryption(c *C, grade string, tc encTestCase) error { 198 restore := release.MockOnClassic(false) 199 defer restore() 200 bootloaderRootdir := c.MkDir() 201 202 var brGadgetRoot, brDevice string 203 var brOpts install.Options 204 var installRunCalled int 205 var installSealingObserver gadget.ContentObserver 206 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 207 // ensure we can grab the lock here, i.e. that it's not taken 208 s.state.Lock() 209 s.state.Unlock() 210 211 c.Check(mod.Grade(), Equals, asserts.ModelGrade(grade)) 212 213 brGadgetRoot = gadgetRoot 214 brDevice = device 215 brOpts = options 216 installSealingObserver = obs 217 installRunCalled++ 218 var keysForRoles map[string]*install.EncryptionKeySet 219 if tc.encrypt { 220 keysForRoles = map[string]*install.EncryptionKeySet{ 221 gadget.SystemData: { 222 Key: dataEncryptionKey, 223 RecoveryKey: dataRecoveryKey, 224 }, 225 gadget.SystemSave: { 226 Key: saveKey, 227 RecoveryKey: reinstallKey, 228 }, 229 } 230 } 231 return &install.InstalledSystemSideData{ 232 KeysForRoles: keysForRoles, 233 }, nil 234 }) 235 defer restore() 236 237 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { 238 if tc.tpm { 239 return nil 240 } else { 241 return fmt.Errorf("TPM not available") 242 } 243 }) 244 defer restore() 245 246 if tc.trustedBootloader { 247 tab := bootloadertest.Mock("trusted", bootloaderRootdir).WithTrustedAssets() 248 tab.TrustedAssetsList = []string{"trusted-asset"} 249 bootloader.Force(tab) 250 s.AddCleanup(func() { bootloader.Force(nil) }) 251 252 err := os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755) 253 c.Assert(err, IsNil) 254 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "trusted-asset"), nil, 0644) 255 c.Assert(err, IsNil) 256 } 257 258 s.state.Lock() 259 mockModel := s.makeMockInstalledPcGadget(c, grade, "", "") 260 s.state.Unlock() 261 262 bypassEncryptionPath := filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted") 263 if tc.bypass { 264 err := os.MkdirAll(filepath.Dir(bypassEncryptionPath), 0755) 265 c.Assert(err, IsNil) 266 f, err := os.Create(bypassEncryptionPath) 267 c.Assert(err, IsNil) 268 f.Close() 269 } else { 270 os.RemoveAll(bypassEncryptionPath) 271 } 272 273 bootMakeBootableCalled := 0 274 restore = devicestate.MockBootMakeSystemRunnable(func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error { 275 c.Check(model, DeepEquals, mockModel) 276 c.Check(bootWith.KernelPath, Matches, ".*/var/lib/snapd/snaps/pc-kernel_1.snap") 277 c.Check(bootWith.BasePath, Matches, ".*/var/lib/snapd/snaps/core20_2.snap") 278 c.Check(bootWith.RecoverySystemDir, Matches, "/systems/20191218") 279 c.Check(bootWith.UnpackedGadgetDir, Equals, filepath.Join(dirs.SnapMountDir, "pc/1")) 280 if tc.encrypt { 281 c.Check(seal, NotNil) 282 } else { 283 c.Check(seal, IsNil) 284 } 285 bootMakeBootableCalled++ 286 return nil 287 }) 288 defer restore() 289 290 modeenv := boot.Modeenv{ 291 Mode: "install", 292 RecoverySystem: "20191218", 293 } 294 c.Assert(modeenv.WriteTo(""), IsNil) 295 devicestate.SetSystemMode(s.mgr, "install") 296 297 // normally done by snap-bootstrap 298 err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755) 299 c.Assert(err, IsNil) 300 301 s.settle(c) 302 303 // the install-system change is created 304 s.state.Lock() 305 defer s.state.Unlock() 306 installSystem := s.findInstallSystem() 307 c.Assert(installSystem, NotNil) 308 309 // and was run successfully 310 if err := installSystem.Err(); err != nil { 311 // we failed, no further checks needed 312 return err 313 } 314 315 c.Assert(installSystem.Status(), Equals, state.DoneStatus) 316 317 // in the right way 318 c.Assert(brGadgetRoot, Equals, filepath.Join(dirs.SnapMountDir, "/pc/1")) 319 c.Assert(brDevice, Equals, "") 320 if tc.encrypt { 321 c.Assert(brOpts, DeepEquals, install.Options{ 322 Mount: true, 323 Encrypt: true, 324 }) 325 } else { 326 c.Assert(brOpts, DeepEquals, install.Options{ 327 Mount: true, 328 }) 329 } 330 if tc.encrypt { 331 // inteface is not nil 332 c.Assert(installSealingObserver, NotNil) 333 // we expect a very specific type 334 trustedInstallObserver, ok := installSealingObserver.(*boot.TrustedAssetsInstallObserver) 335 c.Assert(ok, Equals, true, Commentf("unexpected type: %T", installSealingObserver)) 336 c.Assert(trustedInstallObserver, NotNil) 337 } else { 338 c.Assert(installSealingObserver, IsNil) 339 } 340 341 c.Assert(installRunCalled, Equals, 1) 342 c.Assert(bootMakeBootableCalled, Equals, 1) 343 c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 344 345 return nil 346 } 347 348 func (s *deviceMgrInstallModeSuite) TestInstallTaskErrors(c *C) { 349 restore := release.MockOnClassic(false) 350 defer restore() 351 352 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 353 return nil, fmt.Errorf("The horror, The horror") 354 }) 355 defer restore() 356 357 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 358 []byte("mode=install\n"), 0644) 359 c.Assert(err, IsNil) 360 361 s.state.Lock() 362 s.makeMockInstalledPcGadget(c, "dangerous", "", "") 363 devicestate.SetSystemMode(s.mgr, "install") 364 s.state.Unlock() 365 366 s.settle(c) 367 368 s.state.Lock() 369 defer s.state.Unlock() 370 371 installSystem := s.findInstallSystem() 372 c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks: 373 - Setup system for run mode \(cannot install system: The horror, The horror\)`) 374 // no restart request on failure 375 c.Check(s.restartRequests, HasLen, 0) 376 } 377 378 func (s *deviceMgrInstallModeSuite) TestInstallExpTasks(c *C) { 379 restore := release.MockOnClassic(false) 380 defer restore() 381 382 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 383 return nil, nil 384 }) 385 defer restore() 386 387 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 388 []byte("mode=install\n"), 0644) 389 c.Assert(err, IsNil) 390 391 s.state.Lock() 392 s.makeMockInstalledPcGadget(c, "dangerous", "", "") 393 devicestate.SetSystemMode(s.mgr, "install") 394 s.state.Unlock() 395 396 s.settle(c) 397 398 s.state.Lock() 399 defer s.state.Unlock() 400 401 installSystem := s.findInstallSystem() 402 c.Check(installSystem.Err(), IsNil) 403 404 tasks := installSystem.Tasks() 405 c.Assert(tasks, HasLen, 2) 406 setupRunSystemTask := tasks[0] 407 restartSystemToRunModeTask := tasks[1] 408 409 c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system") 410 c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode") 411 412 // setup-run-system has no pre-reqs 413 c.Assert(setupRunSystemTask.WaitTasks(), HasLen, 0) 414 415 // restart-system-to-run-mode has a pre-req of setup-run-system 416 waitTasks := restartSystemToRunModeTask.WaitTasks() 417 c.Assert(waitTasks, HasLen, 1) 418 c.Assert(waitTasks[0].ID(), Equals, setupRunSystemTask.ID()) 419 420 // we did request a restart through restartSystemToRunModeTask 421 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 422 } 423 424 func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookExpTasks(c *C) { 425 restore := release.MockOnClassic(false) 426 defer restore() 427 428 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 429 return nil, nil 430 }) 431 defer restore() 432 433 hooksCalled := []*hookstate.Context{} 434 restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 435 ctx.Lock() 436 defer ctx.Unlock() 437 438 hooksCalled = append(hooksCalled, ctx) 439 return nil, nil 440 }) 441 defer restore() 442 443 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 444 []byte("mode=install\n"), 0644) 445 c.Assert(err, IsNil) 446 447 s.state.Lock() 448 s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "") 449 devicestate.SetSystemMode(s.mgr, "install") 450 s.state.Unlock() 451 452 s.settle(c) 453 454 s.state.Lock() 455 defer s.state.Unlock() 456 457 installSystem := s.findInstallSystem() 458 c.Check(installSystem.Err(), IsNil) 459 460 tasks := installSystem.Tasks() 461 c.Assert(tasks, HasLen, 3) 462 setupRunSystemTask := tasks[0] 463 installDevice := tasks[1] 464 restartSystemToRunModeTask := tasks[2] 465 466 c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system") 467 c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode") 468 c.Assert(installDevice.Kind(), Equals, "run-hook") 469 470 // setup-run-system has no pre-reqs 471 c.Assert(setupRunSystemTask.WaitTasks(), HasLen, 0) 472 473 // install-device has a pre-req of setup-run-system 474 waitTasks := installDevice.WaitTasks() 475 c.Assert(waitTasks, HasLen, 1) 476 c.Assert(waitTasks[0].ID(), Equals, setupRunSystemTask.ID()) 477 478 // install-device restart-task references to restart-system-to-run-mode 479 var restartTask string 480 err = installDevice.Get("restart-task", &restartTask) 481 c.Assert(err, IsNil) 482 c.Check(restartTask, Equals, restartSystemToRunModeTask.ID()) 483 484 // restart-system-to-run-mode has a pre-req of install-device 485 waitTasks = restartSystemToRunModeTask.WaitTasks() 486 c.Assert(waitTasks, HasLen, 1) 487 c.Assert(waitTasks[0].ID(), Equals, installDevice.ID()) 488 489 // we did request a restart through restartSystemToRunModeTask 490 c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 491 492 c.Assert(hooksCalled, HasLen, 1) 493 c.Assert(hooksCalled[0].HookName(), Equals, "install-device") 494 } 495 496 func (s *deviceMgrInstallModeSuite) testInstallWithInstallDeviceHookSnapctlReboot(c *C, arg string, rst state.RestartType) { 497 restore := release.MockOnClassic(false) 498 defer restore() 499 500 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 501 return nil, nil 502 }) 503 defer restore() 504 505 restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 506 c.Assert(ctx.HookName(), Equals, "install-device") 507 508 // snapctl reboot --halt 509 _, _, err := ctlcmd.Run(ctx, []string{"reboot", arg}, 0) 510 return nil, err 511 }) 512 defer restore() 513 514 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 515 []byte("mode=install\n"), 0644) 516 c.Assert(err, IsNil) 517 518 s.state.Lock() 519 s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "") 520 devicestate.SetSystemMode(s.mgr, "install") 521 s.state.Unlock() 522 523 s.settle(c) 524 525 s.state.Lock() 526 defer s.state.Unlock() 527 528 installSystem := s.findInstallSystem() 529 c.Check(installSystem.Err(), IsNil) 530 531 // we did end up requesting the right shutdown 532 c.Check(s.restartRequests, DeepEquals, []state.RestartType{rst}) 533 } 534 535 func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookSnapctlRebootHalt(c *C) { 536 s.testInstallWithInstallDeviceHookSnapctlReboot(c, "--halt", state.RestartSystemHaltNow) 537 } 538 539 func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookSnapctlRebootPoweroff(c *C) { 540 s.testInstallWithInstallDeviceHookSnapctlReboot(c, "--poweroff", state.RestartSystemPoweroffNow) 541 } 542 543 func (s *deviceMgrInstallModeSuite) TestInstallWithBrokenInstallDeviceHookUnhappy(c *C) { 544 restore := release.MockOnClassic(false) 545 defer restore() 546 547 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 548 return nil, nil 549 }) 550 defer restore() 551 552 hooksCalled := []*hookstate.Context{} 553 restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 554 ctx.Lock() 555 defer ctx.Unlock() 556 557 hooksCalled = append(hooksCalled, ctx) 558 return []byte("hook exited broken"), fmt.Errorf("hook broken") 559 }) 560 defer restore() 561 562 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 563 []byte("mode=install\n"), 0644) 564 c.Assert(err, IsNil) 565 566 s.state.Lock() 567 s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "") 568 devicestate.SetSystemMode(s.mgr, "install") 569 s.state.Unlock() 570 571 s.settle(c) 572 573 s.state.Lock() 574 defer s.state.Unlock() 575 576 installSystem := s.findInstallSystem() 577 c.Check(installSystem.Err(), ErrorMatches, `cannot perform the following tasks: 578 - Run install-device hook \(run hook \"install-device\": hook exited broken\)`) 579 580 tasks := installSystem.Tasks() 581 c.Assert(tasks, HasLen, 3) 582 setupRunSystemTask := tasks[0] 583 installDevice := tasks[1] 584 restartSystemToRunModeTask := tasks[2] 585 586 c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system") 587 c.Assert(installDevice.Kind(), Equals, "run-hook") 588 c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode") 589 590 // install-device is in Error state 591 c.Assert(installDevice.Status(), Equals, state.ErrorStatus) 592 593 // setup-run-system is in Done (it has no undo handler) 594 c.Assert(setupRunSystemTask.Status(), Equals, state.DoneStatus) 595 596 // restart-system-to-run-mode is in Hold 597 c.Assert(restartSystemToRunModeTask.Status(), Equals, state.HoldStatus) 598 599 // we didn't request a restart since restartsystemToRunMode didn't run 600 c.Check(s.restartRequests, HasLen, 0) 601 602 c.Assert(hooksCalled, HasLen, 1) 603 c.Assert(hooksCalled[0].HookName(), Equals, "install-device") 604 } 605 606 func (s *deviceMgrInstallModeSuite) TestInstallSetupRunSystemTaskNoRestarts(c *C) { 607 restore := release.MockOnClassic(false) 608 defer restore() 609 610 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 611 return nil, nil 612 }) 613 defer restore() 614 615 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 616 []byte("mode=install\n"), 0644) 617 c.Assert(err, IsNil) 618 619 s.state.Lock() 620 defer s.state.Unlock() 621 622 s.makeMockInstalledPcGadget(c, "dangerous", "", "") 623 devicestate.SetSystemMode(s.mgr, "install") 624 625 // also set the system as installed so that the install-system change 626 // doesn't get automatically added and we can craft our own change with just 627 // the setup-run-system task and not with the restart-system-to-run-mode 628 // task 629 devicestate.SetInstalledRan(s.mgr, true) 630 631 s.state.Unlock() 632 defer s.state.Lock() 633 634 s.settle(c) 635 636 s.state.Lock() 637 defer s.state.Unlock() 638 639 // make sure there is no install-system change that snuck in underneath us 640 installSystem := s.findInstallSystem() 641 c.Check(installSystem, IsNil) 642 643 t := s.state.NewTask("setup-run-system", "setup run system") 644 chg := s.state.NewChange("install-system", "install the system") 645 chg.AddTask(t) 646 647 // now let the change run 648 s.state.Unlock() 649 defer s.state.Lock() 650 651 s.settle(c) 652 653 s.state.Lock() 654 defer s.state.Unlock() 655 656 // now we should have the install-system change 657 installSystem = s.findInstallSystem() 658 c.Check(installSystem, Not(IsNil)) 659 c.Check(installSystem.Err(), IsNil) 660 661 tasks := installSystem.Tasks() 662 c.Assert(tasks, HasLen, 1) 663 setupRunSystemTask := tasks[0] 664 665 c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system") 666 667 // we did not request a restart (since that is done in restart-system-to-run-mode) 668 c.Check(s.restartRequests, HasLen, 0) 669 } 670 671 func (s *deviceMgrInstallModeSuite) TestInstallModeNotInstallmodeNoChg(c *C) { 672 restore := release.MockOnClassic(false) 673 defer restore() 674 675 s.state.Lock() 676 devicestate.SetSystemMode(s.mgr, "") 677 s.state.Unlock() 678 679 s.settle(c) 680 681 s.state.Lock() 682 defer s.state.Unlock() 683 684 // the install-system change is *not* created (not in install mode) 685 installSystem := s.findInstallSystem() 686 c.Assert(installSystem, IsNil) 687 } 688 689 func (s *deviceMgrInstallModeSuite) TestInstallModeNotClassic(c *C) { 690 restore := release.MockOnClassic(true) 691 defer restore() 692 693 s.state.Lock() 694 devicestate.SetSystemMode(s.mgr, "install") 695 s.state.Unlock() 696 697 s.settle(c) 698 699 s.state.Lock() 700 defer s.state.Unlock() 701 702 // the install-system change is *not* created (we're on classic) 703 installSystem := s.findInstallSystem() 704 c.Assert(installSystem, IsNil) 705 } 706 707 func (s *deviceMgrInstallModeSuite) TestInstallDangerous(c *C) { 708 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: false, encrypt: false}) 709 c.Assert(err, IsNil) 710 } 711 712 func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPM(c *C) { 713 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{ 714 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 715 }) 716 c.Assert(err, IsNil) 717 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 718 } 719 720 func (s *deviceMgrInstallModeSuite) TestInstallDangerousBypassEncryption(c *C) { 721 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: true, encrypt: false}) 722 c.Assert(err, IsNil) 723 } 724 725 func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPMBypassEncryption(c *C) { 726 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: true, bypass: true, encrypt: false}) 727 c.Assert(err, IsNil) 728 } 729 730 func (s *deviceMgrInstallModeSuite) TestInstallSigned(c *C) { 731 err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: false, encrypt: false}) 732 c.Assert(err, IsNil) 733 } 734 735 func (s *deviceMgrInstallModeSuite) TestInstallSignedWithTPM(c *C) { 736 err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{ 737 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 738 }) 739 c.Assert(err, IsNil) 740 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 741 } 742 743 func (s *deviceMgrInstallModeSuite) TestInstallSignedBypassEncryption(c *C) { 744 err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: true, encrypt: false}) 745 c.Assert(err, IsNil) 746 } 747 748 func (s *deviceMgrInstallModeSuite) TestInstallSecured(c *C) { 749 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: false, encrypt: false}) 750 c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*") 751 } 752 753 func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPM(c *C) { 754 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 755 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 756 }) 757 c.Assert(err, IsNil) 758 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 759 } 760 761 func (s *deviceMgrInstallModeSuite) TestInstallDangerousEncryptionWithTPMNoTrustedAssets(c *C) { 762 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{ 763 tpm: true, bypass: false, encrypt: true, trustedBootloader: false, 764 }) 765 c.Assert(err, IsNil) 766 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 767 } 768 769 func (s *deviceMgrInstallModeSuite) TestInstallDangerousNoEncryptionWithTrustedAssets(c *C) { 770 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{ 771 tpm: false, bypass: false, encrypt: false, trustedBootloader: true, 772 }) 773 c.Assert(err, IsNil) 774 } 775 776 func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPMAndSave(c *C) { 777 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 778 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 779 }) 780 c.Assert(err, IsNil) 781 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 782 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key"), testutil.FileEquals, []byte(saveKey)) 783 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key"), testutil.FileEquals, reinstallKey[:]) 784 marker, err := ioutil.ReadFile(filepath.Join(boot.InstallHostFDEDataDir, "marker")) 785 c.Assert(err, IsNil) 786 c.Check(marker, HasLen, 32) 787 c.Check(filepath.Join(boot.InstallHostFDESaveDir, "marker"), testutil.FileEquals, marker) 788 } 789 790 func (s *deviceMgrInstallModeSuite) TestInstallSecuredBypassEncryption(c *C) { 791 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: true, encrypt: false}) 792 c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*") 793 } 794 795 func (s *deviceMgrInstallModeSuite) TestInstallBootloaderVarSetFails(c *C) { 796 restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 797 c.Check(options.Encrypt, Equals, false) 798 // no keys set 799 return &install.InstalledSystemSideData{}, nil 800 }) 801 defer restore() 802 803 restore = devicestate.MockBootEnsureNextBootToRunMode(func(systemLabel string) error { 804 c.Check(systemLabel, Equals, "1234") 805 // no keys set 806 return fmt.Errorf("bootloader goes boom") 807 }) 808 defer restore() 809 810 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("no encrypted soup for you") }) 811 defer restore() 812 813 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 814 []byte("mode=install\nrecovery_system=1234"), 0644) 815 c.Assert(err, IsNil) 816 817 s.state.Lock() 818 s.makeMockInstalledPcGadget(c, "dangerous", "", "") 819 devicestate.SetSystemMode(s.mgr, "install") 820 s.state.Unlock() 821 822 s.settle(c) 823 824 s.state.Lock() 825 defer s.state.Unlock() 826 827 installSystem := s.findInstallSystem() 828 c.Check(installSystem.Err(), ErrorMatches, `cannot perform the following tasks: 829 - Ensure next boot to run mode \(bootloader goes boom\)`) 830 // no restart request on failure 831 c.Check(s.restartRequests, HasLen, 0) 832 } 833 834 func (s *deviceMgrInstallModeSuite) testInstallEncryptionSanityChecks(c *C, errMatch string) { 835 restore := release.MockOnClassic(false) 836 defer restore() 837 838 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil }) 839 defer restore() 840 841 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 842 []byte("mode=install\n"), 0644) 843 c.Assert(err, IsNil) 844 845 s.state.Lock() 846 s.makeMockInstalledPcGadget(c, "dangerous", "", "") 847 devicestate.SetSystemMode(s.mgr, "install") 848 s.state.Unlock() 849 850 s.settle(c) 851 852 s.state.Lock() 853 defer s.state.Unlock() 854 855 installSystem := s.findInstallSystem() 856 c.Check(installSystem.Err(), ErrorMatches, errMatch) 857 // no restart request on failure 858 c.Check(s.restartRequests, HasLen, 0) 859 } 860 861 func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoKeys(c *C) { 862 restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 863 c.Check(options.Encrypt, Equals, true) 864 // no keys set 865 return &install.InstalledSystemSideData{}, nil 866 }) 867 defer restore() 868 s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks: 869 - Setup system for run mode \(internal error: system encryption keys are unset\)`) 870 } 871 872 func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoSystemDataKey(c *C) { 873 restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 874 c.Check(options.Encrypt, Equals, true) 875 // no keys set 876 return &install.InstalledSystemSideData{ 877 // empty map 878 KeysForRoles: map[string]*install.EncryptionKeySet{}, 879 }, nil 880 }) 881 defer restore() 882 s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks: 883 - Setup system for run mode \(internal error: system encryption keys are unset\)`) 884 } 885 886 func (s *deviceMgrInstallModeSuite) mockInstallModeChange(c *C, modelGrade, gadgetDefaultsYaml string) *asserts.Model { 887 restore := release.MockOnClassic(false) 888 defer restore() 889 890 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 891 return nil, nil 892 }) 893 defer restore() 894 895 s.state.Lock() 896 mockModel := s.makeMockInstalledPcGadget(c, modelGrade, "", gadgetDefaultsYaml) 897 s.state.Unlock() 898 c.Check(mockModel.Grade(), Equals, asserts.ModelGrade(modelGrade)) 899 900 restore = devicestate.MockBootMakeSystemRunnable(func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error { 901 return nil 902 }) 903 defer restore() 904 905 modeenv := boot.Modeenv{ 906 Mode: "install", 907 RecoverySystem: "20191218", 908 } 909 c.Assert(modeenv.WriteTo(""), IsNil) 910 devicestate.SetSystemMode(s.mgr, "install") 911 912 // normally done by snap-bootstrap 913 err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755) 914 c.Assert(err, IsNil) 915 916 s.settle(c) 917 918 return mockModel 919 } 920 921 func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfig(c *C) { 922 s.mockInstallModeChange(c, "dangerous", "") 923 924 s.state.Lock() 925 defer s.state.Unlock() 926 927 // the install-system change is created 928 installSystem := s.findInstallSystem() 929 c.Assert(installSystem, NotNil) 930 931 // and was run successfully 932 c.Check(installSystem.Err(), IsNil) 933 c.Check(installSystem.Status(), Equals, state.DoneStatus) 934 935 // and sysconfig.ConfigureTargetSystem was run exactly once 936 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 937 { 938 AllowCloudInit: true, 939 TargetRootDir: boot.InstallHostWritableDir, 940 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 941 }, 942 }) 943 } 944 945 func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfigErr(c *C) { 946 s.ConfigureTargetSystemErr = fmt.Errorf("error from sysconfig.ConfigureTargetSystem") 947 s.mockInstallModeChange(c, "dangerous", "") 948 949 s.state.Lock() 950 defer s.state.Unlock() 951 952 // the install-system was run but errorred as specified in the above mock 953 installSystem := s.findInstallSystem() 954 c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks: 955 - Setup system for run mode \(error from sysconfig.ConfigureTargetSystem\)`) 956 // and sysconfig.ConfigureTargetSystem was run exactly once 957 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 958 { 959 AllowCloudInit: true, 960 TargetRootDir: boot.InstallHostWritableDir, 961 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 962 }, 963 }) 964 } 965 966 func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitInDangerous(c *C) { 967 // pretend we have a cloud-init config on the seed partition 968 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 969 err := os.MkdirAll(cloudCfg, 0755) 970 c.Assert(err, IsNil) 971 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 972 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 973 c.Assert(err, IsNil) 974 } 975 976 s.mockInstallModeChange(c, "dangerous", "") 977 978 // and did tell sysconfig about the cloud-init files 979 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 980 { 981 AllowCloudInit: true, 982 CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"), 983 TargetRootDir: boot.InstallHostWritableDir, 984 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 985 }, 986 }) 987 } 988 989 func (s *deviceMgrInstallModeSuite) TestInstallModeSignedNoUbuntuSeedCloudInit(c *C) { 990 // pretend we have a cloud-init config on the seed partition 991 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 992 err := os.MkdirAll(cloudCfg, 0755) 993 c.Assert(err, IsNil) 994 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 995 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 996 c.Assert(err, IsNil) 997 } 998 999 s.mockInstallModeChange(c, "signed", "") 1000 1001 // and did NOT tell sysconfig about the cloud-init file, but also did not 1002 // explicitly disable cloud init 1003 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1004 { 1005 AllowCloudInit: true, 1006 TargetRootDir: boot.InstallHostWritableDir, 1007 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1008 }, 1009 }) 1010 } 1011 1012 func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredGadgetCloudConfCloudInit(c *C) { 1013 // pretend we have a cloud.conf from the gadget 1014 gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/") 1015 err := os.MkdirAll(gadgetDir, 0755) 1016 c.Assert(err, IsNil) 1017 err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644) 1018 c.Assert(err, IsNil) 1019 1020 err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 1021 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 1022 }) 1023 c.Assert(err, IsNil) 1024 1025 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1026 { 1027 AllowCloudInit: true, 1028 TargetRootDir: boot.InstallHostWritableDir, 1029 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1030 }, 1031 }) 1032 } 1033 1034 func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredNoUbuntuSeedCloudInit(c *C) { 1035 // pretend we have a cloud-init config on the seed partition 1036 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 1037 err := os.MkdirAll(cloudCfg, 0755) 1038 c.Assert(err, IsNil) 1039 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 1040 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 1041 c.Assert(err, IsNil) 1042 } 1043 1044 err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 1045 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 1046 }) 1047 c.Assert(err, IsNil) 1048 1049 // and did NOT tell sysconfig about the cloud-init files, instead it was 1050 // disabled because only gadget cloud-init is allowed 1051 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1052 { 1053 AllowCloudInit: false, 1054 TargetRootDir: boot.InstallHostWritableDir, 1055 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1056 }, 1057 }) 1058 } 1059 1060 func (s *deviceMgrInstallModeSuite) TestInstallModeWritesModel(c *C) { 1061 // pretend we have a cloud-init config on the seed partition 1062 model := s.mockInstallModeChange(c, "dangerous", "") 1063 1064 var buf bytes.Buffer 1065 err := asserts.NewEncoder(&buf).Encode(model) 1066 c.Assert(err, IsNil) 1067 1068 s.state.Lock() 1069 defer s.state.Unlock() 1070 1071 installSystem := s.findInstallSystem() 1072 c.Assert(installSystem, NotNil) 1073 1074 // and was run successfully 1075 c.Check(installSystem.Err(), IsNil) 1076 c.Check(installSystem.Status(), Equals, state.DoneStatus) 1077 1078 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileEquals, buf.String()) 1079 } 1080 1081 func (s *deviceMgrInstallModeSuite) testInstallGadgetNoSave(c *C) { 1082 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 1083 []byte("mode=install\n"), 0644) 1084 c.Assert(err, IsNil) 1085 1086 s.state.Lock() 1087 s.makeMockInstalledPcGadget(c, "dangerous", "", "") 1088 info, err := snapstate.CurrentInfo(s.state, "pc") 1089 c.Assert(err, IsNil) 1090 // replace gadget yaml with one that has no ubuntu-save 1091 c.Assert(uc20gadgetYaml, Not(testutil.Contains), "ubuntu-save") 1092 err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(uc20gadgetYaml), 0644) 1093 c.Assert(err, IsNil) 1094 devicestate.SetSystemMode(s.mgr, "install") 1095 s.state.Unlock() 1096 1097 s.settle(c) 1098 } 1099 1100 func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetErr(c *C) { 1101 restore := release.MockOnClassic(false) 1102 defer restore() 1103 1104 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 1105 return nil, fmt.Errorf("unexpected call") 1106 }) 1107 defer restore() 1108 1109 // pretend we have a TPM 1110 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil }) 1111 defer restore() 1112 1113 s.testInstallGadgetNoSave(c) 1114 1115 s.state.Lock() 1116 defer s.state.Unlock() 1117 1118 installSystem := s.findInstallSystem() 1119 c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks: 1120 - Setup system for run mode \(cannot use gadget: gadget does not support encrypted data: volume "pc" has no structure with system-save role\)`) 1121 // no restart request on failure 1122 c.Check(s.restartRequests, HasLen, 0) 1123 } 1124 1125 func (s *deviceMgrInstallModeSuite) TestInstallWithoutEncryptionValidatesGadgetWithoutSaveHappy(c *C) { 1126 restore := release.MockOnClassic(false) 1127 defer restore() 1128 1129 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 1130 return nil, nil 1131 }) 1132 defer restore() 1133 1134 // pretend we have a TPM 1135 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("TPM2 not available") }) 1136 defer restore() 1137 1138 s.testInstallGadgetNoSave(c) 1139 1140 s.state.Lock() 1141 defer s.state.Unlock() 1142 1143 installSystem := s.findInstallSystem() 1144 c.Check(installSystem.Err(), IsNil) 1145 c.Check(s.restartRequests, HasLen, 1) 1146 } 1147 1148 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncrypted(c *C) { 1149 st := s.state 1150 st.Lock() 1151 defer st.Unlock() 1152 1153 mockModel := s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{ 1154 "architecture": "amd64", 1155 "kernel": "pc-kernel", 1156 "gadget": "pc", 1157 }) 1158 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1159 Brand: "canonical", 1160 Model: "pc", 1161 }) 1162 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1163 1164 for _, tc := range []struct { 1165 hasFDESetupHook bool 1166 hasTPM bool 1167 encrypt bool 1168 }{ 1169 // unhappy: no tpm, no hook 1170 {false, false, false}, 1171 // happy: either tpm or hook or both 1172 {false, true, true}, 1173 {true, false, true}, 1174 {true, true, true}, 1175 } { 1176 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1177 ctx.Lock() 1178 defer ctx.Unlock() 1179 ctx.Set("fde-setup-result", []byte(`{"features":[]}`)) 1180 return nil, nil 1181 } 1182 rhk := hookstate.MockRunHook(hookInvoke) 1183 defer rhk() 1184 1185 if tc.hasFDESetupHook { 1186 makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup) 1187 } else { 1188 makeInstalledMockKernelSnap(c, st, kernelYamlNoFdeSetup) 1189 } 1190 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { 1191 if tc.hasTPM { 1192 return nil 1193 } 1194 return fmt.Errorf("tpm says no") 1195 }) 1196 defer restore() 1197 1198 encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, st, deviceCtx) 1199 c.Assert(err, IsNil) 1200 c.Check(encrypt, Equals, tc.encrypt, Commentf("%v", tc)) 1201 } 1202 } 1203 1204 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedStorageSafety(c *C) { 1205 s.state.Lock() 1206 defer s.state.Unlock() 1207 1208 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil }) 1209 defer restore() 1210 1211 var testCases = []struct { 1212 grade, storageSafety string 1213 1214 expectedEncryption bool 1215 }{ 1216 // we don't test unset here because the assertion assembly 1217 // will ensure it has a default 1218 {"dangerous", "prefer-unencrypted", false}, 1219 {"dangerous", "prefer-encrypted", true}, 1220 {"dangerous", "encrypted", true}, 1221 {"signed", "prefer-unencrypted", false}, 1222 {"signed", "prefer-encrypted", true}, 1223 {"signed", "encrypted", true}, 1224 // secured+prefer-{,un}encrypted is an error at the 1225 // assertion level already so cannot be tested here 1226 {"secured", "encrypted", true}, 1227 } 1228 for _, tc := range testCases { 1229 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 1230 "display-name": "my model", 1231 "architecture": "amd64", 1232 "base": "core20", 1233 "grade": tc.grade, 1234 "storage-safety": tc.storageSafety, 1235 "snaps": []interface{}{ 1236 map[string]interface{}{ 1237 "name": "pc-kernel", 1238 "id": pcKernelSnapID, 1239 "type": "kernel", 1240 "default-channel": "20", 1241 }, 1242 map[string]interface{}{ 1243 "name": "pc", 1244 "id": pcSnapID, 1245 "type": "gadget", 1246 "default-channel": "20", 1247 }}, 1248 }) 1249 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1250 1251 encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1252 c.Assert(err, IsNil) 1253 c.Check(encrypt, Equals, tc.expectedEncryption) 1254 } 1255 } 1256 1257 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrors(c *C) { 1258 s.state.Lock() 1259 defer s.state.Unlock() 1260 1261 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("tpm says no") }) 1262 defer restore() 1263 1264 var testCases = []struct { 1265 grade, storageSafety string 1266 1267 expectedErr string 1268 }{ 1269 // we don't test unset here because the assertion assembly 1270 // will ensure it has a default 1271 { 1272 "dangerous", "encrypted", 1273 "cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no", 1274 }, { 1275 "signed", "encrypted", 1276 "cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no", 1277 }, { 1278 "secured", "", 1279 "cannot encrypt device storage as mandated by model grade secured: tpm says no", 1280 }, { 1281 "secured", "encrypted", 1282 "cannot encrypt device storage as mandated by model grade secured: tpm says no", 1283 }, 1284 } 1285 for _, tc := range testCases { 1286 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 1287 "display-name": "my model", 1288 "architecture": "amd64", 1289 "base": "core20", 1290 "grade": tc.grade, 1291 "storage-safety": tc.storageSafety, 1292 "snaps": []interface{}{ 1293 map[string]interface{}{ 1294 "name": "pc-kernel", 1295 "id": pcKernelSnapID, 1296 "type": "kernel", 1297 "default-channel": "20", 1298 }, 1299 map[string]interface{}{ 1300 "name": "pc", 1301 "id": pcSnapID, 1302 "type": "gadget", 1303 "default-channel": "20", 1304 }}, 1305 }) 1306 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1307 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1308 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%s %s", tc.grade, tc.storageSafety)) 1309 } 1310 } 1311 1312 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedFDEHook(c *C) { 1313 st := s.state 1314 st.Lock() 1315 defer st.Unlock() 1316 1317 s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{ 1318 "architecture": "amd64", 1319 "kernel": "pc-kernel", 1320 "gadget": "pc", 1321 }) 1322 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1323 Brand: "canonical", 1324 Model: "pc", 1325 }) 1326 makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup) 1327 1328 for _, tc := range []struct { 1329 hookOutput string 1330 expectedErr string 1331 }{ 1332 // invalid json 1333 {"xxx", `cannot parse hook output "xxx": invalid character 'x' looking for beginning of value`}, 1334 // no output is invalid 1335 {"", `cannot parse hook output "": unexpected end of JSON input`}, 1336 // specific error 1337 {`{"error":"failed"}`, `cannot use hook: it returned error: failed`}, 1338 {`{}`, `cannot use hook: neither "features" nor "error" returned`}, 1339 // valid 1340 {`{"features":[]}`, ""}, 1341 {`{"features":["a"]}`, ""}, 1342 {`{"features":["a","b"]}`, ""}, 1343 // features must be list of strings 1344 {`{"features":[1]}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`}, 1345 {`{"features":1}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`}, 1346 {`{"features":"1"}`, `cannot parse hook output ".*": json: cannot unmarshal string into Go struct.*`}, 1347 } { 1348 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1349 ctx.Lock() 1350 defer ctx.Unlock() 1351 ctx.Set("fde-setup-result", []byte(tc.hookOutput)) 1352 return nil, nil 1353 } 1354 rhk := hookstate.MockRunHook(hookInvoke) 1355 defer rhk() 1356 1357 err := devicestate.DeviceManagerCheckFDEFeatures(s.mgr, st) 1358 if tc.expectedErr != "" { 1359 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc)) 1360 } else { 1361 c.Check(err, IsNil, Commentf("%v", tc)) 1362 } 1363 } 1364 } 1365 1366 var checkEncryptionModelHeaders = map[string]interface{}{ 1367 "display-name": "my model", 1368 "architecture": "amd64", 1369 "base": "core20", 1370 "grade": "dangerous", 1371 "snaps": []interface{}{ 1372 map[string]interface{}{ 1373 "name": "pc-kernel", 1374 "id": pcKernelSnapID, 1375 "type": "kernel", 1376 "default-channel": "20", 1377 }, 1378 map[string]interface{}{ 1379 "name": "pc", 1380 "id": pcSnapID, 1381 "type": "gadget", 1382 "default-channel": "20", 1383 }}, 1384 } 1385 1386 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsTPM(c *C) { 1387 s.state.Lock() 1388 defer s.state.Unlock() 1389 1390 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { 1391 return fmt.Errorf("tpm says no") 1392 }) 1393 defer restore() 1394 1395 logbuf, restore := logger.MockLogger() 1396 defer restore() 1397 1398 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders) 1399 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1400 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1401 c.Check(err, IsNil) 1402 c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as checking TPM gave: tpm says no\n") 1403 } 1404 1405 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsHook(c *C) { 1406 s.state.Lock() 1407 defer s.state.Unlock() 1408 1409 logbuf, restore := logger.MockLogger() 1410 defer restore() 1411 1412 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders) 1413 // mock kernel installed but no hook or handle so checkEncryption 1414 // will fail 1415 makeInstalledMockKernelSnap(c, s.state, kernelYamlWithFdeSetup) 1416 1417 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1418 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1419 c.Check(err, IsNil) 1420 c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as querying kernel fde-setup hook did not succeed:.*\n") 1421 }