github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/devicestate/devicemgr.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-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 21 22 import ( 23 "context" 24 "encoding/json" 25 "errors" 26 "fmt" 27 "os" 28 "path/filepath" 29 "regexp" 30 "strings" 31 "time" 32 33 "github.com/snapcore/snapd/asserts" 34 "github.com/snapcore/snapd/asserts/sysdb" 35 "github.com/snapcore/snapd/boot" 36 "github.com/snapcore/snapd/dirs" 37 "github.com/snapcore/snapd/gadget" 38 "github.com/snapcore/snapd/i18n" 39 "github.com/snapcore/snapd/logger" 40 "github.com/snapcore/snapd/osutil" 41 "github.com/snapcore/snapd/overlord/assertstate" 42 "github.com/snapcore/snapd/overlord/auth" 43 "github.com/snapcore/snapd/overlord/configstate/config" 44 "github.com/snapcore/snapd/overlord/devicestate/fde" 45 "github.com/snapcore/snapd/overlord/devicestate/internal" 46 "github.com/snapcore/snapd/overlord/hookstate" 47 "github.com/snapcore/snapd/overlord/snapstate" 48 "github.com/snapcore/snapd/overlord/state" 49 "github.com/snapcore/snapd/overlord/storecontext" 50 "github.com/snapcore/snapd/progress" 51 "github.com/snapcore/snapd/release" 52 "github.com/snapcore/snapd/seed" 53 "github.com/snapcore/snapd/snap" 54 "github.com/snapcore/snapd/snap/snapfile" 55 "github.com/snapcore/snapd/snapdenv" 56 "github.com/snapcore/snapd/sysconfig" 57 "github.com/snapcore/snapd/systemd" 58 "github.com/snapcore/snapd/timings" 59 ) 60 61 var ( 62 cloudInitStatus = sysconfig.CloudInitStatus 63 restrictCloudInit = sysconfig.RestrictCloudInit 64 ) 65 66 // EarlyConfig is a hook set by configstate that can process early configuration 67 // during managers' startup. 68 var EarlyConfig func(st *state.State, preloadGadget func() (*gadget.Info, error)) error 69 70 // DeviceManager is responsible for managing the device identity and device 71 // policies. 72 type DeviceManager struct { 73 systemMode string 74 // saveAvailable keeps track whether /var/lib/snapd/save 75 // is available, i.e. exists and is mounted from ubuntu-save 76 // if the latter exists. 77 // TODO: evolve this to state to track things if we start mounting 78 // save as rw vs ro, or mount/umount it fully on demand 79 saveAvailable bool 80 81 state *state.State 82 hookMgr *hookstate.HookManager 83 84 cachedKeypairMgr asserts.KeypairManager 85 86 // newStore can make new stores for remodeling 87 newStore func(storecontext.DeviceBackend) snapstate.StoreService 88 89 bootOkRan bool 90 bootRevisionsUpdated bool 91 92 seedTimings *timings.Timings 93 94 ensureSeedInConfigRan bool 95 96 ensureInstalledRan bool 97 98 cloudInitAlreadyRestricted bool 99 cloudInitErrorAttemptStart *time.Time 100 cloudInitEnabledInactiveAttemptStart *time.Time 101 102 lastBecomeOperationalAttempt time.Time 103 becomeOperationalBackoff time.Duration 104 registered bool 105 reg chan struct{} 106 107 preseed bool 108 } 109 110 // Manager returns a new device manager. 111 func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.TaskRunner, newStore func(storecontext.DeviceBackend) snapstate.StoreService) (*DeviceManager, error) { 112 delayedCrossMgrInit() 113 114 m := &DeviceManager{ 115 state: s, 116 hookMgr: hookManager, 117 newStore: newStore, 118 reg: make(chan struct{}), 119 preseed: snapdenv.Preseeding(), 120 } 121 122 modeEnv, err := maybeReadModeenv() 123 if err != nil { 124 return nil, err 125 } 126 if modeEnv != nil { 127 m.systemMode = modeEnv.Mode 128 } 129 130 s.Lock() 131 s.Cache(deviceMgrKey{}, m) 132 s.Unlock() 133 134 if err := m.confirmRegistered(); err != nil { 135 return nil, err 136 } 137 138 hookManager.Register(regexp.MustCompile("^prepare-device$"), newBasicHookStateHandler) 139 hookManager.Register(regexp.MustCompile("^install-device$"), newBasicHookStateHandler) 140 141 runner.AddHandler("generate-device-key", m.doGenerateDeviceKey, nil) 142 runner.AddHandler("request-serial", m.doRequestSerial, nil) 143 runner.AddHandler("mark-preseeded", m.doMarkPreseeded, nil) 144 runner.AddHandler("mark-seeded", m.doMarkSeeded, nil) 145 runner.AddHandler("setup-run-system", m.doSetupRunSystem, nil) 146 runner.AddHandler("restart-system-to-run-mode", m.doRestartSystemToRunMode, nil) 147 runner.AddHandler("prepare-remodeling", m.doPrepareRemodeling, nil) 148 runner.AddCleanup("prepare-remodeling", m.cleanupRemodel) 149 // this *must* always run last and finalizes a remodel 150 runner.AddHandler("set-model", m.doSetModel, nil) 151 runner.AddCleanup("set-model", m.cleanupRemodel) 152 // There is no undo for successful gadget updates. The system is 153 // rebooted during update, if it boots up to the point where snapd runs 154 // we deem the new assets (be it bootloader or firmware) functional. The 155 // deployed boot assets must be backward compatible with reverted kernel 156 // or gadget snaps. There are no further changes to the boot assets, 157 // unless a new gadget update is deployed. 158 runner.AddHandler("update-gadget-assets", m.doUpdateGadgetAssets, nil) 159 // There is no undo handler for successful boot config update. The 160 // config assets are assumed to be always backwards compatible. 161 runner.AddHandler("update-managed-boot-config", m.doUpdateManagedBootConfig, nil) 162 163 runner.AddBlocked(gadgetUpdateBlocked) 164 165 // wire FDE kernel hook support into boot 166 boot.HasFDESetupHook = m.hasFDESetupHook 167 boot.RunFDESetupHook = m.runFDESetupHook 168 hookManager.Register(regexp.MustCompile("^fde-setup$"), newFdeSetupHandler) 169 170 return m, nil 171 } 172 173 type genericHook struct{} 174 175 func (h genericHook) Before() error { return nil } 176 func (h genericHook) Done() error { return nil } 177 func (h genericHook) Error(err error) error { return nil } 178 179 func newBasicHookStateHandler(context *hookstate.Context) hookstate.Handler { 180 return genericHook{} 181 } 182 183 func maybeReadModeenv() (*boot.Modeenv, error) { 184 modeEnv, err := boot.ReadModeenv("") 185 if err != nil && !os.IsNotExist(err) { 186 return nil, fmt.Errorf("cannot read modeenv: %v", err) 187 } 188 return modeEnv, nil 189 } 190 191 // StartUp implements StateStarterUp.Startup. 192 func (m *DeviceManager) StartUp() error { 193 // system mode is explicitly set on UC20 194 // TODO:UC20: ubuntu-save needs to be mounted for recover too 195 if !release.OnClassic && m.systemMode == "run" { 196 if err := m.maybeSetupUbuntuSave(); err != nil { 197 return fmt.Errorf("cannot set up ubuntu-save: %v", err) 198 } 199 } 200 201 // TODO: setup proper timings measurements for this 202 203 m.state.Lock() 204 defer m.state.Unlock() 205 return EarlyConfig(m.state, m.preloadGadget) 206 } 207 208 func (m *DeviceManager) maybeSetupUbuntuSave() error { 209 // only called for UC20 210 211 saveMounted, err := osutil.IsMounted(dirs.SnapSaveDir) 212 if err != nil { 213 return err 214 } 215 if saveMounted { 216 logger.Noticef("save already mounted under %v", dirs.SnapSaveDir) 217 m.saveAvailable = true 218 return nil 219 } 220 221 runMntSaveMounted, err := osutil.IsMounted(boot.InitramfsUbuntuSaveDir) 222 if err != nil { 223 return err 224 } 225 if !runMntSaveMounted { 226 // we don't have ubuntu-save, save will be used directly 227 logger.Noticef("no ubuntu-save mount") 228 m.saveAvailable = true 229 return nil 230 } 231 232 logger.Noticef("bind-mounting ubuntu-save under %v", dirs.SnapSaveDir) 233 234 err = systemd.New(systemd.SystemMode, progress.Null).Mount(boot.InitramfsUbuntuSaveDir, 235 dirs.SnapSaveDir, "-o", "bind") 236 if err != nil { 237 logger.Noticef("bind-mounting ubuntu-save failed %v", err) 238 return fmt.Errorf("cannot bind mount %v under %v: %v", boot.InitramfsUbuntuSaveDir, dirs.SnapSaveDir, err) 239 } 240 m.saveAvailable = true 241 return nil 242 } 243 244 type deviceMgrKey struct{} 245 246 func deviceMgr(st *state.State) *DeviceManager { 247 mgr := st.Cached(deviceMgrKey{}) 248 if mgr == nil { 249 panic("internal error: device manager is not yet associated with state") 250 } 251 return mgr.(*DeviceManager) 252 } 253 254 func (m *DeviceManager) CanStandby() bool { 255 var seeded bool 256 if err := m.state.Get("seeded", &seeded); err != nil { 257 return false 258 } 259 return seeded 260 } 261 262 func (m *DeviceManager) confirmRegistered() error { 263 m.state.Lock() 264 defer m.state.Unlock() 265 266 device, err := m.device() 267 if err != nil { 268 return err 269 } 270 271 if device.Serial != "" { 272 m.markRegistered() 273 } 274 return nil 275 } 276 277 func (m *DeviceManager) markRegistered() { 278 if m.registered { 279 return 280 } 281 m.registered = true 282 close(m.reg) 283 } 284 285 func gadgetUpdateBlocked(cand *state.Task, running []*state.Task) bool { 286 if cand.Kind() == "update-gadget-assets" && len(running) != 0 { 287 // update-gadget-assets must be the only task running 288 return true 289 } 290 for _, other := range running { 291 if other.Kind() == "update-gadget-assets" { 292 // no other task can be started when 293 // update-gadget-assets is running 294 return true 295 } 296 } 297 298 return false 299 } 300 301 func (m *DeviceManager) changeInFlight(kind string) bool { 302 for _, chg := range m.state.Changes() { 303 if chg.Kind() == kind && !chg.Status().Ready() { 304 // change already in motion 305 return true 306 } 307 } 308 return false 309 } 310 311 // helpers to keep count of attempts to get a serial, useful to decide 312 // to give up holding off trying to auto-refresh 313 314 type ensureOperationalAttemptsKey struct{} 315 316 func incEnsureOperationalAttempts(st *state.State) { 317 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 318 st.Cache(ensureOperationalAttemptsKey{}, cur+1) 319 } 320 321 func ensureOperationalAttempts(st *state.State) int { 322 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 323 return cur 324 } 325 326 // ensureOperationalShouldBackoff returns whether we should abstain from 327 // further become-operational tentatives while its backoff interval is 328 // not expired. 329 func (m *DeviceManager) ensureOperationalShouldBackoff(now time.Time) bool { 330 if !m.lastBecomeOperationalAttempt.IsZero() && m.lastBecomeOperationalAttempt.Add(m.becomeOperationalBackoff).After(now) { 331 return true 332 } 333 if m.becomeOperationalBackoff == 0 { 334 m.becomeOperationalBackoff = 5 * time.Minute 335 } else { 336 newBackoff := m.becomeOperationalBackoff * 2 337 if newBackoff > (12 * time.Hour) { 338 newBackoff = 24 * time.Hour 339 } 340 m.becomeOperationalBackoff = newBackoff 341 } 342 m.lastBecomeOperationalAttempt = now 343 return false 344 } 345 346 func setClassicFallbackModel(st *state.State, device *auth.DeviceState) error { 347 err := assertstate.Add(st, sysdb.GenericClassicModel()) 348 if err != nil && !asserts.IsUnaccceptedUpdate(err) { 349 return fmt.Errorf(`cannot install "generic-classic" fallback model assertion: %v`, err) 350 } 351 device.Brand = "generic" 352 device.Model = "generic-classic" 353 if err := internal.SetDevice(st, device); err != nil { 354 return err 355 } 356 return nil 357 } 358 359 func (m *DeviceManager) SystemMode() string { 360 if m.systemMode == "" { 361 return "run" 362 } 363 return m.systemMode 364 } 365 366 func (m *DeviceManager) ensureOperational() error { 367 m.state.Lock() 368 defer m.state.Unlock() 369 370 if m.SystemMode() != "run" { 371 // avoid doing registration in ephemeral mode 372 // note: this also stop auto-refreshes indirectly 373 return nil 374 } 375 376 device, err := m.device() 377 if err != nil { 378 return err 379 } 380 381 if device.Serial != "" { 382 // serial is set, we are all set 383 return nil 384 } 385 386 perfTimings := timings.New(map[string]string{"ensure": "become-operational"}) 387 388 // conditions to trigger device registration 389 // 390 // * have a model assertion with a gadget (core and 391 // device-like classic) in which case we need also to wait 392 // for the gadget to have been installed though 393 // TODO: consider a way to support lazy registration on classic 394 // even with a gadget and some preseeded snaps 395 // 396 // * classic with a model assertion with a non-default store specified 397 // * lazy classic case (might have a model with no gadget nor store 398 // or no model): we wait to have some snaps installed or be 399 // in the process to install some 400 401 var seeded bool 402 err = m.state.Get("seeded", &seeded) 403 if err != nil && err != state.ErrNoState { 404 return err 405 } 406 407 if device.Brand == "" || device.Model == "" { 408 if !release.OnClassic || !seeded { 409 return nil 410 } 411 // we are on classic and seeded but there is no model: 412 // use a fallback model! 413 err := setClassicFallbackModel(m.state, device) 414 if err != nil { 415 return err 416 } 417 } 418 419 if m.changeInFlight("become-operational") { 420 return nil 421 } 422 423 var storeID, gadget string 424 model, err := m.Model() 425 if err != nil && err != state.ErrNoState { 426 return err 427 } 428 if err == nil { 429 gadget = model.Gadget() 430 storeID = model.Store() 431 } else { 432 return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") 433 } 434 435 if gadget == "" && storeID == "" { 436 // classic: if we have no gadget and no non-default store 437 // wait to have snaps or snap installation 438 439 n, err := snapstate.NumSnaps(m.state) 440 if err != nil { 441 return err 442 } 443 if n == 0 && !snapstate.Installing(m.state) { 444 return nil 445 } 446 } 447 448 var hasPrepareDeviceHook bool 449 // if there's a gadget specified wait for it 450 if gadget != "" { 451 // if have a gadget wait until seeded to proceed 452 if !seeded { 453 // this will be run again, so eventually when the system is 454 // seeded the code below runs 455 return nil 456 457 } 458 459 gadgetInfo, err := snapstate.CurrentInfo(m.state, gadget) 460 if err != nil { 461 return err 462 } 463 hasPrepareDeviceHook = (gadgetInfo.Hooks["prepare-device"] != nil) 464 } 465 466 // have some backoff between full retries 467 if m.ensureOperationalShouldBackoff(time.Now()) { 468 return nil 469 } 470 // increment attempt count 471 incEnsureOperationalAttempts(m.state) 472 473 // XXX: some of these will need to be split and use hooks 474 // retries might need to embrace more than one "task" then, 475 // need to be careful 476 477 tasks := []*state.Task{} 478 479 var prepareDevice *state.Task 480 if hasPrepareDeviceHook { 481 summary := i18n.G("Run prepare-device hook") 482 hooksup := &hookstate.HookSetup{ 483 Snap: gadget, 484 Hook: "prepare-device", 485 } 486 prepareDevice = hookstate.HookTask(m.state, summary, hooksup, nil) 487 tasks = append(tasks, prepareDevice) 488 } 489 490 genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key")) 491 if prepareDevice != nil { 492 genKey.WaitFor(prepareDevice) 493 } 494 tasks = append(tasks, genKey) 495 requestSerial := m.state.NewTask("request-serial", i18n.G("Request device serial")) 496 requestSerial.WaitFor(genKey) 497 tasks = append(tasks, requestSerial) 498 499 chg := m.state.NewChange("become-operational", i18n.G("Initialize device")) 500 chg.AddAll(state.NewTaskSet(tasks...)) 501 502 state.TagTimingsWithChange(perfTimings, chg) 503 perfTimings.Save(m.state) 504 505 return nil 506 } 507 508 var startTime time.Time 509 510 func init() { 511 startTime = time.Now() 512 } 513 514 func (m *DeviceManager) setTimeOnce(name string, t time.Time) error { 515 var prev time.Time 516 err := m.state.Get(name, &prev) 517 if err != nil && err != state.ErrNoState { 518 return err 519 } 520 if !prev.IsZero() { 521 // already set 522 return nil 523 } 524 m.state.Set(name, t) 525 return nil 526 } 527 528 func (m *DeviceManager) seedStart() (*timings.Timings, error) { 529 if m.seedTimings != nil { 530 // reuse the early cached one 531 return m.seedTimings, nil 532 } 533 perfTimings := timings.New(map[string]string{"ensure": "seed"}) 534 535 var recordedStart string 536 var start time.Time 537 if m.preseed { 538 recordedStart = "preseed-start-time" 539 start = timeNow() 540 } else { 541 recordedStart = "seed-start-time" 542 start = startTime 543 } 544 if err := m.setTimeOnce(recordedStart, start); err != nil { 545 return nil, err 546 } 547 return perfTimings, nil 548 } 549 550 func (m *DeviceManager) preloadGadget() (*gadget.Info, error) { 551 var sysLabel string 552 modeEnv, err := maybeReadModeenv() 553 if err != nil { 554 return nil, err 555 } 556 if modeEnv != nil { 557 sysLabel = modeEnv.RecoverySystem 558 } 559 560 // we time preloadGadget + first ensureSeeded together 561 // under --ensure=seed 562 tm, err := m.seedStart() 563 if err != nil { 564 return nil, err 565 } 566 // cached for first ensureSeeded 567 m.seedTimings = tm 568 569 // Here we behave as if there was no gadget if we encounter 570 // errors, under the assumption that those will be resurfaced 571 // in ensureSeed. This preserves having a failing to seed 572 // snapd continuing running. 573 // 574 // TODO: consider changing that again but we need consider the 575 // effect of the different failure mode. 576 // 577 // We also assume that anything sensitive will not be guarded 578 // just by option flags. For example automatic user creation 579 // also requires the model to be known/set. Otherwise ignoring 580 // errors here would be problematic. 581 var deviceSeed seed.Seed 582 timings.Run(tm, "import-assertions[early]", "early import assertions from seed", func(nested timings.Measurer) { 583 deviceSeed, err = loadDeviceSeed(m.state, sysLabel) 584 }) 585 if err != nil { 586 // this same error will be resurfaced in ensureSeed later 587 if err != seed.ErrNoAssertions { 588 logger.Debugf("early import assertions from seed failed: %v", err) 589 } 590 return nil, state.ErrNoState 591 } 592 model := deviceSeed.Model() 593 if model.Gadget() == "" { 594 // no gadget 595 return nil, state.ErrNoState 596 } 597 var gi *gadget.Info 598 timings.Run(tm, "preload-verified-gadget-metadata", "preload verified gadget metadata from seed", func(nested timings.Measurer) { 599 gi, err = func() (*gadget.Info, error) { 600 if err := deviceSeed.LoadEssentialMeta([]snap.Type{snap.TypeGadget}, nested); err != nil { 601 return nil, err 602 } 603 essGadget := deviceSeed.EssentialSnaps() 604 if len(essGadget) != 1 { 605 return nil, fmt.Errorf("multiple gadgets among essential snaps are unexpected") 606 } 607 snapf, err := snapfile.Open(essGadget[0].Path) 608 if err != nil { 609 return nil, err 610 } 611 return gadget.ReadInfoFromSnapFile(snapf, model) 612 }() 613 }) 614 if err != nil { 615 logger.Noticef("preload verified gadget metadata from seed failed: %v", err) 616 return nil, state.ErrNoState 617 } 618 return gi, nil 619 } 620 621 var populateStateFromSeed = populateStateFromSeedImpl 622 623 // ensureSeeded makes sure that the snaps from seed.yaml get installed 624 // with the matching assertions 625 func (m *DeviceManager) ensureSeeded() error { 626 m.state.Lock() 627 defer m.state.Unlock() 628 629 var seeded bool 630 err := m.state.Get("seeded", &seeded) 631 if err != nil && err != state.ErrNoState { 632 return err 633 } 634 if seeded { 635 return nil 636 } 637 638 if m.changeInFlight("seed") { 639 return nil 640 } 641 642 perfTimings, err := m.seedStart() 643 if err != nil { 644 return err 645 } 646 // we time preloadGadget + first ensureSeeded together 647 // succcessive ensureSeeded should be timed separately 648 m.seedTimings = nil 649 650 var opts *populateStateFromSeedOptions 651 if m.preseed { 652 opts = &populateStateFromSeedOptions{Preseed: true} 653 } else { 654 modeEnv, err := maybeReadModeenv() 655 if err != nil { 656 return err 657 } 658 if modeEnv != nil { 659 opts = &populateStateFromSeedOptions{ 660 Mode: m.systemMode, 661 Label: modeEnv.RecoverySystem, 662 } 663 } 664 } 665 666 var tsAll []*state.TaskSet 667 timings.Run(perfTimings, "state-from-seed", "populate state from seed", func(tm timings.Measurer) { 668 tsAll, err = populateStateFromSeed(m.state, opts, tm) 669 }) 670 if err != nil { 671 return err 672 } 673 if len(tsAll) == 0 { 674 return nil 675 } 676 677 chg := m.state.NewChange("seed", "Initialize system state") 678 for _, ts := range tsAll { 679 chg.AddAll(ts) 680 } 681 m.state.EnsureBefore(0) 682 683 state.TagTimingsWithChange(perfTimings, chg) 684 perfTimings.Save(m.state) 685 return nil 686 } 687 688 // ResetBootOk is only useful for integration testing 689 func (m *DeviceManager) ResetBootOk() { 690 m.bootOkRan = false 691 m.bootRevisionsUpdated = false 692 } 693 694 func (m *DeviceManager) ensureBootOk() error { 695 m.state.Lock() 696 defer m.state.Unlock() 697 698 if release.OnClassic { 699 return nil 700 } 701 702 // boot-ok/update-boot-revision is only relevant in run-mode 703 if m.SystemMode() != "run" { 704 return nil 705 } 706 707 if !m.bootOkRan { 708 deviceCtx, err := DeviceCtx(m.state, nil, nil) 709 if err != nil && err != state.ErrNoState { 710 return err 711 } 712 if err == nil { 713 if err := boot.MarkBootSuccessful(deviceCtx); err != nil { 714 return err 715 } 716 } 717 m.bootOkRan = true 718 } 719 720 if !m.bootRevisionsUpdated { 721 if err := snapstate.UpdateBootRevisions(m.state); err != nil { 722 return err 723 } 724 m.bootRevisionsUpdated = true 725 } 726 727 return nil 728 } 729 730 func (m *DeviceManager) ensureCloudInitRestricted() error { 731 m.state.Lock() 732 defer m.state.Unlock() 733 734 if m.cloudInitAlreadyRestricted { 735 return nil 736 } 737 738 var seeded bool 739 err := m.state.Get("seeded", &seeded) 740 if err != nil && err != state.ErrNoState { 741 return err 742 } 743 744 // On Ubuntu Core devices that have been seeded, we want to restrict 745 // cloud-init so that its more dangerous (for an IoT device at least) 746 // features are not exploitable after a device has been seeded. This allows 747 // device administrators and other tools (such as multipass) to still 748 // configure an Ubuntu Core device on first boot, and also allows cloud 749 // vendors to run cloud-init with only a specific data-source on subsequent 750 // boots but disallows arbitrary cloud-init {user,meta,vendor}-data to be 751 // attached to a device via a USB drive and inject code onto the device. 752 753 if seeded && !release.OnClassic { 754 opts := &sysconfig.CloudInitRestrictOptions{} 755 756 // check the current state of cloud-init, if it is disabled or already 757 // restricted then we have nothing to do 758 cloudInitStatus, err := cloudInitStatus() 759 if err != nil { 760 return err 761 } 762 statusMsg := "" 763 764 switch cloudInitStatus { 765 case sysconfig.CloudInitDisabledPermanently, sysconfig.CloudInitRestrictedBySnapd: 766 // already been permanently disabled, nothing to do 767 m.cloudInitAlreadyRestricted = true 768 return nil 769 case sysconfig.CloudInitNotFound: 770 // no cloud init at all 771 statusMsg = "not found" 772 case sysconfig.CloudInitUntriggered: 773 // hasn't been used 774 statusMsg = "reported to be in disabled state" 775 case sysconfig.CloudInitDone: 776 // is done being used 777 statusMsg = "reported to be done" 778 case sysconfig.CloudInitErrored: 779 // cloud-init errored, so we give the device admin / developer a few 780 // minutes to reboot the machine to re-run cloud-init and try again, 781 // otherwise we will disable cloud-init permanently 782 783 // initialize the time we first saw cloud-init in error state 784 if m.cloudInitErrorAttemptStart == nil { 785 // save the time we started the attempt to restrict 786 now := timeNow() 787 m.cloudInitErrorAttemptStart = &now 788 logger.Noticef("System initialized, cloud-init reported to be in error state, will disable in 3 minutes") 789 } 790 791 // check if 3 minutes have elapsed since we first saw cloud-init in 792 // error state 793 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitErrorAttemptStart) 794 if timeSinceFirstAttempt <= 3*time.Minute { 795 // we need to keep waiting for cloud-init, up to 3 minutes 796 nextCheck := 3*time.Minute - timeSinceFirstAttempt 797 m.state.EnsureBefore(nextCheck) 798 return nil 799 } 800 // otherwise, we timed out waiting for cloud-init to be fixed or 801 // rebooted and should restrict cloud-init 802 // we will restrict cloud-init below, but we need to force the 803 // disable, as by default RestrictCloudInit will error on state 804 // CloudInitErrored 805 opts.ForceDisable = true 806 statusMsg = "reported to be in error state after 3 minutes" 807 default: 808 // in unknown states we are conservative and let the device run for 809 // a while to see if it transitions to a known state, but eventually 810 // will disable anyways 811 fallthrough 812 case sysconfig.CloudInitEnabled: 813 // we will give cloud-init up to 5 minutes to try and run, if it 814 // still has not transitioned to some other known state, then we 815 // will give up waiting for it and disable it anyways 816 817 // initialize the first time we saw cloud-init in enabled state 818 if m.cloudInitEnabledInactiveAttemptStart == nil { 819 // save the time we started the attempt to restrict 820 now := timeNow() 821 m.cloudInitEnabledInactiveAttemptStart = &now 822 } 823 824 // keep re-scheduling again in 10 seconds until we hit 5 minutes 825 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitEnabledInactiveAttemptStart) 826 if timeSinceFirstAttempt <= 5*time.Minute { 827 // TODO: should we log a message here about waiting for cloud-init 828 // to be in a "known state"? 829 m.state.EnsureBefore(10 * time.Second) 830 return nil 831 } 832 833 // otherwise, we gave cloud-init 5 minutes to run, if it's still not 834 // done disable it anyways 835 // note we we need to force the disable, as by default 836 // RestrictCloudInit will error on state CloudInitEnabled 837 opts.ForceDisable = true 838 statusMsg = "failed to transition to done or error state after 5 minutes" 839 } 840 841 // we should always have a model if we are seeded and are not on classic 842 model, err := m.Model() 843 if err != nil { 844 return err 845 } 846 847 // For UC20, we want to always disable cloud-init after it has run on 848 // first boot unless we are in a "real cloud", i.e. not using NoCloud, 849 // or if we installed cloud-init configuration from the gadget 850 if model.Grade() != asserts.ModelGradeUnset { 851 // always disable NoCloud/local datasources after first boot on 852 // uc20, this is because even if the gadget has a cloud.conf 853 // configuring NoCloud, the config installed by cloud-init should 854 // not work differently for later boots, so it's sufficient that 855 // NoCloud runs on first-boot and never again 856 opts.DisableAfterLocalDatasourcesRun = true 857 } 858 859 // now restrict/disable cloud-init 860 res, err := restrictCloudInit(cloudInitStatus, opts) 861 if err != nil { 862 return err 863 } 864 865 // log a message about what we did 866 actionMsg := "" 867 switch res.Action { 868 case "disable": 869 actionMsg = "disabled permanently" 870 case "restrict": 871 // log different messages depending on what datasource was used 872 if res.DataSource == "NoCloud" { 873 actionMsg = "set datasource_list to [ NoCloud ] and disabled auto-import by filesystem label" 874 } else { 875 // all other datasources just log that we limited it to that datasource 876 actionMsg = fmt.Sprintf("set datasource_list to [ %s ]", res.DataSource) 877 } 878 default: 879 return fmt.Errorf("internal error: unexpected action %s taken while restricting cloud-init", res.Action) 880 } 881 logger.Noticef("System initialized, cloud-init %s, %s", statusMsg, actionMsg) 882 883 m.cloudInitAlreadyRestricted = true 884 } 885 886 return nil 887 } 888 889 func (m *DeviceManager) ensureInstalled() error { 890 m.state.Lock() 891 defer m.state.Unlock() 892 893 if release.OnClassic { 894 return nil 895 } 896 897 if m.ensureInstalledRan { 898 return nil 899 } 900 901 if m.SystemMode() != "install" { 902 return nil 903 } 904 905 var seeded bool 906 err := m.state.Get("seeded", &seeded) 907 if err != nil && err != state.ErrNoState { 908 return err 909 } 910 if !seeded { 911 return nil 912 } 913 914 if m.changeInFlight("install-system") { 915 return nil 916 } 917 918 model, err := m.Model() 919 if err != nil && err != state.ErrNoState { 920 return err 921 } 922 if err != nil { 923 return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") 924 } 925 926 // check if the gadget has an install-device hook 927 var hasInstallDeviceHook bool 928 929 gadgetInfo, err := snapstate.CurrentInfo(m.state, model.Gadget()) 930 if err != nil { 931 return fmt.Errorf("internal error: device is seeded in install mode but has no gadget snap: %v", err) 932 } 933 hasInstallDeviceHook = (gadgetInfo.Hooks["install-device"] != nil) 934 935 m.ensureInstalledRan = true 936 937 var prev *state.Task 938 setupRunSystem := m.state.NewTask("setup-run-system", i18n.G("Setup system for run mode")) 939 940 tasks := []*state.Task{setupRunSystem} 941 addTask := func(t *state.Task) { 942 t.WaitFor(prev) 943 tasks = append(tasks, t) 944 prev = t 945 } 946 prev = setupRunSystem 947 948 // add the install-device hook before ensure-next-boot-to-run-mode if it 949 // exists in the snap 950 if hasInstallDeviceHook { 951 summary := i18n.G("Run install-device hook") 952 hooksup := &hookstate.HookSetup{ 953 // TODO: what's a reasonable timeout for the install-device hook? 954 Snap: model.Gadget(), 955 Hook: "install-device", 956 } 957 installDevice := hookstate.HookTask(m.state, summary, hooksup, nil) 958 addTask(installDevice) 959 } 960 961 restartSystem := m.state.NewTask("restart-system-to-run-mode", i18n.G("Ensure next boot to run mode")) 962 addTask(restartSystem) 963 964 chg := m.state.NewChange("install-system", i18n.G("Install the system")) 965 chg.AddAll(state.NewTaskSet(tasks...)) 966 967 return nil 968 } 969 970 var timeNow = time.Now 971 972 // StartOfOperationTime returns the time when snapd started operating, 973 // and sets it in the state when called for the first time. 974 // The StartOfOperationTime time is seed-time if available, 975 // or current time otherwise. 976 func (m *DeviceManager) StartOfOperationTime() (time.Time, error) { 977 var opTime time.Time 978 if m.preseed { 979 return opTime, fmt.Errorf("internal error: unexpected call to StartOfOperationTime in preseed mode") 980 } 981 err := m.state.Get("start-of-operation-time", &opTime) 982 if err == nil { 983 return opTime, nil 984 } 985 if err != nil && err != state.ErrNoState { 986 return opTime, err 987 } 988 989 // start-of-operation-time not set yet, use seed-time if available 990 var seedTime time.Time 991 err = m.state.Get("seed-time", &seedTime) 992 if err != nil && err != state.ErrNoState { 993 return opTime, err 994 } 995 if err == nil { 996 opTime = seedTime 997 } else { 998 opTime = timeNow() 999 } 1000 m.state.Set("start-of-operation-time", opTime) 1001 return opTime, nil 1002 } 1003 1004 func markSeededInConfig(st *state.State) error { 1005 var seedDone bool 1006 tr := config.NewTransaction(st) 1007 if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) { 1008 return err 1009 } 1010 if !seedDone { 1011 if err := tr.Set("core", "seed.loaded", true); err != nil { 1012 return err 1013 } 1014 tr.Commit() 1015 } 1016 return nil 1017 } 1018 1019 func (m *DeviceManager) ensureSeedInConfig() error { 1020 m.state.Lock() 1021 defer m.state.Unlock() 1022 1023 if !m.ensureSeedInConfigRan { 1024 // get global seeded option 1025 var seeded bool 1026 if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState { 1027 return err 1028 } 1029 if !seeded { 1030 // wait for ensure again, this is fine because 1031 // doMarkSeeded will run "EnsureBefore(0)" 1032 return nil 1033 } 1034 1035 // Sync seeding with the configuration state. We need to 1036 // do this here to ensure that old systems which did not 1037 // set the configuration on seeding get the configuration 1038 // update too. 1039 if err := markSeededInConfig(m.state); err != nil { 1040 return err 1041 } 1042 m.ensureSeedInConfigRan = true 1043 } 1044 1045 return nil 1046 1047 } 1048 1049 type ensureError struct { 1050 errs []error 1051 } 1052 1053 func (e *ensureError) Error() string { 1054 if len(e.errs) == 1 { 1055 return fmt.Sprintf("devicemgr: %v", e.errs[0]) 1056 } 1057 parts := []string{"devicemgr:"} 1058 for _, e := range e.errs { 1059 parts = append(parts, e.Error()) 1060 } 1061 return strings.Join(parts, "\n - ") 1062 } 1063 1064 // no \n allowed in warnings 1065 var seedFailureFmt = `seeding failed with: %v. This indicates an error in your distribution, please see https://forum.snapcraft.io/t/16341 for more information.` 1066 1067 // Ensure implements StateManager.Ensure. 1068 func (m *DeviceManager) Ensure() error { 1069 var errs []error 1070 1071 if err := m.ensureSeeded(); err != nil { 1072 m.state.Lock() 1073 m.state.Warnf(seedFailureFmt, err) 1074 m.state.Unlock() 1075 errs = append(errs, fmt.Errorf("cannot seed: %v", err)) 1076 } 1077 1078 if !m.preseed { 1079 if err := m.ensureCloudInitRestricted(); err != nil { 1080 errs = append(errs, err) 1081 } 1082 1083 if err := m.ensureOperational(); err != nil { 1084 errs = append(errs, err) 1085 } 1086 1087 if err := m.ensureBootOk(); err != nil { 1088 errs = append(errs, err) 1089 } 1090 1091 if err := m.ensureSeedInConfig(); err != nil { 1092 errs = append(errs, err) 1093 } 1094 1095 if err := m.ensureInstalled(); err != nil { 1096 errs = append(errs, err) 1097 } 1098 } 1099 1100 if len(errs) > 0 { 1101 return &ensureError{errs} 1102 } 1103 1104 return nil 1105 } 1106 1107 var errNoSaveSupport = errors.New("no save directory before UC20") 1108 1109 // withSaveDir invokes a function making sure save dir is available. 1110 // Under UC16/18 it returns errNoSaveSupport 1111 // For UC20 it also checks that ubuntu-save is available/mounted. 1112 func (m *DeviceManager) withSaveDir(f func() error) error { 1113 // we use the model to check whether this is a UC20 device 1114 model, err := m.Model() 1115 if err == state.ErrNoState { 1116 return fmt.Errorf("internal error: cannot access save dir before a model is set") 1117 } 1118 if err != nil { 1119 return err 1120 } 1121 if model.Grade() == asserts.ModelGradeUnset { 1122 return errNoSaveSupport 1123 } 1124 // at this point we need save available 1125 if !m.saveAvailable { 1126 return fmt.Errorf("internal error: save dir is unavailable") 1127 } 1128 1129 return f() 1130 } 1131 1132 // withSaveAssertDB invokes a function making the save device assertion 1133 // backup database available to it. 1134 // Under UC16/18 it returns errNoSaveSupport 1135 // For UC20 it also checks that ubuntu-save is available/mounted. 1136 func (m *DeviceManager) withSaveAssertDB(f func(*asserts.Database) error) error { 1137 return m.withSaveDir(func() error { 1138 // open an ancillary backup assertion database in save/device 1139 assertDB, err := sysdb.OpenAt(dirs.SnapDeviceSaveDir) 1140 if err != nil { 1141 return err 1142 } 1143 return f(assertDB) 1144 }) 1145 } 1146 1147 // withKeypairMgr invokes a function making the device KeypairManager 1148 // available to it. 1149 // It uses the right location for the manager depending on UC16/18 vs 20, 1150 // the latter uses ubuntu-save. 1151 // For UC20 it also checks that ubuntu-save is available/mounted. 1152 func (m *DeviceManager) withKeypairMgr(f func(asserts.KeypairManager) error) error { 1153 // we use the model to check whether this is a UC20 device 1154 // TODO: during a theoretical UC18->20 remodel the location of 1155 // keypair manager keys would move, we will need dedicated code 1156 // to deal with that, this code typically will return the old location 1157 // until a restart 1158 model, err := m.Model() 1159 if err == state.ErrNoState { 1160 return fmt.Errorf("internal error: cannot access device keypair manager before a model is set") 1161 } 1162 if err != nil { 1163 return err 1164 } 1165 underSave := false 1166 if model.Grade() != asserts.ModelGradeUnset { 1167 // on UC20 the keys are kept under the save dir 1168 underSave = true 1169 } 1170 where := dirs.SnapDeviceDir 1171 if underSave { 1172 // at this point we need save available 1173 if !m.saveAvailable { 1174 return fmt.Errorf("internal error: cannot access device keypair manager if ubuntu-save is unavailable") 1175 } 1176 where = dirs.SnapDeviceSaveDir 1177 } 1178 keypairMgr := m.cachedKeypairMgr 1179 if keypairMgr == nil { 1180 var err error 1181 keypairMgr, err = asserts.OpenFSKeypairManager(where) 1182 if err != nil { 1183 return err 1184 } 1185 m.cachedKeypairMgr = keypairMgr 1186 } 1187 return f(keypairMgr) 1188 } 1189 1190 // TODO:UC20: we need proper encapsulated support to read 1191 // tpm-policy-auth-key from save if the latter can get unmounted on 1192 // demand 1193 1194 func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { 1195 device, err := m.device() 1196 if err != nil { 1197 return nil, err 1198 } 1199 1200 if device.KeyID == "" { 1201 return nil, state.ErrNoState 1202 } 1203 1204 var privKey asserts.PrivateKey 1205 err = m.withKeypairMgr(func(keypairMgr asserts.KeypairManager) (err error) { 1206 privKey, err = keypairMgr.Get(device.KeyID) 1207 if err != nil { 1208 return fmt.Errorf("cannot read device key pair: %v", err) 1209 } 1210 return nil 1211 }) 1212 if err != nil { 1213 return nil, err 1214 } 1215 return privKey, nil 1216 } 1217 1218 // Registered returns a channel that is closed when the device is known to have been registered. 1219 func (m *DeviceManager) Registered() <-chan struct{} { 1220 return m.reg 1221 } 1222 1223 // device returns current device state. 1224 func (m *DeviceManager) device() (*auth.DeviceState, error) { 1225 return internal.Device(m.state) 1226 } 1227 1228 // setDevice sets the device details in the state. 1229 func (m *DeviceManager) setDevice(device *auth.DeviceState) error { 1230 return internal.SetDevice(m.state, device) 1231 } 1232 1233 // Model returns the device model assertion. 1234 func (m *DeviceManager) Model() (*asserts.Model, error) { 1235 return findModel(m.state) 1236 } 1237 1238 // Serial returns the device serial assertion. 1239 func (m *DeviceManager) Serial() (*asserts.Serial, error) { 1240 return findSerial(m.state, nil) 1241 } 1242 1243 type SystemAction struct { 1244 Title string 1245 Mode string 1246 } 1247 1248 type System struct { 1249 // Current is true when the system running now was installed from that 1250 // seed 1251 Current bool 1252 // Label of the seed system 1253 Label string 1254 // Model assertion of the system 1255 Model *asserts.Model 1256 // Brand information 1257 Brand *asserts.Account 1258 // Actions available for this system 1259 Actions []SystemAction 1260 } 1261 1262 var defaultSystemActions = []SystemAction{ 1263 {Title: "Install", Mode: "install"}, 1264 } 1265 var currentSystemActions = []SystemAction{ 1266 {Title: "Reinstall", Mode: "install"}, 1267 {Title: "Recover", Mode: "recover"}, 1268 {Title: "Run normally", Mode: "run"}, 1269 } 1270 var recoverSystemActions = []SystemAction{ 1271 {Title: "Reinstall", Mode: "install"}, 1272 {Title: "Run normally", Mode: "run"}, 1273 } 1274 1275 var ErrNoSystems = errors.New("no systems seeds") 1276 1277 // Systems list the available recovery/seeding systems. Returns the list of 1278 // systems, ErrNoSystems when no systems seeds were found or other error. 1279 func (m *DeviceManager) Systems() ([]*System, error) { 1280 // it's tough luck when we cannot determine the current system seed 1281 systemMode := m.SystemMode() 1282 currentSys, _ := currentSystemForMode(m.state, systemMode) 1283 1284 systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*")) 1285 if err != nil && !os.IsNotExist(err) { 1286 return nil, fmt.Errorf("cannot list available systems: %v", err) 1287 } 1288 if len(systemLabels) == 0 { 1289 // maybe not a UC20 system 1290 return nil, ErrNoSystems 1291 } 1292 1293 var systems []*System 1294 for _, fpLabel := range systemLabels { 1295 label := filepath.Base(fpLabel) 1296 system, err := systemFromSeed(label, currentSys) 1297 if err != nil { 1298 // TODO:UC20 add a Broken field to the seed system like 1299 // we do for snap.Info 1300 logger.Noticef("cannot load system %q seed: %v", label, err) 1301 continue 1302 } 1303 systems = append(systems, system) 1304 } 1305 return systems, nil 1306 } 1307 1308 var ErrUnsupportedAction = errors.New("unsupported action") 1309 1310 // Reboot triggers a reboot into the given systemLabel and mode. 1311 // 1312 // When called without a systemLabel and without a mode it will just 1313 // trigger a regular reboot. 1314 // 1315 // When called without a systemLabel but with a mode it will use 1316 // the current system to enter the given mode. 1317 // 1318 // Note that "recover" and "run" modes are only available for the 1319 // current system. 1320 func (m *DeviceManager) Reboot(systemLabel, mode string) error { 1321 rebootCurrent := func() { 1322 logger.Noticef("rebooting system") 1323 m.state.RequestRestart(state.RestartSystemNow) 1324 } 1325 1326 // most simple case: just reboot 1327 if systemLabel == "" && mode == "" { 1328 m.state.Lock() 1329 defer m.state.Unlock() 1330 1331 rebootCurrent() 1332 return nil 1333 } 1334 1335 // no systemLabel means "current" so get the current system label 1336 if systemLabel == "" { 1337 systemMode := m.SystemMode() 1338 currentSys, err := currentSystemForMode(m.state, systemMode) 1339 if err != nil { 1340 return fmt.Errorf("cannot get current system: %v", err) 1341 } 1342 systemLabel = currentSys.System 1343 } 1344 1345 switched := func(systemLabel string, sysAction *SystemAction) { 1346 logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode) 1347 m.state.RequestRestart(state.RestartSystemNow) 1348 } 1349 // even if we are already in the right mode we restart here by 1350 // passing rebootCurrent as this is what the user requested 1351 return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched) 1352 } 1353 1354 // RequestSystemAction requests the provided system to be run in a 1355 // given mode as specified by action. 1356 // A system reboot will be requested when the request can be 1357 // successfully carried out. 1358 func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error { 1359 if systemLabel == "" { 1360 return fmt.Errorf("internal error: system label is unset") 1361 } 1362 1363 nop := func() {} 1364 switched := func(systemLabel string, sysAction *SystemAction) { 1365 logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title) 1366 m.state.RequestRestart(state.RestartSystemNow) 1367 } 1368 // we do nothing (nop) if the mode and system are the same 1369 return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched) 1370 } 1371 1372 // switchToSystemAndMode switches to given systemLabel and mode. 1373 // If the systemLabel and mode are the same as current, it calls 1374 // sameSystemAndMode. If successful otherwise it calls switched. Both 1375 // are called with the state lock held. 1376 func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error { 1377 if err := checkSystemRequestConflict(m.state, systemLabel); err != nil { 1378 return err 1379 } 1380 1381 systemMode := m.SystemMode() 1382 // ignore the error to be robust in scenarios that 1383 // dont' stricly require currentSys to be carried through. 1384 // make sure that currentSys == nil does not break 1385 // the code below! 1386 // TODO: should we log the error? 1387 currentSys, _ := currentSystemForMode(m.state, systemMode) 1388 1389 systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel) 1390 if _, err := os.Stat(systemSeedDir); err != nil { 1391 // XXX: should we wrap this instead return a naked stat error? 1392 return err 1393 } 1394 system, err := systemFromSeed(systemLabel, currentSys) 1395 if err != nil { 1396 return fmt.Errorf("cannot load seed system: %v", err) 1397 } 1398 1399 var sysAction *SystemAction 1400 for _, act := range system.Actions { 1401 if mode == act.Mode { 1402 sysAction = &act 1403 break 1404 } 1405 } 1406 if sysAction == nil { 1407 // XXX: provide more context here like what mode was requested? 1408 return ErrUnsupportedAction 1409 } 1410 1411 // XXX: requested mode is valid; only current system has 'run' and 1412 // recover 'actions' 1413 1414 switch systemMode { 1415 case "recover", "run": 1416 // if going from recover to recover or from run to run and the systems 1417 // are the same do nothing 1418 if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System { 1419 m.state.Lock() 1420 defer m.state.Unlock() 1421 sameSystemAndMode() 1422 return nil 1423 } 1424 case "install": 1425 // requesting system actions in install mode does not make sense atm 1426 // 1427 // TODO:UC20: maybe factory hooks will be able to something like 1428 // this? 1429 return ErrUnsupportedAction 1430 default: 1431 // probably test device manager mocking problem, or also potentially 1432 // missing modeenv 1433 return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode) 1434 } 1435 1436 m.state.Lock() 1437 defer m.state.Unlock() 1438 1439 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1440 if err != nil { 1441 return err 1442 } 1443 if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil { 1444 return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err) 1445 } 1446 1447 switched(systemLabel, sysAction) 1448 return nil 1449 } 1450 1451 // implement storecontext.Backend 1452 1453 type storeContextBackend struct { 1454 *DeviceManager 1455 } 1456 1457 func (scb storeContextBackend) Device() (*auth.DeviceState, error) { 1458 return scb.DeviceManager.device() 1459 } 1460 1461 func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error { 1462 return scb.DeviceManager.setDevice(device) 1463 } 1464 1465 func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) { 1466 st := scb.DeviceManager.state 1467 return proxyStore(st, config.NewTransaction(st)) 1468 } 1469 1470 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 1471 func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 1472 if serial == nil { 1473 // shouldn't happen, but be safe 1474 return nil, fmt.Errorf("internal error: cannot sign a session request without a serial") 1475 } 1476 1477 privKey, err := scb.DeviceManager.keyPair() 1478 if err == state.ErrNoState { 1479 return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key") 1480 } 1481 if err != nil { 1482 return nil, err 1483 } 1484 1485 a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ 1486 "brand-id": serial.BrandID(), 1487 "model": serial.Model(), 1488 "serial": serial.Serial(), 1489 "nonce": nonce, 1490 "timestamp": time.Now().UTC().Format(time.RFC3339), 1491 }, nil, privKey) 1492 if err != nil { 1493 return nil, err 1494 } 1495 1496 return a.(*asserts.DeviceSessionRequest), nil 1497 } 1498 1499 func (m *DeviceManager) StoreContextBackend() storecontext.Backend { 1500 return storeContextBackend{m} 1501 } 1502 1503 func (m *DeviceManager) hasFDESetupHook() (bool, error) { 1504 // state must be locked 1505 st := m.state 1506 1507 deviceCtx, err := DeviceCtx(st, nil, nil) 1508 if err != nil { 1509 return false, fmt.Errorf("cannot get device context: %v", err) 1510 } 1511 1512 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1513 if err != nil { 1514 return false, fmt.Errorf("cannot get kernel info: %v", err) 1515 } 1516 return hasFDESetupHookInKernel(kernelInfo), nil 1517 } 1518 1519 func (m *DeviceManager) runFDESetupHook(op string, params *boot.FDESetupHookParams) ([]byte, error) { 1520 // TODO:UC20: when this runs on refresh we need to be very careful 1521 // that we never run this when the kernel is not fully configured 1522 // i.e. when there are no security profiles for the hook 1523 1524 // state must be locked 1525 st := m.state 1526 1527 deviceCtx, err := DeviceCtx(st, nil, nil) 1528 if err != nil { 1529 return nil, fmt.Errorf("cannot get device context to run fde-setup hook: %v", err) 1530 } 1531 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1532 if err != nil { 1533 return nil, fmt.Errorf("cannot get kernel info to run fde-setup hook: %v", err) 1534 } 1535 hooksup := &hookstate.HookSetup{ 1536 Snap: kernelInfo.InstanceName(), 1537 Revision: kernelInfo.Revision, 1538 Hook: "fde-setup", 1539 // XXX: should this be configurable somehow? 1540 Timeout: 5 * time.Minute, 1541 } 1542 req := &fde.SetupRequest{ 1543 Op: op, 1544 Key: params.Key[:], 1545 KeyName: params.KeyName, 1546 // TODO: include boot chains 1547 } 1548 for _, model := range params.Models { 1549 req.Models = append(req.Models, map[string]string{ 1550 "series": model.Series(), 1551 "brand-id": model.BrandID(), 1552 "model": model.Model(), 1553 "grade": string(model.Grade()), 1554 "signkey-id": model.SignKeyID(), 1555 }) 1556 } 1557 contextData := map[string]interface{}{ 1558 "fde-setup-request": req, 1559 } 1560 st.Unlock() 1561 defer st.Lock() 1562 context, err := m.hookMgr.EphemeralRunHook(context.Background(), hooksup, contextData) 1563 if err != nil { 1564 return nil, fmt.Errorf("cannot run hook for %q: %v", op, err) 1565 } 1566 // the hook is expected to call "snapctl fde-setup-result" which 1567 // will set the "fde-setup-result" value on the task 1568 var hookResult []byte 1569 context.Lock() 1570 err = context.Get("fde-setup-result", &hookResult) 1571 context.Unlock() 1572 if err != nil { 1573 return nil, fmt.Errorf("cannot get result from fde-setup hook %q: %v", op, err) 1574 } 1575 1576 return hookResult, nil 1577 } 1578 1579 func (m *DeviceManager) checkFDEFeatures(st *state.State) error { 1580 // Run fde-setup hook with "op":"features". If the hook 1581 // returns any {"features":[...]} reply we consider the 1582 // hardware supported. If the hook errors or if it returns 1583 // {"error":"hardware-unsupported"} we don't. 1584 output, err := m.runFDESetupHook("features", &boot.FDESetupHookParams{}) 1585 if err != nil { 1586 return err 1587 } 1588 var res struct { 1589 Features []string `json:"features"` 1590 Error string `json:"error"` 1591 } 1592 if err := json.Unmarshal(output, &res); err != nil { 1593 return fmt.Errorf("cannot parse hook output %q: %v", output, err) 1594 } 1595 if res.Features == nil && res.Error == "" { 1596 return fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`) 1597 } 1598 if res.Error != "" { 1599 return fmt.Errorf("cannot use hook: it returned error: %v", res.Error) 1600 } 1601 return nil 1602 } 1603 1604 func hasFDESetupHookInKernel(kernelInfo *snap.Info) bool { 1605 _, ok := kernelInfo.Hooks["fde-setup"] 1606 return ok 1607 } 1608 1609 type fdeSetupHandler struct { 1610 context *hookstate.Context 1611 } 1612 1613 func newFdeSetupHandler(ctx *hookstate.Context) hookstate.Handler { 1614 return fdeSetupHandler{context: ctx} 1615 } 1616 1617 func (h fdeSetupHandler) Before() error { 1618 return nil 1619 } 1620 1621 func (h fdeSetupHandler) Done() error { 1622 return nil 1623 } 1624 1625 func (h fdeSetupHandler) Error(err error) error { 1626 return nil 1627 }