Enable versions API to handle TDX versions

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2024-06-11 16:25:24 +02:00 committed by Daniel Weiße
parent fbddbc9867
commit a34493caa6
13 changed files with 253 additions and 245 deletions

View File

@ -204,18 +204,8 @@ func (f stubVerifyFetcher) FetchAndVerifyMeasurements(_ context.Context, _ strin
type stubAttestationFetcher struct{}
func (f stubAttestationFetcher) FetchSEVSNPVersionList(_ context.Context, _ attestationconfigapi.SEVSNPVersionList) (attestationconfigapi.SEVSNPVersionList, error) {
return attestationconfigapi.SEVSNPVersionList{}, nil
}
func (f stubAttestationFetcher) FetchSEVSNPVersion(_ context.Context, _ attestationconfigapi.SEVSNPVersionAPI) (attestationconfigapi.SEVSNPVersionAPI, error) {
return attestationconfigapi.SEVSNPVersionAPI{
SEVSNPVersion: testCfg,
}, nil
}
func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.SEVSNPVersionAPI, error) {
return attestationconfigapi.SEVSNPVersionAPI{
func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.VersionAPIEntry, error) {
return attestationconfigapi.VersionAPIEntry{
SEVSNPVersion: testCfg,
}, nil
}

View File

@ -171,14 +171,6 @@ type stubConfigFetcher struct {
fetchLatestErr error
}
func (s *stubConfigFetcher) FetchSEVSNPVersion(context.Context, attestationconfigapi.SEVSNPVersionAPI) (attestationconfigapi.SEVSNPVersionAPI, error) {
panic("not implemented")
}
func (s *stubConfigFetcher) FetchSEVSNPVersionList(context.Context, attestationconfigapi.SEVSNPVersionList) (attestationconfigapi.SEVSNPVersionList, error) {
panic("not implemented")
}
func (s *stubConfigFetcher) FetchLatestVersion(context.Context, variant.Variant) (attestationconfigapi.SEVSNPVersionAPI, error) {
return attestationconfigapi.SEVSNPVersionAPI{}, s.fetchLatestErr
func (s *stubConfigFetcher) FetchLatestVersion(context.Context, variant.Variant) (attestationconfigapi.VersionAPIEntry, error) {
return attestationconfigapi.VersionAPIEntry{}, s.fetchLatestErr
}

View File

@ -80,20 +80,20 @@ func (a Client) DeleteSEVSNPVersion(ctx context.Context, attestation variant.Var
}
// List returns the list of versions for the given attestation variant.
func (a Client) List(ctx context.Context, attestation variant.Variant) (attestationconfigapi.SEVSNPVersionList, error) {
func (a Client) List(ctx context.Context, attestation variant.Variant) (attestationconfigapi.VersionList, error) {
if !attestation.Equal(variant.AzureSEVSNP{}) &&
!attestation.Equal(variant.AWSSEVSNP{}) &&
!attestation.Equal(variant.GCPSEVSNP{}) {
return attestationconfigapi.SEVSNPVersionList{}, fmt.Errorf("unsupported attestation variant: %s", attestation)
return attestationconfigapi.VersionList{}, fmt.Errorf("unsupported attestation variant: %s", attestation)
}
versions, err := apiclient.Fetch(ctx, a.s3Client, attestationconfigapi.SEVSNPVersionList{Variant: attestation})
versions, err := apiclient.Fetch(ctx, a.s3Client, attestationconfigapi.VersionList{Variant: attestation})
if err != nil {
var notFoundErr *apiclient.NotFoundError
if errors.As(err, &notFoundErr) {
return attestationconfigapi.SEVSNPVersionList{Variant: attestation}, nil
return attestationconfigapi.VersionList{Variant: attestation}, nil
}
return attestationconfigapi.SEVSNPVersionList{}, err
return attestationconfigapi.VersionList{}, err
}
versions.Variant = attestation
@ -101,10 +101,10 @@ func (a Client) List(ctx context.Context, attestation variant.Variant) (attestat
return versions, nil
}
func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.SEVSNPVersionList, versionStr string) (ops []crudCmd, err error) {
func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.VersionList, versionStr string) (ops []crudCmd, err error) {
versionStr = versionStr + ".json"
ops = append(ops, deleteCmd{
apiObject: attestationconfigapi.SEVSNPVersionAPI{
apiObject: attestationconfigapi.VersionAPIEntry{
Variant: versions.Variant,
Version: versionStr,
},
@ -121,7 +121,7 @@ func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.SEVSNPVersionL
return ops, nil
}
func (a Client) constructUploadCmd(attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, versionNames attestationconfigapi.SEVSNPVersionList, date time.Time) []crudCmd {
func (a Client) constructUploadCmd(attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, versionNames attestationconfigapi.VersionList, date time.Time) []crudCmd {
if !attestation.Equal(versionNames.Variant) {
return nil
}
@ -130,7 +130,7 @@ func (a Client) constructUploadCmd(attestation variant.Variant, version attestat
var res []crudCmd
res = append(res, putCmd{
apiObject: attestationconfigapi.SEVSNPVersionAPI{Version: dateStr, Variant: attestation, SEVSNPVersion: version},
apiObject: attestationconfigapi.VersionAPIEntry{Version: dateStr, Variant: attestation, SEVSNPVersion: version},
signer: a.signer,
})
@ -144,19 +144,19 @@ func (a Client) constructUploadCmd(attestation variant.Variant, version attestat
return res
}
func removeVersion(list attestationconfigapi.SEVSNPVersionList, versionStr string) (removedVersions attestationconfigapi.SEVSNPVersionList, err error) {
func removeVersion(list attestationconfigapi.VersionList, versionStr string) (removedVersions attestationconfigapi.VersionList, err error) {
versions := list.List
for i, v := range versions {
if v == versionStr {
if i == len(versions)-1 {
removedVersions = attestationconfigapi.SEVSNPVersionList{List: versions[:i], Variant: list.Variant}
removedVersions = attestationconfigapi.VersionList{List: versions[:i], Variant: list.Variant}
} else {
removedVersions = attestationconfigapi.SEVSNPVersionList{List: append(versions[:i], versions[i+1:]...), Variant: list.Variant}
removedVersions = attestationconfigapi.VersionList{List: append(versions[:i], versions[i+1:]...), Variant: list.Variant}
}
return removedVersions, nil
}
}
return attestationconfigapi.SEVSNPVersionList{}, fmt.Errorf("version %s not found in list %v", versionStr, versions)
return attestationconfigapi.VersionList{}, fmt.Errorf("version %s not found in list %v", versionStr, versions)
}
type crudCmd interface {

View File

@ -21,11 +21,11 @@ func TestUploadAzureSEVSNP(t *testing.T) {
}
version := attestationconfigapi.SEVSNPVersion{}
date := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC)
ops := sut.constructUploadCmd(variant.AzureSEVSNP{}, version, attestationconfigapi.SEVSNPVersionList{List: []string{"2021-01-01-01-01.json", "2019-01-01-01-01.json"}, Variant: variant.AzureSEVSNP{}}, date)
ops := sut.constructUploadCmd(variant.AzureSEVSNP{}, version, attestationconfigapi.VersionList{List: []string{"2021-01-01-01-01.json", "2019-01-01-01-01.json"}, Variant: variant.AzureSEVSNP{}}, date)
dateStr := "2023-01-01-01-01.json"
assert := assert.New(t)
assert.Contains(ops, putCmd{
apiObject: attestationconfigapi.SEVSNPVersionAPI{
apiObject: attestationconfigapi.VersionAPIEntry{
Variant: variant.AzureSEVSNP{},
Version: dateStr,
SEVSNPVersion: version,
@ -33,7 +33,7 @@ func TestUploadAzureSEVSNP(t *testing.T) {
signer: fakeSigner{},
})
assert.Contains(ops, putCmd{
apiObject: attestationconfigapi.SEVSNPVersionList{Variant: variant.AzureSEVSNP{}, List: []string{"2023-01-01-01-01.json", "2021-01-01-01-01.json", "2019-01-01-01-01.json"}},
apiObject: attestationconfigapi.VersionList{Variant: variant.AzureSEVSNP{}, List: []string{"2023-01-01-01-01.json", "2021-01-01-01-01.json", "2019-01-01-01-01.json"}},
signer: fakeSigner{},
})
}
@ -42,20 +42,20 @@ func TestDeleteAzureSEVSNPVersions(t *testing.T) {
sut := Client{
bucketID: "bucket",
}
versions := attestationconfigapi.SEVSNPVersionList{List: []string{"2023-01-01.json", "2021-01-01.json", "2019-01-01.json"}}
versions := attestationconfigapi.VersionList{List: []string{"2023-01-01.json", "2021-01-01.json", "2019-01-01.json"}}
ops, err := sut.deleteSEVSNPVersion(versions, "2021-01-01")
assert := assert.New(t)
assert.NoError(err)
assert.Contains(ops, deleteCmd{
apiObject: attestationconfigapi.SEVSNPVersionAPI{
apiObject: attestationconfigapi.VersionAPIEntry{
Version: "2021-01-01.json",
},
})
assert.Contains(ops, putCmd{
apiObject: attestationconfigapi.SEVSNPVersionList{List: []string{"2023-01-01.json", "2019-01-01.json"}},
apiObject: attestationconfigapi.VersionList{List: []string{"2023-01-01.json", "2019-01-01.json"}},
})
}

View File

@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file"
@ -127,7 +128,8 @@ func uploadReport(ctx context.Context,
latestAPIVersionAPI, err := attestationconfigapi.NewFetcherWithCustomCDNAndCosignKey(cfg.url, cfg.cosignPublicKey).FetchLatestVersion(ctx, attestation)
if err != nil {
if errors.Is(err, attestationconfigapi.ErrNoVersionsFound) {
var notFoundErr *fetcher.NotFoundError
if errors.As(err, &notFoundErr) {
log.Info("No versions found in API, but assuming that we are uploading the first version.")
} else {
return fmt.Errorf("fetching latest version: %w", err)

View File

@ -8,7 +8,6 @@ package attestationconfigapi
import (
"context"
"errors"
"fmt"
apifetcher "github.com/edgelesssys/constellation/v2/internal/api/fetcher"
@ -19,12 +18,9 @@ import (
const cosignPublicKey = constants.CosignPublicKeyReleases
// ErrNoVersionsFound is returned if no versions are found.
var ErrNoVersionsFound = errors.New("no versions found")
// Fetcher fetches config API resources without authentication.
type Fetcher interface {
FetchLatestVersion(ctx context.Context, attestation variant.Variant) (SEVSNPVersionAPI, error)
FetchLatestVersion(ctx context.Context, attestation variant.Variant) (VersionAPIEntry, error)
}
// fetcher fetches AttestationCfg API resources without authentication.
@ -64,46 +60,43 @@ func newFetcherWithClientAndVerifier(client apifetcher.HTTPClient, cosignVerifie
}
// FetchLatestVersion returns the latest versions of the given type.
func (f *fetcher) FetchLatestVersion(ctx context.Context, attesation variant.Variant) (res SEVSNPVersionAPI, err error) {
list, err := f.fetchVersionList(ctx, SEVSNPVersionList{Variant: attesation})
func (f *fetcher) FetchLatestVersion(ctx context.Context, variant variant.Variant) (VersionAPIEntry, error) {
list, err := f.fetchVersionList(ctx, variant)
if err != nil {
return res, ErrNoVersionsFound
return VersionAPIEntry{}, err
}
getVersionRequest := SEVSNPVersionAPI{
Version: list.List[0], // latest version is first in list
Variant: attesation,
}
res, err = f.fetchVersion(ctx, getVersionRequest)
if err != nil {
return res, err
}
return
// latest version is first in list
return f.fetchVersion(ctx, list.List[0], variant)
}
// fetchVersionList fetches the version list information from the config API.
func (f *fetcher) fetchVersionList(ctx context.Context, list SEVSNPVersionList) (SEVSNPVersionList, error) {
// TODO(derpsteb): Replace with FetchAndVerify once we move to v2 of the config API.
fetchedList, err := apifetcher.Fetch(ctx, f.HTTPClient, f.cdnURL, list)
func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant) (VersionList, error) {
// TODO(derpsteb): Replace with FetchAndVerify once we move to v2 of the config API and the list is saved as (.json) file.
fetchedList, err := apifetcher.Fetch(ctx, f.HTTPClient, f.cdnURL, VersionList{Variant: variant})
if err != nil {
return list, fmt.Errorf("fetching version list: %w", err)
return VersionList{}, fmt.Errorf("fetching version list: %w", err)
}
// Need to set this explicitly as the variant is not part of the marshalled JSON.
fetchedList.Variant = list.Variant
// Set the attestation variant of the list as it is not part of the marshalled JSON retrieved by Fetch
fetchedList.Variant = variant
return fetchedList, nil
}
// fetchVersion fetches the version information from the config API.
func (f *fetcher) fetchVersion(ctx context.Context, version SEVSNPVersionAPI) (SEVSNPVersionAPI, error) {
fetchedVersion, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, version, f.verifier)
func (f *fetcher) fetchVersion(ctx context.Context, version string, variant variant.Variant) (VersionAPIEntry, error) {
obj := VersionAPIEntry{
Version: version,
Variant: variant,
}
fetchedVersion, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, obj, f.verifier)
if err != nil {
return fetchedVersion, fmt.Errorf("fetching version %s: %w", version.Version, err)
return VersionAPIEntry{}, fmt.Errorf("fetching version %q: %w", version, err)
}
// Need to set this explicitly as the variant is not part of the marshalled JSON.
fetchedVersion.Variant = version.Variant
// Set the attestation variant of the list as it is not part of the marshalled JSON retrieved by FetchAndVerify
fetchedVersion.Variant = variant
return fetchedVersion, nil
}

View File

@ -22,82 +22,106 @@ import (
)
func TestFetchLatestSEVSNPVersion(t *testing.T) {
latestVersionSNP := VersionAPIEntry{
SEVSNPVersion: SEVSNPVersion{
Microcode: 93,
TEE: 0,
SNP: 6,
Bootloader: 2,
},
}
olderVersionSNP := VersionAPIEntry{
SEVSNPVersion: SEVSNPVersion{
Microcode: 1,
TEE: 0,
SNP: 1,
Bootloader: 1,
},
}
latestVersionTDX := VersionAPIEntry{
TDXVersion: TDXVersion{
QESVN: 2,
PCESVN: 3,
TEETCBSVN: [16]byte{4},
QEVendorID: [16]byte{5},
XFAM: [8]byte{6},
},
}
olderVersionTDX := VersionAPIEntry{
TDXVersion: TDXVersion{
QESVN: 1,
PCESVN: 2,
TEETCBSVN: [16]byte{3},
QEVendorID: [16]byte{4},
XFAM: [8]byte{5},
},
}
latestStr := "2023-06-11-14-09.json"
olderStr := "2019-01-01-01-01.json"
testcases := map[string]struct {
testCases := map[string]struct {
fetcherVersions []string
timeAtTest time.Time
wantErr bool
attestation variant.Variant
expectedVersion func() SEVSNPVersionAPI
olderVersion func() SEVSNPVersionAPI
latestVersion func() SEVSNPVersionAPI
expectedVersion VersionAPIEntry
olderVersion VersionAPIEntry
latestVersion VersionAPIEntry
}{
"get latest version azure": {
"get latest version azure-sev-snp": {
fetcherVersions: []string{latestStr, olderStr},
attestation: variant.AzureSEVSNP{},
expectedVersion: func() SEVSNPVersionAPI { tmp := latestVersion; tmp.Variant = variant.AzureSEVSNP{}; return tmp },
olderVersion: func() SEVSNPVersionAPI { tmp := olderVersion; tmp.Variant = variant.AzureSEVSNP{}; return tmp },
latestVersion: func() SEVSNPVersionAPI { tmp := latestVersion; tmp.Variant = variant.AzureSEVSNP{}; return tmp },
expectedVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(),
olderVersion: func() VersionAPIEntry { tmp := olderVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(),
latestVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(),
},
"get latest version aws": {
"get latest version aws-sev-snp": {
fetcherVersions: []string{latestStr, olderStr},
attestation: variant.AWSSEVSNP{},
expectedVersion: func() SEVSNPVersionAPI { tmp := latestVersion; tmp.Variant = variant.AWSSEVSNP{}; return tmp },
olderVersion: func() SEVSNPVersionAPI { tmp := olderVersion; tmp.Variant = variant.AWSSEVSNP{}; return tmp },
latestVersion: func() SEVSNPVersionAPI { tmp := latestVersion; tmp.Variant = variant.AWSSEVSNP{}; return tmp },
expectedVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(),
olderVersion: func() VersionAPIEntry { tmp := olderVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(),
latestVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(),
},
"get latest version azure-tdx": {
fetcherVersions: []string{latestStr, olderStr},
attestation: variant.AzureTDX{},
expectedVersion: func() VersionAPIEntry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(),
olderVersion: func() VersionAPIEntry { tmp := olderVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(),
latestVersion: func() VersionAPIEntry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(),
},
}
for name, tc := range testcases {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
client := &http.Client{
Transport: &fakeConfigAPIHandler{
attestation: tc.attestation,
versions: tc.fetcherVersions,
latestDate: latestStr,
latestVersion: tc.latestVersion(),
latestVersion: tc.latestVersion,
olderDate: olderStr,
olderVersion: tc.olderVersion(),
olderVersion: tc.olderVersion,
},
}
fetcher := newFetcherWithClientAndVerifier(client, dummyVerifier{}, constants.CDNRepositoryURL)
fetcher := newFetcherWithClientAndVerifier(client, stubVerifier{}, constants.CDNRepositoryURL)
res, err := fetcher.FetchLatestVersion(context.Background(), tc.attestation)
assert := assert.New(t)
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.expectedVersion(), res)
assert.Equal(tc.expectedVersion, res)
}
})
}
}
var latestVersion = SEVSNPVersionAPI{
SEVSNPVersion: SEVSNPVersion{
Microcode: 93,
TEE: 0,
SNP: 6,
Bootloader: 2,
},
}
var olderVersion = SEVSNPVersionAPI{
SEVSNPVersion: SEVSNPVersion{
Microcode: 1,
TEE: 0,
SNP: 1,
Bootloader: 1,
},
}
type fakeConfigAPIHandler struct {
attestation variant.Variant
versions []string
latestDate string
latestVersion SEVSNPVersionAPI
latestVersion VersionAPIEntry
olderDate string
olderVersion SEVSNPVersionAPI
olderVersion VersionAPIEntry
}
// RoundTrip resolves the request and returns a dummy response.
@ -148,8 +172,8 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err
return nil, errors.New("no endpoint found")
}
type dummyVerifier struct{}
type stubVerifier struct{}
func (s dummyVerifier) VerifySignature(_, _ []byte) error {
func (s stubVerifier) VerifySignature(_, _ []byte) error {
return nil
}

View File

@ -1,110 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package attestationconfigapi
import (
"encoding/json"
"fmt"
"path"
"sort"
"strings"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
)
// AttestationURLPath is the URL path to the attestation versions.
const AttestationURLPath = "constellation/v1/attestation"
// SEVSNPVersion tracks the latest version of each component of the SEVSNP.
type SEVSNPVersion struct {
// Bootloader is the latest version of the SEVSNP bootloader.
Bootloader uint8 `json:"bootloader"`
// TEE is the latest version of the SEVSNP TEE.
TEE uint8 `json:"tee"`
// SNP is the latest version of the SEVSNP SNP.
SNP uint8 `json:"snp"`
// Microcode is the latest version of the SEVSNP microcode.
Microcode uint8 `json:"microcode"`
}
// SEVSNPVersionAPI is the request to get the version information of the specific version in the config api.
// Because variant is not part of the marshalled JSON, fetcher and client methods need to fill the variant property.
// Once we switch to v2 of the API we should embed the variant in the object.
// That would remove the possibility of some fetcher/client code forgetting to set the variant.
type SEVSNPVersionAPI struct {
Version string `json:"-"`
Variant variant.Variant `json:"-"`
SEVSNPVersion
}
// JSONPath returns the path to the JSON file for the request to the config api.
func (i SEVSNPVersionAPI) JSONPath() string {
return path.Join(AttestationURLPath, i.Variant.String(), i.Version)
}
// ValidateRequest validates the request.
func (i SEVSNPVersionAPI) ValidateRequest() error {
if !strings.HasSuffix(i.Version, ".json") {
return fmt.Errorf("version has no .json suffix")
}
return nil
}
// Validate is a No-Op at the moment.
func (i SEVSNPVersionAPI) Validate() error {
return nil
}
// SEVSNPVersionList is the request to list all versions in the config api.
// Because variant is not part of the marshalled JSON, fetcher and client methods need to fill the variant property.
// Once we switch to v2 of the API we could embed the variant in the object and remove some code from fetcher & client.
// That would remove the possibility of some fetcher/client code forgetting to set the variant.
type SEVSNPVersionList struct {
Variant variant.Variant
List []string
}
// MarshalJSON marshals the i's list property to JSON.
func (i SEVSNPVersionList) MarshalJSON() ([]byte, error) {
return json.Marshal(i.List)
}
// UnmarshalJSON unmarshals a list of strings into i's list property.
func (i *SEVSNPVersionList) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &i.List)
}
// JSONPath returns the path to the JSON file for the request to the config api.
func (i SEVSNPVersionList) JSONPath() string {
return path.Join(AttestationURLPath, i.Variant.String(), "list")
}
// ValidateRequest is a NoOp as there is no input.
func (i SEVSNPVersionList) ValidateRequest() error {
return nil
}
// SortReverse sorts the list of versions in reverse order.
func (i *SEVSNPVersionList) SortReverse() {
sort.Sort(sort.Reverse(sort.StringSlice(i.List)))
}
// AddVersion adds new to i's list and sorts the element in descending order.
func (i *SEVSNPVersionList) AddVersion(new string) {
i.List = append(i.List, new)
i.List = variant.RemoveDuplicate(i.List)
i.SortReverse()
}
// Validate validates the response.
func (i SEVSNPVersionList) Validate() error {
if len(i.List) < 1 {
return fmt.Errorf("no versions found in /list")
}
return nil
}

View File

@ -0,0 +1,127 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package attestationconfigapi
import (
"encoding/json"
"fmt"
"path"
"sort"
"strings"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
)
// AttestationURLPath is the URL path to the attestation versions.
const AttestationURLPath = "constellation/v1/attestation"
// SEVSNPVersion tracks the latest version of each component for SEV-SNP.
type SEVSNPVersion struct {
// Bootloader is the latest version of the SEV-SNP bootloader.
Bootloader uint8 `json:"bootloader,omitempty"`
// TEE is the latest version of the SEV-SNP TEE.
TEE uint8 `json:"tee,omitempty"`
// SNP is the latest version of the SEV-SNP SNP.
SNP uint8 `json:"snp,omitempty"`
// Microcode is the latest version of the SEV-SNP microcode.
Microcode uint8 `json:"microcode,omitempty"`
}
// TDXVersion tracks the latest version of each component for TDX.
type TDXVersion struct {
// QESVN is the latest QE security version number.
QESVN uint16 `json:"qeSVN,omitempty"`
// PCESVN is the latest PCE security version number.
PCESVN uint16 `json:"pceSVN,omitempty"`
// TEETCBSVN are the latest component-wise security version numbers for the TEE.
TEETCBSVN [16]byte `json:"teeTCBSVN,omitempty"`
// QEVendorID is the latest QE vendor ID.
QEVendorID [16]byte `json:"qeVendorID,omitempty"`
// XFAM is the latest XFAM field.
XFAM [8]byte `json:"xfam,omitempty"`
}
// VersionAPIEntry is the request to get the version information of the specific version in the config api.
//
// TODO: Because variant is not part of the marshalled JSON, fetcher and client methods need to fill the variant property.
// In API v2 we should embed the variant in the object and remove some code from fetcher & client.
// That would remove the possibility of some fetcher/client code forgetting to set the variant.
type VersionAPIEntry struct {
Version string `json:"-"`
Variant variant.Variant `json:"-"`
SEVSNPVersion
TDXVersion
}
// JSONPath returns the path to the JSON file for the request to the config api.
func (i VersionAPIEntry) JSONPath() string {
return path.Join(AttestationURLPath, i.Variant.String(), i.Version)
}
// ValidateRequest validates the request.
func (i VersionAPIEntry) ValidateRequest() error {
if !strings.HasSuffix(i.Version, ".json") {
return fmt.Errorf("version has no .json suffix")
}
return nil
}
// Validate is a No-Op at the moment.
func (i VersionAPIEntry) Validate() error {
return nil
}
// VersionList is the request to retrieve of all versions in the API for one attestation variant.
//
// TODO: Because variant is not part of the marshalled JSON, fetcher and client methods need to fill the variant property.
// In API v2 we should embed the variant in the object and remove some code from fetcher & client.
// That would remove the possibility of some fetcher/client code forgetting to set the variant.
type VersionList struct {
Variant variant.Variant
List []string
}
// MarshalJSON marshals the i's list property to JSON.
func (i VersionList) MarshalJSON() ([]byte, error) {
return json.Marshal(i.List)
}
// UnmarshalJSON unmarshals a list of strings into i's list property.
func (i *VersionList) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &i.List)
}
// JSONPath returns the path to the JSON file for the request to the config api.
func (i VersionList) JSONPath() string {
return path.Join(AttestationURLPath, i.Variant.String(), "list")
}
// ValidateRequest is a NoOp as there is no input.
func (i VersionList) ValidateRequest() error {
return nil
}
// SortReverse sorts the list of versions in reverse order.
func (i *VersionList) SortReverse() {
sort.Sort(sort.Reverse(sort.StringSlice(i.List)))
}
// AddVersion adds new to i's list and sorts the element in descending order.
func (i *VersionList) AddVersion(new string) {
i.List = append(i.List, new)
i.List = variant.RemoveDuplicate(i.List)
i.SortReverse()
}
// Validate validates the response.
func (i VersionList) Validate() error {
if len(i.List) < 1 {
return fmt.Errorf("no versions found in /list")
}
return nil
}

View File

@ -14,23 +14,23 @@ import (
"github.com/stretchr/testify/require"
)
func TestSEVSNPVersionListMarshalUnmarshalJSON(t *testing.T) {
func TestVersionListMarshalUnmarshalJSON(t *testing.T) {
tests := map[string]struct {
input SEVSNPVersionList
output SEVSNPVersionList
input VersionList
output VersionList
wantDiff bool
}{
"success": {
input: SEVSNPVersionList{List: []string{"v1", "v2"}},
output: SEVSNPVersionList{List: []string{"v1", "v2"}},
input: VersionList{List: []string{"v1", "v2"}},
output: VersionList{List: []string{"v1", "v2"}},
},
"variant is lost": {
input: SEVSNPVersionList{List: []string{"v1", "v2"}, Variant: variant.AzureSEVSNP{}},
output: SEVSNPVersionList{List: []string{"v1", "v2"}},
input: VersionList{List: []string{"v1", "v2"}, Variant: variant.AzureSEVSNP{}},
output: VersionList{List: []string{"v1", "v2"}},
},
"wrong order": {
input: SEVSNPVersionList{List: []string{"v1", "v2"}},
output: SEVSNPVersionList{List: []string{"v2", "v1"}},
input: VersionList{List: []string{"v1", "v2"}},
output: VersionList{List: []string{"v2", "v1"}},
wantDiff: true,
},
}
@ -40,7 +40,7 @@ func TestSEVSNPVersionListMarshalUnmarshalJSON(t *testing.T) {
inputRaw, err := tc.input.MarshalJSON()
require.NoError(t, err)
var actual SEVSNPVersionList
var actual VersionList
err = actual.UnmarshalJSON(inputRaw)
require.NoError(t, err)
@ -53,7 +53,7 @@ func TestSEVSNPVersionListMarshalUnmarshalJSON(t *testing.T) {
}
}
func TestSEVSNPVersionListAddVersion(t *testing.T) {
func TestVersionListAddVersion(t *testing.T) {
tests := map[string]struct {
versions []string
new string
@ -68,7 +68,7 @@ func TestSEVSNPVersionListAddVersion(t *testing.T) {
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
v := SEVSNPVersionList{List: tc.versions}
v := VersionList{List: tc.versions}
v.AddVersion(tc.new)
assert.Equal(t, tc.expected, v.List)

View File

@ -1051,18 +1051,8 @@ func getConfigAsMap(conf *Config, t *testing.T) (res configMap) {
type stubAttestationFetcher struct{}
func (f stubAttestationFetcher) FetchSEVSNPVersionList(_ context.Context, _ attestationconfigapi.SEVSNPVersionList) (attestationconfigapi.SEVSNPVersionList, error) {
return attestationconfigapi.SEVSNPVersionList{}, nil
}
func (f stubAttestationFetcher) FetchSEVSNPVersion(_ context.Context, _ attestationconfigapi.SEVSNPVersionAPI) (attestationconfigapi.SEVSNPVersionAPI, error) {
return attestationconfigapi.SEVSNPVersionAPI{
SEVSNPVersion: testCfg,
}, nil
}
func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.SEVSNPVersionAPI, error) {
return attestationconfigapi.SEVSNPVersionAPI{
func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.VersionAPIEntry, error) {
return attestationconfigapi.VersionAPIEntry{
SEVSNPVersion: testCfg,
}, nil
}

View File

@ -162,7 +162,7 @@ func (d *AttestationDataSource) Read(ctx context.Context, req datasource.ReadReq
insecureFetch := data.Insecure.ValueBool()
snpVersions := attestationconfigapi.SEVSNPVersionAPI{}
snpVersions := attestationconfigapi.VersionAPIEntry{}
if attestationVariant.Equal(variant.AzureSEVSNP{}) ||
attestationVariant.Equal(variant.AWSSEVSNP{}) ||
attestationVariant.Equal(variant.GCPSEVSNP{}) {

View File

@ -137,7 +137,7 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation
}
// convertToTfAttestationCfg converts the constellation attestation config to the related terraform structs.
func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfigapi.SEVSNPVersionAPI) (tfAttestation attestationAttribute, err error) {
func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfigapi.VersionAPIEntry) (tfAttestation attestationAttribute, err error) {
tfAttestation = attestationAttribute{
Variant: attVar.String(),
BootloaderVersion: snpVersions.Bootloader,