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 }