github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/overlord/devicestate/handlers_install.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "compress/gzip" 24 "fmt" 25 "os" 26 "os/exec" 27 "path/filepath" 28 29 "gopkg.in/tomb.v2" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/boot" 33 "github.com/snapcore/snapd/gadget" 34 "github.com/snapcore/snapd/gadget/install" 35 "github.com/snapcore/snapd/logger" 36 "github.com/snapcore/snapd/osutil" 37 "github.com/snapcore/snapd/overlord/snapstate" 38 "github.com/snapcore/snapd/overlord/state" 39 "github.com/snapcore/snapd/randutil" 40 "github.com/snapcore/snapd/secboot" 41 "github.com/snapcore/snapd/sysconfig" 42 ) 43 44 var ( 45 bootMakeRunnable = boot.MakeRunnableSystem 46 bootEnsureNextBootToRunMode = boot.EnsureNextBootToRunMode 47 installRun = install.Run 48 49 sysconfigConfigureTargetSystem = sysconfig.ConfigureTargetSystem 50 ) 51 52 func setSysconfigCloudOptions(opts *sysconfig.Options, gadgetDir string, model *asserts.Model) { 53 ubuntuSeedCloudCfg := filepath.Join(boot.InitramfsUbuntuSeedDir, "data/etc/cloud/cloud.cfg.d") 54 55 // TODO:UC20: on grade signed, allow files from ubuntu-seed, but do 56 // filtering on the resultant cloud config 57 shouldUseUbuntuSeed := model.Grade() == asserts.ModelDangerous && osutil.IsDirectory(ubuntuSeedCloudCfg) 58 59 switch { 60 // if the gadget has a cloud.conf file, always use that regardless of grade 61 case sysconfig.HasGadgetCloudConf(gadgetDir): 62 // this is implicitly handled by ConfigureTargetSystem when it 63 // configures cloud-init, so we just need to allow cloud-init for the 64 // gadget config to be used, but we also should check to see if 65 // ubuntu-seed config should be allowed as well 66 opts.AllowCloudInit = true 67 if shouldUseUbuntuSeed { 68 opts.CloudInitSrcDir = ubuntuSeedCloudCfg 69 } 70 71 // next thing is if are in secured grade and didn't have gadget config, we 72 // disable cloud-init always, clouds should have their own config via 73 // gadgets for grade secured 74 case model.Grade() == asserts.ModelSecured: 75 opts.AllowCloudInit = false 76 77 // next if we are grade dangerous, then we also install cloud configuration 78 // from ubuntu-seed if it exists 79 case shouldUseUbuntuSeed: 80 opts.AllowCloudInit = true 81 opts.CloudInitSrcDir = ubuntuSeedCloudCfg 82 83 // note that if none of the conditions were true, it means we are on grade 84 // dangerous or signed, and cloud-init is still allowed to run without 85 // additional configuration on first-boot, so that NoCloud CIDATA can be 86 // provided for example 87 default: 88 opts.AllowCloudInit = true 89 } 90 } 91 92 func writeModel(model *asserts.Model, where string) error { 93 f, err := os.OpenFile(where, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) 94 if err != nil { 95 return err 96 } 97 defer f.Close() 98 return asserts.NewEncoder(f).Encode(model) 99 } 100 101 func writeLogs(rootdir string) error { 102 // XXX: would be great to use native journal format but it's tied 103 // to machine-id, we could journal -o export but there 104 // is no systemd-journal-remote on core{,18,20} 105 // 106 // XXX: or only log if persistent journal is enabled? 107 logPath := filepath.Join(rootdir, "var/log/install-mode.log.gz") 108 if err := os.MkdirAll(filepath.Dir(logPath), 0755); err != nil { 109 return err 110 } 111 112 f, err := os.Create(logPath) 113 if err != nil { 114 return err 115 } 116 defer f.Close() 117 118 gz := gzip.NewWriter(f) 119 defer gz.Close() 120 121 cmd := exec.Command("journalctl", "-b", "0") 122 cmd.Stdout = gz 123 if err := cmd.Run(); err != nil { 124 return fmt.Errorf("cannot collect journal output: %v", err) 125 } 126 if err := gz.Flush(); err != nil { 127 return fmt.Errorf("cannot flush compressed log output: %v", err) 128 } 129 return nil 130 } 131 132 func (m *DeviceManager) doSetupRunSystem(t *state.Task, _ *tomb.Tomb) error { 133 st := t.State() 134 st.Lock() 135 defer st.Unlock() 136 137 perfTimings := state.TimingsForTask(t) 138 defer perfTimings.Save(st) 139 140 // get gadget dir 141 deviceCtx, err := DeviceCtx(st, t, nil) 142 if err != nil { 143 return fmt.Errorf("cannot get device context: %v", err) 144 } 145 gadgetInfo, err := snapstate.GadgetInfo(st, deviceCtx) 146 if err != nil { 147 return fmt.Errorf("cannot get gadget info: %v", err) 148 } 149 gadgetDir := gadgetInfo.MountDir() 150 151 kernelInfo, err := snapstate.KernelInfo(st, deviceCtx) 152 if err != nil { 153 return fmt.Errorf("cannot get kernel info: %v", err) 154 } 155 kernelDir := kernelInfo.MountDir() 156 157 modeEnv, err := maybeReadModeenv() 158 if err != nil { 159 return err 160 } 161 if modeEnv == nil { 162 return fmt.Errorf("missing modeenv, cannot proceed") 163 } 164 165 // bootstrap 166 bopts := install.Options{ 167 Mount: true, 168 } 169 useEncryption, err := m.checkEncryption(st, deviceCtx) 170 if err != nil { 171 return err 172 } 173 bopts.Encrypt = useEncryption 174 175 model := deviceCtx.Model() 176 177 // make sure that gadget is usable for the set up we want to use it in 178 validationConstraints := gadget.ValidationConstraints{ 179 EncryptedData: useEncryption, 180 } 181 ginfo, err := gadget.ReadInfoAndValidate(gadgetDir, model, &validationConstraints) 182 if err != nil { 183 return fmt.Errorf("cannot use gadget: %v", err) 184 } 185 if err := gadget.ValidateContent(ginfo, gadgetDir, kernelDir); err != nil { 186 return fmt.Errorf("cannot use gadget: %v", err) 187 } 188 189 var trustedInstallObserver *boot.TrustedAssetsInstallObserver 190 // get a nice nil interface by default 191 var installObserver gadget.ContentObserver 192 trustedInstallObserver, err = boot.TrustedAssetsInstallObserverForModel(model, gadgetDir, useEncryption) 193 if err != nil && err != boot.ErrObserverNotApplicable { 194 return fmt.Errorf("cannot setup asset install observer: %v", err) 195 } 196 if err == nil { 197 installObserver = trustedInstallObserver 198 if !useEncryption { 199 // there will be no key sealing, so past the 200 // installation pass no other methods need to be called 201 trustedInstallObserver = nil 202 } 203 } 204 205 var installedSystem *install.InstalledSystemSideData 206 // run the create partition code 207 logger.Noticef("create and deploy partitions") 208 func() { 209 st.Unlock() 210 defer st.Lock() 211 installedSystem, err = installRun(model, gadgetDir, kernelDir, "", bopts, installObserver) 212 }() 213 if err != nil { 214 return fmt.Errorf("cannot install system: %v", err) 215 } 216 217 if trustedInstallObserver != nil { 218 // sanity check 219 if installedSystem.KeysForRoles == nil || installedSystem.KeysForRoles[gadget.SystemData] == nil || installedSystem.KeysForRoles[gadget.SystemSave] == nil { 220 return fmt.Errorf("internal error: system encryption keys are unset") 221 } 222 dataKeySet := installedSystem.KeysForRoles[gadget.SystemData] 223 saveKeySet := installedSystem.KeysForRoles[gadget.SystemSave] 224 225 // make note of the encryption keys 226 trustedInstallObserver.ChosenEncryptionKeys(dataKeySet.Key, saveKeySet.Key) 227 228 // keep track of recovery assets 229 if err := trustedInstallObserver.ObserveExistingTrustedRecoveryAssets(boot.InitramfsUbuntuSeedDir); err != nil { 230 return fmt.Errorf("cannot observe existing trusted recovery assets: err") 231 } 232 if err := saveKeys(installedSystem.KeysForRoles); err != nil { 233 return err 234 } 235 // write markers containing a secret to pair data and save 236 if err := writeMarkers(); err != nil { 237 return err 238 } 239 } 240 241 // keep track of the model we installed 242 err = os.MkdirAll(filepath.Join(boot.InitramfsUbuntuBootDir, "device"), 0755) 243 if err != nil { 244 return fmt.Errorf("cannot store the model: %v", err) 245 } 246 err = writeModel(model, filepath.Join(boot.InitramfsUbuntuBootDir, "device/model")) 247 if err != nil { 248 return fmt.Errorf("cannot store the model: %v", err) 249 } 250 251 // configure the run system 252 opts := &sysconfig.Options{TargetRootDir: boot.InstallHostWritableDir, GadgetDir: gadgetDir} 253 // configure cloud init 254 setSysconfigCloudOptions(opts, gadgetDir, model) 255 if err := sysconfigConfigureTargetSystem(opts); err != nil { 256 return err 257 } 258 259 // make it bootable 260 logger.Noticef("make system runnable") 261 bootBaseInfo, err := snapstate.BootBaseInfo(st, deviceCtx) 262 if err != nil { 263 return fmt.Errorf("cannot get boot base info: %v", err) 264 } 265 recoverySystemDir := filepath.Join("/systems", modeEnv.RecoverySystem) 266 bootWith := &boot.BootableSet{ 267 Base: bootBaseInfo, 268 BasePath: bootBaseInfo.MountFile(), 269 Kernel: kernelInfo, 270 KernelPath: kernelInfo.MountFile(), 271 RecoverySystemDir: recoverySystemDir, 272 UnpackedGadgetDir: gadgetDir, 273 } 274 if err := bootMakeRunnable(deviceCtx.Model(), bootWith, trustedInstallObserver); err != nil { 275 return fmt.Errorf("cannot make system runnable: %v", err) 276 } 277 278 return nil 279 } 280 281 // writeMarkers writes markers containing the same secret to pair data and save. 282 func writeMarkers() error { 283 // ensure directory for markers exists 284 if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil { 285 return err 286 } 287 if err := os.MkdirAll(boot.InstallHostFDESaveDir, 0755); err != nil { 288 return err 289 } 290 291 // generate a secret random marker 292 markerSecret, err := randutil.CryptoTokenBytes(32) 293 if err != nil { 294 return fmt.Errorf("cannot create ubuntu-data/save marker secret: %v", err) 295 } 296 297 dataMarker := filepath.Join(boot.InstallHostFDEDataDir, "marker") 298 if err := osutil.AtomicWriteFile(dataMarker, markerSecret, 0600, 0); err != nil { 299 return err 300 } 301 302 saveMarker := filepath.Join(boot.InstallHostFDESaveDir, "marker") 303 if err := osutil.AtomicWriteFile(saveMarker, markerSecret, 0600, 0); err != nil { 304 return err 305 } 306 307 return nil 308 } 309 310 func saveKeys(keysForRoles map[string]*install.EncryptionKeySet) error { 311 dataKeySet := keysForRoles[gadget.SystemData] 312 313 // ensure directory for keys exists 314 if err := os.MkdirAll(boot.InstallHostFDEDataDir, 0755); err != nil { 315 return err 316 } 317 318 // Write the recovery key 319 recoveryKeyFile := filepath.Join(boot.InstallHostFDEDataDir, "recovery.key") 320 if err := dataKeySet.RecoveryKey.Save(recoveryKeyFile); err != nil { 321 return fmt.Errorf("cannot store recovery key: %v", err) 322 } 323 324 saveKeySet := keysForRoles[gadget.SystemSave] 325 if saveKeySet == nil { 326 // no system-save support 327 return nil 328 } 329 330 saveKey := filepath.Join(boot.InstallHostFDEDataDir, "ubuntu-save.key") 331 reinstallSaveKey := filepath.Join(boot.InstallHostFDEDataDir, "reinstall.key") 332 333 if err := saveKeySet.Key.Save(saveKey); err != nil { 334 return fmt.Errorf("cannot store system save key: %v", err) 335 } 336 if err := saveKeySet.RecoveryKey.Save(reinstallSaveKey); err != nil { 337 return fmt.Errorf("cannot store reinstall key: %v", err) 338 } 339 return nil 340 } 341 342 var secbootCheckTPMKeySealingSupported = secboot.CheckTPMKeySealingSupported 343 344 // checkEncryption verifies whether encryption should be used based on the 345 // model grade and the availability of a TPM device or a fde-setup hook 346 // in the kernel. 347 func (m *DeviceManager) checkEncryption(st *state.State, deviceCtx snapstate.DeviceContext) (res bool, err error) { 348 model := deviceCtx.Model() 349 secured := model.Grade() == asserts.ModelSecured 350 dangerous := model.Grade() == asserts.ModelDangerous 351 encrypted := model.StorageSafety() == asserts.StorageSafetyEncrypted 352 353 // check if we should disable encryption non-secured devices 354 // TODO:UC20: this is not the final mechanism to bypass encryption 355 if dangerous && osutil.FileExists(filepath.Join(boot.InitramfsUbuntuSeedDir, ".force-unencrypted")) { 356 return false, nil 357 } 358 359 // check if the model prefers to be unencrypted 360 // TODO: provide way to select via install chooser menu 361 // if the install is unencrypted or encrypted 362 if model.StorageSafety() == asserts.StorageSafetyPreferUnencrypted { 363 logger.Noticef(`installing system unencrypted to comply with prefer-unencrypted storage-safety model option`) 364 return false, nil 365 } 366 367 // check if encryption is available 368 var ( 369 hasFDESetupHook bool 370 checkEncryptionErr error 371 ) 372 if kernelInfo, err := snapstate.KernelInfo(st, deviceCtx); err == nil { 373 if hasFDESetupHook = hasFDESetupHookInKernel(kernelInfo); hasFDESetupHook { 374 checkEncryptionErr = m.checkFDEFeatures(st) 375 } 376 } 377 // Note that having a fde-setup hook will disable the build-in 378 // secboot encryption 379 if !hasFDESetupHook { 380 checkEncryptionErr = secbootCheckTPMKeySealingSupported() 381 } 382 383 // check if encryption is required 384 if checkEncryptionErr != nil { 385 if secured { 386 return false, fmt.Errorf("cannot encrypt device storage as mandated by model grade secured: %v", checkEncryptionErr) 387 } 388 if encrypted { 389 return false, fmt.Errorf("cannot encrypt device storage as mandated by encrypted storage-safety model option: %v", checkEncryptionErr) 390 } 391 392 if hasFDESetupHook { 393 logger.Noticef("not encrypting device storage as querying kernel fde-setup hook did not succeed: %v", checkEncryptionErr) 394 } else { 395 logger.Noticef("not encrypting device storage as checking TPM gave: %v", checkEncryptionErr) 396 } 397 398 // not required, go without 399 return false, nil 400 } 401 402 // encrypt 403 return true, nil 404 } 405 406 // RebootOptions can be attached to restart-system-to-run-mode tasks to control 407 // their restart behavior. 408 type RebootOptions struct { 409 Op string `json:"op,omitempty"` 410 } 411 412 const ( 413 RebootHaltOp = "halt" 414 RebootPoweroffOp = "poweroff" 415 ) 416 417 func (m *DeviceManager) doRestartSystemToRunMode(t *state.Task, _ *tomb.Tomb) error { 418 st := t.State() 419 st.Lock() 420 defer st.Unlock() 421 422 perfTimings := state.TimingsForTask(t) 423 defer perfTimings.Save(st) 424 425 modeEnv, err := maybeReadModeenv() 426 if err != nil { 427 return err 428 } 429 430 if modeEnv == nil { 431 return fmt.Errorf("missing modeenv, cannot proceed") 432 } 433 434 // store install-mode log into ubuntu-data partition 435 if err := writeLogs(boot.InstallHostWritableDir); err != nil { 436 logger.Noticef("cannot write installation log: %v", err) 437 } 438 439 // ensure the next boot goes into run mode 440 if err := bootEnsureNextBootToRunMode(modeEnv.RecoverySystem); err != nil { 441 return err 442 } 443 444 var rebootOpts RebootOptions 445 err = t.Get("reboot", &rebootOpts) 446 if err != nil && err != state.ErrNoState { 447 return err 448 } 449 450 // request by default a restart as the last action after a 451 // successful install or what install-device requested via 452 // snapctl reboot 453 rst := state.RestartSystemNow 454 what := "restart" 455 switch rebootOpts.Op { 456 case RebootHaltOp: 457 what = "halt" 458 rst = state.RestartSystemHaltNow 459 case RebootPoweroffOp: 460 what = "poweroff" 461 rst = state.RestartSystemPoweroffNow 462 } 463 logger.Noticef("request immediate system %s", what) 464 st.RequestRestart(rst) 465 466 return nil 467 }