github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/overlord/devicestate/handlers_install.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package devicestate
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"path/filepath"
    26  
    27  	"gopkg.in/tomb.v2"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/boot"
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/gadget"
    33  	"github.com/snapcore/snapd/gadget/install"
    34  	"github.com/snapcore/snapd/logger"
    35  	"github.com/snapcore/snapd/osutil"
    36  	"github.com/snapcore/snapd/overlord/snapstate"
    37  	"github.com/snapcore/snapd/overlord/state"
    38  	"github.com/snapcore/snapd/randutil"
    39  	"github.com/snapcore/snapd/secboot"
    40  	"github.com/snapcore/snapd/sysconfig"
    41  )
    42  
    43  var (
    44  	bootMakeBootable = boot.MakeBootable
    45  	installRun       = install.Run
    46  
    47  	sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem
    48  )
    49  
    50  func setSysconfigCloudOptions(opts *sysconfig.Options, gadgetDir string, model *asserts.Model) {
    51  	ubuntuSeedCloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
    52  
    53  	switch {
    54  	// if the gadget has a cloud.conf file, always use that regardless of grade
    55  	case sysconfig.HasGadgetCloudConf(gadgetDir):
    56  		// this is implicitly handled by ConfigureTargetSystem when it configures
    57  		// cloud-init if none of the other options are set, so just break here
    58  		opts.AllowCloudInit = true
    59  
    60  	// next thing is if are in secured grade and didn't have gadget config, we
    61  	// disable cloud-init always, clouds should have their own config via
    62  	// gadgets for grade secured
    63  	case model.Grade() == asserts.ModelSecured:
    64  		opts.AllowCloudInit = false
    65  
    66  	// TODO:UC20: on grade signed, allow files from ubuntu-seed, but do
    67  	//            filtering on the resultant cloud config
    68  
    69  	// next if we are grade dangerous, then we also install cloud configuration
    70  	// from ubuntu-seed if it exists
    71  	case model.Grade() == asserts.ModelDangerous && osutil.IsDirectory(ubuntuSeedCloudCfg):
    72  		opts.AllowCloudInit = true
    73  		opts.CloudInitSrcDir = ubuntuSeedCloudCfg
    74  
    75  	// note that if none of the conditions were true, it means we are on grade
    76  	// dangerous or signed, and cloud-init is still allowed to run without
    77  	// additional configuration on first-boot, so that NoCloud CIDATA can be
    78  	// provided for example
    79  	default:
    80  		opts.AllowCloudInit = true
    81  	}
    82  }
    83  
    84  func writeModel(model *asserts.Model, where string) error {
    85  	f, err := os.OpenFile(where, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
    86  	if err != nil {
    87  		return err
    88  	}
    89  	defer f.Close()
    90  	return asserts.NewEncoder(f).Encode(model)
    91  }
    92  
    93  func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error {
    94  	st := t.State()
    95  	st.Lock()
    96  	defer st.Unlock()
    97  
    98  	perfTimings := state.TimingsForTask(t)
    99  	defer perfTimings.Save(st)
   100  
   101  	// get gadget dir
   102  	deviceCtx, err := DeviceCtx(st, t, nil)
   103  	if err != nil {
   104  		return fmt.Errorf("cannot get device context: %v", err)
   105  	}
   106  	gadgetInfo, err := snapstate.GadgetInfo(st, deviceCtx)
   107  	if err != nil {
   108  		return fmt.Errorf("cannot get gadget info: %v", err)
   109  	}
   110  	gadgetDir := gadgetInfo.MountDir()
   111  
   112  	kernelInfo, err := snapstate.KernelInfo(st, deviceCtx)
   113  	if err != nil {
   114  		return fmt.Errorf("cannot get kernel info: %v", err)
   115  	}
   116  
   117  	modeEnv, err := maybeReadModeenv()
   118  	if err != nil {
   119  		return err
   120  	}
   121  	if modeEnv == nil {
   122  		return fmt.Errorf("missing modeenv, cannot proceed")
   123  	}
   124  
   125  	// bootstrap
   126  	bopts := install.Options{
   127  		Mount: true,
   128  	}
   129  	useEncryption, err := m.checkEncryption(st, deviceCtx)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	bopts.Encrypt = useEncryption
   134  
   135  	model := deviceCtx.Model()
   136  
   137  	// make sure that gadget is usable for the set up we want to use it in
   138  	validationConstraints := gadget.ValidationConstraints{
   139  		EncryptedData: useEncryption,
   140  	}
   141  	ginfo, err := gadget.ReadInfoAndValidate(gadgetDir, model, &validationConstraints)
   142  	if err != nil {
   143  		return fmt.Errorf("cannot use gadget: %v", err)
   144  	}
   145  	if err := gadget.ValidateContent(ginfo, gadgetDir); err != nil {
   146  		return fmt.Errorf("cannot use gadget: %v", err)
   147  	}
   148  
   149  	var trustedInstallObserver *boot.TrustedAssetsInstallObserver
   150  	// get a nice nil interface by default
   151  	var installObserver gadget.ContentObserver
   152  	trustedInstallObserver, err = boot.TrustedAssetsInstallObserverForModel(model, gadgetDir, useEncryption)
   153  	if err != nil && err != boot.ErrObserverNotApplicable {
   154  		return fmt.Errorf("cannot setup asset install observer: %v", err)
   155  	}
   156  	if err == nil {
   157  		installObserver = trustedInstallObserver
   158  		if !useEncryption {
   159  			// there will be no key sealing, so past the
   160  			// installation pass no other methods need to be called
   161  			trustedInstallObserver = nil
   162  		}
   163  	}
   164  
   165  	var installedSystem *install.InstalledSystemSideData
   166  	// run the create partition code
   167  	logger.Noticef("create and deploy partitions")
   168  	func() {
   169  		st.Unlock()
   170  		defer st.Lock()
   171  		installedSystem, err = installRun(model, gadgetDir, "", bopts, installObserver)
   172  	}()
   173  	if err != nil {
   174  		return fmt.Errorf("cannot install system: %v", err)
   175  	}
   176  
   177  	if trustedInstallObserver != nil {
   178  		// sanity check
   179  		if installedSystem.KeysForRoles == nil || installedSystem.KeysForRoles[gadget.SystemData] == nil || installedSystem.KeysForRoles[gadget.SystemSave] == nil {
   180  			return fmt.Errorf("internal error: system encryption keys are unset")
   181  		}
   182  		dataKeySet := installedSystem.KeysForRoles[gadget.SystemData]
   183  		saveKeySet := installedSystem.KeysForRoles[gadget.SystemSave]
   184  
   185  		// make note of the encryption keys
   186  		trustedInstallObserver.ChosenEncryptionKeys(dataKeySet.Key, saveKeySet.Key)
   187  
   188  		// keep track of recovery assets
   189  		if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil {
   190  			return fmt.Errorf("cannot observe existing trusted recovery assets: err")
   191  		}
   192  		if err := saveKeys(installedSystem.KeysForRoles); err != nil {
   193  			return err
   194  		}
   195  		// write markers containing a secret to pair data and save
   196  		if err := writeMarkers(); err != nil {
   197  			return err
   198  		}
   199  	}
   200  
   201  	// keep track of the model we installed
   202  	err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755)
   203  	if err != nil {
   204  		return fmt.Errorf("cannot store the model: %v", err)
   205  	}
   206  	err = writeModel(model, filepath.Join(boot.InitramfsUbuntuBootDir, "device/model"))
   207  	if err != nil {
   208  		return fmt.Errorf("cannot store the model: %v", err)
   209  	}
   210  
   211  	// configure the run system
   212  	opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir}
   213  	// configure cloud init
   214  	setSysconfigCloudOptions(opts, gadgetDir, model)
   215  	if err := sysconfigConfigureTargetSystem(opts); err != nil {
   216  		return err
   217  	}
   218  
   219  	// make it bootable
   220  	logger.Noticef("make system bootable")
   221  	bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx)
   222  	if err != nil {
   223  		return fmt.Errorf("cannot get boot base info: %v", err)
   224  	}
   225  	recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem)
   226  	bootWith := &boot.BootableSet{
   227  		Base:              bootBaseInfo,
   228  		BasePath:          bootBaseInfo.MountFile(),
   229  		Kernel:            kernelInfo,
   230  		KernelPath:        kernelInfo.MountFile(),
   231  		RecoverySystemDir: recoverySystemDir,
   232  		UnpackedGadgetDir: gadgetDir,
   233  	}
   234  	rootdir := dirs.GlobalRootDir
   235  	if err := bootMakeBootable(deviceCtx.Model(), rootdir, bootWith, trustedInstallObserver); err != nil {
   236  		return fmt.Errorf("cannot make run system bootable: %v", err)
   237  	}
   238  
   239  	// request a restart as the last action after a successful install
   240  	logger.Noticef("request system restart")
   241  	st.RequestRestart(state.RestartSystemNow)
   242  
   243  	return nil
   244  }
   245  
   246  // writeMarkers writes markers containing the same secret to pair data and save.
   247  func writeMarkers() error {
   248  	// ensure directory for markers exists
   249  	if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil {
   250  		return err
   251  	}
   252  	if err := os.MkdirAll(boot.InstallHostFDESaveDir, 0755); err != nil {
   253  		return err
   254  	}
   255  
   256  	// generate a secret random marker
   257  	markerSecret, err := randutil.CryptoTokenBytes(32)
   258  	if err != nil {
   259  		return fmt.Errorf("cannot create ubuntu-data/save marker secret: %v", err)
   260  	}
   261  
   262  	dataMarker := filepath.Join(boot.InstallHostFDEDataDir, "marker")
   263  	if err := osutil.AtomicWriteFile(dataMarker, markerSecret, 0600, 0); err != nil {
   264  		return err
   265  	}
   266  
   267  	saveMarker := filepath.Join(boot.InstallHostFDESaveDir, "marker")
   268  	if err := osutil.AtomicWriteFile(saveMarker, markerSecret, 0600, 0); err != nil {
   269  		return err
   270  	}
   271  
   272  	return nil
   273  }
   274  
   275  func saveKeys(keysForRoles map[string]*install.EncryptionKeySet) error {
   276  	dataKeySet := keysForRoles[gadget.SystemData]
   277  
   278  	// ensure directory for keys exists
   279  	if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil {
   280  		return err
   281  	}
   282  
   283  	// Write the recovery key
   284  	recoveryKeyFile := filepath.Join(boot.InstallHostFDEDataDir, "recovery.key")
   285  	if err := dataKeySet.RecoveryKey.Save(recoveryKeyFile); err != nil {
   286  		return fmt.Errorf("cannot store recovery key: %v", err)
   287  	}
   288  
   289  	saveKeySet := keysForRoles[gadget.SystemSave]
   290  	if saveKeySet == nil {
   291  		// no system-save support
   292  		return nil
   293  	}
   294  
   295  	saveKey := filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key")
   296  	reinstallSaveKey := filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key")
   297  
   298  	if err := saveKeySet.Key.Save(saveKey); err != nil {
   299  		return fmt.Errorf("cannot store system save key: %v", err)
   300  	}
   301  	if err := saveKeySet.RecoveryKey.Save(reinstallSaveKey); err != nil {
   302  		return fmt.Errorf("cannot store reinstall key: %v", err)
   303  	}
   304  	return nil
   305  }
   306  
   307  var secbootCheckKeySealingSupported = secboot.CheckKeySealingSupported
   308  
   309  // checkEncryption verifies whether encryption should be used based on the
   310  // model grade and the availability of a TPM device or a fde-setup hook
   311  // in the kernel.
   312  func (m *DeviceManager) checkEncryption(st *state.State, deviceCtx snapstate.DeviceContext) (res bool, err error) {
   313  	model := deviceCtx.Model()
   314  	secured := model.Grade() == asserts.ModelSecured
   315  	dangerous := model.Grade() == asserts.ModelDangerous
   316  	encrypted := model.StorageSafety() == asserts.StorageSafetyEncrypted
   317  
   318  	// check if we should disable encryption non-secured devices
   319  	// TODO:UC20: this is not the final mechanism to bypass encryption
   320  	if dangerous && osutil.FileExists(filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")) {
   321  		return false, nil
   322  	}
   323  
   324  	// check if the model prefers to be unencrypted
   325  	// TODO: provide way to select via install chooser menu
   326  	//       if the install is unencrypted or encrypted
   327  	if model.StorageSafety() == asserts.StorageSafetyPreferUnencrypted {
   328  		logger.Noticef(`installing system unencrypted to comply with prefer-unencrypted storage-safety model option`)
   329  		return false, nil
   330  	}
   331  
   332  	// check if encryption is available
   333  	var (
   334  		hasFDESetupHook    bool
   335  		checkEncryptionErr error
   336  	)
   337  	if kernelInfo, err := snapstate.KernelInfo(st, deviceCtx); err == nil {
   338  		if hasFDESetupHook = hasFDESetupHookInKernel(kernelInfo); hasFDESetupHook {
   339  			checkEncryptionErr = m.checkFDEFeatures(st)
   340  		}
   341  	}
   342  	// Note that having a fde-setup hook will disable the build-in
   343  	// secboot encryption
   344  	if !hasFDESetupHook {
   345  		checkEncryptionErr = secbootCheckKeySealingSupported()
   346  	}
   347  
   348  	// check if encryption is required
   349  	if checkEncryptionErr != nil {
   350  		if secured {
   351  			return false, fmt.Errorf("cannot encrypt device storage as mandated by model grade secured: %v", checkEncryptionErr)
   352  		}
   353  		if encrypted {
   354  			return false, fmt.Errorf("cannot encrypt device storage as mandated by encrypted storage-safety model option: %v", checkEncryptionErr)
   355  		}
   356  
   357  		if hasFDESetupHook {
   358  			logger.Noticef("not encrypting device storage as querying kernel fde-setup hook did not succeed: %v", checkEncryptionErr)
   359  		} else {
   360  			logger.Noticef("not encrypting device storage as checking TPM gave: %v", checkEncryptionErr)
   361  		}
   362  
   363  		// not required, go without
   364  		return false, nil
   365  	}
   366  
   367  	// encrypt
   368  	return true, nil
   369  }