2022-09-12 04:32:01 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"syscall"
|
|
|
|
|
2022-09-21 07:47:57 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/hack/image-measurement/server"
|
2022-11-15 09:40:49 -05:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
2022-09-21 07:47:57 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
2022-09-12 04:32:01 -04:00
|
|
|
"go.uber.org/multierr"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"go.uber.org/zap/zapcore"
|
2022-09-12 05:04:51 -04:00
|
|
|
"gopkg.in/yaml.v3"
|
2022-09-12 04:32:01 -04:00
|
|
|
"libvirt.org/go/libvirt"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Usage:
|
|
|
|
// go build
|
|
|
|
//./image-measurement --path=disk.raw --type=raw
|
|
|
|
|
|
|
|
type libvirtInstance struct {
|
|
|
|
conn *libvirt.Connect
|
|
|
|
log *logger.Logger
|
|
|
|
imagePath string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *libvirtInstance) uploadBaseImage(baseVolume *libvirt.StorageVol) (err error) {
|
|
|
|
stream, err := l.conn.NewStream(libvirt.STREAM_NONBLOCK)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() { _ = stream.Free() }()
|
|
|
|
file, err := os.Open(l.imagePath)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error while opening %s: %s", l.imagePath, err)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
err = multierr.Append(err, file.Close())
|
|
|
|
}()
|
|
|
|
|
|
|
|
fi, err := file.Stat()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := baseVolume.Upload(stream, 0, uint64(fi.Size()), 0); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
transferredBytes := 0
|
|
|
|
buffer := make([]byte, 4*1024*1024)
|
|
|
|
for {
|
|
|
|
_, err := file.Read(buffer)
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
num, err := stream.Send(buffer)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
transferredBytes += num
|
|
|
|
|
|
|
|
}
|
|
|
|
if transferredBytes < int(fi.Size()) {
|
|
|
|
return fmt.Errorf("only send %d out of %d bytes", transferredBytes, fi.Size())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *libvirtInstance) createLibvirtInstance() error {
|
|
|
|
domainXMLString, err := domainXMLConfig.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
poolXMLString, err := poolXMLConfig.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
volumeBootXMLString, err := volumeBootXMLConfig.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
volumeBaseXMLString, err := volumeBaseXMLConfig.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
volumeStateXMLString, err := volumeStateXMLConfig.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
networkXMLString, err := networkXMLConfig.Marshal()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
l.log.Infof("creating network")
|
|
|
|
network, err := l.conn.NetworkCreateXML(networkXMLString)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() { _ = network.Free() }()
|
|
|
|
|
|
|
|
l.log.Infof("creating storage pool")
|
|
|
|
poolObject, err := l.conn.StoragePoolDefineXML(poolXMLString, libvirt.STORAGE_POOL_DEFINE_VALIDATE)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error defining libvirt storage pool: %s", err)
|
|
|
|
}
|
|
|
|
defer func() { _ = poolObject.Free() }()
|
|
|
|
if err := poolObject.Build(libvirt.STORAGE_POOL_BUILD_NEW); err != nil {
|
|
|
|
return fmt.Errorf("error building libvirt storage pool: %s", err)
|
|
|
|
}
|
|
|
|
if err := poolObject.Create(libvirt.STORAGE_POOL_CREATE_NORMAL); err != nil {
|
|
|
|
return fmt.Errorf("error creating libvirt storage pool: %s", err)
|
|
|
|
}
|
|
|
|
volumeBaseObject, err := poolObject.StorageVolCreateXML(volumeBaseXMLString, 0)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating libvirt storage volume 'base': %s", err)
|
|
|
|
}
|
|
|
|
defer func() { _ = volumeBaseObject.Free() }()
|
|
|
|
|
|
|
|
l.log.Infof("uploading image to libvirt")
|
|
|
|
if err := l.uploadBaseImage(volumeBaseObject); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
l.log.Infof("creating storage volume 'boot'")
|
|
|
|
bootVol, err := poolObject.StorageVolCreateXML(volumeBootXMLString, 0)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating libvirt storage volume 'boot': %s", err)
|
|
|
|
}
|
|
|
|
defer func() { _ = bootVol.Free() }()
|
|
|
|
|
|
|
|
l.log.Infof("creating storage volume 'state'")
|
|
|
|
stateVol, err := poolObject.StorageVolCreateXML(volumeStateXMLString, 0)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating libvirt storage volume 'state': %s", err)
|
|
|
|
}
|
|
|
|
defer func() { _ = stateVol.Free() }()
|
|
|
|
|
|
|
|
l.log.Infof("creating domain")
|
|
|
|
domain, err := l.conn.DomainCreateXML(domainXMLString, libvirt.DOMAIN_NONE)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error creating libvirt domain: %s", err)
|
|
|
|
}
|
|
|
|
defer func() { _ = domain.Free() }()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *libvirtInstance) deleteNetwork() error {
|
|
|
|
nets, err := l.conn.ListAllNetworks(libvirt.CONNECT_LIST_NETWORKS_ACTIVE)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
for _, net := range nets {
|
|
|
|
_ = net.Free()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for _, net := range nets {
|
|
|
|
name, err := net.GetName()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if name != networkName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := net.Destroy(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *libvirtInstance) deleteDomain() error {
|
|
|
|
doms, err := l.conn.ListAllDomains(libvirt.CONNECT_LIST_DOMAINS_ACTIVE)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
for _, dom := range doms {
|
|
|
|
_ = dom.Free()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for _, dom := range doms {
|
|
|
|
name, err := dom.GetName()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if name != domainName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := dom.Destroy(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *libvirtInstance) deleteVolume(pool *libvirt.StoragePool) error {
|
|
|
|
volumes, err := pool.ListAllStorageVolumes(0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
for _, volume := range volumes {
|
|
|
|
_ = volume.Free()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for _, volume := range volumes {
|
|
|
|
name, err := volume.GetName()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if name != stateDiskName && name != bootDiskName && name != baseDiskName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := volume.Delete(libvirt.STORAGE_VOL_DELETE_NORMAL); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *libvirtInstance) deletePool() error {
|
|
|
|
pools, err := l.conn.ListAllStoragePools(libvirt.CONNECT_LIST_STORAGE_POOLS_DIR)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
for _, pool := range pools {
|
|
|
|
_ = pool.Free()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
for _, pool := range pools {
|
|
|
|
name, err := pool.GetName()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if name != diskPoolName {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
active, err := pool.IsActive()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if active {
|
|
|
|
if err := l.deleteVolume(&pool); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := pool.Destroy(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := pool.Delete(libvirt.STORAGE_POOL_DELETE_NORMAL); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If something fails and the Pool becomes inactive, we cannot delete/destroy it anymore.
|
|
|
|
// We have to undefine it in this case
|
|
|
|
if err := pool.Undefine(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *libvirtInstance) deleteLibvirtInstance() error {
|
|
|
|
var err error
|
|
|
|
err = multierr.Append(err, l.deleteNetwork())
|
|
|
|
err = multierr.Append(err, l.deleteDomain())
|
|
|
|
err = multierr.Append(err, l.deletePool())
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-11-15 09:40:49 -05:00
|
|
|
func (l *libvirtInstance) obtainMeasurements() (measurements measurements.M, err error) {
|
2022-09-12 04:32:01 -04:00
|
|
|
// sanity check
|
|
|
|
if err := l.deleteLibvirtInstance(); err != nil {
|
2022-09-12 05:04:51 -04:00
|
|
|
return nil, err
|
2022-09-12 04:32:01 -04:00
|
|
|
}
|
|
|
|
done := make(chan struct{}, 1)
|
|
|
|
serv := server.New(l.log, done)
|
|
|
|
go func() {
|
|
|
|
if err := serv.ListenAndServe("8080"); err != http.ErrServerClosed {
|
|
|
|
l.log.With(zap.Error(err)).Fatalf("Failed to serve")
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
defer func() {
|
|
|
|
err = multierr.Append(err, l.deleteLibvirtInstance())
|
|
|
|
}()
|
|
|
|
if err := l.createLibvirtInstance(); err != nil {
|
2022-09-12 05:04:51 -04:00
|
|
|
return nil, err
|
2022-09-12 04:32:01 -04:00
|
|
|
}
|
|
|
|
sigs := make(chan os.Signal, 1)
|
|
|
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
l.log.Infof("waiting for PCRs or CTRL+C")
|
|
|
|
select {
|
|
|
|
case <-done:
|
|
|
|
break
|
|
|
|
case <-sigs:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
signal.Stop(sigs)
|
|
|
|
close(sigs)
|
|
|
|
if err := serv.Shutdown(); err != nil {
|
2022-09-12 05:04:51 -04:00
|
|
|
return nil, err
|
2022-09-12 04:32:01 -04:00
|
|
|
}
|
|
|
|
close(done)
|
2022-09-12 05:04:51 -04:00
|
|
|
|
|
|
|
return serv.GetMeasurements(), nil
|
2022-09-12 04:32:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
2022-09-12 05:04:51 -04:00
|
|
|
var imageLocation, imageType, outFile string
|
|
|
|
var verboseLog bool
|
2022-09-12 04:32:01 -04:00
|
|
|
flag.StringVar(&imageLocation, "path", "", "path to the image to measure (required)")
|
|
|
|
flag.StringVar(&imageType, "type", "", "type of the image. One of 'qcow2' or 'raw' (required)")
|
2022-09-12 05:04:51 -04:00
|
|
|
flag.StringVar(&outFile, "file", "-", "path to output file, or '-' for stdout")
|
|
|
|
flag.BoolVar(&verboseLog, "v", false, "verbose logging")
|
|
|
|
|
2022-09-12 04:32:01 -04:00
|
|
|
flag.Parse()
|
2022-09-12 05:04:51 -04:00
|
|
|
log := logger.New(logger.JSONLog, zapcore.DebugLevel)
|
|
|
|
if !verboseLog {
|
|
|
|
log = log.WithIncreasedLevel(zapcore.FatalLevel) // Only print fatal errors in non-verbose mode
|
|
|
|
}
|
2022-09-12 04:32:01 -04:00
|
|
|
|
|
|
|
if imageLocation == "" || imageType == "" {
|
|
|
|
flag.Usage()
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
volumeBootXMLConfig.BackingStore.Format.Type = imageType
|
|
|
|
domainXMLConfig.Devices.Disks[1].Driver.Type = imageType
|
|
|
|
|
|
|
|
conn, err := libvirt.NewConnect("qemu:///system")
|
|
|
|
if err != nil {
|
|
|
|
log.With(zap.Error(err)).Fatalf("Failed to connect to libvirt")
|
|
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
|
|
|
|
lInstance := libvirtInstance{
|
|
|
|
conn: conn,
|
|
|
|
log: log,
|
|
|
|
imagePath: imageLocation,
|
|
|
|
}
|
|
|
|
|
2022-09-12 05:04:51 -04:00
|
|
|
measurements, err := lInstance.obtainMeasurements()
|
|
|
|
if err != nil {
|
2022-09-12 04:32:01 -04:00
|
|
|
log.With(zap.Error(err)).Fatalf("Failed to obtain PCR measurements")
|
|
|
|
}
|
2022-09-12 05:04:51 -04:00
|
|
|
log.Infof("instances terminated successfully")
|
|
|
|
|
|
|
|
output, err := yaml.Marshal(measurements)
|
|
|
|
if err != nil {
|
|
|
|
log.With(zap.Error(err)).Fatalf("Failed to marshal measurements")
|
|
|
|
}
|
|
|
|
|
|
|
|
if outFile == "-" {
|
|
|
|
fmt.Println(string(output))
|
|
|
|
} else {
|
|
|
|
if err := os.WriteFile(outFile, output, 0o644); err != nil {
|
|
|
|
log.With(zap.Error(err)).Fatalf("Failed to write measurements to file")
|
|
|
|
}
|
|
|
|
}
|
2022-09-12 04:32:01 -04:00
|
|
|
}
|