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