Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support testing secureboot in Qemu #556

Merged
merged 7 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,26 +95,31 @@ wget https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_productio
wget https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_production_qemu_uefi_efi_code.fd.sig
gpg --verify flatcar_production_qemu_uefi_efi_code.fd.sig

wget https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_production_qemu_uefi_efi_vars.fd
wget https://alpha.release.flatcar-linux.net/amd64-usr/current/flatcar_production_qemu_uefi_efi_vars.fd.sig
gpg --verify flatcar_production_qemu_uefi_efi_vars.fd.sig

sudo ./bin/kola run --board amd64-usr --key ${HOME}/.ssh/id_rsa.pub -k -b cl -p qemu \
--qemu-bios flatcar_production_qemu_uefi_efi_code.fd \
--qemu-firmware flatcar_production_qemu_uefi_efi_code.fd \
--qemu-ovmf-vars flatcar_production_qemu_uefi_efi_vars.fd \
--qemu-image flatcar_production_qemu_image.img \
cl.locksmith.cluster
```

###### Run tests for ARM64
Example with the latest `alpha` release:
```shell
wget https://alpha.release.flatcar-linux.net/arm64-usr/current/flatcar_production_qemu_uefi_secure_image.img
wget https://alpha.release.flatcar-linux.net/arm64-usr/current/flatcar_production_qemu_uefi_secure_image.img.sig
gpg --verify flatcar_production_qemu_uefi_secure_image.img.sig
wget https://alpha.release.flatcar-linux.net/arm64-usr/current/flatcar_production_qemu_uefi_image.img
wget https://alpha.release.flatcar-linux.net/arm64-usr/current/flatcar_production_qemu_uefi_image.img.sig
gpg --verify flatcar_production_qemu_uefi_image.img.sig

wget https://alpha.release.flatcar-linux.net/arm64-usr/current/flatcar_production_qemu_uefi_efi_code.fd
wget https://alpha.release.flatcar-linux.net/arm64-usr/current/flatcar_production_qemu_uefi_efi_code.fd.sig
gpg --verify flatcar_production_qemu_uefi_efi_code.fd.sig

sudo ./bin/kola run --board arm64-usr --key ${HOME}/.ssh/id_rsa.pub -k -b cl -p qemu \
--qemu-bios flatcar_production_qemu_uefi_efi_code.fd \
--qemu-image flatcar_production_qemu_uefi_secure_image.bin \
--qemu-firmware flatcar_production_qemu_uefi_efi_code.fd \
--qemu-image flatcar_production_qemu_uefi_image.img \
cl.etcd-member.discovery
```

Expand Down
15 changes: 11 additions & 4 deletions cmd/kola/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ var (
"rhcos": "v3",
}

kolaDefaultBIOS = map[string]string{
kolaDefaultFirmware = map[string]string{
"amd64-usr": "bios-256k.bin",
"arm64-usr": sdk.BuildRoot() + "/images/arm64-usr/latest/flatcar_production_qemu_uefi_efi_code.fd",
}
Expand Down Expand Up @@ -87,6 +87,7 @@ func init() {
sv(&kola.UpdatePayloadFile, "update-payload", "", "Path to an update payload that should be made available to tests")
bv(&kola.ForceFlatcarKey, "force-flatcar-key", false, "Use the Flatcar production key to verify update payload")
sv(&kola.Options.IgnitionVersion, "ignition-version", "", "Ignition version override: v2, v3")
bv(&kola.Options.EnableSecureboot, "enable-secureboot", false, "Instantiate a Secureboot Machine")
iv(&kola.Options.SSHRetries, "ssh-retries", kolaSSHRetries, "Number of retries with the SSH timeout when starting the machine")
dv(&kola.Options.SSHTimeout, "ssh-timeout", kolaSSHTimeout, "A timeout for a single try of establishing an SSH connection when starting the machine")

Expand Down Expand Up @@ -222,8 +223,11 @@ func init() {
// QEMU-specific options
sv(&kola.QEMUOptions.Board, "board", defaultTargetBoard, "target board")
sv(&kola.QEMUOptions.DiskImage, "qemu-image", "", "path to CoreOS disk image")
sv(&kola.QEMUOptions.BIOSImage, "qemu-bios", "", "BIOS to use for QEMU vm")
sv(&kola.QEMUOptions.Firmware, "qemu-bios", "", "bios to use for QEMU vm")
root.PersistentFlags().MarkDeprecated("qemu-bios", "use --qemu-firmware")
sv(&kola.QEMUOptions.Firmware, "qemu-firmware", "", "firmware image to use for QEMU vm")
sv(&kola.QEMUOptions.VNC, "qemu-vnc", "", "VNC port (0 for 5900, 1 for 5901, etc.)")
sv(&kola.QEMUOptions.OVMFVars, "qemu-ovmf-vars", "", "OVMF vars file to use for QEMU vm")
bv(&kola.QEMUOptions.UseVanillaImage, "qemu-skip-mangle", false, "don't modify CL disk image to capture console log")
sv(&kola.QEMUOptions.ExtraBaseDiskSize, "qemu-grow-base-disk-by", "", "grow base disk by the given size in bytes, following optional 1024-based suffixes are allowed: b (ignored), k, K, M, G, T")
bv(&kola.QEMUOptions.EnableTPM, "qemu-tpm", false, "enable TPM device in QEMU. Requires installing swtpm. Use only with 'kola spawn', test cases are responsible for creating a VM with TPM explicitly.")
Expand Down Expand Up @@ -310,8 +314,11 @@ func syncOptions() error {
kola.QEMUOptions.DiskImage = image
}

if kola.QEMUOptions.BIOSImage == "" {
kola.QEMUOptions.BIOSImage = kolaDefaultBIOS[kola.QEMUOptions.Board]
if kola.QEMUOptions.Firmware == "" {
kola.QEMUOptions.Firmware = kolaDefaultFirmware[kola.QEMUOptions.Board]
}
if kola.QEMUOptions.EnableSecureboot && kola.QEMUOptions.OVMFVars == "" {
return fmt.Errorf("Secureboot requires OVMF vars file")
}
units, _ := root.PersistentFlags().GetStringSlice("debug-systemd-units")
for _, unit := range units {
Expand Down
4 changes: 4 additions & 0 deletions kola/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ var (
}
)

func SkipSecureboot(_ semver.Version, channel, arch, platform string) bool {
return Options.EnableSecureboot
}

// NativeRunner is a closure passed to all kola test functions and used
// to run native go functions directly on kola machines. It is necessary
// glue until kola does introspection.
Expand Down
4 changes: 3 additions & 1 deletion kola/tests/misc/falco.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package misc

import (
"github.com/flatcar/mantle/kola"
"github.com/flatcar/mantle/kola/cluster"
"github.com/flatcar/mantle/kola/register"
)
Expand All @@ -16,7 +17,8 @@ func init() {
// falco builder container can't handle our arm64 config (yet)
Architectures: []string{"amd64"},
// selinux blocks insmod from within container
Flags: []register.Flag{register.NoEnableSelinux},
Flags: []register.Flag{register.NoEnableSelinux},
SkipFunc: kola.SkipSecureboot,
})
}

Expand Down
11 changes: 8 additions & 3 deletions kola/tests/sysext/zfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"

"github.com/coreos/go-semver/semver"
"github.com/flatcar/mantle/kola"
"github.com/flatcar/mantle/kola/cluster"
"github.com/flatcar/mantle/kola/register"
"github.com/flatcar/mantle/platform"
Expand Down Expand Up @@ -133,7 +134,7 @@ func init() {
// This test is normally not related to the cloud environment
Platforms: []string{"qemu", "qemu-unpriv"},
MinVersion: semver.Version{Major: 3902},
SkipFunc: skipOnGha,
SkipFunc: skipZfs,
})

register.Register(&register.Test{
Expand All @@ -144,7 +145,7 @@ func init() {
// This test is normally not related to the cloud environment
Platforms: []string{"qemu", "qemu-unpriv"},
MinVersion: semver.Version{Major: 3902},
SkipFunc: skipOnGha,
SkipFunc: skipZfs,
})

register.Register(&register.Test{
Expand All @@ -155,10 +156,14 @@ func init() {
// This test is normally not related to the cloud environment
Platforms: []string{"qemu", "qemu-unpriv"},
MinVersion: semver.Version{Major: 3902},
SkipFunc: skipOnGha,
SkipFunc: skipZfs,
})
}

func skipZfs(version semver.Version, channel, arch, platform string) bool {
return kola.SkipSecureboot(version, channel, arch, platform) || skipOnGha(version, channel, arch, platform)
}

func skipOnGha(version semver.Version, channel, arch, platform string) bool {
// Skip for release tests as we don't yet have a sysext signed with the
// prod key, nor is it on the release server.
Expand Down
22 changes: 18 additions & 4 deletions platform/machine/qemu/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package qemu
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
Expand Down Expand Up @@ -133,11 +134,24 @@ ExecStartPost=/usr/bin/ln -fs /run/metadata/flatcar /run/metadata/coreos

// This uses path arguments with path values being
// relative to the folder created for this machine
biosImage, err := filepath.Abs(qc.flight.opts.BIOSImage)
firmware, err := filepath.Abs(qc.flight.opts.Firmware)
if err != nil {
return nil, fmt.Errorf("failed to canonicalize bios path: %v", err)
return nil, fmt.Errorf("failed to canonicalize firmware path: %v", err)
}
qmCmd, extraFiles, err := platform.CreateQEMUCommand(qc.flight.opts.Board, qm.id, biosImage, qm.consolePath, confPath, qc.flight.diskImagePath, conf.IsIgnition(), options)
ovmfVars := ""
if qc.flight.opts.OVMFVars != "" {
ovmfVars, err = platform.CreateOvmfVarsCopy(qm.subDir, qc.flight.opts.OVMFVars)
if err != nil {
return nil, err
}
defer func() {
if ovmfVars != "" {
os.Remove(path.Join(qm.subDir, ovmfVars))
}
}()
}

qmCmd, extraFiles, err := platform.CreateQEMUCommand(qc.flight.opts.Board, qm.id, firmware, ovmfVars, qm.consolePath, confPath, qc.flight.diskImagePath, qc.flight.opts.EnableSecureboot, conf.IsIgnition(), options)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -181,7 +195,7 @@ ExecStartPost=/usr/bin/ln -fs /run/metadata/flatcar /run/metadata/coreos

// from this point on Destroy() is responsible for cleaning up swtpm
qm.swtpm, swtpm = swtpm, nil

qm.ovmfVars, ovmfVars = ovmfVars, ""
plog.Debugf("qemu PID (manual cleanup needed if --remove=false): %v", qm.qemu.Pid())

if err := platform.StartMachine(qm, qm.journal); err != nil {
Expand Down
7 changes: 5 additions & 2 deletions platform/machine/qemu/flight.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ type Options struct {
// DiskImage is the full path to the disk image to boot in QEMU.
DiskImage string

// BIOSImage is name of the BIOS file to pass to QEMU.
// Firmware is name of the Firmware file to pass to QEMU.
// It can be a plain name, or a full path.
BIOSImage string
Firmware string

// OMVF Vars file to pass to QEMU UEFI
OVMFVars string

// Don't modify CL disk images to add console logging
UseVanillaImage bool
Expand Down
9 changes: 9 additions & 0 deletions platform/machine/qemu/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package qemu

import (
"io/ioutil"
"os"
"path"
"path/filepath"

"golang.org/x/crypto/ssh"
Expand All @@ -33,6 +35,7 @@ type machine struct {
journal *platform.Journal
consolePath string
console string
ovmfVars string
subDir string
swtpm *local.SoftwareTPM
}
Expand Down Expand Up @@ -76,6 +79,12 @@ func (m *machine) Destroy() {
if m.swtpm != nil {
m.swtpm.Stop()
}
if m.ovmfVars != "" {
err := os.Remove(path.Join(m.subDir, m.ovmfVars))
if err != nil {
plog.Errorf("Error removing OVMF vars: %v", err)
}
}
m.journal.Destroy()

if buf, err := ioutil.ReadFile(filepath.Join(m.subDir, m.consolePath)); err == nil {
Expand Down
21 changes: 18 additions & 3 deletions platform/machine/unprivqemu/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"io/ioutil"
"net"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
Expand Down Expand Up @@ -143,13 +144,26 @@ LinkLocalAddressing=no
}
}()
}

ovmfVars := ""
if qc.flight.opts.OVMFVars != "" {
ovmfVars, err = platform.CreateOvmfVarsCopy(qm.subDir, qc.flight.opts.OVMFVars)
if err != nil {
return nil, err
}
defer func() {
if ovmfVars != "" {
os.Remove(path.Join(qm.subDir, ovmfVars))
}
}()
}
// This uses path arguments with path values being
// relative to the folder created for this machine
biosImage, err := filepath.Abs(qc.flight.opts.BIOSImage)
firmware, err := filepath.Abs(qc.flight.opts.Firmware)
if err != nil {
return nil, fmt.Errorf("failed to canonicalize bios path: %v", err)
return nil, fmt.Errorf("failed to canonicalize firmware path: %v", err)
}
qmCmd, extraFiles, err := platform.CreateQEMUCommand(qc.flight.opts.Board, qm.id, biosImage, qm.consolePath, confPath, qc.flight.diskImagePath, conf.IsIgnition(), options)
qmCmd, extraFiles, err := platform.CreateQEMUCommand(qc.flight.opts.Board, qm.id, firmware, ovmfVars, qm.consolePath, confPath, qc.flight.diskImagePath, qc.flight.opts.EnableSecureboot, conf.IsIgnition(), options)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -182,6 +196,7 @@ LinkLocalAddressing=no

// from this point on Destroy() is responsible for cleaning up swtpm
qm.swtpm, swtpm = swtpm, nil
qm.ovmfVars, ovmfVars = ovmfVars, ""
plog.Debugf("qemu PID (manual cleanup needed if --remove=false): %v", qm.qemu.Pid())

pid := strconv.Itoa(qm.qemu.Pid())
Expand Down
9 changes: 9 additions & 0 deletions platform/machine/unprivqemu/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ package unprivqemu

import (
"io/ioutil"
"os"
"path"
"path/filepath"

"golang.org/x/crypto/ssh"
Expand All @@ -32,6 +34,7 @@ type machine struct {
journal *platform.Journal
consolePath string
console string
ovmfVars string
subDir string
swtpm *local.SoftwareTPM
ip string
Expand Down Expand Up @@ -78,6 +81,12 @@ func (m *machine) Destroy() {
if m.swtpm != nil {
m.swtpm.Stop()
}
if m.ovmfVars != "" {
err := os.Remove(path.Join(m.subDir, m.ovmfVars))
if err != nil {
plog.Errorf("Error removing OVMF vars: %v", err)
}
}
m.journal.Destroy()

if buf, err := ioutil.ReadFile(filepath.Join(m.subDir, m.consolePath)); err == nil {
Expand Down
3 changes: 3 additions & 0 deletions platform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ type Options struct {
// Board is the board used by the image
Board string

// Toggle to instantiate a secureboot instance.
EnableSecureboot bool

// How many times to retry establishing an SSH connection when
// creating a journal or when doing a machine check.
SSHRetries int
Expand Down
Loading