github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/devicestate/devicemgr.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2019 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  	"fmt"
    24  	"regexp"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/snapcore/snapd/asserts"
    29  	"github.com/snapcore/snapd/asserts/sysdb"
    30  	"github.com/snapcore/snapd/boot"
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/i18n"
    33  	"github.com/snapcore/snapd/overlord/assertstate"
    34  	"github.com/snapcore/snapd/overlord/auth"
    35  	"github.com/snapcore/snapd/overlord/configstate/config"
    36  	"github.com/snapcore/snapd/overlord/devicestate/internal"
    37  	"github.com/snapcore/snapd/overlord/hookstate"
    38  	"github.com/snapcore/snapd/overlord/snapstate"
    39  	"github.com/snapcore/snapd/overlord/state"
    40  	"github.com/snapcore/snapd/overlord/storecontext"
    41  	"github.com/snapcore/snapd/release"
    42  	"github.com/snapcore/snapd/timings"
    43  )
    44  
    45  // DeviceManager is responsible for managing the device identity and device
    46  // policies.
    47  type DeviceManager struct {
    48  	state      *state.State
    49  	keypairMgr asserts.KeypairManager
    50  
    51  	// newStore can make new stores for remodeling
    52  	newStore func(storecontext.DeviceBackend) snapstate.StoreService
    53  
    54  	bootOkRan            bool
    55  	bootRevisionsUpdated bool
    56  
    57  	ensureSeedInConfigRan bool
    58  
    59  	lastBecomeOperationalAttempt time.Time
    60  	becomeOperationalBackoff     time.Duration
    61  	registered                   bool
    62  	reg                          chan struct{}
    63  }
    64  
    65  // Manager returns a new device manager.
    66  func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.TaskRunner, newStore func(storecontext.DeviceBackend) snapstate.StoreService) (*DeviceManager, error) {
    67  	delayedCrossMgrInit()
    68  
    69  	keypairMgr, err := asserts.OpenFSKeypairManager(dirs.SnapDeviceDir)
    70  	if err != nil {
    71  		return nil, err
    72  
    73  	}
    74  
    75  	m := &DeviceManager{
    76  		state:      s,
    77  		keypairMgr: keypairMgr,
    78  		newStore:   newStore,
    79  		reg:        make(chan struct{}),
    80  	}
    81  
    82  	s.Lock()
    83  	s.Cache(deviceMgrKey{}, m)
    84  	s.Unlock()
    85  
    86  	if err := m.confirmRegistered(); err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	hookManager.Register(regexp.MustCompile("^prepare-device$"), newPrepareDeviceHandler)
    91  
    92  	runner.AddHandler("generate-device-key", m.doGenerateDeviceKey, nil)
    93  	runner.AddHandler("request-serial", m.doRequestSerial, nil)
    94  	runner.AddHandler("mark-seeded", m.doMarkSeeded, nil)
    95  	runner.AddHandler("prepare-remodeling", m.doPrepareRemodeling, nil)
    96  	runner.AddCleanup("prepare-remodeling", m.cleanupRemodel)
    97  	// this *must* always run last and finalizes a remodel
    98  	runner.AddHandler("set-model", m.doSetModel, nil)
    99  	runner.AddCleanup("set-model", m.cleanupRemodel)
   100  	// There is no undo for successful gadget updates. The system is
   101  	// rebooted during update, if it boots up to the point where snapd runs
   102  	// we deem the new assets (be it bootloader or firmware) functional. The
   103  	// deployed boot assets must be backward compatible with reverted kernel
   104  	// or gadget snaps. There are no further changes to the boot assets,
   105  	// unless a new gadget update is deployed.
   106  	runner.AddHandler("update-gadget-assets", m.doUpdateGadgetAssets, nil)
   107  
   108  	runner.AddBlocked(gadgetUpdateBlocked)
   109  
   110  	return m, nil
   111  }
   112  
   113  type deviceMgrKey struct{}
   114  
   115  func deviceMgr(st *state.State) *DeviceManager {
   116  	mgr := st.Cached(deviceMgrKey{})
   117  	if mgr == nil {
   118  		panic("internal error: device manager is not yet associated with state")
   119  	}
   120  	return mgr.(*DeviceManager)
   121  }
   122  
   123  func (m *DeviceManager) CanStandby() bool {
   124  	var seeded bool
   125  	if err := m.state.Get("seeded", &seeded); err != nil {
   126  		return false
   127  	}
   128  	return seeded
   129  }
   130  
   131  func (m *DeviceManager) confirmRegistered() error {
   132  	m.state.Lock()
   133  	defer m.state.Unlock()
   134  
   135  	device, err := m.device()
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	if device.Serial != "" {
   141  		m.markRegistered()
   142  	}
   143  	return nil
   144  }
   145  
   146  func (m *DeviceManager) markRegistered() {
   147  	if m.registered {
   148  		return
   149  	}
   150  	m.registered = true
   151  	close(m.reg)
   152  }
   153  
   154  func gadgetUpdateBlocked(cand *state.Task, running []*state.Task) bool {
   155  	if cand.Kind() == "update-gadget-assets" && len(running) != 0 {
   156  		// update-gadget-assets must be the only task running
   157  		return true
   158  	} else {
   159  		for _, other := range running {
   160  			if other.Kind() == "update-gadget-assets" {
   161  				// no other task can be started when
   162  				// update-gadget-assets is running
   163  				return true
   164  			}
   165  		}
   166  	}
   167  
   168  	return false
   169  }
   170  
   171  type prepareDeviceHandler struct{}
   172  
   173  func newPrepareDeviceHandler(context *hookstate.Context) hookstate.Handler {
   174  	return prepareDeviceHandler{}
   175  }
   176  
   177  func (h prepareDeviceHandler) Before() error {
   178  	return nil
   179  }
   180  
   181  func (h prepareDeviceHandler) Done() error {
   182  	return nil
   183  }
   184  
   185  func (h prepareDeviceHandler) Error(err error) error {
   186  	return nil
   187  }
   188  
   189  func (m *DeviceManager) changeInFlight(kind string) bool {
   190  	for _, chg := range m.state.Changes() {
   191  		if chg.Kind() == kind && !chg.Status().Ready() {
   192  			// change already in motion
   193  			return true
   194  		}
   195  	}
   196  	return false
   197  }
   198  
   199  // helpers to keep count of attempts to get a serial, useful to decide
   200  // to give up holding off trying to auto-refresh
   201  
   202  type ensureOperationalAttemptsKey struct{}
   203  
   204  func incEnsureOperationalAttempts(st *state.State) {
   205  	cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int)
   206  	st.Cache(ensureOperationalAttemptsKey{}, cur+1)
   207  }
   208  
   209  func ensureOperationalAttempts(st *state.State) int {
   210  	cur, _ := st.Cached(ensureOperationalAttemptsKey{}).(int)
   211  	return cur
   212  }
   213  
   214  // ensureOperationalShouldBackoff returns whether we should abstain from
   215  // further become-operational tentatives while its backoff interval is
   216  // not expired.
   217  func (m *DeviceManager) ensureOperationalShouldBackoff(now time.Time) bool {
   218  	if !m.lastBecomeOperationalAttempt.IsZero() && m.lastBecomeOperationalAttempt.Add(m.becomeOperationalBackoff).After(now) {
   219  		return true
   220  	}
   221  	if m.becomeOperationalBackoff == 0 {
   222  		m.becomeOperationalBackoff = 5 * time.Minute
   223  	} else {
   224  		newBackoff := m.becomeOperationalBackoff * 2
   225  		if newBackoff > (12 * time.Hour) {
   226  			newBackoff = 24 * time.Hour
   227  		}
   228  		m.becomeOperationalBackoff = newBackoff
   229  	}
   230  	m.lastBecomeOperationalAttempt = now
   231  	return false
   232  }
   233  
   234  func setClassicFallbackModel(st *state.State, device *auth.DeviceState) error {
   235  	err := assertstate.Add(st, sysdb.GenericClassicModel())
   236  	if err != nil && !asserts.IsUnaccceptedUpdate(err) {
   237  		return fmt.Errorf(`cannot install "generic-classic" fallback model assertion: %v`, err)
   238  	}
   239  	device.Brand = "generic"
   240  	device.Model = "generic-classic"
   241  	if err := internal.SetDevice(st, device); err != nil {
   242  		return err
   243  	}
   244  	return nil
   245  }
   246  
   247  func (m *DeviceManager) ensureOperational() error {
   248  	m.state.Lock()
   249  	defer m.state.Unlock()
   250  
   251  	perfTimings := timings.New(map[string]string{"ensure": "become-operational"})
   252  
   253  	device, err := m.device()
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	if device.Serial != "" {
   259  		// serial is set, we are all set
   260  		return nil
   261  	}
   262  
   263  	// conditions to trigger device registration
   264  	//
   265  	// * have a model assertion with a gadget (core and
   266  	//   device-like classic) in which case we need also to wait
   267  	//   for the gadget to have been installed though
   268  	// TODO: consider a way to support lazy registration on classic
   269  	// even with a gadget and some preseeded snaps
   270  	//
   271  	// * classic with a model assertion with a non-default store specified
   272  	// * lazy classic case (might have a model with no gadget nor store
   273  	//   or no model): we wait to have some snaps installed or be
   274  	//   in the process to install some
   275  
   276  	var seeded bool
   277  	err = m.state.Get("seeded", &seeded)
   278  	if err != nil && err != state.ErrNoState {
   279  		return err
   280  	}
   281  
   282  	if device.Brand == "" || device.Model == "" {
   283  		if !release.OnClassic || !seeded {
   284  			return nil
   285  		}
   286  		// we are on classic and seeded but there is no model:
   287  		// use a fallback model!
   288  		err := setClassicFallbackModel(m.state, device)
   289  		if err != nil {
   290  			return err
   291  		}
   292  	}
   293  
   294  	if m.changeInFlight("become-operational") {
   295  		return nil
   296  	}
   297  
   298  	var storeID, gadget string
   299  	model, err := m.Model()
   300  	if err != nil && err != state.ErrNoState {
   301  		return err
   302  	}
   303  	if err == nil {
   304  		gadget = model.Gadget()
   305  		storeID = model.Store()
   306  	} else {
   307  		return fmt.Errorf("internal error: core device brand and model are set but there is no model assertion")
   308  	}
   309  
   310  	if gadget == "" && storeID == "" {
   311  		// classic: if we have no gadget and no non-default store
   312  		// wait to have snaps or snap installation
   313  
   314  		n, err := snapstate.NumSnaps(m.state)
   315  		if err != nil {
   316  			return err
   317  		}
   318  		if n == 0 && !snapstate.Installing(m.state) {
   319  			return nil
   320  		}
   321  	}
   322  
   323  	var hasPrepareDeviceHook bool
   324  	// if there's a gadget specified wait for it
   325  	if gadget != "" {
   326  		// if have a gadget wait until seeded to proceed
   327  		if !seeded {
   328  			// this will be run again, so eventually when the system is
   329  			// seeded the code below runs
   330  			return nil
   331  
   332  		}
   333  
   334  		gadgetInfo, err := snapstate.CurrentInfo(m.state, gadget)
   335  		if err != nil {
   336  			return err
   337  		}
   338  		hasPrepareDeviceHook = (gadgetInfo.Hooks["prepare-device"] != nil)
   339  	}
   340  
   341  	// have some backoff between full retries
   342  	if m.ensureOperationalShouldBackoff(time.Now()) {
   343  		return nil
   344  	}
   345  	// increment attempt count
   346  	incEnsureOperationalAttempts(m.state)
   347  
   348  	// XXX: some of these will need to be split and use hooks
   349  	// retries might need to embrace more than one "task" then,
   350  	// need to be careful
   351  
   352  	tasks := []*state.Task{}
   353  
   354  	var prepareDevice *state.Task
   355  	if hasPrepareDeviceHook {
   356  		summary := i18n.G("Run prepare-device hook")
   357  		hooksup := &hookstate.HookSetup{
   358  			Snap: gadget,
   359  			Hook: "prepare-device",
   360  		}
   361  		prepareDevice = hookstate.HookTask(m.state, summary, hooksup, nil)
   362  		tasks = append(tasks, prepareDevice)
   363  		// hooks are under a different manager, make sure we consider
   364  		// it immediately
   365  		m.state.EnsureBefore(0)
   366  	}
   367  
   368  	genKey := m.state.NewTask("generate-device-key", i18n.G("Generate device key"))
   369  	if prepareDevice != nil {
   370  		genKey.WaitFor(prepareDevice)
   371  	}
   372  	tasks = append(tasks, genKey)
   373  	requestSerial := m.state.NewTask("request-serial", i18n.G("Request device serial"))
   374  	requestSerial.WaitFor(genKey)
   375  	tasks = append(tasks, requestSerial)
   376  
   377  	chg := m.state.NewChange("become-operational", i18n.G("Initialize device"))
   378  	chg.AddAll(state.NewTaskSet(tasks...))
   379  
   380  	perfTimings.AddTag("change-id", chg.ID())
   381  	perfTimings.Save(m.state)
   382  
   383  	return nil
   384  }
   385  
   386  var populateStateFromSeed = populateStateFromSeedImpl
   387  
   388  // ensureSnaps makes sure that the snaps from seed.yaml get installed
   389  // with the matching assertions
   390  func (m *DeviceManager) ensureSeedYaml() error {
   391  	m.state.Lock()
   392  	defer m.state.Unlock()
   393  
   394  	perfTimings := timings.New(map[string]string{"ensure": "seed"})
   395  
   396  	var seeded bool
   397  	err := m.state.Get("seeded", &seeded)
   398  	if err != nil && err != state.ErrNoState {
   399  		return err
   400  	}
   401  	if seeded {
   402  		return nil
   403  	}
   404  
   405  	if m.changeInFlight("seed") {
   406  		return nil
   407  	}
   408  
   409  	var tsAll []*state.TaskSet
   410  	timings.Run(perfTimings, "state-from-seed", "populate state from seed", func(tm timings.Measurer) {
   411  		tsAll, err = populateStateFromSeed(m.state, tm)
   412  	})
   413  	if err != nil {
   414  		return err
   415  	}
   416  	if len(tsAll) == 0 {
   417  		return nil
   418  	}
   419  
   420  	msg := fmt.Sprintf("Initialize system state")
   421  	chg := m.state.NewChange("seed", msg)
   422  	for _, ts := range tsAll {
   423  		chg.AddAll(ts)
   424  	}
   425  	m.state.EnsureBefore(0)
   426  
   427  	perfTimings.AddTag("change-id", chg.ID())
   428  	perfTimings.Save(m.state)
   429  	return nil
   430  }
   431  
   432  func (m *DeviceManager) ensureBootOk() error {
   433  	m.state.Lock()
   434  	defer m.state.Unlock()
   435  
   436  	if release.OnClassic {
   437  		return nil
   438  	}
   439  
   440  	if !m.bootOkRan {
   441  		if err := boot.MarkBootSuccessful(); err != nil {
   442  			return err
   443  		}
   444  		m.bootOkRan = true
   445  	}
   446  
   447  	if !m.bootRevisionsUpdated {
   448  		if err := snapstate.UpdateBootRevisions(m.state); err != nil {
   449  			return err
   450  		}
   451  		m.bootRevisionsUpdated = true
   452  	}
   453  
   454  	return nil
   455  }
   456  
   457  func markSeededInConfig(st *state.State) error {
   458  	var seedDone bool
   459  	tr := config.NewTransaction(st)
   460  	if err := tr.Get("core", "seed.loaded", &seedDone); err != nil && !config.IsNoOption(err) {
   461  		return err
   462  	}
   463  	if !seedDone {
   464  		if err := tr.Set("core", "seed.loaded", true); err != nil {
   465  			return err
   466  		}
   467  		tr.Commit()
   468  	}
   469  	return nil
   470  }
   471  
   472  func (m *DeviceManager) ensureSeedInConfig() error {
   473  	m.state.Lock()
   474  	defer m.state.Unlock()
   475  
   476  	if !m.ensureSeedInConfigRan {
   477  		// get global seeded option
   478  		var seeded bool
   479  		if err := m.state.Get("seeded", &seeded); err != nil && err != state.ErrNoState {
   480  			return err
   481  		}
   482  		if !seeded {
   483  			// wait for ensure again, this is fine because
   484  			// doMarkSeeded will run "EnsureBefore(0)"
   485  			return nil
   486  		}
   487  
   488  		// Sync seeding with the configuration state. We need to
   489  		// do this here to ensure that old systems which did not
   490  		// set the configuration on seeding get the configuration
   491  		// update too.
   492  		if err := markSeededInConfig(m.state); err != nil {
   493  			return err
   494  		}
   495  		m.ensureSeedInConfigRan = true
   496  	}
   497  
   498  	return nil
   499  
   500  }
   501  
   502  type ensureError struct {
   503  	errs []error
   504  }
   505  
   506  func (e *ensureError) Error() string {
   507  	if len(e.errs) == 1 {
   508  		return fmt.Sprintf("devicemgr: %v", e.errs[0])
   509  	}
   510  	parts := []string{"devicemgr:"}
   511  	for _, e := range e.errs {
   512  		parts = append(parts, e.Error())
   513  	}
   514  	return strings.Join(parts, "\n - ")
   515  }
   516  
   517  // Ensure implements StateManager.Ensure.
   518  func (m *DeviceManager) Ensure() error {
   519  	var errs []error
   520  
   521  	if err := m.ensureSeedYaml(); err != nil {
   522  		errs = append(errs, err)
   523  	}
   524  	if err := m.ensureOperational(); err != nil {
   525  		errs = append(errs, err)
   526  	}
   527  
   528  	if err := m.ensureBootOk(); err != nil {
   529  		errs = append(errs, err)
   530  	}
   531  
   532  	if err := m.ensureSeedInConfig(); err != nil {
   533  		errs = append(errs, err)
   534  	}
   535  
   536  	if len(errs) > 0 {
   537  		return &ensureError{errs}
   538  	}
   539  
   540  	return nil
   541  }
   542  
   543  func (m *DeviceManager) keyPair() (asserts.PrivateKey, error) {
   544  	device, err := m.device()
   545  	if err != nil {
   546  		return nil, err
   547  	}
   548  
   549  	if device.KeyID == "" {
   550  		return nil, state.ErrNoState
   551  	}
   552  
   553  	privKey, err := m.keypairMgr.Get(device.KeyID)
   554  	if err != nil {
   555  		return nil, fmt.Errorf("cannot read device key pair: %v", err)
   556  	}
   557  	return privKey, nil
   558  }
   559  
   560  // Registered returns a channel that is closed when the device is known to have been registered.
   561  func (m *DeviceManager) Registered() <-chan struct{} {
   562  	return m.reg
   563  }
   564  
   565  // device returns current device state.
   566  func (m *DeviceManager) device() (*auth.DeviceState, error) {
   567  	return internal.Device(m.state)
   568  }
   569  
   570  // setDevice sets the device details in the state.
   571  func (m *DeviceManager) setDevice(device *auth.DeviceState) error {
   572  	return internal.SetDevice(m.state, device)
   573  }
   574  
   575  // Model returns the device model assertion.
   576  func (m *DeviceManager) Model() (*asserts.Model, error) {
   577  	return findModel(m.state)
   578  }
   579  
   580  // Serial returns the device serial assertion.
   581  func (m *DeviceManager) Serial() (*asserts.Serial, error) {
   582  	return findSerial(m.state, nil)
   583  }
   584  
   585  // implement storecontext.Backend
   586  
   587  type storeContextBackend struct {
   588  	*DeviceManager
   589  }
   590  
   591  func (scb storeContextBackend) Device() (*auth.DeviceState, error) {
   592  	return scb.DeviceManager.device()
   593  }
   594  
   595  func (scb storeContextBackend) SetDevice(device *auth.DeviceState) error {
   596  	return scb.DeviceManager.setDevice(device)
   597  }
   598  
   599  func (scb storeContextBackend) ProxyStore() (*asserts.Store, error) {
   600  	st := scb.DeviceManager.state
   601  	return proxyStore(st, config.NewTransaction(st))
   602  }
   603  
   604  // SignDeviceSessionRequest produces a signed device-session-request with for given serial assertion and nonce.
   605  func (scb storeContextBackend) SignDeviceSessionRequest(serial *asserts.Serial, nonce string) (*asserts.DeviceSessionRequest, error) {
   606  	if serial == nil {
   607  		// shouldn't happen, but be safe
   608  		return nil, fmt.Errorf("internal error: cannot sign a session request without a serial")
   609  	}
   610  
   611  	privKey, err := scb.DeviceManager.keyPair()
   612  	if err == state.ErrNoState {
   613  		return nil, fmt.Errorf("internal error: inconsistent state with serial but no device key")
   614  	}
   615  	if err != nil {
   616  		return nil, err
   617  	}
   618  
   619  	a, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType, map[string]interface{}{
   620  		"brand-id":  serial.BrandID(),
   621  		"model":     serial.Model(),
   622  		"serial":    serial.Serial(),
   623  		"nonce":     nonce,
   624  		"timestamp": time.Now().UTC().Format(time.RFC3339),
   625  	}, nil, privKey)
   626  	if err != nil {
   627  		return nil, err
   628  	}
   629  
   630  	return a.(*asserts.DeviceSessionRequest), nil
   631  }
   632  
   633  func (m *DeviceManager) StoreContextBackend() storecontext.Backend {
   634  	return storeContextBackend{m}
   635  }