github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	"os/exec"
    26  	"path/filepath"
    27  
    28  	"gopkg.in/tomb.v2"
    29  
    30  	"github.com/snapcore/snapd/asserts"
    31  	"github.com/snapcore/snapd/boot"
    32  	"github.com/snapcore/snapd/dirs"
    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/secboot"
    39  	"github.com/snapcore/snapd/sysconfig"
    40  )
    41  
    42  var (
    43  	bootMakeBootable            = boot.MakeBootable
    44  	sysconfigConfigureRunSystem = sysconfig.ConfigureRunSystem
    45  	installRun                  = install.Run
    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 ConfigureRunSystem 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 writeLogs(rootdir string) error {
    92  	// XXX: would be great to use native journal but it's tied
    93  	//      to machine-id, we could journal -o export but there
    94  	//      is no systemd-journal-remote on core{,18,20}
    95  	//
    96  	// XXX: or only log if persistant journal is enabled?
    97  	logPath := filepath.Join(rootdir, "_writable_defaults/var/log/install-mode.log")
    98  	if err := os.MkdirAll(filepath.Dir(logPath), 0755); err != nil {
    99  		return err
   100  	}
   101  
   102  	f, err := os.Create(logPath)
   103  	if err != nil {
   104  		return err
   105  	}
   106  	defer f.Close()
   107  
   108  	cmd := exec.Command("journalctl")
   109  	cmd.Stdout = f
   110  	return cmd.Run()
   111  }
   112  
   113  func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error {
   114  	st := t.State()
   115  	st.Lock()
   116  	defer st.Unlock()
   117  
   118  	perfTimings := state.TimingsForTask(t)
   119  	defer perfTimings.Save(st)
   120  
   121  	// get gadget dir
   122  	deviceCtx, err := DeviceCtx(st, t, nil)
   123  	if err != nil {
   124  		return fmt.Errorf("cannot get device context: %v", err)
   125  	}
   126  	gadgetInfo, err := snapstate.GadgetInfo(st, deviceCtx)
   127  	if err != nil {
   128  		return fmt.Errorf("cannot get gadget info: %v", err)
   129  	}
   130  	gadgetDir := gadgetInfo.MountDir()
   131  
   132  	kernelInfo, err := snapstate.KernelInfo(st, deviceCtx)
   133  	if err != nil {
   134  		return fmt.Errorf("cannot get kernel info: %v", err)
   135  	}
   136  
   137  	modeEnv, err := maybeReadModeenv()
   138  	if err != nil {
   139  		return err
   140  	}
   141  	if modeEnv == nil {
   142  		return fmt.Errorf("missing modeenv, cannot proceed")
   143  	}
   144  
   145  	// bootstrap
   146  	bopts := install.Options{
   147  		Mount: true,
   148  	}
   149  	useEncryption, err := checkEncryption(deviceCtx.Model())
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	var trustedInstallObserver *boot.TrustedAssetsInstallObserver
   155  	// get a nice nil interface by default
   156  	var installObserver install.SystemInstallObserver
   157  	if useEncryption {
   158  		bopts.Encrypt = true
   159  
   160  		trustedInstallObserver, err = boot.TrustedAssetsInstallObserverForModel(deviceCtx.Model(), gadgetDir)
   161  		if err != nil && err != boot.ErrObserverNotApplicable {
   162  			return fmt.Errorf("cannot setup asset install observer: %v", err)
   163  		}
   164  		if err == nil {
   165  			installObserver = trustedInstallObserver
   166  		}
   167  	}
   168  
   169  	// run the create partition code
   170  	logger.Noticef("create and deploy partitions")
   171  	func() {
   172  		st.Unlock()
   173  		defer st.Lock()
   174  		err = installRun(gadgetDir, "", bopts, installObserver)
   175  	}()
   176  	if err != nil {
   177  		return fmt.Errorf("cannot create partitions: %v", err)
   178  	}
   179  
   180  	if trustedInstallObserver != nil {
   181  		if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil {
   182  			return fmt.Errorf("cannot observe existing trusted recovery assets: err")
   183  		}
   184  	}
   185  
   186  	// keep track of the model we installed
   187  	err = writeModel(deviceCtx.Model(), filepath.Join(boot.InitramfsUbuntuBootDir, "model"))
   188  	if err != nil {
   189  		return fmt.Errorf("cannot store the model: %v", err)
   190  	}
   191  
   192  	// configure the run system
   193  	opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir}
   194  	// configure cloud init
   195  	setSysconfigCloudOptions(opts, gadgetDir, deviceCtx.Model())
   196  	if err := sysconfigConfigureRunSystem(opts); err != nil {
   197  		return err
   198  	}
   199  
   200  	// make it bootable
   201  	logger.Noticef("make system bootable")
   202  	bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx)
   203  	if err != nil {
   204  		return fmt.Errorf("cannot get boot base info: %v", err)
   205  	}
   206  	recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem)
   207  	bootWith := &boot.BootableSet{
   208  		Base:              bootBaseInfo,
   209  		BasePath:          bootBaseInfo.MountFile(),
   210  		Kernel:            kernelInfo,
   211  		KernelPath:        kernelInfo.MountFile(),
   212  		RecoverySystemDir: recoverySystemDir,
   213  		UnpackedGadgetDir: gadgetDir,
   214  	}
   215  	rootdir := dirs.GlobalRootDir
   216  	if err := bootMakeBootable(deviceCtx.Model(), rootdir, bootWith, trustedInstallObserver); err != nil {
   217  		return fmt.Errorf("cannot make run system bootable: %v", err)
   218  	}
   219  
   220  	// store install-mode log into ubuntu-data partition
   221  	if err := writeLogs(boot.InstallHostWritableDir); err != nil {
   222  		logger.Noticef("cannot write logs: %v", err)
   223  	}
   224  
   225  	// request a restart as the last action after a successful install
   226  	logger.Noticef("request system restart")
   227  	st.RequestRestart(state.RestartSystemNow)
   228  
   229  	return nil
   230  }
   231  
   232  var secbootCheckKeySealingSupported = secboot.CheckKeySealingSupported
   233  
   234  // checkEncryption verifies whether encryption should be used based on the
   235  // model grade and the availability of a TPM device.
   236  func checkEncryption(model *asserts.Model) (res bool, err error) {
   237  	secured := model.Grade() == asserts.ModelSecured
   238  	dangerous := model.Grade() == asserts.ModelDangerous
   239  
   240  	// check if we should disable encryption non-secured devices
   241  	// TODO:UC20: this is not the final mechanism to bypass encryption
   242  	if dangerous && osutil.FileExists(filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")) {
   243  		return false, nil
   244  	}
   245  
   246  	// encryption is required in secured devices and optional in other grades
   247  	if err := secbootCheckKeySealingSupported(); err != nil {
   248  		if secured {
   249  			return false, fmt.Errorf("cannot encrypt secured device: %v", err)
   250  		}
   251  		return false, nil
   252  	}
   253  
   254  	return true, nil
   255  }