github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/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) TestInstallModeSupportsCloudInitGadgetOnlyInSigned(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 // we also have gadget cloud init too 1000 gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/") 1001 err = os.MkdirAll(gadgetDir, 0755) 1002 c.Assert(err, IsNil) 1003 err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644) 1004 c.Assert(err, IsNil) 1005 1006 s.mockInstallModeChange(c, "signed", "") 1007 1008 // we didn't tell sysconfig about the ubuntu-seed cloud-init files, just 1009 // the gadget ones 1010 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1011 { 1012 AllowCloudInit: true, 1013 TargetRootDir: boot.InstallHostWritableDir, 1014 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1015 }, 1016 }) 1017 } 1018 1019 func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitBothGadgetAndUbuntuSeedInDangerous(c *C) { 1020 // pretend we have a cloud-init config on the seed partition 1021 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 1022 err := os.MkdirAll(cloudCfg, 0755) 1023 c.Assert(err, IsNil) 1024 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 1025 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 1026 c.Assert(err, IsNil) 1027 } 1028 1029 // we also have gadget cloud init too 1030 gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/") 1031 err = os.MkdirAll(gadgetDir, 0755) 1032 c.Assert(err, IsNil) 1033 err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644) 1034 c.Assert(err, IsNil) 1035 1036 s.mockInstallModeChange(c, "dangerous", "") 1037 1038 // and did tell sysconfig about the cloud-init files 1039 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1040 { 1041 AllowCloudInit: true, 1042 CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"), 1043 TargetRootDir: boot.InstallHostWritableDir, 1044 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1045 }, 1046 }) 1047 } 1048 1049 func (s *deviceMgrInstallModeSuite) TestInstallModeSignedNoUbuntuSeedCloudInit(c *C) { 1050 // pretend we have a cloud-init config on the seed partition 1051 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 1052 err := os.MkdirAll(cloudCfg, 0755) 1053 c.Assert(err, IsNil) 1054 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 1055 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 1056 c.Assert(err, IsNil) 1057 } 1058 1059 s.mockInstallModeChange(c, "signed", "") 1060 1061 // and did NOT tell sysconfig about the cloud-init file, but also did not 1062 // explicitly disable cloud init 1063 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1064 { 1065 AllowCloudInit: true, 1066 TargetRootDir: boot.InstallHostWritableDir, 1067 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1068 }, 1069 }) 1070 } 1071 1072 func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredGadgetCloudConfCloudInit(c *C) { 1073 // pretend we have a cloud.conf from the gadget 1074 gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/") 1075 err := os.MkdirAll(gadgetDir, 0755) 1076 c.Assert(err, IsNil) 1077 err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644) 1078 c.Assert(err, IsNil) 1079 1080 err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 1081 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 1082 }) 1083 c.Assert(err, IsNil) 1084 1085 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1086 { 1087 AllowCloudInit: true, 1088 TargetRootDir: boot.InstallHostWritableDir, 1089 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1090 }, 1091 }) 1092 } 1093 1094 func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredNoUbuntuSeedCloudInit(c *C) { 1095 // pretend we have a cloud-init config on the seed partition 1096 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 1097 err := os.MkdirAll(cloudCfg, 0755) 1098 c.Assert(err, IsNil) 1099 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 1100 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 1101 c.Assert(err, IsNil) 1102 } 1103 1104 err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 1105 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 1106 }) 1107 c.Assert(err, IsNil) 1108 1109 // and did NOT tell sysconfig about the cloud-init files, instead it was 1110 // disabled because only gadget cloud-init is allowed 1111 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 1112 { 1113 AllowCloudInit: false, 1114 TargetRootDir: boot.InstallHostWritableDir, 1115 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 1116 }, 1117 }) 1118 } 1119 1120 func (s *deviceMgrInstallModeSuite) TestInstallModeWritesModel(c *C) { 1121 // pretend we have a cloud-init config on the seed partition 1122 model := s.mockInstallModeChange(c, "dangerous", "") 1123 1124 var buf bytes.Buffer 1125 err := asserts.NewEncoder(&buf).Encode(model) 1126 c.Assert(err, IsNil) 1127 1128 s.state.Lock() 1129 defer s.state.Unlock() 1130 1131 installSystem := s.findInstallSystem() 1132 c.Assert(installSystem, NotNil) 1133 1134 // and was run successfully 1135 c.Check(installSystem.Err(), IsNil) 1136 c.Check(installSystem.Status(), Equals, state.DoneStatus) 1137 1138 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileEquals, buf.String()) 1139 } 1140 1141 func (s *deviceMgrInstallModeSuite) testInstallGadgetNoSave(c *C) { 1142 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 1143 []byte("mode=install\n"), 0644) 1144 c.Assert(err, IsNil) 1145 1146 s.state.Lock() 1147 s.makeMockInstalledPcGadget(c, "dangerous", "", "") 1148 info, err := snapstate.CurrentInfo(s.state, "pc") 1149 c.Assert(err, IsNil) 1150 // replace gadget yaml with one that has no ubuntu-save 1151 c.Assert(uc20gadgetYaml, Not(testutil.Contains), "ubuntu-save") 1152 err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(uc20gadgetYaml), 0644) 1153 c.Assert(err, IsNil) 1154 devicestate.SetSystemMode(s.mgr, "install") 1155 s.state.Unlock() 1156 1157 s.settle(c) 1158 } 1159 1160 func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetErr(c *C) { 1161 restore := release.MockOnClassic(false) 1162 defer restore() 1163 1164 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 1165 return nil, fmt.Errorf("unexpected call") 1166 }) 1167 defer restore() 1168 1169 // pretend we have a TPM 1170 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil }) 1171 defer restore() 1172 1173 s.testInstallGadgetNoSave(c) 1174 1175 s.state.Lock() 1176 defer s.state.Unlock() 1177 1178 installSystem := s.findInstallSystem() 1179 c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks: 1180 - Setup system for run mode \(cannot use gadget: gadget does not support encrypted data: volume "pc" has no structure with system-save role\)`) 1181 // no restart request on failure 1182 c.Check(s.restartRequests, HasLen, 0) 1183 } 1184 1185 func (s *deviceMgrInstallModeSuite) TestInstallWithoutEncryptionValidatesGadgetWithoutSaveHappy(c *C) { 1186 restore := release.MockOnClassic(false) 1187 defer restore() 1188 1189 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 1190 return nil, nil 1191 }) 1192 defer restore() 1193 1194 // pretend we have a TPM 1195 restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("TPM2 not available") }) 1196 defer restore() 1197 1198 s.testInstallGadgetNoSave(c) 1199 1200 s.state.Lock() 1201 defer s.state.Unlock() 1202 1203 installSystem := s.findInstallSystem() 1204 c.Check(installSystem.Err(), IsNil) 1205 c.Check(s.restartRequests, HasLen, 1) 1206 } 1207 1208 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncrypted(c *C) { 1209 st := s.state 1210 st.Lock() 1211 defer st.Unlock() 1212 1213 mockModel := s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{ 1214 "architecture": "amd64", 1215 "kernel": "pc-kernel", 1216 "gadget": "pc", 1217 }) 1218 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1219 Brand: "canonical", 1220 Model: "pc", 1221 }) 1222 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1223 1224 for _, tc := range []struct { 1225 hasFDESetupHook bool 1226 hasTPM bool 1227 encrypt bool 1228 }{ 1229 // unhappy: no tpm, no hook 1230 {false, false, false}, 1231 // happy: either tpm or hook or both 1232 {false, true, true}, 1233 {true, false, true}, 1234 {true, true, true}, 1235 } { 1236 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1237 ctx.Lock() 1238 defer ctx.Unlock() 1239 ctx.Set("fde-setup-result", []byte(`{"features":[]}`)) 1240 return nil, nil 1241 } 1242 rhk := hookstate.MockRunHook(hookInvoke) 1243 defer rhk() 1244 1245 if tc.hasFDESetupHook { 1246 makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup) 1247 } else { 1248 makeInstalledMockKernelSnap(c, st, kernelYamlNoFdeSetup) 1249 } 1250 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { 1251 if tc.hasTPM { 1252 return nil 1253 } 1254 return fmt.Errorf("tpm says no") 1255 }) 1256 defer restore() 1257 1258 encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, st, deviceCtx) 1259 c.Assert(err, IsNil) 1260 c.Check(encrypt, Equals, tc.encrypt, Commentf("%v", tc)) 1261 } 1262 } 1263 1264 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedStorageSafety(c *C) { 1265 s.state.Lock() 1266 defer s.state.Unlock() 1267 1268 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil }) 1269 defer restore() 1270 1271 var testCases = []struct { 1272 grade, storageSafety string 1273 1274 expectedEncryption bool 1275 }{ 1276 // we don't test unset here because the assertion assembly 1277 // will ensure it has a default 1278 {"dangerous", "prefer-unencrypted", false}, 1279 {"dangerous", "prefer-encrypted", true}, 1280 {"dangerous", "encrypted", true}, 1281 {"signed", "prefer-unencrypted", false}, 1282 {"signed", "prefer-encrypted", true}, 1283 {"signed", "encrypted", true}, 1284 // secured+prefer-{,un}encrypted is an error at the 1285 // assertion level already so cannot be tested here 1286 {"secured", "encrypted", true}, 1287 } 1288 for _, tc := range testCases { 1289 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 1290 "display-name": "my model", 1291 "architecture": "amd64", 1292 "base": "core20", 1293 "grade": tc.grade, 1294 "storage-safety": tc.storageSafety, 1295 "snaps": []interface{}{ 1296 map[string]interface{}{ 1297 "name": "pc-kernel", 1298 "id": pcKernelSnapID, 1299 "type": "kernel", 1300 "default-channel": "20", 1301 }, 1302 map[string]interface{}{ 1303 "name": "pc", 1304 "id": pcSnapID, 1305 "type": "gadget", 1306 "default-channel": "20", 1307 }}, 1308 }) 1309 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1310 1311 encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1312 c.Assert(err, IsNil) 1313 c.Check(encrypt, Equals, tc.expectedEncryption) 1314 } 1315 } 1316 1317 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrors(c *C) { 1318 s.state.Lock() 1319 defer s.state.Unlock() 1320 1321 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("tpm says no") }) 1322 defer restore() 1323 1324 var testCases = []struct { 1325 grade, storageSafety string 1326 1327 expectedErr string 1328 }{ 1329 // we don't test unset here because the assertion assembly 1330 // will ensure it has a default 1331 { 1332 "dangerous", "encrypted", 1333 "cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no", 1334 }, { 1335 "signed", "encrypted", 1336 "cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no", 1337 }, { 1338 "secured", "", 1339 "cannot encrypt device storage as mandated by model grade secured: tpm says no", 1340 }, { 1341 "secured", "encrypted", 1342 "cannot encrypt device storage as mandated by model grade secured: tpm says no", 1343 }, 1344 } 1345 for _, tc := range testCases { 1346 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 1347 "display-name": "my model", 1348 "architecture": "amd64", 1349 "base": "core20", 1350 "grade": tc.grade, 1351 "storage-safety": tc.storageSafety, 1352 "snaps": []interface{}{ 1353 map[string]interface{}{ 1354 "name": "pc-kernel", 1355 "id": pcKernelSnapID, 1356 "type": "kernel", 1357 "default-channel": "20", 1358 }, 1359 map[string]interface{}{ 1360 "name": "pc", 1361 "id": pcSnapID, 1362 "type": "gadget", 1363 "default-channel": "20", 1364 }}, 1365 }) 1366 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1367 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1368 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%s %s", tc.grade, tc.storageSafety)) 1369 } 1370 } 1371 1372 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedFDEHook(c *C) { 1373 st := s.state 1374 st.Lock() 1375 defer st.Unlock() 1376 1377 s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{ 1378 "architecture": "amd64", 1379 "kernel": "pc-kernel", 1380 "gadget": "pc", 1381 }) 1382 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 1383 Brand: "canonical", 1384 Model: "pc", 1385 }) 1386 makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup) 1387 1388 for _, tc := range []struct { 1389 hookOutput string 1390 expectedErr string 1391 }{ 1392 // invalid json 1393 {"xxx", `cannot parse hook output "xxx": invalid character 'x' looking for beginning of value`}, 1394 // no output is invalid 1395 {"", `cannot parse hook output "": unexpected end of JSON input`}, 1396 // specific error 1397 {`{"error":"failed"}`, `cannot use hook: it returned error: failed`}, 1398 {`{}`, `cannot use hook: neither "features" nor "error" returned`}, 1399 // valid 1400 {`{"features":[]}`, ""}, 1401 {`{"features":["a"]}`, ""}, 1402 {`{"features":["a","b"]}`, ""}, 1403 // features must be list of strings 1404 {`{"features":[1]}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`}, 1405 {`{"features":1}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`}, 1406 {`{"features":"1"}`, `cannot parse hook output ".*": json: cannot unmarshal string into Go struct.*`}, 1407 } { 1408 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1409 ctx.Lock() 1410 defer ctx.Unlock() 1411 ctx.Set("fde-setup-result", []byte(tc.hookOutput)) 1412 return nil, nil 1413 } 1414 rhk := hookstate.MockRunHook(hookInvoke) 1415 defer rhk() 1416 1417 err := devicestate.DeviceManagerCheckFDEFeatures(s.mgr, st) 1418 if tc.expectedErr != "" { 1419 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc)) 1420 } else { 1421 c.Check(err, IsNil, Commentf("%v", tc)) 1422 } 1423 } 1424 } 1425 1426 var checkEncryptionModelHeaders = map[string]interface{}{ 1427 "display-name": "my model", 1428 "architecture": "amd64", 1429 "base": "core20", 1430 "grade": "dangerous", 1431 "snaps": []interface{}{ 1432 map[string]interface{}{ 1433 "name": "pc-kernel", 1434 "id": pcKernelSnapID, 1435 "type": "kernel", 1436 "default-channel": "20", 1437 }, 1438 map[string]interface{}{ 1439 "name": "pc", 1440 "id": pcSnapID, 1441 "type": "gadget", 1442 "default-channel": "20", 1443 }}, 1444 } 1445 1446 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsTPM(c *C) { 1447 s.state.Lock() 1448 defer s.state.Unlock() 1449 1450 restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { 1451 return fmt.Errorf("tpm says no") 1452 }) 1453 defer restore() 1454 1455 logbuf, restore := logger.MockLogger() 1456 defer restore() 1457 1458 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders) 1459 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1460 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1461 c.Check(err, IsNil) 1462 c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as checking TPM gave: tpm says no\n") 1463 } 1464 1465 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsHook(c *C) { 1466 s.state.Lock() 1467 defer s.state.Unlock() 1468 1469 logbuf, restore := logger.MockLogger() 1470 defer restore() 1471 1472 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders) 1473 // mock kernel installed but no hook or handle so checkEncryption 1474 // will fail 1475 makeInstalledMockKernelSnap(c, s.state, kernelYamlWithFdeSetup) 1476 1477 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1478 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1479 c.Check(err, IsNil) 1480 c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as querying kernel fde-setup hook did not succeed:.*\n") 1481 }