mirror of https://github.com/ericonr/sbctl.git
Added test suite
Signed-off-by: Morten Linderud <morten@linderud.pw>
This commit is contained in:
parent
ae1aec15fb
commit
635be0683f
|
@ -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`
|
|
@ -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())
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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
|
Binary file not shown.
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue