1
0
mirror of https://github.com/kairos-io/immucore.git synced 2025-05-12 01:59:32 +00:00

Support copying sysextensions into final dir ()

This commit is contained in:
Itxaka 2024-06-10 10:10:54 +02:00 committed by GitHub
parent 75fe8a60e6
commit c9924a3205
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 155 additions and 2 deletions
.github/workflows
internal
constants
utils
pkg

View File

@ -18,6 +18,8 @@ jobs:
with:
fetch-depth: 0
- uses: earthly/actions-setup@v1
with:
version: 0.8.12
- name: Run Lint checks
run: earthly +golint
unit-tests:
@ -32,6 +34,8 @@ jobs:
with:
fetch-depth: 0
- uses: earthly/actions-setup@v1
with:
version: 0.8.12
- name: Build
run: earthly +build --GO_VERSION=${{ matrix.go-version }}
- name: Run tests

View File

@ -1,6 +1,8 @@
package constants
import "errors"
import (
"errors"
)
func DefaultRWPaths() []string {
// Default RW_PATHS to mount if not override by the cos-layout.env file
@ -22,6 +24,7 @@ func GenericKernelDrivers() []string {
"ata_piix",
"cdrom",
"dm_mod",
"dm-verity",
"e1000",
"e1000e",
"ehci_hcd",
@ -96,6 +99,8 @@ const (
OpKcryptUpgrade = "upgrade-kcrypt"
OpUkiKcrypt = "uki-unlock"
OpUkiMountLivecd = "mount-livecd"
OpUkiExtractCerts = "extract-certs"
OpUkiCopySysExtensions = "copy-sysextensions"
UkiLivecdMountPoint = "/run/initramfs/live"
UkiIsoBaseTree = "/run/rootfsbase"
UkiIsoBootImage = "efiboot.img"
@ -109,4 +114,8 @@ const (
PathAppend = "/usr/bin:/usr/sbin:/bin:/sbin"
PATH = "PATH"
DefaultPCR = 11
SourceSysExtDir = "/.extra/sysext/"
DestSysExtDir = "/run/extensions"
VerityCertDir = "/run/verity.d/"
SysextDefaultPolicy = "--image-policy=\"root=verity+signed+absent:usr=verity+signed+absent\""
)

View File

@ -4,6 +4,7 @@ import (
"crypto/sha256"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
@ -298,3 +299,32 @@ func PCRExtend(pcr int, data []byte) error {
return nil
}
// Copy copies src to dst like the cp command.
func Copy(src, dst string) error {
if dst == src {
return os.ErrInvalid
}
srcF, err := os.Open(src)
if err != nil {
return err
}
defer srcF.Close()
info, err := srcF.Stat()
if err != nil {
return err
}
dstF, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
if err != nil {
return err
}
defer dstF.Close()
if _, err := io.Copy(dstF, srcF); err != nil {
return err
}
return nil
}

View File

@ -32,6 +32,9 @@ func RegisterUKI(s *state.State, g *herd.Graph) error {
// Mount ESP partition under efi if it exists
s.LogIfError(s.UKIMountESPPartition(g, herd.WithDeps(cnst.OpSentinel, cnst.OpUkiUdev)), "mount ESP partition")
// Extract EFI public certs for sysextensions validation
s.LogIfError(s.ExtractCerts(g, herd.WithDeps(cnst.OpSentinel, cnst.OpUkiUdev)), "extract certs")
// Mount cdrom under /run/initramfs/livecd and /run/rootfsbase for the efiboot.img contents
s.LogIfError(s.UKIMountLiveCd(g, herd.WithDeps(cnst.OpSentinel, cnst.OpUkiUdev)), "Mount LiveCD")
@ -60,8 +63,13 @@ 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")
// 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")
// run initramfs stage
s.LogIfError(s.InitramfsStageDagStep(g, herd.WeakDeps, herd.WithDeps(cnst.OpMountBind)), "uki initramfs")
s.LogIfError(s.InitramfsStageDagStep(g, herd.WeakDeps, herd.WithDeps(cnst.OpMountBind, cnst.OpUkiCopySysExtensions)), "uki initramfs")
s.LogIfError(s.WriteFstabDagStep(g,
herd.WithDeps(cnst.OpLoadConfig, cnst.OpCustomMounts, cnst.OpMountBind, cnst.OpOverlayMount),

View File

@ -3,7 +3,9 @@ package state
import (
"context"
"encoding/json"
"encoding/pem"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
@ -16,6 +18,7 @@ import (
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"
@ -557,3 +560,102 @@ func (s *State) UKIMountESPPartition(g *herd.Graph, opts ...herd.OpOption) error
return nil
}))...)
}
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
}))...)
}
// 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
}))...)
}