From a34493caa63cdea3a96af49dd993bca3701757dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= Date: Tue, 11 Jun 2024 16:25:24 +0200 Subject: [PATCH] Enable versions API to handle TDX versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel Weiße --- .../cmd/configfetchmeasurements_test.go | 14 +- cli/internal/cmd/iamupgradeapply_test.go | 12 +- .../attestationconfigapi/cli/client/client.go | 26 ++-- .../cli/client/client_test.go | 12 +- .../api/attestationconfigapi/cli/upload.go | 4 +- internal/api/attestationconfigapi/fetcher.go | 49 +++---- .../api/attestationconfigapi/fetcher_test.go | 102 ++++++++------ internal/api/attestationconfigapi/snp.go | 110 --------------- internal/api/attestationconfigapi/version.go | 127 ++++++++++++++++++ .../{snp_test.go => version_test.go} | 24 ++-- internal/config/config_test.go | 14 +- .../provider/attestation_data_source.go | 2 +- .../internal/provider/convert.go | 2 +- 13 files changed, 253 insertions(+), 245 deletions(-) delete mode 100644 internal/api/attestationconfigapi/snp.go create mode 100644 internal/api/attestationconfigapi/version.go rename internal/api/attestationconfigapi/{snp_test.go => version_test.go} (65%) diff --git a/cli/internal/cmd/configfetchmeasurements_test.go b/cli/internal/cmd/configfetchmeasurements_test.go index ab81bd808..608f2af90 100644 --- a/cli/internal/cmd/configfetchmeasurements_test.go +++ b/cli/internal/cmd/configfetchmeasurements_test.go @@ -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 } diff --git a/cli/internal/cmd/iamupgradeapply_test.go b/cli/internal/cmd/iamupgradeapply_test.go index 5b4ffb3a0..11a0c0e60 100644 --- a/cli/internal/cmd/iamupgradeapply_test.go +++ b/cli/internal/cmd/iamupgradeapply_test.go @@ -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 } diff --git a/internal/api/attestationconfigapi/cli/client/client.go b/internal/api/attestationconfigapi/cli/client/client.go index 476ea22d5..bf41b5476 100644 --- a/internal/api/attestationconfigapi/cli/client/client.go +++ b/internal/api/attestationconfigapi/cli/client/client.go @@ -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, ¬FoundErr) { - 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 { diff --git a/internal/api/attestationconfigapi/cli/client/client_test.go b/internal/api/attestationconfigapi/cli/client/client_test.go index 77059b27b..ff504bd00 100644 --- a/internal/api/attestationconfigapi/cli/client/client_test.go +++ b/internal/api/attestationconfigapi/cli/client/client_test.go @@ -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"}}, }) } diff --git a/internal/api/attestationconfigapi/cli/upload.go b/internal/api/attestationconfigapi/cli/upload.go index ac52b2f95..f04400337 100644 --- a/internal/api/attestationconfigapi/cli/upload.go +++ b/internal/api/attestationconfigapi/cli/upload.go @@ -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, ¬FoundErr) { 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) diff --git a/internal/api/attestationconfigapi/fetcher.go b/internal/api/attestationconfigapi/fetcher.go index d89a0c751..6990426bf 100644 --- a/internal/api/attestationconfigapi/fetcher.go +++ b/internal/api/attestationconfigapi/fetcher.go @@ -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 } diff --git a/internal/api/attestationconfigapi/fetcher_test.go b/internal/api/attestationconfigapi/fetcher_test.go index 0110009cf..93c013247 100644 --- a/internal/api/attestationconfigapi/fetcher_test.go +++ b/internal/api/attestationconfigapi/fetcher_test.go @@ -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 } diff --git a/internal/api/attestationconfigapi/snp.go b/internal/api/attestationconfigapi/snp.go deleted file mode 100644 index a98fae5f9..000000000 --- a/internal/api/attestationconfigapi/snp.go +++ /dev/null @@ -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 -} diff --git a/internal/api/attestationconfigapi/version.go b/internal/api/attestationconfigapi/version.go new file mode 100644 index 000000000..751244aba --- /dev/null +++ b/internal/api/attestationconfigapi/version.go @@ -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 +} diff --git a/internal/api/attestationconfigapi/snp_test.go b/internal/api/attestationconfigapi/version_test.go similarity index 65% rename from internal/api/attestationconfigapi/snp_test.go rename to internal/api/attestationconfigapi/version_test.go index da2f09cfa..ee87bfc37 100644 --- a/internal/api/attestationconfigapi/snp_test.go +++ b/internal/api/attestationconfigapi/version_test.go @@ -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) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 58a0eb22c..6a932a000 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -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 } diff --git a/terraform-provider-constellation/internal/provider/attestation_data_source.go b/terraform-provider-constellation/internal/provider/attestation_data_source.go index f731683ab..22246cc84 100644 --- a/terraform-provider-constellation/internal/provider/attestation_data_source.go +++ b/terraform-provider-constellation/internal/provider/attestation_data_source.go @@ -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{}) { diff --git a/terraform-provider-constellation/internal/provider/convert.go b/terraform-provider-constellation/internal/provider/convert.go index cfe9ec7fa..8dc9225a4 100644 --- a/terraform-provider-constellation/internal/provider/convert.go +++ b/terraform-provider-constellation/internal/provider/convert.go @@ -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,