diff --git a/.github/actions/e2e_attestationconfigapi/action.yml b/.github/actions/e2e_attestationconfigapi/action.yml index a98b54670..02ec28084 100644 --- a/.github/actions/e2e_attestationconfigapi/action.yml +++ b/.github/actions/e2e_attestationconfigapi/action.yml @@ -2,9 +2,9 @@ name: E2E Attestationconfig API Test description: "Test the attestationconfig CLI is functional." inputs: - csp: - description: "Cloud provider to run tests against" - default: "azure" + attestationVariant: + description: "attestation variant to run tests against" + default: "azure-sev-snp" cosignPrivateKey: description: "Cosign private key" required: true @@ -30,4 +30,4 @@ runs: COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }} COSIGN_PASSWORD: ${{ inputs.cosignPassword }} run: | - bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.csp }} + bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.attestationVariant }} diff --git a/.github/actions/e2e_verify/action.yml b/.github/actions/e2e_verify/action.yml index f330751f5..07abb7a88 100644 --- a/.github/actions/e2e_verify/action.yml +++ b/.github/actions/e2e_verify/action.yml @@ -68,9 +68,9 @@ runs: case "${{ inputs.attestationVariant }}" in - "azure-sev-snp"|"aws-sev-snp"|"gcp-sev-snp") + "azure-sev-snp"|"azure-tdx"|"aws-sev-snp"|"gcp-sev-snp") echo "Extracting TCB versions for API update" - constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "snp-report-${node}.json" + constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "attestation-report-${node}.json" ;; *) constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 @@ -88,22 +88,19 @@ runs: aws-region: eu-central-1 - name: Upload extracted TCBs - if: github.ref_name == 'main' && (inputs.attestationVariant == 'azure-sev-snp' || inputs.attestationVariant == 'aws-sev-snp' || inputs.attestationVariant == 'gcp-sev-snp') + if: github.ref_name == 'main' && (inputs.attestationVariant == 'azure-sev-snp' || inputs.attestationVariant == 'azure-tdx' || inputs.attestationVariant == 'aws-sev-snp' || inputs.attestationVariant == 'gcp-sev-snp') shell: bash env: COSIGN_PASSWORD: ${{ inputs.cosignPassword }} COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }} run: | - reports=(snp-report-*.json) + reports=(attestation-report-*.json) if [ -z ${#reports[@]} ]; then exit 1 fi - attestationVariant=${{ inputs.attestationVariant }} - cloudProvider=${attestationVariant%%-*} - for file in "${reports[@]}"; do path=$(realpath "${file}") cat "${path}" - bazel run //internal/api/attestationconfigapi/cli -- upload "${cloudProvider}" snp-report "${path}" + bazel run //internal/api/attestationconfigapi/cli -- upload ${{ inputs.attestationVariant }} attestation-report "${path}" done diff --git a/.github/workflows/e2e-attestationconfigapi.yml b/.github/workflows/e2e-attestationconfigapi.yml index 09f4f554e..2fdc318d0 100644 --- a/.github/workflows/e2e-attestationconfigapi.yml +++ b/.github/workflows/e2e-attestationconfigapi.yml @@ -15,9 +15,9 @@ jobs: e2e-api: strategy: fail-fast: false - max-parallel: 1 + max-parallel: 2 matrix: - csp: ["azure", "aws", "gcp"] + attestationVariant: ["azure-sev-snp", "azure-tdx", "aws-sev-snp", "gcp-sev-snp"] runs-on: ubuntu-22.04 permissions: id-token: write @@ -36,4 +36,4 @@ jobs: with: cosignPrivateKey: ${{ secrets.COSIGN_DEV_PRIVATE_KEY }} cosignPassword: ${{ secrets.COSIGN_DEV_PASSWORD }} - csp: ${{ matrix.csp }} + attestationVariant: ${{ matrix.attestationVariant }} diff --git a/cli/internal/cmd/configfetchmeasurements_test.go b/cli/internal/cmd/configfetchmeasurements_test.go index 608f2af90..12fd761ae 100644 --- a/cli/internal/cmd/configfetchmeasurements_test.go +++ b/cli/internal/cmd/configfetchmeasurements_test.go @@ -204,8 +204,8 @@ func (f stubVerifyFetcher) FetchAndVerifyMeasurements(_ context.Context, _ strin type stubAttestationFetcher struct{} -func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.VersionAPIEntry, error) { - return attestationconfigapi.VersionAPIEntry{ +func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.Entry, error) { + return attestationconfigapi.Entry{ SEVSNPVersion: testCfg, }, nil } diff --git a/cli/internal/cmd/iamupgradeapply_test.go b/cli/internal/cmd/iamupgradeapply_test.go index 11a0c0e60..786c24857 100644 --- a/cli/internal/cmd/iamupgradeapply_test.go +++ b/cli/internal/cmd/iamupgradeapply_test.go @@ -171,6 +171,6 @@ type stubConfigFetcher struct { fetchLatestErr error } -func (s *stubConfigFetcher) FetchLatestVersion(context.Context, variant.Variant) (attestationconfigapi.VersionAPIEntry, error) { - return attestationconfigapi.VersionAPIEntry{}, s.fetchLatestErr +func (s *stubConfigFetcher) FetchLatestVersion(context.Context, variant.Variant) (attestationconfigapi.Entry, error) { + return attestationconfigapi.Entry{}, s.fetchLatestErr } diff --git a/internal/api/attestationconfigapi/cli/BUILD.bazel b/internal/api/attestationconfigapi/cli/BUILD.bazel index 03e6529d5..32ec8ec2b 100644 --- a/internal/api/attestationconfigapi/cli/BUILD.bazel +++ b/internal/api/attestationconfigapi/cli/BUILD.bazel @@ -22,7 +22,6 @@ go_library( "//internal/api/attestationconfigapi/cli/client", "//internal/api/fetcher", "//internal/attestation/variant", - "//internal/cloud/cloudprovider", "//internal/constants", "//internal/file", "//internal/logger", @@ -31,6 +30,7 @@ go_library( "@com_github_aws_aws_sdk_go_v2//aws", "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", "@com_github_aws_aws_sdk_go_v2_service_s3//types", + "@com_github_google_go_tdx_guest//proto/tdx", "@com_github_spf13_afero//:afero", "@com_github_spf13_cobra//:cobra", ], diff --git a/internal/api/attestationconfigapi/cli/client/BUILD.bazel b/internal/api/attestationconfigapi/cli/client/BUILD.bazel index cacb2f05d..c90cb34b9 100644 --- a/internal/api/attestationconfigapi/cli/client/BUILD.bazel +++ b/internal/api/attestationconfigapi/cli/client/BUILD.bazel @@ -29,7 +29,6 @@ go_test( embed = [":client"], deps = [ "//internal/api/attestationconfigapi", - "//internal/attestation/variant", "@com_github_stretchr_testify//assert", ], ) diff --git a/internal/api/attestationconfigapi/cli/client/client.go b/internal/api/attestationconfigapi/cli/client/client.go index bf41b5476..53c79285e 100644 --- a/internal/api/attestationconfigapi/cli/client/client.go +++ b/internal/api/attestationconfigapi/cli/client/client.go @@ -15,8 +15,11 @@ import ( "errors" "fmt" "log/slog" - "time" + "path" + "strings" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go/aws" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" apiclient "github.com/edgelesssys/constellation/v2/internal/api/client" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" @@ -35,6 +38,8 @@ type Client struct { bucketID string signer sigstore.Signer cacheWindowSize int + + log *slog.Logger } // New returns a new Client. @@ -50,50 +55,34 @@ func New(ctx context.Context, cfg staticupload.Config, cosignPwd, privateKey []b signer: sigstore.NewSigner(cosignPwd, privateKey), bucketID: cfg.Bucket, cacheWindowSize: versionWindowSize, + log: log, } return repo, clientClose, nil } -// uploadSEVSNPVersion uploads the latest version numbers of the SEVSNP. Then version name is the UTC timestamp of the date. The /list entry stores the version name + .json suffix. -func (a Client) uploadSEVSNPVersion(ctx context.Context, attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, date time.Time) error { - versions, err := a.List(ctx, attestation) - if err != nil { - return fmt.Errorf("fetch version list: %w", err) - } - ops := a.constructUploadCmd(attestation, version, versions, date) - - return executeAllCmds(ctx, a.s3Client, ops) -} - -// DeleteSEVSNPVersion deletes the given version (without .json suffix) from the API. -func (a Client) DeleteSEVSNPVersion(ctx context.Context, attestation variant.Variant, versionStr string) error { - versions, err := a.List(ctx, attestation) +// DeleteVersion deletes the given version (without .json suffix) from the API. +func (c Client) DeleteVersion(ctx context.Context, attestation variant.Variant, versionStr string) error { + versions, err := c.List(ctx, attestation) if err != nil { return fmt.Errorf("fetch version list: %w", err) } - ops, err := a.deleteSEVSNPVersion(versions, versionStr) + ops, err := c.deleteVersion(versions, versionStr) if err != nil { return err } - return executeAllCmds(ctx, a.s3Client, ops) + return executeAllCmds(ctx, c.s3Client, ops) } // List returns the list of versions for the given attestation variant. -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.VersionList{}, fmt.Errorf("unsupported attestation variant: %s", attestation) - } - - versions, err := apiclient.Fetch(ctx, a.s3Client, attestationconfigapi.VersionList{Variant: attestation}) +func (c Client) List(ctx context.Context, attestation variant.Variant) (attestationconfigapi.List, error) { + versions, err := apiclient.Fetch(ctx, c.s3Client, attestationconfigapi.List{Variant: attestation}) if err != nil { var notFoundErr *apiclient.NotFoundError if errors.As(err, ¬FoundErr) { - return attestationconfigapi.VersionList{Variant: attestation}, nil + return attestationconfigapi.List{Variant: attestation}, nil } - return attestationconfigapi.VersionList{}, err + return attestationconfigapi.List{}, err } versions.Variant = attestation @@ -101,10 +90,10 @@ func (a Client) List(ctx context.Context, attestation variant.Variant) (attestat return versions, nil } -func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.VersionList, versionStr string) (ops []crudCmd, err error) { +func (c Client) deleteVersion(versions attestationconfigapi.List, versionStr string) (ops []crudCmd, err error) { versionStr = versionStr + ".json" ops = append(ops, deleteCmd{ - apiObject: attestationconfigapi.VersionAPIEntry{ + apiObject: attestationconfigapi.Entry{ Variant: versions.Variant, Version: versionStr, }, @@ -116,47 +105,46 @@ func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.VersionList, v } ops = append(ops, putCmd{ apiObject: removedVersions, - signer: a.signer, + signer: c.signer, }) return ops, nil } -func (a Client) constructUploadCmd(attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, versionNames attestationconfigapi.VersionList, date time.Time) []crudCmd { - if !attestation.Equal(versionNames.Variant) { - return nil +func (c Client) listCachedVersions(ctx context.Context, attestation variant.Variant) ([]string, error) { + list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: aws.String(c.bucketID), + Prefix: aws.String(reportVersionDir(attestation)), + }) + if err != nil { + return nil, fmt.Errorf("list objects: %w", err) } - dateStr := date.Format(VersionFormat) + ".json" - var res []crudCmd + var dates []string + for _, obj := range list.Contents { + fileName := path.Base(*obj.Key) - res = append(res, putCmd{ - apiObject: attestationconfigapi.VersionAPIEntry{Version: dateStr, Variant: attestation, SEVSNPVersion: version}, - signer: a.signer, - }) - - versionNames.AddVersion(dateStr) - - res = append(res, putCmd{ - apiObject: versionNames, - signer: a.signer, - }) - - return res + // The cache contains signature and json files + // We only want the json files + if date, ok := strings.CutSuffix(fileName, ".json"); ok { + dates = append(dates, date) + } + } + return dates, nil } -func removeVersion(list attestationconfigapi.VersionList, versionStr string) (removedVersions attestationconfigapi.VersionList, err error) { +func removeVersion(list attestationconfigapi.List, versionStr string) (removedVersions attestationconfigapi.List, err error) { versions := list.List for i, v := range versions { if v == versionStr { if i == len(versions)-1 { - removedVersions = attestationconfigapi.VersionList{List: versions[:i], Variant: list.Variant} + removedVersions = attestationconfigapi.List{List: versions[:i], Variant: list.Variant} } else { - removedVersions = attestationconfigapi.VersionList{List: append(versions[:i], versions[i+1:]...), Variant: list.Variant} + removedVersions = attestationconfigapi.List{List: append(versions[:i], versions[i+1:]...), Variant: list.Variant} } return removedVersions, nil } } - return attestationconfigapi.VersionList{}, fmt.Errorf("version %s not found in list %v", versionStr, versions) + return attestationconfigapi.List{}, 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 ff504bd00..008414f59 100644 --- a/internal/api/attestationconfigapi/cli/client/client_test.go +++ b/internal/api/attestationconfigapi/cli/client/client_test.go @@ -7,60 +7,28 @@ package client import ( "testing" - "time" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" - "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/stretchr/testify/assert" ) -func TestUploadAzureSEVSNP(t *testing.T) { - sut := Client{ - bucketID: "bucket", - signer: fakeSigner{}, - } - version := attestationconfigapi.SEVSNPVersion{} - date := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC) - 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.VersionAPIEntry{ - Variant: variant.AzureSEVSNP{}, - Version: dateStr, - SEVSNPVersion: version, - }, - signer: fakeSigner{}, - }) - assert.Contains(ops, putCmd{ - 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{}, - }) -} - func TestDeleteAzureSEVSNPVersions(t *testing.T) { sut := Client{ bucketID: "bucket", } - versions := attestationconfigapi.VersionList{List: []string{"2023-01-01.json", "2021-01-01.json", "2019-01-01.json"}} + versions := attestationconfigapi.List{List: []string{"2023-01-01.json", "2021-01-01.json", "2019-01-01.json"}} - ops, err := sut.deleteSEVSNPVersion(versions, "2021-01-01") + ops, err := sut.deleteVersion(versions, "2021-01-01") assert := assert.New(t) assert.NoError(err) assert.Contains(ops, deleteCmd{ - apiObject: attestationconfigapi.VersionAPIEntry{ + apiObject: attestationconfigapi.Entry{ Version: "2021-01-01.json", }, }) assert.Contains(ops, putCmd{ - apiObject: attestationconfigapi.VersionList{List: []string{"2023-01-01.json", "2019-01-01.json"}}, + apiObject: attestationconfigapi.List{List: []string{"2023-01-01.json", "2019-01-01.json"}}, }) } - -type fakeSigner struct{} - -func (fakeSigner) Sign(_ []byte) ([]byte, error) { - return []byte("signature"), nil -} diff --git a/internal/api/attestationconfigapi/cli/client/reporter.go b/internal/api/attestationconfigapi/cli/client/reporter.go index cc0f9b5ed..7e40d6a3e 100644 --- a/internal/api/attestationconfigapi/cli/client/reporter.go +++ b/internal/api/attestationconfigapi/cli/client/reporter.go @@ -8,6 +8,7 @@ package client import ( "context" + "encoding/json" "errors" "fmt" "path" @@ -15,9 +16,6 @@ import ( "strings" "time" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/aws/aws-sdk-go/aws" - "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/client" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" @@ -33,153 +31,335 @@ func reportVersionDir(attestation variant.Variant) string { return path.Join(attestationconfigapi.AttestationURLPath, attestation.String(), cachedVersionsSubDir) } -// UploadSEVSNPVersionLatest saves the given version to the cache, determines the smallest +// UploadLatestVersion saves the given version to the cache, determines the smallest // TCB version in the cache among the last cacheWindowSize versions and updates // the latest version in the API if there is an update. // force can be used to bypass the validation logic against the cached versions. -func (c Client) UploadSEVSNPVersionLatest(ctx context.Context, attestation variant.Variant, inputVersion, - latestAPIVersion attestationconfigapi.SEVSNPVersion, now time.Time, force bool, +func (c Client) UploadLatestVersion( + ctx context.Context, attestationVariant variant.Variant, + inputVersion, latestVersionInAPI any, + now time.Time, force bool, ) error { - if err := c.cacheSEVSNPVersion(ctx, attestation, inputVersion, now); err != nil { - return fmt.Errorf("reporting version: %w", err) - } - if force { - return c.uploadSEVSNPVersion(ctx, attestation, inputVersion, now) - } - versionDates, err := c.listCachedVersions(ctx, attestation) + // Validate input versions against configured attestation variant + // This allows us to skip these checks in the individual variant implementations + var err error + actionForVariant(attestationVariant, + func() { + if _, ok := inputVersion.(attestationconfigapi.TDXVersion); !ok { + err = fmt.Errorf("input version %q is not a TDX version", inputVersion) + } + if _, ok := latestVersionInAPI.(attestationconfigapi.TDXVersion); !ok { + err = fmt.Errorf("latest API version %q is not a TDX version", latestVersionInAPI) + } + }, + func() { + if _, ok := inputVersion.(attestationconfigapi.SEVSNPVersion); !ok { + err = fmt.Errorf("input version %q is not a SNP version", inputVersion) + } + if _, ok := latestVersionInAPI.(attestationconfigapi.SEVSNPVersion); !ok { + err = fmt.Errorf("latest API version %q is not a SNP version", latestVersionInAPI) + } + }, + ) if err != nil { - return fmt.Errorf("list reported versions: %w", err) + return err + } + + if err := c.addVersionToCache(ctx, attestationVariant, inputVersion, now); err != nil { + return fmt.Errorf("adding version to cache: %w", err) + } + + // If force is set, immediately update the latest version to the new version in the API. + if force { + return c.uploadAsLatestVersion(ctx, attestationVariant, inputVersion, now) + } + + // Otherwise, check the cached versions and update the latest version in the API if necessary. + versionDates, err := c.listCachedVersions(ctx, attestationVariant) + if err != nil { + return fmt.Errorf("listing existing cached versions: %w", err) } if len(versionDates) < c.cacheWindowSize { - c.s3Client.Logger.Warn(fmt.Sprintf("Skipping version update, found %d, expected %d reported versions.", len(versionDates), c.cacheWindowSize)) + c.log.Warn(fmt.Sprintf("Skipping version update, found %d, expected %d reported versions.", len(versionDates), c.cacheWindowSize)) return nil } - minVersion, minDate, err := c.findMinVersion(ctx, attestation, versionDates) + + minVersion, minDate, err := c.findMinVersion(ctx, attestationVariant, versionDates) if err != nil { - return fmt.Errorf("get minimal version: %w", err) + return fmt.Errorf("determining minimal version in cache: %w", err) } - c.s3Client.Logger.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate)) - shouldUpdateAPI, err := isInputNewerThanOtherVersion(minVersion, latestAPIVersion) - if err != nil { + c.log.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate)) + + if !isInputNewerThanOtherVersion(attestationVariant, minVersion, latestVersionInAPI) { + c.log.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v. Skipping list update", minVersion, latestVersionInAPI)) return ErrNoNewerVersion } - if !shouldUpdateAPI { - c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v", minVersion, latestAPIVersion)) - return nil - } - c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is newer than latest API version: %+v", minVersion, latestAPIVersion)) + + c.log.Info(fmt.Sprintf("Input version: %+v is newer than latest API version: %+v", minVersion, latestVersionInAPI)) t, err := time.Parse(VersionFormat, minDate) if err != nil { return fmt.Errorf("parsing date: %w", err) } - if err := c.uploadSEVSNPVersion(ctx, attestation, minVersion, t); err != nil { - return fmt.Errorf("uploading version: %w", err) + + if err := c.uploadAsLatestVersion(ctx, attestationVariant, minVersion, t); err != nil { + return fmt.Errorf("uploading as latest version: %w", err) } - c.s3Client.Logger.Info(fmt.Sprintf("Successfully uploaded new SEV-SNP version: %+v", minVersion)) + + c.log.Info(fmt.Sprintf("Successfully uploaded new %s version: %+v", attestationVariant, minVersion)) return nil } -// cacheSEVSNPVersion uploads the latest observed version numbers of the SEVSNP. This version is used to later report the latest version numbers to the API. -func (c Client) cacheSEVSNPVersion(ctx context.Context, attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, date time.Time) error { +// uploadAsLatestVersion uploads the given version and updates the list to set it as the "latest" version. +// The version's name is the UTC timestamp of the date. +// The /list entry stores the version name + .json suffix. +func (c Client) uploadAsLatestVersion(ctx context.Context, variant variant.Variant, inputVersion any, date time.Time) error { + versions, err := c.List(ctx, variant) + if err != nil { + return fmt.Errorf("fetch version list: %w", err) + } + if !variant.Equal(versions.Variant) { + return nil + } + dateStr := date.Format(VersionFormat) + ".json" - res := putCmd{ - apiObject: reportedSEVSNPVersionAPI{Version: dateStr, variant: attestation, SEVSNPVersion: version}, + var ops []crudCmd + + obj := apiVersionObject{version: dateStr, variant: variant, cached: false} + obj.setVersion(inputVersion) + ops = append(ops, putCmd{ + apiObject: obj, + signer: c.signer, + }) + + versions.AddVersion(dateStr) + + ops = append(ops, putCmd{ + apiObject: versions, + signer: c.signer, + }) + + return executeAllCmds(ctx, c.s3Client, ops) +} + +// addVersionToCache adds the given version to the cache. +func (c Client) addVersionToCache(ctx context.Context, variant variant.Variant, inputVersion any, date time.Time) error { + dateStr := date.Format(VersionFormat) + ".json" + obj := apiVersionObject{version: dateStr, variant: variant, cached: true} + obj.setVersion(inputVersion) + cmd := putCmd{ + apiObject: obj, signer: c.signer, } - return res.Execute(ctx, c.s3Client) + return cmd.Execute(ctx, c.s3Client) } -func (c Client) listCachedVersions(ctx context.Context, attestation variant.Variant) ([]string, error) { - list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ - Bucket: aws.String(c.bucketID), - Prefix: aws.String(reportVersionDir(attestation)), - }) - if err != nil { - return nil, fmt.Errorf("list objects: %w", err) - } - var dates []string - for _, obj := range list.Contents { - fileName := path.Base(*obj.Key) - if strings.HasSuffix(fileName, ".json") { - dates = append(dates, fileName[:len(fileName)-5]) - } - } - return dates, nil +// findMinVersion returns the minimal version in the cache among the last cacheWindowSize versions. +func (c Client) findMinVersion( + ctx context.Context, attestationVariant variant.Variant, versionDates []string, +) (any, string, error) { + var getMinimalVersion func() (any, string, error) + + actionForVariant(attestationVariant, + func() { + getMinimalVersion = func() (any, string, error) { + return findMinimalVersion[attestationconfigapi.TDXVersion](ctx, attestationVariant, versionDates, c.s3Client, c.cacheWindowSize) + } + }, + func() { + getMinimalVersion = func() (any, string, error) { + return findMinimalVersion[attestationconfigapi.SEVSNPVersion](ctx, attestationVariant, versionDates, c.s3Client, c.cacheWindowSize) + } + }, + ) + return getMinimalVersion() } -// findMinVersion finds the minimal version of the given version dates among the latest values in the version window size. -func (c Client) findMinVersion(ctx context.Context, attesation variant.Variant, versionDates []string) (attestationconfigapi.SEVSNPVersion, string, error) { - var minimalVersion *attestationconfigapi.SEVSNPVersion +func findMinimalVersion[T attestationconfigapi.TDXVersion | attestationconfigapi.SEVSNPVersion]( + ctx context.Context, variant variant.Variant, versionDates []string, + s3Client *client.Client, cacheWindowSize int, +) (T, string, error) { + var minimalVersion *T var minimalDate string sort.Sort(sort.Reverse(sort.StringSlice(versionDates))) // sort in reverse order to slice the latest versions - versionDates = versionDates[:c.cacheWindowSize] + versionDates = versionDates[:cacheWindowSize] sort.Strings(versionDates) // sort with oldest first to to take the minimal version with the oldest date + for _, date := range versionDates { - obj, err := client.Fetch(ctx, c.s3Client, reportedSEVSNPVersionAPI{Version: date + ".json", variant: attesation}) + obj, err := client.Fetch(ctx, s3Client, apiVersionObject{version: date + ".json", variant: variant, cached: true}) if err != nil { - return attestationconfigapi.SEVSNPVersion{}, "", fmt.Errorf("get object: %w", err) + return *new(T), "", fmt.Errorf("get object: %w", err) } - // Need to set this explicitly as the variant is not part of the marshalled JSON. - obj.variant = attesation + obj.variant = variant // variant is not set by Fetch, set it manually if minimalVersion == nil { - minimalVersion = &obj.SEVSNPVersion + v := obj.getVersion().(T) + minimalVersion = &v + minimalDate = date + continue + } + + // If the current minimal version has newer versions than the one we just fetched, + // update the minimal version to the older version. + if isInputNewerThanOtherVersion(variant, *minimalVersion, obj.getVersion()) { + v := obj.getVersion().(T) + minimalVersion = &v minimalDate = date - } else { - shouldUpdateMinimal, err := isInputNewerThanOtherVersion(*minimalVersion, obj.SEVSNPVersion) - if err != nil { - continue - } - if shouldUpdateMinimal { - minimalVersion = &obj.SEVSNPVersion - minimalDate = date - } } } + return *minimalVersion, minimalDate, nil } -// isInputNewerThanOtherVersion compares all version fields and returns true if any input field is newer. -func isInputNewerThanOtherVersion(input, other attestationconfigapi.SEVSNPVersion) (bool, error) { - if input == other { - return false, nil - } - if input.TEE < other.TEE { - return false, fmt.Errorf("input TEE version: %d is older than latest API version: %d", input.TEE, other.TEE) - } - if input.SNP < other.SNP { - return false, fmt.Errorf("input SNP version: %d is older than latest API version: %d", input.SNP, other.SNP) - } - if input.Microcode < other.Microcode { - return false, fmt.Errorf("input Microcode version: %d is older than latest API version: %d", input.Microcode, other.Microcode) - } - if input.Bootloader < other.Bootloader { - return false, fmt.Errorf("input Bootloader version: %d is older than latest API version: %d", input.Bootloader, other.Bootloader) - } - return true, nil +func isInputNewerThanOtherVersion(variant variant.Variant, inputVersion, otherVersion any) bool { + var result bool + actionForVariant(variant, + func() { + input := inputVersion.(attestationconfigapi.TDXVersion) + other := otherVersion.(attestationconfigapi.TDXVersion) + result = isInputNewerThanOtherTDXVersion(input, other) + }, + func() { + input := inputVersion.(attestationconfigapi.SEVSNPVersion) + other := otherVersion.(attestationconfigapi.SEVSNPVersion) + result = isInputNewerThanOtherSEVSNPVersion(input, other) + }, + ) + return result } -// reportedSEVSNPVersionAPI is the request to get the version information of the specific version in the config api. -type reportedSEVSNPVersionAPI struct { - Version string `json:"-"` +type apiVersionObject struct { + version string `json:"-"` variant variant.Variant `json:"-"` - attestationconfigapi.SEVSNPVersion + cached bool `json:"-"` + snp attestationconfigapi.SEVSNPVersion + tdx attestationconfigapi.TDXVersion +} + +func (a apiVersionObject) MarshalJSON() ([]byte, error) { + var res []byte + var err error + actionForVariant(a.variant, + func() { + res, err = json.Marshal(a.tdx) + }, + func() { + res, err = json.Marshal(a.snp) + }, + ) + return res, err +} + +func (a *apiVersionObject) UnmarshalJSON(data []byte) error { + errTDX := json.Unmarshal(data, &a.tdx) + errSNP := json.Unmarshal(data, &a.snp) + if errTDX == nil || errSNP == nil { + return nil + } + return fmt.Errorf("trying to unmarshal data into both TDX and SNP versions: %w", errors.Join(errTDX, errSNP)) } // JSONPath returns the path to the JSON file for the request to the config api. -func (i reportedSEVSNPVersionAPI) JSONPath() string { - return path.Join(reportVersionDir(i.variant), i.Version) +// This is the path to the cached version in the S3 bucket. +func (a apiVersionObject) JSONPath() string { + if a.cached { + return path.Join(reportVersionDir(a.variant), a.version) + } + return path.Join(attestationconfigapi.AttestationURLPath, a.variant.String(), a.version) } // ValidateRequest validates the request. -func (i reportedSEVSNPVersionAPI) ValidateRequest() error { - if !strings.HasSuffix(i.Version, ".json") { +func (a apiVersionObject) ValidateRequest() error { + if !strings.HasSuffix(a.version, ".json") { return fmt.Errorf("version has no .json suffix") } return nil } -// Validate is a No-Op at the moment. -func (i reportedSEVSNPVersionAPI) Validate() error { +// Validate is a No-Op. +func (a apiVersionObject) Validate() error { return nil } + +// getVersion returns the version. +func (a apiVersionObject) getVersion() any { + var res any + actionForVariant(a.variant, + func() { + res = a.tdx + }, + func() { + res = a.snp + }, + ) + return res +} + +// setVersion sets the version. +func (a *apiVersionObject) setVersion(version any) { + actionForVariant(a.variant, + func() { + a.tdx = version.(attestationconfigapi.TDXVersion) + }, + func() { + a.snp = version.(attestationconfigapi.SEVSNPVersion) + }, + ) +} + +// actionForVariant performs the given action based on the whether variant is a TDX or SEV-SNP variant. +func actionForVariant( + attestationVariant variant.Variant, + tdxAction func(), snpAction func(), +) { + switch attestationVariant { + case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.GCPSEVSNP{}: + snpAction() + case variant.AzureTDX{}: + tdxAction() + default: + panic(fmt.Sprintf("unsupported attestation variant: %s", attestationVariant)) + } +} + +// isInputNewerThanOtherSEVSNPVersion compares all version fields and returns false if any input field is older, or the versions are equal. +func isInputNewerThanOtherSEVSNPVersion(input, other attestationconfigapi.SEVSNPVersion) bool { + if input == other { + return false + } + if input.TEE < other.TEE { + return false + } + if input.SNP < other.SNP { + return false + } + if input.Microcode < other.Microcode { + return false + } + if input.Bootloader < other.Bootloader { + return false + } + return true +} + +// isInputNewerThanOtherSEVSNPVersion compares all version fields and returns false if any input field is older, or the versions are equal. +func isInputNewerThanOtherTDXVersion(input, other attestationconfigapi.TDXVersion) bool { + if input == other { + return false + } + + if input.PCESVN < other.PCESVN { + return false + } + if input.QESVN < other.QESVN { + return false + } + + // Validate component-wise security version numbers + for idx, inputVersion := range input.TEETCBSVN { + if inputVersion < other.TEETCBSVN[idx] { + return false + } + } + + return true +} diff --git a/internal/api/attestationconfigapi/cli/client/reporter_test.go b/internal/api/attestationconfigapi/cli/client/reporter_test.go index f3bc9a4ff..9d786f33d 100644 --- a/internal/api/attestationconfigapi/cli/client/reporter_test.go +++ b/internal/api/attestationconfigapi/cli/client/reporter_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestIsInputNewerThanLatestAPI(t *testing.T) { +func TestIsInputNewerThanOtherSEVSNPVersion(t *testing.T) { newTestCfg := func() attestationconfigapi.SEVSNPVersion { return attestationconfigapi.SEVSNPVersion{ Microcode: 93, @@ -25,7 +25,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) { latest attestationconfigapi.SEVSNPVersion input attestationconfigapi.SEVSNPVersion expect bool - errMsg string }{ "input is older than latest": { input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion { @@ -34,7 +33,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) { }(newTestCfg()), latest: newTestCfg(), expect: false, - errMsg: "input Microcode version: 92 is older than latest API version: 93", }, "input has greater and smaller version field than latest": { input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion { @@ -44,7 +42,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) { }(newTestCfg()), latest: newTestCfg(), expect: false, - errMsg: "input Bootloader version: 1 is older than latest API version: 2", }, "input is newer than latest": { input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion { @@ -62,14 +59,80 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) { } for name, tc := range testCases { t.Run(name, func(t *testing.T) { - isNewer, err := isInputNewerThanOtherVersion(tc.input, tc.latest) - assert := assert.New(t) - if tc.errMsg != "" { - assert.EqualError(err, tc.errMsg) - } else { - assert.NoError(err) - assert.Equal(tc.expect, isNewer) - } + isNewer := isInputNewerThanOtherSEVSNPVersion(tc.input, tc.latest) + assert.Equal(t, tc.expect, isNewer) + }) + } +} + +func TestIsInputNewerThanOtherTDXVersion(t *testing.T) { + newTestVersion := func() attestationconfigapi.TDXVersion { + return attestationconfigapi.TDXVersion{ + QESVN: 1, + PCESVN: 2, + TEETCBSVN: [16]byte{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + QEVendorID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + XFAM: [8]byte{0, 1, 2, 3, 4, 5, 6, 7}, + } + } + + testCases := map[string]struct { + latest attestationconfigapi.TDXVersion + input attestationconfigapi.TDXVersion + expect bool + }{ + "input is older than latest": { + input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion { + c.QESVN-- + return c + }(newTestVersion()), + latest: newTestVersion(), + expect: false, + }, + "input has greater and smaller version field than latest": { + input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion { + c.QESVN++ + c.PCESVN-- + return c + }(newTestVersion()), + latest: newTestVersion(), + expect: false, + }, + "input is newer than latest": { + input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion { + c.QESVN++ + return c + }(newTestVersion()), + latest: newTestVersion(), + expect: true, + }, + "input is equal to latest": { + input: newTestVersion(), + latest: newTestVersion(), + expect: false, + }, + "tee tcb svn is newer": { + input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion { + c.TEETCBSVN[4]++ + return c + }(newTestVersion()), + latest: newTestVersion(), + expect: true, + }, + "xfam is different": { + input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion { + c.XFAM[3]++ + return c + }(newTestVersion()), + latest: newTestVersion(), + expect: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + isNewer := isInputNewerThanOtherTDXVersion(tc.input, tc.latest) + assert.Equal(t, tc.expect, isNewer) }) } } diff --git a/internal/api/attestationconfigapi/cli/delete.go b/internal/api/attestationconfigapi/cli/delete.go index aa3947337..013c69481 100644 --- a/internal/api/attestationconfigapi/cli/delete.go +++ b/internal/api/attestationconfigapi/cli/delete.go @@ -18,7 +18,6 @@ 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/attestation/variant" - "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/staticupload" "github.com/spf13/cobra" @@ -27,21 +26,21 @@ import ( // newDeleteCmd creates the delete command. func newDeleteCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "delete {aws|azure|gcp} {snp-report|guest-firmware} ", + Use: "delete {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} ", Short: "Delete an object from the attestationconfig API", Long: "Delete a specific object version from the config api. is the name of the object to delete (without .json suffix)", - Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete azure snp-report 1.0.0", - Args: cobra.MatchAll(cobra.ExactArgs(3), isCloudProvider(0), isValidKind(1)), + Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete azure-sev-snp attestation-report 1.0.0", + Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)), PreRunE: envCheck, RunE: runDelete, } recursivelyCmd := &cobra.Command{ - Use: "recursive {aws|azure|gcp}", + Use: "recursive {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp}", Short: "delete all objects from the API path constellation/v1/attestation/", Long: "Delete all objects from the API path constellation/v1/attestation/", - Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure", - Args: cobra.MatchAll(cobra.ExactArgs(1), isCloudProvider(0)), + Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure-sev-snp", + Args: cobra.MatchAll(cobra.ExactArgs(1), isAttestationVariant(0)), RunE: runRecursiveDelete, } @@ -75,16 +74,7 @@ func runDelete(cmd *cobra.Command, args []string) (retErr error) { } }() - switch deleteCfg.provider { - case cloudprovider.AWS: - return deleteEntry(cmd.Context(), variant.AWSSEVSNP{}, client, deleteCfg) - case cloudprovider.Azure: - return deleteEntry(cmd.Context(), variant.AzureSEVSNP{}, client, deleteCfg) - case cloudprovider.GCP: - return deleteEntry(cmd.Context(), variant.GCPSEVSNP{}, client, deleteCfg) - default: - return fmt.Errorf("unsupported cloud provider: %s", deleteCfg.provider) - } + return deleteEntry(cmd.Context(), client, deleteCfg) } func runRecursiveDelete(cmd *cobra.Command, args []string) (retErr error) { @@ -112,23 +102,13 @@ func runRecursiveDelete(cmd *cobra.Command, args []string) (retErr error) { } }() - var deletePath string - switch deleteCfg.provider { - case cloudprovider.AWS: - deletePath = path.Join(attestationconfigapi.AttestationURLPath, variant.AWSSEVSNP{}.String()) - case cloudprovider.Azure: - deletePath = path.Join(attestationconfigapi.AttestationURLPath, variant.AzureSEVSNP{}.String()) - case cloudprovider.GCP: - deletePath = path.Join(attestationconfigapi.AttestationURLPath, variant.GCPSEVSNP{}.String()) - default: - return fmt.Errorf("unsupported cloud provider: %s", deleteCfg.provider) - } + deletePath := path.Join(attestationconfigapi.AttestationURLPath, deleteCfg.variant.String()) return deleteEntryRecursive(cmd.Context(), deletePath, client, deleteCfg) } type deleteConfig struct { - provider cloudprovider.Provider + variant variant.Variant kind objectKind version string region string @@ -155,12 +135,15 @@ func newDeleteConfig(cmd *cobra.Command, args [3]string) (deleteConfig, error) { } apiCfg := getAPIEnvironment(testing) - provider := cloudprovider.FromString(args[0]) + variant, err := variant.FromString(args[0]) + if err != nil { + return deleteConfig{}, fmt.Errorf("invalid attestation variant: %q: %w", args[0], err) + } kind := kindFromString(args[1]) version := args[2] return deleteConfig{ - provider: provider, + variant: variant, kind: kind, version: version, region: region, @@ -171,12 +154,12 @@ func newDeleteConfig(cmd *cobra.Command, args [3]string) (deleteConfig, error) { }, nil } -func deleteEntry(ctx context.Context, attvar variant.Variant, client *client.Client, cfg deleteConfig) error { - if cfg.kind != snpReport { +func deleteEntry(ctx context.Context, client *client.Client, cfg deleteConfig) error { + if cfg.kind != attestationReport { return fmt.Errorf("kind %s not supported", cfg.kind) } - return client.DeleteSEVSNPVersion(ctx, attvar, cfg.version) + return client.DeleteVersion(ctx, cfg.variant, cfg.version) } func deleteEntryRecursive(ctx context.Context, path string, client *staticupload.Client, cfg deleteConfig) error { diff --git a/internal/api/attestationconfigapi/cli/e2e/test.sh.in b/internal/api/attestationconfigapi/cli/e2e/test.sh.in index 5fb23f06f..647fd6e08 100755 --- a/internal/api/attestationconfigapi/cli/e2e/test.sh.in +++ b/internal/api/attestationconfigapi/cli/e2e/test.sh.in @@ -19,39 +19,67 @@ configapi_cli=$(realpath @@CONFIGAPI_CLI@@) stat "${configapi_cli}" >> /dev/null configapi_cli="${configapi_cli} --testing" ###### script body ###### -function variant() { - if [[ $1 == "aws" ]]; then - echo "aws-sev-snp" - return 0 - elif [[ $1 == "azure" ]]; then - echo "azure-sev-snp" - return 0 - elif [[ $1 == "gcp" ]]; then - echo "gcp-sev-snp" - return 0 - else - echo "Unknown CSP: $1" - exit 1 - fi -} - -csp=$1 -readonly csp -attestationType=$(variant "$csp") +attestationVariant=$1 +readonly attestationVariant readonly region="eu-west-1" readonly bucket="resource-api-testing" tmpdir=$(mktemp -d) readonly tmpdir -registerExitHandler "rm -rf $tmpdir" +registerExitHandler "rm -rf ${tmpdir}" # empty the bucket version state -${configapi_cli} delete recursive "$csp" --region "$region" --bucket "$bucket" +${configapi_cli} delete recursive "${attestationVariant}" --region "${region}" --bucket "${bucket}" -# the high version numbers ensure that it's newer than the current latest value -readonly current_report_path="$tmpdir/currentSnpReport.json" -cat << EOF > "$current_report_path" +readonly current_report_path="${tmpdir}/attestationReportCurrent.json" +readonly report_path="${tmpdir}/attestationReport.json" +readonly older_report_path="${tmpdir}/attestationReportOld.json" + +if [[ ${attestationVariant} == *-tdx ]]; then + cat << EOF > "${current_report_path}" +{ + "header": { + "qe_svn": "AAA=", + "pce_svn": "AAA=", + "qe_vendor_id": "KioqKioqKioqKioqKioqKg==" + }, + "td_quote_body": { + "tee_tcb_svn": "AAAAAAAAAAAAAAAAAAAAAA==", + "xfam": "AAAAAAAAAAA=" + } +} +EOF + # the high version numbers ensure that it's newer than the current latest value + cat << EOF > "${report_path}" +{ + "header": { + "qe_svn": "//8=", + "pce_svn": "//8=", + "qe_vendor_id": "KioqKioqKioqKioqKioqKg==" + }, + "td_quote_body": { + "tee_tcb_svn": "/////////////////////w==", + "xfam": "AQIDBAUGBwg=" + } +} +EOF + # has an older version + cat << EOF > "${older_report_path}" +{ + "header": { + "qe_svn": "//8=", + "pce_svn": "/v8=", + "qe_vendor_id": "KioqKioqKioqKioqKioqKg==" + }, + "td_quote_body": { + "tee_tcb_svn": "/////////////////////g==", + "xfam": "AQIDBAUGBwg=" + } +} +EOF +elif [[ ${attestationVariant} == *-sev-snp ]]; then + cat << EOF > "${current_report_path}" { "snp_report": { "reported_tcb": { @@ -75,90 +103,105 @@ cat << EOF > "$current_report_path" } } EOF + # the high version numbers ensure that it's newer than the current latest value + cat << EOF > "${report_path}" +{ + "snp_report": { + "reported_tcb": { + "bootloader": 255, + "tee": 255, + "snp": 255, + "microcode": 255 + }, + "committed_tcb": { + "bootloader": 255, + "tee": 255, + "snp": 255, + "microcode": 255 + }, + "launch_tcb": { + "bootloader": 255, + "tee": 255, + "snp": 255, + "microcode": 255 + } + } +} +EOF + # has an older version + cat << EOF > "${older_report_path}" +{ + "snp_report": { + "reported_tcb": { + "bootloader": 255, + "tee": 255, + "snp": 255, + "microcode": 254 + }, + "committed_tcb": { + "bootloader": 255, + "tee": 255, + "snp": 255, + "microcode": 254 + }, + "launch_tcb": { + "bootloader": 255, + "tee": 255, + "snp": 255, + "microcode": 254 + } + } +} +EOF +else + echo "Unknown attestation variant: ${attestationVariant}" + exit 1 +fi + # upload a fake latest version for the fetcher -${configapi_cli} upload "$csp" snp-report "$current_report_path" --force --upload-date "2000-01-01-01-01" --region "$region" --bucket "$bucket" - -# the high version numbers ensure that it's newer than the current latest value -readonly report_path="$tmpdir/snpReport.json" -cat << EOF > "$report_path" -{ - "snp_report": { - "reported_tcb": { - "bootloader": 255, - "tee": 255, - "snp": 255, - "microcode": 255 - }, - "committed_tcb": { - "bootloader": 255, - "tee": 255, - "snp": 255, - "microcode": 255 - }, - "launch_tcb": { - "bootloader": 255, - "tee": 255, - "snp": 255, - "microcode": 255 - } - } -} -EOF - -# has an older version -readonly older_report_path="$tmpdir/snpReportOld.json" -cat << EOF > "$older_report_path" -{ - "snp_report": { - "reported_tcb": { - "bootloader": 255, - "tee": 255, - "snp": 255, - "microcode": 254 - }, - "committed_tcb": { - "bootloader": 255, - "tee": 255, - "snp": 255, - "microcode": 254 - }, - "launch_tcb": { - "bootloader": 255, - "tee": 255, - "snp": 255, - "microcode": 254 - } - } -} -EOF +${configapi_cli} upload "${attestationVariant}" attestation-report "${current_report_path}" --force --upload-date "2000-01-01-01-01" --region "${region}" --bucket "${bucket}" # report 3 versions with different dates to fill the reporter cache readonly date_oldest="2023-02-01-03-04" -${configapi_cli} upload "$csp" snp-report "$older_report_path" --upload-date "$date_oldest" --region "$region" --bucket "$bucket" --cache-window-size 3 +${configapi_cli} upload "${attestationVariant}" attestation-report "${older_report_path}" --upload-date "${date_oldest}" --region "${region}" --bucket "${bucket}" --cache-window-size 3 readonly date_older="2023-02-02-03-04" -${configapi_cli} upload "$csp" snp-report "$older_report_path" --upload-date "$date_older" --region "$region" --bucket "$bucket" --cache-window-size 3 +${configapi_cli} upload "${attestationVariant}" attestation-report "${older_report_path}" --upload-date "${date_older}" --region "${region}" --bucket "${bucket}" --cache-window-size 3 readonly date="2023-02-03-03-04" -${configapi_cli} upload "$csp" snp-report "$report_path" --upload-date "$date" --region "$region" --bucket "$bucket" --cache-window-size 3 +${configapi_cli} upload "${attestationVariant}" attestation-report "${report_path}" --upload-date "${date}" --region "${region}" --bucket "${bucket}" --cache-window-size 3 # expect that $date_oldest is served as latest version -basepath="constellation/v1/attestation/${attestationType}" +basepath="constellation/v1/attestation/${attestationVariant}" baseurl="https://d33dzgxuwsgbpw.cloudfront.net/${basepath}" -if ! curl -fsSL "${baseurl}"/${date_oldest}.json > version.json; then +if ! curl -fsSL "${baseurl}/${date_oldest}.json" > version.json; then echo "Checking for uploaded version file ${basepath}/${date_oldest}.json: request returned ${?}" exit 1 fi -# check that version values are equal to expected -if ! cmp -s <(echo -n '{"bootloader":255,"tee":255,"snp":255,"microcode":254}') version.json; then - echo "The version content:" - cat version.json - echo " is not equal to the expected version content:" - echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}' - exit 1 + +if [[ ${attestationVariant} == *-tdx ]]; then + # check that version values are equal to expected + if ! cmp -s <(echo -n '{"qeSVN":65535,"pceSVN":65534,"teeTCBSVN":[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254],"qeVendorID":[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42],"xfam":[1,2,3,4,5,6,7,8]}') version.json; then + echo "The version content:" + cat version.json + echo " is not equal to the expected version content:" + echo '{"qeSVN":65535,"pceSVN":65534,"teeTCBSVN":[255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254],"qeVendorID":[42,42,42,42,42,42,42,42,42,42,42,42,42,42,42,42],"xfam":[1,2,3,4,5,6,7,8]}' + exit 1 + fi +elif [[ ${attestationVariant} == *-sev-snp ]]; then + # check that version values are equal to expected + if ! cmp -s <(echo -n '{"bootloader":255,"tee":255,"snp":255,"microcode":254}') version.json; then + echo "The version content:" + cat version.json + echo " is not equal to the expected version content:" + echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}' + exit 1 + fi fi -if ! curl -fsSL "${baseurl}"/${date_oldest}.json.sig > /dev/null; then + +if ! curl -fsSL "${baseurl}/${date_oldest}.json.sig" > /dev/null; then echo "Checking for uploaded version signature file ${basepath}/${date_oldest}.json.sig: request returned ${?}" exit 1 fi + # check list endpoint if ! curl -fsSL "${baseurl}"/list > list.json; then echo "Checking for uploaded list file ${basepath}/list: request returned ${?}" @@ -174,28 +217,28 @@ if ! cmp -s <(echo -n '["2023-02-01-03-04.json","2000-01-01-01-01.json"]') list. fi # check that the other versions are not uploaded -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_older}.json) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_older}.json") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date_older}.json, but got ${http_code}" exit 1 fi -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date}.json.sig) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date}.json.sig") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date}.json, but got ${http_code}" exit 1 fi -${configapi_cli} delete "$csp" snp-report "$date_oldest" --region "$region" --bucket "$bucket" +${configapi_cli} delete "${attestationVariant}" attestation-report "${date_oldest}" --region "${region}" --bucket "${bucket}" # Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail. -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}" exit 1 fi # Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail. -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json.sig) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json.sig") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}" exit 1 fi diff --git a/internal/api/attestationconfigapi/cli/upload.go b/internal/api/attestationconfigapi/cli/upload.go index f04400337..54edb01c6 100644 --- a/internal/api/attestationconfigapi/cli/upload.go +++ b/internal/api/attestationconfigapi/cli/upload.go @@ -7,6 +7,7 @@ package main import ( "context" + "encoding/binary" "errors" "fmt" "log/slog" @@ -17,30 +18,30 @@ import ( "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" "github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/staticupload" "github.com/edgelesssys/constellation/v2/internal/verify" + "github.com/google/go-tdx-guest/proto/tdx" "github.com/spf13/afero" "github.com/spf13/cobra" ) func newUploadCmd() *cobra.Command { uploadCmd := &cobra.Command{ - Use: "upload {aws|azure|gcp} {snp-report|guest-firmware} ", + Use: "upload {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} ", Short: "Upload an object to the attestationconfig API", - Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first."+ + Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first.\n"+ "The CLI then determines the lowest version within the cache-window present in the cache and writes that value to the config api if necessary. "+ - "For guest-firmware objects the object is added to the API directly. "+ - "Please authenticate with AWS through your preferred method (e.g. environment variables, CLI)"+ + "For guest-firmware objects the object is added to the API directly.\n"+ + "Please authenticate with AWS through your preferred method (e.g. environment variables, CLI) "+ "to be able to upload to S3. Set the %s and %s environment variables to authenticate with cosign.", envCosignPrivateKey, envCosignPwd, ), - Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure snp-report /some/path/report.json", + Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure-sev-snp attestation-report /some/path/report.json", - Args: cobra.MatchAll(cobra.ExactArgs(3), isCloudProvider(0), isValidKind(1)), + Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)), PreRunE: envCheck, RunE: runUpload, } @@ -91,42 +92,19 @@ func runUpload(cmd *cobra.Command, args []string) (retErr error) { return fmt.Errorf("creating client: %w", err) } - var attestation variant.Variant - switch uploadCfg.provider { - case cloudprovider.AWS: - attestation = variant.AWSSEVSNP{} - case cloudprovider.Azure: - attestation = variant.AzureSEVSNP{} - case cloudprovider.GCP: - attestation = variant.GCPSEVSNP{} - default: - return fmt.Errorf("unsupported cloud provider: %s", uploadCfg.provider) - } - - return uploadReport(ctx, attestation, client, uploadCfg, file.NewHandler(afero.NewOsFs()), log) + return uploadReport(ctx, client, uploadCfg, file.NewHandler(afero.NewOsFs()), log) } -func uploadReport(ctx context.Context, - attestation variant.Variant, - apiClient *client.Client, - cfg uploadConfig, - fs file.Handler, - log *slog.Logger, +func uploadReport( + ctx context.Context, apiClient *client.Client, + cfg uploadConfig, fs file.Handler, log *slog.Logger, ) error { - if cfg.kind != snpReport { + if cfg.kind != attestationReport { return fmt.Errorf("kind %s not supported", cfg.kind) } - log.Info(fmt.Sprintf("Reading SNP report from file: %s", cfg.path)) - var report verify.Report - if err := fs.ReadJSON(cfg.path, &report); err != nil { - return fmt.Errorf("reading snp report: %w", err) - } - - inputVersion := convertTCBVersionToSNPVersion(report.SNPReport.LaunchTCB) - log.Info(fmt.Sprintf("Input report: %+v", inputVersion)) - - latestAPIVersionAPI, err := attestationconfigapi.NewFetcherWithCustomCDNAndCosignKey(cfg.url, cfg.cosignPublicKey).FetchLatestVersion(ctx, attestation) + apiFetcher := attestationconfigapi.NewFetcherWithCustomCDNAndCosignKey(cfg.url, cfg.cosignPublicKey) + latestVersionInAPI, err := apiFetcher.FetchLatestVersion(ctx, cfg.variant) if err != nil { var notFoundErr *fetcher.NotFoundError if errors.As(err, ¬FoundErr) { @@ -136,12 +114,39 @@ func uploadReport(ctx context.Context, } } - latestAPIVersion := latestAPIVersionAPI.SEVSNPVersion - if err := apiClient.UploadSEVSNPVersionLatest(ctx, attestation, inputVersion, latestAPIVersion, cfg.uploadDate, cfg.force); err != nil { - if errors.Is(err, client.ErrNoNewerVersion) { - log.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v", inputVersion, latestAPIVersion)) - return nil + var newVersion, latestVersion any + switch cfg.variant { + case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.GCPSEVSNP{}: + latestVersion = latestVersionInAPI.SEVSNPVersion + + log.Info(fmt.Sprintf("Reading SNP report from file: %s", cfg.path)) + var report verify.Report + if err := fs.ReadJSON(cfg.path, &report); err != nil { + return fmt.Errorf("reading snp report: %w", err) } + + newVersion = convertTCBVersionToSNPVersion(report.SNPReport.LaunchTCB) + log.Info(fmt.Sprintf("Input SNP report: %+v", newVersion)) + + case variant.AzureTDX{}: + latestVersion = latestVersionInAPI.TDXVersion + + log.Info(fmt.Sprintf("Reading TDX report from file: %s", cfg.path)) + var report *tdx.QuoteV4 + if err := fs.ReadJSON(cfg.path, &report); err != nil { + return fmt.Errorf("reading tdx report: %w", err) + } + + newVersion = convertQuoteToTDXVersion(report) + log.Info(fmt.Sprintf("Input TDX report: %+v", newVersion)) + + default: + return fmt.Errorf("variant %s not supported", cfg.variant) + } + + if err := apiClient.UploadLatestVersion( + ctx, cfg.variant, newVersion, latestVersion, cfg.uploadDate, cfg.force, + ); err != nil && !errors.Is(err, client.ErrNoNewerVersion) { return fmt.Errorf("updating latest version: %w", err) } @@ -157,8 +162,18 @@ func convertTCBVersionToSNPVersion(tcb verify.TCBVersion) attestationconfigapi.S } } +func convertQuoteToTDXVersion(quote *tdx.QuoteV4) attestationconfigapi.TDXVersion { + return attestationconfigapi.TDXVersion{ + QESVN: binary.LittleEndian.Uint16(quote.Header.QeSvn), + PCESVN: binary.LittleEndian.Uint16(quote.Header.PceSvn), + QEVendorID: [16]byte(quote.Header.QeVendorId), + XFAM: [8]byte(quote.TdQuoteBody.Xfam), + TEETCBSVN: [16]byte(quote.TdQuoteBody.TeeTcbSvn), + } +} + type uploadConfig struct { - provider cloudprovider.Provider + variant variant.Variant kind objectKind path string uploadDate time.Time @@ -210,12 +225,16 @@ func newConfig(cmd *cobra.Command, args [3]string) (uploadConfig, error) { return uploadConfig{}, fmt.Errorf("getting cache window size: %w", err) } - provider := cloudprovider.FromString(args[0]) + variant, err := variant.FromString(args[0]) + if err != nil { + return uploadConfig{}, fmt.Errorf("invalid attestation variant: %q: %w", args[0], err) + } + kind := kindFromString(args[1]) path := args[2] return uploadConfig{ - provider: provider, + variant: variant, kind: kind, path: path, uploadDate: uploadDate, diff --git a/internal/api/attestationconfigapi/cli/validargs.go b/internal/api/attestationconfigapi/cli/validargs.go index 22a11ac21..0c77ce051 100644 --- a/internal/api/attestationconfigapi/cli/validargs.go +++ b/internal/api/attestationconfigapi/cli/validargs.go @@ -10,16 +10,22 @@ import ( "fmt" "strings" - "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" + "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/spf13/cobra" ) -func isCloudProvider(arg int) cobra.PositionalArgs { +func isAttestationVariant(arg int) cobra.PositionalArgs { return func(_ *cobra.Command, args []string) error { - if provider := cloudprovider.FromString(args[arg]); provider == cloudprovider.Unknown { - return fmt.Errorf("argument %s isn't a valid cloud provider", args[arg]) + attestationVariant, err := variant.FromString(args[arg]) + if err != nil { + return fmt.Errorf("argument %s isn't a valid attestation variant", args[arg]) + } + switch attestationVariant { + case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.AzureTDX{}, variant.GCPSEVSNP{}: + return nil + default: + return fmt.Errorf("argument %s isn't a supported attestation variant", args[arg]) } - return nil } } @@ -37,16 +43,15 @@ type objectKind string const ( // unknown is the default objectKind and does nothing. - unknown objectKind = "unknown-kind" - snpReport objectKind = "snp-report" - tdxReport objectKind = "tdx-report" - guestFirmware objectKind = "guest-firmware" + unknown objectKind = "unknown-kind" + attestationReport objectKind = "attestation-report" + guestFirmware objectKind = "guest-firmware" ) func kindFromString(s string) objectKind { lower := strings.ToLower(s) switch objectKind(lower) { - case snpReport, guestFirmware, tdxReport: + case attestationReport, guestFirmware: return objectKind(lower) default: return unknown diff --git a/internal/api/attestationconfigapi/fetcher.go b/internal/api/attestationconfigapi/fetcher.go index 6990426bf..ca30bb554 100644 --- a/internal/api/attestationconfigapi/fetcher.go +++ b/internal/api/attestationconfigapi/fetcher.go @@ -20,7 +20,7 @@ const cosignPublicKey = constants.CosignPublicKeyReleases // Fetcher fetches config API resources without authentication. type Fetcher interface { - FetchLatestVersion(ctx context.Context, attestation variant.Variant) (VersionAPIEntry, error) + FetchLatestVersion(ctx context.Context, attestation variant.Variant) (Entry, error) } // fetcher fetches AttestationCfg API resources without authentication. @@ -60,10 +60,10 @@ func newFetcherWithClientAndVerifier(client apifetcher.HTTPClient, cosignVerifie } // FetchLatestVersion returns the latest versions of the given type. -func (f *fetcher) FetchLatestVersion(ctx context.Context, variant variant.Variant) (VersionAPIEntry, error) { +func (f *fetcher) FetchLatestVersion(ctx context.Context, variant variant.Variant) (Entry, error) { list, err := f.fetchVersionList(ctx, variant) if err != nil { - return VersionAPIEntry{}, err + return Entry{}, err } // latest version is first in list @@ -71,11 +71,10 @@ func (f *fetcher) FetchLatestVersion(ctx context.Context, variant variant.Varian } // fetchVersionList fetches the version list information from the config API. -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}) +func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant) (List, error) { + fetchedList, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, List{Variant: variant}, f.verifier) if err != nil { - return VersionList{}, fmt.Errorf("fetching version list: %w", err) + return List{}, fmt.Errorf("fetching version list: %w", err) } // Set the attestation variant of the list as it is not part of the marshalled JSON retrieved by Fetch @@ -85,14 +84,14 @@ func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant) } // fetchVersion fetches the version information from the config API. -func (f *fetcher) fetchVersion(ctx context.Context, version string, variant variant.Variant) (VersionAPIEntry, error) { - obj := VersionAPIEntry{ +func (f *fetcher) fetchVersion(ctx context.Context, version string, variant variant.Variant) (Entry, error) { + obj := Entry{ Version: version, Variant: variant, } fetchedVersion, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, obj, f.verifier) if err != nil { - return VersionAPIEntry{}, fmt.Errorf("fetching version %q: %w", version, err) + return Entry{}, fmt.Errorf("fetching version %q: %w", version, err) } // Set the attestation variant of the list as it is not part of the marshalled JSON retrieved by FetchAndVerify diff --git a/internal/api/attestationconfigapi/fetcher_test.go b/internal/api/attestationconfigapi/fetcher_test.go index 93c013247..b3d737f54 100644 --- a/internal/api/attestationconfigapi/fetcher_test.go +++ b/internal/api/attestationconfigapi/fetcher_test.go @@ -22,7 +22,7 @@ import ( ) func TestFetchLatestSEVSNPVersion(t *testing.T) { - latestVersionSNP := VersionAPIEntry{ + latestVersionSNP := Entry{ SEVSNPVersion: SEVSNPVersion{ Microcode: 93, TEE: 0, @@ -30,7 +30,7 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) { Bootloader: 2, }, } - olderVersionSNP := VersionAPIEntry{ + olderVersionSNP := Entry{ SEVSNPVersion: SEVSNPVersion{ Microcode: 1, TEE: 0, @@ -38,7 +38,7 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) { Bootloader: 1, }, } - latestVersionTDX := VersionAPIEntry{ + latestVersionTDX := Entry{ TDXVersion: TDXVersion{ QESVN: 2, PCESVN: 3, @@ -47,7 +47,7 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) { XFAM: [8]byte{6}, }, } - olderVersionTDX := VersionAPIEntry{ + olderVersionTDX := Entry{ TDXVersion: TDXVersion{ QESVN: 1, PCESVN: 2, @@ -64,30 +64,30 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) { timeAtTest time.Time wantErr bool attestation variant.Variant - expectedVersion VersionAPIEntry - olderVersion VersionAPIEntry - latestVersion VersionAPIEntry + expectedVersion Entry + olderVersion Entry + latestVersion Entry }{ "get latest version azure-sev-snp": { fetcherVersions: []string{latestStr, olderStr}, attestation: variant.AzureSEVSNP{}, - 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 }(), + expectedVersion: func() Entry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(), + olderVersion: func() Entry { tmp := olderVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(), + latestVersion: func() Entry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(), }, "get latest version aws-sev-snp": { fetcherVersions: []string{latestStr, olderStr}, attestation: variant.AWSSEVSNP{}, - 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 }(), + expectedVersion: func() Entry { tmp := latestVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(), + olderVersion: func() Entry { tmp := olderVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(), + latestVersion: func() Entry { 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 }(), + expectedVersion: func() Entry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(), + olderVersion: func() Entry { tmp := olderVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(), + latestVersion: func() Entry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(), }, } for name, tc := range testCases { @@ -119,14 +119,15 @@ type fakeConfigAPIHandler struct { attestation variant.Variant versions []string latestDate string - latestVersion VersionAPIEntry + latestVersion Entry olderDate string - olderVersion VersionAPIEntry + olderVersion Entry } // RoundTrip resolves the request and returns a dummy response. func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, error) { - if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/%s/list", f.attestation.String()) { + switch req.URL.Path { + case fmt.Sprintf("/constellation/v1/attestation/%s/list", f.attestation.String()): res := &http.Response{} bt, err := json.Marshal(f.versions) if err != nil { @@ -137,7 +138,14 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err res.Header.Set("Content-Type", "application/json") res.StatusCode = http.StatusOK return res, nil - } else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/%s/%s", f.attestation.String(), f.latestDate) { + + case fmt.Sprintf("/constellation/v1/attestation/%s/list.sig", f.attestation.String()): + res := &http.Response{} + res.Body = io.NopCloser(bytes.NewReader([]byte("null"))) + res.StatusCode = http.StatusOK + return res, nil + + case fmt.Sprintf("/constellation/v1/attestation/%s/%s", f.attestation.String(), f.latestDate): res := &http.Response{} bt, err := json.Marshal(f.latestVersion) if err != nil { @@ -147,7 +155,7 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err res.StatusCode = http.StatusOK return res, nil - } else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/%s/%s", f.attestation.String(), f.olderDate) { + case fmt.Sprintf("/constellation/v1/attestation/%s/%s", f.attestation.String(), f.olderDate): res := &http.Response{} bt, err := json.Marshal(f.olderVersion) if err != nil { @@ -156,13 +164,14 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err res.Body = io.NopCloser(bytes.NewReader(bt)) res.StatusCode = http.StatusOK return res, nil - } else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/%s/%s.sig", f.attestation.String(), f.latestDate) { + + case fmt.Sprintf("/constellation/v1/attestation/%s/%s.sig", f.attestation.String(), f.latestDate): res := &http.Response{} res.Body = io.NopCloser(bytes.NewReader([]byte("null"))) res.StatusCode = http.StatusOK return res, nil - } else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/%s/%s.sig", f.attestation.String(), f.olderDate) { + case fmt.Sprintf("/constellation/v1/attestation/%s/%s.sig", f.attestation.String(), f.olderDate): res := &http.Response{} res.Body = io.NopCloser(bytes.NewReader([]byte("null"))) res.StatusCode = http.StatusOK diff --git a/internal/api/attestationconfigapi/version.go b/internal/api/attestationconfigapi/version.go index 669ee6d4c..13dd17254 100644 --- a/internal/api/attestationconfigapi/version.go +++ b/internal/api/attestationconfigapi/version.go @@ -45,12 +45,12 @@ type TDXVersion struct { XFAM [8]byte `json:"xfam"` } -// VersionAPIEntry is the request to get the version information of the specific version in the config api. +// Entry 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 { +type Entry struct { Version string `json:"-"` Variant variant.Variant `json:"-"` SEVSNPVersion @@ -58,12 +58,12 @@ type VersionAPIEntry struct { } // JSONPath returns the path to the JSON file for the request to the config api. -func (i VersionAPIEntry) JSONPath() string { +func (i Entry) JSONPath() string { return path.Join(AttestationURLPath, i.Variant.String(), i.Version) } // ValidateRequest validates the request. -func (i VersionAPIEntry) ValidateRequest() error { +func (i Entry) ValidateRequest() error { if !strings.HasSuffix(i.Version, ".json") { return fmt.Errorf("version has no .json suffix") } @@ -71,47 +71,47 @@ func (i VersionAPIEntry) ValidateRequest() error { } // Validate is a No-Op at the moment. -func (i VersionAPIEntry) Validate() error { +func (i Entry) Validate() error { return nil } -// VersionList is the request to retrieve of all versions in the API for one attestation variant. +// List 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 { +type List struct { Variant variant.Variant List []string } // MarshalJSON marshals the i's list property to JSON. -func (i VersionList) MarshalJSON() ([]byte, error) { +func (i List) 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 { +func (i *List) 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 { +func (i List) JSONPath() string { return path.Join(AttestationURLPath, i.Variant.String(), "list") } // ValidateRequest is a NoOp as there is no input. -func (i VersionList) ValidateRequest() error { +func (i List) ValidateRequest() error { return nil } // SortReverse sorts the list of versions in reverse order. -func (i *VersionList) SortReverse() { +func (i *List) 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) { +func (i *List) AddVersion(new string) { i.List = append(i.List, new) i.List = variant.RemoveDuplicate(i.List) @@ -119,7 +119,7 @@ func (i *VersionList) AddVersion(new string) { } // Validate validates the response. -func (i VersionList) Validate() error { +func (i List) Validate() error { if len(i.List) < 1 { return fmt.Errorf("no versions found in /list") } diff --git a/internal/api/attestationconfigapi/version_test.go b/internal/api/attestationconfigapi/version_test.go index ee87bfc37..1e5c51441 100644 --- a/internal/api/attestationconfigapi/version_test.go +++ b/internal/api/attestationconfigapi/version_test.go @@ -16,21 +16,21 @@ import ( func TestVersionListMarshalUnmarshalJSON(t *testing.T) { tests := map[string]struct { - input VersionList - output VersionList + input List + output List wantDiff bool }{ "success": { - input: VersionList{List: []string{"v1", "v2"}}, - output: VersionList{List: []string{"v1", "v2"}}, + input: List{List: []string{"v1", "v2"}}, + output: List{List: []string{"v1", "v2"}}, }, "variant is lost": { - input: VersionList{List: []string{"v1", "v2"}, Variant: variant.AzureSEVSNP{}}, - output: VersionList{List: []string{"v1", "v2"}}, + input: List{List: []string{"v1", "v2"}, Variant: variant.AzureSEVSNP{}}, + output: List{List: []string{"v1", "v2"}}, }, "wrong order": { - input: VersionList{List: []string{"v1", "v2"}}, - output: VersionList{List: []string{"v2", "v1"}}, + input: List{List: []string{"v1", "v2"}}, + output: List{List: []string{"v2", "v1"}}, wantDiff: true, }, } @@ -40,7 +40,7 @@ func TestVersionListMarshalUnmarshalJSON(t *testing.T) { inputRaw, err := tc.input.MarshalJSON() require.NoError(t, err) - var actual VersionList + var actual List err = actual.UnmarshalJSON(inputRaw) require.NoError(t, err) @@ -68,7 +68,7 @@ func TestVersionListAddVersion(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { - v := VersionList{List: tc.versions} + v := List{List: tc.versions} v.AddVersion(tc.new) assert.Equal(t, tc.expected, v.List) diff --git a/internal/api/client/client.go b/internal/api/client/client.go index 4929872ed..d3c0a6b75 100644 --- a/internal/api/client/client.go +++ b/internal/api/client/client.go @@ -53,7 +53,7 @@ type Client struct { dirtyPaths []string // written paths to be invalidated DryRun bool // no write operations are performed - Logger *slog.Logger + log *slog.Logger } // NewReadOnlyClient creates a new read-only client. @@ -77,7 +77,7 @@ func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID strin s3ClientClose: staticUploadClientClose, bucket: bucket, DryRun: true, - Logger: log, + log: log, } clientClose := func(ctx context.Context) error { return client.Close(ctx) @@ -106,7 +106,7 @@ func NewClient(ctx context.Context, region, bucket, distributionID string, dryRu s3ClientClose: staticUploadClientClose, bucket: bucket, DryRun: dryRun, - Logger: log, + log: log, } clientClose := func(ctx context.Context) error { return client.Close(ctx) @@ -119,7 +119,7 @@ func NewClient(ctx context.Context, region, bucket, distributionID string, dryRu // It invalidates the CDN cache for all uploaded files. func (c *Client) Close(ctx context.Context) error { if c.s3ClientClose == nil { - c.Logger.Debug("Client has no s3ClientClose") + c.log.Debug("Client has no s3ClientClose") return nil } return c.s3ClientClose(ctx) @@ -131,7 +131,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error { Bucket: &c.bucket, Prefix: &path, } - c.Logger.Debug(fmt.Sprintf("Listing objects in %q", path)) + c.log.Debug(fmt.Sprintf("Listing objects in %q", path)) objs := []s3types.Object{} out := &s3.ListObjectsV2Output{IsTruncated: ptr(true)} for out.IsTruncated != nil && *out.IsTruncated { @@ -142,10 +142,10 @@ func (c *Client) DeletePath(ctx context.Context, path string) error { } objs = append(objs, out.Contents...) } - c.Logger.Debug(fmt.Sprintf("Found %d objects in %q", len(objs), path)) + c.log.Debug(fmt.Sprintf("Found %d objects in %q", len(objs), path)) if len(objs) == 0 { - c.Logger.Warn(fmt.Sprintf("Path %s is already empty", path)) + c.log.Warn(fmt.Sprintf("Path %s is already empty", path)) return nil } @@ -155,7 +155,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error { } if c.DryRun { - c.Logger.Debug(fmt.Sprintf("DryRun: Deleting %d objects with IDs %v", len(objs), objIDs)) + c.log.Debug(fmt.Sprintf("DryRun: Deleting %d objects with IDs %v", len(objs), objIDs)) return nil } @@ -167,7 +167,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error { Objects: objIDs, }, } - c.Logger.Debug(fmt.Sprintf("Deleting %d objects in %q", len(objs), path)) + c.log.Debug(fmt.Sprintf("Deleting %d objects in %q", len(objs), path)) if _, err := c.s3Client.DeleteObjects(ctx, deleteIn); err != nil { return fmt.Errorf("deleting objects in %s: %w", path, err) } @@ -197,7 +197,7 @@ func Fetch[T APIObject](ctx context.Context, c *Client, obj T) (T, error) { Key: ptr(obj.JSONPath()), } - c.Logger.Debug(fmt.Sprintf("Fetching %T from s3: %q", obj, obj.JSONPath())) + c.log.Debug(fmt.Sprintf("Fetching %T from s3: %q", obj, obj.JSONPath())) out, err := c.s3Client.GetObject(ctx, in) var noSuchkey *s3types.NoSuchKey if errors.As(err, &noSuchkey) { @@ -231,7 +231,7 @@ func Update(ctx context.Context, c *Client, obj APIObject) error { } if c.DryRun { - c.Logger.With(slog.String("bucket", c.bucket), slog.String("key", obj.JSONPath()), slog.String("body", string(rawJSON))).Debug("DryRun: s3 put object") + c.log.With(slog.String("bucket", c.bucket), slog.String("key", obj.JSONPath()), slog.String("body", string(rawJSON))).Debug("DryRun: s3 put object") return nil } @@ -243,7 +243,7 @@ func Update(ctx context.Context, c *Client, obj APIObject) error { c.dirtyPaths = append(c.dirtyPaths, "/"+obj.JSONPath()) - c.Logger.Debug(fmt.Sprintf("Uploading %T to s3: %q", obj, obj.JSONPath())) + c.log.Debug(fmt.Sprintf("Uploading %T to s3: %q", obj, obj.JSONPath())) if _, err := c.Upload(ctx, in); err != nil { return fmt.Errorf("uploading %T: %w", obj, err) } @@ -306,7 +306,7 @@ func Delete(ctx context.Context, c *Client, obj APIObject) error { Key: ptr(obj.JSONPath()), } - c.Logger.Debug(fmt.Sprintf("Deleting %T from s3: %q", obj, obj.JSONPath())) + c.log.Debug(fmt.Sprintf("Deleting %T from s3: %q", obj, obj.JSONPath())) if _, err := c.DeleteObject(ctx, in); err != nil { return fmt.Errorf("deleting s3 object at %s: %w", obj.JSONPath(), err) } diff --git a/internal/api/fetcher/fetcher.go b/internal/api/fetcher/fetcher.go index ec3118c9c..52135aa1f 100644 --- a/internal/api/fetcher/fetcher.go +++ b/internal/api/fetcher/fetcher.go @@ -20,12 +20,10 @@ package fetcher import ( "context" "encoding/json" - "errors" "fmt" "io" "net/http" "net/url" - "strings" "github.com/edgelesssys/constellation/v2/internal/sigstore" ) @@ -145,7 +143,7 @@ type apiObject interface { // signature manages the signature of a object saved at location 'Signed'. type signature struct { // Signed is the object that is signed. - Signed string `json:"-"` + Signed string `json:"signed"` // Signature is the signature of `Signed`. Signature []byte `json:"signature"` } @@ -155,12 +153,8 @@ func (s signature) JSONPath() string { return s.Signed + ".sig" } -// ValidateRequest validates the request. +// ValidateRequest is a no-op. func (s signature) ValidateRequest() error { - if !strings.HasSuffix(s.Signed, ".json") { - return errors.New("signed object missing .json suffix") - } - return nil } diff --git a/internal/api/versionsapi/client.go b/internal/api/versionsapi/client.go index 5d14fdacd..496557102 100644 --- a/internal/api/versionsapi/client.go +++ b/internal/api/versionsapi/client.go @@ -23,6 +23,8 @@ import ( type Client struct { *apiclient.Client clientClose func(ctx context.Context) error + + log *slog.Logger } // NewClient creates a new client for the versions API. @@ -31,8 +33,9 @@ func NewClient(ctx context.Context, region, bucket, distributionID string, dryRu ) (*Client, CloseFunc, error) { genericClient, genericClientClose, err := apiclient.NewClient(ctx, region, bucket, distributionID, dryRun, log) versionsClient := &Client{ - genericClient, - genericClientClose, + Client: genericClient, + clientClose: genericClientClose, + log: log, } versionsClientClose := func(ctx context.Context) error { return versionsClient.Close(ctx) @@ -50,8 +53,9 @@ func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID strin return nil, nil, err } versionsClient := &Client{ - genericClient, - genericClientClose, + Client: genericClient, + clientClose: genericClientClose, + log: log, } versionsClientClose := func(ctx context.Context) error { return versionsClient.Close(ctx) @@ -131,18 +135,18 @@ func (c *Client) DeleteRef(ctx context.Context, ref string) error { func (c *Client) DeleteVersion(ctx context.Context, ver Version) error { var retErr error - c.Client.Logger.Debug(fmt.Sprintf("Deleting version %q from minor version list", ver.version)) + c.log.Debug(fmt.Sprintf("Deleting version %q from minor version list", ver.version)) possibleNewLatest, err := c.deleteVersionFromMinorVersionList(ctx, ver) if err != nil { retErr = errors.Join(retErr, fmt.Errorf("removing from minor version list: %w", err)) } - c.Client.Logger.Debug(fmt.Sprintf("Checking latest version for %q", ver.version)) + c.log.Debug(fmt.Sprintf("Checking latest version for %q", ver.version)) if err := c.deleteVersionFromLatest(ctx, ver, possibleNewLatest); err != nil { retErr = errors.Join(retErr, fmt.Errorf("updating latest version: %w", err)) } - c.Client.Logger.Debug(fmt.Sprintf("Deleting artifact path %q for %q", ver.ArtifactPath(APIV1), ver.version)) + c.log.Debug(fmt.Sprintf("Deleting artifact path %q for %q", ver.ArtifactPath(APIV1), ver.version)) if err := c.Client.DeletePath(ctx, ver.ArtifactPath(APIV1)); err != nil { retErr = errors.Join(retErr, fmt.Errorf("deleting artifact path: %w", err)) } @@ -159,20 +163,20 @@ func (c *Client) deleteVersionFromMinorVersionList(ctx context.Context, ver Vers Base: ver.WithGranularity(GranularityMinor), Kind: VersionKindImage, } - c.Client.Logger.Debug(fmt.Sprintf("Fetching minor version list for version %q", ver.version)) + c.log.Debug(fmt.Sprintf("Fetching minor version list for version %q", ver.version)) minorList, err := c.FetchVersionList(ctx, minorList) var notFoundErr *apiclient.NotFoundError if errors.As(err, ¬FoundErr) { - c.Client.Logger.Warn(fmt.Sprintf("Minor version list for version %s not found", ver.version)) - c.Client.Logger.Warn("Skipping update of minor version list") + c.log.Warn(fmt.Sprintf("Minor version list for version %s not found", ver.version)) + c.log.Warn("Skipping update of minor version list") return nil, nil } else if err != nil { return nil, fmt.Errorf("fetching minor version list for version %s: %w", ver.version, err) } if !minorList.Contains(ver.version) { - c.Client.Logger.Warn(fmt.Sprintf("Version %s is not in minor version list %s", ver.version, minorList.JSONPath())) - c.Client.Logger.Warn("Skipping update of minor version list") + c.log.Warn(fmt.Sprintf("Version %s is not in minor version list %s", ver.version, minorList.JSONPath())) + c.log.Warn("Skipping update of minor version list") return nil, nil } @@ -192,20 +196,20 @@ func (c *Client) deleteVersionFromMinorVersionList(ctx context.Context, ver Vers Kind: VersionKindImage, Version: minorList.Versions[len(minorList.Versions)-1], } - c.Client.Logger.Debug(fmt.Sprintf("Possible latest version replacement %q", latest.Version)) + c.log.Debug(fmt.Sprintf("Possible latest version replacement %q", latest.Version)) } if c.Client.DryRun { - c.Client.Logger.Debug(fmt.Sprintf("DryRun: Updating minor version list %q to %v", minorList.JSONPath(), minorList)) + c.log.Debug(fmt.Sprintf("DryRun: Updating minor version list %q to %v", minorList.JSONPath(), minorList)) return latest, nil } - c.Client.Logger.Debug(fmt.Sprintf("Updating minor version list %q", minorList.JSONPath())) + c.log.Debug(fmt.Sprintf("Updating minor version list %q", minorList.JSONPath())) if err := c.UpdateVersionList(ctx, minorList); err != nil { return latest, fmt.Errorf("updating minor version list %s: %w", minorList.JSONPath(), err) } - c.Client.Logger.Debug(fmt.Sprintf("Removed version %q from minor version list %q", ver.version, minorList.JSONPath())) + c.log.Debug(fmt.Sprintf("Removed version %q from minor version list %q", ver.version, minorList.JSONPath())) return latest, nil } @@ -216,33 +220,33 @@ func (c *Client) deleteVersionFromLatest(ctx context.Context, ver Version, possi Stream: ver.stream, Kind: VersionKindImage, } - c.Client.Logger.Debug(fmt.Sprintf("Fetching latest version from %q", latest.JSONPath())) + c.log.Debug(fmt.Sprintf("Fetching latest version from %q", latest.JSONPath())) latest, err := c.FetchVersionLatest(ctx, latest) var notFoundErr *apiclient.NotFoundError if errors.As(err, ¬FoundErr) { - c.Client.Logger.Warn(fmt.Sprintf("Latest version for %s not found", latest.JSONPath())) + c.log.Warn(fmt.Sprintf("Latest version for %s not found", latest.JSONPath())) return nil } else if err != nil { return fmt.Errorf("fetching latest version: %w", err) } if latest.Version != ver.version { - c.Client.Logger.Debug(fmt.Sprintf("Latest version is %q, not the deleted version %q", latest.Version, ver.version)) + c.log.Debug(fmt.Sprintf("Latest version is %q, not the deleted version %q", latest.Version, ver.version)) return nil } if possibleNewLatest == nil { - c.Client.Logger.Error(fmt.Sprintf("Latest version is %s, but no new latest version was found", latest.Version)) - c.Client.Logger.Error(fmt.Sprintf("A manual update of latest at %s might be needed", latest.JSONPath())) + c.log.Error(fmt.Sprintf("Latest version is %s, but no new latest version was found", latest.Version)) + c.log.Error(fmt.Sprintf("A manual update of latest at %s might be needed", latest.JSONPath())) return fmt.Errorf("latest version is %s, but no new latest version was found", latest.Version) } if c.Client.DryRun { - c.Client.Logger.Debug(fmt.Sprintf("Would update latest version from %q to %q", latest.Version, possibleNewLatest.Version)) + c.log.Debug(fmt.Sprintf("Would update latest version from %q to %q", latest.Version, possibleNewLatest.Version)) return nil } - c.Client.Logger.Info(fmt.Sprintf("Updating latest version from %s to %s", latest.Version, possibleNewLatest.Version)) + c.log.Info(fmt.Sprintf("Updating latest version from %s to %s", latest.Version, possibleNewLatest.Version)) if err := c.UpdateVersionLatest(ctx, *possibleNewLatest); err != nil { return fmt.Errorf("updating latest version: %w", err) } diff --git a/internal/config/config.go b/internal/config/config.go index 24849b021..b6533022b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1165,7 +1165,7 @@ type AzureTDX struct { // 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. + // Expected 8 byte hex-encoded eXtended Features Available Mask (XFAM) field. Defaults to the latest available XFAM on Azure VMs. Unset to disable validation. XFAM AttestationVersion[encoding.HexBytes] `json:"xfam" yaml:"xfam"` // description: | // Intel Root Key certificate used to verify the TDX certificate chain. diff --git a/internal/config/config_doc.go b/internal/config/config_doc.go index f155db5c2..cdca2733f 100644 --- a/internal/config/config_doc.go +++ b/internal/config/config_doc.go @@ -545,22 +545,22 @@ func init() { GCPSEVSNPDoc.Fields[0].Description = "Expected TPM measurements." GCPSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." GCPSEVSNPDoc.Fields[1].Name = "bootloaderVersion" - GCPSEVSNPDoc.Fields[1].Type = "AttestationVersion" + GCPSEVSNPDoc.Fields[1].Type = "" GCPSEVSNPDoc.Fields[1].Note = "" GCPSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version." GCPSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version." GCPSEVSNPDoc.Fields[2].Name = "teeVersion" - GCPSEVSNPDoc.Fields[2].Type = "AttestationVersion" + GCPSEVSNPDoc.Fields[2].Type = "" GCPSEVSNPDoc.Fields[2].Note = "" GCPSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version." GCPSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version." GCPSEVSNPDoc.Fields[3].Name = "snpVersion" - GCPSEVSNPDoc.Fields[3].Type = "AttestationVersion" + GCPSEVSNPDoc.Fields[3].Type = "" GCPSEVSNPDoc.Fields[3].Note = "" GCPSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version." GCPSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version." GCPSEVSNPDoc.Fields[4].Name = "microcodeVersion" - GCPSEVSNPDoc.Fields[4].Type = "AttestationVersion" + GCPSEVSNPDoc.Fields[4].Type = "" GCPSEVSNPDoc.Fields[4].Note = "" GCPSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version." GCPSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version." @@ -623,22 +623,22 @@ func init() { AWSSEVSNPDoc.Fields[0].Description = "Expected TPM measurements." AWSSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." AWSSEVSNPDoc.Fields[1].Name = "bootloaderVersion" - AWSSEVSNPDoc.Fields[1].Type = "AttestationVersion" + AWSSEVSNPDoc.Fields[1].Type = "" AWSSEVSNPDoc.Fields[1].Note = "" AWSSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version." AWSSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version." AWSSEVSNPDoc.Fields[2].Name = "teeVersion" - AWSSEVSNPDoc.Fields[2].Type = "AttestationVersion" + AWSSEVSNPDoc.Fields[2].Type = "" AWSSEVSNPDoc.Fields[2].Note = "" AWSSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version." AWSSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version." AWSSEVSNPDoc.Fields[3].Name = "snpVersion" - AWSSEVSNPDoc.Fields[3].Type = "AttestationVersion" + AWSSEVSNPDoc.Fields[3].Type = "" AWSSEVSNPDoc.Fields[3].Note = "" AWSSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version." AWSSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version." AWSSEVSNPDoc.Fields[4].Name = "microcodeVersion" - AWSSEVSNPDoc.Fields[4].Type = "AttestationVersion" + AWSSEVSNPDoc.Fields[4].Type = "" AWSSEVSNPDoc.Fields[4].Note = "" AWSSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version." AWSSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version." @@ -685,22 +685,22 @@ func init() { AzureSEVSNPDoc.Fields[0].Description = "Expected TPM measurements." AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion" - AzureSEVSNPDoc.Fields[1].Type = "AttestationVersion" + AzureSEVSNPDoc.Fields[1].Type = "" AzureSEVSNPDoc.Fields[1].Note = "" AzureSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version." AzureSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version." AzureSEVSNPDoc.Fields[2].Name = "teeVersion" - AzureSEVSNPDoc.Fields[2].Type = "AttestationVersion" + AzureSEVSNPDoc.Fields[2].Type = "" AzureSEVSNPDoc.Fields[2].Note = "" AzureSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version." AzureSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version." AzureSEVSNPDoc.Fields[3].Name = "snpVersion" - AzureSEVSNPDoc.Fields[3].Type = "AttestationVersion" + AzureSEVSNPDoc.Fields[3].Type = "" AzureSEVSNPDoc.Fields[3].Note = "" AzureSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version." AzureSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version." AzureSEVSNPDoc.Fields[4].Name = "microcodeVersion" - AzureSEVSNPDoc.Fields[4].Type = "AttestationVersion" + AzureSEVSNPDoc.Fields[4].Type = "" AzureSEVSNPDoc.Fields[4].Note = "" AzureSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version." AzureSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version." @@ -752,22 +752,22 @@ func init() { AzureTDXDoc.Fields[0].Description = "Expected TPM measurements." AzureTDXDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." AzureTDXDoc.Fields[1].Name = "qeSVN" - AzureTDXDoc.Fields[1].Type = "uint16" + AzureTDXDoc.Fields[1].Type = "" AzureTDXDoc.Fields[1].Note = "" AzureTDXDoc.Fields[1].Description = "Minimum required QE security version number (SVN)." AzureTDXDoc.Fields[1].Comments[encoder.LineComment] = "Minimum required QE security version number (SVN)." AzureTDXDoc.Fields[2].Name = "pceSVN" - AzureTDXDoc.Fields[2].Type = "uint16" + AzureTDXDoc.Fields[2].Type = "" AzureTDXDoc.Fields[2].Note = "" AzureTDXDoc.Fields[2].Description = "Minimum required PCE security version number (SVN)." AzureTDXDoc.Fields[2].Comments[encoder.LineComment] = "Minimum required PCE security version number (SVN)." AzureTDXDoc.Fields[3].Name = "teeTCBSVN" - AzureTDXDoc.Fields[3].Type = "HexBytes" + AzureTDXDoc.Fields[3].Type = "" AzureTDXDoc.Fields[3].Note = "" AzureTDXDoc.Fields[3].Description = "Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN)." AzureTDXDoc.Fields[3].Comments[encoder.LineComment] = "Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN)." AzureTDXDoc.Fields[4].Name = "qeVendorID" - AzureTDXDoc.Fields[4].Type = "HexBytes" + AzureTDXDoc.Fields[4].Type = "" AzureTDXDoc.Fields[4].Note = "" AzureTDXDoc.Fields[4].Description = "Expected 16 byte hex-encoded QE_VENDOR_ID field." AzureTDXDoc.Fields[4].Comments[encoder.LineComment] = "Expected 16 byte hex-encoded QE_VENDOR_ID field." @@ -777,10 +777,10 @@ func init() { AzureTDXDoc.Fields[5].Description = "Expected 48 byte hex-encoded MR_SEAM value." AzureTDXDoc.Fields[5].Comments[encoder.LineComment] = "Expected 48 byte hex-encoded MR_SEAM value." AzureTDXDoc.Fields[6].Name = "xfam" - AzureTDXDoc.Fields[6].Type = "HexBytes" + AzureTDXDoc.Fields[6].Type = "" AzureTDXDoc.Fields[6].Note = "" - AzureTDXDoc.Fields[6].Description = "Expected 8 byte hex-encoded XFAM field." - AzureTDXDoc.Fields[6].Comments[encoder.LineComment] = "Expected 8 byte hex-encoded XFAM field." + AzureTDXDoc.Fields[6].Description = "Expected 8 byte hex-encoded eXtended Features Available Mask (XFAM) field. Defaults to the latest available XFAM on Azure VMs. Unset to disable validation." + AzureTDXDoc.Fields[6].Comments[encoder.LineComment] = "Expected 8 byte hex-encoded eXtended Features Available Mask (XFAM) field. Defaults to the latest available XFAM on Azure VMs. Unset to disable validation." AzureTDXDoc.Fields[7].Name = "intelRootKey" AzureTDXDoc.Fields[7].Type = "Certificate" AzureTDXDoc.Fields[7].Note = "" diff --git a/internal/config/config_test.go b/internal/config/config_test.go index d09ec46c5..5278136ac 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1051,8 +1051,8 @@ func getConfigAsMap(conf *Config, t *testing.T) (res configMap) { type stubAttestationFetcher struct{} -func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.VersionAPIEntry, error) { - return attestationconfigapi.VersionAPIEntry{ +func (f stubAttestationFetcher) FetchLatestVersion(_ context.Context, _ variant.Variant) (attestationconfigapi.Entry, error) { + return attestationconfigapi.Entry{ 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 cc6e9d5f5..a15ace4a8 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() - latestVersions := attestationconfigapi.VersionAPIEntry{} + latestVersions := attestationconfigapi.Entry{} if attestationVariant.Equal(variant.AWSSEVSNP{}) || attestationVariant.Equal(variant.AzureSEVSNP{}) || attestationVariant.Equal(variant.AzureTDX{}) || diff --git a/terraform-provider-constellation/internal/provider/convert.go b/terraform-provider-constellation/internal/provider/convert.go index 57398fb50..84e4c8832 100644 --- a/terraform-provider-constellation/internal/provider/convert.go +++ b/terraform-provider-constellation/internal/provider/convert.go @@ -138,7 +138,7 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation } // convertToTfAttestationCfg converts the constellation attestation config to the related terraform structs. -func convertToTfAttestation(attVar variant.Variant, latestVersions attestationconfigapi.VersionAPIEntry) (tfAttestation attestationAttribute, err error) { +func convertToTfAttestation(attVar variant.Variant, latestVersions attestationconfigapi.Entry) (tfAttestation attestationAttribute, err error) { tfAttestation = attestationAttribute{ Variant: attVar.String(), }