Add a UKI transition step

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>
This commit is contained in:
Itxaka
2025-04-09 16:39:44 +02:00
parent 367ab5610e
commit c59c656fbe
3 changed files with 105 additions and 1 deletions

View File

@@ -102,6 +102,7 @@ const (
OpUkiKcrypt = "uki-unlock"
OpUkiMountLivecd = "mount-livecd"
OpUkiExtractCerts = "extract-certs"
OpUkiTransitionSysext = "uki-transition-sysext"
OpUkiCopySysExtensions = "enable-sysextensions"
UkiLivecdMountPoint = "/run/initramfs/live"
UkiIsoBaseTree = "/run/rootfsbase"
@@ -120,4 +121,5 @@ const (
DestSysExtDir = "/run/extensions"
VerityCertDir = "/run/verity.d/"
SysextDefaultPolicy = "--image-policy=\"root=verity+signed+absent:usr=verity+signed+absent\""
EfiDir = "/efi"
)

View File

@@ -63,10 +63,11 @@ func RegisterUKI(s *state.State, g *herd.Graph) error {
// Depends on mount binds as that usually mounts COS_PERSISTENT
s.LogIfError(s.MountCustomBindsDagStep(g, herd.WeakDeps), "custom binds mount")
s.LogIfError(s.TransitionSysext(g, herd.WithWeakDeps(cnst.OpMountBind)), "uki transition sysextensions")
// Copy any sysextensions found under cnst.SourceSysExtDir into cnst.DestSysExtDir so its loaded by systemd automatically on start
// always after cnst.OpMountBind stage so we have a persistent cnst.DestSysExtDir
// Note that the loading of the extensions is done by systemd with the systemd-sysext service
s.LogIfError(s.EnableSysExtensions(g, herd.WithWeakDeps(cnst.OpMountBind)), "enable sysextensions")
s.LogIfError(s.EnableSysExtensions(g, herd.WithWeakDeps(cnst.OpMountBind, cnst.OpUkiTransitionSysext)), "enable sysextensions")
// run initramfs stage
s.LogIfError(s.InitramfsStageDagStep(g, herd.WeakDeps, herd.WithDeps(cnst.OpMountBind, cnst.OpUkiCopySysExtensions)), "uki initramfs")

View File

@@ -561,6 +561,10 @@ func (s *State) UKIMountESPPartition(g *herd.Graph, opts ...herd.OpOption) error
}))...)
}
// 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
@@ -611,3 +615,100 @@ func (s *State) ExtractCerts(g *herd.Graph, opts ...herd.OpOption) error {
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
}))...)
}