mirror of
https://github.com/kairos-io/immucore.git
synced 2025-05-12 01:59:32 +00:00
Support copying sysextensions into final dir (#330)
This commit is contained in:
parent
75fe8a60e6
commit
c9924a3205
.github/workflows
internal
pkg
4
.github/workflows/unit-tests.yaml
vendored
4
.github/workflows/unit-tests.yaml
vendored
@ -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
|
||||
|
@ -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\""
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
}))...)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user