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