github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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 94 const ( 95 pcSnapID = "pcididididididididididididididid" 96 pcKernelSnapID = "pckernelidididididididididididid" 97 core20SnapID = "core20ididididididididididididid" 98 ) 99 100 func (s *deviceMgrInstallModeSuite) makeMockInstalledPcGadget(c *C, grade, gadgetDefaultsYaml string) *asserts.Model { 101 si := &snap.SideInfo{ 102 RealName: "pc-kernel", 103 Revision: snap.R(1), 104 SnapID: pcKernelSnapID, 105 } 106 snapstate.Set(s.state, "pc-kernel", &snapstate.SnapState{ 107 SnapType: "kernel", 108 Sequence: []*snap.SideInfo{si}, 109 Current: si.Revision, 110 Active: true, 111 }) 112 kernelInfo := snaptest.MockSnapWithFiles(c, "name: pc-kernel\ntype: kernel", si, nil) 113 kernelFn := snaptest.MakeTestSnapWithFiles(c, "name: pc-kernel\ntype: kernel\nversion: 1.0", nil) 114 err := os.Rename(kernelFn, kernelInfo.MountFile()) 115 c.Assert(err, IsNil) 116 117 si = &snap.SideInfo{ 118 RealName: "pc", 119 Revision: snap.R(1), 120 SnapID: pcSnapID, 121 } 122 snapstate.Set(s.state, "pc", &snapstate.SnapState{ 123 SnapType: "gadget", 124 Sequence: []*snap.SideInfo{si}, 125 Current: si.Revision, 126 Active: true, 127 }) 128 snaptest.MockSnapWithFiles(c, "name: pc\ntype: gadget", si, [][]string{ 129 {"meta/gadget.yaml", uc20gadgetYamlWithSave + gadgetDefaultsYaml}, 130 }) 131 132 si = &snap.SideInfo{ 133 RealName: "core20", 134 Revision: snap.R(2), 135 SnapID: core20SnapID, 136 } 137 snapstate.Set(s.state, "core20", &snapstate.SnapState{ 138 SnapType: "base", 139 Sequence: []*snap.SideInfo{si}, 140 Current: si.Revision, 141 Active: true, 142 }) 143 snaptest.MockSnapWithFiles(c, "name: core20\ntype: base", si, nil) 144 145 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 146 "display-name": "my model", 147 "architecture": "amd64", 148 "base": "core20", 149 "grade": grade, 150 "snaps": []interface{}{ 151 map[string]interface{}{ 152 "name": "pc-kernel", 153 "id": pcKernelSnapID, 154 "type": "kernel", 155 "default-channel": "20", 156 }, 157 map[string]interface{}{ 158 "name": "pc", 159 "id": pcSnapID, 160 "type": "gadget", 161 "default-channel": "20", 162 }}, 163 }) 164 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 165 Brand: "my-brand", 166 Model: "my-model", 167 // no serial in install mode 168 }) 169 170 return mockModel 171 } 172 173 type encTestCase struct { 174 tpm bool 175 bypass bool 176 encrypt bool 177 trustedBootloader bool 178 } 179 180 var ( 181 dataEncryptionKey = secboot.EncryptionKey{'d', 'a', 't', 'a', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 182 dataRecoveryKey = secboot.RecoveryKey{'r', 'e', 'c', 'o', 'v', 'e', 'r', 'y', 10, 11, 12, 13, 14, 15, 16, 17} 183 184 saveKey = secboot.EncryptionKey{'s', 'a', 'v', 'e', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} 185 reinstallKey = secboot.RecoveryKey{'r', 'e', 'i', 'n', 's', 't', 'a', 'l', 'l', 11, 12, 13, 14, 15, 16, 17} 186 ) 187 188 func (s *deviceMgrInstallModeSuite) doRunChangeTestWithEncryption(c *C, grade string, tc encTestCase) error { 189 restore := release.MockOnClassic(false) 190 defer restore() 191 bootloaderRootdir := c.MkDir() 192 193 var brGadgetRoot, brDevice string 194 var brOpts install.Options 195 var installRunCalled int 196 var installSealingObserver gadget.ContentObserver 197 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, device string, options install.Options, obs gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 198 // ensure we can grab the lock here, i.e. that it's not taken 199 s.state.Lock() 200 s.state.Unlock() 201 202 c.Check(mod.Grade(), Equals, asserts.ModelGrade(grade)) 203 204 brGadgetRoot = gadgetRoot 205 brDevice = device 206 brOpts = options 207 installSealingObserver = obs 208 installRunCalled++ 209 var keysForRoles map[string]*install.EncryptionKeySet 210 if tc.encrypt { 211 keysForRoles = map[string]*install.EncryptionKeySet{ 212 gadget.SystemData: { 213 Key: dataEncryptionKey, 214 RecoveryKey: dataRecoveryKey, 215 }, 216 gadget.SystemSave: { 217 Key: saveKey, 218 RecoveryKey: reinstallKey, 219 }, 220 } 221 } 222 return &install.InstalledSystemSideData{ 223 KeysForRoles: keysForRoles, 224 }, nil 225 }) 226 defer restore() 227 228 restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { 229 if tc.tpm { 230 return nil 231 } else { 232 return fmt.Errorf("TPM not available") 233 } 234 }) 235 defer restore() 236 237 if tc.trustedBootloader { 238 tab := bootloadertest.Mock("trusted", bootloaderRootdir).WithTrustedAssets() 239 tab.TrustedAssetsList = []string{"trusted-asset"} 240 bootloader.Force(tab) 241 s.AddCleanup(func() { bootloader.Force(nil) }) 242 243 err := os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755) 244 c.Assert(err, IsNil) 245 err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "trusted-asset"), nil, 0644) 246 c.Assert(err, IsNil) 247 } 248 249 s.state.Lock() 250 mockModel := s.makeMockInstalledPcGadget(c, grade, "") 251 s.state.Unlock() 252 253 bypassEncryptionPath := filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted") 254 if tc.bypass { 255 err := os.MkdirAll(filepath.Dir(bypassEncryptionPath), 0755) 256 c.Assert(err, IsNil) 257 f, err := os.Create(bypassEncryptionPath) 258 c.Assert(err, IsNil) 259 f.Close() 260 } else { 261 os.RemoveAll(bypassEncryptionPath) 262 } 263 264 bootMakeBootableCalled := 0 265 restore = devicestate.MockBootMakeBootable(func(model *asserts.Model, rootdir string, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error { 266 c.Check(model, DeepEquals, mockModel) 267 c.Check(rootdir, Equals, dirs.GlobalRootDir) 268 c.Check(bootWith.KernelPath, Matches, ".*/var/lib/snapd/snaps/pc-kernel_1.snap") 269 c.Check(bootWith.BasePath, Matches, ".*/var/lib/snapd/snaps/core20_2.snap") 270 c.Check(bootWith.RecoverySystemDir, Matches, "/systems/20191218") 271 c.Check(bootWith.UnpackedGadgetDir, Equals, filepath.Join(dirs.SnapMountDir, "pc/1")) 272 if tc.encrypt { 273 c.Check(seal, NotNil) 274 } else { 275 c.Check(seal, IsNil) 276 } 277 bootMakeBootableCalled++ 278 return nil 279 }) 280 defer restore() 281 282 modeenv := boot.Modeenv{ 283 Mode: "install", 284 RecoverySystem: "20191218", 285 } 286 c.Assert(modeenv.WriteTo(""), IsNil) 287 devicestate.SetSystemMode(s.mgr, "install") 288 289 // normally done by snap-bootstrap 290 err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755) 291 c.Assert(err, IsNil) 292 293 s.settle(c) 294 295 // the install-system change is created 296 s.state.Lock() 297 defer s.state.Unlock() 298 installSystem := s.findInstallSystem() 299 c.Assert(installSystem, NotNil) 300 301 // and was run successfully 302 if err := installSystem.Err(); err != nil { 303 // we failed, no further checks needed 304 return err 305 } 306 307 c.Assert(installSystem.Status(), Equals, state.DoneStatus) 308 309 // in the right way 310 c.Assert(brGadgetRoot, Equals, filepath.Join(dirs.SnapMountDir, "/pc/1")) 311 c.Assert(brDevice, Equals, "") 312 if tc.encrypt { 313 c.Assert(brOpts, DeepEquals, install.Options{ 314 Mount: true, 315 Encrypt: true, 316 }) 317 } else { 318 c.Assert(brOpts, DeepEquals, install.Options{ 319 Mount: true, 320 }) 321 } 322 if tc.encrypt { 323 // inteface is not nil 324 c.Assert(installSealingObserver, NotNil) 325 // we expect a very specific type 326 trustedInstallObserver, ok := installSealingObserver.(*boot.TrustedAssetsInstallObserver) 327 c.Assert(ok, Equals, true, Commentf("unexpected type: %T", installSealingObserver)) 328 c.Assert(trustedInstallObserver, NotNil) 329 } else { 330 c.Assert(installSealingObserver, IsNil) 331 } 332 333 c.Assert(installRunCalled, Equals, 1) 334 c.Assert(bootMakeBootableCalled, Equals, 1) 335 c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) 336 337 return nil 338 } 339 340 func (s *deviceMgrInstallModeSuite) TestInstallTaskErrors(c *C) { 341 restore := release.MockOnClassic(false) 342 defer restore() 343 344 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 345 return nil, fmt.Errorf("The horror, The horror") 346 }) 347 defer restore() 348 349 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 350 []byte("mode=install\n"), 0644) 351 c.Assert(err, IsNil) 352 353 s.state.Lock() 354 s.makeMockInstalledPcGadget(c, "dangerous", "") 355 devicestate.SetSystemMode(s.mgr, "install") 356 s.state.Unlock() 357 358 s.settle(c) 359 360 s.state.Lock() 361 defer s.state.Unlock() 362 363 installSystem := s.findInstallSystem() 364 c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks: 365 - Setup system for run mode \(cannot install system: The horror, The horror\)`) 366 // no restart request on failure 367 c.Check(s.restartRequests, HasLen, 0) 368 } 369 370 func (s *deviceMgrInstallModeSuite) TestInstallModeNotInstallmodeNoChg(c *C) { 371 restore := release.MockOnClassic(false) 372 defer restore() 373 374 s.state.Lock() 375 devicestate.SetSystemMode(s.mgr, "") 376 s.state.Unlock() 377 378 s.settle(c) 379 380 s.state.Lock() 381 defer s.state.Unlock() 382 383 // the install-system change is *not* created (not in install mode) 384 installSystem := s.findInstallSystem() 385 c.Assert(installSystem, IsNil) 386 } 387 388 func (s *deviceMgrInstallModeSuite) TestInstallModeNotClassic(c *C) { 389 restore := release.MockOnClassic(true) 390 defer restore() 391 392 s.state.Lock() 393 devicestate.SetSystemMode(s.mgr, "install") 394 s.state.Unlock() 395 396 s.settle(c) 397 398 s.state.Lock() 399 defer s.state.Unlock() 400 401 // the install-system change is *not* created (we're on classic) 402 installSystem := s.findInstallSystem() 403 c.Assert(installSystem, IsNil) 404 } 405 406 func (s *deviceMgrInstallModeSuite) TestInstallDangerous(c *C) { 407 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: false, encrypt: false}) 408 c.Assert(err, IsNil) 409 } 410 411 func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPM(c *C) { 412 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{ 413 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 414 }) 415 c.Assert(err, IsNil) 416 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 417 } 418 419 func (s *deviceMgrInstallModeSuite) TestInstallDangerousBypassEncryption(c *C) { 420 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: true, encrypt: false}) 421 c.Assert(err, IsNil) 422 } 423 424 func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPMBypassEncryption(c *C) { 425 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: true, bypass: true, encrypt: false}) 426 c.Assert(err, IsNil) 427 } 428 429 func (s *deviceMgrInstallModeSuite) TestInstallSigned(c *C) { 430 err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: false, encrypt: false}) 431 c.Assert(err, IsNil) 432 } 433 434 func (s *deviceMgrInstallModeSuite) TestInstallSignedWithTPM(c *C) { 435 err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{ 436 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 437 }) 438 c.Assert(err, IsNil) 439 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 440 } 441 442 func (s *deviceMgrInstallModeSuite) TestInstallSignedBypassEncryption(c *C) { 443 err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: true, encrypt: false}) 444 c.Assert(err, IsNil) 445 } 446 447 func (s *deviceMgrInstallModeSuite) TestInstallSecured(c *C) { 448 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: false, encrypt: false}) 449 c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*") 450 } 451 452 func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPM(c *C) { 453 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 454 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 455 }) 456 c.Assert(err, IsNil) 457 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 458 } 459 460 func (s *deviceMgrInstallModeSuite) TestInstallDangerousEncryptionWithTPMNoTrustedAssets(c *C) { 461 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{ 462 tpm: true, bypass: false, encrypt: true, trustedBootloader: false, 463 }) 464 c.Assert(err, IsNil) 465 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 466 } 467 468 func (s *deviceMgrInstallModeSuite) TestInstallDangerousNoEncryptionWithTrustedAssets(c *C) { 469 err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{ 470 tpm: false, bypass: false, encrypt: false, trustedBootloader: true, 471 }) 472 c.Assert(err, IsNil) 473 } 474 475 func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPMAndSave(c *C) { 476 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 477 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 478 }) 479 c.Assert(err, IsNil) 480 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:]) 481 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key"), testutil.FileEquals, saveKey[:]) 482 c.Check(filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key"), testutil.FileEquals, reinstallKey[:]) 483 marker, err := ioutil.ReadFile(filepath.Join(boot.InstallHostFDEDataDir, "marker")) 484 c.Assert(err, IsNil) 485 c.Check(marker, HasLen, 32) 486 c.Check(filepath.Join(boot.InstallHostFDESaveDir, "marker"), testutil.FileEquals, marker) 487 } 488 489 func (s *deviceMgrInstallModeSuite) TestInstallSecuredBypassEncryption(c *C) { 490 err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: true, encrypt: false}) 491 c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*") 492 } 493 494 func (s *deviceMgrInstallModeSuite) testInstallEncryptionSanityChecks(c *C, errMatch string) { 495 restore := release.MockOnClassic(false) 496 defer restore() 497 498 restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { return nil }) 499 defer restore() 500 501 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 502 []byte("mode=install\n"), 0644) 503 c.Assert(err, IsNil) 504 505 s.state.Lock() 506 s.makeMockInstalledPcGadget(c, "dangerous", "") 507 devicestate.SetSystemMode(s.mgr, "install") 508 s.state.Unlock() 509 510 s.settle(c) 511 512 s.state.Lock() 513 defer s.state.Unlock() 514 515 installSystem := s.findInstallSystem() 516 c.Check(installSystem.Err(), ErrorMatches, errMatch) 517 // no restart request on failure 518 c.Check(s.restartRequests, HasLen, 0) 519 } 520 521 func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoKeys(c *C) { 522 restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 523 c.Check(options.Encrypt, Equals, true) 524 // no keys set 525 return &install.InstalledSystemSideData{}, nil 526 }) 527 defer restore() 528 s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks: 529 - Setup system for run mode \(internal error: system encryption keys are unset\)`) 530 } 531 532 func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoSystemDataKey(c *C) { 533 restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 534 c.Check(options.Encrypt, Equals, true) 535 // no keys set 536 return &install.InstalledSystemSideData{ 537 // empty map 538 KeysForRoles: map[string]*install.EncryptionKeySet{}, 539 }, nil 540 }) 541 defer restore() 542 s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks: 543 - Setup system for run mode \(internal error: system encryption keys are unset\)`) 544 } 545 546 func (s *deviceMgrInstallModeSuite) mockInstallModeChange(c *C, modelGrade, gadgetDefaultsYaml string) *asserts.Model { 547 restore := release.MockOnClassic(false) 548 defer restore() 549 550 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 551 return nil, nil 552 }) 553 defer restore() 554 555 s.state.Lock() 556 mockModel := s.makeMockInstalledPcGadget(c, modelGrade, gadgetDefaultsYaml) 557 s.state.Unlock() 558 c.Check(mockModel.Grade(), Equals, asserts.ModelGrade(modelGrade)) 559 560 restore = devicestate.MockBootMakeBootable(func(model *asserts.Model, rootdir string, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error { 561 return nil 562 }) 563 defer restore() 564 565 modeenv := boot.Modeenv{ 566 Mode: "install", 567 RecoverySystem: "20191218", 568 } 569 c.Assert(modeenv.WriteTo(""), IsNil) 570 devicestate.SetSystemMode(s.mgr, "install") 571 572 // normally done by snap-bootstrap 573 err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755) 574 c.Assert(err, IsNil) 575 576 s.settle(c) 577 578 return mockModel 579 } 580 581 func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfig(c *C) { 582 s.mockInstallModeChange(c, "dangerous", "") 583 584 s.state.Lock() 585 defer s.state.Unlock() 586 587 // the install-system change is created 588 installSystem := s.findInstallSystem() 589 c.Assert(installSystem, NotNil) 590 591 // and was run successfully 592 c.Check(installSystem.Err(), IsNil) 593 c.Check(installSystem.Status(), Equals, state.DoneStatus) 594 595 // and sysconfig.ConfigureTargetSystem was run exactly once 596 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 597 { 598 AllowCloudInit: true, 599 TargetRootDir: boot.InstallHostWritableDir, 600 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 601 }, 602 }) 603 } 604 605 func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfigErr(c *C) { 606 s.ConfigureTargetSystemErr = fmt.Errorf("error from sysconfig.ConfigureTargetSystem") 607 s.mockInstallModeChange(c, "dangerous", "") 608 609 s.state.Lock() 610 defer s.state.Unlock() 611 612 // the install-system was run but errorred as specified in the above mock 613 installSystem := s.findInstallSystem() 614 c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks: 615 - Setup system for run mode \(error from sysconfig.ConfigureTargetSystem\)`) 616 // and sysconfig.ConfigureTargetSystem was run exactly once 617 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 618 { 619 AllowCloudInit: true, 620 TargetRootDir: boot.InstallHostWritableDir, 621 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 622 }, 623 }) 624 } 625 626 func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitInDangerous(c *C) { 627 // pretend we have a cloud-init config on the seed partition 628 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 629 err := os.MkdirAll(cloudCfg, 0755) 630 c.Assert(err, IsNil) 631 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 632 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 633 c.Assert(err, IsNil) 634 } 635 636 s.mockInstallModeChange(c, "dangerous", "") 637 638 // and did tell sysconfig about the cloud-init files 639 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 640 { 641 AllowCloudInit: true, 642 CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"), 643 TargetRootDir: boot.InstallHostWritableDir, 644 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 645 }, 646 }) 647 } 648 649 func (s *deviceMgrInstallModeSuite) TestInstallModeSignedNoUbuntuSeedCloudInit(c *C) { 650 // pretend we have a cloud-init config on the seed partition 651 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 652 err := os.MkdirAll(cloudCfg, 0755) 653 c.Assert(err, IsNil) 654 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 655 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 656 c.Assert(err, IsNil) 657 } 658 659 s.mockInstallModeChange(c, "signed", "") 660 661 // and did NOT tell sysconfig about the cloud-init file, but also did not 662 // explicitly disable cloud init 663 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 664 { 665 AllowCloudInit: true, 666 TargetRootDir: boot.InstallHostWritableDir, 667 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 668 }, 669 }) 670 } 671 672 func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredGadgetCloudConfCloudInit(c *C) { 673 // pretend we have a cloud.conf from the gadget 674 gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/") 675 err := os.MkdirAll(gadgetDir, 0755) 676 c.Assert(err, IsNil) 677 err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644) 678 c.Assert(err, IsNil) 679 680 err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 681 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 682 }) 683 c.Assert(err, IsNil) 684 685 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 686 { 687 AllowCloudInit: true, 688 TargetRootDir: boot.InstallHostWritableDir, 689 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 690 }, 691 }) 692 } 693 694 func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredNoUbuntuSeedCloudInit(c *C) { 695 // pretend we have a cloud-init config on the seed partition 696 cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 697 err := os.MkdirAll(cloudCfg, 0755) 698 c.Assert(err, IsNil) 699 for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} { 700 err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644) 701 c.Assert(err, IsNil) 702 } 703 704 err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{ 705 tpm: true, bypass: false, encrypt: true, trustedBootloader: true, 706 }) 707 c.Assert(err, IsNil) 708 709 // and did NOT tell sysconfig about the cloud-init files, instead it was 710 // disabled because only gadget cloud-init is allowed 711 c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{ 712 { 713 AllowCloudInit: false, 714 TargetRootDir: boot.InstallHostWritableDir, 715 GadgetDir: filepath.Join(dirs.SnapMountDir, "pc/1/"), 716 }, 717 }) 718 } 719 720 func (s *deviceMgrInstallModeSuite) TestInstallModeWritesModel(c *C) { 721 // pretend we have a cloud-init config on the seed partition 722 model := s.mockInstallModeChange(c, "dangerous", "") 723 724 var buf bytes.Buffer 725 err := asserts.NewEncoder(&buf).Encode(model) 726 c.Assert(err, IsNil) 727 728 s.state.Lock() 729 defer s.state.Unlock() 730 731 installSystem := s.findInstallSystem() 732 c.Assert(installSystem, NotNil) 733 734 // and was run successfully 735 c.Check(installSystem.Err(), IsNil) 736 c.Check(installSystem.Status(), Equals, state.DoneStatus) 737 738 c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileEquals, buf.String()) 739 } 740 741 func (s *deviceMgrInstallModeSuite) testInstallGadgetNoSave(c *C) { 742 err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"), 743 []byte("mode=install\n"), 0644) 744 c.Assert(err, IsNil) 745 746 s.state.Lock() 747 s.makeMockInstalledPcGadget(c, "dangerous", "") 748 info, err := snapstate.CurrentInfo(s.state, "pc") 749 c.Assert(err, IsNil) 750 // replace gadget yaml with one that has no ubuntu-save 751 c.Assert(uc20gadgetYaml, Not(testutil.Contains), "ubuntu-save") 752 err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(uc20gadgetYaml), 0644) 753 c.Assert(err, IsNil) 754 devicestate.SetSystemMode(s.mgr, "install") 755 s.state.Unlock() 756 757 s.settle(c) 758 } 759 760 func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetErr(c *C) { 761 restore := release.MockOnClassic(false) 762 defer restore() 763 764 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 765 return nil, fmt.Errorf("unexpected call") 766 }) 767 defer restore() 768 769 // pretend we have a TPM 770 restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { return nil }) 771 defer restore() 772 773 s.testInstallGadgetNoSave(c) 774 775 s.state.Lock() 776 defer s.state.Unlock() 777 778 installSystem := s.findInstallSystem() 779 c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks: 780 - Setup system for run mode \(cannot use gadget: gadget does not support encrypted data: volume "pc" has no structure with system-save role\)`) 781 // no restart request on failure 782 c.Check(s.restartRequests, HasLen, 0) 783 } 784 785 func (s *deviceMgrInstallModeSuite) TestInstallWithoutEncryptionValidatesGadgetWithoutSaveHappy(c *C) { 786 restore := release.MockOnClassic(false) 787 defer restore() 788 789 restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) { 790 return nil, nil 791 }) 792 defer restore() 793 794 // pretend we have a TPM 795 restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { return fmt.Errorf("TPM2 not available") }) 796 defer restore() 797 798 s.testInstallGadgetNoSave(c) 799 800 s.state.Lock() 801 defer s.state.Unlock() 802 803 installSystem := s.findInstallSystem() 804 c.Check(installSystem.Err(), IsNil) 805 c.Check(s.restartRequests, HasLen, 1) 806 } 807 808 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncrypted(c *C) { 809 st := s.state 810 st.Lock() 811 defer st.Unlock() 812 813 mockModel := s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{ 814 "architecture": "amd64", 815 "kernel": "pc-kernel", 816 "gadget": "pc", 817 }) 818 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 819 Brand: "canonical", 820 Model: "pc", 821 }) 822 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 823 824 for _, tc := range []struct { 825 hasFDESetupHook bool 826 hasTPM bool 827 encrypt bool 828 }{ 829 // unhappy: no tpm, no hook 830 {false, false, false}, 831 // happy: either tpm or hook or both 832 {false, true, true}, 833 {true, false, true}, 834 {true, true, true}, 835 } { 836 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 837 ctx.Lock() 838 defer ctx.Unlock() 839 ctx.Set("fde-setup-result", []byte(`{"features":[]}`)) 840 return nil, nil 841 } 842 rhk := hookstate.MockRunHook(hookInvoke) 843 defer rhk() 844 845 if tc.hasFDESetupHook { 846 makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup) 847 } else { 848 makeInstalledMockKernelSnap(c, st, kernelYamlNoFdeSetup) 849 } 850 restore := devicestate.MockSecbootCheckKeySealingSupported(func() error { 851 if tc.hasTPM { 852 return nil 853 } 854 return fmt.Errorf("tpm says no") 855 }) 856 defer restore() 857 858 encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, st, deviceCtx) 859 c.Assert(err, IsNil) 860 c.Check(encrypt, Equals, tc.encrypt, Commentf("%v", tc)) 861 } 862 } 863 864 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedStorageSafety(c *C) { 865 s.state.Lock() 866 defer s.state.Unlock() 867 868 restore := devicestate.MockSecbootCheckKeySealingSupported(func() error { return nil }) 869 defer restore() 870 871 var testCases = []struct { 872 grade, storageSafety string 873 874 expectedEncryption bool 875 }{ 876 // we don't test unset here because the assertion assembly 877 // will ensure it has a default 878 {"dangerous", "prefer-unencrypted", false}, 879 {"dangerous", "prefer-encrypted", true}, 880 {"dangerous", "encrypted", true}, 881 {"signed", "prefer-unencrypted", false}, 882 {"signed", "prefer-encrypted", true}, 883 {"signed", "encrypted", true}, 884 // secured+prefer-{,un}encrypted is an error at the 885 // assertion level already so cannot be tested here 886 {"secured", "encrypted", true}, 887 } 888 for _, tc := range testCases { 889 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 890 "display-name": "my model", 891 "architecture": "amd64", 892 "base": "core20", 893 "grade": tc.grade, 894 "storage-safety": tc.storageSafety, 895 "snaps": []interface{}{ 896 map[string]interface{}{ 897 "name": "pc-kernel", 898 "id": pcKernelSnapID, 899 "type": "kernel", 900 "default-channel": "20", 901 }, 902 map[string]interface{}{ 903 "name": "pc", 904 "id": pcSnapID, 905 "type": "gadget", 906 "default-channel": "20", 907 }}, 908 }) 909 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 910 911 encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 912 c.Assert(err, IsNil) 913 c.Check(encrypt, Equals, tc.expectedEncryption) 914 } 915 } 916 917 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrors(c *C) { 918 s.state.Lock() 919 defer s.state.Unlock() 920 921 restore := devicestate.MockSecbootCheckKeySealingSupported(func() error { return fmt.Errorf("tpm says no") }) 922 defer restore() 923 924 var testCases = []struct { 925 grade, storageSafety string 926 927 expectedErr string 928 }{ 929 // we don't test unset here because the assertion assembly 930 // will ensure it has a default 931 { 932 "dangerous", "encrypted", 933 "cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no", 934 }, { 935 "signed", "encrypted", 936 "cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no", 937 }, { 938 "secured", "", 939 "cannot encrypt device storage as mandated by model grade secured: tpm says no", 940 }, { 941 "secured", "encrypted", 942 "cannot encrypt device storage as mandated by model grade secured: tpm says no", 943 }, 944 } 945 for _, tc := range testCases { 946 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{ 947 "display-name": "my model", 948 "architecture": "amd64", 949 "base": "core20", 950 "grade": tc.grade, 951 "storage-safety": tc.storageSafety, 952 "snaps": []interface{}{ 953 map[string]interface{}{ 954 "name": "pc-kernel", 955 "id": pcKernelSnapID, 956 "type": "kernel", 957 "default-channel": "20", 958 }, 959 map[string]interface{}{ 960 "name": "pc", 961 "id": pcSnapID, 962 "type": "gadget", 963 "default-channel": "20", 964 }}, 965 }) 966 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 967 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 968 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%s %s", tc.grade, tc.storageSafety)) 969 } 970 } 971 972 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedFDEHook(c *C) { 973 st := s.state 974 st.Lock() 975 defer st.Unlock() 976 977 s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{ 978 "architecture": "amd64", 979 "kernel": "pc-kernel", 980 "gadget": "pc", 981 }) 982 devicestatetest.SetDevice(s.state, &auth.DeviceState{ 983 Brand: "canonical", 984 Model: "pc", 985 }) 986 makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup) 987 988 for _, tc := range []struct { 989 hookOutput string 990 expectedErr string 991 }{ 992 // invalid json 993 {"xxx", `cannot parse hook output "xxx": invalid character 'x' looking for beginning of value`}, 994 // no output is invalid 995 {"", `cannot parse hook output "": unexpected end of JSON input`}, 996 // specific error 997 {`{"error":"failed"}`, `cannot use hook: it returned error: failed`}, 998 {`{}`, `cannot use hook: neither "features" nor "error" returned`}, 999 // valid 1000 {`{"features":[]}`, ""}, 1001 {`{"features":["a"]}`, ""}, 1002 {`{"features":["a","b"]}`, ""}, 1003 // features must be list of strings 1004 {`{"features":[1]}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`}, 1005 {`{"features":1}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`}, 1006 {`{"features":"1"}`, `cannot parse hook output ".*": json: cannot unmarshal string into Go struct.*`}, 1007 } { 1008 hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) { 1009 ctx.Lock() 1010 defer ctx.Unlock() 1011 ctx.Set("fde-setup-result", []byte(tc.hookOutput)) 1012 return nil, nil 1013 } 1014 rhk := hookstate.MockRunHook(hookInvoke) 1015 defer rhk() 1016 1017 err := devicestate.DeviceManagerCheckFDEFeatures(s.mgr, st) 1018 if tc.expectedErr != "" { 1019 c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc)) 1020 } else { 1021 c.Check(err, IsNil, Commentf("%v", tc)) 1022 } 1023 } 1024 } 1025 1026 var checkEncryptionModelHeaders = map[string]interface{}{ 1027 "display-name": "my model", 1028 "architecture": "amd64", 1029 "base": "core20", 1030 "grade": "dangerous", 1031 "snaps": []interface{}{ 1032 map[string]interface{}{ 1033 "name": "pc-kernel", 1034 "id": pcKernelSnapID, 1035 "type": "kernel", 1036 "default-channel": "20", 1037 }, 1038 map[string]interface{}{ 1039 "name": "pc", 1040 "id": pcSnapID, 1041 "type": "gadget", 1042 "default-channel": "20", 1043 }}, 1044 } 1045 1046 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsTPM(c *C) { 1047 s.state.Lock() 1048 defer s.state.Unlock() 1049 1050 restore := devicestate.MockSecbootCheckKeySealingSupported(func() error { 1051 return fmt.Errorf("tpm says no") 1052 }) 1053 defer restore() 1054 1055 logbuf, restore := logger.MockLogger() 1056 defer restore() 1057 1058 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders) 1059 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1060 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1061 c.Check(err, IsNil) 1062 c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as checking TPM gave: tpm says no\n") 1063 } 1064 1065 func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsHook(c *C) { 1066 s.state.Lock() 1067 defer s.state.Unlock() 1068 1069 logbuf, restore := logger.MockLogger() 1070 defer restore() 1071 1072 mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders) 1073 // mock kernel installed but no hook or handle so checkEncryption 1074 // will fail 1075 makeInstalledMockKernelSnap(c, s.state, kernelYamlWithFdeSetup) 1076 1077 deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel} 1078 _, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx) 1079 c.Check(err, IsNil) 1080 c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as querying kernel fde-setup hook did not succeed:.*\n") 1081 }