diff --git a/internal/pkg/secureboot/uki/generate.go b/internal/pkg/secureboot/uki/generate.go index a4d5623e0e..eb693a9755 100644 --- a/internal/pkg/secureboot/uki/generate.go +++ b/internal/pkg/secureboot/uki/generate.go @@ -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, diff --git a/internal/pkg/secureboot/uki/uki.go b/internal/pkg/secureboot/uki/uki.go index b917fc0d2e..416c2638b9 100644 --- a/internal/pkg/secureboot/uki/uki.go +++ b/internal/pkg/secureboot/uki/uki.go @@ -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" @@ -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") @@ -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 +} diff --git a/pkg/imager/imager.go b/pkg/imager/imager.go index b3b3e22ff9..e250e77441 100644 --- a/pkg/imager/imager.go +++ b/pkg/imager/imager.go @@ -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: @@ -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{ @@ -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 } diff --git a/pkg/imager/iso/uefi.go b/pkg/imager/iso/uefi.go index 15418a1a3c..d9eca8b9c6 100644 --- a/pkg/imager/iso/uefi.go +++ b/pkg/imager/iso/uefi.go @@ -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 != "" { diff --git a/pkg/imager/out.go b/pkg/imager/out.go index 5b837ec058..47a1317122 100644 --- a/pkg/imager/out.go +++ b/pkg/imager/out.go @@ -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 { @@ -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 { @@ -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) } @@ -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}) @@ -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 - ScratchDir: scratchSpace, - OutPath: path, - }) + if i.prof.Input.ImageCache != zeroContainerAsset { + if err := os.MkdirAll(filepath.Join(scratchSpace, "imagecache"), 0o755); err != nil { + return err + } + + 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 } diff --git a/pkg/imager/profile/default.go b/pkg/imager/profile/default.go index b780ff3ef8..d1c3553a56 100644 --- a/pkg/imager/profile/default.go +++ b/pkg/imager/profile/default.go @@ -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), diff --git a/pkg/imager/profile/input.go b/pkg/imager/profile/input.go index 0c891ffa6d..137f7f46b5 100644 --- a/pkg/imager/profile/input.go +++ b/pkg/imager/profile/input.go @@ -209,15 +209,15 @@ func (i *Input) FillDefaults(arch, version string, secureboot bool) { i.BaseInstaller.ImageRef = fmt.Sprintf("%s:%s", images.DefaultInstallerImageRepository, version) } - if secureboot { - if i.SDStub == zeroFileAsset { - i.SDStub.Path = fmt.Sprintf(constants.SDStubAssetPath, arch) - } + if i.SDStub == zeroFileAsset { + i.SDStub.Path = fmt.Sprintf(constants.SDStubAssetPath, arch) + } - if i.SDBoot == zeroFileAsset { - i.SDBoot.Path = fmt.Sprintf(constants.SDBootAssetPath, arch) - } + if i.SDBoot == zeroFileAsset { + i.SDBoot.Path = fmt.Sprintf(constants.SDBootAssetPath, arch) + } + if secureboot { if i.SecureBoot == nil { i.SecureBoot = &SecureBootAssets{} } diff --git a/pkg/imager/profile/output.go b/pkg/imager/profile/output.go index 715a69f58e..7229b4716e 100644 --- a/pkg/imager/profile/output.go +++ b/pkg/imager/profile/output.go @@ -60,6 +60,7 @@ const ( OutKindInitramfs // initramfs OutKindUKI // uki OutKindCmdline // cmdline + OutKindISOLegacy // iso-legacy ) // OutFormat is output format specification. diff --git a/pkg/imager/profile/outputkind_enumer.go b/pkg/imager/profile/outputkind_enumer.go index 0139428ab2..c51f762517 100644 --- a/pkg/imager/profile/outputkind_enumer.go +++ b/pkg/imager/profile/outputkind_enumer.go @@ -7,11 +7,11 @@ import ( "strings" ) -const _OutputKindName = "unknownisoimageinstallerkernelinitramfsukicmdline" +const _OutputKindName = "unknownisoimageinstallerkernelinitramfsukicmdlineiso-legacy" -var _OutputKindIndex = [...]uint8{0, 7, 10, 15, 24, 30, 39, 42, 49} +var _OutputKindIndex = [...]uint8{0, 7, 10, 15, 24, 30, 39, 42, 49, 59} -const _OutputKindLowerName = "unknownisoimageinstallerkernelinitramfsukicmdline" +const _OutputKindLowerName = "unknownisoimageinstallerkernelinitramfsukicmdlineiso-legacy" func (i OutputKind) String() string { if i < 0 || i >= OutputKind(len(_OutputKindIndex)-1) { @@ -32,9 +32,10 @@ func _OutputKindNoOp() { _ = x[OutKindInitramfs-(5)] _ = x[OutKindUKI-(6)] _ = x[OutKindCmdline-(7)] + _ = x[OutKindISOLegacy-(8)] } -var _OutputKindValues = []OutputKind{OutKindUnknown, OutKindISO, OutKindImage, OutKindInstaller, OutKindKernel, OutKindInitramfs, OutKindUKI, OutKindCmdline} +var _OutputKindValues = []OutputKind{OutKindUnknown, OutKindISO, OutKindImage, OutKindInstaller, OutKindKernel, OutKindInitramfs, OutKindUKI, OutKindCmdline, OutKindISOLegacy} var _OutputKindNameToValueMap = map[string]OutputKind{ _OutputKindName[0:7]: OutKindUnknown, @@ -53,6 +54,8 @@ var _OutputKindNameToValueMap = map[string]OutputKind{ _OutputKindLowerName[39:42]: OutKindUKI, _OutputKindName[42:49]: OutKindCmdline, _OutputKindLowerName[42:49]: OutKindCmdline, + _OutputKindName[49:59]: OutKindISOLegacy, + _OutputKindLowerName[49:59]: OutKindISOLegacy, } var _OutputKindNames = []string{ @@ -64,6 +67,7 @@ var _OutputKindNames = []string{ _OutputKindName[30:39], _OutputKindName[39:42], _OutputKindName[42:49], + _OutputKindName[49:59], } // OutputKindString retrieves an enum value from the enum constants string name. diff --git a/pkg/imager/profile/profile.go b/pkg/imager/profile/profile.go index 2cb244ed2d..fc705517f2 100644 --- a/pkg/imager/profile/profile.go +++ b/pkg/imager/profile/profile.go @@ -94,7 +94,7 @@ func (p *Profile) Validate() error { switch p.Output.Kind { case OutKindUnknown: return errors.New("unknown output kind") - case OutKindISO: + case OutKindISO, OutKindISOLegacy: // ISO supports all kinds of customization case OutKindCmdline: // cmdline supports all kinds of customization @@ -153,6 +153,8 @@ func (p *Profile) OutputPath() string { panic("unknown output kind") case OutKindISO: path += ".iso" + case OutKindISOLegacy: + path += "-legacy.iso" case OutKindImage: path += "." + p.Output.ImageOptions.DiskFormat.String() case OutKindInstaller: