mirror of
https://github.com/kairos-io/immucore.git
synced 2025-04-27 19:05:40 +00:00
Implement generic sysext management (#459)
This commit is contained in:
parent
6eca30162d
commit
367ab5610e
@ -102,7 +102,7 @@ const (
|
||||
OpUkiKcrypt = "uki-unlock"
|
||||
OpUkiMountLivecd = "mount-livecd"
|
||||
OpUkiExtractCerts = "extract-certs"
|
||||
OpUkiCopySysExtensions = "copy-sysextensions"
|
||||
OpUkiCopySysExtensions = "enable-sysextensions"
|
||||
UkiLivecdMountPoint = "/run/initramfs/live"
|
||||
UkiIsoBaseTree = "/run/rootfsbase"
|
||||
UkiIsoBootImage = "efiboot.img"
|
||||
@ -116,7 +116,7 @@ const (
|
||||
PathAppend = "/usr/bin:/usr/sbin:/bin:/sbin"
|
||||
PATH = "PATH"
|
||||
DefaultPCR = 11
|
||||
SourceSysExtDir = "/.extra/sysext/"
|
||||
SourceSysExtDir = "/var/lib/kairos/extensions/"
|
||||
DestSysExtDir = "/run/extensions"
|
||||
VerityCertDir = "/run/verity.d/"
|
||||
SysextDefaultPolicy = "--image-policy=\"root=verity+signed+absent:usr=verity+signed+absent\""
|
||||
|
@ -57,6 +57,9 @@ func RegisterNormalBoot(s *state.State, g *herd.Graph) error {
|
||||
// Depends on mount binds as that usually mounts COS_PERSISTENT
|
||||
s.LogIfError(s.MountCustomBindsDagStep(g), "custom binds mount")
|
||||
|
||||
//
|
||||
s.LogIfError(s.EnableSysExtensions(g, herd.WithWeakDeps(cnst.OpMountBind)), "enable sysextensions")
|
||||
|
||||
// Write fstab file
|
||||
s.LogIfError(s.WriteFstabDagStep(g,
|
||||
herd.WithDeps(cnst.OpMountRoot, cnst.OpDiscoverState, cnst.OpLoadConfig),
|
||||
|
@ -66,7 +66,7 @@ func RegisterUKI(s *state.State, g *herd.Graph) error {
|
||||
// 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.CopySysExtensionsDagStep(g, herd.WithDeps(cnst.OpMountBind)), "copy sysextensions")
|
||||
s.LogIfError(s.EnableSysExtensions(g, herd.WithWeakDeps(cnst.OpMountBind)), "enable sysextensions")
|
||||
|
||||
// run initramfs stage
|
||||
s.LogIfError(s.InitramfsStageDagStep(g, herd.WeakDeps, herd.WithDeps(cnst.OpMountBind, cnst.OpUkiCopySysExtensions)), "uki initramfs")
|
||||
|
@ -397,6 +397,88 @@ func (s *State) MountCustomBindsDagStep(g *herd.Graph, opts ...herd.OpOption) er
|
||||
)...)
|
||||
}
|
||||
|
||||
// EnableSysExtensions softlinks extensions for the running state from /var/lib/kairos/extensions/$STATE to /run/extensions.
|
||||
// So when initramfs stage runs and enables systemd-sysext it can load the extensions for a given bootentry.
|
||||
func (s *State) EnableSysExtensions(g *herd.Graph, opts ...herd.OpOption) error {
|
||||
return g.Add(cnst.OpUkiCopySysExtensions, append(opts, herd.WithCallback(func(_ context.Context) error {
|
||||
// If uki and we are not booting from install media then do nothing
|
||||
if internalUtils.IsUKI() {
|
||||
if !state.EfiBootFromInstall(internalUtils.Log) {
|
||||
internalUtils.Log.Debug().Msg("Not copying sysextensions as we think we are booting from removable media")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Not that while we are using the source the /sysroot by using s.path
|
||||
// the destination dir is actually /run/extensions without any sysroot path appended
|
||||
// This is because after initramfs finishes it will be moved into the final sysroot automatically
|
||||
// and the one under /sysroot/run will be shadowed
|
||||
// create the /run/extensions dir if it does not exist
|
||||
if _, err := os.Stat(cnst.DestSysExtDir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(cnst.DestSysExtDir, 0755)
|
||||
if err != nil {
|
||||
internalUtils.Log.Err(err).Msg("Creating sysext dir")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// At this point the extensions dir should be available
|
||||
r, err := state.NewRuntimeWithLogger(internalUtils.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dir string
|
||||
|
||||
switch r.BootState {
|
||||
case state.Active:
|
||||
dir = fmt.Sprintf("%s/%s", cnst.SourceSysExtDir, "active")
|
||||
case state.Passive:
|
||||
dir = fmt.Sprintf("%s/%s", cnst.SourceSysExtDir, "passive")
|
||||
case state.Recovery:
|
||||
dir = fmt.Sprintf("%s/%s", cnst.SourceSysExtDir, "recovery")
|
||||
default:
|
||||
internalUtils.Log.Debug().Str("state", string(r.BootState)).Msg("Not copying sysextensions as we are not in a state that we know off")
|
||||
return nil
|
||||
|
||||
}
|
||||
// move to use dir with the full path from here so its simpler
|
||||
entries, err := os.ReadDir(s.path(dir))
|
||||
// We don't care if the dir does not exist
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we wanted to use a common dir for extensions used for both entries, here we would do something like:
|
||||
// commonEntries, _ := os.ReadDir(s.path(fmt.Sprintf("%s/%s", cnst.SourceSysExtDir, "common")))
|
||||
// entries = append(entries, commonEntries...)
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() && filepath.Ext(entry.Name()) == ".raw" {
|
||||
// If the file is a raw file, lets softlink it
|
||||
if internalUtils.IsUKI() {
|
||||
// Verify the signature
|
||||
output, err := internalUtils.CommandWithPath(fmt.Sprintf("systemd-dissect --validate %s %s", cnst.SysextDefaultPolicy, s.path(filepath.Join(dir, entry.Name()))))
|
||||
if err != nil {
|
||||
// If the file didn't pass the validation, we don't copy it
|
||||
internalUtils.Log.Warn().Str("src", s.path(filepath.Join(dir, entry.Name()))).Msg("Sysextension does not pass validation")
|
||||
internalUtils.Log.Debug().Err(err).Str("src", s.path(filepath.Join(dir, entry.Name()))).Str("output", output).Msg("Validating sysextension")
|
||||
continue
|
||||
}
|
||||
}
|
||||
// it has to link to the final dir after initramfs, so we avoid setting s.path here for the target
|
||||
err = os.Symlink(filepath.Join(dir, entry.Name()), filepath.Join(cnst.DestSysExtDir, entry.Name()))
|
||||
if err != nil {
|
||||
internalUtils.Log.Err(err).Msg("Creating symlink")
|
||||
return err
|
||||
}
|
||||
internalUtils.Log.Debug().Str("what", entry.Name()).Msg("Enabled sysextension")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}))...)
|
||||
}
|
||||
|
||||
// WriteFstabDagStep will add writing the final fstab file with all the mounts
|
||||
// Depends on everything but weak, so it will still try to write.
|
||||
func (s *State) WriteFstabDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -612,51 +611,3 @@ func (s *State) ExtractCerts(g *herd.Graph, opts ...herd.OpOption) error {
|
||||
return nil
|
||||
}))...)
|
||||
}
|
||||
|
||||
// CopySysExtensionsDagStep Copies extensions from the EFI partitions to the persistent one so they can be started.
|
||||
func (s *State) CopySysExtensionsDagStep(g *herd.Graph, opts ...herd.OpOption) error {
|
||||
return g.Add(cnst.OpUkiCopySysExtensions, append(opts, herd.WithCallback(func(_ context.Context) error {
|
||||
if !state.EfiBootFromInstall(internalUtils.Log) {
|
||||
internalUtils.Log.Debug().Msg("Not copying sysextensions as we think we are booting from removable media")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy the sys extensions to the rootfs
|
||||
// Remember that we use s.path for the destination as it adds the future /sysroot prefix
|
||||
// But for source, we are in initramfs so it should be without the prefix
|
||||
// return if the source or dest dir is not there
|
||||
if _, err := os.Stat(cnst.SourceSysExtDir); os.IsNotExist(err) {
|
||||
internalUtils.Log.Debug().Str("dir", cnst.SourceSysExtDir).Msg("No sysextensions found")
|
||||
return nil
|
||||
}
|
||||
if _, err := os.Stat(s.path(cnst.DestSysExtDir)); os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(s.path(cnst.DestSysExtDir), 0755)
|
||||
}
|
||||
err := filepath.WalkDir(s.path(cnst.SourceSysExtDir), func(_ string, d fs.DirEntry, err error) error {
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
src := filepath.Join(cnst.SourceSysExtDir, d.Name())
|
||||
dest := s.path(filepath.Join(cnst.DestSysExtDir, d.Name()))
|
||||
|
||||
// TODO: Use the policy from the system config if exists, otherwise drop to default?
|
||||
// This is to make it work also in non-uki envs where we might have a relaxed policy
|
||||
output, err2 := internalUtils.CommandWithPath(fmt.Sprintf("systemd-dissect --validate %s %s", cnst.SysextDefaultPolicy, src))
|
||||
if err2 != nil {
|
||||
// If the file didn't pass the validation, we don't copy it
|
||||
internalUtils.Log.Warn().Str("src", src).Msg("Sysextension does not pass validation")
|
||||
internalUtils.Log.Debug().Err(err2).Str("src", src).Str("output", output).Msg("Validating sysextension")
|
||||
return nil
|
||||
}
|
||||
// Copy the file to the sys-extensions directory
|
||||
err2 = internalUtils.Copy(src, dest)
|
||||
if err != nil {
|
||||
internalUtils.Log.Err(err2).Str("src", src).Str("dest", dest).Msg("Copying sysextension")
|
||||
}
|
||||
internalUtils.Log.Debug().Str("src", src).Str("dest", dest).Msg("Copied sysextension")
|
||||
|
||||
return err2
|
||||
})
|
||||
return err
|
||||
}))...)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user