github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/overlord/devicestate/devicestate_install_mode_test.go (about)

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