Skip to content

Commit

Permalink
feat: uki iso
Browse files Browse the repository at this point in the history
Switch to using sd-boot for newer iso.
New profile `legacy-iso` added to support booting with grub.

Part of: #9633

Signed-off-by: Noel Georgi <[email protected]>
  • Loading branch information
frezbo committed Jan 7, 2025
1 parent 32c67c2 commit 8f1aff7
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 77 deletions.
10 changes: 7 additions & 3 deletions internal/pkg/secureboot/uki/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,14 @@ func (builder *Builder) generatePCRPublicKey() error {
}

func (builder *Builder) generateKernel() error {
path := filepath.Join(builder.scratchDir, "kernel")
path := builder.KernelPath

if err := builder.peSigner.Sign(builder.KernelPath, path); err != nil {
return err
if builder.peSigner != nil {
path = filepath.Join(builder.scratchDir, "kernel")

if err := builder.peSigner.Sign(builder.KernelPath, path); err != nil {
return err
}
}

builder.sections = append(builder.sections,
Expand Down
63 changes: 60 additions & 3 deletions internal/pkg/secureboot/uki/uki.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
"log"
"os"

"github.com/siderolabs/go-copy/copy"

"github.com/siderolabs/talos/internal/pkg/secureboot"
"github.com/siderolabs/talos/internal/pkg/secureboot/measure"
"github.com/siderolabs/talos/internal/pkg/secureboot/pesign"
Expand Down Expand Up @@ -67,14 +69,14 @@ type Builder struct {
unsignedUKIPath string
}

// Build the UKI file.
// BuildSignedUKI builds the signed UKI file.
//
// Build process is as follows:
// BuildSignedUKI process is as follows:
// - sign the sd-boot EFI binary, and write it to the OutSdBootPath
// - build ephemeral sections (uname, os-release), and other proposed sections
// - measure sections, generate signature, and append to the list of sections
// - assemble the final UKI file starting from sd-stub and appending generated section.
func (builder *Builder) Build(printf func(string, ...any)) error {
func (builder *Builder) BuildSignedUKI(printf func(string, ...any)) error {
var err error

builder.scratchDir, err = os.MkdirTemp("", "talos-uki")
Expand Down Expand Up @@ -133,3 +135,58 @@ func (builder *Builder) Build(printf func(string, ...any)) error {
// sign the UKI file
return builder.peSigner.Sign(builder.unsignedUKIPath, builder.OutUKIPath)
}

// Build the unsigned UKI file.
//
// Build process is as follows:
// - build ephemeral sections (uname, os-release), and other proposed sections
// - assemble the final UKI file starting from sd-stub and appending generated section.
func (builder *Builder) Build(printf func(string, ...any)) error {
var err error

builder.scratchDir, err = os.MkdirTemp("", "talos-uki")
if err != nil {
return err
}

defer func() {
if err = os.RemoveAll(builder.scratchDir); err != nil {
log.Printf("failed to remove scratch dir: %v", err)
}
}()

if err := copy.File(builder.SdBootPath, builder.OutSdBootPath); err != nil {
return err
}

printf("generating UKI sections")

// generate and build list of all sections
for _, generateSection := range []func() error{
builder.generateOSRel,
builder.generateCmdline,
builder.generateInitrd,
builder.generateSplash,
builder.generateUname,
builder.generateSBAT,
// append kernel last to account for decompression
builder.generateKernel,
} {
if err = generateSection(); err != nil {
return fmt.Errorf("error generating sections: %w", err)
}
}

printf("assembling UKI")

// assemble the final UKI file
if err = builder.assemble(); err != nil {
return fmt.Errorf("error assembling UKI: %w", err)
}

if err := copy.File(builder.unsignedUKIPath, builder.OutUKIPath); err != nil {
return err
}

return nil
}
52 changes: 30 additions & 22 deletions pkg/imager/imager.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,23 +103,18 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
Status: reporter.StatusSucceeded,
})

// 4. Build UKI if Secure Boot is enabled.
if i.prof.SecureBootEnabled() {
if err = i.buildUKI(ctx, report); err != nil {
return "", err
}
}

// 5. Build the output.
outputAssetPath = filepath.Join(outputPath, i.prof.OutputPath())

switch i.prof.Output.Kind {
case profile.OutKindISO:
err = i.outISO(ctx, outputAssetPath, report)
case profile.OutKindISOLegacy:
err = i.outISOLegacy(ctx, outputAssetPath, report)
case profile.OutKindKernel:
err = i.outKernel(outputAssetPath, report)
case profile.OutKindUKI:
err = i.outUKI(outputAssetPath, report)
err = i.outUKI(ctx, outputAssetPath, report)
case profile.OutKindInitramfs:
err = i.outInitramfs(outputAssetPath, report)
case profile.OutKindCmdline:
Expand Down Expand Up @@ -406,17 +401,12 @@ func (i *Imager) buildCmdline() error {
func (i *Imager) buildUKI(ctx context.Context, report *reporter.Reporter) error {
printf := progressPrintf(report, reporter.Update{Message: "building UKI...", Status: reporter.StatusRunning})

i.sdBootPath = filepath.Join(i.tempDir, "systemd-boot.efi.signed")
i.ukiPath = filepath.Join(i.tempDir, "vmlinuz.efi.signed")

pcrSigner, err := i.prof.Input.SecureBoot.PCRSigner.GetSigner(ctx)
if err != nil {
return fmt.Errorf("failed to get PCR signer: %w", err)
}
i.sdBootPath = filepath.Join(i.tempDir, "systemd-boot.efi")
i.ukiPath = filepath.Join(i.tempDir, "vmlinuz.efi")

securebootSigner, err := i.prof.Input.SecureBoot.SecureBootSigner.GetSigner(ctx)
if err != nil {
return fmt.Errorf("failed to get SecureBoot signer: %w", err)
if i.prof.SecureBootEnabled() {
i.sdBootPath = filepath.Join(i.tempDir, "systemd-boot.efi.signed")
i.ukiPath = filepath.Join(i.tempDir, "vmlinuz.efi.signed")
}

builder := uki.Builder{
Expand All @@ -428,14 +418,32 @@ func (i *Imager) buildUKI(ctx context.Context, report *reporter.Reporter) error
InitrdPath: i.initramfsPath,
Cmdline: i.cmdline,

SecureBootSigner: securebootSigner,
PCRSigner: pcrSigner,

OutSdBootPath: i.sdBootPath,
OutUKIPath: i.ukiPath,
}

if err := builder.Build(printf); err != nil {
if i.prof.SecureBootEnabled() {
pcrSigner, err := i.prof.Input.SecureBoot.PCRSigner.GetSigner(ctx)
if err != nil {
return fmt.Errorf("failed to get PCR signer: %w", err)
}

securebootSigner, err := i.prof.Input.SecureBoot.SecureBootSigner.GetSigner(ctx)
if err != nil {
return fmt.Errorf("failed to get SecureBoot signer: %w", err)
}

builder.PCRSigner = pcrSigner
builder.SecureBootSigner = securebootSigner
}

buildFunc := builder.Build

if i.prof.SecureBootEnabled() {
buildFunc = builder.BuildSignedUKI
}

if err := buildFunc(printf); err != nil {
return err
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/imager/iso/uefi.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,10 @@ func CreateUEFI(printf func(string, ...any), options UEFIOptions) error {
return err
}

if err := copy.File(options.UKISigningCertDerPath, filepath.Join(options.ScratchDir, "EFI/keys/uki-signing-cert.der")); err != nil {
return err
if options.UKISigningCertDerPath != "" {
if err := copy.File(options.UKISigningCertDerPath, filepath.Join(options.ScratchDir, "EFI/keys/uki-signing-cert.der")); err != nil {
return err
}
}

if options.PlatformKeyPath != "" {
Expand Down
103 changes: 68 additions & 35 deletions pkg/imager/out.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ func (i *Imager) outKernel(path string, report *reporter.Reporter) error {
return nil
}

func (i *Imager) outUKI(path string, report *reporter.Reporter) error {
func (i *Imager) outUKI(ctx context.Context, path string, report *reporter.Reporter) error {
if err := i.buildUKI(ctx, report); err != nil {
return err
}

printf := progressPrintf(report, reporter.Update{Message: "copying kernel...", Status: reporter.StatusRunning})

if err := utils.CopyFiles(printf, utils.SourceDestination(i.ukiPath, path)); err != nil {
Expand All @@ -85,14 +89,16 @@ func (i *Imager) outCmdline(path string) error {

//nolint:gocyclo
func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Reporter) error {
// build the UKI first, as it is needed for the ISO
if err := i.buildUKI(ctx, report); err != nil {
return err
}

printf := progressPrintf(report, reporter.Update{Message: "building ISO...", Status: reporter.StatusRunning})

scratchSpace := filepath.Join(i.tempDir, "iso")

var (
err error
zeroContainerAsset profile.ContainerAsset
)
var zeroContainerAsset profile.ContainerAsset

if i.prof.Input.ImageCache != zeroContainerAsset {
if err := os.MkdirAll(filepath.Join(scratchSpace, "imagecache"), 0o755); err != nil {
Expand All @@ -104,12 +110,25 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor
}
}

options := iso.UEFIOptions{
UKIPath: i.ukiPath,
SDBootPath: i.sdBootPath,

Arch: i.prof.Arch,
Version: i.prof.Version,

ScratchDir: scratchSpace,
OutPath: path,
}

if i.prof.SecureBootEnabled() {
isoOptions := pointer.SafeDeref(i.prof.Output.ISOOptions)

options.SDBootSecureBootEnrollKeys = isoOptions.SDBootEnrollKeys.String()

var signer pesign.CertificateSigner

signer, err = i.prof.Input.SecureBoot.SecureBootSigner.GetSigner(ctx)
signer, err := i.prof.Input.SecureBoot.SecureBootSigner.GetSigner(ctx)
if err != nil {
return fmt.Errorf("failed to get SecureBoot signer: %w", err)
}
Expand All @@ -120,24 +139,7 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor
return fmt.Errorf("failed to write uki.der: %w", err)
}

options := iso.UEFIOptions{
UKIPath: i.ukiPath,
SDBootPath: i.sdBootPath,

SDBootSecureBootEnrollKeys: isoOptions.SDBootEnrollKeys.String(),

UKISigningCertDerPath: derCrtPath,

PlatformKeyPath: i.prof.Input.SecureBoot.PlatformKeyPath,
KeyExchangeKeyPath: i.prof.Input.SecureBoot.KeyExchangeKeyPath,
SignatureKeyPath: i.prof.Input.SecureBoot.SignatureKeyPath,

Arch: i.prof.Arch,
Version: i.prof.Version,

ScratchDir: scratchSpace,
OutPath: path,
}
options.UKISigningCertDerPath = derCrtPath

if i.prof.Input.SecureBoot.PlatformKeyPath == "" {
report.Report(reporter.Update{Message: "generating SecureBoot database...", Status: reporter.StatusRunning})
Expand Down Expand Up @@ -178,21 +180,43 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor
options.KeyExchangeKeyPath = i.prof.Input.SecureBoot.KeyExchangeKeyPath
options.SignatureKeyPath = i.prof.Input.SecureBoot.SignatureKeyPath
}
}

err = iso.CreateUEFI(printf, options)
} else {
err = iso.CreateGRUB(printf, iso.GRUBOptions{
KernelPath: i.prof.Input.Kernel.Path,
InitramfsPath: i.initramfsPath,
Cmdline: i.cmdline,
Version: i.prof.Version,
if err := iso.CreateUEFI(printf, options); err != nil {
return fmt.Errorf("failed to create UEFI ISO: %w", err)
}

report.Report(reporter.Update{Message: "ISO ready", Status: reporter.StatusSucceeded})

return nil
}

func (i *Imager) outISOLegacy(ctx context.Context, path string, report *reporter.Reporter) error {
printf := progressPrintf(report, reporter.Update{Message: "building legacy ISO...", Status: reporter.StatusRunning})

scratchSpace := filepath.Join(i.tempDir, "iso")

var zeroContainerAsset profile.ContainerAsset

if i.prof.Input.ImageCache != zeroContainerAsset {
if err := os.MkdirAll(filepath.Join(scratchSpace, "imagecache"), 0o755); err != nil {
return err
}

ScratchDir: scratchSpace,
OutPath: path,
})
if err := i.prof.Input.ImageCache.Extract(ctx, filepath.Join(scratchSpace, "imagecache"), i.prof.Arch, printf); err != nil {
return err
}
}

if err != nil {
if err := iso.CreateGRUB(printf, iso.GRUBOptions{
KernelPath: i.prof.Input.Kernel.Path,
InitramfsPath: i.initramfsPath,
Cmdline: i.cmdline,
Version: i.prof.Version,

ScratchDir: scratchSpace,
OutPath: path,
}); err != nil {
return err
}

Expand All @@ -201,7 +225,12 @@ func (i *Imager) outISO(ctx context.Context, path string, report *reporter.Repor
return nil
}

//nolint:gocyclo
func (i *Imager) outImage(ctx context.Context, path string, report *reporter.Reporter) error {
if err := i.buildUKI(ctx, report); err != nil {
return err
}

printf := progressPrintf(report, reporter.Update{Message: "creating disk image...", Status: reporter.StatusRunning})

if err := i.buildImage(ctx, path, printf); err != nil {
Expand Down Expand Up @@ -343,6 +372,10 @@ func (i *Imager) buildImage(ctx context.Context, path string, printf func(string

//nolint:gocyclo,cyclop
func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter.Reporter) error {
if err := i.buildUKI(ctx, report); err != nil {
return err
}

printf := progressPrintf(report, reporter.Update{Message: "building installer...", Status: reporter.StatusRunning})

baseInstallerImg, err := i.prof.Input.BaseInstaller.Pull(ctx, i.prof.Arch, printf)
Expand Down
8 changes: 8 additions & 0 deletions pkg/imager/profile/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ var Default = map[string]Profile{
OutFormat: OutFormatRaw,
},
},
"iso-legacy": {
Platform: constants.PlatformMetal,
SecureBoot: pointer.To(false),
Output: Output{
Kind: OutKindISOLegacy,
OutFormat: OutFormatRaw,
},
},
"secureboot-iso": {
Platform: constants.PlatformMetal,
SecureBoot: pointer.To(true),
Expand Down
Loading

0 comments on commit 8f1aff7

Please sign in to comment.