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