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