github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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/install"
    33  	"github.com/snapcore/snapd/logger"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/overlord/snapstate"
    36  	"github.com/snapcore/snapd/overlord/state"
    37  	"github.com/snapcore/snapd/secboot"
    38  	"github.com/snapcore/snapd/sysconfig"
    39  )
    40  
    41  var (
    42  	bootMakeBootable = boot.MakeBootable
    43  	installRun       = install.Run
    44  
    45  	sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem
    46  )
    47  
    48  func setSysconfigCloudOptions(opts *sysconfig.Options, gadgetDir string, model *asserts.Model) {
    49  	ubuntuSeedCloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d")
    50  
    51  	switch {
    52  	// if the gadget has a cloud.conf file, always use that regardless of grade
    53  	case sysconfig.HasGadgetCloudConf(gadgetDir):
    54  		// this is implicitly handled by ConfigureTargetSystem when it configures
    55  		// cloud-init if none of the other options are set, so just break here
    56  		opts.AllowCloudInit = true
    57  
    58  	// next thing is if are in secured grade and didn't have gadget config, we
    59  	// disable cloud-init always, clouds should have their own config via
    60  	// gadgets for grade secured
    61  	case model.Grade() == asserts.ModelSecured:
    62  		opts.AllowCloudInit = false
    63  
    64  	// TODO:UC20: on grade signed, allow files from ubuntu-seed, but do
    65  	//            filtering on the resultant cloud config
    66  
    67  	// next if we are grade dangerous, then we also install cloud configuration
    68  	// from ubuntu-seed if it exists
    69  	case model.Grade() == asserts.ModelDangerous && osutil.IsDirectory(ubuntuSeedCloudCfg):
    70  		opts.AllowCloudInit = true
    71  		opts.CloudInitSrcDir = ubuntuSeedCloudCfg
    72  
    73  	// note that if none of the conditions were true, it means we are on grade
    74  	// dangerous or signed, and cloud-init is still allowed to run without
    75  	// additional configuration on first-boot, so that NoCloud CIDATA can be
    76  	// provided for example
    77  	default:
    78  		opts.AllowCloudInit = true
    79  	}
    80  }
    81  
    82  func writeModel(model *asserts.Model, where string) error {
    83  	f, err := os.OpenFile(where, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
    84  	if err != nil {
    85  		return err
    86  	}
    87  	defer f.Close()
    88  	return asserts.NewEncoder(f).Encode(model)
    89  }
    90  
    91  func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error {
    92  	st := t.State()
    93  	st.Lock()
    94  	defer st.Unlock()
    95  
    96  	perfTimings := state.TimingsForTask(t)
    97  	defer perfTimings.Save(st)
    98  
    99  	// get gadget dir
   100  	deviceCtx, err := DeviceCtx(st, t, nil)
   101  	if err != nil {
   102  		return fmt.Errorf("cannot get device context: %v", err)
   103  	}
   104  	gadgetInfo, err := snapstate.GadgetInfo(st, deviceCtx)
   105  	if err != nil {
   106  		return fmt.Errorf("cannot get gadget info: %v", err)
   107  	}
   108  	gadgetDir := gadgetInfo.MountDir()
   109  
   110  	kernelInfo, err := snapstate.KernelInfo(st, deviceCtx)
   111  	if err != nil {
   112  		return fmt.Errorf("cannot get kernel info: %v", err)
   113  	}
   114  
   115  	modeEnv, err := maybeReadModeenv()
   116  	if err != nil {
   117  		return err
   118  	}
   119  	if modeEnv == nil {
   120  		return fmt.Errorf("missing modeenv, cannot proceed")
   121  	}
   122  
   123  	// bootstrap
   124  	bopts := install.Options{
   125  		Mount: true,
   126  	}
   127  	useEncryption, err := checkEncryption(deviceCtx.Model())
   128  	if err != nil {
   129  		return err
   130  	}
   131  	bopts.Encrypt = useEncryption
   132  
   133  	var trustedInstallObserver *boot.TrustedAssetsInstallObserver
   134  	// get a nice nil interface by default
   135  	var installObserver install.SystemInstallObserver
   136  	trustedInstallObserver, err = boot.TrustedAssetsInstallObserverForModel(deviceCtx.Model(), gadgetDir, useEncryption)
   137  	if err != nil && err != boot.ErrObserverNotApplicable {
   138  		return fmt.Errorf("cannot setup asset install observer: %v", err)
   139  	}
   140  	if err == nil {
   141  		installObserver = trustedInstallObserver
   142  		if !useEncryption {
   143  			// there will be no key sealing, so past the
   144  			// installation pass no other methods need to be called
   145  			trustedInstallObserver = nil
   146  		}
   147  	}
   148  
   149  	// run the create partition code
   150  	logger.Noticef("create and deploy partitions")
   151  	func() {
   152  		st.Unlock()
   153  		defer st.Lock()
   154  		err = installRun(gadgetDir, "", bopts, installObserver)
   155  	}()
   156  	if err != nil {
   157  		return fmt.Errorf("cannot create partitions: %v", err)
   158  	}
   159  
   160  	if trustedInstallObserver != nil {
   161  		if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil {
   162  			return fmt.Errorf("cannot observe existing trusted recovery assets: err")
   163  		}
   164  	}
   165  
   166  	// keep track of the model we installed
   167  	err = writeModel(deviceCtx.Model(), filepath.Join(boot.InitramfsUbuntuBootDir, "model"))
   168  	if err != nil {
   169  		return fmt.Errorf("cannot store the model: %v", err)
   170  	}
   171  
   172  	// configure the run system
   173  	opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir}
   174  	// configure cloud init
   175  	setSysconfigCloudOptions(opts, gadgetDir, deviceCtx.Model())
   176  	if err := sysconfigConfigureTargetSystem(opts); err != nil {
   177  		return err
   178  	}
   179  
   180  	// make it bootable
   181  	logger.Noticef("make system bootable")
   182  	bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx)
   183  	if err != nil {
   184  		return fmt.Errorf("cannot get boot base info: %v", err)
   185  	}
   186  	recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem)
   187  	bootWith := &boot.BootableSet{
   188  		Base:              bootBaseInfo,
   189  		BasePath:          bootBaseInfo.MountFile(),
   190  		Kernel:            kernelInfo,
   191  		KernelPath:        kernelInfo.MountFile(),
   192  		RecoverySystemDir: recoverySystemDir,
   193  		UnpackedGadgetDir: gadgetDir,
   194  	}
   195  	rootdir := dirs.GlobalRootDir
   196  	if err := bootMakeBootable(deviceCtx.Model(), rootdir, bootWith, trustedInstallObserver); err != nil {
   197  		return fmt.Errorf("cannot make run system bootable: %v", err)
   198  	}
   199  
   200  	// request a restart as the last action after a successful install
   201  	logger.Noticef("request system restart")
   202  	st.RequestRestart(state.RestartSystemNow)
   203  
   204  	return nil
   205  }
   206  
   207  var secbootCheckKeySealingSupported = secboot.CheckKeySealingSupported
   208  
   209  // checkEncryption verifies whether encryption should be used based on the
   210  // model grade and the availability of a TPM device.
   211  func checkEncryption(model *asserts.Model) (res bool, err error) {
   212  	secured := model.Grade() == asserts.ModelSecured
   213  	dangerous := model.Grade() == asserts.ModelDangerous
   214  
   215  	// check if we should disable encryption non-secured devices
   216  	// TODO:UC20: this is not the final mechanism to bypass encryption
   217  	if dangerous && osutil.FileExists(filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")) {
   218  		return false, nil
   219  	}
   220  
   221  	// encryption is required in secured devices and optional in other grades
   222  	if err := secbootCheckKeySealingSupported(); err != nil {
   223  		if secured {
   224  			return false, fmt.Errorf("cannot encrypt secured device: %v", err)
   225  		}
   226  		return false, nil
   227  	}
   228  
   229  	return true, nil
   230  }