gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/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 if !seeded { 784 // we need to wait until we are seeded 785 return nil 786 } 787 788 if release.OnClassic { 789 // don't re-run on classic since classic devices don't get subject to 790 // the cloud-init restricting that core devices do 791 m.cloudInitAlreadyRestricted = true 792 return nil 793 } 794 795 // On Ubuntu Core devices that have been seeded, we want to restrict 796 // cloud-init so that its more dangerous (for an IoT device at least) 797 // features are not exploitable after a device has been seeded. This allows 798 // device administrators and other tools (such as multipass) to still 799 // configure an Ubuntu Core device on first boot, and also allows cloud 800 // vendors to run cloud-init with only a specific data-source on subsequent 801 // boots but disallows arbitrary cloud-init {user,meta,vendor}-data to be 802 // attached to a device via a USB drive and inject code onto the device. 803 804 opts := &sysconfig.CloudInitRestrictOptions{} 805 806 // check the current state of cloud-init, if it is disabled or already 807 // restricted then we have nothing to do 808 cloudInitStatus, err := cloudInitStatus() 809 if err != nil { 810 return err 811 } 812 statusMsg := "" 813 814 switch cloudInitStatus { 815 case sysconfig.CloudInitDisabledPermanently, sysconfig.CloudInitRestrictedBySnapd: 816 // already been permanently disabled, nothing to do 817 m.cloudInitAlreadyRestricted = true 818 return nil 819 case sysconfig.CloudInitNotFound: 820 // no cloud init at all 821 statusMsg = "not found" 822 case sysconfig.CloudInitUntriggered: 823 // hasn't been used 824 statusMsg = "reported to be in disabled state" 825 case sysconfig.CloudInitDone: 826 // is done being used 827 statusMsg = "reported to be done" 828 case sysconfig.CloudInitErrored: 829 // cloud-init errored, so we give the device admin / developer a few 830 // minutes to reboot the machine to re-run cloud-init and try again, 831 // otherwise we will disable cloud-init permanently 832 833 // initialize the time we first saw cloud-init in error state 834 if m.cloudInitErrorAttemptStart == nil { 835 // save the time we started the attempt to restrict 836 now := timeNow() 837 m.cloudInitErrorAttemptStart = &now 838 logger.Noticef("System initialized, cloud-init reported to be in error state, will disable in 3 minutes") 839 } 840 841 // check if 3 minutes have elapsed since we first saw cloud-init in 842 // error state 843 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitErrorAttemptStart) 844 if timeSinceFirstAttempt <= 3*time.Minute { 845 // we need to keep waiting for cloud-init, up to 3 minutes 846 nextCheck := 3*time.Minute - timeSinceFirstAttempt 847 m.state.EnsureBefore(nextCheck) 848 return nil 849 } 850 // otherwise, we timed out waiting for cloud-init to be fixed or 851 // rebooted and should restrict cloud-init 852 // we will restrict cloud-init below, but we need to force the 853 // disable, as by default RestrictCloudInit will error on state 854 // CloudInitErrored 855 opts.ForceDisable = true 856 statusMsg = "reported to be in error state after 3 minutes" 857 default: 858 // in unknown states we are conservative and let the device run for 859 // a while to see if it transitions to a known state, but eventually 860 // will disable anyways 861 fallthrough 862 case sysconfig.CloudInitEnabled: 863 // we will give cloud-init up to 5 minutes to try and run, if it 864 // still has not transitioned to some other known state, then we 865 // will give up waiting for it and disable it anyways 866 867 // initialize the first time we saw cloud-init in enabled state 868 if m.cloudInitEnabledInactiveAttemptStart == nil { 869 // save the time we started the attempt to restrict 870 now := timeNow() 871 m.cloudInitEnabledInactiveAttemptStart = &now 872 } 873 874 // keep re-scheduling again in 10 seconds until we hit 5 minutes 875 timeSinceFirstAttempt := timeNow().Sub(*m.cloudInitEnabledInactiveAttemptStart) 876 if timeSinceFirstAttempt <= 5*time.Minute { 877 // TODO: should we log a message here about waiting for cloud-init 878 // to be in a "known state"? 879 m.state.EnsureBefore(10 * time.Second) 880 return nil 881 } 882 883 // otherwise, we gave cloud-init 5 minutes to run, if it's still not 884 // done disable it anyways 885 // note we we need to force the disable, as by default 886 // RestrictCloudInit will error on state CloudInitEnabled 887 opts.ForceDisable = true 888 statusMsg = "failed to transition to done or error state after 5 minutes" 889 } 890 891 // we should always have a model if we are seeded and are not on classic 892 model, err := m.Model() 893 if err != nil { 894 return err 895 } 896 897 // For UC20, we want to always disable cloud-init after it has run on 898 // first boot unless we are in a "real cloud", i.e. not using NoCloud, 899 // or if we installed cloud-init configuration from the gadget 900 if model.Grade() != asserts.ModelGradeUnset { 901 // always disable NoCloud/local datasources after first boot on 902 // uc20, this is because even if the gadget has a cloud.conf 903 // configuring NoCloud, the config installed by cloud-init should 904 // not work differently for later boots, so it's sufficient that 905 // NoCloud runs on first-boot and never again 906 opts.DisableAfterLocalDatasourcesRun = true 907 } 908 909 // now restrict/disable cloud-init 910 res, err := restrictCloudInit(cloudInitStatus, opts) 911 if err != nil { 912 return err 913 } 914 915 // log a message about what we did 916 actionMsg := "" 917 switch res.Action { 918 case "disable": 919 actionMsg = "disabled permanently" 920 case "restrict": 921 // log different messages depending on what datasource was used 922 if res.DataSource == "NoCloud" { 923 actionMsg = "set datasource_list to [ NoCloud ] and disabled auto-import by filesystem label" 924 } else { 925 // all other datasources just log that we limited it to that datasource 926 actionMsg = fmt.Sprintf("set datasource_list to [ %s ]", res.DataSource) 927 } 928 default: 929 return fmt.Errorf("internal error: unexpected action %s taken while restricting cloud-init", res.Action) 930 } 931 logger.Noticef("System initialized, cloud-init %s, %s", statusMsg, actionMsg) 932 933 m.cloudInitAlreadyRestricted = true 934 935 return nil 936 } 937 938 func (m *DeviceManager) ensureInstalled() error { 939 m.state.Lock() 940 defer m.state.Unlock() 941 942 if release.OnClassic { 943 return nil 944 } 945 946 if m.ensureInstalledRan { 947 return nil 948 } 949 950 if m.SystemMode(SysHasModeenv) != "install" { 951 return nil 952 } 953 954 var seeded bool 955 err := m.state.Get("seeded", &seeded) 956 if err != nil && err != state.ErrNoState { 957 return err 958 } 959 if !seeded { 960 return nil 961 } 962 963 if m.changeInFlight("install-system") { 964 return nil 965 } 966 967 perfTimings := timings.New(map[string]string{"ensure": "install-system"}) 968 969 model, err := m.Model() 970 if err != nil && err != state.ErrNoState { 971 return err 972 } 973 if err != nil { 974 return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion") 975 } 976 977 // check if the gadget has an install-device hook 978 var hasInstallDeviceHook bool 979 980 gadgetInfo, err := snapstate.CurrentInfo(m.state, model.Gadget()) 981 if err != nil { 982 return fmt.Errorf("internal error: device is seeded in install mode but has no gadget snap: %v", err) 983 } 984 hasInstallDeviceHook = (gadgetInfo.Hooks["install-device"] != nil) 985 986 m.ensureInstalledRan = true 987 988 var prev *state.Task 989 setupRunSystem := m.state.NewTask("setup-run-system", i18n.G("Setup system for run mode")) 990 991 tasks := []*state.Task{setupRunSystem} 992 addTask := func(t *state.Task) { 993 t.WaitFor(prev) 994 tasks = append(tasks, t) 995 prev = t 996 } 997 prev = setupRunSystem 998 999 // add the install-device hook before ensure-next-boot-to-run-mode if it 1000 // exists in the snap 1001 var installDevice *state.Task 1002 if hasInstallDeviceHook { 1003 summary := i18n.G("Run install-device hook") 1004 hooksup := &hookstate.HookSetup{ 1005 // TODO: what's a reasonable timeout for the install-device hook? 1006 Snap: model.Gadget(), 1007 Hook: "install-device", 1008 } 1009 installDevice = hookstate.HookTask(m.state, summary, hooksup, nil) 1010 addTask(installDevice) 1011 } 1012 1013 restartSystem := m.state.NewTask("restart-system-to-run-mode", i18n.G("Ensure next boot to run mode")) 1014 addTask(restartSystem) 1015 1016 if installDevice != nil { 1017 // reference used by snapctl reboot 1018 installDevice.Set("restart-task", restartSystem.ID()) 1019 } 1020 1021 chg := m.state.NewChange("install-system", i18n.G("Install the system")) 1022 chg.AddAll(state.NewTaskSet(tasks...)) 1023 1024 state.TagTimingsWithChange(perfTimings, chg) 1025 perfTimings.Save(m.state) 1026 1027 return nil 1028 } 1029 1030 var timeNow = time.Now 1031 1032 // StartOfOperationTime returns the time when snapd started operating, 1033 // and sets it in the state when called for the first time. 1034 // The StartOfOperationTime time is seed-time if available, 1035 // or current time otherwise. 1036 func (m *DeviceManager) StartOfOperationTime() (time.Time, error) { 1037 var opTime time.Time 1038 if m.preseed { 1039 return opTime, fmt.Errorf("internal error: unexpected call to StartOfOperationTime in preseed mode") 1040 } 1041 err := m.state.Get("start-of-operation-time", &opTime) 1042 if err == nil { 1043 return opTime, nil 1044 } 1045 if err != nil && err != state.ErrNoState { 1046 return opTime, err 1047 } 1048 1049 // start-of-operation-time not set yet, use seed-time if available 1050 var seedTime time.Time 1051 err = m.state.Get("seed-time", &seedTime) 1052 if err != nil && err != state.ErrNoState { 1053 return opTime, err 1054 } 1055 if err == nil { 1056 opTime = seedTime 1057 } else { 1058 opTime = timeNow() 1059 } 1060 m.state.Set("start-of-operation-time", opTime) 1061 return opTime, nil 1062 } 1063 1064 func markSeededInConfig(st *state.State) error { 1065 var seedDone bool 1066 tr := config.NewTransaction(st) 1067 if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) { 1068 return err 1069 } 1070 if !seedDone { 1071 if err := tr.Set("core", "seed.loaded", true); err != nil { 1072 return err 1073 } 1074 tr.Commit() 1075 } 1076 return nil 1077 } 1078 1079 func (m *DeviceManager) ensureSeedInConfig() error { 1080 m.state.Lock() 1081 defer m.state.Unlock() 1082 1083 if !m.ensureSeedInConfigRan { 1084 // get global seeded option 1085 var seeded bool 1086 if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState { 1087 return err 1088 } 1089 if !seeded { 1090 // wait for ensure again, this is fine because 1091 // doMarkSeeded will run "EnsureBefore(0)" 1092 return nil 1093 } 1094 1095 // Sync seeding with the configuration state. We need to 1096 // do this here to ensure that old systems which did not 1097 // set the configuration on seeding get the configuration 1098 // update too. 1099 if err := markSeededInConfig(m.state); err != nil { 1100 return err 1101 } 1102 m.ensureSeedInConfigRan = true 1103 } 1104 1105 return nil 1106 1107 } 1108 1109 func (m *DeviceManager) appendTriedRecoverySystem(label string) error { 1110 // state is locked by the caller 1111 1112 var triedSystems []string 1113 if err := m.state.Get("tried-systems", &triedSystems); err != nil && err != state.ErrNoState { 1114 return err 1115 } 1116 if strutil.ListContains(triedSystems, label) { 1117 // system already recorded as tried? 1118 return nil 1119 } 1120 triedSystems = append(triedSystems, label) 1121 m.state.Set("tried-systems", triedSystems) 1122 return nil 1123 } 1124 1125 func (m *DeviceManager) ensureTriedRecoverySystem() error { 1126 if release.OnClassic { 1127 return nil 1128 } 1129 // nothing to do if not UC20 and run mode 1130 if m.SystemMode(SysHasModeenv) != "run" { 1131 return nil 1132 } 1133 if m.ensureTriedRecoverySystemRan { 1134 return nil 1135 } 1136 1137 m.state.Lock() 1138 defer m.state.Unlock() 1139 1140 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1141 if err != nil && err != state.ErrNoState { 1142 return err 1143 } 1144 outcome, label, err := boot.InspectTryRecoverySystemOutcome(deviceCtx) 1145 if err != nil { 1146 if !boot.IsInconsistentRecoverySystemState(err) { 1147 return err 1148 } 1149 // boot variables state was inconsistent 1150 logger.Noticef("tried recovery system outcome error: %v", err) 1151 } 1152 switch outcome { 1153 case boot.TryRecoverySystemOutcomeSuccess: 1154 logger.Noticef("tried recovery system %q was successful", label) 1155 if err := m.appendTriedRecoverySystem(label); err != nil { 1156 return err 1157 } 1158 case boot.TryRecoverySystemOutcomeFailure: 1159 logger.Noticef("tried recovery system %q failed", label) 1160 case boot.TryRecoverySystemOutcomeInconsistent: 1161 logger.Noticef("inconsistent outcome of a tried recovery system") 1162 case boot.TryRecoverySystemOutcomeNoneTried: 1163 // no system was tried 1164 } 1165 if outcome != boot.TryRecoverySystemOutcomeNoneTried { 1166 if err := boot.ClearTryRecoverySystem(deviceCtx, label); err != nil { 1167 logger.Noticef("cannot clear tried recovery system status: %v", err) 1168 return err 1169 } 1170 } 1171 1172 m.ensureTriedRecoverySystemRan = true 1173 return nil 1174 } 1175 1176 type ensureError struct { 1177 errs []error 1178 } 1179 1180 func (e *ensureError) Error() string { 1181 if len(e.errs) == 1 { 1182 return fmt.Sprintf("devicemgr: %v", e.errs[0]) 1183 } 1184 parts := []string{"devicemgr:"} 1185 for _, e := range e.errs { 1186 parts = append(parts, e.Error()) 1187 } 1188 return strings.Join(parts, "\n - ") 1189 } 1190 1191 // no \n allowed in warnings 1192 var seedFailureFmt = `seeding failed with: %v. This indicates an error in your distribution, please see https://forum.snapcraft.io/t/16341 for more information.` 1193 1194 // Ensure implements StateManager.Ensure. 1195 func (m *DeviceManager) Ensure() error { 1196 var errs []error 1197 1198 if err := m.ensureSeeded(); err != nil { 1199 m.state.Lock() 1200 m.state.Warnf(seedFailureFmt, err) 1201 m.state.Unlock() 1202 errs = append(errs, fmt.Errorf("cannot seed: %v", err)) 1203 } 1204 1205 if !m.preseed { 1206 if err := m.ensureCloudInitRestricted(); err != nil { 1207 errs = append(errs, err) 1208 } 1209 1210 if err := m.ensureOperational(); err != nil { 1211 errs = append(errs, err) 1212 } 1213 1214 if err := m.ensureBootOk(); err != nil { 1215 errs = append(errs, err) 1216 } 1217 1218 if err := m.ensureSeedInConfig(); err != nil { 1219 errs = append(errs, err) 1220 } 1221 1222 if err := m.ensureInstalled(); err != nil { 1223 errs = append(errs, err) 1224 } 1225 1226 if err := m.ensureTriedRecoverySystem(); err != nil { 1227 errs = append(errs, err) 1228 } 1229 } 1230 1231 if len(errs) > 0 { 1232 return &ensureError{errs} 1233 } 1234 1235 return nil 1236 } 1237 1238 // ResetToPostBootState is only useful for integration testing. 1239 func (m *DeviceManager) ResetToPostBootState() { 1240 osutil.MustBeTestBinary("ResetToPostBootState can only be called from tests") 1241 m.bootOkRan = false 1242 m.bootRevisionsUpdated = false 1243 m.ensureTriedRecoverySystemRan = false 1244 } 1245 1246 var errNoSaveSupport = errors.New("no save directory before UC20") 1247 1248 // withSaveDir invokes a function making sure save dir is available. 1249 // Under UC16/18 it returns errNoSaveSupport 1250 // For UC20 it also checks that ubuntu-save is available/mounted. 1251 func (m *DeviceManager) withSaveDir(f func() error) error { 1252 // we use the model to check whether this is a UC20 device 1253 model, err := m.Model() 1254 if err == state.ErrNoState { 1255 return fmt.Errorf("internal error: cannot access save dir before a model is set") 1256 } 1257 if err != nil { 1258 return err 1259 } 1260 if model.Grade() == asserts.ModelGradeUnset { 1261 return errNoSaveSupport 1262 } 1263 // at this point we need save available 1264 if !m.saveAvailable { 1265 return fmt.Errorf("internal error: save dir is unavailable") 1266 } 1267 1268 return f() 1269 } 1270 1271 // withSaveAssertDB invokes a function making the save device assertion 1272 // backup database available to it. 1273 // Under UC16/18 it returns errNoSaveSupport 1274 // For UC20 it also checks that ubuntu-save is available/mounted. 1275 func (m *DeviceManager) withSaveAssertDB(f func(*asserts.Database) error) error { 1276 return m.withSaveDir(func() error { 1277 // open an ancillary backup assertion database in save/device 1278 assertDB, err := sysdb.OpenAt(dirs.SnapDeviceSaveDir) 1279 if err != nil { 1280 return err 1281 } 1282 return f(assertDB) 1283 }) 1284 } 1285 1286 // withKeypairMgr invokes a function making the device KeypairManager 1287 // available to it. 1288 // It uses the right location for the manager depending on UC16/18 vs 20, 1289 // the latter uses ubuntu-save. 1290 // For UC20 it also checks that ubuntu-save is available/mounted. 1291 func (m *DeviceManager) withKeypairMgr(f func(asserts.KeypairManager) error) error { 1292 // we use the model to check whether this is a UC20 device 1293 // TODO: during a theoretical UC18->20 remodel the location of 1294 // keypair manager keys would move, we will need dedicated code 1295 // to deal with that, this code typically will return the old location 1296 // until a restart 1297 model, err := m.Model() 1298 if err == state.ErrNoState { 1299 return fmt.Errorf("internal error: cannot access device keypair manager before a model is set") 1300 } 1301 if err != nil { 1302 return err 1303 } 1304 underSave := false 1305 if model.Grade() != asserts.ModelGradeUnset { 1306 // on UC20 the keys are kept under the save dir 1307 underSave = true 1308 } 1309 where := dirs.SnapDeviceDir 1310 if underSave { 1311 // at this point we need save available 1312 if !m.saveAvailable { 1313 return fmt.Errorf("internal error: cannot access device keypair manager if ubuntu-save is unavailable") 1314 } 1315 where = dirs.SnapDeviceSaveDir 1316 } 1317 keypairMgr := m.cachedKeypairMgr 1318 if keypairMgr == nil { 1319 var err error 1320 keypairMgr, err = asserts.OpenFSKeypairManager(where) 1321 if err != nil { 1322 return err 1323 } 1324 m.cachedKeypairMgr = keypairMgr 1325 } 1326 return f(keypairMgr) 1327 } 1328 1329 // TODO:UC20: we need proper encapsulated support to read 1330 // tpm-policy-auth-key from save if the latter can get unmounted on 1331 // demand 1332 1333 func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) { 1334 device, err := m.device() 1335 if err != nil { 1336 return nil, err 1337 } 1338 1339 if device.KeyID == "" { 1340 return nil, state.ErrNoState 1341 } 1342 1343 var privKey asserts.PrivateKey 1344 err = m.withKeypairMgr(func(keypairMgr asserts.KeypairManager) (err error) { 1345 privKey, err = keypairMgr.Get(device.KeyID) 1346 if err != nil { 1347 return fmt.Errorf("cannot read device key pair: %v", err) 1348 } 1349 return nil 1350 }) 1351 if err != nil { 1352 return nil, err 1353 } 1354 return privKey, nil 1355 } 1356 1357 // Registered returns a channel that is closed when the device is known to have been registered. 1358 func (m *DeviceManager) Registered() <-chan struct{} { 1359 return m.reg 1360 } 1361 1362 // device returns current device state. 1363 func (m *DeviceManager) device() (*auth.DeviceState, error) { 1364 return internal.Device(m.state) 1365 } 1366 1367 // setDevice sets the device details in the state. 1368 func (m *DeviceManager) setDevice(device *auth.DeviceState) error { 1369 return internal.SetDevice(m.state, device) 1370 } 1371 1372 // Model returns the device model assertion. 1373 func (m *DeviceManager) Model() (*asserts.Model, error) { 1374 return findModel(m.state) 1375 } 1376 1377 // Serial returns the device serial assertion. 1378 func (m *DeviceManager) Serial() (*asserts.Serial, error) { 1379 return findSerial(m.state, nil) 1380 } 1381 1382 type SystemModeInfo struct { 1383 Mode string 1384 HasModeenv bool 1385 Seeded bool 1386 BootFlags []string 1387 HostDataLocations []string 1388 } 1389 1390 // SystemModeInfo returns details about the current system mode the device is in. 1391 func (m *DeviceManager) SystemModeInfo() (*SystemModeInfo, error) { 1392 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1393 if err == state.ErrNoState { 1394 return nil, fmt.Errorf("cannot report system mode information before device model is acknowledged") 1395 } 1396 if err != nil { 1397 return nil, err 1398 } 1399 1400 var seeded bool 1401 err = m.state.Get("seeded", &seeded) 1402 if err != nil && err != state.ErrNoState { 1403 return nil, err 1404 } 1405 1406 mode := deviceCtx.SystemMode() 1407 smi := SystemModeInfo{ 1408 Mode: mode, 1409 HasModeenv: deviceCtx.HasModeenv(), 1410 Seeded: seeded, 1411 } 1412 if smi.HasModeenv { 1413 bootFlags, err := boot.BootFlags(deviceCtx) 1414 if err != nil { 1415 return nil, err 1416 } 1417 smi.BootFlags = bootFlags 1418 1419 hostDataLocs, err := boot.HostUbuntuDataForMode(mode) 1420 if err != nil { 1421 return nil, err 1422 } 1423 smi.HostDataLocations = hostDataLocs 1424 } 1425 return &smi, nil 1426 } 1427 1428 type SystemAction struct { 1429 Title string 1430 Mode string 1431 } 1432 1433 type System struct { 1434 // Current is true when the system running now was installed from that 1435 // seed 1436 Current bool 1437 // Label of the seed system 1438 Label string 1439 // Model assertion of the system 1440 Model *asserts.Model 1441 // Brand information 1442 Brand *asserts.Account 1443 // Actions available for this system 1444 Actions []SystemAction 1445 } 1446 1447 var defaultSystemActions = []SystemAction{ 1448 {Title: "Install", Mode: "install"}, 1449 } 1450 var currentSystemActions = []SystemAction{ 1451 {Title: "Reinstall", Mode: "install"}, 1452 {Title: "Recover", Mode: "recover"}, 1453 {Title: "Run normally", Mode: "run"}, 1454 } 1455 var recoverSystemActions = []SystemAction{ 1456 {Title: "Reinstall", Mode: "install"}, 1457 {Title: "Run normally", Mode: "run"}, 1458 } 1459 1460 var ErrNoSystems = errors.New("no systems seeds") 1461 1462 // Systems list the available recovery/seeding systems. Returns the list of 1463 // systems, ErrNoSystems when no systems seeds were found or other error. 1464 func (m *DeviceManager) Systems() ([]*System, error) { 1465 // it's tough luck when we cannot determine the current system seed 1466 systemMode := m.SystemMode(SysAny) 1467 currentSys, _ := currentSystemForMode(m.state, systemMode) 1468 1469 systemLabels, err := filepath.Glob(filepath.Join(dirs.SnapSeedDir, "systems", "*")) 1470 if err != nil && !os.IsNotExist(err) { 1471 return nil, fmt.Errorf("cannot list available systems: %v", err) 1472 } 1473 if len(systemLabels) == 0 { 1474 // maybe not a UC20 system 1475 return nil, ErrNoSystems 1476 } 1477 1478 var systems []*System 1479 for _, fpLabel := range systemLabels { 1480 label := filepath.Base(fpLabel) 1481 system, err := systemFromSeed(label, currentSys) 1482 if err != nil { 1483 // TODO:UC20 add a Broken field to the seed system like 1484 // we do for snap.Info 1485 logger.Noticef("cannot load system %q seed: %v", label, err) 1486 continue 1487 } 1488 systems = append(systems, system) 1489 } 1490 return systems, nil 1491 } 1492 1493 var ErrUnsupportedAction = errors.New("unsupported action") 1494 1495 // Reboot triggers a reboot into the given systemLabel and mode. 1496 // 1497 // When called without a systemLabel and without a mode it will just 1498 // trigger a regular reboot. 1499 // 1500 // When called without a systemLabel but with a mode it will use 1501 // the current system to enter the given mode. 1502 // 1503 // Note that "recover" and "run" modes are only available for the 1504 // current system. 1505 func (m *DeviceManager) Reboot(systemLabel, mode string) error { 1506 rebootCurrent := func() { 1507 logger.Noticef("rebooting system") 1508 m.state.RequestRestart(state.RestartSystemNow) 1509 } 1510 1511 // most simple case: just reboot 1512 if systemLabel == "" && mode == "" { 1513 m.state.Lock() 1514 defer m.state.Unlock() 1515 1516 rebootCurrent() 1517 return nil 1518 } 1519 1520 // no systemLabel means "current" so get the current system label 1521 if systemLabel == "" { 1522 systemMode := m.SystemMode(SysAny) 1523 currentSys, err := currentSystemForMode(m.state, systemMode) 1524 if err != nil { 1525 return fmt.Errorf("cannot get current system: %v", err) 1526 } 1527 systemLabel = currentSys.System 1528 } 1529 1530 switched := func(systemLabel string, sysAction *SystemAction) { 1531 logger.Noticef("rebooting into system %q in %q mode", systemLabel, sysAction.Mode) 1532 m.state.RequestRestart(state.RestartSystemNow) 1533 } 1534 // even if we are already in the right mode we restart here by 1535 // passing rebootCurrent as this is what the user requested 1536 return m.switchToSystemAndMode(systemLabel, mode, rebootCurrent, switched) 1537 } 1538 1539 // RequestSystemAction requests the provided system to be run in a 1540 // given mode as specified by action. 1541 // A system reboot will be requested when the request can be 1542 // successfully carried out. 1543 func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error { 1544 if systemLabel == "" { 1545 return fmt.Errorf("internal error: system label is unset") 1546 } 1547 1548 nop := func() {} 1549 switched := func(systemLabel string, sysAction *SystemAction) { 1550 logger.Noticef("restarting into system %q for action %q", systemLabel, sysAction.Title) 1551 m.state.RequestRestart(state.RestartSystemNow) 1552 } 1553 // we do nothing (nop) if the mode and system are the same 1554 return m.switchToSystemAndMode(systemLabel, action.Mode, nop, switched) 1555 } 1556 1557 // switchToSystemAndMode switches to given systemLabel and mode. 1558 // If the systemLabel and mode are the same as current, it calls 1559 // sameSystemAndMode. If successful otherwise it calls switched. Both 1560 // are called with the state lock held. 1561 func (m *DeviceManager) switchToSystemAndMode(systemLabel, mode string, sameSystemAndMode func(), switched func(systemLabel string, sysAction *SystemAction)) error { 1562 if err := checkSystemRequestConflict(m.state, systemLabel); err != nil { 1563 return err 1564 } 1565 1566 systemMode := m.SystemMode(SysAny) 1567 // ignore the error to be robust in scenarios that 1568 // dont' stricly require currentSys to be carried through. 1569 // make sure that currentSys == nil does not break 1570 // the code below! 1571 // TODO: should we log the error? 1572 currentSys, _ := currentSystemForMode(m.state, systemMode) 1573 1574 systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel) 1575 if _, err := os.Stat(systemSeedDir); err != nil { 1576 // XXX: should we wrap this instead return a naked stat error? 1577 return err 1578 } 1579 system, err := systemFromSeed(systemLabel, currentSys) 1580 if err != nil { 1581 return fmt.Errorf("cannot load seed system: %v", err) 1582 } 1583 1584 var sysAction *SystemAction 1585 for _, act := range system.Actions { 1586 if mode == act.Mode { 1587 sysAction = &act 1588 break 1589 } 1590 } 1591 if sysAction == nil { 1592 // XXX: provide more context here like what mode was requested? 1593 return ErrUnsupportedAction 1594 } 1595 1596 // XXX: requested mode is valid; only current system has 'run' and 1597 // recover 'actions' 1598 1599 switch systemMode { 1600 case "recover", "run": 1601 // if going from recover to recover or from run to run and the systems 1602 // are the same do nothing 1603 if systemMode == sysAction.Mode && currentSys != nil && systemLabel == currentSys.System { 1604 m.state.Lock() 1605 defer m.state.Unlock() 1606 sameSystemAndMode() 1607 return nil 1608 } 1609 case "install": 1610 // requesting system actions in install mode does not make sense atm 1611 // 1612 // TODO:UC20: maybe factory hooks will be able to something like 1613 // this? 1614 return ErrUnsupportedAction 1615 default: 1616 // probably test device manager mocking problem, or also potentially 1617 // missing modeenv 1618 return fmt.Errorf("internal error: unexpected manager system mode %q", systemMode) 1619 } 1620 1621 m.state.Lock() 1622 defer m.state.Unlock() 1623 1624 deviceCtx, err := DeviceCtx(m.state, nil, nil) 1625 if err != nil { 1626 return err 1627 } 1628 if err := boot.SetRecoveryBootSystemAndMode(deviceCtx, systemLabel, mode); err != nil { 1629 return fmt.Errorf("cannot set device to boot into system %q in mode %q: %v", systemLabel, mode, err) 1630 } 1631 1632 switched(systemLabel, sysAction) 1633 return nil 1634 } 1635 1636 // implement storecontext.Backend 1637 1638 type storeContextBackend struct { 1639 *DeviceManager 1640 } 1641 1642 func (scb storeContextBackend) Device() (*auth.DeviceState, error) { 1643 return scb.DeviceManager.device() 1644 } 1645 1646 func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error { 1647 return scb.DeviceManager.setDevice(device) 1648 } 1649 1650 func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) { 1651 st := scb.DeviceManager.state 1652 return proxyStore(st, config.NewTransaction(st)) 1653 } 1654 1655 // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce. 1656 func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) { 1657 if serial == nil { 1658 // shouldn't happen, but be safe 1659 return nil, fmt.Errorf("internal error: cannot sign a session request without a serial") 1660 } 1661 1662 privKey, err := scb.DeviceManager.keyPair() 1663 if err == state.ErrNoState { 1664 return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key") 1665 } 1666 if err != nil { 1667 return nil, err 1668 } 1669 1670 a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{ 1671 "brand-id": serial.BrandID(), 1672 "model": serial.Model(), 1673 "serial": serial.Serial(), 1674 "nonce": nonce, 1675 "timestamp": time.Now().UTC().Format(time.RFC3339), 1676 }, nil, privKey) 1677 if err != nil { 1678 return nil, err 1679 } 1680 1681 return a.(*asserts.DeviceSessionRequest), nil 1682 } 1683 1684 func (m *DeviceManager) StoreContextBackend() storecontext.Backend { 1685 return storeContextBackend{m} 1686 } 1687 1688 func (m *DeviceManager) hasFDESetupHook() (bool, error) { 1689 // state must be locked 1690 st := m.state 1691 1692 deviceCtx, err := DeviceCtx(st, nil, nil) 1693 if err != nil { 1694 return false, fmt.Errorf("cannot get device context: %v", err) 1695 } 1696 1697 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1698 if err != nil { 1699 return false, fmt.Errorf("cannot get kernel info: %v", err) 1700 } 1701 return hasFDESetupHookInKernel(kernelInfo), nil 1702 } 1703 1704 func (m *DeviceManager) runFDESetupHook(req *fde.SetupRequest) ([]byte, error) { 1705 // TODO:UC20: when this runs on refresh we need to be very careful 1706 // that we never run this when the kernel is not fully configured 1707 // i.e. when there are no security profiles for the hook 1708 1709 // state must be locked 1710 st := m.state 1711 1712 deviceCtx, err := DeviceCtx(st, nil, nil) 1713 if err != nil { 1714 return nil, fmt.Errorf("cannot get device context to run fde-setup hook: %v", err) 1715 } 1716 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 1717 if err != nil { 1718 return nil, fmt.Errorf("cannot get kernel info to run fde-setup hook: %v", err) 1719 } 1720 hooksup := &hookstate.HookSetup{ 1721 Snap: kernelInfo.InstanceName(), 1722 Revision: kernelInfo.Revision, 1723 Hook: "fde-setup", 1724 // XXX: should this be configurable somehow? 1725 Timeout: 5 * time.Minute, 1726 } 1727 contextData := map[string]interface{}{ 1728 "fde-setup-request": req, 1729 } 1730 st.Unlock() 1731 defer st.Lock() 1732 context, err := m.hookMgr.EphemeralRunHook(context.Background(), hooksup, contextData) 1733 if err != nil { 1734 return nil, fmt.Errorf("cannot run hook for %q: %v", req.Op, err) 1735 } 1736 // the hook is expected to call "snapctl fde-setup-result" which 1737 // will set the "fde-setup-result" value on the task 1738 var hookOutput []byte 1739 context.Lock() 1740 err = context.Get("fde-setup-result", &hookOutput) 1741 context.Unlock() 1742 if err != nil { 1743 return nil, fmt.Errorf("cannot get result from fde-setup hook %q: %v", req.Op, err) 1744 } 1745 return hookOutput, nil 1746 } 1747 1748 func (m *DeviceManager) checkFDEFeatures(st *state.State) error { 1749 // TODO: move most of this to kernel/fde.Features 1750 // Run fde-setup hook with "op":"features". If the hook 1751 // returns any {"features":[...]} reply we consider the 1752 // hardware supported. If the hook errors or if it returns 1753 // {"error":"hardware-unsupported"} we don't. 1754 req := &fde.SetupRequest{ 1755 Op: "features", 1756 } 1757 output, err := m.runFDESetupHook(req) 1758 if err != nil { 1759 return err 1760 } 1761 var res struct { 1762 Features []string `json:"features"` 1763 Error string `json:"error"` 1764 } 1765 if err := json.Unmarshal(output, &res); err != nil { 1766 return fmt.Errorf("cannot parse hook output %q: %v", output, err) 1767 } 1768 if res.Features == nil && res.Error == "" { 1769 return fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`) 1770 } 1771 if res.Error != "" { 1772 return fmt.Errorf("cannot use hook: it returned error: %v", res.Error) 1773 } 1774 return nil 1775 } 1776 1777 func hasFDESetupHookInKernel(kernelInfo *snap.Info) bool { 1778 _, ok := kernelInfo.Hooks["fde-setup"] 1779 return ok 1780 } 1781 1782 type fdeSetupHandler struct { 1783 context *hookstate.Context 1784 } 1785 1786 func newFdeSetupHandler(ctx *hookstate.Context) hookstate.Handler { 1787 return fdeSetupHandler{context: ctx} 1788 } 1789 1790 func (h fdeSetupHandler) Before() error { 1791 return nil 1792 } 1793 1794 func (h fdeSetupHandler) Done() error { 1795 return nil 1796 } 1797 1798 func (h fdeSetupHandler) Error(err error) (bool, error) { 1799 return false, nil 1800 }