github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/gadget/install/install.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  // +build !nosecboot
     3  
     4  /*
     5   * Copyright (C) 2019-2020 Canonical Ltd
     6   *
     7   * This program is free software: you can redistribute it and/or modify
     8   * it under the terms of the GNU General Public License version 3 as
     9   * published by the Free Software Foundation.
    10   *
    11   * This program is distributed in the hope that it will be useful,
    12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14   * GNU General Public License for more details.
    15   *
    16   * You should have received a copy of the GNU General Public License
    17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18   *
    19   */
    20  
    21  package install
    22  
    23  import (
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	"github.com/snapcore/snapd/boot"
    29  	"github.com/snapcore/snapd/gadget"
    30  	"github.com/snapcore/snapd/secboot"
    31  )
    32  
    33  const (
    34  	ubuntuDataLabel = "ubuntu-data"
    35  )
    36  
    37  func deviceFromRole(lv *gadget.LaidOutVolume, role string) (device string, err error) {
    38  	for _, vs := range lv.LaidOutStructure {
    39  		// XXX: this part of the finding maybe should be a
    40  		// method on gadget.*Volume
    41  		if vs.Role == role {
    42  			device, err = gadget.FindDeviceForStructure(&vs)
    43  			if err != nil {
    44  				return "", fmt.Errorf("cannot find device for role %q: %v", role, err)
    45  			}
    46  			return gadget.ParentDiskFromPartition(device)
    47  		}
    48  	}
    49  	return "", fmt.Errorf("cannot find role %s in gadget", role)
    50  }
    51  
    52  // Run bootstraps the partitions of a device, by either creating
    53  // missing ones or recreating installed ones.
    54  func Run(gadgetRoot, device string, options Options, observer SystemInstallObserver) error {
    55  	if gadgetRoot == "" {
    56  		return fmt.Errorf("cannot use empty gadget root directory")
    57  	}
    58  
    59  	lv, err := gadget.PositionedVolumeFromGadget(gadgetRoot)
    60  	if err != nil {
    61  		return fmt.Errorf("cannot layout the volume: %v", err)
    62  	}
    63  
    64  	// XXX: the only situation where auto-detect is not desired is
    65  	//      in (spread) testing - consider to remove forcing a device
    66  	//
    67  	// auto-detect device if no device is forced
    68  	if device == "" {
    69  		device, err = deviceFromRole(lv, gadget.SystemSeed)
    70  		if err != nil {
    71  			return fmt.Errorf("cannot find device to create partitions on: %v", err)
    72  		}
    73  	}
    74  
    75  	diskLayout, err := gadget.OnDiskVolumeFromDevice(device)
    76  	if err != nil {
    77  		return fmt.Errorf("cannot read %v partitions: %v", device, err)
    78  	}
    79  
    80  	// check if the current partition table is compatible with the gadget,
    81  	// ignoring partitions added by the installer (will be removed later)
    82  	if err := ensureLayoutCompatibility(lv, diskLayout); err != nil {
    83  		return fmt.Errorf("gadget and %v partition table not compatible: %v", device, err)
    84  	}
    85  
    86  	// remove partitions added during a previous install attempt
    87  	if err := removeCreatedPartitions(diskLayout); err != nil {
    88  		return fmt.Errorf("cannot remove partitions from previous install: %v", err)
    89  	}
    90  	// at this point we removed any existing partition, nuke any
    91  	// of the existing sealed key files placed outside of the
    92  	// encrypted partitions (LP: #1879338)
    93  	sealedKeyFiles, _ := filepath.Glob(filepath.Join(boot.InitramfsEncryptionKeyDir, "*.sealed-key"))
    94  	for _, keyFile := range sealedKeyFiles {
    95  		if err := os.Remove(keyFile); err != nil && !os.IsNotExist(err) {
    96  			return fmt.Errorf("cannot cleanup obsolete key file: %v", keyFile)
    97  		}
    98  	}
    99  
   100  	created, err := createMissingPartitions(diskLayout, lv)
   101  	if err != nil {
   102  		return fmt.Errorf("cannot create the partitions: %v", err)
   103  	}
   104  
   105  	// We're currently generating a single encryption key, this may change later
   106  	// if we create multiple encrypted partitions.
   107  	var key secboot.EncryptionKey
   108  	var rkey secboot.RecoveryKey
   109  
   110  	if options.Encrypt {
   111  		key, err = secboot.NewEncryptionKey()
   112  		if err != nil {
   113  			return fmt.Errorf("cannot create encryption key: %v", err)
   114  		}
   115  
   116  		rkey, err = secboot.NewRecoveryKey()
   117  		if err != nil {
   118  			return fmt.Errorf("cannot create recovery key: %v", err)
   119  		}
   120  	}
   121  
   122  	for _, part := range created {
   123  		if options.Encrypt && part.Role == gadget.SystemData {
   124  			dataPart, err := newEncryptedDevice(&part, key, ubuntuDataLabel)
   125  			if err != nil {
   126  				return err
   127  			}
   128  
   129  			if err := dataPart.AddRecoveryKey(key, rkey); err != nil {
   130  				return err
   131  			}
   132  
   133  			// update the encrypted device node
   134  			part.Node = dataPart.Node
   135  		}
   136  
   137  		if err := makeFilesystem(&part); err != nil {
   138  			return err
   139  		}
   140  
   141  		if err := writeContent(&part, gadgetRoot, observer); err != nil {
   142  			return err
   143  		}
   144  
   145  		if options.Mount && part.Label != "" && part.HasFilesystem() {
   146  			if err := mountFilesystem(&part, boot.InitramfsRunMntDir); err != nil {
   147  				return err
   148  			}
   149  		}
   150  	}
   151  
   152  	if !options.Encrypt {
   153  		return nil
   154  	}
   155  
   156  	// ensure directories
   157  	for _, p := range []string{boot.InitramfsEncryptionKeyDir, boot.InstallHostFDEDataDir} {
   158  		if err := os.MkdirAll(p, 0755); err != nil {
   159  			return err
   160  		}
   161  	}
   162  
   163  	// Write the recovery key
   164  	recoveryKeyFile := filepath.Join(boot.InstallHostFDEDataDir, "recovery.key")
   165  	if err := rkey.Save(recoveryKeyFile); err != nil {
   166  		return fmt.Errorf("cannot store recovery key: %v", err)
   167  	}
   168  
   169  	if observer != nil {
   170  		observer.ChosenEncryptionKey(key)
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  func ensureLayoutCompatibility(gadgetLayout *gadget.LaidOutVolume, diskLayout *gadget.OnDiskVolume) error {
   177  	eq := func(ds gadget.OnDiskStructure, gs gadget.LaidOutStructure) bool {
   178  		dv := ds.VolumeStructure
   179  		gv := gs.VolumeStructure
   180  		nameMatch := gv.Name == dv.Name
   181  		if gadgetLayout.Schema == "mbr" {
   182  			// partitions have no names in MBR
   183  			nameMatch = true
   184  		}
   185  		// Previous installation may have failed before filesystem creation or partition may be encrypted
   186  		check := nameMatch && ds.StartOffset == gs.StartOffset && (ds.CreatedDuringInstall || dv.Filesystem == gv.Filesystem)
   187  		if gv.Role == gadget.SystemData {
   188  			// system-data may have been expanded
   189  			return check && dv.Size >= gv.Size
   190  		}
   191  		return check && dv.Size == gv.Size
   192  	}
   193  	contains := func(haystack []gadget.LaidOutStructure, needle gadget.OnDiskStructure) bool {
   194  		for _, h := range haystack {
   195  			if eq(needle, h) {
   196  				return true
   197  			}
   198  		}
   199  		return false
   200  	}
   201  
   202  	if gadgetLayout.Size > diskLayout.Size {
   203  		return fmt.Errorf("device %v (%s) is too small to fit the requested layout (%s)", diskLayout.Device,
   204  			diskLayout.Size.IECString(), gadgetLayout.Size.IECString())
   205  	}
   206  
   207  	// Check if top level properties match
   208  	if !isCompatibleSchema(gadgetLayout.Volume.Schema, diskLayout.Schema) {
   209  		return fmt.Errorf("disk partitioning schema %q doesn't match gadget schema %q", diskLayout.Schema, gadgetLayout.Volume.Schema)
   210  	}
   211  	if gadgetLayout.Volume.ID != "" && gadgetLayout.Volume.ID != diskLayout.ID {
   212  		return fmt.Errorf("disk ID %q doesn't match gadget volume ID %q", diskLayout.ID, gadgetLayout.Volume.ID)
   213  	}
   214  
   215  	// Check if all existing device partitions are also in gadget
   216  	for _, ds := range diskLayout.Structure {
   217  		if !contains(gadgetLayout.LaidOutStructure, ds) {
   218  			return fmt.Errorf("cannot find disk partition %s (starting at %d) in gadget", ds.Node, ds.StartOffset)
   219  		}
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func isCompatibleSchema(gadgetSchema, diskSchema string) bool {
   226  	switch gadgetSchema {
   227  	// XXX: "mbr,gpt" is currently unsupported
   228  	case "", "gpt":
   229  		return diskSchema == "gpt"
   230  	case "mbr":
   231  		return diskSchema == "dos"
   232  	default:
   233  		return false
   234  	}
   235  }