feat: add uki iso generation

This adds code to generate a UKI ISO (UEFI only).

Fixes: #7261

Signed-off-by: Noel Georgi <git@frezbo.dev>
This commit is contained in:
Noel Georgi 2023-05-31 02:15:05 +05:30
parent bab484a405
commit 3f68485e44
No known key found for this signature in database
GPG Key ID: 21A9F444075C9E36
8 changed files with 285 additions and 92 deletions

View File

@ -193,10 +193,12 @@ local Pipeline(name, steps=[], depends_on=[], with_docker=true, disable_clone=fa
local external_artifacts = Step('external-artifacts', depends_on=[setup_ci]);
local generate = Step('generate', target='generate docs', depends_on=[setup_ci]);
local check_dirty = Step('check-dirty', depends_on=[generate, external_artifacts]);
local build = Step('build', target='talosctl-all kernel initramfs installer imager talos _out/integration-test-linux-amd64', depends_on=[check_dirty], environment={ IMAGE_REGISTRY: local_registry, PUSH: true });
local uki_certs = Step('uki-certs', depends_on=[setup_ci], environment={ PLATFORM: 'linux/amd64' });
local build = Step('build', target='talosctl-all kernel initramfs installer imager talos _out/integration-test-linux-amd64', depends_on=[check_dirty, uki_certs], environment={ IMAGE_REGISTRY: local_registry, PUSH: true });
local lint = Step('lint', depends_on=[build]);
local talosctl_cni_bundle = Step('talosctl-cni-bundle', depends_on=[build, lint]);
local iso = Step('iso', target='iso', depends_on=[build], environment={ IMAGE_REGISTRY: local_registry });
local iso_uki = Step('iso-uki', target='iso-uki', depends_on=[build], environment={ IMAGE_REGISTRY: local_registry });
local images_essential = Step('images-essential', target='images-essential', depends_on=[iso], environment={ IMAGE_REGISTRY: local_registry });
local unit_tests = Step('unit-tests', target='unit-tests unit-tests-race', depends_on=[build, lint]);
local e2e_docker = Step('e2e-docker-short', depends_on=[build, unit_tests], target='e2e-docker', environment={ SHORT_INTEGRATION_TEST: 'yes', IMAGE_REGISTRY: local_registry });
@ -281,7 +283,7 @@ local save_artifacts = {
'az storage blob upload-batch --overwrite -s _out -d ${CI_COMMIT_SHA}${DRONE_TAG//./-}',
],
volumes: volumes.ForStep(),
depends_on: [build.name, images_essential.name, iso.name, talosctl_cni_bundle.name],
depends_on: [build.name, images_essential.name, iso.name, iso_uki.name, talosctl_cni_bundle.name],
};
local load_artifacts = {
@ -359,10 +361,12 @@ local default_steps = [
external_artifacts,
generate,
check_dirty,
uki_certs,
build,
lint,
talosctl_cni_bundle,
iso,
iso_uki,
images_essential,
unit_tests,
save_artifacts,
@ -708,6 +712,8 @@ local release = {
'_out/scaleway-arm64.raw.xz',
'_out/talos-amd64.iso',
'_out/talos-arm64.iso',
'_out/talos-uki-amd64.iso',
'_out/talos-uki-arm64.iso',
'_out/talosctl-cni-bundle-amd64.tar.gz',
'_out/talosctl-cni-bundle-arm64.tar.gz',
'_out/talosctl-darwin-amd64',

View File

@ -676,9 +676,46 @@ COPY --from=rootfs / /
LABEL org.opencontainers.image.source https://github.com/siderolabs/talos
ENTRYPOINT ["/sbin/init"]
FROM --platform=${BUILDPLATFORM} tools AS gen-uki-certs
RUN gen-uki-certs
FROM scratch as uki-certs
COPY --from=gen-uki-certs /_out /
FROM --platform=${BUILDPLATFORM} tools AS uki-build-amd64
WORKDIR /build
COPY --from=pkg-sd-stub-amd64 / _out/
COPY --from=pkg-sd-boot-amd64 / _out/
COPY --from=pkg-kernel-amd64 /boot/vmlinuz _out/vmlinuz-amd64
COPY --from=initramfs-archive-amd64 /initramfs.xz _out/initramfs-amd64.xz
COPY _out/uki-certs _out/uki-certs
RUN ukify
FROM scratch AS uki-amd64
COPY --from=uki-build-amd64 /build/_out/systemd-bootx64.efi.signed /systemd-boot.efi.signed
COPY --from=uki-build-amd64 /build/_out/vmlinuz.efi.signed /vmlinuz.efi.signed
FROM --platform=${BUILDPLATFORM} tools AS uki-build-arm64
WORKDIR /build
COPY --from=pkg-sd-stub-arm64 / _out/
COPY --from=pkg-sd-boot-arm64 / _out/
COPY --from=pkg-kernel-arm64 /boot/vmlinuz _out/vmlinuz-arm64
COPY --from=initramfs-archive-arm64 /initramfs.xz _out/initramfs-arm64.xz
COPY _out/uki-certs _out/uki-certs
RUN ukify \
-sd-stub _out/linuxaa64.efi.stub \
-sd-boot _out/systemd-bootaa64.efi \
-kernel _out/vmlinuz-arm64 \
-initrd _out/initramfs-arm64.xz
FROM scratch AS uki-arm64
COPY --from=uki-build-arm64 /build/_out/systemd-bootaa64.efi.signed /systemd-boot.efi.signed
COPY --from=uki-build-arm64 /build/_out/vmlinuz.efi.signed /vmlinuz.efi.signed
FROM --platform=${BUILDPLATFORM} uki-${TARGETARCH} AS uki
# The installer target generates an image that can be used to install Talos to
# various environments.
FROM base AS installer-build
ARG GO_BUILDFLAGS
ARG GO_LDFLAGS
@ -695,12 +732,16 @@ COPY --from=pkg-grub-amd64 /usr/lib/grub /usr/lib/grub
COPY --from=pkg-kernel-amd64 /boot/vmlinuz /usr/install/amd64/vmlinuz
COPY --from=pkg-kernel-amd64 /dtb /usr/install/amd64/dtb
COPY --from=initramfs-archive-amd64 /initramfs.xz /usr/install/amd64/initramfs.xz
COPY --from=uki-amd64 /systemd-boot.efi.signed /usr/install/amd64/systemd-boot.efi.signed
COPY --from=uki-amd64 /vmlinuz.efi.signed /usr/install/amd64/vmlinuz.efi.signed
FROM scratch AS install-artifacts-arm64
COPY --from=pkg-grub-arm64 /usr/lib/grub /usr/lib/grub
COPY --from=pkg-kernel-arm64 /boot/vmlinuz /usr/install/arm64/vmlinuz
COPY --from=pkg-kernel-arm64 /dtb /usr/install/arm64/dtb
COPY --from=initramfs-archive-arm64 /initramfs.xz /usr/install/arm64/initramfs.xz
COPY --from=uki-arm64 /systemd-boot.efi.signed /usr/install/arm64/systemd-boot.efi.signed
COPY --from=uki-arm64 /vmlinuz.efi.signed /usr/install/arm64/vmlinuz.efi.signed
COPY --from=pkg-u-boot-arm64 / /usr/install/arm64/u-boot
COPY --from=pkg-raspberrypi-firmware-arm64 / /usr/install/arm64/raspberrypi-firmware
@ -720,6 +761,7 @@ ENV SOURCE_DATE_EPOCH ${SOURCE_DATE_EPOCH}
RUN apk add --no-cache --update --no-scripts \
bash \
cpio \
dosfstools \
efibootmgr \
kmod \
mtools \
@ -797,8 +839,33 @@ COPY --from=iso-arm64-build /out /
FROM --platform=${BUILDPLATFORM} iso-${TARGETARCH} AS iso
# The test target performs tests on the source code.
FROM imager as iso-uki-amd64-build
ARG SOURCE_DATE_EPOCH
ENV SOURCE_DATE_EPOCH ${SOURCE_DATE_EPOCH}
RUN /bin/installer \
iso \
--uki \
--arch amd64 \
--output /out
FROM imager as iso-uki-arm64-build
ARG SOURCE_DATE_EPOCH
ENV SOURCE_DATE_EPOCH ${SOURCE_DATE_EPOCH}
RUN /bin/installer \
iso \
--uki \
--arch arm64 \
--output /out
FROM scratch as iso-uki-amd64
COPY --from=iso-uki-amd64-build /out /
FROM scratch as iso-uki-arm64
COPY --from=iso-uki-arm64-build /out /
FROM --platform=${BUILDPLATFORM} iso-uki-${TARGETARCH} AS iso-uki
# The test target performs tests on the source code.
FROM base AS unit-tests-runner
RUN unlink /etc/ssl
COPY --from=rootfs / /
@ -878,44 +945,6 @@ RUN --mount=type=cache,target=/.cache GOOS=linux GOARCH=amd64 GOAMD64=${GOAMD64}
FROM scratch AS module-sig-verify-linux
COPY --from=module-sig-verify-linux-build /src/module-sig-verify/module-sig-verify /module-sig-verify-linux-amd64
FROM --platform=${BUILDPLATFORM} tools AS gen-uki-certs
RUN gen-uki-certs
FROM scratch as uki-certs
COPY --from=gen-uki-certs /_out /
FROM --platform=${BUILDPLATFORM} tools AS uki-build-amd64
WORKDIR /build
COPY --from=pkg-sd-stub-amd64 / _out/
COPY --from=pkg-sd-boot-amd64 / _out/
COPY --from=pkg-kernel-amd64 /boot/vmlinuz _out/vmlinuz-amd64
COPY --from=initramfs-archive-amd64 /initramfs.xz _out/initramfs-amd64.xz
COPY _out/uki-certs _out/uki-certs
RUN ukify
FROM scratch AS uki-amd64
COPY --from=uki-build-amd64 /build/_out/systemd-bootx64.efi.signed /systemd-bootx64.efi.signed
COPY --from=uki-build-amd64 /build/_out/vmlinuz.efi /vmlinuz-amd64.signed.efi
FROM --platform=${BUILDPLATFORM} tools AS uki-build-arm64
WORKDIR /build
COPY --from=pkg-sd-stub-arm64 / _out/
COPY --from=pkg-sd-boot-arm64 / _out/
COPY --from=pkg-kernel-arm64 /boot/vmlinuz _out/vmlinuz-arm64
COPY --from=initramfs-archive-arm64 /initramfs.xz _out/initramfs-arm64.xz
COPY _out/uki-certs _out/uki-certs
RUN ukify \
-sd-stub _out/linuxaa64.efi.stub \
-sd-boot _out/systemd-bootaa64.efi \
-kernel _out/vmlinuz-arm64 \
-initrd _out/initramfs-arm64.xz
FROM scratch AS uki-arm64
COPY --from=uki-build-arm64 /build/_out/systemd-bootaa64.efi.signed /systemd-bootaa64.efi.signed
COPY --from=uki-build-arm64 /build/_out/vmlinuz.efi /vmlinuz-arm64.signed.efi
FROM --platform=${BUILDPLATFORM} uki-${TARGETARCH} AS uki
# The lint target performs linting on the source code.
FROM base AS lint-go
COPY .golangci.yml .

View File

@ -324,6 +324,14 @@ iso: ## Builds the ISO and outputs it to the artifact directory.
docker run --rm -e SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) -i $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) iso --arch $$arch --tar-to-stdout $(IMAGER_ARGS) | tar xz -C $(ARTIFACTS) ; \
done
.PHONY: iso-uki
iso-uki: ## Builds UEFI only ISO which uses UKI and outputs it to the artifact directory.
@docker pull $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG)
@for platform in $(subst $(,),$(space),$(PLATFORM)); do \
arch=`basename "$${platform}"` ; \
docker run --rm -e SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) -i $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) iso --arch $$arch --uki --tar-to-stdout $(IMAGER_ARGS) | tar xz -C $(ARTIFACTS) ; \
done
.PHONY: talosctl-cni-bundle
talosctl-cni-bundle: ## Creates a compressed tarball that includes CNI bundle for talosctl.
@$(MAKE) local-$@ DEST=$(ARTIFACTS)

View File

@ -26,8 +26,11 @@ import (
"github.com/siderolabs/talos/pkg/machinery/kernel"
)
//go:embed grub.iso.cfg
var isoGrubCfg []byte
var (
//go:embed grub.iso.cfg
isoGrubCfg []byte
uki bool
)
// isoCmd represents the iso command.
var isoCmd = &cobra.Command{
@ -42,6 +45,7 @@ var isoCmd = &cobra.Command{
}
func init() {
isoCmd.Flags().BoolVar(&uki, "uki", false, "Create UKI ISO")
isoCmd.Flags().StringVar(&outputArg, "output", "/out", "The output path")
isoCmd.Flags().BoolVar(&tarToStdout, "tar-to-stdout", false, "Tar output and send to stdout")
rootCmd.AddCommand(isoCmd)
@ -53,36 +57,56 @@ func runISOCmd() error {
return err
}
out := fmt.Sprintf("/tmp/talos-%s.iso", options.Arch)
if uki {
out = fmt.Sprintf("/tmp/talos-uki-%s.iso", options.Arch)
if err := createUKIISO(out); err != nil {
return err
}
} else {
if err := createISO(out); err != nil {
return err
}
}
from, err := os.Open(out)
if err != nil {
return err
}
//nolint:errcheck
defer from.Close()
to, err := os.OpenFile(filepath.Join(outputArg, filepath.Base(out)), os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return err
}
//nolint:errcheck
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
if tarToStdout {
if err := tarOutput(); err != nil {
return err
}
}
return nil
}
func createISO(out string) error {
files := map[string]string{
fmt.Sprintf("/usr/install/%s/vmlinuz", options.Arch): "/mnt/boot/vmlinuz",
fmt.Sprintf("/usr/install/%s/initramfs.xz", options.Arch): "/mnt/boot/initramfs.xz",
}
for src, dest := range files {
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
return err
}
log.Printf("copying %s to %s", src, dest)
from, err := os.Open(src)
if err != nil {
return err
}
//nolint:errcheck
defer from.Close()
to, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return err
}
//nolint:errcheck
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
if err := copyFiles(files); err != nil {
return err
}
log.Println("creating grub.cfg")
@ -145,33 +169,48 @@ func runISOCmd() error {
log.Println("creating ISO")
out := fmt.Sprintf("/tmp/talos-%s.iso", options.Arch)
return pkg.CreateISO(out, "/mnt")
}
if err = pkg.CreateISO(out, "/mnt"); err != nil {
func createUKIISO(out string) error {
files := map[string]string{
fmt.Sprintf("/usr/install/%s/systemd-boot.efi.signed", options.Arch): "/mnt/systemd-boot.efi.signed",
fmt.Sprintf("/usr/install/%s/vmlinuz.efi.signed", options.Arch): "/mnt/vmlinuz.efi.signed",
}
if err := copyFiles(files); err != nil {
return err
}
from, err := os.Open(out)
if err != nil {
return err
}
//nolint:errcheck
defer from.Close()
log.Println("creating UKI ISO")
to, err := os.OpenFile(filepath.Join(outputArg, filepath.Base(out)), os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return err
}
//nolint:errcheck
defer to.Close()
return pkg.CreateUKIISO(out, "/mnt", options.Arch)
}
_, err = io.Copy(to, from)
if err != nil {
return err
}
func copyFiles(files map[string]string) error {
for src, dest := range files {
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
return err
}
if tarToStdout {
if err := tarOutput(); err != nil {
log.Printf("copying %s to %s", src, dest)
from, err := os.Open(src)
if err != nil {
return err
}
//nolint:errcheck
defer from.Close()
to, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return err
}
//nolint:errcheck
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
}

View File

@ -7,9 +7,18 @@ package pkg
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/siderolabs/go-cmd/pkg/cmd"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/makefs"
)
const (
// UKIISOSize is the size of the UKI ISO.
UKIISOSize = 120 * 1024 * 1024
)
// CreateISO creates an iso by invoking the `grub-mkrescue` command.
@ -42,3 +51,86 @@ func CreateISO(iso, dir string) error {
return nil
}
// CreateUKIISO creates an iso using a UKI and UEFI only.
// nolint:gocyclo
func CreateUKIISO(iso, dir, arch string) error {
isoDir := filepath.Join(dir, "iso")
if err := os.MkdirAll(isoDir, 0o755); err != nil {
return err
}
defer os.RemoveAll(isoDir) // nolint:errcheck
efiBootImg := filepath.Join(isoDir, "efiboot.img")
f, err := os.Create(efiBootImg)
if err != nil {
return err
}
if err := f.Truncate(UKIISOSize); err != nil {
return err
}
defer f.Close() // nolint:errcheck
fopts := []makefs.Option{
makefs.WithLabel(constants.EFIPartitionLabel),
makefs.WithReproducible(true),
}
if err := makefs.VFAT(efiBootImg, fopts...); err != nil {
return err
}
if _, err := cmd.Run("mmd", "-i", efiBootImg, "::EFI"); err != nil {
return err
}
if _, err := cmd.Run("mmd", "-i", efiBootImg, "::EFI/BOOT"); err != nil {
return err
}
if _, err := cmd.Run("mmd", "-i", efiBootImg, "::EFI/Linux"); err != nil {
return err
}
efiBootPath := "::EFI/BOOT/BOOTX64.efi"
if arch == "arm64" {
efiBootPath = "::EFI/BOOT/BOOTAA64.EFI"
}
if _, err := cmd.Run("mcopy", "-i", efiBootImg, filepath.Join(dir, "systemd-boot.efi.signed"), efiBootPath); err != nil {
return err
}
if _, err := cmd.Run("mcopy", "-i", efiBootImg, filepath.Join(dir, "vmlinuz.efi.signed"), "::EFI/Linux/talos-A.efi"); err != nil {
return err
}
// fixup directory timestamps recursively
if err := TouchFiles(dir); err != nil {
return err
}
if _, err := cmd.Run(
"xorriso",
"-as",
"mkisofs",
"-V",
"Talos Secure Boot ISO",
"-e",
"efiboot.img",
"-no-emul-boot",
"-o",
iso,
isoDir,
); err != nil {
return err
}
return nil
}

View File

@ -298,7 +298,13 @@ func run() error {
return err
}
return buildUKI(sdStub, output, sections)
if err := buildUKI(sdStub, output, sections); err != nil {
return err
}
_, err = sbSign(output)
return err
}
func main() {

View File

@ -10,8 +10,9 @@ type Option func(*Options)
// Options for makefs.
type Options struct {
Label string
Force bool
Label string
Force bool
Reproducible bool
}
// WithLabel sets the label for the filesystem to be created.
@ -28,6 +29,14 @@ func WithForce(force bool) Option {
}
}
// WithReproducible sets the reproducible flag for the filesystem to be created.
// This should only be used when creating filesystems on raw disk images.
func WithReproducible(reproducible bool) Option {
return func(o *Options) {
o.Reproducible = reproducible
}
}
// NewDefaultOptions builds options with specified setters applied.
func NewDefaultOptions(setters ...Option) Options {
var opt Options

View File

@ -18,6 +18,10 @@ func VFAT(partname string, setters ...Option) error {
args = append(args, "-F", "32", "-n", opts.Label)
}
if opts.Reproducible {
args = append(args, "--invariant")
}
args = append(args, partname)
_, err := cmd.Run("mkfs.vfat", args...)