mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-04-27 11:31:05 +00:00
runtime: add support for io.katacontainers.config.runtime.cc_init_data
io.katacontainers.config.runtime.cc_init_data specifies initdata used by the pod in base64(gzip(initdata toml)) format. The initdata will be encapsulated into an initdata image and mount it as a raw block device to the guest. The initdata image will be aligned with 512 bytes, which is chosen as a usual sector size supported by different hypervisors like qemu, clh and dragonball. Note that this patch only adds support for qemu hypervisor. Signed-off-by: Xynnn007 <xynnn@linux.alibaba.com>
This commit is contained in:
parent
17d0db9865
commit
91bb6b7c34
@ -15,6 +15,7 @@ package qemu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -328,6 +329,9 @@ type Object struct {
|
||||
// SnpIdAuth is the 4096-byte, base64-encoded blob to provide the ‘ID Authentication Information Structure’
|
||||
// for the SNP_LAUNCH_FINISH command defined in the SEV-SNP firmware ABI (default: all-zero)
|
||||
SnpIdAuth string
|
||||
|
||||
// Raw byte slice of initdata digest
|
||||
InitdataDigest []byte
|
||||
}
|
||||
|
||||
// Valid returns true if the Object structure is valid and complete.
|
||||
@ -353,6 +357,12 @@ func (object Object) Valid() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func adjustProperLength(data []byte, len int) []byte {
|
||||
adjusted := make([]byte, len)
|
||||
copy(adjusted, data)
|
||||
return adjusted
|
||||
}
|
||||
|
||||
// QemuParams returns the qemu parameters built out of this Object device.
|
||||
func (object Object) QemuParams(config *Config) []string {
|
||||
var objectParams []string
|
||||
@ -394,6 +404,14 @@ func (object Object) QemuParams(config *Config) []string {
|
||||
driveParams = append(driveParams, "if=pflash,format=raw,readonly=on")
|
||||
driveParams = append(driveParams, fmt.Sprintf("file=%s", object.File))
|
||||
case SNPGuest:
|
||||
if len(object.InitdataDigest) > 0 {
|
||||
// due to https://github.com/confidential-containers/qemu/blob/amd-snp-202402240000/qapi/qom.json#L926-L929
|
||||
// hostdata in SEV-SNP should be exactly 32 bytes
|
||||
hostdataSlice := adjustProperLength(object.InitdataDigest, 32)
|
||||
hostdata := base64.StdEncoding.EncodeToString(hostdataSlice)
|
||||
objectParams = append(objectParams, fmt.Sprintf("host-data=%s", hostdata))
|
||||
}
|
||||
|
||||
objectParams = append(objectParams, string(object.Type))
|
||||
objectParams = append(objectParams, fmt.Sprintf("id=%s", object.ID))
|
||||
objectParams = append(objectParams, fmt.Sprintf("cbitpos=%d", object.CBitPos))
|
||||
@ -485,10 +503,21 @@ func getQgsSocketAddress(portNum uint32) SocketAddress {
|
||||
|
||||
func prepareTDXObject(object Object) string {
|
||||
qgsSocket := getQgsSocketAddress(object.QgsPort)
|
||||
// due to https://github.com/intel-staging/qemu-tdx/blob/tdx-qemu-upstream-2023.9.21-v8.1.0/qapi/qom.json#L880
|
||||
// mrconfigid in TDX should be exactly 48 bytes
|
||||
|
||||
var mrconfigid string
|
||||
if len(object.InitdataDigest) > 0 {
|
||||
mrconfigidSlice := adjustProperLength(object.InitdataDigest, 48)
|
||||
mrconfigid = base64.StdEncoding.EncodeToString(mrconfigidSlice)
|
||||
|
||||
} else {
|
||||
mrconfigid = ""
|
||||
}
|
||||
tdxObject := TdxQomObject{
|
||||
string(object.Type), // qom-type
|
||||
object.ID, // id
|
||||
"", // mrconfigid
|
||||
mrconfigid, // mrconfigid
|
||||
"", // mrowner
|
||||
"", // mrownerconfig
|
||||
qgsSocket, // quote-generation-socket
|
||||
|
@ -7,11 +7,16 @@
|
||||
package oci
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -21,6 +26,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
ctrAnnotations "github.com/containerd/containerd/pkg/cri/annotations"
|
||||
podmanAnnotations "github.com/containers/podman/v4/pkg/annotations"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
@ -31,6 +37,7 @@ import (
|
||||
vc "github.com/kata-containers/kata-containers/src/runtime/virtcontainers"
|
||||
|
||||
"github.com/kata-containers/kata-containers/src/runtime/pkg/device/config"
|
||||
kataTypes "github.com/kata-containers/kata-containers/src/runtime/pkg/types"
|
||||
exp "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/experimental"
|
||||
vcAnnotations "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/annotations"
|
||||
dockershimAnnotations "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/annotations/dockershim"
|
||||
@ -485,6 +492,10 @@ func addHypervisorConfigOverrides(ocispec specs.Spec, config *vc.SandboxConfig,
|
||||
return err
|
||||
}
|
||||
|
||||
if err := addHypervisorInitdataOverrides(ocispec, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if value, ok := ocispec.Annotations[vcAnnotations.MachineType]; ok {
|
||||
if value != "" {
|
||||
config.HypervisorConfig.HypervisorMachineType = value
|
||||
@ -556,9 +567,6 @@ func addHypervisorConfigOverrides(ocispec specs.Spec, config *vc.SandboxConfig,
|
||||
|
||||
config.HypervisorConfig.SGXEPCSize = size
|
||||
}
|
||||
if initdata, ok := ocispec.Annotations[vcAnnotations.Initdata]; ok {
|
||||
config.HypervisorConfig.Initdata = initdata
|
||||
}
|
||||
|
||||
if err := addHypervisorGPUOverrides(ocispec, config); err != nil {
|
||||
return err
|
||||
@ -919,6 +927,53 @@ func addHypervisorNetworkOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfi
|
||||
})
|
||||
}
|
||||
|
||||
func addHypervisorInitdataOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig) error {
|
||||
if value, ok := ocispec.Annotations[vcAnnotations.Initdata]; ok {
|
||||
if len(value) == 0 {
|
||||
ociLog.Debug("Initdata annotation set without any value")
|
||||
return nil
|
||||
}
|
||||
b64Reader := base64.NewDecoder(base64.StdEncoding, strings.NewReader(value))
|
||||
gzipReader, err := gzip.NewReader(b64Reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("initdata create gzip reader error: %v", err)
|
||||
}
|
||||
|
||||
initdataToml, err := io.ReadAll(gzipReader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("uncompressing initdata with gzip error: %v", err)
|
||||
}
|
||||
|
||||
initdataStr := string(initdataToml)
|
||||
var initdata kataTypes.Initdata
|
||||
if _, err := toml.Decode(initdataStr, &initdata); err != nil {
|
||||
return fmt.Errorf("parsing initdata annotation failed: %v", err)
|
||||
}
|
||||
|
||||
var initdataDigest []byte
|
||||
var h hash.Hash
|
||||
switch initdata.Algorithm {
|
||||
case "sha256":
|
||||
h = sha256.New()
|
||||
case "sha384":
|
||||
h = sha512.New384()
|
||||
case "sha512":
|
||||
h = sha512.New()
|
||||
}
|
||||
|
||||
h.Write([]byte(initdataToml))
|
||||
initdataDigest = h.Sum(nil)
|
||||
|
||||
ociLog.Debugf("Initdata digest set to: %v", initdataDigest)
|
||||
|
||||
sbConfig.HypervisorConfig.Initdata = initdataStr
|
||||
|
||||
sbConfig.HypervisorConfig.InitdataDigest = initdataDigest
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addRuntimeConfigOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig, runtime RuntimeConfig) error {
|
||||
|
||||
if err := newAnnotationConfiguration(ocispec, vcAnnotations.DisableGuestSeccomp).setBool(func(disableGuestSeccomp bool) {
|
||||
|
@ -771,10 +771,23 @@ func TestAddRemoteHypervisorAnnotations(t *testing.T) {
|
||||
assert.NoError(err)
|
||||
|
||||
// When initdata specified, remote hypervisor annotations do have the annotation added.
|
||||
ocispec.Annotations[vcAnnotations.Initdata] = "initdata"
|
||||
// Note that the initdata annotation parsing logic will extract it into plaintext
|
||||
ocispec.Annotations[vcAnnotations.Initdata] = "H4sIAFlC92cAAytLLSrOzM9TsFVQMtAz1DNQ4krMSc8vyizJyAWJFWckGpmaKXFFpySWJMZyKSUm6pXk5+YoAeXU1dW5QJhLKTklA4toQX5OZnKlXlFqej6yBABS/5JkcQAAAA=="
|
||||
err = addAnnotations(ocispec, &sbConfig, runtimeConfig)
|
||||
assert.NoError(err)
|
||||
assert.Equal(sbConfig.HypervisorConfig.Initdata, "initdata")
|
||||
assert.Equal(sbConfig.HypervisorConfig.Initdata, `version = "0.1.0"
|
||||
algorithm = "sha256"
|
||||
[data]
|
||||
"aa.toml" = '''
|
||||
'''
|
||||
|
||||
"cdh.toml" = '''
|
||||
'''
|
||||
|
||||
"policy.rego" = '''
|
||||
'''
|
||||
`)
|
||||
assert.Equal(sbConfig.HypervisorConfig.InitdataDigest, []byte{0xc6, 0x69, 0x4b, 0xb7, 0xa2, 0x9d, 0x6f, 0x37, 0xec, 0x72, 0xa1, 0x55, 0x82, 0xe0, 0x4, 0xb9, 0xf3, 0x14, 0x21, 0x59, 0x68, 0x2d, 0xb8, 0x50, 0x9a, 0x30, 0x44, 0x7, 0x41, 0x9a, 0x49, 0xe5})
|
||||
|
||||
// When GPU annotations are specified, remote hypervisor annotations have the annotation added
|
||||
ocispec.Annotations[vcAnnotations.DefaultGPUs] = "-1"
|
||||
@ -879,7 +892,9 @@ func TestAddRuntimeAnnotations(t *testing.T) {
|
||||
ocispec.Annotations[vcAnnotations.DisableNewNetNs] = "true"
|
||||
ocispec.Annotations[vcAnnotations.InterNetworkModel] = "macvtap"
|
||||
ocispec.Annotations[vcAnnotations.CreateContainerTimeout] = "100"
|
||||
ocispec.Annotations[vcAnnotations.Initdata] = "initdata"
|
||||
|
||||
// Note that the initdata annotation parsing logic will extract it into plaintext
|
||||
ocispec.Annotations[vcAnnotations.Initdata] = "H4sIAFlC92cAAytLLSrOzM9TsFVQMtAz1DNQ4krMSc8vyizJyAWJFWckGpmaKXFFpySWJMZyKSUm6pXk5+YoAeXU1dW5QJhLKTklA4toQX5OZnKlXlFqej6yBABS/5JkcQAAAA=="
|
||||
|
||||
addAnnotations(ocispec, &config, runtimeConfig)
|
||||
assert.Equal(config.DisableGuestSeccomp, true)
|
||||
@ -887,7 +902,20 @@ func TestAddRuntimeAnnotations(t *testing.T) {
|
||||
assert.Equal(config.NetworkConfig.DisableNewNetwork, true)
|
||||
assert.Equal(config.NetworkConfig.InterworkingModel, vc.NetXConnectMacVtapModel)
|
||||
assert.Equal(config.CreateContainerTimeout, uint64(100))
|
||||
assert.Equal(config.HypervisorConfig.Initdata, "initdata")
|
||||
assert.Equal(config.HypervisorConfig.Initdata, `version = "0.1.0"
|
||||
algorithm = "sha256"
|
||||
[data]
|
||||
"aa.toml" = '''
|
||||
'''
|
||||
|
||||
"cdh.toml" = '''
|
||||
'''
|
||||
|
||||
"policy.rego" = '''
|
||||
'''
|
||||
`)
|
||||
assert.Equal(config.HypervisorConfig.InitdataDigest, []byte{0xc6, 0x69, 0x4b, 0xb7, 0xa2, 0x9d, 0x6f, 0x37, 0xec, 0x72, 0xa1, 0x55, 0x82, 0xe0, 0x4, 0xb9, 0xf3, 0x14, 0x21, 0x59, 0x68, 0x2d, 0xb8, 0x50, 0x9a, 0x30, 0x44, 0x7, 0x41, 0x9a, 0x49, 0xe5})
|
||||
|
||||
}
|
||||
|
||||
func TestRegexpContains(t *testing.T) {
|
||||
|
@ -10,3 +10,9 @@ const (
|
||||
KataRuntimeNameRegexp = `io\.containerd\.kata.*\.v2`
|
||||
ContainerdRuntimeTaskPath = "io.containerd.runtime.v2.task"
|
||||
)
|
||||
|
||||
type Initdata struct {
|
||||
Version string `toml:"version"`
|
||||
Algorithm string `toml:"algorithm"`
|
||||
Data map[string]string `toml:"data"`
|
||||
}
|
||||
|
@ -682,6 +682,14 @@ type HypervisorConfig struct {
|
||||
// Initdata defines the initdata passed into guest when CreateVM
|
||||
Initdata string
|
||||
|
||||
// InitdataDigest represents opaque binary data attached to a TEE and typically used
|
||||
// for Guest attestation. This will be encoded in the format expected by QEMU for each TEE type.
|
||||
InitdataDigest []byte
|
||||
|
||||
// The initdata image on the host side to store the initdata and be mounted
|
||||
// as a raw block device to guest
|
||||
InitdataImage string
|
||||
|
||||
// GPU specific annotations (currently only applicable for Remote Hypervisor)
|
||||
//DefaultGPUs specifies the number of GPUs required for the Kata VM
|
||||
DefaultGPUs uint32
|
||||
|
@ -9,7 +9,10 @@ package virtcontainers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -394,6 +397,23 @@ func (q *qemu) createQmpSocket() ([]govmmQemu.QMPSocket, error) {
|
||||
return sockets, nil
|
||||
}
|
||||
|
||||
func (q *qemu) buildInitdataDevice(devices []govmmQemu.Device, InitdataImage string) []govmmQemu.Device {
|
||||
device := govmmQemu.BlockDevice{
|
||||
Driver: govmmQemu.VirtioBlock,
|
||||
Transport: govmmQemu.TransportPCI,
|
||||
ID: "initdata",
|
||||
File: InitdataImage,
|
||||
SCSI: false,
|
||||
WCE: false,
|
||||
AIO: govmmQemu.Threads,
|
||||
Interface: "none",
|
||||
Format: "raw",
|
||||
}
|
||||
|
||||
devices = append(devices, device)
|
||||
return devices
|
||||
}
|
||||
|
||||
func (q *qemu) buildDevices(ctx context.Context, kernelPath string) ([]govmmQemu.Device, *govmmQemu.IOThread, *govmmQemu.Kernel, error) {
|
||||
var devices []govmmQemu.Device
|
||||
|
||||
@ -540,6 +560,94 @@ func (q *qemu) createVirtiofsDaemon(sharedPath string) (VirtiofsDaemon, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// prepareInitdataImage will create an image with a very simple layout
|
||||
//
|
||||
// There will be multiple sectors. The first 8 bytes are Magic number "initdata".
|
||||
// Then a "length" field of 8 bytes follows (unsigned int64).
|
||||
// Finally the gzipped initdata toml. The image will be padded to an
|
||||
// integer multiple of the sector size for alignment.
|
||||
//
|
||||
// offset 0 8 16
|
||||
// 0 'i' 'n' 'i' 't' 'd' 'a' 't' 'a' | gzip length in le |
|
||||
// 16 gzip(initdata toml) ...
|
||||
// (end of the last sector) '\0' paddings
|
||||
func prepareInitdataImage(initdata string, imagePath string) error {
|
||||
SectorSize := 512
|
||||
var buf bytes.Buffer
|
||||
gzipper := gzip.NewWriter(&buf)
|
||||
defer gzipper.Close()
|
||||
|
||||
gzipper.Write([]byte(initdata))
|
||||
err := gzipper.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to compress initdata: %v", err)
|
||||
}
|
||||
|
||||
compressedInitdata := buf.Bytes()
|
||||
|
||||
compressedInitdataLength := len(compressedInitdata)
|
||||
lengthBuffer := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(lengthBuffer, uint64(compressedInitdataLength))
|
||||
|
||||
paddingLength := (compressedInitdataLength+16+SectorSize-1)/SectorSize*SectorSize - (compressedInitdataLength + 16)
|
||||
paddingBuffer := make([]byte, paddingLength)
|
||||
|
||||
file, err := os.OpenFile(imagePath, os.O_CREATE|os.O_RDWR, 0640)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create initdata image: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = file.Write([]byte("initdata"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write magic number to initdata image: %v", err)
|
||||
}
|
||||
|
||||
_, err = file.Write(lengthBuffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write data length to initdata image: %v", err)
|
||||
}
|
||||
|
||||
_, err = file.Write([]byte(compressedInitdata))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write compressed initdata to initdata image: %v", err)
|
||||
}
|
||||
|
||||
_, err = file.Write(paddingBuffer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write compressed initdata to initdata image: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *qemu) prepareInitdataMount(config *HypervisorConfig) error {
|
||||
if len(config.Initdata) == 0 {
|
||||
q.Logger().Info("No initdata provided. Skip prepare initdata device")
|
||||
return nil
|
||||
}
|
||||
|
||||
q.Logger().Info("Start to prepare initdata")
|
||||
initdataWorkdir := filepath.Join("/run/kata-containers/shared/initdata", q.id)
|
||||
initdataImagePath := filepath.Join(initdataWorkdir, "data.img")
|
||||
|
||||
err := os.MkdirAll(initdataWorkdir, 0755)
|
||||
if err != nil {
|
||||
q.Logger().WithField("initdata", "create initdata image path").WithError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = prepareInitdataImage(config.Initdata, initdataImagePath)
|
||||
if err != nil {
|
||||
q.Logger().WithField("initdata", "prepare initdata image").WithError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
config.InitdataImage = initdataImagePath
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateVM is the Hypervisor VM creation implementation for govmmQemu.
|
||||
func (q *qemu) CreateVM(ctx context.Context, id string, network Network, hypervisorConfig *HypervisorConfig) error {
|
||||
// Save the tracing context
|
||||
@ -552,6 +660,10 @@ func (q *qemu) CreateVM(ctx context.Context, id string, network Network, hypervi
|
||||
return err
|
||||
}
|
||||
|
||||
if err := q.prepareInitdataMount(hypervisorConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
machine, err := q.getQemuMachine()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -650,6 +762,10 @@ func (q *qemu) CreateVM(ctx context.Context, id string, network Network, hypervi
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hypervisorConfig.Initdata) > 0 {
|
||||
devices = q.buildInitdataDevice(devices, hypervisorConfig.InitdataImage)
|
||||
}
|
||||
|
||||
// some devices configuration may also change kernel params, make sure this is called afterwards
|
||||
kernel.Params = q.kernelParameters()
|
||||
q.checkBpfEnabled()
|
||||
@ -681,7 +797,7 @@ func (q *qemu) CreateVM(ctx context.Context, id string, network Network, hypervi
|
||||
Debug: hypervisorConfig.Debug,
|
||||
}
|
||||
|
||||
qemuConfig.Devices, qemuConfig.Bios, err = q.arch.appendProtectionDevice(qemuConfig.Devices, firmwarePath, firmwareVolumePath)
|
||||
qemuConfig.Devices, qemuConfig.Bios, err = q.arch.appendProtectionDevice(qemuConfig.Devices, firmwarePath, firmwareVolumePath, hypervisorConfig.InitdataDigest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1254,6 +1370,7 @@ func (q *qemu) StopVM(ctx context.Context, waitOnly bool) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if q.config.SharedFS == config.VirtioFS || q.config.SharedFS == config.VirtioFSNydus {
|
||||
if err := q.stopVirtiofsDaemon(ctx); err != nil {
|
||||
return err
|
||||
@ -1318,6 +1435,15 @@ func (q *qemu) cleanupVM() error {
|
||||
}).Debug("successfully removed the non root user")
|
||||
}
|
||||
|
||||
// If we have initdata, we should drop initdata image path
|
||||
hypervisorConfig := q.HypervisorConfig()
|
||||
if len(hypervisorConfig.Initdata) > 0 {
|
||||
initdataWorkdir := filepath.Join(string(filepath.Separator), "/run/kata-containers/shared/initdata", q.id)
|
||||
if err := os.RemoveAll(initdataWorkdir); err != nil {
|
||||
q.Logger().WithError(err).Warnf("failed to remove initdata work dir %s", initdataWorkdir)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -274,7 +274,7 @@ func (q *qemuAmd64) enableProtection() error {
|
||||
}
|
||||
|
||||
// append protection device
|
||||
func (q *qemuAmd64) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string) ([]govmmQemu.Device, string, error) {
|
||||
func (q *qemuAmd64) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string, initdataDigest []byte) ([]govmmQemu.Device, string, error) {
|
||||
if q.sgxEPCSize != 0 {
|
||||
devices = append(devices,
|
||||
govmmQemu.Object{
|
||||
@ -299,6 +299,7 @@ func (q *qemuAmd64) appendProtectionDevice(devices []govmmQemu.Device, firmware,
|
||||
Debug: false,
|
||||
File: firmware,
|
||||
FirmwareVolume: firmwareVolume,
|
||||
InitdataDigest: initdataDigest,
|
||||
}), "", nil
|
||||
case sevProtection:
|
||||
return append(devices,
|
||||
@ -318,6 +319,7 @@ func (q *qemuAmd64) appendProtectionDevice(devices []govmmQemu.Device, firmware,
|
||||
File: firmware,
|
||||
CBitPos: cpuid.AMDMemEncrypt.CBitPosition,
|
||||
ReducedPhysBits: 1,
|
||||
InitdataDigest: initdataDigest,
|
||||
}
|
||||
if q.snpIdBlock != "" && q.snpIdAuth != "" {
|
||||
obj.SnpIdBlock = q.snpIdBlock
|
||||
|
@ -257,7 +257,7 @@ func TestQemuAmd64AppendProtectionDevice(t *testing.T) {
|
||||
firmware := "tdvf.fd"
|
||||
var bios string
|
||||
var err error
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "", []byte(""))
|
||||
assert.NoError(err)
|
||||
|
||||
// non-protection
|
||||
@ -265,20 +265,20 @@ func TestQemuAmd64AppendProtectionDevice(t *testing.T) {
|
||||
|
||||
// pef protection
|
||||
amd64.(*qemuAmd64).protection = pefProtection
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "", []byte(""))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
// Secure Execution protection
|
||||
amd64.(*qemuAmd64).protection = seProtection
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "", []byte(""))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
// sev protection
|
||||
amd64.(*qemuAmd64).protection = sevProtection
|
||||
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "", []byte(""))
|
||||
assert.NoError(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
@ -298,7 +298,7 @@ func TestQemuAmd64AppendProtectionDevice(t *testing.T) {
|
||||
// snp protection
|
||||
amd64.(*qemuAmd64).protection = snpProtection
|
||||
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "", []uint8(nil))
|
||||
assert.NoError(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
@ -318,18 +318,19 @@ func TestQemuAmd64AppendProtectionDevice(t *testing.T) {
|
||||
// tdxProtection
|
||||
amd64.(*qemuAmd64).protection = tdxProtection
|
||||
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = amd64.appendProtectionDevice(devices, firmware, "", []byte(""))
|
||||
assert.NoError(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
expectedOut = append(expectedOut,
|
||||
govmmQemu.Object{
|
||||
Driver: govmmQemu.Loader,
|
||||
Type: govmmQemu.TDXGuest,
|
||||
ID: "tdx",
|
||||
DeviceID: fmt.Sprintf("fd%d", id),
|
||||
Debug: false,
|
||||
File: firmware,
|
||||
Driver: govmmQemu.Loader,
|
||||
Type: govmmQemu.TDXGuest,
|
||||
ID: "tdx",
|
||||
DeviceID: fmt.Sprintf("fd%d", id),
|
||||
Debug: false,
|
||||
File: firmware,
|
||||
InitdataDigest: []byte(""),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -165,7 +165,7 @@ type qemuArch interface {
|
||||
// This implementation is architecture specific, some archs may need
|
||||
// a firmware, returns a string containing the path to the firmware that should
|
||||
// be used with the -bios option, ommit -bios option if the path is empty.
|
||||
appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string) ([]govmmQemu.Device, string, error)
|
||||
appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string, initdataDigest []byte) ([]govmmQemu.Device, string, error)
|
||||
|
||||
// scans the PCIe space and returns the biggest BAR sizes for 32-bit
|
||||
// and 64-bit addressable memory
|
||||
@ -920,7 +920,7 @@ func (q *qemuArchBase) setPFlash(p []string) {
|
||||
}
|
||||
|
||||
// append protection device
|
||||
func (q *qemuArchBase) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string) ([]govmmQemu.Device, string, error) {
|
||||
func (q *qemuArchBase) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string, initdataDigest []byte) ([]govmmQemu.Device, string, error) {
|
||||
hvLogger.WithField("arch", runtime.GOARCH).Warnf("Confidential Computing has not been implemented for this architecture")
|
||||
return devices, firmware, nil
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ func (q *qemuArm64) enableProtection() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *qemuArm64) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string) ([]govmmQemu.Device, string, error) {
|
||||
func (q *qemuArm64) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string, initdataDigest []byte) ([]govmmQemu.Device, string, error) {
|
||||
err := q.enableProtection()
|
||||
if err != nil {
|
||||
hvLogger.WithField("arch", runtime.GOARCH).Error(err)
|
||||
|
@ -183,42 +183,42 @@ func TestQemuArm64AppendProtectionDevice(t *testing.T) {
|
||||
var err error
|
||||
|
||||
// no protection
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Empty(devices)
|
||||
assert.Empty(bios)
|
||||
assert.NoError(err)
|
||||
|
||||
// PEF protection
|
||||
arm64.(*qemuArm64).protection = pefProtection
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Empty(devices)
|
||||
assert.Empty(bios)
|
||||
assert.NoError(err)
|
||||
|
||||
// Secure Execution protection
|
||||
arm64.(*qemuArm64).protection = seProtection
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Empty(devices)
|
||||
assert.Empty(bios)
|
||||
assert.NoError(err)
|
||||
|
||||
// SEV protection
|
||||
arm64.(*qemuArm64).protection = sevProtection
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Empty(devices)
|
||||
assert.Empty(bios)
|
||||
assert.NoError(err)
|
||||
|
||||
// SNP protection
|
||||
arm64.(*qemuArm64).protection = snpProtection
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Empty(devices)
|
||||
assert.Empty(bios)
|
||||
assert.NoError(err)
|
||||
|
||||
// TDX protection
|
||||
arm64.(*qemuArm64).protection = tdxProtection
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = arm64.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Empty(devices)
|
||||
assert.Empty(bios)
|
||||
assert.NoError(err)
|
||||
|
@ -157,7 +157,7 @@ func (q *qemuPPC64le) enableProtection() error {
|
||||
}
|
||||
|
||||
// append protection device
|
||||
func (q *qemuPPC64le) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string) ([]govmmQemu.Device, string, error) {
|
||||
func (q *qemuPPC64le) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string, initdataDigest []byte) ([]govmmQemu.Device, string, error) {
|
||||
switch q.protection {
|
||||
case pefProtection:
|
||||
return append(devices,
|
||||
|
@ -60,7 +60,7 @@ func TestQemuPPC64leAppendProtectionDevice(t *testing.T) {
|
||||
var devices []govmmQemu.Device
|
||||
var bios, firmware string
|
||||
var err error
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.NoError(err)
|
||||
|
||||
//no protection
|
||||
@ -68,31 +68,31 @@ func TestQemuPPC64leAppendProtectionDevice(t *testing.T) {
|
||||
|
||||
//Secure Execution protection
|
||||
ppc64le.(*qemuPPC64le).protection = seProtection
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
//SEV protection
|
||||
ppc64le.(*qemuPPC64le).protection = sevProtection
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
//SNP protection
|
||||
ppc64le.(*qemuPPC64le).protection = snpProtection
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
//TDX protection
|
||||
ppc64le.(*qemuPPC64le).protection = tdxProtection
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
//PEF protection
|
||||
ppc64le.(*qemuPPC64le).protection = pefProtection
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = ppc64le.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.NoError(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
|
@ -344,7 +344,7 @@ func (q *qemuS390x) enableProtection() error {
|
||||
|
||||
// appendProtectionDevice appends a QEMU object for Secure Execution.
|
||||
// Takes devices and returns updated version. Takes BIOS and returns it (no modification on s390x).
|
||||
func (q *qemuS390x) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string) ([]govmmQemu.Device, string, error) {
|
||||
func (q *qemuS390x) appendProtectionDevice(devices []govmmQemu.Device, firmware, firmwareVolume string, initdataDigest []byte) ([]govmmQemu.Device, string, error) {
|
||||
switch q.protection {
|
||||
case seProtection:
|
||||
return append(devices,
|
||||
|
@ -111,7 +111,7 @@ func TestQemuS390xAppendProtectionDevice(t *testing.T) {
|
||||
var devices []govmmQemu.Device
|
||||
var bios, firmware string
|
||||
var err error
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.NoError(err)
|
||||
|
||||
// no protection
|
||||
@ -119,32 +119,32 @@ func TestQemuS390xAppendProtectionDevice(t *testing.T) {
|
||||
|
||||
// PEF protection
|
||||
s390x.(*qemuS390x).protection = pefProtection
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
// TDX protection
|
||||
s390x.(*qemuS390x).protection = tdxProtection
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
// SEV protection
|
||||
s390x.(*qemuS390x).protection = sevProtection
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
// SNP protection
|
||||
s390x.(*qemuS390x).protection = snpProtection
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.Error(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
// Secure Execution protection
|
||||
s390x.(*qemuS390x).protection = seProtection
|
||||
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "")
|
||||
devices, bios, err = s390x.appendProtectionDevice(devices, firmware, "", []byte(nil))
|
||||
assert.NoError(err)
|
||||
assert.Empty(bios)
|
||||
|
||||
|
@ -8,9 +8,14 @@
|
||||
package virtcontainers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
@ -770,3 +775,53 @@ func TestQemuStartSandbox(t *testing.T) {
|
||||
err = q.StartVM(context.Background(), 10)
|
||||
assert.Error(err)
|
||||
}
|
||||
|
||||
func TestPrepareInitdataImage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
}{
|
||||
{
|
||||
"create an initdata image",
|
||||
"some content",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
imageDir := t.TempDir()
|
||||
imagePath := path.Join(imageDir, "initdata.img")
|
||||
err := prepareInitdataImage(tt.content, imagePath)
|
||||
if err != nil {
|
||||
t.Errorf("prepareInitdataImage() error = %v", err)
|
||||
}
|
||||
defer os.Remove(imagePath)
|
||||
|
||||
fullContent, err := os.ReadFile(imagePath)
|
||||
if err != nil {
|
||||
t.Errorf("read initdata image failed: %v", err)
|
||||
}
|
||||
|
||||
magicNumber := fullContent[:8]
|
||||
if string(magicNumber) != "initdata" {
|
||||
t.Errorf("initdata magic number is not correct, got %s, want initdata", string(magicNumber))
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint64(fullContent[8:16])
|
||||
contentSlice := fullContent[16 : 16+length]
|
||||
gzipReader, err := gzip.NewReader(bytes.NewBuffer(contentSlice))
|
||||
if err != nil {
|
||||
t.Errorf("read gzipped initdata failed: %v", err)
|
||||
}
|
||||
defer gzipReader.Close()
|
||||
|
||||
content, err := io.ReadAll(gzipReader)
|
||||
if err != nil {
|
||||
t.Errorf("read gzipped initdata failed: %v", err)
|
||||
}
|
||||
|
||||
if string(content) != tt.content {
|
||||
t.Errorf("initdata content is not correct, got %s, want %s", string(content), tt.content)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user