mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
feat: extract kernel/initrd from uki for grub
Extract Kernel, Initrd and Commandline from UKI for GRUB installs. Fixes: #10191 Signed-off-by: Noel Georgi <git@frezbo.dev>
This commit is contained in:
parent
ff175b9fbd
commit
601cdccb97
@ -6,6 +6,7 @@ package grub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -16,6 +17,7 @@ import (
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/mount"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader/options"
|
||||
"github.com/siderolabs/talos/internal/pkg/partition"
|
||||
"github.com/siderolabs/talos/internal/pkg/uki"
|
||||
"github.com/siderolabs/talos/pkg/imager/utils"
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
)
|
||||
@ -68,18 +70,43 @@ func (c *Config) install(opts options.InstallOptions) (*options.InstallResult, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := utils.CopyFiles(
|
||||
opts.Printf,
|
||||
utils.SourceDestination(
|
||||
opts.BootAssets.KernelPath,
|
||||
filepath.Join(opts.MountPrefix, constants.BootMountPoint, string(c.Default), constants.KernelAsset),
|
||||
),
|
||||
utils.SourceDestination(
|
||||
opts.BootAssets.InitramfsPath,
|
||||
filepath.Join(opts.MountPrefix, constants.BootMountPoint, string(c.Default), constants.InitramfsAsset),
|
||||
),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
// if we have a kernel path, assume that the kernel and initramfs are available
|
||||
if _, err := os.Stat(opts.BootAssets.KernelPath); err == nil {
|
||||
if err := utils.CopyFiles(
|
||||
opts.Printf,
|
||||
utils.SourceDestination(
|
||||
opts.BootAssets.KernelPath,
|
||||
filepath.Join(opts.MountPrefix, constants.BootMountPoint, string(c.Default), constants.KernelAsset),
|
||||
),
|
||||
utils.SourceDestination(
|
||||
opts.BootAssets.InitramfsPath,
|
||||
filepath.Join(opts.MountPrefix, constants.BootMountPoint, string(c.Default), constants.InitramfsAsset),
|
||||
),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// if the kernel path does not exist, assume that the kernel and initramfs are in the UKI
|
||||
assetInfo, err := uki.Extract(opts.BootAssets.UKIPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer assetInfo.Close() //nolint:errcheck
|
||||
|
||||
if err := utils.CopyReader(
|
||||
opts.Printf,
|
||||
utils.ReaderDestination(
|
||||
assetInfo.Kernel,
|
||||
filepath.Join(opts.MountPrefix, constants.BootMountPoint, string(c.Default), constants.KernelAsset),
|
||||
),
|
||||
utils.ReaderDestination(
|
||||
assetInfo.Initrd,
|
||||
filepath.Join(opts.MountPrefix, constants.BootMountPoint, string(c.Default), constants.InitramfsAsset),
|
||||
),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.Put(c.Default, opts.Cmdline, opts.Version); err != nil {
|
||||
|
||||
59
internal/pkg/uki/internal/pe/extract.go
Normal file
59
internal/pkg/uki/internal/pe/extract.go
Normal file
@ -0,0 +1,59 @@
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package pe
|
||||
|
||||
import (
|
||||
"debug/pe"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// fileCloser is an interface that wraps the Close method.
|
||||
type fileCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// AssetInfo contains the kernel, initrd, and cmdline from a PE file.
|
||||
type AssetInfo struct {
|
||||
Kernel io.ReadSeeker
|
||||
Initrd io.ReadSeeker
|
||||
Cmdline io.ReadSeeker
|
||||
fileCloser
|
||||
}
|
||||
|
||||
// Extract extracts the kernel, initrd, and cmdline from a PE file.
|
||||
func Extract(ukiPath string) (assetInfo AssetInfo, err error) {
|
||||
peFile, err := pe.Open(ukiPath)
|
||||
if err != nil {
|
||||
return assetInfo, fmt.Errorf("failed to open PE file: %w", err)
|
||||
}
|
||||
|
||||
assetInfo.fileCloser = peFile
|
||||
|
||||
for _, section := range peFile.Sections {
|
||||
switch section.Name {
|
||||
case ".initrd":
|
||||
assetInfo.Initrd = section.Open()
|
||||
case ".cmdline":
|
||||
assetInfo.Cmdline = section.Open()
|
||||
case ".linux":
|
||||
assetInfo.Kernel = section.Open()
|
||||
}
|
||||
}
|
||||
|
||||
if assetInfo.Kernel == nil {
|
||||
return assetInfo, fmt.Errorf("kernel not found in PE file")
|
||||
}
|
||||
|
||||
if assetInfo.Initrd == nil {
|
||||
return assetInfo, fmt.Errorf("initrd not found in PE file")
|
||||
}
|
||||
|
||||
if assetInfo.Cmdline == nil {
|
||||
return assetInfo, fmt.Errorf("cmdline not found in PE file")
|
||||
}
|
||||
|
||||
return assetInfo, nil
|
||||
}
|
||||
@ -198,3 +198,8 @@ func (builder *Builder) BuildSigned(printf func(string, ...any)) error {
|
||||
// sign the UKI file
|
||||
return builder.peSigner.Sign(builder.unsignedUKIPath, builder.OutUKIPath)
|
||||
}
|
||||
|
||||
// Extract extracts the kernel, initrd, and cmdline from the UKI file.
|
||||
func Extract(ukiPath string) (asset pe.AssetInfo, err error) {
|
||||
return pe.Extract(ukiPath)
|
||||
}
|
||||
|
||||
@ -111,8 +111,10 @@ func (i *Imager) Execute(ctx context.Context, outputPath string, report *reporte
|
||||
if !needBuildUKI {
|
||||
return "", fmt.Errorf("UKI output is not supported in this Talos version")
|
||||
}
|
||||
case profile.OutKindISO, profile.OutKindImage, profile.OutKindInstaller:
|
||||
case profile.OutKindISO, profile.OutKindImage:
|
||||
needBuildUKI = needBuildUKI && i.prof.SecureBootEnabled()
|
||||
case profile.OutKindInstaller:
|
||||
needBuildUKI = needBuildUKI || quirks.New(i.prof.Version).UseSDBootForUEFI()
|
||||
case profile.OutKindCmdline, profile.OutKindKernel, profile.OutKindInitramfs:
|
||||
needBuildUKI = false
|
||||
case profile.OutKindUnknown:
|
||||
|
||||
@ -393,16 +393,24 @@ func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter
|
||||
|
||||
printf("generating artifacts layer")
|
||||
|
||||
if i.prof.SecureBootEnabled() {
|
||||
ukiPath := strings.TrimLeft(fmt.Sprintf(constants.UKIAssetPath, i.prof.Arch), "/")
|
||||
|
||||
quirks := quirks.New(i.prof.Version)
|
||||
|
||||
if i.prof.SecureBootEnabled() && !quirks.UseSDBootForUEFI() {
|
||||
ukiPath += ".signed" // support for older secureboot installers
|
||||
}
|
||||
|
||||
if quirks.UseSDBootForUEFI() {
|
||||
artifacts = append(artifacts,
|
||||
filemap.File{
|
||||
ImagePath: strings.TrimLeft(fmt.Sprintf(constants.UKIAssetPath, i.prof.Arch), "/"),
|
||||
SourcePath: i.ukiPath,
|
||||
},
|
||||
filemap.File{
|
||||
ImagePath: strings.TrimLeft(fmt.Sprintf(constants.SDBootAssetPath, i.prof.Arch), "/"),
|
||||
SourcePath: i.sdBootPath,
|
||||
},
|
||||
filemap.File{
|
||||
ImagePath: ukiPath,
|
||||
SourcePath: i.ukiPath,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
artifacts = append(artifacts,
|
||||
@ -417,7 +425,7 @@ func (i *Imager) outInstaller(ctx context.Context, path string, report *reporter
|
||||
)
|
||||
}
|
||||
|
||||
if !quirks.New(i.prof.Version).SupportsOverlay() {
|
||||
if !quirks.SupportsOverlay() {
|
||||
for _, extraArtifact := range []struct {
|
||||
sourcePath string
|
||||
imagePath string
|
||||
|
||||
@ -108,10 +108,6 @@ func (p *Profile) Validate() error {
|
||||
return errors.New("disk size is required for image output")
|
||||
}
|
||||
case OutKindInstaller:
|
||||
if !p.SecureBootEnabled() && len(p.Customization.ExtraKernelArgs) > 0 {
|
||||
return fmt.Errorf("customization of kernel args is not supported for %s output in !secureboot mode", p.Output.Kind)
|
||||
}
|
||||
|
||||
if len(p.Customization.MetaContents) > 0 {
|
||||
return fmt.Errorf("customization of meta partition is not supported for %s output", p.Output.Kind)
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ type CopyInstruction = ordered.Pair[string, string]
|
||||
|
||||
// SourceDestination returns a CopyInstruction that copies src to dest.
|
||||
func SourceDestination(src, dest string) CopyInstruction {
|
||||
return ordered.MakePair[string, string](src, dest)
|
||||
return ordered.MakePair(src, dest)
|
||||
}
|
||||
|
||||
// CopyFiles copies files according to the given instructions.
|
||||
@ -57,3 +57,44 @@ func CopyFiles(printf func(string, ...any), instructions ...CopyInstruction) err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyReaderInstruction describes a reader copy operation.
|
||||
type CopyReaderInstruction struct {
|
||||
Reader io.Reader
|
||||
Dest string
|
||||
}
|
||||
|
||||
// ReaderDestination returns a CopyReaderInstruction that copies reader to dest.
|
||||
func ReaderDestination(reader io.Reader, dest string) CopyReaderInstruction {
|
||||
return CopyReaderInstruction{Reader: reader, Dest: dest}
|
||||
}
|
||||
|
||||
// CopyReader copies readers according to the given instructions.
|
||||
func CopyReader(printf func(string, ...any), instructions ...CopyReaderInstruction) error {
|
||||
for _, instruction := range instructions {
|
||||
if err := func(instruction CopyReaderInstruction) error {
|
||||
dest := instruction.Dest
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printf("copying from io reader to %s", dest)
|
||||
|
||||
to, err := os.OpenFile(dest, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//nolint:errcheck
|
||||
defer to.Close()
|
||||
|
||||
_, err = io.Copy(to, instruction.Reader)
|
||||
|
||||
return err
|
||||
}(instruction); err != nil {
|
||||
return fmt.Errorf("error copying reader -> %s: %w", instruction.Dest, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -630,7 +630,7 @@ const (
|
||||
RootfsAsset = "rootfs.sqsh"
|
||||
|
||||
// UKIAsset defines a well known name for our UKI filename.
|
||||
UKIAsset = "vmlinuz.efi.signed"
|
||||
UKIAsset = "vmlinuz.efi"
|
||||
|
||||
// UKIAssetPath is the path to the UKI in the installer.
|
||||
UKIAssetPath = "/usr/install/%s/" + UKIAsset
|
||||
|
||||
@ -161,3 +161,16 @@ func (q Quirks) SupportsSELinux() bool {
|
||||
|
||||
return q.v.GTE(minVersionSELinux)
|
||||
}
|
||||
|
||||
// minVersionUseSDBootOnly is the version that supports only SDBoot for UEFI.
|
||||
var minTalosVersionUseSDBootOnly = semver.MustParse("1.10.0")
|
||||
|
||||
// UseSDBootForUEFI returns true if the Talos version supports only SDBoot for UEFI.
|
||||
func (q Quirks) UseSDBootForUEFI() bool {
|
||||
// if the version doesn't parse, we assume it's latest Talos
|
||||
if q.v == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return q.v.GTE(minTalosVersionUseSDBootOnly)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user