diff --git a/.github/workflows/run-kata-coco-tests.yaml b/.github/workflows/run-kata-coco-tests.yaml index 8c96b8c2a7..25b95e6c49 100644 --- a/.github/workflows/run-kata-coco-tests.yaml +++ b/.github/workflows/run-kata-coco-tests.yaml @@ -174,6 +174,8 @@ jobs: KUBECONFIG: /home/kata/.kube/config KUBERNETES: "vanilla" USING_NFD: "false" + KBS: "true" + KBS_INGRESS: "nodeport" K8S_TEST_HOST_TYPE: "baremetal" SNAPSHOTTER: ${{ matrix.snapshotter }} PULL_TYPE: ${{ matrix.pull-type }} @@ -199,6 +201,18 @@ jobs: timeout-minutes: 10 run: bash tests/integration/kubernetes/gha-run.sh deploy-kata-snp + - name: Uninstall previous `kbs-client` + timeout-minutes: 10 + run: bash tests/integration/kubernetes/gha-run.sh uninstall-kbs-client + + - name: Deploy CoCo KBS + timeout-minutes: 10 + run: bash tests/integration/kubernetes/gha-run.sh deploy-coco-kbs + + - name: Install `kbs-client` + timeout-minutes: 10 + run: bash tests/integration/kubernetes/gha-run.sh install-kbs-client + - name: Run tests timeout-minutes: 30 run: bash tests/integration/kubernetes/gha-run.sh run-tests @@ -211,6 +225,10 @@ jobs: if: always() run: bash tests/integration/kubernetes/gha-run.sh cleanup-snapshotter + - name: Delete CoCo KBS + if: always() + run: bash tests/integration/kubernetes/gha-run.sh delete-coco-kbs + # Generate jobs for testing CoCo on non-TEE environments run-k8s-tests-coco-nontee: strategy: diff --git a/src/runtime/Makefile b/src/runtime/Makefile index 69c86974c2..da91a71611 100644 --- a/src/runtime/Makefile +++ b/src/runtime/Makefile @@ -150,6 +150,7 @@ FIRMWARETDVFVOLUMEPATH := FIRMWARESEVPATH := $(PREFIXDEPS)/share/ovmf/OVMF.fd FIRMWARESNPPATH := $(PREFIXDEPS)/share/ovmf/AMDSEV.fd +SNPCERTSPATH := /opt/snp/cert_chain.cert ROOTMEASURECONFIG ?= "" KERNELPARAMS += $(ROOTMEASURECONFIG) @@ -679,6 +680,7 @@ USER_VARS += FIRMWARETDVFPATH USER_VARS += FIRMWAREVOLUMEPATH USER_VARS += FIRMWARETDVFVOLUMEPATH USER_VARS += FIRMWARESNPPATH +USER_VARS += SNPCERTSPATH USER_VARS += MACHINEACCELERATORS USER_VARS += CPUFEATURES USER_VARS += TDXCPUFEATURES diff --git a/src/runtime/config/configuration-qemu-snp.toml.in b/src/runtime/config/configuration-qemu-snp.toml.in index 38bec359d8..c655c8db1b 100644 --- a/src/runtime/config/configuration-qemu-snp.toml.in +++ b/src/runtime/config/configuration-qemu-snp.toml.in @@ -44,6 +44,11 @@ confidential_guest = true # enable SEV SNP VMs sev_snp_guest = true +# The path to the file containing the SNP certificate chain (including +# VCEK/VLEK certificates). This wil be used to get the extended attestation +# report from the guest. The default path is @SNPCERTSPATH@. +snp_certs_path = "@SNPCERTSPATH@" + # Enable running QEMU VMM as a non-root user. # By default QEMU VMM run as root. When this is set to true, QEMU VMM process runs as # a non-root random user. See documentation for the limitations of this mode. diff --git a/src/runtime/pkg/govmm/qemu/qemu.go b/src/runtime/pkg/govmm/qemu/qemu.go index e752f81814..6d71e28f93 100644 --- a/src/runtime/pkg/govmm/qemu/qemu.go +++ b/src/runtime/pkg/govmm/qemu/qemu.go @@ -300,6 +300,10 @@ type Object struct { // and UEFI program image. FirmwareVolume string + // The path to the file containing the AMD SEV-SNP certificate chain + // (including VCEK/VLEK certificates). + SnpCertsPath string + // CBitPos is the location of the C-bit in a guest page table entry // This is only relevant for sev-guest objects CBitPos uint32 @@ -388,6 +392,9 @@ func (object Object) QemuParams(config *Config) []string { objectParams = append(objectParams, fmt.Sprintf("cbitpos=%d", object.CBitPos)) objectParams = append(objectParams, fmt.Sprintf("reduced-phys-bits=%d", object.ReducedPhysBits)) objectParams = append(objectParams, "kernel-hashes=on") + if object.SnpCertsPath != "" { + objectParams = append(objectParams, fmt.Sprintf("certs-path=%s", object.SnpCertsPath)) + } driveParams = append(driveParams, "if=pflash,format=raw,readonly=on") driveParams = append(driveParams, fmt.Sprintf("file=%s", object.File)) diff --git a/src/runtime/pkg/katautils/config-settings.go.in b/src/runtime/pkg/katautils/config-settings.go.in index bd57d30875..bebf5a0224 100644 --- a/src/runtime/pkg/katautils/config-settings.go.in +++ b/src/runtime/pkg/katautils/config-settings.go.in @@ -117,3 +117,5 @@ const defaultPCIeSwitchPort = 0 const defaultRemoteHypervisorSocket = "/run/peerpod/hypervisor.sock" const defaultRemoteHypervisorTimeout = 600 + +const defaultSnpCertsPath = "/opt/snp/cert_chain.cert" diff --git a/src/runtime/pkg/katautils/config.go b/src/runtime/pkg/katautils/config.go index 5d2a655d68..627dafa47c 100644 --- a/src/runtime/pkg/katautils/config.go +++ b/src/runtime/pkg/katautils/config.go @@ -106,6 +106,7 @@ type hypervisor struct { SeccompSandbox string `toml:"seccompsandbox"` BlockDeviceAIO string `toml:"block_device_aio"` RemoteHypervisorSocket string `toml:"remote_hypervisor_socket"` + SnpCertsPath string `toml:"snp_certs_path"` HypervisorPathList []string `toml:"valid_hypervisor_paths"` JailerPathList []string `toml:"valid_jailer_paths"` CtlPathList []string `toml:"valid_ctlpaths"` @@ -295,6 +296,24 @@ func (h hypervisor) firmware() (string, error) { return ResolvePath(p) } +func (h hypervisor) snpCertsPath() (string, error) { + p := h.SnpCertsPath + + if p == "" { + p = defaultSnpCertsPath + } + + path, err := ResolvePath(p) + if err != nil { + if p == defaultSnpCertsPath { + msg := fmt.Sprintf("failed to resolve SNP certificates path: %s", defaultSnpCertsPath) + kataUtilsLogger.Warn(msg) + return "", nil + } + } + return path, err +} + func (h hypervisor) coldPlugVFIO() config.PCIePort { if h.ColdPlugVFIO == "" { return defaultColdPlugVFIO @@ -850,6 +869,11 @@ func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { return vc.HypervisorConfig{}, err } + snpCertsPath, err := h.snpCertsPath() + if err != nil { + return vc.HypervisorConfig{}, err + } + machineAccelerators := h.machineAccelerators() cpuFeatures := h.cpuFeatures() kernelParams := h.kernelParams() @@ -914,6 +938,7 @@ func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) { RootfsType: rootfsType, FirmwarePath: firmware, FirmwareVolumePath: firmwareVolume, + SnpCertsPath: snpCertsPath, PFlash: pflashes, MachineAccelerators: machineAccelerators, CPUFeatures: cpuFeatures, diff --git a/src/runtime/virtcontainers/hypervisor.go b/src/runtime/virtcontainers/hypervisor.go index aa30823247..cc37433105 100644 --- a/src/runtime/virtcontainers/hypervisor.go +++ b/src/runtime/virtcontainers/hypervisor.go @@ -475,6 +475,10 @@ type HypervisorConfig struct { // The user maps to the uid. User string + // The path to the file containing the AMD SEV-SNP certificate chain + // (including VCEK/VLEK certificates). + SnpCertsPath string + // KernelParams are additional guest kernel parameters. KernelParams []Param diff --git a/src/runtime/virtcontainers/qemu_amd64.go b/src/runtime/virtcontainers/qemu_amd64.go index ade7356eb6..1d1be17118 100644 --- a/src/runtime/virtcontainers/qemu_amd64.go +++ b/src/runtime/virtcontainers/qemu_amd64.go @@ -33,6 +33,8 @@ type qemuAmd64 struct { sgxEPCSize int64 qgsPort uint32 + + snpCertsPath string } const ( @@ -125,9 +127,10 @@ func newQemuArch(config HypervisorConfig) (qemuArch, error) { protection: noneProtection, legacySerial: config.LegacySerial, }, - vmFactory: factory, - snpGuest: config.SevSnpGuest, - qgsPort: config.QgsPort, + vmFactory: factory, + snpGuest: config.SevSnpGuest, + qgsPort: config.QgsPort, + snpCertsPath: config.SnpCertsPath, } if config.ConfidentialGuest { @@ -311,6 +314,7 @@ func (q *qemuAmd64) appendProtectionDevice(devices []govmmQemu.Device, firmware, File: firmware, CBitPos: cpuid.AMDMemEncrypt.CBitPosition, ReducedPhysBits: 1, + SnpCertsPath: q.snpCertsPath, }), "", nil case noneProtection: diff --git a/tests/integration/kubernetes/k8s-confidential-attestation.bats b/tests/integration/kubernetes/k8s-confidential-attestation.bats index a3e45de0f0..edf722f69e 100644 --- a/tests/integration/kubernetes/k8s-confidential-attestation.bats +++ b/tests/integration/kubernetes/k8s-confidential-attestation.bats @@ -20,6 +20,9 @@ setup() { if [ "${KBS}" = "false" ]; then skip "Test skipped as KBS not setup" fi + if [ "${KATA_HYPERVISOR}" = "qemu-snp" ]; then + skip "Test skipped as SNP attestation not setup" + fi setup_common get_pod_config_dir @@ -90,6 +93,9 @@ teardown() { if [ "${KBS}" = "false" ]; then skip "Test skipped as KBS not setup" fi + if [ "${KATA_HYPERVISOR}" = "qemu-snp" ]; then + skip "Test skipped as SNP attestation not setup" + fi [ -n "${pod_name:-}" ] && kubectl describe "pod/${pod_name}" || true [ -n "${pod_config_dir:-}" ] && kubectl delete -f "${K8S_TEST_YAML}" || true