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