github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/overlord/devicestate/handlers_install.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
    21  
    22  import (
    23  	"compress/gzip"
    24  	"fmt"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  
    29  	"gopkg.in/tomb.v2"
    30  
    31  	"github.com/snapcore/snapd/asserts"
    32  	"github.com/snapcore/snapd/boot"
    33  	"github.com/snapcore/snapd/gadget"
    34  	"github.com/snapcore/snapd/gadget/install"
    35  	"github.com/snapcore/snapd/logger"
    36  	"github.com/snapcore/snapd/osutil"
    37  	"github.com/snapcore/snapd/overlord/snapstate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  	"github.com/snapcore/snapd/randutil"
    40  	"github.com/snapcore/snapd/secboot"
    41  	"github.com/snapcore/snapd/sysconfig"
    42  )
    43  
    44  var (
    45  	bootMakeRunnable            = boot.MakeRunnableSystem
    46  	bootEnsureNextBootToRunMode = boot.EnsureNextBootToRunMode
    47  	installRun                  = install.Run
    48  
    49  	sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem
    50  )
    51  
    52  func setSysconfigCloudOptions(opts *sysconfig.Options, gadgetDir string, model *asserts.Model) {
    53  	ubuntuSeedCloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
    54  
    55  	switch {
    56  	// if the gadget has a cloud.conf file, always use that regardless of grade
    57  	case sysconfig.HasGadgetCloudConf(gadgetDir):
    58  		// this is implicitly handled by ConfigureTargetSystem when it configures
    59  		// cloud-init if none of the other options are set, so just break here
    60  		opts.AllowCloudInit = true
    61  
    62  	// next thing is if are in secured grade and didn't have gadget config, we
    63  	// disable cloud-init always, clouds should have their own config via
    64  	// gadgets for grade secured
    65  	case model.Grade() == asserts.ModelSecured:
    66  		opts.AllowCloudInit = false
    67  
    68  	// TODO:UC20: on grade signed, allow files from ubuntu-seed, but do
    69  	//            filtering on the resultant cloud config
    70  
    71  	// next if we are grade dangerous, then we also install cloud configuration
    72  	// from ubuntu-seed if it exists
    73  	case model.Grade() == asserts.ModelDangerous && osutil.IsDirectory(ubuntuSeedCloudCfg):
    74  		opts.AllowCloudInit = true
    75  		opts.CloudInitSrcDir = ubuntuSeedCloudCfg
    76  
    77  	// note that if none of the conditions were true, it means we are on grade
    78  	// dangerous or signed, and cloud-init is still allowed to run without
    79  	// additional configuration on first-boot, so that NoCloud CIDATA can be
    80  	// provided for example
    81  	default:
    82  		opts.AllowCloudInit = true
    83  	}
    84  }
    85  
    86  func writeModel(model *asserts.Model, where string) error {
    87  	f, err := os.OpenFile(where, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer f.Close()
    92  	return asserts.NewEncoder(f).Encode(model)
    93  }
    94  
    95  func writeLogs(rootdir string) error {
    96  	// XXX: would be great to use native journal format but it's tied
    97  	//      to machine-id, we could journal -o export but there
    98  	//      is no systemd-journal-remote on core{,18,20}
    99  	//
   100  	// XXX: or only log if persistent journal is enabled?
   101  	logPath := filepath.Join(rootdir, "var/log/install-mode.log.gz")
   102  	if err := os.MkdirAll(filepath.Dir(logPath), 0755); err != nil {
   103  		return err
   104  	}
   105  
   106  	f, err := os.Create(logPath)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	defer f.Close()
   111  
   112  	gz := gzip.NewWriter(f)
   113  	defer gz.Close()
   114  
   115  	cmd := exec.Command("journalctl", "-b", "0")
   116  	cmd.Stdout = gz
   117  	if err := cmd.Run(); err != nil {
   118  		return fmt.Errorf("cannot collect journal output: %v", err)
   119  	}
   120  	if err := gz.Flush(); err != nil {
   121  		return fmt.Errorf("cannot flush compressed log output: %v", err)
   122  	}
   123  	return nil
   124  }
   125  
   126  func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error {
   127  	st := t.State()
   128  	st.Lock()
   129  	defer st.Unlock()
   130  
   131  	perfTimings := state.TimingsForTask(t)
   132  	defer perfTimings.Save(st)
   133  
   134  	// get gadget dir
   135  	deviceCtx, err := DeviceCtx(st, t, nil)
   136  	if err != nil {
   137  		return fmt.Errorf("cannot get device context: %v", err)
   138  	}
   139  	gadgetInfo, err := snapstate.GadgetInfo(st, deviceCtx)
   140  	if err != nil {
   141  		return fmt.Errorf("cannot get gadget info: %v", err)
   142  	}
   143  	gadgetDir := gadgetInfo.MountDir()
   144  
   145  	kernelInfo, err := snapstate.KernelInfo(st, deviceCtx)
   146  	if err != nil {
   147  		return fmt.Errorf("cannot get kernel info: %v", err)
   148  	}
   149  	kernelDir := kernelInfo.MountDir()
   150  
   151  	modeEnv, err := maybeReadModeenv()
   152  	if err != nil {
   153  		return err
   154  	}
   155  	if modeEnv == nil {
   156  		return fmt.Errorf("missing modeenv, cannot proceed")
   157  	}
   158  
   159  	// bootstrap
   160  	bopts := install.Options{
   161  		Mount: true,
   162  	}
   163  	useEncryption, err := m.checkEncryption(st, deviceCtx)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	bopts.Encrypt = useEncryption
   168  
   169  	model := deviceCtx.Model()
   170  
   171  	// make sure that gadget is usable for the set up we want to use it in
   172  	validationConstraints := gadget.ValidationConstraints{
   173  		EncryptedData: useEncryption,
   174  	}
   175  	ginfo, err := gadget.ReadInfoAndValidate(gadgetDir, model, &validationConstraints)
   176  	if err != nil {
   177  		return fmt.Errorf("cannot use gadget: %v", err)
   178  	}
   179  	if err := gadget.ValidateContent(ginfo, gadgetDir, kernelDir); err != nil {
   180  		return fmt.Errorf("cannot use gadget: %v", err)
   181  	}
   182  
   183  	var trustedInstallObserver *boot.TrustedAssetsInstallObserver
   184  	// get a nice nil interface by default
   185  	var installObserver gadget.ContentObserver
   186  	trustedInstallObserver, err = boot.TrustedAssetsInstallObserverForModel(model, gadgetDir, useEncryption)
   187  	if err != nil && err != boot.ErrObserverNotApplicable {
   188  		return fmt.Errorf("cannot setup asset install observer: %v", err)
   189  	}
   190  	if err == nil {
   191  		installObserver = trustedInstallObserver
   192  		if !useEncryption {
   193  			// there will be no key sealing, so past the
   194  			// installation pass no other methods need to be called
   195  			trustedInstallObserver = nil
   196  		}
   197  	}
   198  
   199  	var installedSystem *install.InstalledSystemSideData
   200  	// run the create partition code
   201  	logger.Noticef("create and deploy partitions")
   202  	func() {
   203  		st.Unlock()
   204  		defer st.Lock()
   205  		installedSystem, err = installRun(model, gadgetDir, kernelDir, "", bopts, installObserver)
   206  	}()
   207  	if err != nil {
   208  		return fmt.Errorf("cannot install system: %v", err)
   209  	}
   210  
   211  	if trustedInstallObserver != nil {
   212  		// sanity check
   213  		if installedSystem.KeysForRoles == nil || installedSystem.KeysForRoles[gadget.SystemData] == nil || installedSystem.KeysForRoles[gadget.SystemSave] == nil {
   214  			return fmt.Errorf("internal error: system encryption keys are unset")
   215  		}
   216  		dataKeySet := installedSystem.KeysForRoles[gadget.SystemData]
   217  		saveKeySet := installedSystem.KeysForRoles[gadget.SystemSave]
   218  
   219  		// make note of the encryption keys
   220  		trustedInstallObserver.ChosenEncryptionKeys(dataKeySet.Key, saveKeySet.Key)
   221  
   222  		// keep track of recovery assets
   223  		if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil {
   224  			return fmt.Errorf("cannot observe existing trusted recovery assets: err")
   225  		}
   226  		if err := saveKeys(installedSystem.KeysForRoles); err != nil {
   227  			return err
   228  		}
   229  		// write markers containing a secret to pair data and save
   230  		if err := writeMarkers(); err != nil {
   231  			return err
   232  		}
   233  	}
   234  
   235  	// keep track of the model we installed
   236  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755)
   237  	if err != nil {
   238  		return fmt.Errorf("cannot store the model: %v", err)
   239  	}
   240  	err = writeModel(model, filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"))
   241  	if err != nil {
   242  		return fmt.Errorf("cannot store the model: %v", err)
   243  	}
   244  
   245  	// configure the run system
   246  	opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir}
   247  	// configure cloud init
   248  	setSysconfigCloudOptions(opts, gadgetDir, model)
   249  	if err := sysconfigConfigureTargetSystem(opts); err != nil {
   250  		return err
   251  	}
   252  
   253  	// make it bootable
   254  	logger.Noticef("make system runnable")
   255  	bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx)
   256  	if err != nil {
   257  		return fmt.Errorf("cannot get boot base info: %v", err)
   258  	}
   259  	recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem)
   260  	bootWith := &boot.BootableSet{
   261  		Base:              bootBaseInfo,
   262  		BasePath:          bootBaseInfo.MountFile(),
   263  		Kernel:            kernelInfo,
   264  		KernelPath:        kernelInfo.MountFile(),
   265  		RecoverySystemDir: recoverySystemDir,
   266  		UnpackedGadgetDir: gadgetDir,
   267  	}
   268  	if err := bootMakeRunnable(deviceCtx.Model(), bootWith, trustedInstallObserver); err != nil {
   269  		return fmt.Errorf("cannot make system runnable: %v", err)
   270  	}
   271  
   272  	return nil
   273  }
   274  
   275  // writeMarkers writes markers containing the same secret to pair data and save.
   276  func writeMarkers() error {
   277  	// ensure directory for markers exists
   278  	if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil {
   279  		return err
   280  	}
   281  	if err := os.MkdirAll(boot.InstallHostFDESaveDir, 0755); err != nil {
   282  		return err
   283  	}
   284  
   285  	// generate a secret random marker
   286  	markerSecret, err := randutil.CryptoTokenBytes(32)
   287  	if err != nil {
   288  		return fmt.Errorf("cannot create ubuntu-data/save marker secret: %v", err)
   289  	}
   290  
   291  	dataMarker := filepath.Join(boot.InstallHostFDEDataDir, "marker")
   292  	if err := osutil.AtomicWriteFile(dataMarker, markerSecret, 0600, 0); err != nil {
   293  		return err
   294  	}
   295  
   296  	saveMarker := filepath.Join(boot.InstallHostFDESaveDir, "marker")
   297  	if err := osutil.AtomicWriteFile(saveMarker, markerSecret, 0600, 0); err != nil {
   298  		return err
   299  	}
   300  
   301  	return nil
   302  }
   303  
   304  func saveKeys(keysForRoles map[string]*install.EncryptionKeySet) error {
   305  	dataKeySet := keysForRoles[gadget.SystemData]
   306  
   307  	// ensure directory for keys exists
   308  	if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil {
   309  		return err
   310  	}
   311  
   312  	// Write the recovery key
   313  	recoveryKeyFile := filepath.Join(boot.InstallHostFDEDataDir, "recovery.key")
   314  	if err := dataKeySet.RecoveryKey.Save(recoveryKeyFile); err != nil {
   315  		return fmt.Errorf("cannot store recovery key: %v", err)
   316  	}
   317  
   318  	saveKeySet := keysForRoles[gadget.SystemSave]
   319  	if saveKeySet == nil {
   320  		// no system-save support
   321  		return nil
   322  	}
   323  
   324  	saveKey := filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key")
   325  	reinstallSaveKey := filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key")
   326  
   327  	if err := saveKeySet.Key.Save(saveKey); err != nil {
   328  		return fmt.Errorf("cannot store system save key: %v", err)
   329  	}
   330  	if err := saveKeySet.RecoveryKey.Save(reinstallSaveKey); err != nil {
   331  		return fmt.Errorf("cannot store reinstall key: %v", err)
   332  	}
   333  	return nil
   334  }
   335  
   336  var secbootCheckTPMKeySealingSupported = secboot.CheckTPMKeySealingSupported
   337  
   338  // checkEncryption verifies whether encryption should be used based on the
   339  // model grade and the availability of a TPM device or a fde-setup hook
   340  // in the kernel.
   341  func (m *DeviceManager) checkEncryption(st *state.State, deviceCtx snapstate.DeviceContext) (res bool, err error) {
   342  	model := deviceCtx.Model()
   343  	secured := model.Grade() == asserts.ModelSecured
   344  	dangerous := model.Grade() == asserts.ModelDangerous
   345  	encrypted := model.StorageSafety() == asserts.StorageSafetyEncrypted
   346  
   347  	// check if we should disable encryption non-secured devices
   348  	// TODO:UC20: this is not the final mechanism to bypass encryption
   349  	if dangerous && osutil.FileExists(filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")) {
   350  		return false, nil
   351  	}
   352  
   353  	// check if the model prefers to be unencrypted
   354  	// TODO: provide way to select via install chooser menu
   355  	//       if the install is unencrypted or encrypted
   356  	if model.StorageSafety() == asserts.StorageSafetyPreferUnencrypted {
   357  		logger.Noticef(`installing system unencrypted to comply with prefer-unencrypted storage-safety model option`)
   358  		return false, nil
   359  	}
   360  
   361  	// check if encryption is available
   362  	var (
   363  		hasFDESetupHook    bool
   364  		checkEncryptionErr error
   365  	)
   366  	if kernelInfo, err := snapstate.KernelInfo(st, deviceCtx); err == nil {
   367  		if hasFDESetupHook = hasFDESetupHookInKernel(kernelInfo); hasFDESetupHook {
   368  			checkEncryptionErr = m.checkFDEFeatures(st)
   369  		}
   370  	}
   371  	// Note that having a fde-setup hook will disable the build-in
   372  	// secboot encryption
   373  	if !hasFDESetupHook {
   374  		checkEncryptionErr = secbootCheckTPMKeySealingSupported()
   375  	}
   376  
   377  	// check if encryption is required
   378  	if checkEncryptionErr != nil {
   379  		if secured {
   380  			return false, fmt.Errorf("cannot encrypt device storage as mandated by model grade secured: %v", checkEncryptionErr)
   381  		}
   382  		if encrypted {
   383  			return false, fmt.Errorf("cannot encrypt device storage as mandated by encrypted storage-safety model option: %v", checkEncryptionErr)
   384  		}
   385  
   386  		if hasFDESetupHook {
   387  			logger.Noticef("not encrypting device storage as querying kernel fde-setup hook did not succeed: %v", checkEncryptionErr)
   388  		} else {
   389  			logger.Noticef("not encrypting device storage as checking TPM gave: %v", checkEncryptionErr)
   390  		}
   391  
   392  		// not required, go without
   393  		return false, nil
   394  	}
   395  
   396  	// encrypt
   397  	return true, nil
   398  }
   399  
   400  // RebootOptions can be attached to restart-system-to-run-mode tasks to control
   401  // their restart behavior.
   402  type RebootOptions struct {
   403  	Op string `json:"op,omitempty"`
   404  }
   405  
   406  const (
   407  	RebootHaltOp     = "halt"
   408  	RebootPoweroffOp = "poweroff"
   409  )
   410  
   411  func (m *DeviceManager) doRestartSystemToRunMode(t *state.Task, _ *tomb.Tomb) error {
   412  	st := t.State()
   413  	st.Lock()
   414  	defer st.Unlock()
   415  
   416  	perfTimings := state.TimingsForTask(t)
   417  	defer perfTimings.Save(st)
   418  
   419  	modeEnv, err := maybeReadModeenv()
   420  	if err != nil {
   421  		return err
   422  	}
   423  
   424  	if modeEnv == nil {
   425  		return fmt.Errorf("missing modeenv, cannot proceed")
   426  	}
   427  
   428  	// store install-mode log into ubuntu-data partition
   429  	if err := writeLogs(boot.InstallHostWritableDir); err != nil {
   430  		logger.Noticef("cannot write installation log: %v", err)
   431  	}
   432  
   433  	// ensure the next boot goes into run mode
   434  	if err := bootEnsureNextBootToRunMode(modeEnv.RecoverySystem); err != nil {
   435  		return err
   436  	}
   437  
   438  	var rebootOpts RebootOptions
   439  	err = t.Get("reboot", &rebootOpts)
   440  	if err != nil && err != state.ErrNoState {
   441  		return err
   442  	}
   443  
   444  	// request by default a restart as the last action after a
   445  	// successful install or what install-device requested via
   446  	// snapctl reboot
   447  	rst := state.RestartSystemNow
   448  	what := "restart"
   449  	switch rebootOpts.Op {
   450  	case RebootHaltOp:
   451  		what = "halt"
   452  		rst = state.RestartSystemHaltNow
   453  	case RebootPoweroffOp:
   454  		what = "poweroff"
   455  		rst = state.RestartSystemPoweroffNow
   456  	}
   457  	logger.Noticef("request immediate system %s", what)
   458  	st.RequestRestart(rst)
   459  
   460  	return nil
   461  }