Added test suite

Signed-off-by: Morten Linderud <morten@linderud.pw>
This commit is contained in:
Morten Linderud 2021-05-29 22:42:39 +02:00
parent ae1aec15fb
commit 635be0683f
No known key found for this signature in database
GPG Key ID: E742683BA08CB2FF
10 changed files with 391 additions and 0 deletions

5
tests/README.md Normal file
View File

@ -0,0 +1,5 @@
Integration tests
=================
Follow https://github.com/anatol/vmtest/blob/master/docs/prepare_image.md
Expects `/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd`

29
tests/integration_test.go Normal file
View File

@ -0,0 +1,29 @@
package tests
import (
"log"
"os"
"os/exec"
"testing"
"github.com/foxboron/sbctl/tests/utils"
)
func TestKeyEnrollment(t *testing.T) {
conf := utils.NewConfig()
conf.AddFile("sbctl")
utils.WithVM(conf,
func(vm *utils.TestVM) {
t.Run("Enroll Keys", vm.RunTest("./integrations/enroll_keys_test.go"))
})
}
func TestMain(m *testing.M) {
cmd := exec.Command("go", "build", "../cmd/sbctl")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
os.Exit(m.Run())
}

View File

@ -0,0 +1,41 @@
// +build integrations
package main
import (
"os"
"os/exec"
"strings"
"testing"
"github.com/foxboron/go-uefi/efi"
)
func Exec(c string) error {
args := strings.Split(c, " ")
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func TestEnrollKeys(t *testing.T) {
if !efi.GetSetupMode() {
t.Fatal("not in setup mode")
}
if efi.GetSecureBoot() {
t.Fatal("in secure boot mode")
}
Exec("/mnt/sbctl status")
if !efi.GetSetupMode() {
t.Fatal("not in setup mode")
}
}

26
tests/kernel/mkimage.sh Normal file
View File

@ -0,0 +1,26 @@
#!/bin/bash
dd if=/dev/zero of=rootfs.raw bs=1G count=1
mkfs.ext4 rootfs.raw
sudo losetup -fP rootfs.raw
mkdir rootfs
sudo mount /dev/loop0 rootfs
sudo pacstrap rootfs base openssh sbsigntools terminus-font
echo "[Match]
Name=enp0s3
[Network]
DHCP=yes" | sudo tee rootfs/etc/systemd/network/20-wired.network
sudo sed -i '/^root/ { s/:x:/::/ }' rootfs/etc/passwd
sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' rootfs/etc/ssh/sshd_config
sudo sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' rootfs/etc/ssh/sshd_config
echo "shared /mnt 9p rw,sync,dirsync,access=client,trans=virtio 0 0" | sudo tee rootfs/etc/fstab
echo "FONT=ter-132n" | sudo tee rootfs/etc/vconsole.conf
sudo arch-chroot rootfs systemctl enable sshd systemd-networkd
sudo rm rootfs/var/cache/pacman/pkg/*
sudo umount rootfs
sudo losetup -d /dev/loop0
rm -r rootfs
qemu-img create -o backing_file=rootfs.raw,backing_fmt=raw -f qcow2 rootfs.cow

24
tests/make_image.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
dd if=/dev/zero of=rootfs.raw bs=1G count=1
mkfs.ext4 rootfs.raw
sudo losetup -fP rootfs.raw
mkdir rootfs
sudo mount /dev/loop0 rootfs
sudo pacstrap rootfs base openssh
echo "[Match]
Name=enp0s3
[Network]
DHCP=yes" | sudo tee rootfs/etc/systemd/network/20-wired.network
sudo sed -i '/^root/ { s/:x:/::/ }' rootfs/etc/passwd
sudo sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' rootfs/etc/ssh/sshd_config
sudo sed -i 's/#PermitEmptyPasswords no/PermitEmptyPasswords yes/' rootfs/etc/ssh/sshd_config
sudo arch-chroot rootfs systemctl enable sshd systemd-networkd
sudo rm rootfs/var/cache/pacman/pkg/*
sudo umount rootfs
sudo losetup -d /dev/loop0
rm -r rootfs
qemu-img create -o backing_file=rootfs.raw,backing_fmt=raw -f qcow2 rootfs.cow

BIN
tests/ovmf/OVMF_VARS.fd Normal file

Binary file not shown.

0
tests/shared/test Normal file
View File

54
tests/utils/certs.go Normal file
View File

@ -0,0 +1,54 @@
package utils
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"log"
"math/big"
"time"
)
func CreateKey() ([]byte, []byte) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("Failed to generate serial number: %v", err)
}
c := x509.Certificate{
SerialNumber: serialNumber,
PublicKeyAlgorithm: x509.RSA,
SignatureAlgorithm: x509.SHA256WithRSA,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(5, 0, 0),
KeyUsage: x509.KeyUsageDigitalSignature,
Subject: pkix.Name{
Country: []string{"Test Suite Key"},
CommonName: "Test Suite",
},
}
priv, err := rsa.GenerateKey(rand.Reader, 4098)
if err != nil {
log.Fatal(err)
}
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
log.Fatalf("Unable to marshal private key: %v", err)
}
keyOut := new(bytes.Buffer)
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyBytes}); err != nil {
log.Fatalf("Failed to write data to key.pem: %v", err)
}
derBytes, err := x509.CreateCertificate(rand.Reader, &c, &c, &priv.PublicKey, priv)
if err != nil {
log.Fatalf("Failed to create certificate: %v", err)
}
certOut := new(bytes.Buffer)
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
log.Fatalf("Failed to write data to cert.pem: %v", err)
}
return keyOut.Bytes(), certOut.Bytes()
}

69
tests/utils/config.go Normal file
View File

@ -0,0 +1,69 @@
package utils
import (
"io"
"log"
"os"
"path"
"path/filepath"
)
type TestConfig struct {
Shared string
Ovmf string
Secboot string
Files []string
}
func NewConfig() *TestConfig {
dir, _ := os.MkdirTemp("", "go-uefi-test")
ret := &TestConfig{
Shared: dir,
Ovmf: path.Join(dir, "OVMF_VARS.fd"),
Secboot: path.Join(dir, "OVMF_CODE.secboot.fd"),
Files: []string{},
}
CopyFile("/usr/share/edk2-ovmf/x64/OVMF_VARS.fd", ret.Ovmf)
CopyFile("/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd", ret.Secboot)
return ret
}
func (tc *TestConfig) AddFile(file string) {
dst := path.Join(tc.Shared, filepath.Base(file))
tc.Files = append(tc.Files, dst)
CopyFile(file, dst)
}
func (tc *TestConfig) AddBytes(b []byte, name string) {
dst := path.Join(tc.Shared, name)
tc.Files = append(tc.Files, dst)
os.WriteFile(dst, b, 0644)
}
func (tc *TestConfig) Remove() {
os.RemoveAll(tc.Shared)
}
func CopyFile(src, dst string) bool {
source, err := os.Open(src)
if err != nil {
log.Fatal(err)
}
defer source.Close()
f, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
log.Fatal(err)
}
defer f.Close()
io.Copy(f, source)
si, err := os.Stat(src)
if err != nil {
log.Fatal(err)
}
err = os.Chmod(dst, si.Mode())
if err != nil {
log.Fatal(err)
}
return true
}

143
tests/utils/utils.go Normal file
View File

@ -0,0 +1,143 @@
package utils
import (
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
"github.com/anatol/vmtest"
"golang.org/x/crypto/ssh"
)
func StartOVMF(conf TestConfig) *vmtest.Qemu {
params := []string{
"-machine", "type=q35,smm=on,accel=kvm",
"-boot", "order=c,menu=on,strict=on",
"-net", "none",
"-global", "driver=cfi.pflash01,property=secure,value=on",
"-global", "ICH9-LPC.disable_s3=1",
"-drive", "if=pflash,format=raw,unit=0,file=/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd,readonly",
"-drive", "if=pflash,format=raw,unit=1,file=ovmf/OVMF_VARS.fd",
}
if conf.Shared != "" {
params = append(params, "-drive", fmt.Sprintf("file=fat:rw:%s", conf.Shared))
}
opts := vmtest.QemuOptions{
Params: params,
Verbose: false, //testing.Verbose(),
Timeout: 50 * time.Second,
}
// Run QEMU instance
ovmf, err := vmtest.NewQemu(&opts)
if err != nil {
panic(err)
}
ovmf.ConsoleExpect("Shell>")
return ovmf
}
type TestVM struct {
qemu *vmtest.Qemu
conn *ssh.Client
}
func WithVM(conf *TestConfig, fn func(vm *TestVM)) {
vm := StartVM(conf)
defer vm.Close()
fn(vm)
}
// TODO: Wire this up with 9p instead of ssh
func StartVM(conf *TestConfig) *TestVM {
params := []string{
"-machine", "type=q35,smm=on,accel=kvm",
"-debugcon", "file:debug.log", "-global", "isa-debugcon.iobase=0x402",
"-netdev", "user,id=net0,hostfwd=tcp::10022-:22",
"-device", "virtio-net-pci,netdev=net0",
"-nic", "user,model=virtio-net-pci",
"-fsdev", fmt.Sprintf("local,id=test_dev,path=%s,security_model=none", conf.Shared),
"-device", "virtio-9p-pci,fsdev=test_dev,mount_tag=shared",
"-global", "driver=cfi.pflash01,property=secure,value=on",
"-global", "ICH9-LPC.disable_s3=1",
"-drive", fmt.Sprintf("if=pflash,format=raw,unit=0,file=%s,readonly", conf.Secboot),
"-drive", fmt.Sprintf("if=pflash,format=raw,unit=1,file=%s", conf.Ovmf),
"-m", "8G", "-smp", "2", "-enable-kvm", "-cpu", "host",
}
opts := vmtest.QemuOptions{
OperatingSystem: vmtest.OS_LINUX,
Kernel: "kernel/bzImage",
Params: params,
Disks: []vmtest.QemuDisk{{"kernel/rootfs.cow", "qcow2"}},
Append: []string{"root=/dev/sda", "quiet", "rw"},
Verbose: false, //testing.Verbose()
Timeout: 50 * time.Second,
}
// Run QEMU instance
qemu, err := vmtest.NewQemu(&opts)
if err != nil {
panic(err)
}
qemu.ConsoleExpect("login:")
config := &ssh.ClientConfig{
User: "root",
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", "localhost:10022", config)
if err != nil {
panic(err)
}
return &TestVM{qemu, conn}
}
func (t *TestVM) Run(command string) (ret string, err error) {
sess, err := t.conn.NewSession()
if err != nil {
log.Fatal(err)
}
output, err := sess.CombinedOutput(command)
return string(output), err
}
func (t *TestVM) Close() {
t.conn.Close()
t.qemu.Shutdown()
}
func (t *TestVM) CopyFile(path string) {
cmd := exec.Command("scp", "-P10022", path, "root@localhost:/")
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
func (tvm *TestVM) RunTest(path string) func(t *testing.T) {
return func(t *testing.T) {
testName := fmt.Sprintf("%s%s", filepath.Base(path), ".test")
cmd := exec.Command("go", "test", "-o", testName, "-c", path)
if testing.Verbose() {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
if err := cmd.Run(); err != nil {
tvm.Close()
t.Error(err)
}
tvm.CopyFile(testName)
os.Remove(testName)
ret, err := tvm.Run(fmt.Sprintf("/%s -test.v", testName))
t.Logf("\n%s", ret)
if err != nil {
tvm.Close()
t.Error(err)
}
}
}