mirror of
https://github.com/kairos-io/immucore.git
synced 2025-09-03 23:54:36 +00:00
This help the transition between 3.3.x to 3.4.x by moving existing sysexts to their new places and creating the expected links so it should behave exactly like it was before Signed-off-by: Itxaka <itxaka@kairos.io>
715 lines
24 KiB
Go
715 lines
24 KiB
Go
package state
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/foxboron/go-uefi/efi"
|
|
"github.com/hashicorp/go-multierror"
|
|
cnst "github.com/kairos-io/immucore/internal/constants"
|
|
internalUtils "github.com/kairos-io/immucore/internal/utils"
|
|
"github.com/kairos-io/immucore/pkg/op"
|
|
"github.com/kairos-io/immucore/pkg/schema"
|
|
"github.com/kairos-io/kairos-sdk/signatures"
|
|
"github.com/kairos-io/kairos-sdk/state"
|
|
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
|
|
"github.com/mudler/go-kdetect"
|
|
"github.com/spectrocloud-labs/herd"
|
|
)
|
|
|
|
// UKIExtendPCR extends the PCR with the given extension in a graceful way.
|
|
func UKIExtendPCR(extension string) error {
|
|
return internalUtils.PCRExtend(cnst.DefaultPCR, []byte(extension))
|
|
|
|
}
|
|
|
|
// UKIMountBaseSystem mounts the base system for the UKI boot system
|
|
// as when booting in UKI mode we have a blank slate and we need to mount everything
|
|
// Make sure we set the directories as MS_SHARED
|
|
// This is important afterwards when running containers and they get unshared and so on
|
|
// And can lead to rootfs out of boundaries issues for them
|
|
// also it doesnt help when mounting the final rootfs as we want to broke the mounts into it and any submounts.
|
|
func (s *State) UKIMountBaseSystem(g *herd.Graph) error {
|
|
type mount struct {
|
|
where string
|
|
what string
|
|
fs string
|
|
flags uintptr
|
|
data string
|
|
}
|
|
|
|
return g.Add(
|
|
cnst.OpUkiBaseMounts,
|
|
herd.WithCallback(
|
|
func(_ context.Context) error {
|
|
var err error
|
|
// Mount base mounts
|
|
mounts := []mount{
|
|
{
|
|
"/sys",
|
|
"sysfs",
|
|
"sysfs",
|
|
syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_RELATIME,
|
|
"",
|
|
},
|
|
{
|
|
"/sys",
|
|
"",
|
|
"",
|
|
syscall.MS_SHARED,
|
|
"",
|
|
},
|
|
{
|
|
"/sys/kernel/security",
|
|
"securityfs",
|
|
"securityfs",
|
|
0,
|
|
"",
|
|
},
|
|
{
|
|
"/sys/kernel/debug",
|
|
"debugfs",
|
|
"debugfs",
|
|
0,
|
|
"",
|
|
},
|
|
{
|
|
"/sys/firmware/efi/efivars",
|
|
"efivarfs",
|
|
"efivarfs",
|
|
syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_RELATIME,
|
|
"",
|
|
},
|
|
{
|
|
"/dev",
|
|
"devtmpfs",
|
|
"devtmpfs",
|
|
syscall.MS_NOSUID,
|
|
"mode=755",
|
|
},
|
|
{
|
|
"/dev",
|
|
"",
|
|
"",
|
|
syscall.MS_SHARED,
|
|
"",
|
|
},
|
|
{
|
|
"/dev/pts",
|
|
"devpts",
|
|
"devpts",
|
|
syscall.MS_NOSUID | syscall.MS_NOEXEC,
|
|
"ptmxmode=000,gid=5,mode=620",
|
|
},
|
|
{
|
|
"/dev/shm",
|
|
"tmpfs",
|
|
"tmpfs",
|
|
0,
|
|
"",
|
|
},
|
|
{
|
|
"/tmp",
|
|
"tmpfs",
|
|
"tmpfs",
|
|
syscall.MS_NOSUID | syscall.MS_NODEV,
|
|
"",
|
|
},
|
|
{
|
|
"/tmp",
|
|
"",
|
|
"",
|
|
syscall.MS_SHARED,
|
|
"",
|
|
},
|
|
}
|
|
|
|
for dir, perm := range map[string]os.FileMode{
|
|
"/proc": 0o555,
|
|
"/dev": 0o777,
|
|
"/dev/pts": 0o777,
|
|
"/dev/shm": 0o777,
|
|
"/sys": 0o555,
|
|
} {
|
|
e := os.MkdirAll(dir, perm)
|
|
if e != nil {
|
|
internalUtils.Log.Err(e).Str("dir", dir).Interface("permissions", perm).Msg("Creating dir")
|
|
}
|
|
}
|
|
for _, m := range mounts {
|
|
e := os.MkdirAll(m.where, 0755)
|
|
if e != nil {
|
|
err = multierror.Append(err, e)
|
|
internalUtils.Log.Err(e).Msg("Creating dir")
|
|
}
|
|
|
|
e = internalUtils.Mount(m.what, m.where, m.fs, m.flags, m.data)
|
|
if e != nil {
|
|
err = multierror.Append(err, e)
|
|
internalUtils.Log.Err(e).Str("what", m.what).Str("where", m.where).Str("type", m.fs).Msg("Mounting")
|
|
}
|
|
}
|
|
|
|
// Now that we have all the mounts, check if we got secureboot enabled
|
|
if !efi.GetSecureBoot() && len(internalUtils.ReadCMDLineArg("rd.immucore.securebootdisabled")) == 0 {
|
|
internalUtils.RebootOrWait("Secure boot is not enabled", nil)
|
|
}
|
|
return err
|
|
},
|
|
),
|
|
)
|
|
}
|
|
|
|
// UkiPivotToSysroot moves the rootfs to the sysroot and chroots into it
|
|
// Making the /sysroot the new rootfs with a tmpfs fs
|
|
// And moving all the mounts into it and all the files as well.
|
|
func (s *State) UkiPivotToSysroot(g *herd.Graph) error {
|
|
return g.Add(cnst.OpUkiPivotToSysroot,
|
|
herd.WithDeps(cnst.OpUkiBaseMounts),
|
|
herd.WithCallback(func(_ context.Context) error {
|
|
var err error
|
|
// Create the new sysroot and move to it
|
|
// We need the sysroot to NOT be of type rootfs, otherwise kubernetes stuff doesnt really work
|
|
internalUtils.Log.Debug().Str("what", s.path(cnst.UkiSysrootDir)).Msg("Creating sysroot dir")
|
|
err = os.MkdirAll(s.path(cnst.UkiSysrootDir), 0755) // #nosec G301 -- Sysroot needs to be 755 to be world readable
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("creating sysroot dir")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
|
|
// Mount a tmpfs under sysroot
|
|
internalUtils.Log.Debug().Msg("Mounting tmpfs on sysroot")
|
|
err = internalUtils.Mount("tmpfs", s.path(cnst.UkiSysrootDir), "tmpfs", 0, "")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("mounting tmpfs on sysroot")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
|
|
// Move all the dirs in root FS that are not a mountpoint to the new root via cp -R
|
|
rootDirs, err := os.ReadDir(s.Rootdir)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("reading rootdir content")
|
|
}
|
|
|
|
var mountPoints []string
|
|
for _, file := range rootDirs {
|
|
if file.Name() == cnst.UkiSysrootDir {
|
|
continue
|
|
}
|
|
if file.IsDir() {
|
|
path := file.Name()
|
|
fileInfo, err := os.Stat(s.path(path))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
parentPath := filepath.Dir(s.path(path))
|
|
parentInfo, err := os.Stat(parentPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// If the directory has the same device as its parent, it's not a mount point.
|
|
if fileInfo.Sys().(*syscall.Stat_t).Dev == parentInfo.Sys().(*syscall.Stat_t).Dev {
|
|
internalUtils.Log.Debug().Str("what", path).Msg("simple directory")
|
|
err = os.MkdirAll(filepath.Join(s.path(cnst.UkiSysrootDir), path), fileInfo.Mode())
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("what", filepath.Join(s.path(cnst.UkiSysrootDir), path)).Msg("mkdir")
|
|
return err
|
|
}
|
|
|
|
// Copy it over
|
|
out, err := internalUtils.CommandWithPath(fmt.Sprintf("cp -a %s %s", s.path(path), s.path(cnst.UkiSysrootDir)))
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("out", out).Str("what", s.path(path)).Str("where", s.path(cnst.UkiSysrootDir)).Msg("copying dir into sysroot")
|
|
}
|
|
continue
|
|
}
|
|
|
|
internalUtils.Log.Debug().Str("what", path).Msg("mount point")
|
|
mountPoints = append(mountPoints, s.path(path))
|
|
|
|
continue
|
|
}
|
|
|
|
info, _ := file.Info()
|
|
fileInfo, _ := os.Lstat(file.Name())
|
|
|
|
// Symlink
|
|
if fileInfo.Mode()&os.ModeSymlink != 0 {
|
|
target, err := os.Readlink(file.Name())
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read symlink: %w", err)
|
|
}
|
|
symlinkPath := s.path(filepath.Join(cnst.UkiSysrootDir, file.Name()))
|
|
err = os.Symlink(target, symlinkPath)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("from", target).Str("to", symlinkPath).Msg("Symlink")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
internalUtils.Log.Debug().Str("from", target).Str("to", symlinkPath).Msg("Symlinked file")
|
|
} else {
|
|
// If its a file in the root dir just copy it over
|
|
content, _ := os.ReadFile(s.path(file.Name()))
|
|
newFilePath := s.path(filepath.Join(cnst.UkiSysrootDir, file.Name()))
|
|
_ = os.WriteFile(newFilePath, content, info.Mode())
|
|
internalUtils.Log.Debug().Str("from", s.path(file.Name())).Str("to", newFilePath).Msg("Copied file")
|
|
}
|
|
}
|
|
|
|
// Now move the system mounts into the new dir
|
|
for _, d := range mountPoints {
|
|
newDir := filepath.Join(s.path(cnst.UkiSysrootDir), d)
|
|
if _, err := os.Stat(newDir); err != nil {
|
|
err = os.MkdirAll(newDir, 0755)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("what", newDir).Msg("mkdir")
|
|
}
|
|
}
|
|
|
|
err = internalUtils.Mount(d, newDir, "", syscall.MS_MOVE, "")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("what", d).Str("where", newDir).Msg("move mount")
|
|
continue
|
|
}
|
|
internalUtils.Log.Debug().Str("from", d).Str("to", newDir).Msg("Mount moved")
|
|
}
|
|
|
|
internalUtils.Log.Debug().Str("to", s.path(cnst.UkiSysrootDir)).Msg("Changing dir")
|
|
if err = syscall.Chdir(s.path(cnst.UkiSysrootDir)); err != nil {
|
|
internalUtils.Log.Err(err).Msg("chdir")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
|
|
internalUtils.Log.Debug().Str("what", s.path(cnst.UkiSysrootDir)).Str("where", "/").Msg("Moving mount")
|
|
if err = internalUtils.Mount(s.path(cnst.UkiSysrootDir), "/", "", syscall.MS_MOVE, ""); err != nil {
|
|
internalUtils.Log.Err(err).Msg("mount move")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
|
|
internalUtils.Log.Debug().Str("to", ".").Msg("Chrooting")
|
|
if err = syscall.Chroot("."); err != nil {
|
|
internalUtils.Log.Err(err).Msg("chroot")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
|
|
ext := "enter-initrd"
|
|
pcrErr := UKIExtendPCR(ext)
|
|
if pcrErr != nil {
|
|
internalUtils.Log.Err(pcrErr).Str("ext", ext).Msg("extend-pcr")
|
|
}
|
|
|
|
pcrErr = os.MkdirAll("/run/systemd", 0755) // #nosec G301 -- Original dir has this permissions
|
|
if pcrErr != nil {
|
|
internalUtils.Log.Err(pcrErr).Msg("Creating /run/systemd dir")
|
|
}
|
|
// This dir is created by systemd-stub and passed to the kernel as a cpio archive
|
|
// that gets mounted in the initial ramdisk where we run immucore from
|
|
// It contains the tpm public key and signatures of the current uki
|
|
out, pcrErr := internalUtils.CommandWithPath("cp /.extra/* /run/systemd/")
|
|
if pcrErr != nil {
|
|
internalUtils.Log.Err(pcrErr).Str("out", out).Msg("Copying extra files")
|
|
}
|
|
return err
|
|
}))
|
|
}
|
|
|
|
// UKIUdevDaemon launches the udevd daemon and triggers+settles in order to discover devices
|
|
// Needed if we expect to find devices by label...
|
|
func (s *State) UKIUdevDaemon(g *herd.Graph) error {
|
|
return g.Add(cnst.OpUkiUdev,
|
|
herd.WithDeps(cnst.OpUkiBaseMounts, cnst.OpUkiPivotToSysroot, cnst.OpUkiKernelModules),
|
|
herd.WithCallback(func(_ context.Context) error {
|
|
// Should probably figure out other udevd binaries....
|
|
var udevBin string
|
|
if _, err := os.Stat("/usr/lib/systemd/systemd-udevd"); !os.IsNotExist(err) {
|
|
udevBin = "/usr/lib/systemd/systemd-udevd"
|
|
}
|
|
cmd := fmt.Sprintf("%s --daemon", udevBin)
|
|
out, err := internalUtils.CommandWithPath(cmd)
|
|
internalUtils.Log.Debug().Str("out", out).Str("cmd", cmd).Msg("Udev daemon")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("Udev daemon")
|
|
return err
|
|
}
|
|
out, err = internalUtils.CommandWithPath("udevadm trigger")
|
|
internalUtils.Log.Debug().Str("out", out).Msg("Udev trigger")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("Udev trigger")
|
|
return err
|
|
}
|
|
|
|
out, err = internalUtils.CommandWithPath("udevadm settle")
|
|
internalUtils.Log.Debug().Str("out", out).Msg("Udev settle")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("Udev settle")
|
|
return err
|
|
}
|
|
return nil
|
|
}),
|
|
)
|
|
}
|
|
|
|
// UKILoadKernelModules loads kernel modules needed during uki boot to load the disks for.
|
|
// Mainly block devices and net devices
|
|
// probably others down the line.
|
|
func (s *State) UKILoadKernelModules(g *herd.Graph) error {
|
|
return g.Add(cnst.OpUkiKernelModules,
|
|
herd.WithDeps(cnst.OpUkiBaseMounts, cnst.OpUkiPivotToSysroot),
|
|
herd.WithCallback(func(_ context.Context) error {
|
|
drivers, err := kdetect.ProbeKernelModules("")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("Detecting needed modules")
|
|
}
|
|
drivers = append(drivers, cnst.GenericKernelDrivers()...)
|
|
internalUtils.Log.Debug().Strs("drivers", drivers).Msg("Detecting needed modules")
|
|
for _, driver := range drivers {
|
|
cmd := fmt.Sprintf("modprobe %s", driver)
|
|
out, err := internalUtils.CommandWithPath(cmd)
|
|
if err != nil {
|
|
internalUtils.Log.Debug().Err(err).Str("out", out).Msg("modprobe")
|
|
}
|
|
}
|
|
return nil
|
|
}),
|
|
)
|
|
}
|
|
|
|
// UKIUnlock tries to unlock the disks with the TPM policy.
|
|
func (s *State) UKIUnlock(g *herd.Graph, opts ...herd.OpOption) error {
|
|
return g.Add(cnst.OpUkiKcrypt, append(opts, herd.WithCallback(func(_ context.Context) error {
|
|
// Set full path on uki to get all the binaries
|
|
if !state.EfiBootFromInstall(internalUtils.Log) {
|
|
internalUtils.Log.Debug().Msg("Not unlocking disks as we think we are booting from removable media")
|
|
return nil
|
|
}
|
|
_ = os.Setenv("PATH", "/usr/bin:/usr/sbin:/bin:/sbin")
|
|
internalUtils.Log.Debug().Msg("Will now try to unlock partitions")
|
|
err := kcrypt.UnlockAllWithLogger(true, internalUtils.KLog)
|
|
if err != nil {
|
|
internalUtils.RebootOrWait("Unlocking partitions failed", err)
|
|
}
|
|
return nil
|
|
}))...)
|
|
}
|
|
|
|
// UKIMountLiveCd tries to mount the livecd if we are booting from one into /run/initramfs/live
|
|
// to mimic the same behavior as the livecd on non-uki boot.
|
|
func (s *State) UKIMountLiveCd(g *herd.Graph, opts ...herd.OpOption) error {
|
|
return g.Add(cnst.OpUkiMountLivecd, append(opts, herd.WithCallback(func(_ context.Context) error {
|
|
// If we are booting from Install Media
|
|
if state.EfiBootFromInstall(internalUtils.Log) {
|
|
internalUtils.Log.Debug().Msg("Not mounting livecd as we think we are booting from removable media")
|
|
return nil
|
|
}
|
|
|
|
err := os.MkdirAll(s.path(cnst.UkiLivecdMountPoint), 0755)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg(fmt.Sprintf("Creating %s", cnst.UkiLivecdMountPoint))
|
|
return err
|
|
}
|
|
err = os.MkdirAll(s.path(cnst.UkiIsoBaseTree), 0755)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg(fmt.Sprintf("Creating %s", cnst.UkiIsoBaseTree))
|
|
return nil
|
|
}
|
|
|
|
// Select the correct device to mount
|
|
// Try to find the CDROM device by label /dev/disk/by-label/UKI_ISO_INSTALL
|
|
// try a couple of times as the udev daemon can take a bit of time to populate the devices
|
|
var cdrom string
|
|
|
|
for i := 0; i < 5; i++ {
|
|
_, err = os.Stat(cnst.UkiLivecdPath)
|
|
// if found, set it
|
|
if err == nil {
|
|
cdrom = cnst.UkiLivecdPath
|
|
break
|
|
}
|
|
|
|
internalUtils.Log.Debug().Msg(fmt.Sprintf("No media with label found at %s", cnst.UkiLivecdPath))
|
|
out, _ := internalUtils.CommandWithPath("ls -ltra /dev/disk/by-label/")
|
|
internalUtils.Log.Debug().Str("out", out).Msg("contents of /dev/disk/by-label/")
|
|
time.Sleep(time.Duration(i) * time.Second)
|
|
}
|
|
|
|
// Fallback to try to get the /dev/sr0 device directly, no retry as that wont take time to appear
|
|
if cdrom == "" {
|
|
_, err = os.Stat(cnst.UkiDefaultcdrom)
|
|
if err == nil {
|
|
cdrom = cnst.UkiDefaultcdrom
|
|
} else {
|
|
internalUtils.Log.Debug().Msg(fmt.Sprintf("No media found at %s", cnst.UkiDefaultcdrom))
|
|
}
|
|
}
|
|
|
|
// Mount it
|
|
if cdrom != "" {
|
|
err = internalUtils.Mount(cdrom, s.path(cnst.UkiLivecdMountPoint), cnst.UkiDefaultcdromFsType, syscall.MS_RDONLY, "")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg(fmt.Sprintf("Mounting %s", cdrom))
|
|
return err
|
|
}
|
|
internalUtils.Log.Debug().Msg(fmt.Sprintf("Mounted %s", cdrom))
|
|
|
|
// This needs the loop module to be inserted in the kernel!
|
|
cmd := fmt.Sprintf("losetup --show -f %s", s.path(filepath.Join(cnst.UkiLivecdMountPoint, cnst.UkiIsoBootImage)))
|
|
out, err := internalUtils.CommandWithPath(cmd)
|
|
loop := strings.TrimSpace(out)
|
|
|
|
if err != nil || loop == "" {
|
|
internalUtils.Log.Err(err).Str("out", out).Msg(cmd)
|
|
return err
|
|
}
|
|
|
|
err = internalUtils.Mount(loop, s.path(cnst.UkiIsoBaseTree), cnst.UkiDefaultEfiimgFsType, syscall.MS_RDONLY, "")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg(fmt.Sprintf("Mounting %s into %s", loop, s.path(cnst.UkiIsoBaseTree)))
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
internalUtils.Log.Debug().Msg("No livecd/install media found")
|
|
return nil
|
|
}))...)
|
|
}
|
|
|
|
// UKIBootInitDagStep tries to launch /sbin/init in root and pass over the system
|
|
// booting to the real init process
|
|
// Drops to emergency if not able to. Panic if it cant even launch emergency.
|
|
func (s *State) UKIBootInitDagStep(g *herd.Graph) error {
|
|
return g.Add(cnst.OpUkiInit,
|
|
herd.WeakDeps,
|
|
herd.WithWeakDeps(cnst.OpRootfsHook, cnst.OpInitramfsHook, cnst.OpWriteFstab),
|
|
herd.WithCallback(func(_ context.Context) error {
|
|
var err error
|
|
|
|
ext := "leave-initrd"
|
|
err = UKIExtendPCR(ext)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("ext", ext).Msg("extend-pcr")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
|
|
internalUtils.Log.Debug().Str("what", s.path(s.Rootdir)).Msg("Mount / RO")
|
|
if err = internalUtils.Mount("", s.path(s.Rootdir), "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "ro"); err != nil {
|
|
internalUtils.Log.Err(err).Msg("Mount / RO")
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
|
|
// Print dag before exit, otherwise its never printed as we never exit the program
|
|
internalUtils.Log.Info().Msg(s.WriteDAG(g))
|
|
internalUtils.Log.Debug().Msg("Executing init callback!")
|
|
if err := syscall.Exec("/sbin/init", []string{"/sbin/init"}, os.Environ()); err != nil {
|
|
internalUtils.DropToEmergencyShell()
|
|
}
|
|
return nil
|
|
}))
|
|
}
|
|
|
|
// UKIMountESPPartition tries to mount the ESP into /efi
|
|
// Doesnt matter if it fails, its just for niceness.
|
|
func (s *State) UKIMountESPPartition(g *herd.Graph, opts ...herd.OpOption) error {
|
|
return g.Add("mount-esp", append(opts, herd.WithCallback(func(_ context.Context) error {
|
|
if !state.EfiBootFromInstall(internalUtils.Log) {
|
|
internalUtils.Log.Debug().Msg("Not mounting ESP as we think we are booting from removable media")
|
|
return nil
|
|
}
|
|
cmd := "lsblk -J -o NAME,PARTTYPE"
|
|
out, err := internalUtils.CommandWithPath(cmd)
|
|
internalUtils.Log.Debug().Str("out", out).Str("cmd", cmd).Msg("ESP")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("ESP")
|
|
return nil
|
|
}
|
|
|
|
lsblk := &schema.LsblkOutput{}
|
|
err = json.Unmarshal([]byte(out), lsblk)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
for _, bd := range lsblk.Blockdevices {
|
|
for _, cd := range bd.Children {
|
|
if strings.TrimSpace(cd.Parttype) == "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" {
|
|
// This is the ESP device
|
|
device := filepath.Join("/dev", cd.Name)
|
|
if !internalUtils.IsMounted(device) {
|
|
fstab, err := op.MountOPWithFstab(
|
|
device,
|
|
s.path("/efi"),
|
|
"vfat",
|
|
[]string{
|
|
"ro",
|
|
}, 5*time.Second,
|
|
)
|
|
for _, f := range fstab {
|
|
s.fstabs = append(s.fstabs, f)
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}))...)
|
|
}
|
|
|
|
// ExtractCerts extracts the public keys from the EFI variables and writes them to /run/verity.d
|
|
// This is used by the sysextensions to verify the signatures of the images
|
|
// TODO: A public cert could be provided in the config that its used for this, so we should
|
|
// expand this in the future to also extract that cert during boot from the config into the /run/verity.d
|
|
func (s *State) ExtractCerts(g *herd.Graph, opts ...herd.OpOption) error {
|
|
return g.Add(cnst.OpUkiExtractCerts, append(opts, herd.WithCallback(func(_ context.Context) error {
|
|
// Get all the full certs
|
|
certs, err := signatures.GetAllFullCerts()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.MkdirAll(s.path(cnst.VerityCertDir), 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Write all certs in x509 PEM format to /run/verity.d/ for sysextensions to verify against
|
|
for i, cert := range certs.PK {
|
|
publicKeyBlock := pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: cert.Raw,
|
|
}
|
|
publicKeyPem := pem.EncodeToMemory(&publicKeyBlock)
|
|
err := os.WriteFile(filepath.Join(s.path(cnst.VerityCertDir), fmt.Sprintf("PK%d.crt", i)), publicKeyPem, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for i, cert := range certs.KEK {
|
|
publicKeyBlock := pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: cert.Raw,
|
|
}
|
|
publicKeyPem := pem.EncodeToMemory(&publicKeyBlock)
|
|
err := os.WriteFile(filepath.Join(s.path(cnst.VerityCertDir), fmt.Sprintf("KEK%d.crt", i)), publicKeyPem, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for i, cert := range certs.DB {
|
|
publicKeyBlock := pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: cert.Raw,
|
|
}
|
|
publicKeyPem := pem.EncodeToMemory(&publicKeyBlock)
|
|
err := os.WriteFile(filepath.Join(s.path(cnst.VerityCertDir), fmt.Sprintf("DB%d.crt", i)), publicKeyPem, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}))...)
|
|
}
|
|
|
|
// TransitionSysext is a workaround for upgrades from 3.3.x to 3.4.x
|
|
// In 3.3.x we had the extensions in the EFI dir directly, under /efi/EFI/kairos/{active,passive}.efi.extra.d/
|
|
// In 3.4.x we moved them to /var/lib/kairos/extensions/ for generic and for enabled ones to /var/lib/kairos/extensions/{active,passive}/
|
|
// This is a workaround to move the extensions from the old location to the new one to help with upgrades
|
|
// The order is:
|
|
// Check both active and passive dirs
|
|
// If something is found, move it to the new location at /var/lib/kairos/extensions/
|
|
// Enable it by creating a softlink from /var/lib/kairos/extensions/{active,passive}/EXTENSION to /var/lib/kairos/extensions/EXTENSION
|
|
// Remove it from the old location
|
|
func (s *State) TransitionSysext(g *herd.Graph, opts ...herd.OpOption) error {
|
|
return g.Add(cnst.OpUkiTransitionSysext, append(opts, herd.WithCallback(func(_ context.Context) error {
|
|
if !state.EfiBootFromInstall(internalUtils.Log) {
|
|
internalUtils.Log.Debug().Msg("Not transitioning sysext as we think we are booting from removable media")
|
|
return nil
|
|
}
|
|
|
|
// Check or create target dir
|
|
if _, err := os.Stat(s.path("/var/lib/kairos/extensions")); os.IsNotExist(err) {
|
|
err = os.MkdirAll(s.path("/var/lib/kairos/extensions"), 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// We have to remount the EFI partition as RW to be able to move the files
|
|
err := syscall.Mount(cnst.EfiDir, cnst.EfiDir, cnst.UkiDefaultEfiimgFsType, syscall.MS_REMOUNT, "rw")
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("Mounting EFI partition")
|
|
return err
|
|
}
|
|
// We need to remount it as RO after we are done
|
|
defer func(source string, target string, fstype string, flags uintptr, data string) {
|
|
err := syscall.Mount(source, target, fstype, flags, data)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("Mounting EFI partition as RO")
|
|
} else {
|
|
internalUtils.Log.Debug().Msg("Remounting EFI partition as RO")
|
|
}
|
|
}(cnst.EfiDir, cnst.EfiDir, cnst.UkiDefaultEfiimgFsType, syscall.MS_REMOUNT|syscall.MS_RDONLY, "")
|
|
|
|
for _, bootState := range []string{"active", "passive"} {
|
|
dir := s.path(fmt.Sprintf("/efi/EFI/kairos/%s.efi.extra.d/", bootState))
|
|
internalUtils.Log.Debug().Str("dir", dir).Msg("Checking for sysextensions")
|
|
targetDir := s.path(fmt.Sprintf("/var/lib/kairos/extensions/%s", bootState))
|
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
internalUtils.Log.Debug().Str("dir", dir).Msg("No sysextensions found")
|
|
continue
|
|
}
|
|
// Create target dirs as well
|
|
if _, err := os.Stat(targetDir); os.IsNotExist(err) {
|
|
err = os.MkdirAll(targetDir, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Move the files over to the main extensions dir
|
|
files, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Msg("Reading dir")
|
|
continue
|
|
}
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
// Skip directories
|
|
continue
|
|
}
|
|
source := filepath.Join(dir, file.Name())
|
|
target := filepath.Join(s.path("/var/lib/kairos/extensions"), file.Name())
|
|
// Copy the file to the main extensions dir
|
|
internalUtils.Log.Debug().Str("source", source).Str("target", target).Msg("Moving sysextension")
|
|
err = internalUtils.Copy(source, target)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("source", source).Str("target", target).Msg("Moving sysextension")
|
|
continue
|
|
}
|
|
internalUtils.Log.Debug().Str("source", source).Str("target", target).Msg("Moved sysextension")
|
|
|
|
internalUtils.Log.Debug().Str("target", target).Str("to", s.path(filepath.Join("/var/lib/kairos/extensions", bootState, file.Name()))).Msg("Creating symlink")
|
|
// Create a symlink to the new location
|
|
err = os.Symlink(target, s.path(filepath.Join("/var/lib/kairos/extensions", bootState, file.Name())))
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("target", target).Str("to", s.path(filepath.Join("/var/lib/kairos/extensions", bootState, file.Name()))).Msg("Creating symlink")
|
|
continue
|
|
}
|
|
// If no errors at this point, remove the original sysext
|
|
err = os.Remove(source)
|
|
if err != nil {
|
|
internalUtils.Log.Err(err).Str("source", source).Msg("Removing old sysext")
|
|
continue
|
|
}
|
|
internalUtils.Log.Debug().Str("source", source).Msg("Done sysext")
|
|
}
|
|
}
|
|
return nil
|
|
}))...)
|
|
}
|