config: add SEV-SNP config options for GCP

This commit is contained in:
Moritz Sanft 2024-04-04 16:31:41 +02:00
parent 02f78b2bae
commit 4228981a82
No known key found for this signature in database
GPG Key ID: 335D28368B1DA615
8 changed files with 247 additions and 33 deletions

View File

@ -10,6 +10,7 @@ go_library(
"azure.go",
"config.go",
"config_doc.go",
"gcp.go",
# keep
"image_enterprise.go",
# keep

View File

@ -52,6 +52,8 @@ func UnmarshalAttestationConfig(data []byte, attestVariant variant.Variant) (Att
return unmarshalTypedConfig[*AzureTDX](data)
case variant.GCPSEVES{}:
return unmarshalTypedConfig[*GCPSEVES](data)
case variant.GCPSEVSNP{}:
return unmarshalTypedConfig[*GCPSEVSNP](data)
case variant.QEMUVTPM{}:
return unmarshalTypedConfig[*QEMUVTPM](data)
case variant.QEMUTDX{}:

View File

@ -41,6 +41,9 @@ func TestUnmarshalAttestationConfig(t *testing.T) {
"GCPSEVES": {
cfg: &GCPSEVES{Measurements: measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVES{})},
},
"GCPSEVSNP": {
cfg: DefaultForGCPSEVSNP(),
},
"QEMUVTPM": {
cfg: &QEMUVTPM{Measurements: measurements.DefaultsFor(cloudprovider.QEMU, variant.QEMUVTPM{})},
},

View File

@ -278,6 +278,9 @@ type AttestationConfig struct {
// GCP SEV-ES attestation.
GCPSEVES *GCPSEVES `yaml:"gcpSEVES,omitempty" validate:"omitempty,dive"`
// description: |
// GCP SEV-SNP attestation.
GCPSEVSNP *GCPSEVSNP `yaml:"gcpSEVSNP,omitempty" validate:"omitempty,dive"`
// description: |
// QEMU tdx attestation.
QEMUTDX *QEMUTDX `yaml:"qemuTDX,omitempty" validate:"omitempty,dive"`
// description: |
@ -390,6 +393,7 @@ func Default() *Config {
AzureTDX: DefaultForAzureTDX(),
AzureTrustedLaunch: &AzureTrustedLaunch{Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureTrustedLaunch{})},
GCPSEVES: &GCPSEVES{Measurements: measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVES{})},
GCPSEVSNP: DefaultForGCPSEVSNP(),
QEMUVTPM: &QEMUVTPM{Measurements: measurements.DefaultsFor(cloudprovider.QEMU, variant.QEMUVTPM{})},
},
}
@ -518,6 +522,9 @@ func (c *Config) UpdateMeasurements(newMeasurements measurements.M) {
if c.Attestation.GCPSEVES != nil {
c.Attestation.GCPSEVES.Measurements.CopyFrom(newMeasurements)
}
if c.Attestation.GCPSEVSNP != nil {
c.Attestation.GCPSEVSNP.Measurements.CopyFrom(newMeasurements)
}
if c.Attestation.QEMUVTPM != nil {
c.Attestation.QEMUVTPM.Measurements.CopyFrom(newMeasurements)
}
@ -570,6 +577,8 @@ func (c *Config) SetAttestation(attestation variant.Variant) {
c.Attestation = AttestationConfig{AzureTrustedLaunch: currentAttestationConfigs.AzureTrustedLaunch}
case variant.GCPSEVES:
c.Attestation = AttestationConfig{GCPSEVES: currentAttestationConfigs.GCPSEVES}
case variant.GCPSEVSNP:
c.Attestation = AttestationConfig{GCPSEVSNP: currentAttestationConfigs.GCPSEVSNP}
case variant.QEMUVTPM:
c.Attestation = AttestationConfig{QEMUVTPM: currentAttestationConfigs.QEMUVTPM}
}
@ -637,6 +646,9 @@ func (c *Config) GetAttestationConfig() AttestationCfg {
if c.Attestation.GCPSEVES != nil {
return c.Attestation.GCPSEVES
}
if c.Attestation.GCPSEVSNP != nil {
return c.Attestation.GCPSEVSNP
}
if c.Attestation.QEMUVTPM != nil {
return c.Attestation.QEMUVTPM
}
@ -964,28 +976,29 @@ type GCPSEVES struct {
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
}
// GetVariant returns gcp-sev-es as the variant.
func (GCPSEVES) GetVariant() variant.Variant {
return variant.GCPSEVES{}
}
// GetMeasurements returns the measurements used for attestation.
func (c GCPSEVES) GetMeasurements() measurements.M {
return c.Measurements
}
// SetMeasurements updates a config's measurements using the given measurements.
func (c *GCPSEVES) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c GCPSEVES) EqualTo(other AttestationCfg) (bool, error) {
otherCfg, ok := other.(*GCPSEVES)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, other)
}
return c.Measurements.EqualTo(otherCfg.Measurements), nil
// GCPSEVSNP is the configuration for GCP SEV-SNP attestation.
type GCPSEVSNP struct {
// description: |
// Expected TPM measurements.
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
// description: |
// Lowest acceptable bootloader version.
BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"`
// description: |
// Lowest acceptable TEE version.
TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"`
// description: |
// Lowest acceptable SEV-SNP version.
SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"`
// description: |
// Lowest acceptable microcode version.
MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"`
// description: |
// AMD Root Key certificate used to verify the SEV-SNP certificate chain.
AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"`
// description: |
// AMD Signing Key certificate used to verify the SEV-SNP VCEK / VLEK certificate.
AMDSigningKey Certificate `json:"amdSigningKey,omitempty" yaml:"amdSigningKey,omitempty"`
}
// QEMUVTPM is the configuration for QEMU vTPM attestation.

View File

@ -23,6 +23,7 @@ var (
UnsupportedAppRegistrationErrorDoc encoder.Doc
SNPFirmwareSignerConfigDoc encoder.Doc
GCPSEVESDoc encoder.Doc
GCPSEVSNPDoc encoder.Doc
QEMUVTPMDoc encoder.Doc
QEMUTDXDoc encoder.Doc
AWSSEVSNPDoc encoder.Doc
@ -388,7 +389,7 @@ func init() {
FieldName: "attestation",
},
}
AttestationConfigDoc.Fields = make([]encoder.Doc, 8)
AttestationConfigDoc.Fields = make([]encoder.Doc, 9)
AttestationConfigDoc.Fields[0].Name = "awsSEVSNP"
AttestationConfigDoc.Fields[0].Type = "AWSSEVSNP"
AttestationConfigDoc.Fields[0].Note = ""
@ -419,16 +420,21 @@ func init() {
AttestationConfigDoc.Fields[5].Note = ""
AttestationConfigDoc.Fields[5].Description = "GCP SEV-ES attestation."
AttestationConfigDoc.Fields[5].Comments[encoder.LineComment] = "GCP SEV-ES attestation."
AttestationConfigDoc.Fields[6].Name = "qemuTDX"
AttestationConfigDoc.Fields[6].Type = "QEMUTDX"
AttestationConfigDoc.Fields[6].Name = "gcpSEVSNP"
AttestationConfigDoc.Fields[6].Type = "GCPSEVSNP"
AttestationConfigDoc.Fields[6].Note = ""
AttestationConfigDoc.Fields[6].Description = "QEMU tdx attestation."
AttestationConfigDoc.Fields[6].Comments[encoder.LineComment] = "QEMU tdx attestation."
AttestationConfigDoc.Fields[7].Name = "qemuVTPM"
AttestationConfigDoc.Fields[7].Type = "QEMUVTPM"
AttestationConfigDoc.Fields[6].Description = "description: |\n GCP SEV-SNP attestation.\n"
AttestationConfigDoc.Fields[6].Comments[encoder.LineComment] = "description: |"
AttestationConfigDoc.Fields[7].Name = "qemuTDX"
AttestationConfigDoc.Fields[7].Type = "QEMUTDX"
AttestationConfigDoc.Fields[7].Note = ""
AttestationConfigDoc.Fields[7].Description = "QEMU vTPM attestation."
AttestationConfigDoc.Fields[7].Comments[encoder.LineComment] = "QEMU vTPM attestation."
AttestationConfigDoc.Fields[7].Description = "QEMU tdx attestation."
AttestationConfigDoc.Fields[7].Comments[encoder.LineComment] = "QEMU tdx attestation."
AttestationConfigDoc.Fields[8].Name = "qemuVTPM"
AttestationConfigDoc.Fields[8].Type = "QEMUVTPM"
AttestationConfigDoc.Fields[8].Note = ""
AttestationConfigDoc.Fields[8].Description = "QEMU vTPM attestation."
AttestationConfigDoc.Fields[8].Comments[encoder.LineComment] = "QEMU vTPM attestation."
NodeGroupDoc.Type = "NodeGroup"
NodeGroupDoc.Comments[encoder.LineComment] = "NodeGroup defines a group of nodes with the same role and configuration."
@ -518,6 +524,52 @@ func init() {
GCPSEVESDoc.Fields[0].Description = "Expected TPM measurements."
GCPSEVESDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
GCPSEVSNPDoc.Type = "GCPSEVSNP"
GCPSEVSNPDoc.Comments[encoder.LineComment] = "GCPSEVSNP is the configuration for GCP SEV-SNP attestation."
GCPSEVSNPDoc.Description = "GCPSEVSNP is the configuration for GCP SEV-SNP attestation."
GCPSEVSNPDoc.AppearsIn = []encoder.Appearance{
{
TypeName: "AttestationConfig",
FieldName: "gcpSEVSNP",
},
}
GCPSEVSNPDoc.Fields = make([]encoder.Doc, 7)
GCPSEVSNPDoc.Fields[0].Name = "measurements"
GCPSEVSNPDoc.Fields[0].Type = "M"
GCPSEVSNPDoc.Fields[0].Note = ""
GCPSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
GCPSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
GCPSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
GCPSEVSNPDoc.Fields[1].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[1].Note = ""
GCPSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
GCPSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
GCPSEVSNPDoc.Fields[2].Name = "teeVersion"
GCPSEVSNPDoc.Fields[2].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[2].Note = ""
GCPSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
GCPSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
GCPSEVSNPDoc.Fields[3].Name = "snpVersion"
GCPSEVSNPDoc.Fields[3].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[3].Note = ""
GCPSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
GCPSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
GCPSEVSNPDoc.Fields[4].Name = "microcodeVersion"
GCPSEVSNPDoc.Fields[4].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[4].Note = ""
GCPSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
GCPSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version."
GCPSEVSNPDoc.Fields[5].Name = "amdRootKey"
GCPSEVSNPDoc.Fields[5].Type = "Certificate"
GCPSEVSNPDoc.Fields[5].Note = ""
GCPSEVSNPDoc.Fields[5].Description = "AMD Root Key certificate used to verify the SEV-SNP certificate chain."
GCPSEVSNPDoc.Fields[5].Comments[encoder.LineComment] = "AMD Root Key certificate used to verify the SEV-SNP certificate chain."
GCPSEVSNPDoc.Fields[6].Name = "amdSigningKey"
GCPSEVSNPDoc.Fields[6].Type = "Certificate"
GCPSEVSNPDoc.Fields[6].Note = ""
GCPSEVSNPDoc.Fields[6].Description = "AMD Signing Key certificate used to verify the SEV-SNP VCEK / VLEK certificate."
GCPSEVSNPDoc.Fields[6].Comments[encoder.LineComment] = "AMD Signing Key certificate used to verify the SEV-SNP VCEK / VLEK certificate."
QEMUVTPMDoc.Type = "QEMUVTPM"
QEMUVTPMDoc.Comments[encoder.LineComment] = "QEMUVTPM is the configuration for QEMU vTPM attestation."
QEMUVTPMDoc.Description = "QEMUVTPM is the configuration for QEMU vTPM attestation."
@ -779,6 +831,10 @@ func (_ GCPSEVES) Doc() *encoder.Doc {
return &GCPSEVESDoc
}
func (_ GCPSEVSNP) Doc() *encoder.Doc {
return &GCPSEVSNPDoc
}
func (_ QEMUVTPM) Doc() *encoder.Doc {
return &QEMUVTPMDoc
}
@ -825,6 +881,7 @@ func GetConfigurationDoc() *encoder.FileDoc {
&UnsupportedAppRegistrationErrorDoc,
&SNPFirmwareSignerConfigDoc,
&GCPSEVESDoc,
&GCPSEVSNPDoc,
&QEMUVTPMDoc,
&QEMUTDXDoc,
&AWSSEVSNPDoc,

View File

@ -328,7 +328,7 @@ func TestFromFile(t *testing.T) {
}
func TestValidate(t *testing.T) {
const defaultErrCount = 32 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
const defaultErrCount = 33 // expect this number of error messages by default because user-specific values are not set and multiple providers are defined by default
const azErrCount = 7
const awsErrCount = 8
const gcpErrCount = 8
@ -735,6 +735,11 @@ func TestValidInstanceTypeForProvider(t *testing.T) {
instanceTypes: instancetypes.GCPInstanceTypes,
expectedResult: true,
},
"gcp sev-snp": {
variant: variant.GCPSEVSNP{},
instanceTypes: instancetypes.GCPInstanceTypes,
expectedResult: true,
},
"put gcp when azure is set": {
variant: variant.AzureSEVSNP{},
instanceTypes: instancetypes.GCPInstanceTypes,

127
internal/config/gcp.go Normal file
View File

@ -0,0 +1,127 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package config
import (
"bytes"
"context"
"fmt"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
)
var _ svnResolveMarshaller = &GCPSEVSNP{}
// DefaultForGCPSEVSNP provides a valid default configuration for GCP SEV-SNP attestation.
func DefaultForGCPSEVSNP() *GCPSEVSNP {
return &GCPSEVSNP{
Measurements: measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVSNP{}),
BootloaderVersion: NewLatestPlaceholderVersion(),
TEEVersion: NewLatestPlaceholderVersion(),
SNPVersion: NewLatestPlaceholderVersion(),
MicrocodeVersion: NewLatestPlaceholderVersion(),
AMDRootKey: mustParsePEM(arkPEM),
}
}
// GetVariant returns gcp-sev-snp as the variant.
func (GCPSEVSNP) GetVariant() variant.Variant {
return variant.GCPSEVSNP{}
}
// GetMeasurements returns the measurements used for attestation.
func (c GCPSEVSNP) GetMeasurements() measurements.M {
return c.Measurements
}
// SetMeasurements updates a config's measurements using the given measurements.
func (c *GCPSEVSNP) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c GCPSEVSNP) EqualTo(other AttestationCfg) (bool, error) {
otherCfg, ok := other.(*GCPSEVSNP)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, other)
}
measurementsEqual := c.Measurements.EqualTo(otherCfg.Measurements)
bootloaderEqual := c.BootloaderVersion == otherCfg.BootloaderVersion
teeEqual := c.TEEVersion == otherCfg.TEEVersion
snpEqual := c.SNPVersion == otherCfg.SNPVersion
microcodeEqual := c.MicrocodeVersion == otherCfg.MicrocodeVersion
rootKeyEqual := bytes.Equal(c.AMDRootKey.Raw, otherCfg.AMDRootKey.Raw)
signingKeyEqual := bytes.Equal(c.AMDSigningKey.Raw, otherCfg.AMDSigningKey.Raw)
return measurementsEqual && bootloaderEqual && teeEqual && snpEqual && microcodeEqual && rootKeyEqual && signingKeyEqual, nil
}
func (c *GCPSEVSNP) getToMarshallLatestWithResolvedVersions() AttestationCfg {
cp := *c
cp.BootloaderVersion.WantLatest = false
cp.TEEVersion.WantLatest = false
cp.SNPVersion.WantLatest = false
cp.MicrocodeVersion.WantLatest = false
return &cp
}
// FetchAndSetLatestVersionNumbers fetches the latest version numbers from the configapi and sets them.
func (c *GCPSEVSNP) FetchAndSetLatestVersionNumbers(ctx context.Context, fetcher attestationconfigapi.Fetcher) error {
// Only talk to the API if at least one version number is set to latest.
if !(c.BootloaderVersion.WantLatest || c.TEEVersion.WantLatest || c.SNPVersion.WantLatest || c.MicrocodeVersion.WantLatest) {
return nil
}
versions, err := fetcher.FetchSEVSNPVersionLatest(ctx, variant.GCPSEVSNP{})
if err != nil {
return fmt.Errorf("fetching latest TCB versions from configapi: %w", err)
}
// set number and keep isLatest flag
c.mergeWithLatestVersion(versions.SEVSNPVersion)
return nil
}
func (c *GCPSEVSNP) mergeWithLatestVersion(latest attestationconfigapi.SEVSNPVersion) {
if c.BootloaderVersion.WantLatest {
c.BootloaderVersion.Value = latest.Bootloader
}
if c.TEEVersion.WantLatest {
c.TEEVersion.Value = latest.TEE
}
if c.SNPVersion.WantLatest {
c.SNPVersion.Value = latest.SNP
}
if c.MicrocodeVersion.WantLatest {
c.MicrocodeVersion.Value = latest.Microcode
}
}
// GetVariant returns gcp-sev-es as the variant.
func (GCPSEVES) GetVariant() variant.Variant {
return variant.GCPSEVES{}
}
// GetMeasurements returns the measurements used for attestation.
func (c GCPSEVES) GetMeasurements() measurements.M {
return c.Measurements
}
// SetMeasurements updates a config's measurements using the given measurements.
func (c *GCPSEVES) SetMeasurements(m measurements.M) {
c.Measurements = m
}
// EqualTo returns true if the config is equal to the given config.
func (c GCPSEVES) EqualTo(other AttestationCfg) (bool, error) {
otherCfg, ok := other.(*GCPSEVES)
if !ok {
return false, fmt.Errorf("cannot compare %T with %T", c, other)
}
return c.Measurements.EqualTo(otherCfg.Measurements), nil
}

View File

@ -202,6 +202,9 @@ func validateAttestation(sl validator.StructLevel) {
if attestation.GCPSEVES != nil {
attestationCount++
}
if attestation.GCPSEVSNP != nil {
attestationCount++
}
if attestation.QEMUVTPM != nil {
attestationCount++
}
@ -364,6 +367,9 @@ func (c *Config) translateMoreThanOneAttestationError(ut ut.Translator, fe valid
if c.Attestation.GCPSEVES != nil {
definedAttestations = append(definedAttestations, "GCPSEVES")
}
if c.Attestation.GCPSEVSNP != nil {
definedAttestations = append(definedAttestations, "GCPSEVSNP")
}
if c.Attestation.QEMUVTPM != nil {
definedAttestations = append(definedAttestations, "QEMUVTPM")
}
@ -536,7 +542,7 @@ func validInstanceTypeForProvider(insType string, attestation variant.Variant) b
return true
}
}
case variant.GCPSEVES{}:
case variant.GCPSEVES{}, variant.GCPSEVSNP{}:
for _, instanceType := range instancetypes.GCPInstanceTypes {
if insType == instanceType {
return true