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 }