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 }