github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/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) TestInstallModeSupportsCloudInitGadgetOnlyInSigned(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  	// we also have gadget cloud init too
  1000  	gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/")
  1001  	err = os.MkdirAll(gadgetDir, 0755)
  1002  	c.Assert(err, IsNil)
  1003  	err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644)
  1004  	c.Assert(err, IsNil)
  1005  
  1006  	s.mockInstallModeChange(c, "signed", "")
  1007  
  1008  	// we didn't tell sysconfig about the ubuntu-seed cloud-init files, just
  1009  	// the gadget ones
  1010  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1011  		{
  1012  			AllowCloudInit: true,
  1013  			TargetRootDir:  boot.InstallHostWritableDir,
  1014  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1015  		},
  1016  	})
  1017  }
  1018  
  1019  func (s *deviceMgrInstallModeSuite) TestInstallModeSupportsCloudInitBothGadgetAndUbuntuSeedInDangerous(c *C) {
  1020  	// pretend we have a cloud-init config on the seed partition
  1021  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
  1022  	err := os.MkdirAll(cloudCfg, 0755)
  1023  	c.Assert(err, IsNil)
  1024  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
  1025  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
  1026  		c.Assert(err, IsNil)
  1027  	}
  1028  
  1029  	// we also have gadget cloud init too
  1030  	gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/")
  1031  	err = os.MkdirAll(gadgetDir, 0755)
  1032  	c.Assert(err, IsNil)
  1033  	err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644)
  1034  	c.Assert(err, IsNil)
  1035  
  1036  	s.mockInstallModeChange(c, "dangerous", "")
  1037  
  1038  	// and did tell sysconfig about the cloud-init files
  1039  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1040  		{
  1041  			AllowCloudInit:  true,
  1042  			CloudInitSrcDir: filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d"),
  1043  			TargetRootDir:   boot.InstallHostWritableDir,
  1044  			GadgetDir:       filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1045  		},
  1046  	})
  1047  }
  1048  
  1049  func (s *deviceMgrInstallModeSuite) TestInstallModeSignedNoUbuntuSeedCloudInit(c *C) {
  1050  	// pretend we have a cloud-init config on the seed partition
  1051  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
  1052  	err := os.MkdirAll(cloudCfg, 0755)
  1053  	c.Assert(err, IsNil)
  1054  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
  1055  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
  1056  		c.Assert(err, IsNil)
  1057  	}
  1058  
  1059  	s.mockInstallModeChange(c, "signed", "")
  1060  
  1061  	// and did NOT tell sysconfig about the cloud-init file, but also did not
  1062  	// explicitly disable cloud init
  1063  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1064  		{
  1065  			AllowCloudInit: true,
  1066  			TargetRootDir:  boot.InstallHostWritableDir,
  1067  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1068  		},
  1069  	})
  1070  }
  1071  
  1072  func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredGadgetCloudConfCloudInit(c *C) {
  1073  	// pretend we have a cloud.conf from the gadget
  1074  	gadgetDir := filepath.Join(dirs.SnapMountDir, "pc/1/")
  1075  	err := os.MkdirAll(gadgetDir, 0755)
  1076  	c.Assert(err, IsNil)
  1077  	err = ioutil.WriteFile(filepath.Join(gadgetDir, "cloud.conf"), nil, 0644)
  1078  	c.Assert(err, IsNil)
  1079  
  1080  	err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
  1081  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
  1082  	})
  1083  	c.Assert(err, IsNil)
  1084  
  1085  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1086  		{
  1087  			AllowCloudInit: true,
  1088  			TargetRootDir:  boot.InstallHostWritableDir,
  1089  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1090  		},
  1091  	})
  1092  }
  1093  
  1094  func (s *deviceMgrInstallModeSuite) TestInstallModeSecuredNoUbuntuSeedCloudInit(c *C) {
  1095  	// pretend we have a cloud-init config on the seed partition
  1096  	cloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
  1097  	err := os.MkdirAll(cloudCfg, 0755)
  1098  	c.Assert(err, IsNil)
  1099  	for _, mockCfg := range []string{"foo.cfg", "bar.cfg"} {
  1100  		err = ioutil.WriteFile(filepath.Join(cloudCfg, mockCfg), []byte(fmt.Sprintf("%s config", mockCfg)), 0644)
  1101  		c.Assert(err, IsNil)
  1102  	}
  1103  
  1104  	err = s.doRunChangeTestWithEncryption(c, "secured", encTestCase{
  1105  		tpm: true, bypass: false, encrypt: true, trustedBootloader: true,
  1106  	})
  1107  	c.Assert(err, IsNil)
  1108  
  1109  	// and did NOT tell sysconfig about the cloud-init files, instead it was
  1110  	// disabled because only gadget cloud-init is allowed
  1111  	c.Assert(s.ConfigureTargetSystemOptsPassed, DeepEquals, []*sysconfig.Options{
  1112  		{
  1113  			AllowCloudInit: false,
  1114  			TargetRootDir:  boot.InstallHostWritableDir,
  1115  			GadgetDir:      filepath.Join(dirs.SnapMountDir, "pc/1/"),
  1116  		},
  1117  	})
  1118  }
  1119  
  1120  func (s *deviceMgrInstallModeSuite) TestInstallModeWritesModel(c *C) {
  1121  	// pretend we have a cloud-init config on the seed partition
  1122  	model := s.mockInstallModeChange(c, "dangerous", "")
  1123  
  1124  	var buf bytes.Buffer
  1125  	err := asserts.NewEncoder(&buf).Encode(model)
  1126  	c.Assert(err, IsNil)
  1127  
  1128  	s.state.Lock()
  1129  	defer s.state.Unlock()
  1130  
  1131  	installSystem := s.findInstallSystem()
  1132  	c.Assert(installSystem, NotNil)
  1133  
  1134  	// and was run successfully
  1135  	c.Check(installSystem.Err(), IsNil)
  1136  	c.Check(installSystem.Status(), Equals, state.DoneStatus)
  1137  
  1138  	c.Check(filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"), testutil.FileEquals, buf.String())
  1139  }
  1140  
  1141  func (s *deviceMgrInstallModeSuite) testInstallGadgetNoSave(c *C) {
  1142  	err := ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/var/lib/snapd/modeenv"),
  1143  		[]byte("mode=install\n"), 0644)
  1144  	c.Assert(err, IsNil)
  1145  
  1146  	s.state.Lock()
  1147  	s.makeMockInstalledPcGadget(c, "dangerous", "", "")
  1148  	info, err := snapstate.CurrentInfo(s.state, "pc")
  1149  	c.Assert(err, IsNil)
  1150  	// replace gadget yaml with one that has no ubuntu-save
  1151  	c.Assert(uc20gadgetYaml, Not(testutil.Contains), "ubuntu-save")
  1152  	err = ioutil.WriteFile(filepath.Join(info.MountDir(), "meta/gadget.yaml"), []byte(uc20gadgetYaml), 0644)
  1153  	c.Assert(err, IsNil)
  1154  	devicestate.SetSystemMode(s.mgr, "install")
  1155  	s.state.Unlock()
  1156  
  1157  	s.settle(c)
  1158  }
  1159  
  1160  func (s *deviceMgrInstallModeSuite) TestInstallWithEncryptionValidatesGadgetErr(c *C) {
  1161  	restore := release.MockOnClassic(false)
  1162  	defer restore()
  1163  
  1164  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
  1165  		return nil, fmt.Errorf("unexpected call")
  1166  	})
  1167  	defer restore()
  1168  
  1169  	// pretend we have a TPM
  1170  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil })
  1171  	defer restore()
  1172  
  1173  	s.testInstallGadgetNoSave(c)
  1174  
  1175  	s.state.Lock()
  1176  	defer s.state.Unlock()
  1177  
  1178  	installSystem := s.findInstallSystem()
  1179  	c.Check(installSystem.Err(), ErrorMatches, `(?ms)cannot perform the following tasks:
  1180  - Setup system for run mode \(cannot use gadget: gadget does not support encrypted data: volume "pc" has no structure with system-save role\)`)
  1181  	// no restart request on failure
  1182  	c.Check(s.restartRequests, HasLen, 0)
  1183  }
  1184  
  1185  func (s *deviceMgrInstallModeSuite) TestInstallWithoutEncryptionValidatesGadgetWithoutSaveHappy(c *C) {
  1186  	restore := release.MockOnClassic(false)
  1187  	defer restore()
  1188  
  1189  	restore = devicestate.MockInstallRun(func(mod gadget.Model, gadgetRoot, kernelRoot, device string, options install.Options, _ gadget.ContentObserver) (*install.InstalledSystemSideData, error) {
  1190  		return nil, nil
  1191  	})
  1192  	defer restore()
  1193  
  1194  	// pretend we have a TPM
  1195  	restore = devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("TPM2 not available") })
  1196  	defer restore()
  1197  
  1198  	s.testInstallGadgetNoSave(c)
  1199  
  1200  	s.state.Lock()
  1201  	defer s.state.Unlock()
  1202  
  1203  	installSystem := s.findInstallSystem()
  1204  	c.Check(installSystem.Err(), IsNil)
  1205  	c.Check(s.restartRequests, HasLen, 1)
  1206  }
  1207  
  1208  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncrypted(c *C) {
  1209  	st := s.state
  1210  	st.Lock()
  1211  	defer st.Unlock()
  1212  
  1213  	mockModel := s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{
  1214  		"architecture": "amd64",
  1215  		"kernel":       "pc-kernel",
  1216  		"gadget":       "pc",
  1217  	})
  1218  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1219  		Brand: "canonical",
  1220  		Model: "pc",
  1221  	})
  1222  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1223  
  1224  	for _, tc := range []struct {
  1225  		hasFDESetupHook bool
  1226  		hasTPM          bool
  1227  		encrypt         bool
  1228  	}{
  1229  		// unhappy: no tpm, no hook
  1230  		{false, false, false},
  1231  		// happy: either tpm or hook or both
  1232  		{false, true, true},
  1233  		{true, false, true},
  1234  		{true, true, true},
  1235  	} {
  1236  		hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1237  			ctx.Lock()
  1238  			defer ctx.Unlock()
  1239  			ctx.Set("fde-setup-result", []byte(`{"features":[]}`))
  1240  			return nil, nil
  1241  		}
  1242  		rhk := hookstate.MockRunHook(hookInvoke)
  1243  		defer rhk()
  1244  
  1245  		if tc.hasFDESetupHook {
  1246  			makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup)
  1247  		} else {
  1248  			makeInstalledMockKernelSnap(c, st, kernelYamlNoFdeSetup)
  1249  		}
  1250  		restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error {
  1251  			if tc.hasTPM {
  1252  				return nil
  1253  			}
  1254  			return fmt.Errorf("tpm says no")
  1255  		})
  1256  		defer restore()
  1257  
  1258  		encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, st, deviceCtx)
  1259  		c.Assert(err, IsNil)
  1260  		c.Check(encrypt, Equals, tc.encrypt, Commentf("%v", tc))
  1261  	}
  1262  }
  1263  
  1264  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedStorageSafety(c *C) {
  1265  	s.state.Lock()
  1266  	defer s.state.Unlock()
  1267  
  1268  	restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return nil })
  1269  	defer restore()
  1270  
  1271  	var testCases = []struct {
  1272  		grade, storageSafety string
  1273  
  1274  		expectedEncryption bool
  1275  	}{
  1276  		// we don't test unset here because the assertion assembly
  1277  		// will ensure it has a default
  1278  		{"dangerous", "prefer-unencrypted", false},
  1279  		{"dangerous", "prefer-encrypted", true},
  1280  		{"dangerous", "encrypted", true},
  1281  		{"signed", "prefer-unencrypted", false},
  1282  		{"signed", "prefer-encrypted", true},
  1283  		{"signed", "encrypted", true},
  1284  		// secured+prefer-{,un}encrypted is an error at the
  1285  		// assertion level already so cannot be tested here
  1286  		{"secured", "encrypted", true},
  1287  	}
  1288  	for _, tc := range testCases {
  1289  		mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
  1290  			"display-name":   "my model",
  1291  			"architecture":   "amd64",
  1292  			"base":           "core20",
  1293  			"grade":          tc.grade,
  1294  			"storage-safety": tc.storageSafety,
  1295  			"snaps": []interface{}{
  1296  				map[string]interface{}{
  1297  					"name":            "pc-kernel",
  1298  					"id":              pcKernelSnapID,
  1299  					"type":            "kernel",
  1300  					"default-channel": "20",
  1301  				},
  1302  				map[string]interface{}{
  1303  					"name":            "pc",
  1304  					"id":              pcSnapID,
  1305  					"type":            "gadget",
  1306  					"default-channel": "20",
  1307  				}},
  1308  		})
  1309  		deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1310  
  1311  		encrypt, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1312  		c.Assert(err, IsNil)
  1313  		c.Check(encrypt, Equals, tc.expectedEncryption)
  1314  	}
  1315  }
  1316  
  1317  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrors(c *C) {
  1318  	s.state.Lock()
  1319  	defer s.state.Unlock()
  1320  
  1321  	restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error { return fmt.Errorf("tpm says no") })
  1322  	defer restore()
  1323  
  1324  	var testCases = []struct {
  1325  		grade, storageSafety string
  1326  
  1327  		expectedErr string
  1328  	}{
  1329  		// we don't test unset here because the assertion assembly
  1330  		// will ensure it has a default
  1331  		{
  1332  			"dangerous", "encrypted",
  1333  			"cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no",
  1334  		}, {
  1335  			"signed", "encrypted",
  1336  			"cannot encrypt device storage as mandated by encrypted storage-safety model option: tpm says no",
  1337  		}, {
  1338  			"secured", "",
  1339  			"cannot encrypt device storage as mandated by model grade secured: tpm says no",
  1340  		}, {
  1341  			"secured", "encrypted",
  1342  			"cannot encrypt device storage as mandated by model grade secured: tpm says no",
  1343  		},
  1344  	}
  1345  	for _, tc := range testCases {
  1346  		mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", map[string]interface{}{
  1347  			"display-name":   "my model",
  1348  			"architecture":   "amd64",
  1349  			"base":           "core20",
  1350  			"grade":          tc.grade,
  1351  			"storage-safety": tc.storageSafety,
  1352  			"snaps": []interface{}{
  1353  				map[string]interface{}{
  1354  					"name":            "pc-kernel",
  1355  					"id":              pcKernelSnapID,
  1356  					"type":            "kernel",
  1357  					"default-channel": "20",
  1358  				},
  1359  				map[string]interface{}{
  1360  					"name":            "pc",
  1361  					"id":              pcSnapID,
  1362  					"type":            "gadget",
  1363  					"default-channel": "20",
  1364  				}},
  1365  		})
  1366  		deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1367  		_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1368  		c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%s %s", tc.grade, tc.storageSafety))
  1369  	}
  1370  }
  1371  
  1372  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedFDEHook(c *C) {
  1373  	st := s.state
  1374  	st.Lock()
  1375  	defer st.Unlock()
  1376  
  1377  	s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{
  1378  		"architecture": "amd64",
  1379  		"kernel":       "pc-kernel",
  1380  		"gadget":       "pc",
  1381  	})
  1382  	devicestatetest.SetDevice(s.state, &auth.DeviceState{
  1383  		Brand: "canonical",
  1384  		Model: "pc",
  1385  	})
  1386  	makeInstalledMockKernelSnap(c, st, kernelYamlWithFdeSetup)
  1387  
  1388  	for _, tc := range []struct {
  1389  		hookOutput  string
  1390  		expectedErr string
  1391  	}{
  1392  		// invalid json
  1393  		{"xxx", `cannot parse hook output "xxx": invalid character 'x' looking for beginning of value`},
  1394  		// no output is invalid
  1395  		{"", `cannot parse hook output "": unexpected end of JSON input`},
  1396  		// specific error
  1397  		{`{"error":"failed"}`, `cannot use hook: it returned error: failed`},
  1398  		{`{}`, `cannot use hook: neither "features" nor "error" returned`},
  1399  		// valid
  1400  		{`{"features":[]}`, ""},
  1401  		{`{"features":["a"]}`, ""},
  1402  		{`{"features":["a","b"]}`, ""},
  1403  		// features must be list of strings
  1404  		{`{"features":[1]}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`},
  1405  		{`{"features":1}`, `cannot parse hook output ".*": json: cannot unmarshal number into Go struct.*`},
  1406  		{`{"features":"1"}`, `cannot parse hook output ".*": json: cannot unmarshal string into Go struct.*`},
  1407  	} {
  1408  		hookInvoke := func(ctx *hookstate.Context, tomb *tomb.Tomb) ([]byte, error) {
  1409  			ctx.Lock()
  1410  			defer ctx.Unlock()
  1411  			ctx.Set("fde-setup-result", []byte(tc.hookOutput))
  1412  			return nil, nil
  1413  		}
  1414  		rhk := hookstate.MockRunHook(hookInvoke)
  1415  		defer rhk()
  1416  
  1417  		err := devicestate.DeviceManagerCheckFDEFeatures(s.mgr, st)
  1418  		if tc.expectedErr != "" {
  1419  			c.Check(err, ErrorMatches, tc.expectedErr, Commentf("%v", tc))
  1420  		} else {
  1421  			c.Check(err, IsNil, Commentf("%v", tc))
  1422  		}
  1423  	}
  1424  }
  1425  
  1426  var checkEncryptionModelHeaders = map[string]interface{}{
  1427  	"display-name": "my model",
  1428  	"architecture": "amd64",
  1429  	"base":         "core20",
  1430  	"grade":        "dangerous",
  1431  	"snaps": []interface{}{
  1432  		map[string]interface{}{
  1433  			"name":            "pc-kernel",
  1434  			"id":              pcKernelSnapID,
  1435  			"type":            "kernel",
  1436  			"default-channel": "20",
  1437  		},
  1438  		map[string]interface{}{
  1439  			"name":            "pc",
  1440  			"id":              pcSnapID,
  1441  			"type":            "gadget",
  1442  			"default-channel": "20",
  1443  		}},
  1444  }
  1445  
  1446  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsTPM(c *C) {
  1447  	s.state.Lock()
  1448  	defer s.state.Unlock()
  1449  
  1450  	restore := devicestate.MockSecbootCheckTPMKeySealingSupported(func() error {
  1451  		return fmt.Errorf("tpm says no")
  1452  	})
  1453  	defer restore()
  1454  
  1455  	logbuf, restore := logger.MockLogger()
  1456  	defer restore()
  1457  
  1458  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders)
  1459  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1460  	_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1461  	c.Check(err, IsNil)
  1462  	c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as checking TPM gave: tpm says no\n")
  1463  }
  1464  
  1465  func (s *deviceMgrInstallModeSuite) TestInstallCheckEncryptedErrorsLogsHook(c *C) {
  1466  	s.state.Lock()
  1467  	defer s.state.Unlock()
  1468  
  1469  	logbuf, restore := logger.MockLogger()
  1470  	defer restore()
  1471  
  1472  	mockModel := s.makeModelAssertionInState(c, "my-brand", "my-model", checkEncryptionModelHeaders)
  1473  	// mock kernel installed but no hook or handle so checkEncryption
  1474  	// will fail
  1475  	makeInstalledMockKernelSnap(c, s.state, kernelYamlWithFdeSetup)
  1476  
  1477  	deviceCtx := &snapstatetest.TrivialDeviceContext{DeviceModel: mockModel}
  1478  	_, err := devicestate.DeviceManagerCheckEncryption(s.mgr, s.state, deviceCtx)
  1479  	c.Check(err, IsNil)
  1480  	c.Check(logbuf.String(), Matches, "(?s).*: not encrypting device storage as querying kernel fde-setup hook did not succeed:.*\n")
  1481  }