Skip to content

Commit

Permalink
feat: support generating unsigned UKIs
Browse files Browse the repository at this point in the history
Support generating unsigned UKI's.

Also plumb in support to `talosctl cluster create` to boot off UKI's.
This doesn't work yet as installer needs more work.

Signed-off-by: Noel Georgi <git@frezbo.dev>
  • Loading branch information
frezbo committed Jan 9, 2025
1 parent bbd6067 commit 43a1223
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 33 deletions.
6 changes: 6 additions & 0 deletions cmd/talosctl/cmd/mgmt/cluster/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ var (
nodeInitramfsPath string
nodeISOPath string
nodeUSBPath string
nodeUKIPath string
nodeDiskImagePath string
nodeIPXEBootScript string
applyConfigEnabled bool
Expand Down Expand Up @@ -231,6 +232,9 @@ func downloadBootAssets(ctx context.Context) error {
{
path: &nodeUSBPath,
},
{
path: &nodeUKIPath,
},
{
path: &nodeDiskImagePath,
},
Expand Down Expand Up @@ -471,6 +475,7 @@ func create(ctx context.Context) error {
InitramfsPath: nodeInitramfsPath,
ISOPath: nodeISOPath,
USBPath: nodeUSBPath,
UKIPath: nodeUKIPath,
IPXEBootScript: nodeIPXEBootScript,
DiskImagePath: nodeDiskImagePath,

Expand Down Expand Up @@ -1255,6 +1260,7 @@ func init() {
createCmd.Flags().StringVar(&nodeVmlinuzPath, "vmlinuz-path", helpers.ArtifactPath(constants.KernelAssetWithArch), "the compressed kernel image to use")
createCmd.Flags().StringVar(&nodeISOPath, "iso-path", "", "the ISO path to use for the initial boot (VM only)")
createCmd.Flags().StringVar(&nodeUSBPath, "usb-path", "", "the USB stick image path to use for the initial boot (VM only)")
createCmd.Flags().StringVar(&nodeUKIPath, "uki-path", "", "the UKI image path to use for the initial boot (VM only)")
createCmd.Flags().StringVar(&nodeInitramfsPath, "initrd-path", helpers.ArtifactPath(constants.InitramfsAssetWithArch), "initramfs image to use")
createCmd.Flags().StringVar(&nodeDiskImagePath, "disk-image-path", "", "disk image to use")
createCmd.Flags().StringVar(&nodeIPXEBootScript, "ipxe-boot-script", "", "iPXE boot script (URL) to use")
Expand Down
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
56 changes: 54 additions & 2 deletions internal/pkg/secureboot/uki/uki.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/siderolabs/talos/internal/pkg/secureboot"
"github.com/siderolabs/talos/internal/pkg/secureboot/measure"
"github.com/siderolabs/talos/internal/pkg/secureboot/pesign"
"github.com/siderolabs/talos/pkg/imager/utils"
)

// section is a UKI file section.
Expand Down Expand Up @@ -67,14 +68,65 @@ type Builder struct {
unsignedUKIPath string
}

// Build the UKI file.
// 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 := utils.CopyFiles(printf, utils.SourceDestination(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)
}

return utils.CopyFiles(printf, utils.SourceDestination(builder.unsignedUKIPath, builder.OutUKIPath))
}

// BuildSigned the UKI file.
//
// BuildSigned 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) BuildSigned(printf func(string, ...any)) error {
var err error

builder.scratchDir, err = os.MkdirTemp("", "talos-uki")
Expand Down
48 changes: 30 additions & 18 deletions pkg/imager/imager.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ 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() {
// 4. Build UKI unless the output is a kernel or cmdline.
if i.prof.Output.Kind != profile.OutKindKernel && i.prof.Output.Kind != profile.OutKindCmdline {
if err = i.buildUKI(ctx, report); err != nil {
return "", err
}
Expand Down Expand Up @@ -406,18 +406,8 @@ 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)
}

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

builder := uki.Builder{
Arch: i.prof.Arch,
Expand All @@ -428,14 +418,36 @@ 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 {
buildFunc := builder.Build

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

builder.OutSdBootPath = i.sdBootPath
builder.OutUKIPath = i.ukiPath

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.SecureBootSigner = securebootSigner
builder.PCRSigner = pcrSigner

buildFunc = builder.BuildSigned
}

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

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 @@ -55,6 +55,14 @@ var Default = map[string]Profile{
},
},
},
"metal-uki": {
Platform: constants.PlatformMetal,
SecureBoot: pointer.To(false),
Output: Output{
Kind: OutKindUKI,
OutFormat: OutFormatRaw,
},
},
"secureboot-metal": {
Platform: constants.PlatformMetal,
SecureBoot: pointer.To(true),
Expand Down
14 changes: 7 additions & 7 deletions pkg/imager/profile/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
}
Expand Down
3 changes: 0 additions & 3 deletions pkg/imager/profile/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,6 @@ func (p *Profile) Validate() error {
return fmt.Errorf("customization of meta partition is not supported for %s output", p.Output.Kind)
}
case OutKindUKI:
if !p.SecureBootEnabled() {
return fmt.Errorf("!secureboot is not supported for %s output", p.Output.Kind)
}
}

return nil
Expand Down
6 changes: 6 additions & 0 deletions pkg/provision/providers/qemu/launch.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type LaunchConfig struct {
InitrdPath string
ISOPath string
USBPath string
UKIPath string
ExtraISOPath string
PFlashImages []string
KernelArgs string
Expand Down Expand Up @@ -468,6 +469,11 @@ func launchVM(config *LaunchConfig) error {
"-device", "nec-usb-xhci,id=xhci",
"-device", "usb-storage,bus=xhci.0,drive=stick,removable=on",
)
case config.UKIPath != "":
args = append(args,
"-kernel", config.UKIPath,
"-append", config.KernelArgs,
)
case config.KernelImagePath != "":
args = append(args,
"-kernel", config.KernelImagePath,
Expand Down
1 change: 1 addition & 0 deletions pkg/provision/providers/qemu/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ func (p *provisioner) createNode(state *vm.State, clusterReq provision.ClusterRe
launchConfig.InitrdPath = strings.ReplaceAll(clusterReq.InitramfsPath, constants.ArchVariable, opts.TargetArch)
launchConfig.ISOPath = strings.ReplaceAll(clusterReq.ISOPath, constants.ArchVariable, opts.TargetArch)
launchConfig.USBPath = strings.ReplaceAll(clusterReq.USBPath, constants.ArchVariable, opts.TargetArch)
launchConfig.UKIPath = strings.ReplaceAll(clusterReq.UKIPath, constants.ArchVariable, opts.TargetArch)
}

launchConfig.StatePath, err = state.StatePath()
Expand Down
1 change: 1 addition & 0 deletions pkg/provision/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type ClusterRequest struct {
InitramfsPath string
ISOPath string
USBPath string
UKIPath string
DiskImagePath string
IPXEBootScript string

Expand Down
1 change: 1 addition & 0 deletions website/content/v1.10/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ talosctl cluster create [flags]
--skip-kubeconfig skip merging kubeconfig from the created cluster
--talos-version string the desired Talos version to generate config for (if not set, defaults to image version)
--talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
--uki-path string the UKI image path to use for the initial boot (VM only)
--usb-path string the USB stick image path to use for the initial boot (VM only)
--use-vip use a virtual IP for the controlplane endpoint instead of the loadbalancer
--user-disk strings list of disks to create for each VM in format: <mount_point1>:<size1>:<mount_point2>:<size2>
Expand Down

0 comments on commit 43a1223

Please sign in to comment.