Enable upload of TDX reports to Constellation CDN

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2024-06-12 16:30:03 +02:00 committed by Daniel Weiße
parent 9159b60331
commit d67d0ac9df
27 changed files with 782 additions and 531 deletions

View File

@ -2,9 +2,9 @@ name: E2E Attestationconfig API Test
description: "Test the attestationconfig CLI is functional." description: "Test the attestationconfig CLI is functional."
inputs: inputs:
csp: attestationVariant:
description: "Cloud provider to run tests against" description: "attestation variant to run tests against"
default: "azure" default: "azure-sev-snp"
cosignPrivateKey: cosignPrivateKey:
description: "Cosign private key" description: "Cosign private key"
required: true required: true
@ -30,4 +30,4 @@ runs:
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }} COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
COSIGN_PASSWORD: ${{ inputs.cosignPassword }} COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
run: | run: |
bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.csp }} bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.attestationVariant }}

View File

@ -68,9 +68,9 @@ runs:
case "${{ inputs.attestationVariant }}" case "${{ inputs.attestationVariant }}"
in 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" 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 constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090
@ -88,22 +88,19 @@ runs:
aws-region: eu-central-1 aws-region: eu-central-1
- name: Upload extracted TCBs - 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 shell: bash
env: env:
COSIGN_PASSWORD: ${{ inputs.cosignPassword }} COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }} COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
run: | run: |
reports=(snp-report-*.json) reports=(attestation-report-*.json)
if [ -z ${#reports[@]} ]; then if [ -z ${#reports[@]} ]; then
exit 1 exit 1
fi fi
attestationVariant=${{ inputs.attestationVariant }}
cloudProvider=${attestationVariant%%-*}
for file in "${reports[@]}"; do for file in "${reports[@]}"; do
path=$(realpath "${file}") path=$(realpath "${file}")
cat "${path}" 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 done

View File

@ -15,9 +15,9 @@ jobs:
e2e-api: e2e-api:
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 1 max-parallel: 2
matrix: matrix:
csp: ["azure", "aws", "gcp"] attestationVariant: ["azure-sev-snp", "azure-tdx", "aws-sev-snp", "gcp-sev-snp"]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
permissions: permissions:
id-token: write id-token: write
@ -36,4 +36,4 @@ jobs:
with: with:
cosignPrivateKey: ${{ secrets.COSIGN_DEV_PRIVATE_KEY }} cosignPrivateKey: ${{ secrets.COSIGN_DEV_PRIVATE_KEY }}
cosignPassword: ${{ secrets.COSIGN_DEV_PASSWORD }} cosignPassword: ${{ secrets.COSIGN_DEV_PASSWORD }}
csp: ${{ matrix.csp }} attestationVariant: ${{ matrix.attestationVariant }}

View File

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

View File

@ -171,6 +171,6 @@ type stubConfigFetcher struct {
fetchLatestErr error fetchLatestErr error
} }
func (s *stubConfigFetcher) FetchLatestVersion(context.Context, variant.Variant) (attestationconfigapi.VersionAPIEntry, error) { func (s *stubConfigFetcher) FetchLatestVersion(context.Context, variant.Variant) (attestationconfigapi.Entry, error) {
return attestationconfigapi.VersionAPIEntry{}, s.fetchLatestErr return attestationconfigapi.Entry{}, s.fetchLatestErr
} }

View File

@ -22,7 +22,6 @@ go_library(
"//internal/api/attestationconfigapi/cli/client", "//internal/api/attestationconfigapi/cli/client",
"//internal/api/fetcher", "//internal/api/fetcher",
"//internal/attestation/variant", "//internal/attestation/variant",
"//internal/cloud/cloudprovider",
"//internal/constants", "//internal/constants",
"//internal/file", "//internal/file",
"//internal/logger", "//internal/logger",
@ -31,6 +30,7 @@ go_library(
"@com_github_aws_aws_sdk_go_v2//aws", "@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//:s3",
"@com_github_aws_aws_sdk_go_v2_service_s3//types", "@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_afero//:afero",
"@com_github_spf13_cobra//:cobra", "@com_github_spf13_cobra//:cobra",
], ],

View File

@ -29,7 +29,6 @@ go_test(
embed = [":client"], embed = [":client"],
deps = [ deps = [
"//internal/api/attestationconfigapi", "//internal/api/attestationconfigapi",
"//internal/attestation/variant",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",
], ],
) )

View File

@ -15,8 +15,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "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" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
apiclient "github.com/edgelesssys/constellation/v2/internal/api/client" apiclient "github.com/edgelesssys/constellation/v2/internal/api/client"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/attestation/variant"
@ -35,6 +38,8 @@ type Client struct {
bucketID string bucketID string
signer sigstore.Signer signer sigstore.Signer
cacheWindowSize int cacheWindowSize int
log *slog.Logger
} }
// New returns a new Client. // 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), signer: sigstore.NewSigner(cosignPwd, privateKey),
bucketID: cfg.Bucket, bucketID: cfg.Bucket,
cacheWindowSize: versionWindowSize, cacheWindowSize: versionWindowSize,
log: log,
} }
return repo, clientClose, nil 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. // DeleteVersion deletes the given version (without .json suffix) from the API.
func (a Client) uploadSEVSNPVersion(ctx context.Context, attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, date time.Time) error { func (c Client) DeleteVersion(ctx context.Context, attestation variant.Variant, versionStr string) error {
versions, err := a.List(ctx, attestation) versions, err := c.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)
if err != nil { if err != nil {
return fmt.Errorf("fetch version list: %w", err) return fmt.Errorf("fetch version list: %w", err)
} }
ops, err := a.deleteSEVSNPVersion(versions, versionStr) ops, err := c.deleteVersion(versions, versionStr)
if err != nil { if err != nil {
return err 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. // List returns the list of versions for the given attestation variant.
func (a Client) List(ctx context.Context, attestation variant.Variant) (attestationconfigapi.VersionList, error) { func (c Client) List(ctx context.Context, attestation variant.Variant) (attestationconfigapi.List, error) {
if !attestation.Equal(variant.AzureSEVSNP{}) && versions, err := apiclient.Fetch(ctx, c.s3Client, attestationconfigapi.List{Variant: attestation})
!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})
if err != nil { if err != nil {
var notFoundErr *apiclient.NotFoundError var notFoundErr *apiclient.NotFoundError
if errors.As(err, &notFoundErr) { if errors.As(err, &notFoundErr) {
return attestationconfigapi.VersionList{Variant: attestation}, nil return attestationconfigapi.List{Variant: attestation}, nil
} }
return attestationconfigapi.VersionList{}, err return attestationconfigapi.List{}, err
} }
versions.Variant = attestation versions.Variant = attestation
@ -101,10 +90,10 @@ func (a Client) List(ctx context.Context, attestation variant.Variant) (attestat
return versions, nil 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" versionStr = versionStr + ".json"
ops = append(ops, deleteCmd{ ops = append(ops, deleteCmd{
apiObject: attestationconfigapi.VersionAPIEntry{ apiObject: attestationconfigapi.Entry{
Variant: versions.Variant, Variant: versions.Variant,
Version: versionStr, Version: versionStr,
}, },
@ -116,47 +105,46 @@ func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.VersionList, v
} }
ops = append(ops, putCmd{ ops = append(ops, putCmd{
apiObject: removedVersions, apiObject: removedVersions,
signer: a.signer, signer: c.signer,
}) })
return ops, nil return ops, nil
} }
func (a Client) constructUploadCmd(attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, versionNames attestationconfigapi.VersionList, date time.Time) []crudCmd { func (c Client) listCachedVersions(ctx context.Context, attestation variant.Variant) ([]string, error) {
if !attestation.Equal(versionNames.Variant) { list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
return nil 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 dates []string
var res []crudCmd for _, obj := range list.Contents {
fileName := path.Base(*obj.Key)
res = append(res, putCmd{ // The cache contains signature and json files
apiObject: attestationconfigapi.VersionAPIEntry{Version: dateStr, Variant: attestation, SEVSNPVersion: version}, // We only want the json files
signer: a.signer, if date, ok := strings.CutSuffix(fileName, ".json"); ok {
}) dates = append(dates, date)
}
versionNames.AddVersion(dateStr) }
return dates, nil
res = append(res, putCmd{
apiObject: versionNames,
signer: a.signer,
})
return res
} }
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 versions := list.List
for i, v := range versions { for i, v := range versions {
if v == versionStr { if v == versionStr {
if i == len(versions)-1 { if i == len(versions)-1 {
removedVersions = attestationconfigapi.VersionList{List: versions[:i], Variant: list.Variant} removedVersions = attestationconfigapi.List{List: versions[:i], Variant: list.Variant}
} else { } 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 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 { type crudCmd interface {

View File

@ -7,60 +7,28 @@ package client
import ( import (
"testing" "testing"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/stretchr/testify/assert" "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) { func TestDeleteAzureSEVSNPVersions(t *testing.T) {
sut := Client{ sut := Client{
bucketID: "bucket", 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 := assert.New(t)
assert.NoError(err) assert.NoError(err)
assert.Contains(ops, deleteCmd{ assert.Contains(ops, deleteCmd{
apiObject: attestationconfigapi.VersionAPIEntry{ apiObject: attestationconfigapi.Entry{
Version: "2021-01-01.json", Version: "2021-01-01.json",
}, },
}) })
assert.Contains(ops, putCmd{ 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
}

View File

@ -8,6 +8,7 @@ package client
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"path" "path"
@ -15,9 +16,6 @@ import (
"strings" "strings"
"time" "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/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/client" "github.com/edgelesssys/constellation/v2/internal/api/client"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "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) 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 // TCB version in the cache among the last cacheWindowSize versions and updates
// the latest version in the API if there is an update. // the latest version in the API if there is an update.
// force can be used to bypass the validation logic against the cached versions. // force can be used to bypass the validation logic against the cached versions.
func (c Client) UploadSEVSNPVersionLatest(ctx context.Context, attestation variant.Variant, inputVersion, func (c Client) UploadLatestVersion(
latestAPIVersion attestationconfigapi.SEVSNPVersion, now time.Time, force bool, ctx context.Context, attestationVariant variant.Variant,
inputVersion, latestVersionInAPI any,
now time.Time, force bool,
) error { ) error {
if err := c.cacheSEVSNPVersion(ctx, attestation, inputVersion, now); err != nil { // Validate input versions against configured attestation variant
return fmt.Errorf("reporting version: %w", err) // 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 force { if _, ok := latestVersionInAPI.(attestationconfigapi.TDXVersion); !ok {
return c.uploadSEVSNPVersion(ctx, attestation, inputVersion, now) err = fmt.Errorf("latest API version %q is not a TDX version", latestVersionInAPI)
} }
versionDates, err := c.listCachedVersions(ctx, attestation) },
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 { 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 { 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 return nil
} }
minVersion, minDate, err := c.findMinVersion(ctx, attestation, versionDates)
minVersion, minDate, err := c.findMinVersion(ctx, attestationVariant, versionDates)
if err != nil { 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)) c.log.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate))
shouldUpdateAPI, err := isInputNewerThanOtherVersion(minVersion, latestAPIVersion)
if err != nil { 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 return ErrNoNewerVersion
} }
if !shouldUpdateAPI {
c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is not 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))
return nil
}
c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is newer than latest API version: %+v", minVersion, latestAPIVersion))
t, err := time.Parse(VersionFormat, minDate) t, err := time.Parse(VersionFormat, minDate)
if err != nil { if err != nil {
return fmt.Errorf("parsing date: %w", err) 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 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. // uploadAsLatestVersion uploads the given version and updates the list to set it as the "latest" version.
func (c Client) cacheSEVSNPVersion(ctx context.Context, attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, date time.Time) error { // 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" dateStr := date.Format(VersionFormat) + ".json"
res := putCmd{ var ops []crudCmd
apiObject: reportedSEVSNPVersionAPI{Version: dateStr, variant: attestation, SEVSNPVersion: version},
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, 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) { // findMinVersion returns the minimal version in the cache among the last cacheWindowSize versions.
list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ func (c Client) findMinVersion(
Bucket: aws.String(c.bucketID), ctx context.Context, attestationVariant variant.Variant, versionDates []string,
Prefix: aws.String(reportVersionDir(attestation)), ) (any, string, error) {
}) var getMinimalVersion func() (any, string, error)
if err != nil {
return nil, fmt.Errorf("list objects: %w", err) actionForVariant(attestationVariant,
func() {
getMinimalVersion = func() (any, string, error) {
return findMinimalVersion[attestationconfigapi.TDXVersion](ctx, attestationVariant, versionDates, c.s3Client, c.cacheWindowSize)
} }
var dates []string },
for _, obj := range list.Contents { func() {
fileName := path.Base(*obj.Key) getMinimalVersion = func() (any, string, error) {
if strings.HasSuffix(fileName, ".json") { return findMinimalVersion[attestationconfigapi.SEVSNPVersion](ctx, attestationVariant, versionDates, c.s3Client, c.cacheWindowSize)
dates = append(dates, fileName[:len(fileName)-5])
} }
} },
return dates, nil )
return getMinimalVersion()
} }
// findMinVersion finds the minimal version of the given version dates among the latest values in the version window size. func findMinimalVersion[T attestationconfigapi.TDXVersion | attestationconfigapi.SEVSNPVersion](
func (c Client) findMinVersion(ctx context.Context, attesation variant.Variant, versionDates []string) (attestationconfigapi.SEVSNPVersion, string, error) { ctx context.Context, variant variant.Variant, versionDates []string,
var minimalVersion *attestationconfigapi.SEVSNPVersion s3Client *client.Client, cacheWindowSize int,
) (T, string, error) {
var minimalVersion *T
var minimalDate string var minimalDate string
sort.Sort(sort.Reverse(sort.StringSlice(versionDates))) // sort in reverse order to slice the latest versions 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 sort.Strings(versionDates) // sort with oldest first to to take the minimal version with the oldest date
for _, date := range versionDates { 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 { 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 = variant // variant is not set by Fetch, set it manually
obj.variant = attesation
if minimalVersion == nil { if minimalVersion == nil {
minimalVersion = &obj.SEVSNPVersion v := obj.getVersion().(T)
minimalVersion = &v
minimalDate = date minimalDate = date
} else {
shouldUpdateMinimal, err := isInputNewerThanOtherVersion(*minimalVersion, obj.SEVSNPVersion)
if err != nil {
continue continue
} }
if shouldUpdateMinimal {
minimalVersion = &obj.SEVSNPVersion // 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 minimalDate = date
} }
} }
}
return *minimalVersion, minimalDate, nil return *minimalVersion, minimalDate, nil
} }
// isInputNewerThanOtherVersion compares all version fields and returns true if any input field is newer. func isInputNewerThanOtherVersion(variant variant.Variant, inputVersion, otherVersion any) bool {
func isInputNewerThanOtherVersion(input, other attestationconfigapi.SEVSNPVersion) (bool, error) { var result bool
if input == other { actionForVariant(variant,
return false, nil func() {
} input := inputVersion.(attestationconfigapi.TDXVersion)
if input.TEE < other.TEE { other := otherVersion.(attestationconfigapi.TDXVersion)
return false, fmt.Errorf("input TEE version: %d is older than latest API version: %d", input.TEE, other.TEE) result = isInputNewerThanOtherTDXVersion(input, other)
} },
if input.SNP < other.SNP { func() {
return false, fmt.Errorf("input SNP version: %d is older than latest API version: %d", input.SNP, other.SNP) input := inputVersion.(attestationconfigapi.SEVSNPVersion)
} other := otherVersion.(attestationconfigapi.SEVSNPVersion)
if input.Microcode < other.Microcode { result = isInputNewerThanOtherSEVSNPVersion(input, other)
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 result
return false, fmt.Errorf("input Bootloader version: %d is older than latest API version: %d", input.Bootloader, other.Bootloader)
}
return true, nil
} }
// reportedSEVSNPVersionAPI is the request to get the version information of the specific version in the config api. type apiVersionObject struct {
type reportedSEVSNPVersionAPI struct { version string `json:"-"`
Version string `json:"-"`
variant variant.Variant `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. // JSONPath returns the path to the JSON file for the request to the config api.
func (i reportedSEVSNPVersionAPI) JSONPath() string { // This is the path to the cached version in the S3 bucket.
return path.Join(reportVersionDir(i.variant), i.Version) 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. // ValidateRequest validates the request.
func (i reportedSEVSNPVersionAPI) ValidateRequest() error { func (a apiVersionObject) ValidateRequest() error {
if !strings.HasSuffix(i.Version, ".json") { if !strings.HasSuffix(a.version, ".json") {
return fmt.Errorf("version has no .json suffix") return fmt.Errorf("version has no .json suffix")
} }
return nil return nil
} }
// Validate is a No-Op at the moment. // Validate is a No-Op.
func (i reportedSEVSNPVersionAPI) Validate() error { func (a apiVersionObject) Validate() error {
return nil 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
}

View File

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestIsInputNewerThanLatestAPI(t *testing.T) { func TestIsInputNewerThanOtherSEVSNPVersion(t *testing.T) {
newTestCfg := func() attestationconfigapi.SEVSNPVersion { newTestCfg := func() attestationconfigapi.SEVSNPVersion {
return attestationconfigapi.SEVSNPVersion{ return attestationconfigapi.SEVSNPVersion{
Microcode: 93, Microcode: 93,
@ -25,7 +25,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
latest attestationconfigapi.SEVSNPVersion latest attestationconfigapi.SEVSNPVersion
input attestationconfigapi.SEVSNPVersion input attestationconfigapi.SEVSNPVersion
expect bool expect bool
errMsg string
}{ }{
"input is older than latest": { "input is older than latest": {
input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion { input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion {
@ -34,7 +33,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
}(newTestCfg()), }(newTestCfg()),
latest: newTestCfg(), latest: newTestCfg(),
expect: false, expect: false,
errMsg: "input Microcode version: 92 is older than latest API version: 93",
}, },
"input has greater and smaller version field than latest": { "input has greater and smaller version field than latest": {
input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion { input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion {
@ -44,7 +42,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
}(newTestCfg()), }(newTestCfg()),
latest: newTestCfg(), latest: newTestCfg(),
expect: false, expect: false,
errMsg: "input Bootloader version: 1 is older than latest API version: 2",
}, },
"input is newer than latest": { "input is newer than latest": {
input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion { input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion {
@ -62,14 +59,80 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
} }
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
isNewer, err := isInputNewerThanOtherVersion(tc.input, tc.latest) isNewer := isInputNewerThanOtherSEVSNPVersion(tc.input, tc.latest)
assert := assert.New(t) assert.Equal(t, tc.expect, isNewer)
if tc.errMsg != "" { })
assert.EqualError(err, tc.errMsg) }
} else { }
assert.NoError(err)
assert.Equal(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)
}) })
} }
} }

View File

@ -18,7 +18,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "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/logger"
"github.com/edgelesssys/constellation/v2/internal/staticupload" "github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -27,21 +26,21 @@ import (
// newDeleteCmd creates the delete command. // newDeleteCmd creates the delete command.
func newDeleteCmd() *cobra.Command { func newDeleteCmd() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "delete {aws|azure|gcp} {snp-report|guest-firmware} <version>", Use: "delete {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} <version>",
Short: "Delete an object from the attestationconfig API", Short: "Delete an object from the attestationconfig API",
Long: "Delete a specific object version from the config api. <version> is the name of the object to delete (without .json suffix)", Long: "Delete a specific object version from the config api. <version> 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", Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete azure-sev-snp attestation-report 1.0.0",
Args: cobra.MatchAll(cobra.ExactArgs(3), isCloudProvider(0), isValidKind(1)), Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)),
PreRunE: envCheck, PreRunE: envCheck,
RunE: runDelete, RunE: runDelete,
} }
recursivelyCmd := &cobra.Command{ 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/<csp>", Short: "delete all objects from the API path constellation/v1/attestation/<csp>",
Long: "Delete all objects from the API path constellation/v1/attestation/<csp>", Long: "Delete all objects from the API path constellation/v1/attestation/<csp>",
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure", Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure-sev-snp",
Args: cobra.MatchAll(cobra.ExactArgs(1), isCloudProvider(0)), Args: cobra.MatchAll(cobra.ExactArgs(1), isAttestationVariant(0)),
RunE: runRecursiveDelete, RunE: runRecursiveDelete,
} }
@ -75,16 +74,7 @@ func runDelete(cmd *cobra.Command, args []string) (retErr error) {
} }
}() }()
switch deleteCfg.provider { return deleteEntry(cmd.Context(), client, deleteCfg)
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)
}
} }
func runRecursiveDelete(cmd *cobra.Command, args []string) (retErr error) { 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 deletePath := path.Join(attestationconfigapi.AttestationURLPath, deleteCfg.variant.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)
}
return deleteEntryRecursive(cmd.Context(), deletePath, client, deleteCfg) return deleteEntryRecursive(cmd.Context(), deletePath, client, deleteCfg)
} }
type deleteConfig struct { type deleteConfig struct {
provider cloudprovider.Provider variant variant.Variant
kind objectKind kind objectKind
version string version string
region string region string
@ -155,12 +135,15 @@ func newDeleteConfig(cmd *cobra.Command, args [3]string) (deleteConfig, error) {
} }
apiCfg := getAPIEnvironment(testing) 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]) kind := kindFromString(args[1])
version := args[2] version := args[2]
return deleteConfig{ return deleteConfig{
provider: provider, variant: variant,
kind: kind, kind: kind,
version: version, version: version,
region: region, region: region,
@ -171,12 +154,12 @@ func newDeleteConfig(cmd *cobra.Command, args [3]string) (deleteConfig, error) {
}, nil }, nil
} }
func deleteEntry(ctx context.Context, attvar variant.Variant, client *client.Client, cfg deleteConfig) error { func deleteEntry(ctx context.Context, client *client.Client, cfg deleteConfig) error {
if cfg.kind != snpReport { if cfg.kind != attestationReport {
return fmt.Errorf("kind %s not supported", cfg.kind) 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 { func deleteEntryRecursive(ctx context.Context, path string, client *staticupload.Client, cfg deleteConfig) error {

View File

@ -19,39 +19,67 @@ configapi_cli=$(realpath @@CONFIGAPI_CLI@@)
stat "${configapi_cli}" >> /dev/null stat "${configapi_cli}" >> /dev/null
configapi_cli="${configapi_cli} --testing" configapi_cli="${configapi_cli} --testing"
###### script body ###### ###### script body ######
function variant() { attestationVariant=$1
if [[ $1 == "aws" ]]; then readonly attestationVariant
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")
readonly region="eu-west-1" readonly region="eu-west-1"
readonly bucket="resource-api-testing" readonly bucket="resource-api-testing"
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
readonly tmpdir readonly tmpdir
registerExitHandler "rm -rf $tmpdir" registerExitHandler "rm -rf ${tmpdir}"
# empty the bucket version state # 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}/attestationReportCurrent.json"
readonly current_report_path="$tmpdir/currentSnpReport.json" readonly report_path="${tmpdir}/attestationReport.json"
cat << EOF > "$current_report_path" 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": { "snp_report": {
"reported_tcb": { "reported_tcb": {
@ -75,90 +103,105 @@ cat << EOF > "$current_report_path"
} }
} }
EOF 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 # 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" ${configapi_cli} upload "${attestationVariant}" attestation-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
# report 3 versions with different dates to fill the reporter cache # report 3 versions with different dates to fill the reporter cache
readonly date_oldest="2023-02-01-03-04" 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" 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" 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 # 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}" 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 ${?}" echo "Checking for uploaded version file ${basepath}/${date_oldest}.json: request returned ${?}"
exit 1 exit 1
fi 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 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:" echo "The version content:"
cat version.json cat version.json
echo " is not equal to the expected version content:" echo " is not equal to the expected version content:"
echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}' echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}'
exit 1 exit 1
fi
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 ${?}" echo "Checking for uploaded version signature file ${basepath}/${date_oldest}.json.sig: request returned ${?}"
exit 1 exit 1
fi fi
# check list endpoint # check list endpoint
if ! curl -fsSL "${baseurl}"/list > list.json; then if ! curl -fsSL "${baseurl}"/list > list.json; then
echo "Checking for uploaded list file ${basepath}/list: request returned ${?}" 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 fi
# check that the other versions are not uploaded # check that the other versions are not uploaded
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_older}.json) http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_older}.json")
if [[ $http_code -ne 404 ]]; then if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date_older}.json, but got ${http_code}" echo "Expected HTTP code 404 for: ${basepath}/${date_older}.json, but got ${http_code}"
exit 1 exit 1
fi fi
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date}.json.sig) http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date}.json.sig")
if [[ $http_code -ne 404 ]]; then if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date}.json, but got ${http_code}" echo "Expected HTTP code 404 for: ${basepath}/${date}.json, but got ${http_code}"
exit 1 exit 1
fi 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. # 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) http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json")
if [[ $http_code -ne 404 ]]; then if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}" echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}"
exit 1 exit 1
fi fi
# Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail. # 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) http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json.sig")
if [[ $http_code -ne 404 ]]; then if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}" echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}"
exit 1 exit 1
fi fi

View File

@ -7,6 +7,7 @@ package main
import ( import (
"context" "context"
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
@ -17,30 +18,30 @@ import (
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher" "github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant" "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/file"
"github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/staticupload" "github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/edgelesssys/constellation/v2/internal/verify" "github.com/edgelesssys/constellation/v2/internal/verify"
"github.com/google/go-tdx-guest/proto/tdx"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func newUploadCmd() *cobra.Command { func newUploadCmd() *cobra.Command {
uploadCmd := &cobra.Command{ uploadCmd := &cobra.Command{
Use: "upload {aws|azure|gcp} {snp-report|guest-firmware} <path>", Use: "upload {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} <path>",
Short: "Upload an object to the attestationconfig API", 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. "+ "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. "+ "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)"+ "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.", "to be able to upload to S3. Set the %s and %s environment variables to authenticate with cosign.",
envCosignPrivateKey, envCosignPwd, 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, PreRunE: envCheck,
RunE: runUpload, RunE: runUpload,
} }
@ -91,42 +92,19 @@ func runUpload(cmd *cobra.Command, args []string) (retErr error) {
return fmt.Errorf("creating client: %w", err) return fmt.Errorf("creating client: %w", err)
} }
var attestation variant.Variant return uploadReport(ctx, client, uploadCfg, file.NewHandler(afero.NewOsFs()), log)
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)
} }
func uploadReport(ctx context.Context, func uploadReport(
attestation variant.Variant, ctx context.Context, apiClient *client.Client,
apiClient *client.Client, cfg uploadConfig, fs file.Handler, log *slog.Logger,
cfg uploadConfig,
fs file.Handler,
log *slog.Logger,
) error { ) error {
if cfg.kind != snpReport { if cfg.kind != attestationReport {
return fmt.Errorf("kind %s not supported", cfg.kind) return fmt.Errorf("kind %s not supported", cfg.kind)
} }
log.Info(fmt.Sprintf("Reading SNP report from file: %s", cfg.path)) apiFetcher := attestationconfigapi.NewFetcherWithCustomCDNAndCosignKey(cfg.url, cfg.cosignPublicKey)
var report verify.Report latestVersionInAPI, err := apiFetcher.FetchLatestVersion(ctx, cfg.variant)
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)
if err != nil { if err != nil {
var notFoundErr *fetcher.NotFoundError var notFoundErr *fetcher.NotFoundError
if errors.As(err, &notFoundErr) { if errors.As(err, &notFoundErr) {
@ -136,12 +114,39 @@ func uploadReport(ctx context.Context,
} }
} }
latestAPIVersion := latestAPIVersionAPI.SEVSNPVersion var newVersion, latestVersion any
if err := apiClient.UploadSEVSNPVersionLatest(ctx, attestation, inputVersion, latestAPIVersion, cfg.uploadDate, cfg.force); err != nil { switch cfg.variant {
if errors.Is(err, client.ErrNoNewerVersion) { case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.GCPSEVSNP{}:
log.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v", inputVersion, latestAPIVersion)) latestVersion = latestVersionInAPI.SEVSNPVersion
return nil
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) 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 { type uploadConfig struct {
provider cloudprovider.Provider variant variant.Variant
kind objectKind kind objectKind
path string path string
uploadDate time.Time 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) 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]) kind := kindFromString(args[1])
path := args[2] path := args[2]
return uploadConfig{ return uploadConfig{
provider: provider, variant: variant,
kind: kind, kind: kind,
path: path, path: path,
uploadDate: uploadDate, uploadDate: uploadDate,

View File

@ -10,16 +10,22 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func isCloudProvider(arg int) cobra.PositionalArgs { func isAttestationVariant(arg int) cobra.PositionalArgs {
return func(_ *cobra.Command, args []string) error { return func(_ *cobra.Command, args []string) error {
if provider := cloudprovider.FromString(args[arg]); provider == cloudprovider.Unknown { attestationVariant, err := variant.FromString(args[arg])
return fmt.Errorf("argument %s isn't a valid cloud provider", 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 return nil
default:
return fmt.Errorf("argument %s isn't a supported attestation variant", args[arg])
}
} }
} }
@ -38,15 +44,14 @@ type objectKind string
const ( const (
// unknown is the default objectKind and does nothing. // unknown is the default objectKind and does nothing.
unknown objectKind = "unknown-kind" unknown objectKind = "unknown-kind"
snpReport objectKind = "snp-report" attestationReport objectKind = "attestation-report"
tdxReport objectKind = "tdx-report"
guestFirmware objectKind = "guest-firmware" guestFirmware objectKind = "guest-firmware"
) )
func kindFromString(s string) objectKind { func kindFromString(s string) objectKind {
lower := strings.ToLower(s) lower := strings.ToLower(s)
switch objectKind(lower) { switch objectKind(lower) {
case snpReport, guestFirmware, tdxReport: case attestationReport, guestFirmware:
return objectKind(lower) return objectKind(lower)
default: default:
return unknown return unknown

View File

@ -20,7 +20,7 @@ const cosignPublicKey = constants.CosignPublicKeyReleases
// Fetcher fetches config API resources without authentication. // Fetcher fetches config API resources without authentication.
type Fetcher interface { 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. // 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. // 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) list, err := f.fetchVersionList(ctx, variant)
if err != nil { if err != nil {
return VersionAPIEntry{}, err return Entry{}, err
} }
// latest version is first in list // 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. // fetchVersionList fetches the version list information from the config API.
func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant) (VersionList, error) { func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant) (List, 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.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, List{Variant: variant}, f.verifier)
fetchedList, err := apifetcher.Fetch(ctx, f.HTTPClient, f.cdnURL, VersionList{Variant: variant})
if err != nil { 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 // 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. // fetchVersion fetches the version information from the config API.
func (f *fetcher) fetchVersion(ctx context.Context, version string, variant variant.Variant) (VersionAPIEntry, error) { func (f *fetcher) fetchVersion(ctx context.Context, version string, variant variant.Variant) (Entry, error) {
obj := VersionAPIEntry{ obj := Entry{
Version: version, Version: version,
Variant: variant, Variant: variant,
} }
fetchedVersion, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, obj, f.verifier) fetchedVersion, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, obj, f.verifier)
if err != nil { 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 // Set the attestation variant of the list as it is not part of the marshalled JSON retrieved by FetchAndVerify

View File

@ -22,7 +22,7 @@ import (
) )
func TestFetchLatestSEVSNPVersion(t *testing.T) { func TestFetchLatestSEVSNPVersion(t *testing.T) {
latestVersionSNP := VersionAPIEntry{ latestVersionSNP := Entry{
SEVSNPVersion: SEVSNPVersion{ SEVSNPVersion: SEVSNPVersion{
Microcode: 93, Microcode: 93,
TEE: 0, TEE: 0,
@ -30,7 +30,7 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) {
Bootloader: 2, Bootloader: 2,
}, },
} }
olderVersionSNP := VersionAPIEntry{ olderVersionSNP := Entry{
SEVSNPVersion: SEVSNPVersion{ SEVSNPVersion: SEVSNPVersion{
Microcode: 1, Microcode: 1,
TEE: 0, TEE: 0,
@ -38,7 +38,7 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) {
Bootloader: 1, Bootloader: 1,
}, },
} }
latestVersionTDX := VersionAPIEntry{ latestVersionTDX := Entry{
TDXVersion: TDXVersion{ TDXVersion: TDXVersion{
QESVN: 2, QESVN: 2,
PCESVN: 3, PCESVN: 3,
@ -47,7 +47,7 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) {
XFAM: [8]byte{6}, XFAM: [8]byte{6},
}, },
} }
olderVersionTDX := VersionAPIEntry{ olderVersionTDX := Entry{
TDXVersion: TDXVersion{ TDXVersion: TDXVersion{
QESVN: 1, QESVN: 1,
PCESVN: 2, PCESVN: 2,
@ -64,30 +64,30 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) {
timeAtTest time.Time timeAtTest time.Time
wantErr bool wantErr bool
attestation variant.Variant attestation variant.Variant
expectedVersion VersionAPIEntry expectedVersion Entry
olderVersion VersionAPIEntry olderVersion Entry
latestVersion VersionAPIEntry latestVersion Entry
}{ }{
"get latest version azure-sev-snp": { "get latest version azure-sev-snp": {
fetcherVersions: []string{latestStr, olderStr}, fetcherVersions: []string{latestStr, olderStr},
attestation: variant.AzureSEVSNP{}, attestation: variant.AzureSEVSNP{},
expectedVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(), expectedVersion: func() Entry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(),
olderVersion: func() VersionAPIEntry { tmp := olderVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(), olderVersion: func() Entry { tmp := olderVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(),
latestVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(), latestVersion: func() Entry { tmp := latestVersionSNP; tmp.Variant = variant.AzureSEVSNP{}; return tmp }(),
}, },
"get latest version aws-sev-snp": { "get latest version aws-sev-snp": {
fetcherVersions: []string{latestStr, olderStr}, fetcherVersions: []string{latestStr, olderStr},
attestation: variant.AWSSEVSNP{}, attestation: variant.AWSSEVSNP{},
expectedVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(), expectedVersion: func() Entry { tmp := latestVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(),
olderVersion: func() VersionAPIEntry { tmp := olderVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(), olderVersion: func() Entry { tmp := olderVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(),
latestVersion: func() VersionAPIEntry { tmp := latestVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(), latestVersion: func() Entry { tmp := latestVersionSNP; tmp.Variant = variant.AWSSEVSNP{}; return tmp }(),
}, },
"get latest version azure-tdx": { "get latest version azure-tdx": {
fetcherVersions: []string{latestStr, olderStr}, fetcherVersions: []string{latestStr, olderStr},
attestation: variant.AzureTDX{}, attestation: variant.AzureTDX{},
expectedVersion: func() VersionAPIEntry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(), expectedVersion: func() Entry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(),
olderVersion: func() VersionAPIEntry { tmp := olderVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(), olderVersion: func() Entry { tmp := olderVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(),
latestVersion: func() VersionAPIEntry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(), latestVersion: func() Entry { tmp := latestVersionTDX; tmp.Variant = variant.AzureTDX{}; return tmp }(),
}, },
} }
for name, tc := range testCases { for name, tc := range testCases {
@ -119,14 +119,15 @@ type fakeConfigAPIHandler struct {
attestation variant.Variant attestation variant.Variant
versions []string versions []string
latestDate string latestDate string
latestVersion VersionAPIEntry latestVersion Entry
olderDate string olderDate string
olderVersion VersionAPIEntry olderVersion Entry
} }
// RoundTrip resolves the request and returns a dummy response. // RoundTrip resolves the request and returns a dummy response.
func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, error) { 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{} res := &http.Response{}
bt, err := json.Marshal(f.versions) bt, err := json.Marshal(f.versions)
if err != nil { 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.Header.Set("Content-Type", "application/json")
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK
return res, nil 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{} res := &http.Response{}
bt, err := json.Marshal(f.latestVersion) bt, err := json.Marshal(f.latestVersion)
if err != nil { if err != nil {
@ -147,7 +155,7 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK
return res, nil 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{} res := &http.Response{}
bt, err := json.Marshal(f.olderVersion) bt, err := json.Marshal(f.olderVersion)
if err != nil { 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.Body = io.NopCloser(bytes.NewReader(bt))
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK
return res, nil 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 := &http.Response{}
res.Body = io.NopCloser(bytes.NewReader([]byte("null"))) res.Body = io.NopCloser(bytes.NewReader([]byte("null")))
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK
return res, nil 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 := &http.Response{}
res.Body = io.NopCloser(bytes.NewReader([]byte("null"))) res.Body = io.NopCloser(bytes.NewReader([]byte("null")))
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK

View File

@ -45,12 +45,12 @@ type TDXVersion struct {
XFAM [8]byte `json:"xfam"` 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. // 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. // 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. // That would remove the possibility of some fetcher/client code forgetting to set the variant.
type VersionAPIEntry struct { type Entry struct {
Version string `json:"-"` Version string `json:"-"`
Variant variant.Variant `json:"-"` Variant variant.Variant `json:"-"`
SEVSNPVersion SEVSNPVersion
@ -58,12 +58,12 @@ type VersionAPIEntry struct {
} }
// JSONPath returns the path to the JSON file for the request to the config api. // 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) return path.Join(AttestationURLPath, i.Variant.String(), i.Version)
} }
// ValidateRequest validates the request. // ValidateRequest validates the request.
func (i VersionAPIEntry) ValidateRequest() error { func (i Entry) ValidateRequest() error {
if !strings.HasSuffix(i.Version, ".json") { if !strings.HasSuffix(i.Version, ".json") {
return fmt.Errorf("version has no .json suffix") 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. // Validate is a No-Op at the moment.
func (i VersionAPIEntry) Validate() error { func (i Entry) Validate() error {
return nil 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. // 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. // 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. // That would remove the possibility of some fetcher/client code forgetting to set the variant.
type VersionList struct { type List struct {
Variant variant.Variant Variant variant.Variant
List []string List []string
} }
// MarshalJSON marshals the i's list property to JSON. // 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) return json.Marshal(i.List)
} }
// UnmarshalJSON unmarshals a list of strings into i's list property. // 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) return json.Unmarshal(data, &i.List)
} }
// JSONPath returns the path to the JSON file for the request to the config api. // 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") return path.Join(AttestationURLPath, i.Variant.String(), "list")
} }
// ValidateRequest is a NoOp as there is no input. // ValidateRequest is a NoOp as there is no input.
func (i VersionList) ValidateRequest() error { func (i List) ValidateRequest() error {
return nil return nil
} }
// SortReverse sorts the list of versions in reverse order. // 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))) sort.Sort(sort.Reverse(sort.StringSlice(i.List)))
} }
// AddVersion adds new to i's list and sorts the element in descending order. // 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 = append(i.List, new)
i.List = variant.RemoveDuplicate(i.List) i.List = variant.RemoveDuplicate(i.List)
@ -119,7 +119,7 @@ func (i *VersionList) AddVersion(new string) {
} }
// Validate validates the response. // Validate validates the response.
func (i VersionList) Validate() error { func (i List) Validate() error {
if len(i.List) < 1 { if len(i.List) < 1 {
return fmt.Errorf("no versions found in /list") return fmt.Errorf("no versions found in /list")
} }

View File

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

View File

@ -53,7 +53,7 @@ type Client struct {
dirtyPaths []string // written paths to be invalidated dirtyPaths []string // written paths to be invalidated
DryRun bool // no write operations are performed DryRun bool // no write operations are performed
Logger *slog.Logger log *slog.Logger
} }
// NewReadOnlyClient creates a new read-only client. // NewReadOnlyClient creates a new read-only client.
@ -77,7 +77,7 @@ func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID strin
s3ClientClose: staticUploadClientClose, s3ClientClose: staticUploadClientClose,
bucket: bucket, bucket: bucket,
DryRun: true, DryRun: true,
Logger: log, log: log,
} }
clientClose := func(ctx context.Context) error { clientClose := func(ctx context.Context) error {
return client.Close(ctx) return client.Close(ctx)
@ -106,7 +106,7 @@ func NewClient(ctx context.Context, region, bucket, distributionID string, dryRu
s3ClientClose: staticUploadClientClose, s3ClientClose: staticUploadClientClose,
bucket: bucket, bucket: bucket,
DryRun: dryRun, DryRun: dryRun,
Logger: log, log: log,
} }
clientClose := func(ctx context.Context) error { clientClose := func(ctx context.Context) error {
return client.Close(ctx) 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. // It invalidates the CDN cache for all uploaded files.
func (c *Client) Close(ctx context.Context) error { func (c *Client) Close(ctx context.Context) error {
if c.s3ClientClose == nil { if c.s3ClientClose == nil {
c.Logger.Debug("Client has no s3ClientClose") c.log.Debug("Client has no s3ClientClose")
return nil return nil
} }
return c.s3ClientClose(ctx) return c.s3ClientClose(ctx)
@ -131,7 +131,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error {
Bucket: &c.bucket, Bucket: &c.bucket,
Prefix: &path, 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{} objs := []s3types.Object{}
out := &s3.ListObjectsV2Output{IsTruncated: ptr(true)} out := &s3.ListObjectsV2Output{IsTruncated: ptr(true)}
for out.IsTruncated != nil && *out.IsTruncated { 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...) 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 { 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 return nil
} }
@ -155,7 +155,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error {
} }
if c.DryRun { 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 return nil
} }
@ -167,7 +167,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error {
Objects: objIDs, 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 { if _, err := c.s3Client.DeleteObjects(ctx, deleteIn); err != nil {
return fmt.Errorf("deleting objects in %s: %w", path, err) 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()), 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) out, err := c.s3Client.GetObject(ctx, in)
var noSuchkey *s3types.NoSuchKey var noSuchkey *s3types.NoSuchKey
if errors.As(err, &noSuchkey) { if errors.As(err, &noSuchkey) {
@ -231,7 +231,7 @@ func Update(ctx context.Context, c *Client, obj APIObject) error {
} }
if c.DryRun { 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 return nil
} }
@ -243,7 +243,7 @@ func Update(ctx context.Context, c *Client, obj APIObject) error {
c.dirtyPaths = append(c.dirtyPaths, "/"+obj.JSONPath()) 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 { if _, err := c.Upload(ctx, in); err != nil {
return fmt.Errorf("uploading %T: %w", obj, err) 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()), 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 { if _, err := c.DeleteObject(ctx, in); err != nil {
return fmt.Errorf("deleting s3 object at %s: %w", obj.JSONPath(), err) return fmt.Errorf("deleting s3 object at %s: %w", obj.JSONPath(), err)
} }

View File

@ -20,12 +20,10 @@ package fetcher
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"github.com/edgelesssys/constellation/v2/internal/sigstore" "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'. // signature manages the signature of a object saved at location 'Signed'.
type signature struct { type signature struct {
// Signed is the object that is signed. // Signed is the object that is signed.
Signed string `json:"-"` Signed string `json:"signed"`
// Signature is the signature of `Signed`. // Signature is the signature of `Signed`.
Signature []byte `json:"signature"` Signature []byte `json:"signature"`
} }
@ -155,12 +153,8 @@ func (s signature) JSONPath() string {
return s.Signed + ".sig" return s.Signed + ".sig"
} }
// ValidateRequest validates the request. // ValidateRequest is a no-op.
func (s signature) ValidateRequest() error { func (s signature) ValidateRequest() error {
if !strings.HasSuffix(s.Signed, ".json") {
return errors.New("signed object missing .json suffix")
}
return nil return nil
} }

View File

@ -23,6 +23,8 @@ import (
type Client struct { type Client struct {
*apiclient.Client *apiclient.Client
clientClose func(ctx context.Context) error clientClose func(ctx context.Context) error
log *slog.Logger
} }
// NewClient creates a new client for the versions API. // 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) { ) (*Client, CloseFunc, error) {
genericClient, genericClientClose, err := apiclient.NewClient(ctx, region, bucket, distributionID, dryRun, log) genericClient, genericClientClose, err := apiclient.NewClient(ctx, region, bucket, distributionID, dryRun, log)
versionsClient := &Client{ versionsClient := &Client{
genericClient, Client: genericClient,
genericClientClose, clientClose: genericClientClose,
log: log,
} }
versionsClientClose := func(ctx context.Context) error { versionsClientClose := func(ctx context.Context) error {
return versionsClient.Close(ctx) return versionsClient.Close(ctx)
@ -50,8 +53,9 @@ func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID strin
return nil, nil, err return nil, nil, err
} }
versionsClient := &Client{ versionsClient := &Client{
genericClient, Client: genericClient,
genericClientClose, clientClose: genericClientClose,
log: log,
} }
versionsClientClose := func(ctx context.Context) error { versionsClientClose := func(ctx context.Context) error {
return versionsClient.Close(ctx) 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 { func (c *Client) DeleteVersion(ctx context.Context, ver Version) error {
var retErr 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) possibleNewLatest, err := c.deleteVersionFromMinorVersionList(ctx, ver)
if err != nil { if err != nil {
retErr = errors.Join(retErr, fmt.Errorf("removing from minor version list: %w", err)) 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 { if err := c.deleteVersionFromLatest(ctx, ver, possibleNewLatest); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("updating latest version: %w", err)) 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 { if err := c.Client.DeletePath(ctx, ver.ArtifactPath(APIV1)); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting artifact path: %w", err)) 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), Base: ver.WithGranularity(GranularityMinor),
Kind: VersionKindImage, 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) minorList, err := c.FetchVersionList(ctx, minorList)
var notFoundErr *apiclient.NotFoundError var notFoundErr *apiclient.NotFoundError
if errors.As(err, &notFoundErr) { if errors.As(err, &notFoundErr) {
c.Client.Logger.Warn(fmt.Sprintf("Minor version list for version %s not found", ver.version)) c.log.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("Skipping update of minor version list")
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("fetching minor version list for version %s: %w", ver.version, err) return nil, fmt.Errorf("fetching minor version list for version %s: %w", ver.version, err)
} }
if !minorList.Contains(ver.version) { 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.log.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("Skipping update of minor version list")
return nil, nil return nil, nil
} }
@ -192,20 +196,20 @@ func (c *Client) deleteVersionFromMinorVersionList(ctx context.Context, ver Vers
Kind: VersionKindImage, Kind: VersionKindImage,
Version: minorList.Versions[len(minorList.Versions)-1], 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 { 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 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 { if err := c.UpdateVersionList(ctx, minorList); err != nil {
return latest, fmt.Errorf("updating minor version list %s: %w", minorList.JSONPath(), err) 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 return latest, nil
} }
@ -216,33 +220,33 @@ func (c *Client) deleteVersionFromLatest(ctx context.Context, ver Version, possi
Stream: ver.stream, Stream: ver.stream,
Kind: VersionKindImage, 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) latest, err := c.FetchVersionLatest(ctx, latest)
var notFoundErr *apiclient.NotFoundError var notFoundErr *apiclient.NotFoundError
if errors.As(err, &notFoundErr) { if errors.As(err, &notFoundErr) {
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 return nil
} else if err != nil { } else if err != nil {
return fmt.Errorf("fetching latest version: %w", err) return fmt.Errorf("fetching latest version: %w", err)
} }
if latest.Version != ver.version { 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 return nil
} }
if possibleNewLatest == nil { if possibleNewLatest == nil {
c.Client.Logger.Error(fmt.Sprintf("Latest version is %s, but no new latest version was found", latest.Version)) c.log.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("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) return fmt.Errorf("latest version is %s, but no new latest version was found", latest.Version)
} }
if c.Client.DryRun { 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 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 { if err := c.UpdateVersionLatest(ctx, *possibleNewLatest); err != nil {
return fmt.Errorf("updating latest version: %w", err) return fmt.Errorf("updating latest version: %w", err)
} }

View File

@ -1165,7 +1165,7 @@ type AzureTDX struct {
// Expected 48 byte hex-encoded MR_SEAM value. // Expected 48 byte hex-encoded MR_SEAM value.
MRSeam encoding.HexBytes `json:"mrSeam,omitempty" yaml:"mrSeam,omitempty"` MRSeam encoding.HexBytes `json:"mrSeam,omitempty" yaml:"mrSeam,omitempty"`
// description: | // 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"` XFAM AttestationVersion[encoding.HexBytes] `json:"xfam" yaml:"xfam"`
// description: | // description: |
// Intel Root Key certificate used to verify the TDX certificate chain. // Intel Root Key certificate used to verify the TDX certificate chain.

View File

@ -545,22 +545,22 @@ func init() {
GCPSEVSNPDoc.Fields[0].Description = "Expected TPM measurements." GCPSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
GCPSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." GCPSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
GCPSEVSNPDoc.Fields[1].Name = "bootloaderVersion" GCPSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
GCPSEVSNPDoc.Fields[1].Type = "AttestationVersion" GCPSEVSNPDoc.Fields[1].Type = ""
GCPSEVSNPDoc.Fields[1].Note = "" GCPSEVSNPDoc.Fields[1].Note = ""
GCPSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version." GCPSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
GCPSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version." GCPSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
GCPSEVSNPDoc.Fields[2].Name = "teeVersion" GCPSEVSNPDoc.Fields[2].Name = "teeVersion"
GCPSEVSNPDoc.Fields[2].Type = "AttestationVersion" GCPSEVSNPDoc.Fields[2].Type = ""
GCPSEVSNPDoc.Fields[2].Note = "" GCPSEVSNPDoc.Fields[2].Note = ""
GCPSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version." GCPSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
GCPSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version." GCPSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
GCPSEVSNPDoc.Fields[3].Name = "snpVersion" GCPSEVSNPDoc.Fields[3].Name = "snpVersion"
GCPSEVSNPDoc.Fields[3].Type = "AttestationVersion" GCPSEVSNPDoc.Fields[3].Type = ""
GCPSEVSNPDoc.Fields[3].Note = "" GCPSEVSNPDoc.Fields[3].Note = ""
GCPSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version." GCPSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
GCPSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version." GCPSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
GCPSEVSNPDoc.Fields[4].Name = "microcodeVersion" GCPSEVSNPDoc.Fields[4].Name = "microcodeVersion"
GCPSEVSNPDoc.Fields[4].Type = "AttestationVersion" GCPSEVSNPDoc.Fields[4].Type = ""
GCPSEVSNPDoc.Fields[4].Note = "" GCPSEVSNPDoc.Fields[4].Note = ""
GCPSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version." GCPSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
GCPSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "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].Description = "Expected TPM measurements."
AWSSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." AWSSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
AWSSEVSNPDoc.Fields[1].Name = "bootloaderVersion" AWSSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
AWSSEVSNPDoc.Fields[1].Type = "AttestationVersion" AWSSEVSNPDoc.Fields[1].Type = ""
AWSSEVSNPDoc.Fields[1].Note = "" AWSSEVSNPDoc.Fields[1].Note = ""
AWSSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version." AWSSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
AWSSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version." AWSSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
AWSSEVSNPDoc.Fields[2].Name = "teeVersion" AWSSEVSNPDoc.Fields[2].Name = "teeVersion"
AWSSEVSNPDoc.Fields[2].Type = "AttestationVersion" AWSSEVSNPDoc.Fields[2].Type = ""
AWSSEVSNPDoc.Fields[2].Note = "" AWSSEVSNPDoc.Fields[2].Note = ""
AWSSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version." AWSSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
AWSSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version." AWSSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
AWSSEVSNPDoc.Fields[3].Name = "snpVersion" AWSSEVSNPDoc.Fields[3].Name = "snpVersion"
AWSSEVSNPDoc.Fields[3].Type = "AttestationVersion" AWSSEVSNPDoc.Fields[3].Type = ""
AWSSEVSNPDoc.Fields[3].Note = "" AWSSEVSNPDoc.Fields[3].Note = ""
AWSSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version." AWSSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
AWSSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version." AWSSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
AWSSEVSNPDoc.Fields[4].Name = "microcodeVersion" AWSSEVSNPDoc.Fields[4].Name = "microcodeVersion"
AWSSEVSNPDoc.Fields[4].Type = "AttestationVersion" AWSSEVSNPDoc.Fields[4].Type = ""
AWSSEVSNPDoc.Fields[4].Note = "" AWSSEVSNPDoc.Fields[4].Note = ""
AWSSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version." AWSSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
AWSSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "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].Description = "Expected TPM measurements."
AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion" AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
AzureSEVSNPDoc.Fields[1].Type = "AttestationVersion" AzureSEVSNPDoc.Fields[1].Type = ""
AzureSEVSNPDoc.Fields[1].Note = "" AzureSEVSNPDoc.Fields[1].Note = ""
AzureSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version." AzureSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
AzureSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version." AzureSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
AzureSEVSNPDoc.Fields[2].Name = "teeVersion" AzureSEVSNPDoc.Fields[2].Name = "teeVersion"
AzureSEVSNPDoc.Fields[2].Type = "AttestationVersion" AzureSEVSNPDoc.Fields[2].Type = ""
AzureSEVSNPDoc.Fields[2].Note = "" AzureSEVSNPDoc.Fields[2].Note = ""
AzureSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version." AzureSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
AzureSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version." AzureSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
AzureSEVSNPDoc.Fields[3].Name = "snpVersion" AzureSEVSNPDoc.Fields[3].Name = "snpVersion"
AzureSEVSNPDoc.Fields[3].Type = "AttestationVersion" AzureSEVSNPDoc.Fields[3].Type = ""
AzureSEVSNPDoc.Fields[3].Note = "" AzureSEVSNPDoc.Fields[3].Note = ""
AzureSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version." AzureSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
AzureSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version." AzureSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
AzureSEVSNPDoc.Fields[4].Name = "microcodeVersion" AzureSEVSNPDoc.Fields[4].Name = "microcodeVersion"
AzureSEVSNPDoc.Fields[4].Type = "AttestationVersion" AzureSEVSNPDoc.Fields[4].Type = ""
AzureSEVSNPDoc.Fields[4].Note = "" AzureSEVSNPDoc.Fields[4].Note = ""
AzureSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version." AzureSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
AzureSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "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].Description = "Expected TPM measurements."
AzureTDXDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements." AzureTDXDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
AzureTDXDoc.Fields[1].Name = "qeSVN" AzureTDXDoc.Fields[1].Name = "qeSVN"
AzureTDXDoc.Fields[1].Type = "uint16" AzureTDXDoc.Fields[1].Type = ""
AzureTDXDoc.Fields[1].Note = "" AzureTDXDoc.Fields[1].Note = ""
AzureTDXDoc.Fields[1].Description = "Minimum required QE security version number (SVN)." 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[1].Comments[encoder.LineComment] = "Minimum required QE security version number (SVN)."
AzureTDXDoc.Fields[2].Name = "pceSVN" AzureTDXDoc.Fields[2].Name = "pceSVN"
AzureTDXDoc.Fields[2].Type = "uint16" AzureTDXDoc.Fields[2].Type = ""
AzureTDXDoc.Fields[2].Note = "" AzureTDXDoc.Fields[2].Note = ""
AzureTDXDoc.Fields[2].Description = "Minimum required PCE security version number (SVN)." 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[2].Comments[encoder.LineComment] = "Minimum required PCE security version number (SVN)."
AzureTDXDoc.Fields[3].Name = "teeTCBSVN" AzureTDXDoc.Fields[3].Name = "teeTCBSVN"
AzureTDXDoc.Fields[3].Type = "HexBytes" AzureTDXDoc.Fields[3].Type = ""
AzureTDXDoc.Fields[3].Note = "" 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].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[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].Name = "qeVendorID"
AzureTDXDoc.Fields[4].Type = "HexBytes" AzureTDXDoc.Fields[4].Type = ""
AzureTDXDoc.Fields[4].Note = "" AzureTDXDoc.Fields[4].Note = ""
AzureTDXDoc.Fields[4].Description = "Expected 16 byte hex-encoded QE_VENDOR_ID field." 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." 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].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[5].Comments[encoder.LineComment] = "Expected 48 byte hex-encoded MR_SEAM value."
AzureTDXDoc.Fields[6].Name = "xfam" AzureTDXDoc.Fields[6].Name = "xfam"
AzureTDXDoc.Fields[6].Type = "HexBytes" AzureTDXDoc.Fields[6].Type = ""
AzureTDXDoc.Fields[6].Note = "" AzureTDXDoc.Fields[6].Note = ""
AzureTDXDoc.Fields[6].Description = "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 XFAM field." 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].Name = "intelRootKey"
AzureTDXDoc.Fields[7].Type = "Certificate" AzureTDXDoc.Fields[7].Type = "Certificate"
AzureTDXDoc.Fields[7].Note = "" AzureTDXDoc.Fields[7].Note = ""

View File

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

View File

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

View File

@ -138,7 +138,7 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation
} }
// convertToTfAttestationCfg converts the constellation attestation config to the related terraform structs. // 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{ tfAttestation = attestationAttribute{
Variant: attVar.String(), Variant: attVar.String(),
} }