From eee2df9723a217e6f8b22c0398bd703f0b5c422f Mon Sep 17 00:00:00 2001 From: Benedict Schlueter Date: Mon, 12 Sep 2022 10:32:01 +0200 Subject: [PATCH] add image-measurement tool (#106) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Benedict Schlueter Co-authored-by: Daniel Weiße --- hack/go.mod | 1 + hack/go.sum | 7 +- hack/image-measurement/definitions.go | 317 +++++++++++++++++++++ hack/image-measurement/main.go | 350 ++++++++++++++++++++++++ hack/image-measurement/server/server.go | 81 ++++++ image/build/.gitkeep | 0 6 files changed, 752 insertions(+), 4 deletions(-) create mode 100644 hack/image-measurement/definitions.go create mode 100644 hack/image-measurement/main.go create mode 100644 hack/image-measurement/server/server.go delete mode 100644 image/build/.gitkeep diff --git a/hack/go.mod b/hack/go.mod index c53f45d06..c93a990f2 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -52,6 +52,7 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v3 v3.0.1 libvirt.org/go/libvirt v1.8004.0 + libvirt.org/go/libvirtxml v1.8007.0 ) require ( diff --git a/hack/go.sum b/hack/go.sum index e96e2babb..5640bf6ab 100644 --- a/hack/go.sum +++ b/hack/go.sum @@ -455,10 +455,7 @@ github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= @@ -901,6 +898,7 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= @@ -1786,7 +1784,6 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= @@ -1885,6 +1882,8 @@ k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 h1:XmRqFcQlCy/lKRZ39j+RVpokYNroH k8s.io/utils v0.0.0-20220812165043-ad590609e2e5/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= libvirt.org/go/libvirt v1.8004.0 h1:SKa5hQNKQfc1VjU4LqLMorqPCxC1lplnz8LwLiMrPyM= libvirt.org/go/libvirt v1.8004.0/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ= +libvirt.org/go/libvirtxml v1.8007.0 h1:vklDVA/xhA6mnQffFDTFkqf76TKHcKmsPeU/1qQY06M= +libvirt.org/go/libvirtxml v1.8007.0/go.mod h1:7Oq2BLDstLr/XtoQD8Fr3mfDNrzlI3utYKySXF2xkng= pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/hack/image-measurement/definitions.go b/hack/image-measurement/definitions.go new file mode 100644 index 000000000..e5bd63aaa --- /dev/null +++ b/hack/image-measurement/definitions.go @@ -0,0 +1,317 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package main + +import ( + "libvirt.org/go/libvirtxml" +) + +var ( + libvirtImagePath = "/var/lib/libvirt/images/" + baseDiskName = "constellation-measurement" + stateDiskName = "constellation-measurement-state" + bootDiskName = "constellation-measurement-boot" + diskPoolName = "constellation-measurement-pool" + domainName = "constellation-measurement-vm" + networkName = "constellation-measurement-net" + + networkXMLConfig = libvirtxml.Network{ + Name: networkName, + Forward: &libvirtxml.NetworkForward{ + Mode: "nat", + NAT: &libvirtxml.NetworkForwardNAT{ + Ports: []libvirtxml.NetworkForwardNATPort{ + { + Start: 1024, + End: 65535, + }, + }, + }, + }, + Bridge: &libvirtxml.NetworkBridge{ + Name: "virbr1", + STP: "on", + Delay: "0", + }, + DNS: &libvirtxml.NetworkDNS{ + Enable: "yes", + }, + IPs: []libvirtxml.NetworkIP{ + { + Family: "ipv4", + Address: "10.42.0.1", + Prefix: 16, + DHCP: &libvirtxml.NetworkDHCP{ + Ranges: []libvirtxml.NetworkDHCPRange{ + { + Start: "10.42.0.2", + End: "10.42.255.254", + }, + }, + }, + }, + }, + } + poolXMLConfig = libvirtxml.StoragePool{ + Name: diskPoolName, + Type: "dir", + Source: &libvirtxml.StoragePoolSource{}, + Target: &libvirtxml.StoragePoolTarget{ + Path: libvirtImagePath, + Permissions: &libvirtxml.StoragePoolTargetPermissions{ + Owner: "0", + Group: "0", + Mode: "0711", + }, + }, + } + volumeBootXMLConfig = libvirtxml.StorageVolume{ + Type: "file", + Name: bootDiskName, + Target: &libvirtxml.StorageVolumeTarget{ + Path: libvirtImagePath + bootDiskName, + Format: &libvirtxml.StorageVolumeTargetFormat{ + Type: "qcow2", + }, + }, + BackingStore: &libvirtxml.StorageVolumeBackingStore{ + Path: libvirtImagePath + baseDiskName, + Format: &libvirtxml.StorageVolumeTargetFormat{}, + }, + Capacity: &libvirtxml.StorageVolumeSize{ + Unit: "GiB", + Value: uint64(10), + }, + } + + volumeBaseXMLConfig = libvirtxml.StorageVolume{ + Type: "file", + Name: baseDiskName, + Target: &libvirtxml.StorageVolumeTarget{ + Path: libvirtImagePath + baseDiskName, + Format: &libvirtxml.StorageVolumeTargetFormat{ + Type: "qcow2", + }, + }, + Capacity: &libvirtxml.StorageVolumeSize{ + Unit: "GiB", + Value: uint64(10), + }, + } + + volumeStateXMLConfig = libvirtxml.StorageVolume{ + Type: "file", + Name: stateDiskName, + Target: &libvirtxml.StorageVolumeTarget{ + Path: libvirtImagePath + stateDiskName, + Format: &libvirtxml.StorageVolumeTargetFormat{ + Type: "qcow2", + }, + }, + Capacity: &libvirtxml.StorageVolumeSize{ + Unit: "GiB", + Value: uint64(10), + }, + } + + port = uint(0) + domainXMLConfig = libvirtxml.Domain{ + Title: "measurement-VM", + Name: domainName, + Type: "kvm", + Memory: &libvirtxml.DomainMemory{ + Value: 2, + Unit: "GiB", + }, + Resource: &libvirtxml.DomainResource{ + Partition: "/machine", + }, + VCPU: &libvirtxml.DomainVCPU{ + Placement: "static", + Current: 2, + Value: 2, + }, + CPU: &libvirtxml.DomainCPU{ + Mode: "custom", + Model: &libvirtxml.DomainCPUModel{ + Fallback: "forbid", + Value: "qemu64", + }, + Features: []libvirtxml.DomainCPUFeature{ + { + Policy: "require", + Name: "x2apic", + }, + { + Policy: "require", + Name: "hypervisor", + }, + { + Policy: "require", + Name: "lahf_lm", + }, + { + Policy: "disable", + Name: "svm", + }, + }, + }, + Features: &libvirtxml.DomainFeatureList{ + ACPI: &libvirtxml.DomainFeature{}, + PAE: &libvirtxml.DomainFeature{}, + SMM: &libvirtxml.DomainFeatureSMM{ + State: "on", + }, + APIC: &libvirtxml.DomainFeatureAPIC{}, + }, + + OS: &libvirtxml.DomainOS{ + // If Firmware is set, Loader and NVRam will be chosen automatically + Firmware: "efi", + Type: &libvirtxml.DomainOSType{ + Arch: "x86_64", + Machine: "q35", + Type: "hvm", + }, + BootDevices: []libvirtxml.DomainBootDevice{ + { + Dev: "hd", + }, + }, + }, + Devices: &libvirtxml.DomainDeviceList{ + Emulator: "/usr/bin/qemu-system-x86_64", + Disks: []libvirtxml.DomainDisk{ + { + Device: "disk", + Driver: &libvirtxml.DomainDiskDriver{ + Name: "qemu", + Type: "qcow2", + }, + Target: &libvirtxml.DomainDiskTarget{ + Dev: "sda", + Bus: "scsi", + }, + Source: &libvirtxml.DomainDiskSource{ + Index: 2, + Volume: &libvirtxml.DomainDiskSourceVolume{ + Pool: diskPoolName, + Volume: bootDiskName, + }, + }, + }, + { + Device: "disk", + Driver: &libvirtxml.DomainDiskDriver{ + Name: "qemu", + }, + Target: &libvirtxml.DomainDiskTarget{ + Dev: "vda", + Bus: "virtio", + }, + Source: &libvirtxml.DomainDiskSource{ + Index: 1, + Volume: &libvirtxml.DomainDiskSourceVolume{ + Pool: diskPoolName, + Volume: stateDiskName, + }, + }, + Alias: &libvirtxml.DomainAlias{ + Name: "virtio-disk1", + }, + }, + }, + Controllers: []libvirtxml.DomainController{ + { + Type: "scsi", + Model: "virtio-scsi", + }, + }, + TPMs: []libvirtxml.DomainTPM{ + { + Model: "tpm-tis", + Backend: &libvirtxml.DomainTPMBackend{ + Emulator: &libvirtxml.DomainTPMBackendEmulator{ + Version: "2.0", + ActivePCRBanks: &libvirtxml.DomainTPMBackendPCRBanks{ + SHA1: &libvirtxml.DomainTPMBackendPCRBank{}, + SHA256: &libvirtxml.DomainTPMBackendPCRBank{}, + SHA384: &libvirtxml.DomainTPMBackendPCRBank{}, + SHA512: &libvirtxml.DomainTPMBackendPCRBank{}, + }, + }, + }, + }, + }, + Interfaces: []libvirtxml.DomainInterface{ + { + Model: &libvirtxml.DomainInterfaceModel{ + Type: "virtio", + }, + Source: &libvirtxml.DomainInterfaceSource{ + Network: &libvirtxml.DomainInterfaceSourceNetwork{ + Network: networkName, + Bridge: "virbr1", + }, + }, + Alias: &libvirtxml.DomainAlias{ + Name: "net0", + }, + }, + }, + Serials: []libvirtxml.DomainSerial{ + { + Source: &libvirtxml.DomainChardevSource{ + Pty: &libvirtxml.DomainChardevSourcePty{ + Path: "/dev/pts/4", + }, + }, + Target: &libvirtxml.DomainSerialTarget{ + Type: "isa-serial", + Port: &port, + Model: &libvirtxml.DomainSerialTargetModel{ + Name: "isa-serial", + }, + }, + Log: &libvirtxml.DomainChardevLog{ + File: "/tmp/libvirt.log", + }, + }, + }, + Consoles: []libvirtxml.DomainConsole{ + { + TTY: "/dev/pts/4", + Source: &libvirtxml.DomainChardevSource{ + Pty: &libvirtxml.DomainChardevSourcePty{ + Path: "/dev/pts/4", + }, + }, + Target: &libvirtxml.DomainConsoleTarget{ + Type: "serial", + Port: &port, + }, + }, + }, + RNGs: []libvirtxml.DomainRNG{ + { + Model: "virtio", + Backend: &libvirtxml.DomainRNGBackend{ + Random: &libvirtxml.DomainRNGBackendRandom{ + Device: "/dev/urandom", + }, + }, + Alias: &libvirtxml.DomainAlias{ + Name: "rng0", + }, + }, + }, + }, + OnPoweroff: "destroy", + OnCrash: "destroy", + OnReboot: "restart", + } +) diff --git a/hack/image-measurement/main.go b/hack/image-measurement/main.go new file mode 100644 index 000000000..222f09861 --- /dev/null +++ b/hack/image-measurement/main.go @@ -0,0 +1,350 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package main + +import ( + "flag" + "fmt" + "io" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/edgelesssys/constellation/hack/image-measurement/server" + "github.com/edgelesssys/constellation/internal/logger" + "go.uber.org/multierr" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "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 +} + +func (l *libvirtInstance) obtainMeasurements() (err error) { + // sanity check + if err := l.deleteLibvirtInstance(); err != nil { + return err + } + 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 { + return err + } + 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 { + return err + } + close(done) + return nil +} + +func main() { + var imageLocation string + var imageType string + 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)") + flag.Parse() + log := logger.New(logger.JSONLog, zapcore.InfoLevel) + + 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, + } + + if err := lInstance.obtainMeasurements(); err != nil { + log.With(zap.Error(err)).Fatalf("Failed to obtain PCR measurements") + } + log.Infof("instaces terminated successfully") +} diff --git a/hack/image-measurement/server/server.go b/hack/image-measurement/server/server.go new file mode 100644 index 000000000..68f385fdc --- /dev/null +++ b/hack/image-measurement/server/server.go @@ -0,0 +1,81 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package server + +import ( + "context" + "encoding/json" + "net" + "net/http" + + "github.com/edgelesssys/constellation/internal/logger" + "go.uber.org/zap" +) + +type Server struct { + log *logger.Logger + server http.Server + done chan<- struct{} +} + +func New(log *logger.Logger, done chan<- struct{}) *Server { + return &Server{ + log: log, + done: done, + } +} + +func (s *Server) ListenAndServe(port string) error { + mux := http.NewServeMux() + mux.Handle("/pcrs", http.HandlerFunc(s.logPCRs)) + + s.server = http.Server{ + Handler: mux, + } + + lis, err := net.Listen("tcp", net.JoinHostPort("", port)) + if err != nil { + return err + } + s.log.Infof("Starting QEMU metadata API on %s", lis.Addr()) + return s.server.Serve(lis) +} + +func (s *Server) Shutdown() error { + return s.server.Shutdown(context.Background()) +} + +// logPCRs allows QEMU instances to export their TPM state during boot. +func (s *Server) logPCRs(w http.ResponseWriter, r *http.Request) { + log := s.log.With(zap.String("peer", r.RemoteAddr)) + if r.Method != http.MethodPost { + log.With(zap.String("method", r.Method)).Errorf("Invalid method for /log") + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + log.Infof("Serving POST request for /pcrs") + + if r.Body == nil { + log.Errorf("Request body is empty") + http.Error(w, "Request body is empty", http.StatusBadRequest) + return + } + + // unmarshal the request body into a map of PCRs + var pcrs map[uint32][]byte + if err := json.NewDecoder(r.Body).Decode(&pcrs); err != nil { + log.With(zap.Error(err)).Errorf("Failed to read request body") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + log.Infof("PCR 4 %x", pcrs[4]) + log.Infof("PCR 8 %x", pcrs[8]) + log.Infof("PCR 9 %x", pcrs[9]) + s.done <- struct{}{} +} diff --git a/image/build/.gitkeep b/image/build/.gitkeep deleted file mode 100644 index e69de29bb..000000000