github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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 func (m *DeviceManager) ensureTriedRecoverySystem() error { 1116 if release.OnClassic { 1117 return nil 1118 } 1119 // nothing to do if not UC20 and run mode 1120 if m.SystemMode(SysHasModeenv) != "run" { 1121 return nil 1122 } 1123 if m.ensureTriedRecoverySystemRan { 1124 return nil 1125 } 1126 1127 m.state.Lock() 1128 defer m.state.Unlock() 1129 1130 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1131 if err != nil && err != state.ErrNoState { 1132 return err 1133 } 1134 outcome, label, err := boot.InspectTryRecoverySystemOutcome(deviceCtx) 1135 if err != nil { 1136 if !boot.IsInconsistentRecoverySystemState(err) { 1137 return err 1138 } 1139 // boot variables state was inconsistent 1140 logger.Noticef("tried recovery system outcome error: %v", err) 1141 } 1142 switch outcome { 1143 case boot.TryRecoverySystemOutcomeSuccess: 1144 logger.Noticef("tried recovery system %q was successful", label) 1145 if err := m.appendTriedRecoverySystem(label); err != nil { 1146 return err 1147 } 1148 case boot.TryRecoverySystemOutcomeFailure: 1149 logger.Noticef("tried recovery system %q failed", label) 1150 case boot.TryRecoverySystemOutcomeInconsistent: 1151 logger.Noticef("inconsistent outcome of a tried recovery system") 1152 case boot.TryRecoverySystemOutcomeNoneTried: 1153 // no system was tried 1154 } 1155 if outcome != boot.TryRecoverySystemOutcomeNoneTried { 1156 if err := boot.ClearTryRecoverySystem(deviceCtx, label); err != nil { 1157 logger.Noticef("cannot clear tried recovery system status: %v", err) 1158 return err 1159 } 1160 } 1161 1162 m.ensureTriedRecoverySystemRan = true 1163 return nil 1164 } 1165 1166 type ensureError struct { 1167 errs []error 1168 } 1169 1170 func (e *ensureError) Error() string { 1171 if len(e.errs) == 1 { 1172 return fmt.Sprintf("devicemgr: %v", e.errs[0]) 1173 } 1174 parts := []string{"devicemgr:"} 1175 for _, e := range e.errs { 1176 parts = append(parts, e.Error()) 1177 } 1178 return strings.Join(parts, "\n - ") 1179 } 1180 1181 // no \n allowed in warnings 1182 var seedFailureFmt = `seeding failed with: %v. This indicates an error in your distribution, please see https://forum.snapcraft.io/t/16341 for more information.` 1183 1184 // Ensure implements StateManager.Ensure. 1185 func (m *DeviceManager) Ensure() error { 1186 var errs []error 1187 1188 if err := m.ensureSeeded(); err != nil { 1189 m.state.Lock() 1190 m.state.Warnf(seedFailureFmt, err) 1191 m.state.Unlock() 1192 errs = append(errs, fmt.Errorf("cannot seed: %v", err)) 1193 } 1194 1195 if !m.preseed { 1196 if err := m.ensureCloudInitRestricted(); err != nil { 1197 errs = append(errs, err) 1198 } 1199 1200 if err := m.ensureOperational(); err != nil { 1201 errs = append(errs, err) 1202 } 1203 1204 if err := m.ensureBootOk(); err != nil { 1205 errs = append(errs, err) 1206 } 1207 1208 if err := m.ensureSeedInConfig(); err != nil { 1209 errs = append(errs, err) 1210 } 1211 1212 if err := m.ensureInstalled(); err != nil { 1213 errs = append(errs, err) 1214 } 1215 1216 if err := m.ensureTriedRecoverySystem(); err != nil { 1217 errs = append(errs, err) 1218 } 1219 } 1220 1221 if len(errs) > 0 { 1222 return &ensureError{errs} 1223 } 1224 1225 return nil 1226 } 1227 1228 var errNoSaveSupport = errors.New("no save directory before UC20") 1229 1230 // withSaveDir invokes a function making sure save dir is available. 1231 // Under UC16/18 it returns errNoSaveSupport 1232 // For UC20 it also checks that ubuntu-save is available/mounted. 1233 func (m *DeviceManager) withSaveDir(f func() error) error { 1234 // we use the model to check whether this is a UC20 device 1235 model, err := m.Model() 1236 if err == state.ErrNoState { 1237 return fmt.Errorf("internal error: cannot access save dir before a model is set") 1238 } 1239 if err != nil { 1240 return err 1241 } 1242 if model.Grade() == asserts.ModelGradeUnset { 1243 return errNoSaveSupport 1244 } 1245 // at this point we need save available 1246 if !m.saveAvailable { 1247 return fmt.Errorf("internal error: save dir is unavailable") 1248 } 1249 1250 return f() 1251 } 1252 1253 // withSaveAssertDB invokes a function making the save device assertion 1254 // backup database available to it. 1255 // Under UC16/18 it returns errNoSaveSupport 1256 // For UC20 it also checks that ubuntu-save is available/mounted. 1257 func (m *DeviceManager) withSaveAssertDB(f func(*asserts.Database) error) error { 1258 return m.withSaveDir(func() error { 1259 // open an ancillary backup assertion database in save/device 1260 assertDB, err := sysdb.OpenAt(dirs.SnapDeviceSaveDir) 1261 if err != nil { 1262 return err 1263 } 1264 return f(assertDB) 1265 }) 1266 } 1267 1268 // withKeypairMgr invokes a function making the device KeypairManager 1269 // available to it. 1270 // It uses the right location for the manager depending on UC16/18 vs 20, 1271 // the latter uses ubuntu-save. 1272 // For UC20 it also checks that ubuntu-save is available/mounted. 1273 func (m *DeviceManager) withKeypairMgr(f func(asserts.KeypairManager) error) error { 1274 // we use the model to check whether this is a UC20 device 1275 // TODO: during a theoretical UC18->20 remodel the location of 1276 // keypair manager keys would move, we will need dedicated code 1277 // to deal with that, this code typically will return the old location 1278 // until a restart 1279 model, err := m.Model() 1280 if err == state.ErrNoState { 1281 return fmt.Errorf("internal error: cannot access device keypair manager before a model is set") 1282 } 1283 if err != nil { 1284 return err 1285 } 1286 underSave := false 1287 if model.Grade() != asserts.ModelGradeUnset { 1288 // on UC20 the keys are kept under the save dir 1289 underSave = true 1290 } 1291 where := dirs.SnapDeviceDir 1292 if underSave { 1293 // at this point we need save available 1294 if !m.saveAvailable { 1295 return fmt.Errorf("internal error: cannot access device keypair manager if ubuntu-save is unavailable") 1296 } 1297 where = dirs.SnapDeviceSaveDir 1298 } 1299 keypairMgr := m.cachedKeypairMgr 1300 if keypairMgr == nil { 1301 var err error 1302 keypairMgr, err = asserts.OpenFSKeypairManager(where) 1303 if err != nil { 1304 return err 1305 } 1306 m.cachedKeypairMgr = keypairMgr 1307 } 1308 return f(keypairMgr) 1309 } 1310 1311 // TODO:UC20: we need proper encapsulated support to read 1312 // tpm-policy-auth-key from save if the latter can get unmounted on 1313 // demand 1314 1315 func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { 1316 device, err := m.device() 1317 if err != nil { 1318 return nil, err 1319 } 1320 1321 if device.KeyID == "" { 1322 return nil, state.ErrNoState 1323 } 1324 1325 var privKey asserts.PrivateKey 1326 err = m.withKeypairMgr(func(keypairMgr asserts.KeypairManager) (err error) { 1327 privKey, err = keypairMgr.Get(device.KeyID) 1328 if err != nil { 1329 return fmt.Errorf("cannot read device key pair: %v", err) 1330 } 1331 return nil 1332 }) 1333 if err != nil { 1334 return nil, err 1335 } 1336 return privKey, nil 1337 } 1338 1339 // Registered returns a channel that is closed when the device is known to have been registered. 1340 func (m *DeviceManager) Registered() <-chan struct{} { 1341 return m.reg 1342 } 1343 1344 // device returns current device state. 1345 func (m *DeviceManager) device() (*auth.DeviceState, error) { 1346 return internal.Device(m.state) 1347 } 1348 1349 // setDevice sets the device details in the state. 1350 func (m *DeviceManager) setDevice(device *auth.DeviceState) error { 1351 return internal.SetDevice(m.state, device) 1352 } 1353 1354 // Model returns the device model assertion. 1355 func (m *DeviceManager) Model() (*asserts.Model, error) { 1356 return findModel(m.state) 1357 } 1358 1359 // Serial returns the device serial assertion. 1360 func (m *DeviceManager) Serial() (*asserts.Serial, error) { 1361 return findSerial(m.state, nil) 1362 } 1363 1364 type SystemModeInfo struct { 1365 Mode string 1366 HasModeenv bool 1367 Seeded bool 1368 BootFlags []string 1369 HostDataLocations []string 1370 } 1371 1372 // SystemModeInfo returns details about the current system mode the device is in. 1373 func (m *DeviceManager) SystemModeInfo() (*SystemModeInfo, error) { 1374 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1375 if err == state.ErrNoState { 1376 return nil, fmt.Errorf("cannot report system mode information before device model is acknowledged") 1377 } 1378 if err != nil { 1379 return nil, err 1380 } 1381 1382 var seeded bool 1383 err = m.state.Get("seeded", &seeded) 1384 if err != nil && err != state.ErrNoState { 1385 return nil, err 1386 } 1387 1388 mode := deviceCtx.SystemMode() 1389 smi := SystemModeInfo{ 1390 Mode: mode, 1391 HasModeenv: deviceCtx.HasModeenv(), 1392 Seeded: seeded, 1393 } 1394 if smi.HasModeenv { 1395 bootFlags, err := boot.BootFlags(deviceCtx) 1396 if err != nil { 1397 return nil, err 1398 } 1399 smi.BootFlags = bootFlags 1400 1401 hostDataLocs, err := boot.HostUbuntuDataForMode(mode) 1402 if err != nil { 1403 return nil, err 1404 } 1405 smi.HostDataLocations = hostDataLocs 1406 } 1407 return &smi, nil 1408 } 1409 1410 type SystemAction struct { 1411 Title string 1412 Mode string 1413 } 1414 1415 type System struct { 1416 // Current is true when the system running now was installed from that 1417 // seed 1418 Current bool 1419 // Label of the seed system 1420 Label string 1421 // Model assertion of the system 1422 Model *asserts.Model 1423 // Brand information 1424 Brand *asserts.Account 1425 // Actions available for this system 1426 Actions []SystemAction 1427 } 1428 1429 var defaultSystemActions = []SystemAction{ 1430 {Title: "Install", Mode: "install"}, 1431 } 1432 var currentSystemActions = []SystemAction{ 1433 {Title: "Reinstall", Mode: "install"}, 1434 {Title: "Recover", Mode: "recover"}, 1435 {Title: "Run normally", Mode: "run"}, 1436 } 1437 var recoverSystemActions = []SystemAction{ 1438 {Title: "Reinstall", Mode: "install"}, 1439 {Title: "Run normally", Mode: "run"}, 1440 } 1441 1442 var ErrNoSystems = errors.New("no systems seeds") 1443 1444 // Systems list the available recovery/seeding systems. Returns the list of 1445 // systems, ErrNoSystems when no systems seeds were found or other error. 1446 func (m *DeviceManager) Systems() ([]*System, error) { 1447 // it's tough luck when we cannot determine the current system seed 1448 systemMode := m.SystemMode(SysAny) 1449 currentSys, _ := currentSystemForMode(m.state, systemMode) 1450 1451 systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*")) 1452 if err != nil && !os.IsNotExist(err) { 1453 return nil, fmt.Errorf("cannot list available systems: %v", err) 1454 } 1455 if len(systemLabels) == 0 { 1456 // maybe not a UC20 system 1457 return nil, ErrNoSystems 1458 } 1459 1460 var systems []*System 1461 for _, fpLabel := range systemLabels { 1462 label := filepath.Base(fpLabel) 1463 system, err := systemFromSeed(label, currentSys) 1464 if err != nil { 1465 // TODO:UC20 add a Broken field to the seed system like 1466 // we do for snap.Info 1467 logger.Noticef("cannot load system %q seed: %v", label, err) 1468 continue 1469 } 1470 systems = append(systems, system) 1471 } 1472 return systems, nil 1473 } 1474 1475 var ErrUnsupportedAction = errors.New("unsupported action") 1476 1477 // Reboot triggers a reboot into the given systemLabel and mode. 1478 // 1479 // When called without a systemLabel and without a mode it will just 1480 // trigger a regular reboot. 1481 // 1482 // When called without a systemLabel but with a mode it will use 1483 // the current system to enter the given mode. 1484 // 1485 // Note that "recover" and "run" modes are only available for the 1486 // current system. 1487 func (m *DeviceManager) Reboot(systemLabel, mode string) error { 1488 rebootCurrent := func() { 1489 logger.Noticef("rebooting system") 1490 m.state.RequestRestart(state.RestartSystemNow) 1491 } 1492 1493 // most simple case: just reboot 1494 if systemLabel == "" && mode == "" { 1495 m.state.Lock() 1496 defer m.state.Unlock() 1497 1498 rebootCurrent() 1499 return nil 1500 } 1501 1502 // no systemLabel means "current" so get the current system label 1503 if systemLabel == "" { 1504 systemMode := m.SystemMode(SysAny) 1505 currentSys, err := currentSystemForMode(m.state, systemMode) 1506 if err != nil { 1507 return fmt.Errorf("cannot get current system: %v", err) 1508 } 1509 systemLabel = currentSys.System 1510 } 1511 1512 switched := func(systemLabel string, sysAction *SystemAction) { 1513 logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode) 1514 m.state.RequestRestart(state.RestartSystemNow) 1515 } 1516 // even if we are already in the right mode we restart here by 1517 // passing rebootCurrent as this is what the user requested 1518 return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched) 1519 } 1520 1521 // RequestSystemAction requests the provided system to be run in a 1522 // given mode as specified by action. 1523 // A system reboot will be requested when the request can be 1524 // successfully carried out. 1525 func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error { 1526 if systemLabel == "" { 1527 return fmt.Errorf("internal error: system label is unset") 1528 } 1529 1530 nop := func() {} 1531 switched := func(systemLabel string, sysAction *SystemAction) { 1532 logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title) 1533 m.state.RequestRestart(state.RestartSystemNow) 1534 } 1535 // we do nothing (nop) if the mode and system are the same 1536 return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched) 1537 } 1538 1539 // switchToSystemAndMode switches to given systemLabel and mode. 1540 // If the systemLabel and mode are the same as current, it calls 1541 // sameSystemAndMode. If successful otherwise it calls switched. Both 1542 // are called with the state lock held. 1543 func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error { 1544 if err := checkSystemRequestConflict(m.state, systemLabel); err != nil { 1545 return err 1546 } 1547 1548 systemMode := m.SystemMode(SysAny) 1549 // ignore the error to be robust in scenarios that 1550 // dont' stricly require currentSys to be carried through. 1551 // make sure that currentSys == nil does not break 1552 // the code below! 1553 // TODO: should we log the error? 1554 currentSys, _ := currentSystemForMode(m.state, systemMode) 1555 1556 systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel) 1557 if _, err := os.Stat(systemSeedDir); err != nil { 1558 // XXX: should we wrap this instead return a naked stat error? 1559 return err 1560 } 1561 system, err := systemFromSeed(systemLabel, currentSys) 1562 if err != nil { 1563 return fmt.Errorf("cannot load seed system: %v", err) 1564 } 1565 1566 var sysAction *SystemAction 1567 for _, act := range system.Actions { 1568 if mode == act.Mode { 1569 sysAction = &act 1570 break 1571 } 1572 } 1573 if sysAction == nil { 1574 // XXX: provide more context here like what mode was requested? 1575 return ErrUnsupportedAction 1576 } 1577 1578 // XXX: requested mode is valid; only current system has 'run' and 1579 // recover 'actions' 1580 1581 switch systemMode { 1582 case "recover", "run": 1583 // if going from recover to recover or from run to run and the systems 1584 // are the same do nothing 1585 if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System { 1586 m.state.Lock() 1587 defer m.state.Unlock() 1588 sameSystemAndMode() 1589 return nil 1590 } 1591 case "install": 1592 // requesting system actions in install mode does not make sense atm 1593 // 1594 // TODO:UC20: maybe factory hooks will be able to something like 1595 // this? 1596 return ErrUnsupportedAction 1597 default: 1598 // probably test device manager mocking problem, or also potentially 1599 // missing modeenv 1600 return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode) 1601 } 1602 1603 m.state.Lock() 1604 defer m.state.Unlock() 1605 1606 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1607 if err != nil { 1608 return err 1609 } 1610 if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil { 1611 return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err) 1612 } 1613 1614 switched(systemLabel, sysAction) 1615 return nil 1616 } 1617 1618 // implement storecontext.Backend 1619 1620 type storeContextBackend struct { 1621 *DeviceManager 1622 } 1623 1624 func (scb storeContextBackend) Device() (*auth.DeviceState, error) { 1625 return scb.DeviceManager.device() 1626 } 1627 1628 func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error { 1629 return scb.DeviceManager.setDevice(device) 1630 } 1631 1632 func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) { 1633 st := scb.DeviceManager.state 1634 return proxyStore(st, config.NewTransaction(st)) 1635 } 1636 1637 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 1638 func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 1639 if serial == nil { 1640 // shouldn't happen, but be safe 1641 return nil, fmt.Errorf("internal error: cannot sign a session request without a serial") 1642 } 1643 1644 privKey, err := scb.DeviceManager.keyPair() 1645 if err == state.ErrNoState { 1646 return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key") 1647 } 1648 if err != nil { 1649 return nil, err 1650 } 1651 1652 a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ 1653 "brand-id": serial.BrandID(), 1654 "model": serial.Model(), 1655 "serial": serial.Serial(), 1656 "nonce": nonce, 1657 "timestamp": time.Now().UTC().Format(time.RFC3339), 1658 }, nil, privKey) 1659 if err != nil { 1660 return nil, err 1661 } 1662 1663 return a.(*asserts.DeviceSessionRequest), nil 1664 } 1665 1666 func (m *DeviceManager) StoreContextBackend() storecontext.Backend { 1667 return storeContextBackend{m} 1668 } 1669 1670 func (m *DeviceManager) hasFDESetupHook() (bool, error) { 1671 // state must be locked 1672 st := m.state 1673 1674 deviceCtx, err := DeviceCtx(st, nil, nil) 1675 if err != nil { 1676 return false, fmt.Errorf("cannot get device context: %v", err) 1677 } 1678 1679 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1680 if err != nil { 1681 return false, fmt.Errorf("cannot get kernel info: %v", err) 1682 } 1683 return hasFDESetupHookInKernel(kernelInfo), nil 1684 } 1685 1686 func (m *DeviceManager) runFDESetupHook(req *fde.SetupRequest) ([]byte, error) { 1687 // TODO:UC20: when this runs on refresh we need to be very careful 1688 // that we never run this when the kernel is not fully configured 1689 // i.e. when there are no security profiles for the hook 1690 1691 // state must be locked 1692 st := m.state 1693 1694 deviceCtx, err := DeviceCtx(st, nil, nil) 1695 if err != nil { 1696 return nil, fmt.Errorf("cannot get device context to run fde-setup hook: %v", err) 1697 } 1698 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1699 if err != nil { 1700 return nil, fmt.Errorf("cannot get kernel info to run fde-setup hook: %v", err) 1701 } 1702 hooksup := &hookstate.HookSetup{ 1703 Snap: kernelInfo.InstanceName(), 1704 Revision: kernelInfo.Revision, 1705 Hook: "fde-setup", 1706 // XXX: should this be configurable somehow? 1707 Timeout: 5 * time.Minute, 1708 } 1709 contextData := map[string]interface{}{ 1710 "fde-setup-request": req, 1711 } 1712 st.Unlock() 1713 defer st.Lock() 1714 context, err := m.hookMgr.EphemeralRunHook(context.Background(), hooksup, contextData) 1715 if err != nil { 1716 return nil, fmt.Errorf("cannot run hook for %q: %v", req.Op, err) 1717 } 1718 // the hook is expected to call "snapctl fde-setup-result" which 1719 // will set the "fde-setup-result" value on the task 1720 var hookOutput []byte 1721 context.Lock() 1722 err = context.Get("fde-setup-result", &hookOutput) 1723 context.Unlock() 1724 if err != nil { 1725 return nil, fmt.Errorf("cannot get result from fde-setup hook %q: %v", req.Op, err) 1726 } 1727 return hookOutput, nil 1728 } 1729 1730 func (m *DeviceManager) checkFDEFeatures(st *state.State) error { 1731 // TODO: move most of this to kernel/fde.Features 1732 // Run fde-setup hook with "op":"features". If the hook 1733 // returns any {"features":[...]} reply we consider the 1734 // hardware supported. If the hook errors or if it returns 1735 // {"error":"hardware-unsupported"} we don't. 1736 req := &fde.SetupRequest{ 1737 Op: "features", 1738 } 1739 output, err := m.runFDESetupHook(req) 1740 if err != nil { 1741 return err 1742 } 1743 var res struct { 1744 Features []string `json:"features"` 1745 Error string `json:"error"` 1746 } 1747 if err := json.Unmarshal(output, &res); err != nil { 1748 return fmt.Errorf("cannot parse hook output %q: %v", output, err) 1749 } 1750 if res.Features == nil && res.Error == "" { 1751 return fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`) 1752 } 1753 if res.Error != "" { 1754 return fmt.Errorf("cannot use hook: it returned error: %v", res.Error) 1755 } 1756 return nil 1757 } 1758 1759 func hasFDESetupHookInKernel(kernelInfo *snap.Info) bool { 1760 _, ok := kernelInfo.Hooks["fde-setup"] 1761 return ok 1762 } 1763 1764 type fdeSetupHandler struct { 1765 context *hookstate.Context 1766 } 1767 1768 func newFdeSetupHandler(ctx *hookstate.Context) hookstate.Handler { 1769 return fdeSetupHandler{context: ctx} 1770 } 1771 1772 func (h fdeSetupHandler) Before() error { 1773 return nil 1774 } 1775 1776 func (h fdeSetupHandler) Done() error { 1777 return nil 1778 } 1779 1780 func (h fdeSetupHandler) Error(err error) error { 1781 return nil 1782 }