github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/overlord/devicestate/devicestate_install_mode_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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_test
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  
    29  	. "gopkg.in/check.v1"
    30  	"gopkg.in/tomb.v2"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/boot"
    34  	"github.com/snapcore/snapd/bootloader"
    35  	"github.com/snapcore/snapd/bootloader/bootloadertest"
    36  	"github.com/snapcore/snapd/dirs"
    37  	"github.com/snapcore/snapd/gadget"
    38  	"github.com/snapcore/snapd/gadget/install"
    39  	"github.com/snapcore/snapd/logger"
    40  	"github.com/snapcore/snapd/overlord/auth"
    41  	"github.com/snapcore/snapd/overlord/devicestate"
    42  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    43  	"github.com/snapcore/snapd/overlord/hookstate"
    44  	"github.com/snapcore/snapd/overlord/snapstate"
    45  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    46  	"github.com/snapcore/snapd/overlord/state"
    47  	"github.com/snapcore/snapd/release"
    48  	"github.com/snapcore/snapd/secboot"
    49  	"github.com/snapcore/snapd/snap"
    50  	"github.com/snapcore/snapd/snap/snaptest"
    51  	"github.com/snapcore/snapd/sysconfig"
    52  	"github.com/snapcore/snapd/testutil"
    53  )
    54  
    55  type deviceMgrInstallModeSuite struct {
    56  	deviceMgrBaseSuite
    57  
    58  	ConfigureTargetSystemOptsPassed []*sysconfig.Options
    59  	ConfigureTargetSystemErr        error
    60  }
    61  
    62  var _ = Suite(&deviceMgrInstallModeSuite{})
    63  
    64  func (s *deviceMgrInstallModeSuite) findInstallSystem() *state.Change {
    65  	for _, chg := range s.state.Changes() {
    66  		if chg.Kind() == "install-system" {
    67  			return chg
    68  		}
    69  	}
    70  	return nil
    71  }
    72  
    73  func (s *deviceMgrInstallModeSuite) SetUpTest(c *C) {
    74  	s.deviceMgrBaseSuite.SetUpTest(c)
    75  
    76  	s.ConfigureTargetSystemOptsPassed = nil
    77  	s.ConfigureTargetSystemErr = nil
    78  	restore := devicestate.MockSysconfigConfigureTargetSystem(func(opts *sysconfig.Options) error {
    79  		s.ConfigureTargetSystemOptsPassed = append(s.ConfigureTargetSystemOptsPassed, opts)
    80  		return s.ConfigureTargetSystemErr
    81  	})
    82  	s.AddCleanup(restore)
    83  
    84  	restore = devicestate.MockSecbootCheckKeySealingSupported(func() error {
    85  		return fmt.Errorf("TPM not available")
    86  	})
    87  	s.AddCleanup(restore)
    88  
    89  	s.state.Lock()
    90  	defer s.state.Unlock()
    91  	s.state.Set("seeded", true)
    92  
    93  	fakeJournalctl := testutil.MockCommand(c, "journalctl", "")
    94  	s.AddCleanup(fakeJournalctl.Restore)
    95  }
    96  
    97  const (
    98  	pcSnapID       = "pcididididididididididididididid"
    99  	pcKernelSnapID = "pckernelidididididididididididid"
   100  	core20SnapID   = "core20ididididididididididididid"
   101  )
   102  
   103  func (s *deviceMgrInstallModeSuite) makeMockInstalledPcGadget(c *C, grade, installDeviceHook string, gadgetDefaultsYaml string) *asserts.Model {
   104  	si := &snap.SideInfo{
   105  		RealName: "pc-kernel",
   106  		Revision: snap.R(1),
   107  		SnapID:   pcKernelSnapID,
   108  	}
   109  	snapstate.Set(s.state, "pc-kernel", &snapstate.SnapState{
   110  		SnapType: "kernel",
   111  		Sequence: []*snap.SideInfo{si},
   112  		Current:  si.Revision,
   113  		Active:   true,
   114  	})
   115  	kernelInfo := snaptest.MockSnapWithFiles(c, "name: pc-kernel\ntype: kernel", si, nil)
   116  	kernelFn := snaptest.MakeTestSnapWithFiles(c, "name: pc-kernel\ntype: kernel\nversion: 1.0", nil)
   117  	err := os.Rename(kernelFn, kernelInfo.MountFile())
   118  	c.Assert(err, IsNil)
   119  
   120  	si = &snap.SideInfo{
   121  		RealName: "pc",
   122  		Revision: snap.R(1),
   123  		SnapID:   pcSnapID,
   124  	}
   125  	snapstate.Set(s.state, "pc", &snapstate.SnapState{
   126  		SnapType: "gadget",
   127  		Sequence: []*snap.SideInfo{si},
   128  		Current:  si.Revision,
   129  		Active:   true,
   130  	})
   131  
   132  	files := [][]string{
   133  		{"meta/gadget.yaml", uc20gadgetYamlWithSave + gadgetDefaultsYaml},
   134  	}
   135  	if installDeviceHook != "" {
   136  		files = append(files, []string{"meta/hooks/install-device", installDeviceHook})
   137  	}
   138  	snaptest.MockSnapWithFiles(c, "name: pc\ntype: gadget", si, files)
   139  
   140  	si = &snap.SideInfo{
   141  		RealName: "core20",
   142  		Revision: snap.R(2),
   143  		SnapID:   core20SnapID,
   144  	}
   145  	snapstate.Set(s.state, "core20", &snapstate.SnapState{
   146  		SnapType: "base",
   147  		Sequence: []*snap.SideInfo{si},
   148  		Current:  si.Revision,
   149  		Active:   true,
   150  	})
   151  	snaptest.MockSnapWithFiles(c, "name: core20\ntype: base", si, nil)
   152  
   153  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
   154  		"display-name": "my model",
   155  		"architecture": "amd64",
   156  		"base":         "core20",
   157  		"grade":        grade,
   158  		"snaps": []interface{}{
   159  			map[string]interface{}{
   160  				"name":            "pc-kernel",
   161  				"id":              pcKernelSnapID,
   162  				"type":            "kernel",
   163  				"default-channel": "20",
   164  			},
   165  			map[string]interface{}{
   166  				"name":            "pc",
   167  				"id":              pcSnapID,
   168  				"type":            "gadget",
   169  				"default-channel": "20",
   170  			}},
   171  	})
   172  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
   173  		Brand: "my-brand",
   174  		Model: "my-model",
   175  		// no serial in install mode
   176  	})
   177  
   178  	return mockModel
   179  }
   180  
   181  type encTestCase struct {
   182  	tpm               bool
   183  	bypass            bool
   184  	encrypt           bool
   185  	trustedBootloader bool
   186  }
   187  
   188  var (
   189  	dataEncryptionKey = secboot.EncryptionKey{'d', 'a', 't', 'a', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
   190  	dataRecoveryKey   = secboot.RecoveryKey{'r', 'e', 'c', 'o', 'v', 'e', 'r', 'y', 10, 11, 12, 13, 14, 15, 16, 17}
   191  
   192  	saveKey      = secboot.EncryptionKey{'s', 'a', 'v', 'e', 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
   193  	reinstallKey = secboot.RecoveryKey{'r', 'e', 'i', 'n', 's', 't', 'a', 'l', 'l', 11, 12, 13, 14, 15, 16, 17}
   194  )
   195  
   196  func (s *deviceMgrInstallModeSuite) doRunChangeTestWithEncryption(c *C, grade string, tc encTestCase) error {
   197  	restore := release.MockOnClassic(false)
   198  	defer restore()
   199  	bootloaderRootdir := c.MkDir()
   200  
   201  	var brGadgetRoot, brDevice string
   202  	var brOpts install.Options
   203  	var installRunCalled int
   204  	var installSealingObserver gadget.ContentObserver
   205  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, obs gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   206  		// ensure we can grab the lock here, i.e. that it's not taken
   207  		s.state.Lock()
   208  		s.state.Unlock()
   209  
   210  		c.Check(mod.Grade(), Equals, asserts.ModelGrade(grade))
   211  
   212  		brGadgetRoot = gadgetRoot
   213  		brDevice = device
   214  		brOpts = options
   215  		installSealingObserver = obs
   216  		installRunCalled++
   217  		var keysForRoles map[string]*install.EncryptionKeySet
   218  		if tc.encrypt {
   219  			keysForRoles = map[string]*install.EncryptionKeySet{
   220  				gadget.SystemData: {
   221  					Key:         dataEncryptionKey,
   222  					RecoveryKey: dataRecoveryKey,
   223  				},
   224  				gadget.SystemSave: {
   225  					Key:         saveKey,
   226  					RecoveryKey: reinstallKey,
   227  				},
   228  			}
   229  		}
   230  		return &install.InstalledSystemSideData{
   231  			KeysForRoles: keysForRoles,
   232  		}, nil
   233  	})
   234  	defer restore()
   235  
   236  	restore = devicestate.MockSecbootCheckKeySealingSupported(func() error {
   237  		if tc.tpm {
   238  			return nil
   239  		} else {
   240  			return fmt.Errorf("TPM not available")
   241  		}
   242  	})
   243  	defer restore()
   244  
   245  	if tc.trustedBootloader {
   246  		tab := bootloadertest.Mock("trusted", bootloaderRootdir).WithTrustedAssets()
   247  		tab.TrustedAssetsList = []string{"trusted-asset"}
   248  		bootloader.Force(tab)
   249  		s.AddCleanup(func() { bootloader.Force(nil) })
   250  
   251  		err := os.MkdirAll(boot.InitramfsUbuntuSeedDir, 0755)
   252  		c.Assert(err, IsNil)
   253  		err = ioutil.WriteFile(filepath.Join(boot.InitramfsUbuntuSeedDir, "trusted-asset"), nil, 0644)
   254  		c.Assert(err, IsNil)
   255  	}
   256  
   257  	s.state.Lock()
   258  	mockModel := s.makeMockInstalledPcGadget(c, grade, "", "")
   259  	s.state.Unlock()
   260  
   261  	bypassEncryptionPath := filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")
   262  	if tc.bypass {
   263  		err := os.MkdirAll(filepath.Dir(bypassEncryptionPath), 0755)
   264  		c.Assert(err, IsNil)
   265  		f, err := os.Create(bypassEncryptionPath)
   266  		c.Assert(err, IsNil)
   267  		f.Close()
   268  	} else {
   269  		os.RemoveAll(bypassEncryptionPath)
   270  	}
   271  
   272  	bootMakeBootableCalled := 0
   273  	restore = devicestate.MockBootMakeSystemRunnable(func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error {
   274  		c.Check(model, DeepEquals, mockModel)
   275  		c.Check(bootWith.KernelPath, Matches, ".*/var/lib/snapd/snaps/pc-kernel_1.snap")
   276  		c.Check(bootWith.BasePath, Matches, ".*/var/lib/snapd/snaps/core20_2.snap")
   277  		c.Check(bootWith.RecoverySystemDir, Matches, "/systems/20191218")
   278  		c.Check(bootWith.UnpackedGadgetDir, Equals, filepath.Join(dirs.SnapMountDir, "pc/1"))
   279  		if tc.encrypt {
   280  			c.Check(seal, NotNil)
   281  		} else {
   282  			c.Check(seal, IsNil)
   283  		}
   284  		bootMakeBootableCalled++
   285  		return nil
   286  	})
   287  	defer restore()
   288  
   289  	modeenv := boot.Modeenv{
   290  		Mode:           "install",
   291  		RecoverySystem: "20191218",
   292  	}
   293  	c.Assert(modeenv.WriteTo(""), IsNil)
   294  	devicestate.SetSystemMode(s.mgr, "install")
   295  
   296  	// normally done by snap-bootstrap
   297  	err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755)
   298  	c.Assert(err, IsNil)
   299  
   300  	s.settle(c)
   301  
   302  	// the install-system change is created
   303  	s.state.Lock()
   304  	defer s.state.Unlock()
   305  	installSystem := s.findInstallSystem()
   306  	c.Assert(installSystem, NotNil)
   307  
   308  	// and was run successfully
   309  	if err := installSystem.Err(); err != nil {
   310  		// we failed, no further checks needed
   311  		return err
   312  	}
   313  
   314  	c.Assert(installSystem.Status(), Equals, state.DoneStatus)
   315  
   316  	// in the right way
   317  	c.Assert(brGadgetRoot, Equals, filepath.Join(dirs.SnapMountDir, "/pc/1"))
   318  	c.Assert(brDevice, Equals, "")
   319  	if tc.encrypt {
   320  		c.Assert(brOpts, DeepEquals, install.Options{
   321  			Mount:   true,
   322  			Encrypt: true,
   323  		})
   324  	} else {
   325  		c.Assert(brOpts, DeepEquals, install.Options{
   326  			Mount: true,
   327  		})
   328  	}
   329  	if tc.encrypt {
   330  		// inteface is not nil
   331  		c.Assert(installSealingObserver, NotNil)
   332  		// we expect a very specific type
   333  		trustedInstallObserver, ok := installSealingObserver.(*boot.TrustedAssetsInstallObserver)
   334  		c.Assert(ok, Equals, true, Commentf("unexpected type: %T", installSealingObserver))
   335  		c.Assert(trustedInstallObserver, NotNil)
   336  	} else {
   337  		c.Assert(installSealingObserver, IsNil)
   338  	}
   339  
   340  	c.Assert(installRunCalled, Equals, 1)
   341  	c.Assert(bootMakeBootableCalled, Equals, 1)
   342  	c.Assert(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   343  
   344  	return nil
   345  }
   346  
   347  func (s *deviceMgrInstallModeSuite) TestInstallTaskErrors(c *C) {
   348  	restore := release.MockOnClassic(false)
   349  	defer restore()
   350  
   351  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   352  		return nil, fmt.Errorf("The horror, The horror")
   353  	})
   354  	defer restore()
   355  
   356  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   357  		[]byte("mode=install\n"), 0644)
   358  	c.Assert(err, IsNil)
   359  
   360  	s.state.Lock()
   361  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   362  	devicestate.SetSystemMode(s.mgr, "install")
   363  	s.state.Unlock()
   364  
   365  	s.settle(c)
   366  
   367  	s.state.Lock()
   368  	defer s.state.Unlock()
   369  
   370  	installSystem := s.findInstallSystem()
   371  	c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks:
   372  - Setup system for run mode \(cannot install system: The horror, The horror\)`)
   373  	// no restart request on failure
   374  	c.Check(s.restartRequests, HasLen, 0)
   375  }
   376  
   377  func (s *deviceMgrInstallModeSuite) TestInstallExpTasks(c *C) {
   378  	restore := release.MockOnClassic(false)
   379  	defer restore()
   380  
   381  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   382  		return nil, nil
   383  	})
   384  	defer restore()
   385  
   386  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   387  		[]byte("mode=install\n"), 0644)
   388  	c.Assert(err, IsNil)
   389  
   390  	s.state.Lock()
   391  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   392  	devicestate.SetSystemMode(s.mgr, "install")
   393  	s.state.Unlock()
   394  
   395  	s.settle(c)
   396  
   397  	s.state.Lock()
   398  	defer s.state.Unlock()
   399  
   400  	installSystem := s.findInstallSystem()
   401  	c.Check(installSystem.Err(), IsNil)
   402  
   403  	tasks := installSystem.Tasks()
   404  	c.Assert(tasks, HasLen, 2)
   405  	setupRunSystemTask := tasks[0]
   406  	restartSystemToRunModeTask := tasks[1]
   407  
   408  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   409  	c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode")
   410  
   411  	// setup-run-system has no pre-reqs
   412  	c.Assert(setupRunSystemTask.WaitTasks(), HasLen, 0)
   413  
   414  	// restart-system-to-run-mode has a pre-req of setup-run-system
   415  	waitTasks := restartSystemToRunModeTask.WaitTasks()
   416  	c.Assert(waitTasks, HasLen, 1)
   417  	c.Assert(waitTasks[0].ID(), Equals, setupRunSystemTask.ID())
   418  
   419  	// we did request a restart through restartSystemToRunModeTask
   420  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   421  }
   422  
   423  func (s *deviceMgrInstallModeSuite) TestInstallWithInstallDeviceHookExpTasks(c *C) {
   424  	restore := release.MockOnClassic(false)
   425  	defer restore()
   426  
   427  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   428  		return nil, nil
   429  	})
   430  	defer restore()
   431  
   432  	hooksCalled := []*hookstate.Context{}
   433  	restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
   434  		ctx.Lock()
   435  		defer ctx.Unlock()
   436  
   437  		hooksCalled = append(hooksCalled, ctx)
   438  		return nil, nil
   439  	})
   440  	defer restore()
   441  
   442  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   443  		[]byte("mode=install\n"), 0644)
   444  	c.Assert(err, IsNil)
   445  
   446  	s.state.Lock()
   447  	s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "")
   448  	devicestate.SetSystemMode(s.mgr, "install")
   449  	s.state.Unlock()
   450  
   451  	s.settle(c)
   452  
   453  	s.state.Lock()
   454  	defer s.state.Unlock()
   455  
   456  	installSystem := s.findInstallSystem()
   457  	c.Check(installSystem.Err(), IsNil)
   458  
   459  	tasks := installSystem.Tasks()
   460  	c.Assert(tasks, HasLen, 3)
   461  	setupRunSystemTask := tasks[0]
   462  	installDevice := tasks[1]
   463  	restartSystemToRunModeTask := tasks[2]
   464  
   465  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   466  	c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode")
   467  	c.Assert(installDevice.Kind(), Equals, "run-hook")
   468  
   469  	// setup-run-system has no pre-reqs
   470  	c.Assert(setupRunSystemTask.WaitTasks(), HasLen, 0)
   471  
   472  	// install-device has a pre-req of setup-run-system
   473  	waitTasks := installDevice.WaitTasks()
   474  	c.Assert(waitTasks, HasLen, 1)
   475  	c.Assert(waitTasks[0].ID(), Equals, setupRunSystemTask.ID())
   476  
   477  	// restart-system-to-run-mode has a pre-req of install-device
   478  	waitTasks = restartSystemToRunModeTask.WaitTasks()
   479  	c.Assert(waitTasks, HasLen, 1)
   480  	c.Assert(waitTasks[0].ID(), Equals, installDevice.ID())
   481  
   482  	// we did request a restart through restartSystemToRunModeTask
   483  	c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow})
   484  
   485  	c.Assert(hooksCalled, HasLen, 1)
   486  	c.Assert(hooksCalled[0].HookName(), Equals, "install-device")
   487  }
   488  
   489  func (s *deviceMgrInstallModeSuite) TestInstallWithBrokenInstallDeviceHookUnhappy(c *C) {
   490  	restore := release.MockOnClassic(false)
   491  	defer restore()
   492  
   493  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   494  		return nil, nil
   495  	})
   496  	defer restore()
   497  
   498  	hooksCalled := []*hookstate.Context{}
   499  	restore = hookstate.MockRunHook(func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
   500  		ctx.Lock()
   501  		defer ctx.Unlock()
   502  
   503  		hooksCalled = append(hooksCalled, ctx)
   504  		return []byte("hook exited broken"), fmt.Errorf("hook broken")
   505  	})
   506  	defer restore()
   507  
   508  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   509  		[]byte("mode=install\n"), 0644)
   510  	c.Assert(err, IsNil)
   511  
   512  	s.state.Lock()
   513  	s.makeMockInstalledPcGadget(c, "dangerous", "install-device-hook-content", "")
   514  	devicestate.SetSystemMode(s.mgr, "install")
   515  	s.state.Unlock()
   516  
   517  	s.settle(c)
   518  
   519  	s.state.Lock()
   520  	defer s.state.Unlock()
   521  
   522  	installSystem := s.findInstallSystem()
   523  	c.Check(installSystem.Err(), ErrorMatches, `cannot perform the following tasks:
   524  - Run install-device hook \(run hook \"install-device\": hook exited broken\)`)
   525  
   526  	tasks := installSystem.Tasks()
   527  	c.Assert(tasks, HasLen, 3)
   528  	setupRunSystemTask := tasks[0]
   529  	installDevice := tasks[1]
   530  	restartSystemToRunModeTask := tasks[2]
   531  
   532  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   533  	c.Assert(installDevice.Kind(), Equals, "run-hook")
   534  	c.Assert(restartSystemToRunModeTask.Kind(), Equals, "restart-system-to-run-mode")
   535  
   536  	// install-device is in Error state
   537  	c.Assert(installDevice.Status(), Equals, state.ErrorStatus)
   538  
   539  	// setup-run-system is in Done (it has no undo handler)
   540  	c.Assert(setupRunSystemTask.Status(), Equals, state.DoneStatus)
   541  
   542  	// restart-system-to-run-mode is in Hold
   543  	c.Assert(restartSystemToRunModeTask.Status(), Equals, state.HoldStatus)
   544  
   545  	// we didn't request a restart since restartsystemToRunMode didn't run
   546  	c.Check(s.restartRequests, HasLen, 0)
   547  
   548  	c.Assert(hooksCalled, HasLen, 1)
   549  	c.Assert(hooksCalled[0].HookName(), Equals, "install-device")
   550  }
   551  
   552  func (s *deviceMgrInstallModeSuite) TestInstallSetupRunSystemTaskNoRestarts(c *C) {
   553  	restore := release.MockOnClassic(false)
   554  	defer restore()
   555  
   556  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   557  		return nil, nil
   558  	})
   559  	defer restore()
   560  
   561  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   562  		[]byte("mode=install\n"), 0644)
   563  	c.Assert(err, IsNil)
   564  
   565  	s.state.Lock()
   566  	defer s.state.Unlock()
   567  
   568  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   569  	devicestate.SetSystemMode(s.mgr, "install")
   570  
   571  	// also set the system as installed so that the install-system change
   572  	// doesn't get automatically added and we can craft our own change with just
   573  	// the setup-run-system task and not with the restart-system-to-run-mode
   574  	// task
   575  	devicestate.SetInstalledRan(s.mgr, true)
   576  
   577  	s.state.Unlock()
   578  	defer s.state.Lock()
   579  
   580  	s.settle(c)
   581  
   582  	s.state.Lock()
   583  	defer s.state.Unlock()
   584  
   585  	// make sure there is no install-system change that snuck in underneath us
   586  	installSystem := s.findInstallSystem()
   587  	c.Check(installSystem, IsNil)
   588  
   589  	t := s.state.NewTask("setup-run-system", "setup run system")
   590  	chg := s.state.NewChange("install-system", "install the system")
   591  	chg.AddTask(t)
   592  
   593  	// now let the change run
   594  	s.state.Unlock()
   595  	defer s.state.Lock()
   596  
   597  	s.settle(c)
   598  
   599  	s.state.Lock()
   600  	defer s.state.Unlock()
   601  
   602  	// now we should have the install-system change
   603  	installSystem = s.findInstallSystem()
   604  	c.Check(installSystem, Not(IsNil))
   605  	c.Check(installSystem.Err(), IsNil)
   606  
   607  	tasks := installSystem.Tasks()
   608  	c.Assert(tasks, HasLen, 1)
   609  	setupRunSystemTask := tasks[0]
   610  
   611  	c.Assert(setupRunSystemTask.Kind(), Equals, "setup-run-system")
   612  
   613  	// we did not request a restart (since that is done in restart-system-to-run-mode)
   614  	c.Check(s.restartRequests, HasLen, 0)
   615  }
   616  
   617  func (s *deviceMgrInstallModeSuite) TestInstallModeNotInstallmodeNoChg(c *C) {
   618  	restore := release.MockOnClassic(false)
   619  	defer restore()
   620  
   621  	s.state.Lock()
   622  	devicestate.SetSystemMode(s.mgr, "")
   623  	s.state.Unlock()
   624  
   625  	s.settle(c)
   626  
   627  	s.state.Lock()
   628  	defer s.state.Unlock()
   629  
   630  	// the install-system change is *not* created (not in install mode)
   631  	installSystem := s.findInstallSystem()
   632  	c.Assert(installSystem, IsNil)
   633  }
   634  
   635  func (s *deviceMgrInstallModeSuite) TestInstallModeNotClassic(c *C) {
   636  	restore := release.MockOnClassic(true)
   637  	defer restore()
   638  
   639  	s.state.Lock()
   640  	devicestate.SetSystemMode(s.mgr, "install")
   641  	s.state.Unlock()
   642  
   643  	s.settle(c)
   644  
   645  	s.state.Lock()
   646  	defer s.state.Unlock()
   647  
   648  	// the install-system change is *not* created (we're on classic)
   649  	installSystem := s.findInstallSystem()
   650  	c.Assert(installSystem, IsNil)
   651  }
   652  
   653  func (s *deviceMgrInstallModeSuite) TestInstallDangerous(c *C) {
   654  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: false, encrypt: false})
   655  	c.Assert(err, IsNil)
   656  }
   657  
   658  func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPM(c *C) {
   659  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{
   660  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   661  	})
   662  	c.Assert(err, IsNil)
   663  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   664  }
   665  
   666  func (s *deviceMgrInstallModeSuite) TestInstallDangerousBypassEncryption(c *C) {
   667  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: false, bypass: true, encrypt: false})
   668  	c.Assert(err, IsNil)
   669  }
   670  
   671  func (s *deviceMgrInstallModeSuite) TestInstallDangerousWithTPMBypassEncryption(c *C) {
   672  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{tpm: true, bypass: true, encrypt: false})
   673  	c.Assert(err, IsNil)
   674  }
   675  
   676  func (s *deviceMgrInstallModeSuite) TestInstallSigned(c *C) {
   677  	err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: false, encrypt: false})
   678  	c.Assert(err, IsNil)
   679  }
   680  
   681  func (s *deviceMgrInstallModeSuite) TestInstallSignedWithTPM(c *C) {
   682  	err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{
   683  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   684  	})
   685  	c.Assert(err, IsNil)
   686  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   687  }
   688  
   689  func (s *deviceMgrInstallModeSuite) TestInstallSignedBypassEncryption(c *C) {
   690  	err := s.doRunChangeTestWithEncryption(c, "signed", encTestCase{tpm: false, bypass: true, encrypt: false})
   691  	c.Assert(err, IsNil)
   692  }
   693  
   694  func (s *deviceMgrInstallModeSuite) TestInstallSecured(c *C) {
   695  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: false, encrypt: false})
   696  	c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*")
   697  }
   698  
   699  func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPM(c *C) {
   700  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
   701  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   702  	})
   703  	c.Assert(err, IsNil)
   704  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   705  }
   706  
   707  func (s *deviceMgrInstallModeSuite) TestInstallDangerousEncryptionWithTPMNoTrustedAssets(c *C) {
   708  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{
   709  		tpm: true, bypass: false, encrypt: true, trustedBootloader: false,
   710  	})
   711  	c.Assert(err, IsNil)
   712  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   713  }
   714  
   715  func (s *deviceMgrInstallModeSuite) TestInstallDangerousNoEncryptionWithTrustedAssets(c *C) {
   716  	err := s.doRunChangeTestWithEncryption(c, "dangerous", encTestCase{
   717  		tpm: false, bypass: false, encrypt: false, trustedBootloader: true,
   718  	})
   719  	c.Assert(err, IsNil)
   720  }
   721  
   722  func (s *deviceMgrInstallModeSuite) TestInstallSecuredWithTPMAndSave(c *C) {
   723  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
   724  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   725  	})
   726  	c.Assert(err, IsNil)
   727  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "recovery.key"), testutil.FileEquals, dataRecoveryKey[:])
   728  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key"), testutil.FileEquals, saveKey[:])
   729  	c.Check(filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key"), testutil.FileEquals, reinstallKey[:])
   730  	marker, err := ioutil.ReadFile(filepath.Join(boot.InstallHostFDEDataDir, "marker"))
   731  	c.Assert(err, IsNil)
   732  	c.Check(marker, HasLen, 32)
   733  	c.Check(filepath.Join(boot.InstallHostFDESaveDir, "marker"), testutil.FileEquals, marker)
   734  }
   735  
   736  func (s *deviceMgrInstallModeSuite) TestInstallSecuredBypassEncryption(c *C) {
   737  	err := s.doRunChangeTestWithEncryption(c, "secured", encTestCase{tpm: false, bypass: true, encrypt: false})
   738  	c.Assert(err, ErrorMatches, "(?s).*cannot encrypt device storage as mandated by model grade secured:.*TPM not available.*")
   739  }
   740  
   741  func (s *deviceMgrInstallModeSuite) TestInstallBootloaderVarSetFails(c *C) {
   742  	restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   743  		c.Check(options.Encrypt, Equals, false)
   744  		// no keys set
   745  		return &install.InstalledSystemSideData{}, nil
   746  	})
   747  	defer restore()
   748  
   749  	restore = devicestate.MockBootEnsureNextBootToRunMode(func(systemLabel string) error {
   750  		c.Check(systemLabel, Equals, "1234")
   751  		// no keys set
   752  		return fmt.Errorf("bootloader goes boom")
   753  	})
   754  	defer restore()
   755  
   756  	restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { return fmt.Errorf("no encrypted soup for you") })
   757  	defer restore()
   758  
   759  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   760  		[]byte("mode=install\nrecovery_system=1234"), 0644)
   761  	c.Assert(err, IsNil)
   762  
   763  	s.state.Lock()
   764  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   765  	devicestate.SetSystemMode(s.mgr, "install")
   766  	s.state.Unlock()
   767  
   768  	s.settle(c)
   769  
   770  	s.state.Lock()
   771  	defer s.state.Unlock()
   772  
   773  	installSystem := s.findInstallSystem()
   774  	c.Check(installSystem.Err(), ErrorMatches, `cannot perform the following tasks:
   775  - Ensure next boot to run mode \(bootloader goes boom\)`)
   776  	// no restart request on failure
   777  	c.Check(s.restartRequests, HasLen, 0)
   778  }
   779  
   780  func (s *deviceMgrInstallModeSuite) testInstallEncryptionSanityChecks(c *C, errMatch string) {
   781  	restore := release.MockOnClassic(false)
   782  	defer restore()
   783  
   784  	restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { return nil })
   785  	defer restore()
   786  
   787  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
   788  		[]byte("mode=install\n"), 0644)
   789  	c.Assert(err, IsNil)
   790  
   791  	s.state.Lock()
   792  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
   793  	devicestate.SetSystemMode(s.mgr, "install")
   794  	s.state.Unlock()
   795  
   796  	s.settle(c)
   797  
   798  	s.state.Lock()
   799  	defer s.state.Unlock()
   800  
   801  	installSystem := s.findInstallSystem()
   802  	c.Check(installSystem.Err(), ErrorMatches, errMatch)
   803  	// no restart request on failure
   804  	c.Check(s.restartRequests, HasLen, 0)
   805  }
   806  
   807  func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoKeys(c *C) {
   808  	restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   809  		c.Check(options.Encrypt, Equals, true)
   810  		// no keys set
   811  		return &install.InstalledSystemSideData{}, nil
   812  	})
   813  	defer restore()
   814  	s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks:
   815  - Setup system for run mode \(internal error: system encryption keys are unset\)`)
   816  }
   817  
   818  func (s *deviceMgrInstallModeSuite) TestInstallEncryptionSanityChecksNoSystemDataKey(c *C) {
   819  	restore := devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   820  		c.Check(options.Encrypt, Equals, true)
   821  		// no keys set
   822  		return &install.InstalledSystemSideData{
   823  			// empty map
   824  			KeysForRoles: map[string]*install.EncryptionKeySet{},
   825  		}, nil
   826  	})
   827  	defer restore()
   828  	s.testInstallEncryptionSanityChecks(c, `(?ms)cannot perform the following tasks:
   829  - Setup system for run mode \(internal error: system encryption keys are unset\)`)
   830  }
   831  
   832  func (s *deviceMgrInstallModeSuite) mockInstallModeChange(c *C, modelGrade, gadgetDefaultsYaml string) *asserts.Model {
   833  	restore := release.MockOnClassic(false)
   834  	defer restore()
   835  
   836  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
   837  		return nil, nil
   838  	})
   839  	defer restore()
   840  
   841  	s.state.Lock()
   842  	mockModel := s.makeMockInstalledPcGadget(c, modelGrade, "", gadgetDefaultsYaml)
   843  	s.state.Unlock()
   844  	c.Check(mockModel.Grade(), Equals, asserts.ModelGrade(modelGrade))
   845  
   846  	restore = devicestate.MockBootMakeSystemRunnable(func(model *asserts.Model, bootWith *boot.BootableSet, seal *boot.TrustedAssetsInstallObserver) error {
   847  		return nil
   848  	})
   849  	defer restore()
   850  
   851  	modeenv := boot.Modeenv{
   852  		Mode:           "install",
   853  		RecoverySystem: "20191218",
   854  	}
   855  	c.Assert(modeenv.WriteTo(""), IsNil)
   856  	devicestate.SetSystemMode(s.mgr, "install")
   857  
   858  	// normally done by snap-bootstrap
   859  	err := os.MkdirAll(boot.InitramfsUbuntuBootDir, 0755)
   860  	c.Assert(err, IsNil)
   861  
   862  	s.settle(c)
   863  
   864  	return mockModel
   865  }
   866  
   867  func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfig(c *C) {
   868  	s.mockInstallModeChange(c, "dangerous", "")
   869  
   870  	s.state.Lock()
   871  	defer s.state.Unlock()
   872  
   873  	// the install-system change is created
   874  	installSystem := s.findInstallSystem()
   875  	c.Assert(installSystem, NotNil)
   876  
   877  	// and was run successfully
   878  	c.Check(installSystem.Err(), IsNil)
   879  	c.Check(installSystem.Status(), Equals, state.DoneStatus)
   880  
   881  	// and sysconfig.ConfigureTargetSystem was run exactly once
   882  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   883  		{
   884  			AllowCloudInit: true,
   885  			TargetRootDir:  boot.InstallHostWritableDir,
   886  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
   887  		},
   888  	})
   889  }
   890  
   891  func (s *deviceMgrInstallModeSuite) TestInstallModeRunSysconfigErr(c *C) {
   892  	s.ConfigureTargetSystemErr = fmt.Errorf("error from sysconfig.ConfigureTargetSystem")
   893  	s.mockInstallModeChange(c, "dangerous", "")
   894  
   895  	s.state.Lock()
   896  	defer s.state.Unlock()
   897  
   898  	// the install-system was run but errorred as specified in the above mock
   899  	installSystem := s.findInstallSystem()
   900  	c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks:
   901  - Setup system for run mode \(error from sysconfig.ConfigureTargetSystem\)`)
   902  	// and sysconfig.ConfigureTargetSystem was run exactly once
   903  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   904  		{
   905  			AllowCloudInit: true,
   906  			TargetRootDir:  boot.InstallHostWritableDir,
   907  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
   908  		},
   909  	})
   910  }
   911  
   912  func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitInDangerous(c *C) {
   913  	// pretend we have a cloud-init config on the seed partition
   914  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
   915  	err := os.MkdirAll(cloudCfg, 0755)
   916  	c.Assert(err, IsNil)
   917  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
   918  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
   919  		c.Assert(err, IsNil)
   920  	}
   921  
   922  	s.mockInstallModeChange(c, "dangerous", "")
   923  
   924  	// and did tell sysconfig about the cloud-init files
   925  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   926  		{
   927  			AllowCloudInit:  true,
   928  			CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"),
   929  			TargetRootDir:   boot.InstallHostWritableDir,
   930  			GadgetDir:       filepath.Join(dirs.SnapMountDir, "pc/1/"),
   931  		},
   932  	})
   933  }
   934  
   935  func (s *deviceMgrInstallModeSuite) TestInstallModeSignedNoUbuntuSeedCloudInit(c *C) {
   936  	// pretend we have a cloud-init config on the seed partition
   937  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
   938  	err := os.MkdirAll(cloudCfg, 0755)
   939  	c.Assert(err, IsNil)
   940  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
   941  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
   942  		c.Assert(err, IsNil)
   943  	}
   944  
   945  	s.mockInstallModeChange(c, "signed", "")
   946  
   947  	// and did NOT tell sysconfig about the cloud-init file, but also did not
   948  	// explicitly disable cloud init
   949  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   950  		{
   951  			AllowCloudInit: true,
   952  			TargetRootDir:  boot.InstallHostWritableDir,
   953  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
   954  		},
   955  	})
   956  }
   957  
   958  func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredGadgetCloudConfCloudInit(c *C) {
   959  	// pretend we have a cloud.conf from the gadget
   960  	gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/")
   961  	err := os.MkdirAll(gadgetDir, 0755)
   962  	c.Assert(err, IsNil)
   963  	err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644)
   964  	c.Assert(err, IsNil)
   965  
   966  	err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
   967  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   968  	})
   969  	c.Assert(err, IsNil)
   970  
   971  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   972  		{
   973  			AllowCloudInit: true,
   974  			TargetRootDir:  boot.InstallHostWritableDir,
   975  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
   976  		},
   977  	})
   978  }
   979  
   980  func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredNoUbuntuSeedCloudInit(c *C) {
   981  	// pretend we have a cloud-init config on the seed partition
   982  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
   983  	err := os.MkdirAll(cloudCfg, 0755)
   984  	c.Assert(err, IsNil)
   985  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
   986  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
   987  		c.Assert(err, IsNil)
   988  	}
   989  
   990  	err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
   991  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
   992  	})
   993  	c.Assert(err, IsNil)
   994  
   995  	// and did NOT tell sysconfig about the cloud-init files, instead it was
   996  	// disabled because only gadget cloud-init is allowed
   997  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
   998  		{
   999  			AllowCloudInit: false,
  1000  			TargetRootDir:  boot.InstallHostWritableDir,
  1001  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1002  		},
  1003  	})
  1004  }
  1005  
  1006  func (s *deviceMgrInstallModeSuite) TestInstallModeWritesModel(c *C) {
  1007  	// pretend we have a cloud-init config on the seed partition
  1008  	model := s.mockInstallModeChange(c, "dangerous", "")
  1009  
  1010  	var buf bytes.Buffer
  1011  	err := asserts.NewEncoder(&buf).Encode(model)
  1012  	c.Assert(err, IsNil)
  1013  
  1014  	s.state.Lock()
  1015  	defer s.state.Unlock()
  1016  
  1017  	installSystem := s.findInstallSystem()
  1018  	c.Assert(installSystem, NotNil)
  1019  
  1020  	// and was run successfully
  1021  	c.Check(installSystem.Err(), IsNil)
  1022  	c.Check(installSystem.Status(), Equals, state.DoneStatus)
  1023  
  1024  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileEquals, buf.String())
  1025  }
  1026  
  1027  func (s *deviceMgrInstallModeSuite) testInstallGadgetNoSave(c *C) {
  1028  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
  1029  		[]byte("mode=install\n"), 0644)
  1030  	c.Assert(err, IsNil)
  1031  
  1032  	s.state.Lock()
  1033  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
  1034  	info, err := snapstate.CurrentInfo(s.state, "pc")
  1035  	c.Assert(err, IsNil)
  1036  	// replace gadget yaml with one that has no ubuntu-save
  1037  	c.Assert(uc20gadgetYaml, Not(testutil.Contains), "ubuntu-save")
  1038  	err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(uc20gadgetYaml), 0644)
  1039  	c.Assert(err, IsNil)
  1040  	devicestate.SetSystemMode(s.mgr, "install")
  1041  	s.state.Unlock()
  1042  
  1043  	s.settle(c)
  1044  }
  1045  
  1046  func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetErr(c *C) {
  1047  	restore := release.MockOnClassic(false)
  1048  	defer restore()
  1049  
  1050  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
  1051  		return nil, fmt.Errorf("unexpected call")
  1052  	})
  1053  	defer restore()
  1054  
  1055  	// pretend we have a TPM
  1056  	restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { return nil })
  1057  	defer restore()
  1058  
  1059  	s.testInstallGadgetNoSave(c)
  1060  
  1061  	s.state.Lock()
  1062  	defer s.state.Unlock()
  1063  
  1064  	installSystem := s.findInstallSystem()
  1065  	c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks:
  1066  - Setup system for run mode \(cannot use gadget: gadget does not support encrypted data: volume "pc" has no structure with system-save role\)`)
  1067  	// no restart request on failure
  1068  	c.Check(s.restartRequests, HasLen, 0)
  1069  }
  1070  
  1071  func (s *deviceMgrInstallModeSuite) TestInstallWithoutEncryptionValidatesGadgetWithoutSaveHappy(c *C) {
  1072  	restore := release.MockOnClassic(false)
  1073  	defer restore()
  1074  
  1075  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
  1076  		return nil, nil
  1077  	})
  1078  	defer restore()
  1079  
  1080  	// pretend we have a TPM
  1081  	restore = devicestate.MockSecbootCheckKeySealingSupported(func() error { return fmt.Errorf("TPM2 not available") })
  1082  	defer restore()
  1083  
  1084  	s.testInstallGadgetNoSave(c)
  1085  
  1086  	s.state.Lock()
  1087  	defer s.state.Unlock()
  1088  
  1089  	installSystem := s.findInstallSystem()
  1090  	c.Check(installSystem.Err(), IsNil)
  1091  	c.Check(s.restartRequests, HasLen, 1)
  1092  }
  1093  
  1094  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncrypted(c *C) {
  1095  	st := s.state
  1096  	st.Lock()
  1097  	defer st.Unlock()
  1098  
  1099  	mockModel := s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{
  1100  		"architecture": "amd64",
  1101  		"kernel":       "pc-kernel",
  1102  		"gadget":       "pc",
  1103  	})
  1104  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1105  		Brand: "canonical",
  1106  		Model: "pc",
  1107  	})
  1108  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1109  
  1110  	for _, tc := range []struct {
  1111  		hasFDESetupHook bool
  1112  		hasTPM          bool
  1113  		encrypt         bool
  1114  	}{
  1115  		// unhappy: no tpm, no hook
  1116  		{false, false, false},
  1117  		// happy: either tpm or hook or both
  1118  		{false, true, true},
  1119  		{true, false, true},
  1120  		{true, true, true},
  1121  	} {
  1122  		hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1123  			ctx.Lock()
  1124  			defer ctx.Unlock()
  1125  			ctx.Set("fde-setup-result", []byte(`{"features":[]}`))
  1126  			return nil, nil
  1127  		}
  1128  		rhk := hookstate.MockRunHook(hookInvoke)
  1129  		defer rhk()
  1130  
  1131  		if tc.hasFDESetupHook {
  1132  			makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup)
  1133  		} else {
  1134  			makeInstalledMockKernelSnap(c, st, kernelYamlNoFdeSetup)
  1135  		}
  1136  		restore := devicestate.MockSecbootCheckKeySealingSupported(func() error {
  1137  			if tc.hasTPM {
  1138  				return nil
  1139  			}
  1140  			return fmt.Errorf("tpm says no")
  1141  		})
  1142  		defer restore()
  1143  
  1144  		encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, st, deviceCtx)
  1145  		c.Assert(err, IsNil)
  1146  		c.Check(encrypt, Equals, tc.encrypt, Commentf("%v", tc))
  1147  	}
  1148  }
  1149  
  1150  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedStorageSafety(c *C) {
  1151  	s.state.Lock()
  1152  	defer s.state.Unlock()
  1153  
  1154  	restore := devicestate.MockSecbootCheckKeySealingSupported(func() error { return nil })
  1155  	defer restore()
  1156  
  1157  	var testCases = []struct {
  1158  		grade, storageSafety string
  1159  
  1160  		expectedEncryption bool
  1161  	}{
  1162  		// we don't test unset here because the assertion assembly
  1163  		// will ensure it has a default
  1164  		{"dangerous", "prefer-unencrypted", false},
  1165  		{"dangerous", "prefer-encrypted", true},
  1166  		{"dangerous", "encrypted", true},
  1167  		{"signed", "prefer-unencrypted", false},
  1168  		{"signed", "prefer-encrypted", true},
  1169  		{"signed", "encrypted", true},
  1170  		// secured+prefer-{,un}encrypted is an error at the
  1171  		// assertion level already so cannot be tested here
  1172  		{"secured", "encrypted", true},
  1173  	}
  1174  	for _, tc := range testCases {
  1175  		mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
  1176  			"display-name":   "my model",
  1177  			"architecture":   "amd64",
  1178  			"base":           "core20",
  1179  			"grade":          tc.grade,
  1180  			"storage-safety": tc.storageSafety,
  1181  			"snaps": []interface{}{
  1182  				map[string]interface{}{
  1183  					"name":            "pc-kernel",
  1184  					"id":              pcKernelSnapID,
  1185  					"type":            "kernel",
  1186  					"default-channel": "20",
  1187  				},
  1188  				map[string]interface{}{
  1189  					"name":            "pc",
  1190  					"id":              pcSnapID,
  1191  					"type":            "gadget",
  1192  					"default-channel": "20",
  1193  				}},
  1194  		})
  1195  		deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1196  
  1197  		encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1198  		c.Assert(err, IsNil)
  1199  		c.Check(encrypt, Equals, tc.expectedEncryption)
  1200  	}
  1201  }
  1202  
  1203  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrors(c *C) {
  1204  	s.state.Lock()
  1205  	defer s.state.Unlock()
  1206  
  1207  	restore := devicestate.MockSecbootCheckKeySealingSupported(func() error { return fmt.Errorf("tpm says no") })
  1208  	defer restore()
  1209  
  1210  	var testCases = []struct {
  1211  		grade, storageSafety string
  1212  
  1213  		expectedErr string
  1214  	}{
  1215  		// we don't test unset here because the assertion assembly
  1216  		// will ensure it has a default
  1217  		{
  1218  			"dangerous", "encrypted",
  1219  			"cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no",
  1220  		}, {
  1221  			"signed", "encrypted",
  1222  			"cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no",
  1223  		}, {
  1224  			"secured", "",
  1225  			"cannot encrypt device storage as mandated by model grade secured: tpm says no",
  1226  		}, {
  1227  			"secured", "encrypted",
  1228  			"cannot encrypt device storage as mandated by model grade secured: tpm says no",
  1229  		},
  1230  	}
  1231  	for _, tc := range testCases {
  1232  		mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
  1233  			"display-name":   "my model",
  1234  			"architecture":   "amd64",
  1235  			"base":           "core20",
  1236  			"grade":          tc.grade,
  1237  			"storage-safety": tc.storageSafety,
  1238  			"snaps": []interface{}{
  1239  				map[string]interface{}{
  1240  					"name":            "pc-kernel",
  1241  					"id":              pcKernelSnapID,
  1242  					"type":            "kernel",
  1243  					"default-channel": "20",
  1244  				},
  1245  				map[string]interface{}{
  1246  					"name":            "pc",
  1247  					"id":              pcSnapID,
  1248  					"type":            "gadget",
  1249  					"default-channel": "20",
  1250  				}},
  1251  		})
  1252  		deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1253  		_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1254  		c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%s %s", tc.grade, tc.storageSafety))
  1255  	}
  1256  }
  1257  
  1258  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedFDEHook(c *C) {
  1259  	st := s.state
  1260  	st.Lock()
  1261  	defer st.Unlock()
  1262  
  1263  	s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{
  1264  		"architecture": "amd64",
  1265  		"kernel":       "pc-kernel",
  1266  		"gadget":       "pc",
  1267  	})
  1268  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1269  		Brand: "canonical",
  1270  		Model: "pc",
  1271  	})
  1272  	makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup)
  1273  
  1274  	for _, tc := range []struct {
  1275  		hookOutput  string
  1276  		expectedErr string
  1277  	}{
  1278  		// invalid json
  1279  		{"xxx", `cannot parse hook output "xxx": invalid character 'x' looking for beginning of value`},
  1280  		// no output is invalid
  1281  		{"", `cannot parse hook output "": unexpected end of JSON input`},
  1282  		// specific error
  1283  		{`{"error":"failed"}`, `cannot use hook: it returned error: failed`},
  1284  		{`{}`, `cannot use hook: neither "features" nor "error" returned`},
  1285  		// valid
  1286  		{`{"features":[]}`, ""},
  1287  		{`{"features":["a"]}`, ""},
  1288  		{`{"features":["a","b"]}`, ""},
  1289  		// features must be list of strings
  1290  		{`{"features":[1]}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`},
  1291  		{`{"features":1}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`},
  1292  		{`{"features":"1"}`, `cannot parse hook output ".*": json: cannot unmarshal string into Go struct.*`},
  1293  	} {
  1294  		hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1295  			ctx.Lock()
  1296  			defer ctx.Unlock()
  1297  			ctx.Set("fde-setup-result", []byte(tc.hookOutput))
  1298  			return nil, nil
  1299  		}
  1300  		rhk := hookstate.MockRunHook(hookInvoke)
  1301  		defer rhk()
  1302  
  1303  		err := devicestate.DeviceManagerCheckFDEFeatures(s.mgr, st)
  1304  		if tc.expectedErr != "" {
  1305  			c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc))
  1306  		} else {
  1307  			c.Check(err, IsNil, Commentf("%v", tc))
  1308  		}
  1309  	}
  1310  }
  1311  
  1312  var checkEncryptionModelHeaders = map[string]interface{}{
  1313  	"display-name": "my model",
  1314  	"architecture": "amd64",
  1315  	"base":         "core20",
  1316  	"grade":        "dangerous",
  1317  	"snaps": []interface{}{
  1318  		map[string]interface{}{
  1319  			"name":            "pc-kernel",
  1320  			"id":              pcKernelSnapID,
  1321  			"type":            "kernel",
  1322  			"default-channel": "20",
  1323  		},
  1324  		map[string]interface{}{
  1325  			"name":            "pc",
  1326  			"id":              pcSnapID,
  1327  			"type":            "gadget",
  1328  			"default-channel": "20",
  1329  		}},
  1330  }
  1331  
  1332  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsTPM(c *C) {
  1333  	s.state.Lock()
  1334  	defer s.state.Unlock()
  1335  
  1336  	restore := devicestate.MockSecbootCheckKeySealingSupported(func() error {
  1337  		return fmt.Errorf("tpm says no")
  1338  	})
  1339  	defer restore()
  1340  
  1341  	logbuf, restore := logger.MockLogger()
  1342  	defer restore()
  1343  
  1344  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders)
  1345  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1346  	_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1347  	c.Check(err, IsNil)
  1348  	c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as checking TPM gave: tpm says no\n")
  1349  }
  1350  
  1351  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsHook(c *C) {
  1352  	s.state.Lock()
  1353  	defer s.state.Unlock()
  1354  
  1355  	logbuf, restore := logger.MockLogger()
  1356  	defer restore()
  1357  
  1358  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders)
  1359  	// mock kernel installed but no hook or handle so checkEncryption
  1360  	// will fail
  1361  	makeInstalledMockKernelSnap(c, s.state, kernelYamlWithFdeSetup)
  1362  
  1363  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1364  	_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1365  	c.Check(err, IsNil)
  1366  	c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as querying kernel fde-setup hook did not succeed:.*\n")
  1367  }