github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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() (sysconfig.Device, *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) (bool, error) { return false, 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 type SysExpectation int 216 217 const ( 218 // SysAny indicates any system is appropriate. 219 SysAny SysExpectation = iota 220 // SysHasModeenv indicates only systems with modeenv are appropriate. 221 SysHasModeenv 222 ) 223 224 // SystemMode returns the current mode of the system. 225 // An expectation about the system controls the returned mode when 226 // none is set explicitly, as it's the case on pre-UC20 systems. In 227 // which case, with SysAny, the mode defaults to implicit "run", thus 228 // covering pre-UC20 systems. With SysHasModeeenv, as there is always 229 // an explicit mode in systems that use modeenv, no implicit default 230 // is used and thus "" is returned for pre-UC20 systems. 231 func (m *DeviceManager) SystemMode(sysExpect SysExpectation) string { 232 if m.sysMode == "" { 233 if sysExpect == SysHasModeenv { 234 return "" 235 } 236 return "run" 237 } 238 return m.sysMode 239 } 240 241 // StartUp implements StateStarterUp.Startup. 242 func (m *DeviceManager) StartUp() error { 243 // TODO:UC20: ubuntu-save needs to be mounted for recover too 244 if !release.OnClassic && m.SystemMode(SysHasModeenv) == "run" { 245 // UC20 and run mode => setup /var/lib/snapd/save 246 if err := m.maybeSetupUbuntuSave(); err != nil { 247 return fmt.Errorf("cannot set up ubuntu-save: %v", err) 248 } 249 } 250 251 // TODO: setup proper timings measurements for this 252 253 m.state.Lock() 254 defer m.state.Unlock() 255 return EarlyConfig(m.state, m.preloadGadget) 256 } 257 258 func (m *DeviceManager) maybeSetupUbuntuSave() error { 259 // only called for UC20 260 261 saveMounted, err := osutil.IsMounted(dirs.SnapSaveDir) 262 if err != nil { 263 return err 264 } 265 if saveMounted { 266 logger.Noticef("save already mounted under %v", dirs.SnapSaveDir) 267 m.saveAvailable = true 268 return nil 269 } 270 271 runMntSaveMounted, err := osutil.IsMounted(boot.InitramfsUbuntuSaveDir) 272 if err != nil { 273 return err 274 } 275 if !runMntSaveMounted { 276 // we don't have ubuntu-save, save will be used directly 277 logger.Noticef("no ubuntu-save mount") 278 m.saveAvailable = true 279 return nil 280 } 281 282 logger.Noticef("bind-mounting ubuntu-save under %v", dirs.SnapSaveDir) 283 284 err = systemd.New(systemd.SystemMode, progress.Null).Mount(boot.InitramfsUbuntuSaveDir, 285 dirs.SnapSaveDir, "-o", "bind") 286 if err != nil { 287 logger.Noticef("bind-mounting ubuntu-save failed %v", err) 288 return fmt.Errorf("cannot bind mount %v under %v: %v", boot.InitramfsUbuntuSaveDir, dirs.SnapSaveDir, err) 289 } 290 m.saveAvailable = true 291 return nil 292 } 293 294 type deviceMgrKey struct{} 295 296 func deviceMgr(st *state.State) *DeviceManager { 297 mgr := st.Cached(deviceMgrKey{}) 298 if mgr == nil { 299 panic("internal error: device manager is not yet associated with state") 300 } 301 return mgr.(*DeviceManager) 302 } 303 304 func (m *DeviceManager) CanStandby() bool { 305 var seeded bool 306 if err := m.state.Get("seeded", &seeded); err != nil { 307 return false 308 } 309 return seeded 310 } 311 312 func (m *DeviceManager) confirmRegistered() error { 313 m.state.Lock() 314 defer m.state.Unlock() 315 316 device, err := m.device() 317 if err != nil { 318 return err 319 } 320 321 if device.Serial != "" { 322 m.markRegistered() 323 } 324 return nil 325 } 326 327 func (m *DeviceManager) markRegistered() { 328 if m.registered { 329 return 330 } 331 m.registered = true 332 close(m.reg) 333 } 334 335 func gadgetUpdateBlocked(cand *state.Task, running []*state.Task) bool { 336 if cand.Kind() == "update-gadget-assets" && len(running) != 0 { 337 // update-gadget-assets must be the only task running 338 return true 339 } 340 for _, other := range running { 341 if other.Kind() == "update-gadget-assets" { 342 // no other task can be started when 343 // update-gadget-assets is running 344 return true 345 } 346 } 347 348 return false 349 } 350 351 func (m *DeviceManager) changeInFlight(kind string) bool { 352 for _, chg := range m.state.Changes() { 353 if chg.Kind() == kind && !chg.Status().Ready() { 354 // change already in motion 355 return true 356 } 357 } 358 return false 359 } 360 361 // helpers to keep count of attempts to get a serial, useful to decide 362 // to give up holding off trying to auto-refresh 363 364 type ensureOperationalAttemptsKey struct{} 365 366 func incEnsureOperationalAttempts(st *state.State) { 367 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 368 st.Cache(ensureOperationalAttemptsKey{}, cur+1) 369 } 370 371 func ensureOperationalAttempts(st *state.State) int { 372 cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int) 373 return cur 374 } 375 376 // ensureOperationalShouldBackoff returns whether we should abstain from 377 // further become-operational tentatives while its backoff interval is 378 // not expired. 379 func (m *DeviceManager) ensureOperationalShouldBackoff(now time.Time) bool { 380 if !m.lastBecomeOperationalAttempt.IsZero() && m.lastBecomeOperationalAttempt.Add(m.becomeOperationalBackoff).After(now) { 381 return true 382 } 383 if m.becomeOperationalBackoff == 0 { 384 m.becomeOperationalBackoff = 5 * time.Minute 385 } else { 386 newBackoff := m.becomeOperationalBackoff * 2 387 if newBackoff > (12 * time.Hour) { 388 newBackoff = 24 * time.Hour 389 } 390 m.becomeOperationalBackoff = newBackoff 391 } 392 m.lastBecomeOperationalAttempt = now 393 return false 394 } 395 396 func setClassicFallbackModel(st *state.State, device *auth.DeviceState) error { 397 err := assertstate.Add(st, sysdb.GenericClassicModel()) 398 if err != nil && !asserts.IsUnaccceptedUpdate(err) { 399 return fmt.Errorf(`cannot install "generic-classic" fallback model assertion: %v`, err) 400 } 401 device.Brand = "generic" 402 device.Model = "generic-classic" 403 if err := internal.SetDevice(st, device); err != nil { 404 return err 405 } 406 return nil 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() (sysconfig.Device, *gadget.Info, error) { 594 var sysLabel string 595 modeEnv, err := maybeReadModeenv() 596 if err != nil { 597 return nil, 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, 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, nil, state.ErrNoState 634 } 635 model := deviceSeed.Model() 636 if model.Gadget() == "" { 637 // no gadget 638 return nil, 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, nil, state.ErrNoState 660 } 661 662 dev := newModelDeviceContext(m, model) 663 return dev, gi, nil 664 } 665 666 var populateStateFromSeed = populateStateFromSeedImpl 667 668 // ensureSeeded makes sure that the snaps from seed.yaml get installed 669 // with the matching assertions 670 func (m *DeviceManager) ensureSeeded() error { 671 m.state.Lock() 672 defer m.state.Unlock() 673 674 var seeded bool 675 err := m.state.Get("seeded", &seeded) 676 if err != nil && err != state.ErrNoState { 677 return err 678 } 679 if seeded { 680 return nil 681 } 682 683 if m.changeInFlight("seed") { 684 return nil 685 } 686 687 perfTimings, err := m.seedStart() 688 if err != nil { 689 return err 690 } 691 // we time preloadGadget + first ensureSeeded together 692 // succcessive ensureSeeded should be timed separately 693 m.seedTimings = nil 694 695 var opts *populateStateFromSeedOptions 696 if m.preseed { 697 opts = &populateStateFromSeedOptions{Preseed: true} 698 } else { 699 modeEnv, err := maybeReadModeenv() 700 if err != nil { 701 return err 702 } 703 if modeEnv != nil { 704 opts = &populateStateFromSeedOptions{ 705 Mode: modeEnv.Mode, 706 Label: modeEnv.RecoverySystem, 707 } 708 } 709 } 710 711 var tsAll []*state.TaskSet 712 timings.Run(perfTimings, "state-from-seed", "populate state from seed", func(tm timings.Measurer) { 713 tsAll, err = populateStateFromSeed(m.state, opts, tm) 714 }) 715 if err != nil { 716 return err 717 } 718 if len(tsAll) == 0 { 719 return nil 720 } 721 722 chg := m.state.NewChange("seed", "Initialize system state") 723 for _, ts := range tsAll { 724 chg.AddAll(ts) 725 } 726 m.state.EnsureBefore(0) 727 728 state.TagTimingsWithChange(perfTimings, chg) 729 perfTimings.Save(m.state) 730 return nil 731 } 732 733 func (m *DeviceManager) ensureBootOk() error { 734 m.state.Lock() 735 defer m.state.Unlock() 736 737 if release.OnClassic { 738 return nil 739 } 740 741 // boot-ok/update-boot-revision is only relevant in run-mode 742 if m.SystemMode(SysAny) != "run" { 743 return nil 744 } 745 746 if !m.bootOkRan { 747 deviceCtx, err := DeviceCtx(m.state, nil, nil) 748 if err != nil && err != state.ErrNoState { 749 return err 750 } 751 if err == nil { 752 if err := boot.MarkBootSuccessful(deviceCtx); err != nil { 753 return err 754 } 755 } 756 m.bootOkRan = true 757 } 758 759 if !m.bootRevisionsUpdated { 760 if err := snapstate.UpdateBootRevisions(m.state); err != nil { 761 return err 762 } 763 m.bootRevisionsUpdated = true 764 } 765 766 return nil 767 } 768 769 func (m *DeviceManager) ensureCloudInitRestricted() error { 770 m.state.Lock() 771 defer m.state.Unlock() 772 773 if m.cloudInitAlreadyRestricted { 774 return nil 775 } 776 777 var seeded bool 778 err := m.state.Get("seeded", &seeded) 779 if err != nil && err != state.ErrNoState { 780 return err 781 } 782 783 // On Ubuntu Core devices that have been seeded, we want to restrict 784 // cloud-init so that its more dangerous (for an IoT device at least) 785 // features are not exploitable after a device has been seeded. This allows 786 // device administrators and other tools (such as multipass) to still 787 // configure an Ubuntu Core device on first boot, and also allows cloud 788 // vendors to run cloud-init with only a specific data-source on subsequent 789 // boots but disallows arbitrary cloud-init {user,meta,vendor}-data to be 790 // attached to a device via a USB drive and inject code onto the device. 791 792 if seeded && !release.OnClassic { 793 opts := &sysconfig.CloudInitRestrictOptions{} 794 795 // check the current state of cloud-init, if it is disabled or already 796 // restricted then we have nothing to do 797 cloudInitStatus, err := cloudInitStatus() 798 if err != nil { 799 return err 800 } 801 statusMsg := "" 802 803 switch cloudInitStatus { 804 case sysconfig.CloudInitDisabledPermanently, sysconfig.CloudInitRestrictedBySnapd: 805 // already been permanently disabled, nothing to do 806 m.cloudInitAlreadyRestricted = true 807 return nil 808 case sysconfig.CloudInitNotFound: 809 // no cloud init at all 810 statusMsg = "not found" 811 case sysconfig.CloudInitUntriggered: 812 // hasn't been used 813 statusMsg = "reported to be in disabled state" 814 case sysconfig.CloudInitDone: 815 // is done being used 816 statusMsg = "reported to be done" 817 case sysconfig.CloudInitErrored: 818 // cloud-init errored, so we give the device admin / developer a few 819 // minutes to reboot the machine to re-run cloud-init and try again, 820 // otherwise we will disable cloud-init permanently 821 822 // initialize the time we first saw cloud-init in error state 823 if m.cloudInitErrorAttemptStart == nil { 824 // save the time we started the attempt to restrict 825 now := timeNow() 826 m.cloudInitErrorAttemptStart = &now 827 logger.Noticef("System initialized, cloud-init reported to be in error state, will disable in 3 minutes") 828 } 829 830 // check if 3 minutes have elapsed since we first saw cloud-init in 831 // error state 832 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitErrorAttemptStart) 833 if timeSinceFirstAttempt <= 3*time.Minute { 834 // we need to keep waiting for cloud-init, up to 3 minutes 835 nextCheck := 3*time.Minute - timeSinceFirstAttempt 836 m.state.EnsureBefore(nextCheck) 837 return nil 838 } 839 // otherwise, we timed out waiting for cloud-init to be fixed or 840 // rebooted and should restrict cloud-init 841 // we will restrict cloud-init below, but we need to force the 842 // disable, as by default RestrictCloudInit will error on state 843 // CloudInitErrored 844 opts.ForceDisable = true 845 statusMsg = "reported to be in error state after 3 minutes" 846 default: 847 // in unknown states we are conservative and let the device run for 848 // a while to see if it transitions to a known state, but eventually 849 // will disable anyways 850 fallthrough 851 case sysconfig.CloudInitEnabled: 852 // we will give cloud-init up to 5 minutes to try and run, if it 853 // still has not transitioned to some other known state, then we 854 // will give up waiting for it and disable it anyways 855 856 // initialize the first time we saw cloud-init in enabled state 857 if m.cloudInitEnabledInactiveAttemptStart == nil { 858 // save the time we started the attempt to restrict 859 now := timeNow() 860 m.cloudInitEnabledInactiveAttemptStart = &now 861 } 862 863 // keep re-scheduling again in 10 seconds until we hit 5 minutes 864 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitEnabledInactiveAttemptStart) 865 if timeSinceFirstAttempt <= 5*time.Minute { 866 // TODO: should we log a message here about waiting for cloud-init 867 // to be in a "known state"? 868 m.state.EnsureBefore(10 * time.Second) 869 return nil 870 } 871 872 // otherwise, we gave cloud-init 5 minutes to run, if it's still not 873 // done disable it anyways 874 // note we we need to force the disable, as by default 875 // RestrictCloudInit will error on state CloudInitEnabled 876 opts.ForceDisable = true 877 statusMsg = "failed to transition to done or error state after 5 minutes" 878 } 879 880 // we should always have a model if we are seeded and are not on classic 881 model, err := m.Model() 882 if err != nil { 883 return err 884 } 885 886 // For UC20, we want to always disable cloud-init after it has run on 887 // first boot unless we are in a "real cloud", i.e. not using NoCloud, 888 // or if we installed cloud-init configuration from the gadget 889 if model.Grade() != asserts.ModelGradeUnset { 890 // always disable NoCloud/local datasources after first boot on 891 // uc20, this is because even if the gadget has a cloud.conf 892 // configuring NoCloud, the config installed by cloud-init should 893 // not work differently for later boots, so it's sufficient that 894 // NoCloud runs on first-boot and never again 895 opts.DisableAfterLocalDatasourcesRun = true 896 } 897 898 // now restrict/disable cloud-init 899 res, err := restrictCloudInit(cloudInitStatus, opts) 900 if err != nil { 901 return err 902 } 903 904 // log a message about what we did 905 actionMsg := "" 906 switch res.Action { 907 case "disable": 908 actionMsg = "disabled permanently" 909 case "restrict": 910 // log different messages depending on what datasource was used 911 if res.DataSource == "NoCloud" { 912 actionMsg = "set datasource_list to [ NoCloud ] and disabled auto-import by filesystem label" 913 } else { 914 // all other datasources just log that we limited it to that datasource 915 actionMsg = fmt.Sprintf("set datasource_list to [ %s ]", res.DataSource) 916 } 917 default: 918 return fmt.Errorf("internal error: unexpected action %s taken while restricting cloud-init", res.Action) 919 } 920 logger.Noticef("System initialized, cloud-init %s, %s", statusMsg, actionMsg) 921 922 m.cloudInitAlreadyRestricted = true 923 } 924 925 return nil 926 } 927 928 func (m *DeviceManager) ensureInstalled() error { 929 m.state.Lock() 930 defer m.state.Unlock() 931 932 if release.OnClassic { 933 return nil 934 } 935 936 if m.ensureInstalledRan { 937 return nil 938 } 939 940 if m.SystemMode(SysHasModeenv) != "install" { 941 return nil 942 } 943 944 var seeded bool 945 err := m.state.Get("seeded", &seeded) 946 if err != nil && err != state.ErrNoState { 947 return err 948 } 949 if !seeded { 950 return nil 951 } 952 953 if m.changeInFlight("install-system") { 954 return nil 955 } 956 957 perfTimings := timings.New(map[string]string{"ensure": "install-system"}) 958 959 model, err := m.Model() 960 if err != nil && err != state.ErrNoState { 961 return err 962 } 963 if err != nil { 964 return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") 965 } 966 967 // check if the gadget has an install-device hook 968 var hasInstallDeviceHook bool 969 970 gadgetInfo, err := snapstate.CurrentInfo(m.state, model.Gadget()) 971 if err != nil { 972 return fmt.Errorf("internal error: device is seeded in install mode but has no gadget snap: %v", err) 973 } 974 hasInstallDeviceHook = (gadgetInfo.Hooks["install-device"] != nil) 975 976 m.ensureInstalledRan = true 977 978 var prev *state.Task 979 setupRunSystem := m.state.NewTask("setup-run-system", i18n.G("Setup system for run mode")) 980 981 tasks := []*state.Task{setupRunSystem} 982 addTask := func(t *state.Task) { 983 t.WaitFor(prev) 984 tasks = append(tasks, t) 985 prev = t 986 } 987 prev = setupRunSystem 988 989 // add the install-device hook before ensure-next-boot-to-run-mode if it 990 // exists in the snap 991 var installDevice *state.Task 992 if hasInstallDeviceHook { 993 summary := i18n.G("Run install-device hook") 994 hooksup := &hookstate.HookSetup{ 995 // TODO: what's a reasonable timeout for the install-device hook? 996 Snap: model.Gadget(), 997 Hook: "install-device", 998 } 999 installDevice = hookstate.HookTask(m.state, summary, hooksup, nil) 1000 addTask(installDevice) 1001 } 1002 1003 restartSystem := m.state.NewTask("restart-system-to-run-mode", i18n.G("Ensure next boot to run mode")) 1004 addTask(restartSystem) 1005 1006 if installDevice != nil { 1007 // reference used by snapctl reboot 1008 installDevice.Set("restart-task", restartSystem.ID()) 1009 } 1010 1011 chg := m.state.NewChange("install-system", i18n.G("Install the system")) 1012 chg.AddAll(state.NewTaskSet(tasks...)) 1013 1014 state.TagTimingsWithChange(perfTimings, chg) 1015 perfTimings.Save(m.state) 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 // ResetToPostBootState is only useful for integration testing. 1229 func (m *DeviceManager) ResetToPostBootState() { 1230 osutil.MustBeTestBinary("ResetToPostBootState can only be called from tests") 1231 m.bootOkRan = false 1232 m.bootRevisionsUpdated = false 1233 m.ensureTriedRecoverySystemRan = false 1234 } 1235 1236 var errNoSaveSupport = errors.New("no save directory before UC20") 1237 1238 // withSaveDir invokes a function making sure save dir is available. 1239 // Under UC16/18 it returns errNoSaveSupport 1240 // For UC20 it also checks that ubuntu-save is available/mounted. 1241 func (m *DeviceManager) withSaveDir(f func() error) error { 1242 // we use the model to check whether this is a UC20 device 1243 model, err := m.Model() 1244 if err == state.ErrNoState { 1245 return fmt.Errorf("internal error: cannot access save dir before a model is set") 1246 } 1247 if err != nil { 1248 return err 1249 } 1250 if model.Grade() == asserts.ModelGradeUnset { 1251 return errNoSaveSupport 1252 } 1253 // at this point we need save available 1254 if !m.saveAvailable { 1255 return fmt.Errorf("internal error: save dir is unavailable") 1256 } 1257 1258 return f() 1259 } 1260 1261 // withSaveAssertDB invokes a function making the save device assertion 1262 // backup database available to it. 1263 // Under UC16/18 it returns errNoSaveSupport 1264 // For UC20 it also checks that ubuntu-save is available/mounted. 1265 func (m *DeviceManager) withSaveAssertDB(f func(*asserts.Database) error) error { 1266 return m.withSaveDir(func() error { 1267 // open an ancillary backup assertion database in save/device 1268 assertDB, err := sysdb.OpenAt(dirs.SnapDeviceSaveDir) 1269 if err != nil { 1270 return err 1271 } 1272 return f(assertDB) 1273 }) 1274 } 1275 1276 // withKeypairMgr invokes a function making the device KeypairManager 1277 // available to it. 1278 // It uses the right location for the manager depending on UC16/18 vs 20, 1279 // the latter uses ubuntu-save. 1280 // For UC20 it also checks that ubuntu-save is available/mounted. 1281 func (m *DeviceManager) withKeypairMgr(f func(asserts.KeypairManager) error) error { 1282 // we use the model to check whether this is a UC20 device 1283 // TODO: during a theoretical UC18->20 remodel the location of 1284 // keypair manager keys would move, we will need dedicated code 1285 // to deal with that, this code typically will return the old location 1286 // until a restart 1287 model, err := m.Model() 1288 if err == state.ErrNoState { 1289 return fmt.Errorf("internal error: cannot access device keypair manager before a model is set") 1290 } 1291 if err != nil { 1292 return err 1293 } 1294 underSave := false 1295 if model.Grade() != asserts.ModelGradeUnset { 1296 // on UC20 the keys are kept under the save dir 1297 underSave = true 1298 } 1299 where := dirs.SnapDeviceDir 1300 if underSave { 1301 // at this point we need save available 1302 if !m.saveAvailable { 1303 return fmt.Errorf("internal error: cannot access device keypair manager if ubuntu-save is unavailable") 1304 } 1305 where = dirs.SnapDeviceSaveDir 1306 } 1307 keypairMgr := m.cachedKeypairMgr 1308 if keypairMgr == nil { 1309 var err error 1310 keypairMgr, err = asserts.OpenFSKeypairManager(where) 1311 if err != nil { 1312 return err 1313 } 1314 m.cachedKeypairMgr = keypairMgr 1315 } 1316 return f(keypairMgr) 1317 } 1318 1319 // TODO:UC20: we need proper encapsulated support to read 1320 // tpm-policy-auth-key from save if the latter can get unmounted on 1321 // demand 1322 1323 func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { 1324 device, err := m.device() 1325 if err != nil { 1326 return nil, err 1327 } 1328 1329 if device.KeyID == "" { 1330 return nil, state.ErrNoState 1331 } 1332 1333 var privKey asserts.PrivateKey 1334 err = m.withKeypairMgr(func(keypairMgr asserts.KeypairManager) (err error) { 1335 privKey, err = keypairMgr.Get(device.KeyID) 1336 if err != nil { 1337 return fmt.Errorf("cannot read device key pair: %v", err) 1338 } 1339 return nil 1340 }) 1341 if err != nil { 1342 return nil, err 1343 } 1344 return privKey, nil 1345 } 1346 1347 // Registered returns a channel that is closed when the device is known to have been registered. 1348 func (m *DeviceManager) Registered() <-chan struct{} { 1349 return m.reg 1350 } 1351 1352 // device returns current device state. 1353 func (m *DeviceManager) device() (*auth.DeviceState, error) { 1354 return internal.Device(m.state) 1355 } 1356 1357 // setDevice sets the device details in the state. 1358 func (m *DeviceManager) setDevice(device *auth.DeviceState) error { 1359 return internal.SetDevice(m.state, device) 1360 } 1361 1362 // Model returns the device model assertion. 1363 func (m *DeviceManager) Model() (*asserts.Model, error) { 1364 return findModel(m.state) 1365 } 1366 1367 // Serial returns the device serial assertion. 1368 func (m *DeviceManager) Serial() (*asserts.Serial, error) { 1369 return findSerial(m.state, nil) 1370 } 1371 1372 type SystemModeInfo struct { 1373 Mode string 1374 HasModeenv bool 1375 Seeded bool 1376 BootFlags []string 1377 HostDataLocations []string 1378 } 1379 1380 // SystemModeInfo returns details about the current system mode the device is in. 1381 func (m *DeviceManager) SystemModeInfo() (*SystemModeInfo, error) { 1382 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1383 if err == state.ErrNoState { 1384 return nil, fmt.Errorf("cannot report system mode information before device model is acknowledged") 1385 } 1386 if err != nil { 1387 return nil, err 1388 } 1389 1390 var seeded bool 1391 err = m.state.Get("seeded", &seeded) 1392 if err != nil && err != state.ErrNoState { 1393 return nil, err 1394 } 1395 1396 mode := deviceCtx.SystemMode() 1397 smi := SystemModeInfo{ 1398 Mode: mode, 1399 HasModeenv: deviceCtx.HasModeenv(), 1400 Seeded: seeded, 1401 } 1402 if smi.HasModeenv { 1403 bootFlags, err := boot.BootFlags(deviceCtx) 1404 if err != nil { 1405 return nil, err 1406 } 1407 smi.BootFlags = bootFlags 1408 1409 hostDataLocs, err := boot.HostUbuntuDataForMode(mode) 1410 if err != nil { 1411 return nil, err 1412 } 1413 smi.HostDataLocations = hostDataLocs 1414 } 1415 return &smi, nil 1416 } 1417 1418 type SystemAction struct { 1419 Title string 1420 Mode string 1421 } 1422 1423 type System struct { 1424 // Current is true when the system running now was installed from that 1425 // seed 1426 Current bool 1427 // Label of the seed system 1428 Label string 1429 // Model assertion of the system 1430 Model *asserts.Model 1431 // Brand information 1432 Brand *asserts.Account 1433 // Actions available for this system 1434 Actions []SystemAction 1435 } 1436 1437 var defaultSystemActions = []SystemAction{ 1438 {Title: "Install", Mode: "install"}, 1439 } 1440 var currentSystemActions = []SystemAction{ 1441 {Title: "Reinstall", Mode: "install"}, 1442 {Title: "Recover", Mode: "recover"}, 1443 {Title: "Run normally", Mode: "run"}, 1444 } 1445 var recoverSystemActions = []SystemAction{ 1446 {Title: "Reinstall", Mode: "install"}, 1447 {Title: "Run normally", Mode: "run"}, 1448 } 1449 1450 var ErrNoSystems = errors.New("no systems seeds") 1451 1452 // Systems list the available recovery/seeding systems. Returns the list of 1453 // systems, ErrNoSystems when no systems seeds were found or other error. 1454 func (m *DeviceManager) Systems() ([]*System, error) { 1455 // it's tough luck when we cannot determine the current system seed 1456 systemMode := m.SystemMode(SysAny) 1457 currentSys, _ := currentSystemForMode(m.state, systemMode) 1458 1459 systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*")) 1460 if err != nil && !os.IsNotExist(err) { 1461 return nil, fmt.Errorf("cannot list available systems: %v", err) 1462 } 1463 if len(systemLabels) == 0 { 1464 // maybe not a UC20 system 1465 return nil, ErrNoSystems 1466 } 1467 1468 var systems []*System 1469 for _, fpLabel := range systemLabels { 1470 label := filepath.Base(fpLabel) 1471 system, err := systemFromSeed(label, currentSys) 1472 if err != nil { 1473 // TODO:UC20 add a Broken field to the seed system like 1474 // we do for snap.Info 1475 logger.Noticef("cannot load system %q seed: %v", label, err) 1476 continue 1477 } 1478 systems = append(systems, system) 1479 } 1480 return systems, nil 1481 } 1482 1483 var ErrUnsupportedAction = errors.New("unsupported action") 1484 1485 // Reboot triggers a reboot into the given systemLabel and mode. 1486 // 1487 // When called without a systemLabel and without a mode it will just 1488 // trigger a regular reboot. 1489 // 1490 // When called without a systemLabel but with a mode it will use 1491 // the current system to enter the given mode. 1492 // 1493 // Note that "recover" and "run" modes are only available for the 1494 // current system. 1495 func (m *DeviceManager) Reboot(systemLabel, mode string) error { 1496 rebootCurrent := func() { 1497 logger.Noticef("rebooting system") 1498 m.state.RequestRestart(state.RestartSystemNow) 1499 } 1500 1501 // most simple case: just reboot 1502 if systemLabel == "" && mode == "" { 1503 m.state.Lock() 1504 defer m.state.Unlock() 1505 1506 rebootCurrent() 1507 return nil 1508 } 1509 1510 // no systemLabel means "current" so get the current system label 1511 if systemLabel == "" { 1512 systemMode := m.SystemMode(SysAny) 1513 currentSys, err := currentSystemForMode(m.state, systemMode) 1514 if err != nil { 1515 return fmt.Errorf("cannot get current system: %v", err) 1516 } 1517 systemLabel = currentSys.System 1518 } 1519 1520 switched := func(systemLabel string, sysAction *SystemAction) { 1521 logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode) 1522 m.state.RequestRestart(state.RestartSystemNow) 1523 } 1524 // even if we are already in the right mode we restart here by 1525 // passing rebootCurrent as this is what the user requested 1526 return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched) 1527 } 1528 1529 // RequestSystemAction requests the provided system to be run in a 1530 // given mode as specified by action. 1531 // A system reboot will be requested when the request can be 1532 // successfully carried out. 1533 func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error { 1534 if systemLabel == "" { 1535 return fmt.Errorf("internal error: system label is unset") 1536 } 1537 1538 nop := func() {} 1539 switched := func(systemLabel string, sysAction *SystemAction) { 1540 logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title) 1541 m.state.RequestRestart(state.RestartSystemNow) 1542 } 1543 // we do nothing (nop) if the mode and system are the same 1544 return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched) 1545 } 1546 1547 // switchToSystemAndMode switches to given systemLabel and mode. 1548 // If the systemLabel and mode are the same as current, it calls 1549 // sameSystemAndMode. If successful otherwise it calls switched. Both 1550 // are called with the state lock held. 1551 func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error { 1552 if err := checkSystemRequestConflict(m.state, systemLabel); err != nil { 1553 return err 1554 } 1555 1556 systemMode := m.SystemMode(SysAny) 1557 // ignore the error to be robust in scenarios that 1558 // dont' stricly require currentSys to be carried through. 1559 // make sure that currentSys == nil does not break 1560 // the code below! 1561 // TODO: should we log the error? 1562 currentSys, _ := currentSystemForMode(m.state, systemMode) 1563 1564 systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel) 1565 if _, err := os.Stat(systemSeedDir); err != nil { 1566 // XXX: should we wrap this instead return a naked stat error? 1567 return err 1568 } 1569 system, err := systemFromSeed(systemLabel, currentSys) 1570 if err != nil { 1571 return fmt.Errorf("cannot load seed system: %v", err) 1572 } 1573 1574 var sysAction *SystemAction 1575 for _, act := range system.Actions { 1576 if mode == act.Mode { 1577 sysAction = &act 1578 break 1579 } 1580 } 1581 if sysAction == nil { 1582 // XXX: provide more context here like what mode was requested? 1583 return ErrUnsupportedAction 1584 } 1585 1586 // XXX: requested mode is valid; only current system has 'run' and 1587 // recover 'actions' 1588 1589 switch systemMode { 1590 case "recover", "run": 1591 // if going from recover to recover or from run to run and the systems 1592 // are the same do nothing 1593 if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System { 1594 m.state.Lock() 1595 defer m.state.Unlock() 1596 sameSystemAndMode() 1597 return nil 1598 } 1599 case "install": 1600 // requesting system actions in install mode does not make sense atm 1601 // 1602 // TODO:UC20: maybe factory hooks will be able to something like 1603 // this? 1604 return ErrUnsupportedAction 1605 default: 1606 // probably test device manager mocking problem, or also potentially 1607 // missing modeenv 1608 return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode) 1609 } 1610 1611 m.state.Lock() 1612 defer m.state.Unlock() 1613 1614 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1615 if err != nil { 1616 return err 1617 } 1618 if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil { 1619 return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err) 1620 } 1621 1622 switched(systemLabel, sysAction) 1623 return nil 1624 } 1625 1626 // implement storecontext.Backend 1627 1628 type storeContextBackend struct { 1629 *DeviceManager 1630 } 1631 1632 func (scb storeContextBackend) Device() (*auth.DeviceState, error) { 1633 return scb.DeviceManager.device() 1634 } 1635 1636 func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error { 1637 return scb.DeviceManager.setDevice(device) 1638 } 1639 1640 func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) { 1641 st := scb.DeviceManager.state 1642 return proxyStore(st, config.NewTransaction(st)) 1643 } 1644 1645 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 1646 func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 1647 if serial == nil { 1648 // shouldn't happen, but be safe 1649 return nil, fmt.Errorf("internal error: cannot sign a session request without a serial") 1650 } 1651 1652 privKey, err := scb.DeviceManager.keyPair() 1653 if err == state.ErrNoState { 1654 return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key") 1655 } 1656 if err != nil { 1657 return nil, err 1658 } 1659 1660 a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ 1661 "brand-id": serial.BrandID(), 1662 "model": serial.Model(), 1663 "serial": serial.Serial(), 1664 "nonce": nonce, 1665 "timestamp": time.Now().UTC().Format(time.RFC3339), 1666 }, nil, privKey) 1667 if err != nil { 1668 return nil, err 1669 } 1670 1671 return a.(*asserts.DeviceSessionRequest), nil 1672 } 1673 1674 func (m *DeviceManager) StoreContextBackend() storecontext.Backend { 1675 return storeContextBackend{m} 1676 } 1677 1678 func (m *DeviceManager) hasFDESetupHook() (bool, error) { 1679 // state must be locked 1680 st := m.state 1681 1682 deviceCtx, err := DeviceCtx(st, nil, nil) 1683 if err != nil { 1684 return false, fmt.Errorf("cannot get device context: %v", err) 1685 } 1686 1687 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1688 if err != nil { 1689 return false, fmt.Errorf("cannot get kernel info: %v", err) 1690 } 1691 return hasFDESetupHookInKernel(kernelInfo), nil 1692 } 1693 1694 func (m *DeviceManager) runFDESetupHook(req *fde.SetupRequest) ([]byte, error) { 1695 // TODO:UC20: when this runs on refresh we need to be very careful 1696 // that we never run this when the kernel is not fully configured 1697 // i.e. when there are no security profiles for the hook 1698 1699 // state must be locked 1700 st := m.state 1701 1702 deviceCtx, err := DeviceCtx(st, nil, nil) 1703 if err != nil { 1704 return nil, fmt.Errorf("cannot get device context to run fde-setup hook: %v", err) 1705 } 1706 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1707 if err != nil { 1708 return nil, fmt.Errorf("cannot get kernel info to run fde-setup hook: %v", err) 1709 } 1710 hooksup := &hookstate.HookSetup{ 1711 Snap: kernelInfo.InstanceName(), 1712 Revision: kernelInfo.Revision, 1713 Hook: "fde-setup", 1714 // XXX: should this be configurable somehow? 1715 Timeout: 5 * time.Minute, 1716 } 1717 contextData := map[string]interface{}{ 1718 "fde-setup-request": req, 1719 } 1720 st.Unlock() 1721 defer st.Lock() 1722 context, err := m.hookMgr.EphemeralRunHook(context.Background(), hooksup, contextData) 1723 if err != nil { 1724 return nil, fmt.Errorf("cannot run hook for %q: %v", req.Op, err) 1725 } 1726 // the hook is expected to call "snapctl fde-setup-result" which 1727 // will set the "fde-setup-result" value on the task 1728 var hookOutput []byte 1729 context.Lock() 1730 err = context.Get("fde-setup-result", &hookOutput) 1731 context.Unlock() 1732 if err != nil { 1733 return nil, fmt.Errorf("cannot get result from fde-setup hook %q: %v", req.Op, err) 1734 } 1735 return hookOutput, nil 1736 } 1737 1738 func (m *DeviceManager) checkFDEFeatures(st *state.State) error { 1739 // TODO: move most of this to kernel/fde.Features 1740 // Run fde-setup hook with "op":"features". If the hook 1741 // returns any {"features":[...]} reply we consider the 1742 // hardware supported. If the hook errors or if it returns 1743 // {"error":"hardware-unsupported"} we don't. 1744 req := &fde.SetupRequest{ 1745 Op: "features", 1746 } 1747 output, err := m.runFDESetupHook(req) 1748 if err != nil { 1749 return err 1750 } 1751 var res struct { 1752 Features []string `json:"features"` 1753 Error string `json:"error"` 1754 } 1755 if err := json.Unmarshal(output, &res); err != nil { 1756 return fmt.Errorf("cannot parse hook output %q: %v", output, err) 1757 } 1758 if res.Features == nil && res.Error == "" { 1759 return fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`) 1760 } 1761 if res.Error != "" { 1762 return fmt.Errorf("cannot use hook: it returned error: %v", res.Error) 1763 } 1764 return nil 1765 } 1766 1767 func hasFDESetupHookInKernel(kernelInfo *snap.Info) bool { 1768 _, ok := kernelInfo.Hooks["fde-setup"] 1769 return ok 1770 } 1771 1772 type fdeSetupHandler struct { 1773 context *hookstate.Context 1774 } 1775 1776 func newFdeSetupHandler(ctx *hookstate.Context) hookstate.Handler { 1777 return fdeSetupHandler{context: ctx} 1778 } 1779 1780 func (h fdeSetupHandler) Before() error { 1781 return nil 1782 } 1783 1784 func (h fdeSetupHandler) Done() error { 1785 return nil 1786 } 1787 1788 func (h fdeSetupHandler) Error(err error) (bool, error) { 1789 return false, nil 1790 }