diff --git a/internal/api/attestationconfigapi/BUILD.bazel b/internal/api/attestationconfigapi/BUILD.bazel index 39f3bf5a31..80397adb3c 100644 --- a/internal/api/attestationconfigapi/BUILD.bazel +++ b/internal/api/attestationconfigapi/BUILD.bazel @@ -6,7 +6,7 @@ go_library( srcs = [ "attestationconfigapi.go", "fetcher.go", - "snp.go", + "version.go", ], importpath = "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi", visibility = ["//:__subpackages__"], @@ -22,7 +22,7 @@ go_test( name = "attestationconfigapi_test", srcs = [ "fetcher_test.go", - "snp_test.go", + "version_test.go", ], embed = [":attestationconfigapi"], deps = [ diff --git a/internal/api/attestationconfigapi/cli/BUILD.bazel b/internal/api/attestationconfigapi/cli/BUILD.bazel index 8378b0d03f..03e6529d5f 100644 --- a/internal/api/attestationconfigapi/cli/BUILD.bazel +++ b/internal/api/attestationconfigapi/cli/BUILD.bazel @@ -20,6 +20,7 @@ go_library( deps = [ "//internal/api/attestationconfigapi", "//internal/api/attestationconfigapi/cli/client", + "//internal/api/fetcher", "//internal/attestation/variant", "//internal/cloud/cloudprovider", "//internal/constants", diff --git a/internal/attestation/azure/tdx/validator.go b/internal/attestation/azure/tdx/validator.go index 02a8d3d6df..6a496f2bb1 100644 --- a/internal/attestation/azure/tdx/validator.go +++ b/internal/attestation/azure/tdx/validator.go @@ -103,14 +103,14 @@ func (v *Validator) validateQuote(tdxQuote *tdx.QuoteV4) error { if err := validate.TdxQuote(tdxQuote, &validate.Options{ HeaderOptions: validate.HeaderOptions{ - MinimumQeSvn: v.cfg.QESVN, - MinimumPceSvn: v.cfg.PCESVN, - QeVendorID: v.cfg.QEVendorID, + MinimumQeSvn: v.cfg.QESVN.Value, + MinimumPceSvn: v.cfg.PCESVN.Value, + QeVendorID: v.cfg.QEVendorID.Value, }, TdQuoteBodyOptions: validate.TdQuoteBodyOptions{ - MinimumTeeTcbSvn: v.cfg.TEETCBSVN, + MinimumTeeTcbSvn: v.cfg.TEETCBSVN.Value, MrSeam: v.cfg.MRSeam, - Xfam: v.cfg.XFAM, + Xfam: v.cfg.XFAM.Value, }, }); err != nil { return err diff --git a/internal/config/BUILD.bazel b/internal/config/BUILD.bazel index 8ea071ae89..8a96864c57 100644 --- a/internal/config/BUILD.bazel +++ b/internal/config/BUILD.bazel @@ -63,6 +63,7 @@ go_test( "//internal/cloud/cloudprovider", "//internal/config/instancetypes", "//internal/constants", + "//internal/encoding", "//internal/file", "//internal/semver", "//internal/versions", diff --git a/internal/config/attestationversion.go b/internal/config/attestationversion.go index a7949c5c39..e2e5a12a57 100644 --- a/internal/config/attestationversion.go +++ b/internal/config/attestationversion.go @@ -9,47 +9,49 @@ package config import ( "encoding/json" "fmt" - "math" - "strconv" "strings" + + "github.com/edgelesssys/constellation/v2/internal/encoding" ) -const placeholderVersionValue = 0 +type versionValue interface { + encoding.HexBytes | uint8 | uint16 +} + +func placeholderVersionValue[T versionValue]() T { + var placeholder T + return placeholder +} // NewLatestPlaceholderVersion returns the latest version with a placeholder version value. -func NewLatestPlaceholderVersion() AttestationVersion { - return AttestationVersion{ - Value: placeholderVersionValue, +func NewLatestPlaceholderVersion[T versionValue]() AttestationVersion[T] { + return AttestationVersion[T]{ + Value: placeholderVersionValue[T](), WantLatest: true, } } -// AttestationVersion is a type that represents a version of a SNP. -type AttestationVersion struct { - Value uint8 +// AttestationVersion holds version information. +type AttestationVersion[T versionValue] struct { + Value T WantLatest bool } -// MarshalYAML implements a custom marshaller to resolve "latest" values. -func (v AttestationVersion) MarshalYAML() (any, error) { +// MarshalYAML implements a custom marshaller to write "latest" as the type's value, if set. +func (v AttestationVersion[T]) MarshalYAML() (any, error) { if v.WantLatest { return "latest", nil } return v.Value, nil } -// UnmarshalYAML implements a custom unmarshaller to resolve "atest" values. -func (v *AttestationVersion) UnmarshalYAML(unmarshal func(any) error) error { - var rawUnmarshal string - if err := unmarshal(&rawUnmarshal); err != nil { - return fmt.Errorf("raw unmarshal: %w", err) - } - - return v.parseRawUnmarshal(rawUnmarshal) +// UnmarshalYAML implements a custom unmarshaller to resolve "latest" values. +func (v *AttestationVersion[T]) UnmarshalYAML(unmarshal func(any) error) error { + return v.unmarshal(unmarshal) } -// MarshalJSON implements a custom marshaller to resolve "latest" values. -func (v AttestationVersion) MarshalJSON() ([]byte, error) { +// MarshalJSON implements a custom marshaller to write "latest" as the type's value, if set. +func (v AttestationVersion[T]) MarshalJSON() ([]byte, error) { if v.WantLatest { return json.Marshal("latest") } @@ -57,39 +59,31 @@ func (v AttestationVersion) MarshalJSON() ([]byte, error) { } // UnmarshalJSON implements a custom unmarshaller to resolve "latest" values. -func (v *AttestationVersion) UnmarshalJSON(data []byte) (err error) { - // JSON has two distinct ways to represent numbers and strings. - // This means we cannot simply unmarshal to string, like with YAML. - // Unmarshalling to `any` causes Go to unmarshal numbers to float64. - // Therefore, try to unmarshal to string, and then to int, instead of using type assertions. - var unmarshalString string - if err := json.Unmarshal(data, &unmarshalString); err != nil { - var unmarshalInt int64 - if err := json.Unmarshal(data, &unmarshalInt); err != nil { - return fmt.Errorf("unable to unmarshal to string or int: %w", err) - } - unmarshalString = strconv.FormatInt(unmarshalInt, 10) - } - - return v.parseRawUnmarshal(unmarshalString) +func (v *AttestationVersion[T]) UnmarshalJSON(data []byte) (err error) { + return v.unmarshal(func(a any) error { + return json.Unmarshal(data, a) + }) } -func (v *AttestationVersion) parseRawUnmarshal(str string) error { - if strings.HasPrefix(str, "0") && len(str) != 1 { - return fmt.Errorf("no format with prefixed 0 (octal, hexadecimal) allowed: %s", str) +// unmarshal takes care of unmarshalling the value from YAML or JSON. +func (v *AttestationVersion[T]) unmarshal(unmarshal func(any) error) error { + // Start by trying to unmarshal to the distinct type + var distinctType T + if err := unmarshal(&distinctType); err == nil { + v.Value = distinctType + return nil } - if strings.ToLower(str) == "latest" { + + var unmarshalString string + if err := unmarshal(&unmarshalString); err != nil { + return fmt.Errorf("failed unmarshalling to %T or string: %w", distinctType, err) + } + + if strings.ToLower(unmarshalString) == "latest" { v.WantLatest = true - v.Value = placeholderVersionValue - } else { - ui, err := strconv.ParseUint(str, 10, 8) - if err != nil { - return fmt.Errorf("invalid version value: %s", str) - } - if ui > math.MaxUint8 { - return fmt.Errorf("integer value is out ouf uint8 range: %d", ui) - } - v.Value = uint8(ui) + v.Value = placeholderVersionValue[T]() + return nil } - return nil + + return fmt.Errorf("failed unmarshalling to %T or string: invalid value: %s", distinctType, unmarshalString) } diff --git a/internal/config/attestationversion_test.go b/internal/config/attestationversion_test.go index 52d68e2a8c..45a4012162 100644 --- a/internal/config/attestationversion_test.go +++ b/internal/config/attestationversion_test.go @@ -7,204 +7,307 @@ SPDX-License-Identifier: AGPL-3.0-only package config import ( + "bytes" "encoding/json" "testing" - "github.com/stretchr/testify/require" + "github.com/edgelesssys/constellation/v2/internal/encoding" + "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) func TestVersionMarshalYAML(t *testing.T) { - tests := map[string]struct { - sut AttestationVersion + testCasesUint8 := map[string]struct { + sut AttestationVersion[uint8] want string }{ - "isLatest resolves to latest": { - sut: AttestationVersion{ + "version with latest writes latest": { + sut: AttestationVersion[uint8]{ Value: 1, WantLatest: true, }, want: "latest\n", }, - "value 5 resolves to 5": { - sut: AttestationVersion{ + "value 5 writes 5": { + sut: AttestationVersion[uint8]{ Value: 5, WantLatest: false, }, want: "5\n", }, } - for name, tc := range tests { + for name, tc := range testCasesUint8 { t.Run(name, func(t *testing.T) { - require := require.New(t) + assert := assert.New(t) bt, err := yaml.Marshal(tc.sut) - require.NoError(err) - require.Equal(tc.want, string(bt)) + assert.NoError(err) + assert.Equal(tc.want, string(bt)) + }) + } + + testCasesUint16 := map[string]struct { + sut AttestationVersion[uint16] + want string + }{ + "version with latest writes latest": { + sut: AttestationVersion[uint16]{ + Value: 1, + WantLatest: true, + }, + want: "latest\n", + }, + "value 5 writes 5": { + sut: AttestationVersion[uint16]{ + Value: 5, + WantLatest: false, + }, + want: "5\n", + }, + } + for name, tc := range testCasesUint16 { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + bt, err := yaml.Marshal(tc.sut) + assert.NoError(err) + assert.Equal(tc.want, string(bt)) + }) + } + + testCasesHexBytes := map[string]struct { + sut AttestationVersion[encoding.HexBytes] + want string + }{ + "version with latest writes latest": { + sut: AttestationVersion[encoding.HexBytes]{ + Value: encoding.HexBytes(bytes.Repeat([]byte("0"), 16)), + WantLatest: true, + }, + want: "latest\n", + }, + "value 5 writes 5": { + sut: AttestationVersion[encoding.HexBytes]{ + Value: encoding.HexBytes(bytes.Repeat([]byte("A"), 16)), + WantLatest: false, + }, + want: "\"41414141414141414141414141414141\"\n", + }, + } + for name, tc := range testCasesHexBytes { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + bt, err := yaml.Marshal(tc.sut) + assert.NoError(err) + assert.Equal(tc.want, string(bt)) }) } } -func TestVersionUnmarshalYAML(t *testing.T) { - tests := map[string]struct { - sut string - want AttestationVersion - wantErr bool +func TestVersionUnmarshal(t *testing.T) { + testCasesUint8 := map[string]struct { + yamlData string + jsonData string + want AttestationVersion[uint8] + wantErr bool }{ "latest resolves to isLatest": { - sut: "latest", - want: AttestationVersion{ + yamlData: "latest", + jsonData: "\"latest\"", + want: AttestationVersion[uint8]{ Value: 0, WantLatest: true, }, wantErr: false, }, "1 resolves to value 1": { - sut: "1", - want: AttestationVersion{ + yamlData: "1", + jsonData: "1", + want: AttestationVersion[uint8]{ Value: 1, WantLatest: false, }, wantErr: false, }, "max uint8+1 errors": { - sut: "256", - wantErr: true, + yamlData: "256", + jsonData: "256", + wantErr: true, }, "-1 errors": { - sut: "-1", - wantErr: true, - }, - "2.6 errors": { - sut: "2.6", - wantErr: true, - }, - "2.0 errors": { - sut: "2.0", - wantErr: true, - }, - "hex format is invalid": { - sut: "0x10", - wantErr: true, - }, - "octal format is invalid": { - sut: "010", - wantErr: true, + yamlData: "-1", + jsonData: "-1", + wantErr: true, }, "0 resolves to value 0": { - sut: "0", - want: AttestationVersion{ + yamlData: "0", + jsonData: "0", + want: AttestationVersion[uint8]{ Value: 0, WantLatest: false, }, }, - "00 errors": { - sut: "00", - wantErr: true, - }, } - for name, tc := range tests { + for name, tc := range testCasesUint8 { t.Run(name, func(t *testing.T) { - require := require.New(t) + assert := assert.New(t) - var sut AttestationVersion - err := yaml.Unmarshal([]byte(tc.sut), &sut) - if tc.wantErr { - require.Error(err) - return + { + var sut AttestationVersion[uint8] + err := yaml.Unmarshal([]byte(tc.yamlData), &sut) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.want, sut) + } + } + + { + var sut AttestationVersion[uint8] + err := json.Unmarshal([]byte(tc.jsonData), &sut) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.want, sut) + } } - require.NoError(err) - require.Equal(tc.want, sut) }) } -} -func TestVersionUnmarshalJSON(t *testing.T) { - tests := map[string]struct { - sut string - want AttestationVersion - wantErr bool + testCasesUint16 := map[string]struct { + yamlData string + jsonData string + want AttestationVersion[uint16] + wantErr bool }{ "latest resolves to isLatest": { - sut: `"latest"`, - want: AttestationVersion{ + yamlData: "latest", + jsonData: "\"latest\"", + want: AttestationVersion[uint16]{ Value: 0, WantLatest: true, }, + wantErr: false, }, "1 resolves to value 1": { - sut: "1", - want: AttestationVersion{ - Value: 1, - WantLatest: false, - }, - }, - "quoted number resolves to value": { - sut: `"1"`, - want: AttestationVersion{ + yamlData: "1", + jsonData: "1", + want: AttestationVersion[uint16]{ Value: 1, WantLatest: false, }, + wantErr: false, }, - "quoted float errors": { - sut: `"1.0"`, - wantErr: true, - }, - "max uint8+1 errors": { - sut: "256", - wantErr: true, + "max uint16+1 errors": { + yamlData: "65536", + jsonData: "65536", + wantErr: true, }, "-1 errors": { - sut: "-1", - wantErr: true, - }, - "2.6 errors": { - sut: "2.6", - wantErr: true, - }, - "2.0 errors": { - sut: "2.0", - wantErr: true, - }, - "hex format is invalid": { - sut: "0x10", - wantErr: true, - }, - "octal format is invalid": { - sut: "010", - wantErr: true, + yamlData: "-1", + jsonData: "-1", + wantErr: true, }, "0 resolves to value 0": { - sut: "0", - want: AttestationVersion{ + yamlData: "0", + jsonData: "0", + want: AttestationVersion[uint16]{ Value: 0, WantLatest: false, }, }, - "quoted 0 resolves to value 0": { - sut: `"0"`, - want: AttestationVersion{ - Value: 0, + } + for name, tc := range testCasesUint16 { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + { + var sut AttestationVersion[uint16] + err := yaml.Unmarshal([]byte(tc.yamlData), &sut) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.want, sut) + } + } + + { + var sut AttestationVersion[uint16] + err := json.Unmarshal([]byte(tc.jsonData), &sut) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.want, sut) + } + } + }) + } + + testCasesHexBytes := map[string]struct { + yamlData string + jsonData string + want AttestationVersion[encoding.HexBytes] + wantErr bool + }{ + "latest resolves to isLatest": { + yamlData: "latest", + jsonData: "\"latest\"", + want: AttestationVersion[encoding.HexBytes]{ + Value: encoding.HexBytes(nil), + WantLatest: true, + }, + wantErr: false, + }, + "hex string resolves to correctly": { + yamlData: "41414141414141414141414141414141", + jsonData: "\"41414141414141414141414141414141\"", + want: AttestationVersion[encoding.HexBytes]{ + Value: encoding.HexBytes(bytes.Repeat([]byte("A"), 16)), WantLatest: false, }, + wantErr: false, + }, + "invalid hex string": { + yamlData: "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG", + jsonData: "\"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\"", + wantErr: true, }, - "00 errors": { - sut: "00", - wantErr: true, + "non hex data": { + yamlData: "-15", + jsonData: "-15", + wantErr: true, }, } - for name, tc := range tests { + for name, tc := range testCasesHexBytes { t.Run(name, func(t *testing.T) { - require := require.New(t) + assert := assert.New(t) + + { + var sut AttestationVersion[encoding.HexBytes] + err := yaml.Unmarshal([]byte(tc.yamlData), &sut) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.want, sut) + } + } - var sut AttestationVersion - err := json.Unmarshal([]byte(tc.sut), &sut) - if tc.wantErr { - require.Error(err) - return + { + var sut AttestationVersion[encoding.HexBytes] + err := json.Unmarshal([]byte(tc.jsonData), &sut) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.want, sut) + } } - require.NoError(err) - require.Equal(tc.want, sut) }) } } diff --git a/internal/config/aws.go b/internal/config/aws.go index b75887b076..10dc449e21 100644 --- a/internal/config/aws.go +++ b/internal/config/aws.go @@ -22,10 +22,10 @@ var _ svnResolveMarshaller = &AWSSEVSNP{} func DefaultForAWSSEVSNP() *AWSSEVSNP { return &AWSSEVSNP{ Measurements: measurements.DefaultsFor(cloudprovider.AWS, variant.AWSSEVSNP{}), - BootloaderVersion: NewLatestPlaceholderVersion(), - TEEVersion: NewLatestPlaceholderVersion(), - SNPVersion: NewLatestPlaceholderVersion(), - MicrocodeVersion: NewLatestPlaceholderVersion(), + BootloaderVersion: NewLatestPlaceholderVersion[uint8](), + TEEVersion: NewLatestPlaceholderVersion[uint8](), + SNPVersion: NewLatestPlaceholderVersion[uint8](), + MicrocodeVersion: NewLatestPlaceholderVersion[uint8](), AMDRootKey: mustParsePEM(arkPEM), } } diff --git a/internal/config/azure.go b/internal/config/azure.go index ac2e39a2a8..273f543813 100644 --- a/internal/config/azure.go +++ b/internal/config/azure.go @@ -28,10 +28,10 @@ var ( func DefaultForAzureSEVSNP() *AzureSEVSNP { return &AzureSEVSNP{ Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureSEVSNP{}), - BootloaderVersion: NewLatestPlaceholderVersion(), - TEEVersion: NewLatestPlaceholderVersion(), - SNPVersion: NewLatestPlaceholderVersion(), - MicrocodeVersion: NewLatestPlaceholderVersion(), + BootloaderVersion: NewLatestPlaceholderVersion[uint8](), + TEEVersion: NewLatestPlaceholderVersion[uint8](), + SNPVersion: NewLatestPlaceholderVersion[uint8](), + MicrocodeVersion: NewLatestPlaceholderVersion[uint8](), FirmwareSignerConfig: SNPFirmwareSignerConfig{ AcceptedKeyDigests: idkeydigest.DefaultList(), EnforcementPolicy: idkeydigest.MAAFallback, @@ -142,14 +142,14 @@ func DefaultForAzureTDX() *AzureTDX { return &AzureTDX{ Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureTDX{}), // TODO(AB#3798): Enable latest versioning for Azure TDX - QESVN: 0, - PCESVN: 0, - TEETCBSVN: encoding.HexBytes{0x02, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - QEVendorID: encoding.HexBytes{0x93, 0x9a, 0x72, 0x33, 0xf7, 0x9c, 0x4c, 0xa9, 0x94, 0x0a, 0x0d, 0xb3, 0x95, 0x7f, 0x06, 0x07}, + QESVN: NewLatestPlaceholderVersion[uint16](), + PCESVN: NewLatestPlaceholderVersion[uint16](), + TEETCBSVN: NewLatestPlaceholderVersion[encoding.HexBytes](), + QEVendorID: NewLatestPlaceholderVersion[encoding.HexBytes](), // Don't set a default for MRSEAM as it effectively prevents upgrading the SEAM module // Quote verification still makes sure the module comes from Intel (through MRSIGNERSEAM), and is not of a lower version than expected // MRSeam: nil, - XFAM: encoding.HexBytes{0xe7, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00}, + XFAM: NewLatestPlaceholderVersion[encoding.HexBytes](), IntelRootKey: mustParsePEM(tdxRootPEM), } @@ -179,9 +179,43 @@ func (c AzureTDX) EqualTo(other AttestationCfg) (bool, error) { return c.Measurements.EqualTo(otherCfg.Measurements), nil } +// FetchAndSetLatestVersionNumbers fetches the latest version numbers from the configapi and sets them. +func (c *AzureTDX) 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.PCESVN.WantLatest || c.QESVN.WantLatest || c.TEETCBSVN.WantLatest || c.QEVendorID.WantLatest || c.XFAM.WantLatest) { + return nil + } + + versions, err := fetcher.FetchLatestVersion(ctx, variant.AzureTDX{}) + if err != nil { + return fmt.Errorf("fetching latest TCB versions from configapi: %w", err) + } + + // set values and keep WantLatest flag + if c.PCESVN.WantLatest { + c.PCESVN.Value = versions.PCESVN + } + if c.QESVN.WantLatest { + c.QESVN.Value = versions.QESVN + } + if c.TEETCBSVN.WantLatest { + c.TEETCBSVN.Value = versions.TEETCBSVN[:] + } + if c.QEVendorID.WantLatest { + c.QEVendorID.Value = versions.QEVendorID[:] + } + if c.XFAM.WantLatest { + c.XFAM.Value = versions.XFAM[:] + } + return nil +} + func (c *AzureTDX) getToMarshallLatestWithResolvedVersions() AttestationCfg { cp := *c - // TODO: We probably want to support "latest" pseudo versioning for Azure TDX - // But we should decide on which claims can be reliably used for attestation first + cp.PCESVN.WantLatest = false + cp.QESVN.WantLatest = false + cp.TEETCBSVN.WantLatest = false + cp.QEVendorID.WantLatest = false + cp.XFAM.WantLatest = false return &cp } diff --git a/internal/config/config.go b/internal/config/config.go index e4b1fa7653..24849b0211 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -468,18 +468,22 @@ func New(fileHandler file.Handler, name string, fetcher attestationconfigapi.Fet return nil, err } + // Replace "latest" placeholders for attestation version numbers with the actual latest version numbers from config API if azure := c.Attestation.AzureSEVSNP; azure != nil { if err := azure.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil { return c, err } } - + if azure := c.Attestation.AzureTDX; azure != nil { + if err := azure.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil { + return c, err + } + } if aws := c.Attestation.AWSSEVSNP; aws != nil { if err := aws.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil { return c, err } } - if gcp := c.Attestation.GCPSEVSNP; gcp != nil { if err := gcp.FetchAndSetLatestVersionNumbers(context.Background(), fetcher); err != nil { return c, err @@ -993,16 +997,16 @@ type GCPSEVSNP struct { Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"` // description: | // Lowest acceptable bootloader version. - BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"` + BootloaderVersion AttestationVersion[uint8] `json:"bootloaderVersion" yaml:"bootloaderVersion"` // description: | // Lowest acceptable TEE version. - TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"` + TEEVersion AttestationVersion[uint8] `json:"teeVersion" yaml:"teeVersion"` // description: | // Lowest acceptable SEV-SNP version. - SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"` + SNPVersion AttestationVersion[uint8] `json:"snpVersion" yaml:"snpVersion"` // description: | // Lowest acceptable microcode version. - MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"` + MicrocodeVersion AttestationVersion[uint8] `json:"microcodeVersion" yaml:"microcodeVersion"` // description: | // AMD Root Key certificate used to verify the SEV-SNP certificate chain. AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"` @@ -1080,16 +1084,16 @@ type AWSSEVSNP struct { Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"` // description: | // Lowest acceptable bootloader version. - BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"` + BootloaderVersion AttestationVersion[uint8] `json:"bootloaderVersion" yaml:"bootloaderVersion"` // description: | // Lowest acceptable TEE version. - TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"` + TEEVersion AttestationVersion[uint8] `json:"teeVersion" yaml:"teeVersion"` // description: | // Lowest acceptable SEV-SNP version. - SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"` + SNPVersion AttestationVersion[uint8] `json:"snpVersion" yaml:"snpVersion"` // description: | // Lowest acceptable microcode version. - MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"` + MicrocodeVersion AttestationVersion[uint8] `json:"microcodeVersion" yaml:"microcodeVersion"` // description: | // AMD Root Key certificate used to verify the SEV-SNP certificate chain. AMDRootKey Certificate `json:"amdRootKey" yaml:"amdRootKey"` @@ -1112,16 +1116,16 @@ type AzureSEVSNP struct { Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"` // description: | // Lowest acceptable bootloader version. - BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"` + BootloaderVersion AttestationVersion[uint8] `json:"bootloaderVersion" yaml:"bootloaderVersion"` // description: | // Lowest acceptable TEE version. - TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"` + TEEVersion AttestationVersion[uint8] `json:"teeVersion" yaml:"teeVersion"` // description: | // Lowest acceptable SEV-SNP version. - SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"` + SNPVersion AttestationVersion[uint8] `json:"snpVersion" yaml:"snpVersion"` // description: | // Lowest acceptable microcode version. - MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"` + MicrocodeVersion AttestationVersion[uint8] `json:"microcodeVersion" yaml:"microcodeVersion"` // description: | // Configuration for validating the firmware signature. FirmwareSignerConfig SNPFirmwareSignerConfig `json:"firmwareSignerConfig" yaml:"firmwareSignerConfig"` @@ -1147,22 +1151,22 @@ type AzureTDX struct { Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"` // description: | // Minimum required QE security version number (SVN). - QESVN uint16 `json:"qeSVN" yaml:"qeSVN"` + QESVN AttestationVersion[uint16] `json:"qeSVN" yaml:"qeSVN"` // description: | // Minimum required PCE security version number (SVN). - PCESVN uint16 `json:"pceSVN" yaml:"pceSVN"` + PCESVN AttestationVersion[uint16] `json:"pceSVN" yaml:"pceSVN"` // description: | // Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN). - TEETCBSVN encoding.HexBytes `json:"teeTCBSVN" yaml:"teeTCBSVN"` + TEETCBSVN AttestationVersion[encoding.HexBytes] `json:"teeTCBSVN" yaml:"teeTCBSVN"` // description: | // Expected 16 byte hex-encoded QE_VENDOR_ID field. - QEVendorID encoding.HexBytes `json:"qeVendorID" yaml:"qeVendorID"` + QEVendorID AttestationVersion[encoding.HexBytes] `json:"qeVendorID" yaml:"qeVendorID"` // description: | // Expected 48 byte hex-encoded MR_SEAM value. MRSeam encoding.HexBytes `json:"mrSeam,omitempty" yaml:"mrSeam,omitempty"` // description: | // Expected 8 byte hex-encoded XFAM field. - XFAM encoding.HexBytes `json:"xfam" yaml:"xfam"` + XFAM AttestationVersion[encoding.HexBytes] `json:"xfam" yaml:"xfam"` // description: | // Intel Root Key certificate used to verify the TDX certificate chain. IntelRootKey Certificate `json:"intelRootKey" yaml:"intelRootKey"` diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 6a932a000b..d09ec46c5c 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -70,10 +70,10 @@ func TestGetAttestationConfigMarshalsNumericalVersion(t *testing.T) { var mp map[string]interface{} require.NoError(yaml.Unmarshal(bt, &mp)) assert := assert.New(t) - assert.Equal(placeholderVersionValue, mp["microcodeVersion"]) - assert.Equal(placeholderVersionValue, mp["teeVersion"]) - assert.Equal(placeholderVersionValue, mp["snpVersion"]) - assert.Equal(placeholderVersionValue, mp["bootloaderVersion"]) + assert.EqualValues(placeholderVersionValue[uint8](), mp["microcodeVersion"]) + assert.EqualValues(placeholderVersionValue[uint8](), mp["teeVersion"]) + assert.EqualValues(placeholderVersionValue[uint8](), mp["snpVersion"]) + assert.EqualValues(placeholderVersionValue[uint8](), mp["bootloaderVersion"]) } func TestNew(t *testing.T) { @@ -99,19 +99,19 @@ func TestNew(t *testing.T) { wantResult: func() *Config { conf := Default() modifyConfigForAzureToPassValidate(conf) - conf.Attestation.AzureSEVSNP.MicrocodeVersion = AttestationVersion{ + conf.Attestation.AzureSEVSNP.MicrocodeVersion = AttestationVersion[uint8]{ Value: testCfg.Microcode, WantLatest: true, } - conf.Attestation.AzureSEVSNP.TEEVersion = AttestationVersion{ + conf.Attestation.AzureSEVSNP.TEEVersion = AttestationVersion[uint8]{ Value: 2, WantLatest: false, } - conf.Attestation.AzureSEVSNP.BootloaderVersion = AttestationVersion{ + conf.Attestation.AzureSEVSNP.BootloaderVersion = AttestationVersion[uint8]{ Value: 1, WantLatest: false, } - conf.Attestation.AzureSEVSNP.SNPVersion = AttestationVersion{ + conf.Attestation.AzureSEVSNP.SNPVersion = AttestationVersion[uint8]{ Value: testCfg.SNP, WantLatest: true, } diff --git a/internal/config/gcp.go b/internal/config/gcp.go index 47f6afcc96..20f8eaac57 100644 --- a/internal/config/gcp.go +++ b/internal/config/gcp.go @@ -22,10 +22,10 @@ var _ svnResolveMarshaller = &GCPSEVSNP{} func DefaultForGCPSEVSNP() *GCPSEVSNP { return &GCPSEVSNP{ Measurements: measurements.DefaultsFor(cloudprovider.GCP, variant.GCPSEVSNP{}), - BootloaderVersion: NewLatestPlaceholderVersion(), - TEEVersion: NewLatestPlaceholderVersion(), - SNPVersion: NewLatestPlaceholderVersion(), - MicrocodeVersion: NewLatestPlaceholderVersion(), + BootloaderVersion: NewLatestPlaceholderVersion[uint8](), + TEEVersion: NewLatestPlaceholderVersion[uint8](), + SNPVersion: NewLatestPlaceholderVersion[uint8](), + MicrocodeVersion: NewLatestPlaceholderVersion[uint8](), AMDRootKey: mustParsePEM(arkPEM), } } diff --git a/internal/config/migration/migration.go b/internal/config/migration/migration.go index 54ca54335c..d8fdd659ce 100644 --- a/internal/config/migration/migration.go +++ b/internal/config/migration/migration.go @@ -415,19 +415,19 @@ func V3ToV4(path string, fileHandler file.Handler) error { case cfgV3.Attestation.AzureSEVSNP != nil: cfgV4.Attestation.AzureSEVSNP = &config.AzureSEVSNP{ Measurements: cfgV3.Attestation.AzureSEVSNP.Measurements, - BootloaderVersion: config.AttestationVersion{ + BootloaderVersion: config.AttestationVersion[uint8]{ Value: cfgV3.Attestation.AzureSEVSNP.BootloaderVersion.Value, WantLatest: cfgV3.Attestation.AzureSEVSNP.BootloaderVersion.WantLatest, }, - TEEVersion: config.AttestationVersion{ + TEEVersion: config.AttestationVersion[uint8]{ Value: cfgV3.Attestation.AzureSEVSNP.TEEVersion.Value, WantLatest: cfgV3.Attestation.AzureSEVSNP.TEEVersion.WantLatest, }, - SNPVersion: config.AttestationVersion{ + SNPVersion: config.AttestationVersion[uint8]{ Value: cfgV3.Attestation.AzureSEVSNP.SNPVersion.Value, WantLatest: cfgV3.Attestation.AzureSEVSNP.SNPVersion.WantLatest, }, - MicrocodeVersion: config.AttestationVersion{ + MicrocodeVersion: config.AttestationVersion[uint8]{ Value: cfgV3.Attestation.AzureSEVSNP.MicrocodeVersion.Value, WantLatest: cfgV3.Attestation.AzureSEVSNP.MicrocodeVersion.WantLatest, }, diff --git a/terraform-provider-constellation/internal/provider/BUILD.bazel b/terraform-provider-constellation/internal/provider/BUILD.bazel index 1fac7618a9..54400f07f3 100644 --- a/terraform-provider-constellation/internal/provider/BUILD.bazel +++ b/terraform-provider-constellation/internal/provider/BUILD.bazel @@ -32,6 +32,7 @@ go_library( "//internal/constellation/helm", "//internal/constellation/kubecmd", "//internal/constellation/state", + "//internal/encoding", "//internal/file", "//internal/grpc/dialer", "//internal/imagefetcher", diff --git a/terraform-provider-constellation/internal/provider/attestation_data_source.go b/terraform-provider-constellation/internal/provider/attestation_data_source.go index 22246cc849..cc6e9d5f53 100644 --- a/terraform-provider-constellation/internal/provider/attestation_data_source.go +++ b/terraform-provider-constellation/internal/provider/attestation_data_source.go @@ -162,17 +162,18 @@ func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadReq insecureFetch := data.Insecure.ValueBool() - snpVersions := attestationconfigapi.VersionAPIEntry{} - if attestationVariant.Equal(variant.AzureSEVSNP{}) || - attestationVariant.Equal(variant.AWSSEVSNP{}) || + latestVersions := attestationconfigapi.VersionAPIEntry{} + if attestationVariant.Equal(variant.AWSSEVSNP{}) || + attestationVariant.Equal(variant.AzureSEVSNP{}) || + attestationVariant.Equal(variant.AzureTDX{}) || attestationVariant.Equal(variant.GCPSEVSNP{}) { - snpVersions, err = d.fetcher.FetchLatestVersion(ctx, attestationVariant) + latestVersions, err = d.fetcher.FetchLatestVersion(ctx, attestationVariant) if err != nil { resp.Diagnostics.AddError("Fetching SNP Version numbers", err.Error()) return } } - tfAttestation, err := convertToTfAttestation(attestationVariant, snpVersions) + tfAttestation, err := convertToTfAttestation(attestationVariant, latestVersions) if err != nil { resp.Diagnostics.AddError("Converting attestation", err.Error()) } diff --git a/terraform-provider-constellation/internal/provider/convert.go b/terraform-provider-constellation/internal/provider/convert.go index 8dc9225a47..57398fb502 100644 --- a/terraform-provider-constellation/internal/provider/convert.go +++ b/terraform-provider-constellation/internal/provider/convert.go @@ -17,6 +17,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/config" + "github.com/edgelesssys/constellation/v2/internal/encoding" ) // naming schema: @@ -110,12 +111,12 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation attestationConfig = &config.AzureTDX{ Measurements: c11nMeasurements, - QESVN: tfAttestation.TDX.QESVN, - PCESVN: tfAttestation.TDX.PCESVN, - TEETCBSVN: teeTCBSVN, - QEVendorID: qeVendorID, + QESVN: newVersion(tfAttestation.TDX.QESVN), + PCESVN: newVersion(tfAttestation.TDX.PCESVN), + TEETCBSVN: newVersion(encoding.HexBytes(teeTCBSVN)), + QEVendorID: newVersion(encoding.HexBytes(qeVendorID)), MRSeam: mrSeam, - XFAM: xfam, + XFAM: newVersion(encoding.HexBytes(xfam)), IntelRootKey: rootKey, } case variant.GCPSEVES{}: @@ -137,13 +138,9 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation } // convertToTfAttestationCfg converts the constellation attestation config to the related terraform structs. -func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfigapi.VersionAPIEntry) (tfAttestation attestationAttribute, err error) { +func convertToTfAttestation(attVar variant.Variant, latestVersions attestationconfigapi.VersionAPIEntry) (tfAttestation attestationAttribute, err error) { tfAttestation = attestationAttribute{ - Variant: attVar.String(), - BootloaderVersion: snpVersions.Bootloader, - TEEVersion: snpVersions.TEE, - SNPVersion: snpVersions.SNP, - MicrocodeVersion: snpVersions.Microcode, + Variant: attVar.String(), } switch attVar { @@ -153,6 +150,10 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi return tfAttestation, err } tfAttestation.AMDRootKey = certStr + tfAttestation.BootloaderVersion = latestVersions.Bootloader + tfAttestation.TEEVersion = latestVersions.TEE + tfAttestation.SNPVersion = latestVersions.SNP + tfAttestation.MicrocodeVersion = latestVersions.Microcode case variant.GCPSEVSNP{}: certStr, err := certAsString(config.DefaultForGCPSEVSNP().AMDRootKey) @@ -160,6 +161,10 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi return tfAttestation, err } tfAttestation.AMDRootKey = certStr + tfAttestation.BootloaderVersion = latestVersions.Bootloader + tfAttestation.TEEVersion = latestVersions.TEE + tfAttestation.SNPVersion = latestVersions.SNP + tfAttestation.MicrocodeVersion = latestVersions.Microcode case variant.AzureSEVSNP{}: certStr, err := certAsString(config.DefaultForAzureSEVSNP().AMDRootKey) @@ -167,6 +172,10 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi return tfAttestation, err } tfAttestation.AMDRootKey = certStr + tfAttestation.BootloaderVersion = latestVersions.Bootloader + tfAttestation.TEEVersion = latestVersions.TEE + tfAttestation.SNPVersion = latestVersions.SNP + tfAttestation.MicrocodeVersion = latestVersions.Microcode firmwareCfg := config.DefaultForAzureSEVSNP().FirmwareSignerConfig tfFirmwareCfg, err := convertToTfFirmwareCfg(firmwareCfg) @@ -174,24 +183,19 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi return tfAttestation, err } tfAttestation.AzureSNPFirmwareSignerConfig = tfFirmwareCfg + case variant.AzureTDX{}: - tdxCfg := config.DefaultForAzureTDX() - certStr, err := certAsString(tdxCfg.IntelRootKey) + certStr, err := certAsString(config.DefaultForAzureTDX().IntelRootKey) if err != nil { return tfAttestation, err } + tfAttestation.TDX.IntelRootKey = certStr + tfAttestation.TDX.PCESVN = latestVersions.PCESVN + tfAttestation.TDX.QESVN = latestVersions.QESVN + tfAttestation.TDX.TEETCBSVN = hex.EncodeToString(latestVersions.TEETCBSVN[:]) + tfAttestation.TDX.QEVendorID = hex.EncodeToString(latestVersions.QEVendorID[:]) + tfAttestation.TDX.XFAM = hex.EncodeToString(latestVersions.XFAM[:]) - tfTdxCfg := tdxConfigAttribute{ - IntelRootKey: certStr, - // TODO(AB#3798): Load these values dynamically from our attestation API - QESVN: tdxCfg.QESVN, - PCESVN: tdxCfg.PCESVN, - TEETCBSVN: hex.EncodeToString(tdxCfg.TEETCBSVN), - QEVendorID: hex.EncodeToString(tdxCfg.QEVendorID), - MRSeam: hex.EncodeToString(tdxCfg.MRSeam), - XFAM: hex.EncodeToString(tdxCfg.XFAM), - } - tfAttestation.TDX = tfTdxCfg case variant.GCPSEVES{}, variant.QEMUVTPM{}: // no additional fields default: @@ -251,8 +255,8 @@ func convertToTfMeasurements(m measurements.M) map[string]measurementAttribute { return tfMeasurements } -func newVersion(v uint8) config.AttestationVersion { - return config.AttestationVersion{ +func newVersion[T uint8 | uint16 | encoding.HexBytes](v T) config.AttestationVersion[T] { + return config.AttestationVersion[T]{ Value: v, } }