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."
inputs:
csp:
description: "Cloud provider to run tests against"
default: "azure"
attestationVariant:
description: "attestation variant to run tests against"
default: "azure-sev-snp"
cosignPrivateKey:
description: "Cosign private key"
required: true
@ -30,4 +30,4 @@ runs:
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
run: |
bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.csp }}
bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.attestationVariant }}

View File

@ -68,9 +68,9 @@ runs:
case "${{ inputs.attestationVariant }}"
in
"azure-sev-snp"|"aws-sev-snp"|"gcp-sev-snp")
"azure-sev-snp"|"azure-tdx"|"aws-sev-snp"|"gcp-sev-snp")
echo "Extracting TCB versions for API update"
constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "snp-report-${node}.json"
constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "attestation-report-${node}.json"
;;
*)
constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090
@ -88,22 +88,19 @@ runs:
aws-region: eu-central-1
- name: Upload extracted TCBs
if: github.ref_name == 'main' && (inputs.attestationVariant == 'azure-sev-snp' || inputs.attestationVariant == 'aws-sev-snp' || inputs.attestationVariant == 'gcp-sev-snp')
if: github.ref_name == 'main' && (inputs.attestationVariant == 'azure-sev-snp' || inputs.attestationVariant == 'azure-tdx' || inputs.attestationVariant == 'aws-sev-snp' || inputs.attestationVariant == 'gcp-sev-snp')
shell: bash
env:
COSIGN_PASSWORD: ${{ inputs.cosignPassword }}
COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }}
run: |
reports=(snp-report-*.json)
reports=(attestation-report-*.json)
if [ -z ${#reports[@]} ]; then
exit 1
fi
attestationVariant=${{ inputs.attestationVariant }}
cloudProvider=${attestationVariant%%-*}
for file in "${reports[@]}"; do
path=$(realpath "${file}")
cat "${path}"
bazel run //internal/api/attestationconfigapi/cli -- upload "${cloudProvider}" snp-report "${path}"
bazel run //internal/api/attestationconfigapi/cli -- upload ${{ inputs.attestationVariant }} attestation-report "${path}"
done

View File

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

View File

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

View File

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

View File

@ -22,7 +22,6 @@ go_library(
"//internal/api/attestationconfigapi/cli/client",
"//internal/api/fetcher",
"//internal/attestation/variant",
"//internal/cloud/cloudprovider",
"//internal/constants",
"//internal/file",
"//internal/logger",
@ -31,6 +30,7 @@ go_library(
"@com_github_aws_aws_sdk_go_v2//aws",
"@com_github_aws_aws_sdk_go_v2_service_s3//:s3",
"@com_github_aws_aws_sdk_go_v2_service_s3//types",
"@com_github_google_go_tdx_guest//proto/tdx",
"@com_github_spf13_afero//:afero",
"@com_github_spf13_cobra//:cobra",
],

View File

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

View File

@ -15,8 +15,11 @@ import (
"errors"
"fmt"
"log/slog"
"time"
"path"
"strings"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go/aws"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
apiclient "github.com/edgelesssys/constellation/v2/internal/api/client"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
@ -35,6 +38,8 @@ type Client struct {
bucketID string
signer sigstore.Signer
cacheWindowSize int
log *slog.Logger
}
// New returns a new Client.
@ -50,50 +55,34 @@ func New(ctx context.Context, cfg staticupload.Config, cosignPwd, privateKey []b
signer: sigstore.NewSigner(cosignPwd, privateKey),
bucketID: cfg.Bucket,
cacheWindowSize: versionWindowSize,
log: log,
}
return repo, clientClose, nil
}
// uploadSEVSNPVersion uploads the latest version numbers of the SEVSNP. Then version name is the UTC timestamp of the date. The /list entry stores the version name + .json suffix.
func (a Client) uploadSEVSNPVersion(ctx context.Context, attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, date time.Time) error {
versions, err := a.List(ctx, attestation)
if err != nil {
return fmt.Errorf("fetch version list: %w", err)
}
ops := a.constructUploadCmd(attestation, version, versions, date)
return executeAllCmds(ctx, a.s3Client, ops)
}
// DeleteSEVSNPVersion deletes the given version (without .json suffix) from the API.
func (a Client) DeleteSEVSNPVersion(ctx context.Context, attestation variant.Variant, versionStr string) error {
versions, err := a.List(ctx, attestation)
// DeleteVersion deletes the given version (without .json suffix) from the API.
func (c Client) DeleteVersion(ctx context.Context, attestation variant.Variant, versionStr string) error {
versions, err := c.List(ctx, attestation)
if err != nil {
return fmt.Errorf("fetch version list: %w", err)
}
ops, err := a.deleteSEVSNPVersion(versions, versionStr)
ops, err := c.deleteVersion(versions, versionStr)
if err != nil {
return err
}
return executeAllCmds(ctx, a.s3Client, ops)
return executeAllCmds(ctx, c.s3Client, ops)
}
// List returns the list of versions for the given attestation variant.
func (a Client) List(ctx context.Context, attestation variant.Variant) (attestationconfigapi.VersionList, error) {
if !attestation.Equal(variant.AzureSEVSNP{}) &&
!attestation.Equal(variant.AWSSEVSNP{}) &&
!attestation.Equal(variant.GCPSEVSNP{}) {
return attestationconfigapi.VersionList{}, fmt.Errorf("unsupported attestation variant: %s", attestation)
}
versions, err := apiclient.Fetch(ctx, a.s3Client, attestationconfigapi.VersionList{Variant: attestation})
func (c Client) List(ctx context.Context, attestation variant.Variant) (attestationconfigapi.List, error) {
versions, err := apiclient.Fetch(ctx, c.s3Client, attestationconfigapi.List{Variant: attestation})
if err != nil {
var notFoundErr *apiclient.NotFoundError
if errors.As(err, &notFoundErr) {
return attestationconfigapi.VersionList{Variant: attestation}, nil
return attestationconfigapi.List{Variant: attestation}, nil
}
return attestationconfigapi.VersionList{}, err
return attestationconfigapi.List{}, err
}
versions.Variant = attestation
@ -101,10 +90,10 @@ func (a Client) List(ctx context.Context, attestation variant.Variant) (attestat
return versions, nil
}
func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.VersionList, versionStr string) (ops []crudCmd, err error) {
func (c Client) deleteVersion(versions attestationconfigapi.List, versionStr string) (ops []crudCmd, err error) {
versionStr = versionStr + ".json"
ops = append(ops, deleteCmd{
apiObject: attestationconfigapi.VersionAPIEntry{
apiObject: attestationconfigapi.Entry{
Variant: versions.Variant,
Version: versionStr,
},
@ -116,47 +105,46 @@ func (a Client) deleteSEVSNPVersion(versions attestationconfigapi.VersionList, v
}
ops = append(ops, putCmd{
apiObject: removedVersions,
signer: a.signer,
signer: c.signer,
})
return ops, nil
}
func (a Client) constructUploadCmd(attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, versionNames attestationconfigapi.VersionList, date time.Time) []crudCmd {
if !attestation.Equal(versionNames.Variant) {
return nil
func (c Client) listCachedVersions(ctx context.Context, attestation variant.Variant) ([]string, error) {
list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(c.bucketID),
Prefix: aws.String(reportVersionDir(attestation)),
})
if err != nil {
return nil, fmt.Errorf("list objects: %w", err)
}
dateStr := date.Format(VersionFormat) + ".json"
var res []crudCmd
var dates []string
for _, obj := range list.Contents {
fileName := path.Base(*obj.Key)
res = append(res, putCmd{
apiObject: attestationconfigapi.VersionAPIEntry{Version: dateStr, Variant: attestation, SEVSNPVersion: version},
signer: a.signer,
})
versionNames.AddVersion(dateStr)
res = append(res, putCmd{
apiObject: versionNames,
signer: a.signer,
})
return res
// The cache contains signature and json files
// We only want the json files
if date, ok := strings.CutSuffix(fileName, ".json"); ok {
dates = append(dates, date)
}
}
return dates, nil
}
func removeVersion(list attestationconfigapi.VersionList, versionStr string) (removedVersions attestationconfigapi.VersionList, err error) {
func removeVersion(list attestationconfigapi.List, versionStr string) (removedVersions attestationconfigapi.List, err error) {
versions := list.List
for i, v := range versions {
if v == versionStr {
if i == len(versions)-1 {
removedVersions = attestationconfigapi.VersionList{List: versions[:i], Variant: list.Variant}
removedVersions = attestationconfigapi.List{List: versions[:i], Variant: list.Variant}
} else {
removedVersions = attestationconfigapi.VersionList{List: append(versions[:i], versions[i+1:]...), Variant: list.Variant}
removedVersions = attestationconfigapi.List{List: append(versions[:i], versions[i+1:]...), Variant: list.Variant}
}
return removedVersions, nil
}
}
return attestationconfigapi.VersionList{}, fmt.Errorf("version %s not found in list %v", versionStr, versions)
return attestationconfigapi.List{}, fmt.Errorf("version %s not found in list %v", versionStr, versions)
}
type crudCmd interface {

View File

@ -7,60 +7,28 @@ package client
import (
"testing"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/stretchr/testify/assert"
)
func TestUploadAzureSEVSNP(t *testing.T) {
sut := Client{
bucketID: "bucket",
signer: fakeSigner{},
}
version := attestationconfigapi.SEVSNPVersion{}
date := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC)
ops := sut.constructUploadCmd(variant.AzureSEVSNP{}, version, attestationconfigapi.VersionList{List: []string{"2021-01-01-01-01.json", "2019-01-01-01-01.json"}, Variant: variant.AzureSEVSNP{}}, date)
dateStr := "2023-01-01-01-01.json"
assert := assert.New(t)
assert.Contains(ops, putCmd{
apiObject: attestationconfigapi.VersionAPIEntry{
Variant: variant.AzureSEVSNP{},
Version: dateStr,
SEVSNPVersion: version,
},
signer: fakeSigner{},
})
assert.Contains(ops, putCmd{
apiObject: attestationconfigapi.VersionList{Variant: variant.AzureSEVSNP{}, List: []string{"2023-01-01-01-01.json", "2021-01-01-01-01.json", "2019-01-01-01-01.json"}},
signer: fakeSigner{},
})
}
func TestDeleteAzureSEVSNPVersions(t *testing.T) {
sut := Client{
bucketID: "bucket",
}
versions := attestationconfigapi.VersionList{List: []string{"2023-01-01.json", "2021-01-01.json", "2019-01-01.json"}}
versions := attestationconfigapi.List{List: []string{"2023-01-01.json", "2021-01-01.json", "2019-01-01.json"}}
ops, err := sut.deleteSEVSNPVersion(versions, "2021-01-01")
ops, err := sut.deleteVersion(versions, "2021-01-01")
assert := assert.New(t)
assert.NoError(err)
assert.Contains(ops, deleteCmd{
apiObject: attestationconfigapi.VersionAPIEntry{
apiObject: attestationconfigapi.Entry{
Version: "2021-01-01.json",
},
})
assert.Contains(ops, putCmd{
apiObject: attestationconfigapi.VersionList{List: []string{"2023-01-01.json", "2019-01-01.json"}},
apiObject: attestationconfigapi.List{List: []string{"2023-01-01.json", "2019-01-01.json"}},
})
}
type fakeSigner struct{}
func (fakeSigner) Sign(_ []byte) ([]byte, error) {
return []byte("signature"), nil
}

View File

@ -8,6 +8,7 @@ package client
import (
"context"
"encoding/json"
"errors"
"fmt"
"path"
@ -15,9 +16,6 @@ import (
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go/aws"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/client"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
@ -33,153 +31,335 @@ func reportVersionDir(attestation variant.Variant) string {
return path.Join(attestationconfigapi.AttestationURLPath, attestation.String(), cachedVersionsSubDir)
}
// UploadSEVSNPVersionLatest saves the given version to the cache, determines the smallest
// UploadLatestVersion saves the given version to the cache, determines the smallest
// TCB version in the cache among the last cacheWindowSize versions and updates
// the latest version in the API if there is an update.
// force can be used to bypass the validation logic against the cached versions.
func (c Client) UploadSEVSNPVersionLatest(ctx context.Context, attestation variant.Variant, inputVersion,
latestAPIVersion attestationconfigapi.SEVSNPVersion, now time.Time, force bool,
func (c Client) UploadLatestVersion(
ctx context.Context, attestationVariant variant.Variant,
inputVersion, latestVersionInAPI any,
now time.Time, force bool,
) error {
if err := c.cacheSEVSNPVersion(ctx, attestation, inputVersion, now); err != nil {
return fmt.Errorf("reporting version: %w", err)
// Validate input versions against configured attestation variant
// This allows us to skip these checks in the individual variant implementations
var err error
actionForVariant(attestationVariant,
func() {
if _, ok := inputVersion.(attestationconfigapi.TDXVersion); !ok {
err = fmt.Errorf("input version %q is not a TDX version", inputVersion)
}
if force {
return c.uploadSEVSNPVersion(ctx, attestation, inputVersion, now)
if _, ok := latestVersionInAPI.(attestationconfigapi.TDXVersion); !ok {
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 {
return fmt.Errorf("list reported versions: %w", err)
return err
}
if err := c.addVersionToCache(ctx, attestationVariant, inputVersion, now); err != nil {
return fmt.Errorf("adding version to cache: %w", err)
}
// If force is set, immediately update the latest version to the new version in the API.
if force {
return c.uploadAsLatestVersion(ctx, attestationVariant, inputVersion, now)
}
// Otherwise, check the cached versions and update the latest version in the API if necessary.
versionDates, err := c.listCachedVersions(ctx, attestationVariant)
if err != nil {
return fmt.Errorf("listing existing cached versions: %w", err)
}
if len(versionDates) < c.cacheWindowSize {
c.s3Client.Logger.Warn(fmt.Sprintf("Skipping version update, found %d, expected %d reported versions.", len(versionDates), c.cacheWindowSize))
c.log.Warn(fmt.Sprintf("Skipping version update, found %d, expected %d reported versions.", len(versionDates), c.cacheWindowSize))
return nil
}
minVersion, minDate, err := c.findMinVersion(ctx, attestation, versionDates)
minVersion, minDate, err := c.findMinVersion(ctx, attestationVariant, versionDates)
if err != nil {
return fmt.Errorf("get minimal version: %w", err)
return fmt.Errorf("determining minimal version in cache: %w", err)
}
c.s3Client.Logger.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate))
shouldUpdateAPI, err := isInputNewerThanOtherVersion(minVersion, latestAPIVersion)
if err != nil {
c.log.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate))
if !isInputNewerThanOtherVersion(attestationVariant, minVersion, latestVersionInAPI) {
c.log.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v. Skipping list update", minVersion, latestVersionInAPI))
return ErrNoNewerVersion
}
if !shouldUpdateAPI {
c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v", minVersion, latestAPIVersion))
return nil
}
c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is newer than latest API version: %+v", minVersion, latestAPIVersion))
c.log.Info(fmt.Sprintf("Input version: %+v is newer than latest API version: %+v", minVersion, latestVersionInAPI))
t, err := time.Parse(VersionFormat, minDate)
if err != nil {
return fmt.Errorf("parsing date: %w", err)
}
if err := c.uploadSEVSNPVersion(ctx, attestation, minVersion, t); err != nil {
return fmt.Errorf("uploading version: %w", err)
if err := c.uploadAsLatestVersion(ctx, attestationVariant, minVersion, t); err != nil {
return fmt.Errorf("uploading as latest version: %w", err)
}
c.s3Client.Logger.Info(fmt.Sprintf("Successfully uploaded new SEV-SNP version: %+v", minVersion))
c.log.Info(fmt.Sprintf("Successfully uploaded new %s version: %+v", attestationVariant, minVersion))
return nil
}
// uploadAsLatestVersion uploads the given version and updates the list to set it as the "latest" version.
// The version's name is the UTC timestamp of the date.
// The /list entry stores the version name + .json suffix.
func (c Client) uploadAsLatestVersion(ctx context.Context, variant variant.Variant, inputVersion any, date time.Time) error {
versions, err := c.List(ctx, variant)
if err != nil {
return fmt.Errorf("fetch version list: %w", err)
}
if !variant.Equal(versions.Variant) {
return nil
}
// cacheSEVSNPVersion uploads the latest observed version numbers of the SEVSNP. This version is used to later report the latest version numbers to the API.
func (c Client) cacheSEVSNPVersion(ctx context.Context, attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, date time.Time) error {
dateStr := date.Format(VersionFormat) + ".json"
res := putCmd{
apiObject: reportedSEVSNPVersionAPI{Version: dateStr, variant: attestation, SEVSNPVersion: version},
var ops []crudCmd
obj := apiVersionObject{version: dateStr, variant: variant, cached: false}
obj.setVersion(inputVersion)
ops = append(ops, putCmd{
apiObject: obj,
signer: c.signer,
})
versions.AddVersion(dateStr)
ops = append(ops, putCmd{
apiObject: versions,
signer: c.signer,
})
return executeAllCmds(ctx, c.s3Client, ops)
}
// addVersionToCache adds the given version to the cache.
func (c Client) addVersionToCache(ctx context.Context, variant variant.Variant, inputVersion any, date time.Time) error {
dateStr := date.Format(VersionFormat) + ".json"
obj := apiVersionObject{version: dateStr, variant: variant, cached: true}
obj.setVersion(inputVersion)
cmd := putCmd{
apiObject: obj,
signer: c.signer,
}
return res.Execute(ctx, c.s3Client)
return cmd.Execute(ctx, c.s3Client)
}
func (c Client) listCachedVersions(ctx context.Context, attestation variant.Variant) ([]string, error) {
list, err := c.s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(c.bucketID),
Prefix: aws.String(reportVersionDir(attestation)),
})
if err != nil {
return nil, fmt.Errorf("list objects: %w", err)
// findMinVersion returns the minimal version in the cache among the last cacheWindowSize versions.
func (c Client) findMinVersion(
ctx context.Context, attestationVariant variant.Variant, versionDates []string,
) (any, string, error) {
var getMinimalVersion func() (any, string, error)
actionForVariant(attestationVariant,
func() {
getMinimalVersion = func() (any, string, error) {
return findMinimalVersion[attestationconfigapi.TDXVersion](ctx, attestationVariant, versionDates, c.s3Client, c.cacheWindowSize)
}
var dates []string
for _, obj := range list.Contents {
fileName := path.Base(*obj.Key)
if strings.HasSuffix(fileName, ".json") {
dates = append(dates, fileName[:len(fileName)-5])
},
func() {
getMinimalVersion = func() (any, string, error) {
return findMinimalVersion[attestationconfigapi.SEVSNPVersion](ctx, attestationVariant, versionDates, c.s3Client, c.cacheWindowSize)
}
}
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 (c Client) findMinVersion(ctx context.Context, attesation variant.Variant, versionDates []string) (attestationconfigapi.SEVSNPVersion, string, error) {
var minimalVersion *attestationconfigapi.SEVSNPVersion
func findMinimalVersion[T attestationconfigapi.TDXVersion | attestationconfigapi.SEVSNPVersion](
ctx context.Context, variant variant.Variant, versionDates []string,
s3Client *client.Client, cacheWindowSize int,
) (T, string, error) {
var minimalVersion *T
var minimalDate string
sort.Sort(sort.Reverse(sort.StringSlice(versionDates))) // sort in reverse order to slice the latest versions
versionDates = versionDates[:c.cacheWindowSize]
versionDates = versionDates[:cacheWindowSize]
sort.Strings(versionDates) // sort with oldest first to to take the minimal version with the oldest date
for _, date := range versionDates {
obj, err := client.Fetch(ctx, c.s3Client, reportedSEVSNPVersionAPI{Version: date + ".json", variant: attesation})
obj, err := client.Fetch(ctx, s3Client, apiVersionObject{version: date + ".json", variant: variant, cached: true})
if err != nil {
return attestationconfigapi.SEVSNPVersion{}, "", fmt.Errorf("get object: %w", err)
return *new(T), "", fmt.Errorf("get object: %w", err)
}
// Need to set this explicitly as the variant is not part of the marshalled JSON.
obj.variant = attesation
obj.variant = variant // variant is not set by Fetch, set it manually
if minimalVersion == nil {
minimalVersion = &obj.SEVSNPVersion
v := obj.getVersion().(T)
minimalVersion = &v
minimalDate = date
} else {
shouldUpdateMinimal, err := isInputNewerThanOtherVersion(*minimalVersion, obj.SEVSNPVersion)
if err != nil {
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
}
}
}
return *minimalVersion, minimalDate, nil
}
// isInputNewerThanOtherVersion compares all version fields and returns true if any input field is newer.
func isInputNewerThanOtherVersion(input, other attestationconfigapi.SEVSNPVersion) (bool, error) {
if input == other {
return false, nil
}
if input.TEE < other.TEE {
return false, fmt.Errorf("input TEE version: %d is older than latest API version: %d", input.TEE, other.TEE)
}
if input.SNP < other.SNP {
return false, fmt.Errorf("input SNP version: %d is older than latest API version: %d", input.SNP, other.SNP)
}
if input.Microcode < other.Microcode {
return false, fmt.Errorf("input Microcode version: %d is older than latest API version: %d", input.Microcode, other.Microcode)
}
if input.Bootloader < other.Bootloader {
return false, fmt.Errorf("input Bootloader version: %d is older than latest API version: %d", input.Bootloader, other.Bootloader)
}
return true, nil
func isInputNewerThanOtherVersion(variant variant.Variant, inputVersion, otherVersion any) bool {
var result bool
actionForVariant(variant,
func() {
input := inputVersion.(attestationconfigapi.TDXVersion)
other := otherVersion.(attestationconfigapi.TDXVersion)
result = isInputNewerThanOtherTDXVersion(input, other)
},
func() {
input := inputVersion.(attestationconfigapi.SEVSNPVersion)
other := otherVersion.(attestationconfigapi.SEVSNPVersion)
result = isInputNewerThanOtherSEVSNPVersion(input, other)
},
)
return result
}
// reportedSEVSNPVersionAPI is the request to get the version information of the specific version in the config api.
type reportedSEVSNPVersionAPI struct {
Version string `json:"-"`
type apiVersionObject struct {
version string `json:"-"`
variant variant.Variant `json:"-"`
attestationconfigapi.SEVSNPVersion
cached bool `json:"-"`
snp attestationconfigapi.SEVSNPVersion
tdx attestationconfigapi.TDXVersion
}
func (a apiVersionObject) MarshalJSON() ([]byte, error) {
var res []byte
var err error
actionForVariant(a.variant,
func() {
res, err = json.Marshal(a.tdx)
},
func() {
res, err = json.Marshal(a.snp)
},
)
return res, err
}
func (a *apiVersionObject) UnmarshalJSON(data []byte) error {
errTDX := json.Unmarshal(data, &a.tdx)
errSNP := json.Unmarshal(data, &a.snp)
if errTDX == nil || errSNP == nil {
return nil
}
return fmt.Errorf("trying to unmarshal data into both TDX and SNP versions: %w", errors.Join(errTDX, errSNP))
}
// JSONPath returns the path to the JSON file for the request to the config api.
func (i reportedSEVSNPVersionAPI) JSONPath() string {
return path.Join(reportVersionDir(i.variant), i.Version)
// This is the path to the cached version in the S3 bucket.
func (a apiVersionObject) JSONPath() string {
if a.cached {
return path.Join(reportVersionDir(a.variant), a.version)
}
return path.Join(attestationconfigapi.AttestationURLPath, a.variant.String(), a.version)
}
// ValidateRequest validates the request.
func (i reportedSEVSNPVersionAPI) ValidateRequest() error {
if !strings.HasSuffix(i.Version, ".json") {
func (a apiVersionObject) ValidateRequest() error {
if !strings.HasSuffix(a.version, ".json") {
return fmt.Errorf("version has no .json suffix")
}
return nil
}
// Validate is a No-Op at the moment.
func (i reportedSEVSNPVersionAPI) Validate() error {
// Validate is a No-Op.
func (a apiVersionObject) Validate() error {
return nil
}
// getVersion returns the version.
func (a apiVersionObject) getVersion() any {
var res any
actionForVariant(a.variant,
func() {
res = a.tdx
},
func() {
res = a.snp
},
)
return res
}
// setVersion sets the version.
func (a *apiVersionObject) setVersion(version any) {
actionForVariant(a.variant,
func() {
a.tdx = version.(attestationconfigapi.TDXVersion)
},
func() {
a.snp = version.(attestationconfigapi.SEVSNPVersion)
},
)
}
// actionForVariant performs the given action based on the whether variant is a TDX or SEV-SNP variant.
func actionForVariant(
attestationVariant variant.Variant,
tdxAction func(), snpAction func(),
) {
switch attestationVariant {
case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.GCPSEVSNP{}:
snpAction()
case variant.AzureTDX{}:
tdxAction()
default:
panic(fmt.Sprintf("unsupported attestation variant: %s", attestationVariant))
}
}
// isInputNewerThanOtherSEVSNPVersion compares all version fields and returns false if any input field is older, or the versions are equal.
func isInputNewerThanOtherSEVSNPVersion(input, other attestationconfigapi.SEVSNPVersion) bool {
if input == other {
return false
}
if input.TEE < other.TEE {
return false
}
if input.SNP < other.SNP {
return false
}
if input.Microcode < other.Microcode {
return false
}
if input.Bootloader < other.Bootloader {
return false
}
return true
}
// isInputNewerThanOtherSEVSNPVersion compares all version fields and returns false if any input field is older, or the versions are equal.
func isInputNewerThanOtherTDXVersion(input, other attestationconfigapi.TDXVersion) bool {
if input == other {
return false
}
if input.PCESVN < other.PCESVN {
return false
}
if input.QESVN < other.QESVN {
return false
}
// Validate component-wise security version numbers
for idx, inputVersion := range input.TEETCBSVN {
if inputVersion < other.TEETCBSVN[idx] {
return false
}
}
return true
}

View File

@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestIsInputNewerThanLatestAPI(t *testing.T) {
func TestIsInputNewerThanOtherSEVSNPVersion(t *testing.T) {
newTestCfg := func() attestationconfigapi.SEVSNPVersion {
return attestationconfigapi.SEVSNPVersion{
Microcode: 93,
@ -25,7 +25,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
latest attestationconfigapi.SEVSNPVersion
input attestationconfigapi.SEVSNPVersion
expect bool
errMsg string
}{
"input is older than latest": {
input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion {
@ -34,7 +33,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
}(newTestCfg()),
latest: newTestCfg(),
expect: false,
errMsg: "input Microcode version: 92 is older than latest API version: 93",
},
"input has greater and smaller version field than latest": {
input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion {
@ -44,7 +42,6 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
}(newTestCfg()),
latest: newTestCfg(),
expect: false,
errMsg: "input Bootloader version: 1 is older than latest API version: 2",
},
"input is newer than latest": {
input: func(c attestationconfigapi.SEVSNPVersion) attestationconfigapi.SEVSNPVersion {
@ -62,14 +59,80 @@ func TestIsInputNewerThanLatestAPI(t *testing.T) {
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
isNewer, err := isInputNewerThanOtherVersion(tc.input, tc.latest)
assert := assert.New(t)
if tc.errMsg != "" {
assert.EqualError(err, tc.errMsg)
} else {
assert.NoError(err)
assert.Equal(tc.expect, isNewer)
}
isNewer := isInputNewerThanOtherSEVSNPVersion(tc.input, tc.latest)
assert.Equal(t, tc.expect, isNewer)
})
}
}
func TestIsInputNewerThanOtherTDXVersion(t *testing.T) {
newTestVersion := func() attestationconfigapi.TDXVersion {
return attestationconfigapi.TDXVersion{
QESVN: 1,
PCESVN: 2,
TEETCBSVN: [16]byte{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
QEVendorID: [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
XFAM: [8]byte{0, 1, 2, 3, 4, 5, 6, 7},
}
}
testCases := map[string]struct {
latest attestationconfigapi.TDXVersion
input attestationconfigapi.TDXVersion
expect bool
}{
"input is older than latest": {
input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion {
c.QESVN--
return c
}(newTestVersion()),
latest: newTestVersion(),
expect: false,
},
"input has greater and smaller version field than latest": {
input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion {
c.QESVN++
c.PCESVN--
return c
}(newTestVersion()),
latest: newTestVersion(),
expect: false,
},
"input is newer than latest": {
input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion {
c.QESVN++
return c
}(newTestVersion()),
latest: newTestVersion(),
expect: true,
},
"input is equal to latest": {
input: newTestVersion(),
latest: newTestVersion(),
expect: false,
},
"tee tcb svn is newer": {
input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion {
c.TEETCBSVN[4]++
return c
}(newTestVersion()),
latest: newTestVersion(),
expect: true,
},
"xfam is different": {
input: func(c attestationconfigapi.TDXVersion) attestationconfigapi.TDXVersion {
c.XFAM[3]++
return c
}(newTestVersion()),
latest: newTestVersion(),
expect: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
isNewer := isInputNewerThanOtherTDXVersion(tc.input, tc.latest)
assert.Equal(t, tc.expect, isNewer)
})
}
}

View File

@ -18,7 +18,6 @@ import (
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/spf13/cobra"
@ -27,21 +26,21 @@ import (
// newDeleteCmd creates the delete command.
func newDeleteCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "delete {aws|azure|gcp} {snp-report|guest-firmware} <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",
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",
Args: cobra.MatchAll(cobra.ExactArgs(3), isCloudProvider(0), isValidKind(1)),
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete azure-sev-snp attestation-report 1.0.0",
Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)),
PreRunE: envCheck,
RunE: runDelete,
}
recursivelyCmd := &cobra.Command{
Use: "recursive {aws|azure|gcp}",
Use: "recursive {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp}",
Short: "delete all objects from the API path constellation/v1/attestation/<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",
Args: cobra.MatchAll(cobra.ExactArgs(1), isCloudProvider(0)),
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure-sev-snp",
Args: cobra.MatchAll(cobra.ExactArgs(1), isAttestationVariant(0)),
RunE: runRecursiveDelete,
}
@ -75,16 +74,7 @@ func runDelete(cmd *cobra.Command, args []string) (retErr error) {
}
}()
switch deleteCfg.provider {
case cloudprovider.AWS:
return deleteEntry(cmd.Context(), variant.AWSSEVSNP{}, client, deleteCfg)
case cloudprovider.Azure:
return deleteEntry(cmd.Context(), variant.AzureSEVSNP{}, client, deleteCfg)
case cloudprovider.GCP:
return deleteEntry(cmd.Context(), variant.GCPSEVSNP{}, client, deleteCfg)
default:
return fmt.Errorf("unsupported cloud provider: %s", deleteCfg.provider)
}
return deleteEntry(cmd.Context(), client, deleteCfg)
}
func runRecursiveDelete(cmd *cobra.Command, args []string) (retErr error) {
@ -112,23 +102,13 @@ func runRecursiveDelete(cmd *cobra.Command, args []string) (retErr error) {
}
}()
var deletePath string
switch deleteCfg.provider {
case cloudprovider.AWS:
deletePath = path.Join(attestationconfigapi.AttestationURLPath, variant.AWSSEVSNP{}.String())
case cloudprovider.Azure:
deletePath = path.Join(attestationconfigapi.AttestationURLPath, variant.AzureSEVSNP{}.String())
case cloudprovider.GCP:
deletePath = path.Join(attestationconfigapi.AttestationURLPath, variant.GCPSEVSNP{}.String())
default:
return fmt.Errorf("unsupported cloud provider: %s", deleteCfg.provider)
}
deletePath := path.Join(attestationconfigapi.AttestationURLPath, deleteCfg.variant.String())
return deleteEntryRecursive(cmd.Context(), deletePath, client, deleteCfg)
}
type deleteConfig struct {
provider cloudprovider.Provider
variant variant.Variant
kind objectKind
version string
region string
@ -155,12 +135,15 @@ func newDeleteConfig(cmd *cobra.Command, args [3]string) (deleteConfig, error) {
}
apiCfg := getAPIEnvironment(testing)
provider := cloudprovider.FromString(args[0])
variant, err := variant.FromString(args[0])
if err != nil {
return deleteConfig{}, fmt.Errorf("invalid attestation variant: %q: %w", args[0], err)
}
kind := kindFromString(args[1])
version := args[2]
return deleteConfig{
provider: provider,
variant: variant,
kind: kind,
version: version,
region: region,
@ -171,12 +154,12 @@ func newDeleteConfig(cmd *cobra.Command, args [3]string) (deleteConfig, error) {
}, nil
}
func deleteEntry(ctx context.Context, attvar variant.Variant, client *client.Client, cfg deleteConfig) error {
if cfg.kind != snpReport {
func deleteEntry(ctx context.Context, client *client.Client, cfg deleteConfig) error {
if cfg.kind != attestationReport {
return fmt.Errorf("kind %s not supported", cfg.kind)
}
return client.DeleteSEVSNPVersion(ctx, attvar, cfg.version)
return client.DeleteVersion(ctx, cfg.variant, cfg.version)
}
func deleteEntryRecursive(ctx context.Context, path string, client *staticupload.Client, cfg deleteConfig) error {

View File

@ -19,95 +19,117 @@ configapi_cli=$(realpath @@CONFIGAPI_CLI@@)
stat "${configapi_cli}" >> /dev/null
configapi_cli="${configapi_cli} --testing"
###### script body ######
function variant() {
if [[ $1 == "aws" ]]; then
echo "aws-sev-snp"
return 0
elif [[ $1 == "azure" ]]; then
echo "azure-sev-snp"
return 0
elif [[ $1 == "gcp" ]]; then
echo "gcp-sev-snp"
return 0
else
echo "Unknown CSP: $1"
exit 1
fi
}
csp=$1
readonly csp
attestationType=$(variant "$csp")
attestationVariant=$1
readonly attestationVariant
readonly region="eu-west-1"
readonly bucket="resource-api-testing"
tmpdir=$(mktemp -d)
readonly tmpdir
registerExitHandler "rm -rf $tmpdir"
registerExitHandler "rm -rf ${tmpdir}"
# empty the bucket version state
${configapi_cli} delete recursive "$csp" --region "$region" --bucket "$bucket"
${configapi_cli} delete recursive "${attestationVariant}" --region "${region}" --bucket "${bucket}"
# the high version numbers ensure that it's newer than the current latest value
readonly current_report_path="$tmpdir/currentSnpReport.json"
cat << EOF > "$current_report_path"
readonly current_report_path="${tmpdir}/attestationReportCurrent.json"
readonly report_path="${tmpdir}/attestationReport.json"
readonly older_report_path="${tmpdir}/attestationReportOld.json"
if [[ ${attestationVariant} == *-tdx ]]; then
cat << EOF > "${current_report_path}"
{
"snp_report": {
"reported_tcb": {
"bootloader": 1,
"tee": 1,
"snp": 1,
"microcode": 1
"header": {
"qe_svn": "AAA=",
"pce_svn": "AAA=",
"qe_vendor_id": "KioqKioqKioqKioqKioqKg=="
},
"committed_tcb": {
"bootloader": 1,
"tee": 1,
"snp": 1,
"microcode": 1
},
"launch_tcb": {
"bootloader": 1,
"tee": 1,
"snp": 1,
"microcode": 1
}
"td_quote_body": {
"tee_tcb_svn": "AAAAAAAAAAAAAAAAAAAAAA==",
"xfam": "AAAAAAAAAAA="
}
}
EOF
# upload a fake latest version for the fetcher
${configapi_cli} upload "$csp" snp-report "$current_report_path" --force --upload-date "2000-01-01-01-01" --region "$region" --bucket "$bucket"
# the high version numbers ensure that it's newer than the current latest value
readonly report_path="$tmpdir/snpReport.json"
cat << EOF > "$report_path"
cat << EOF > "${report_path}"
{
"snp_report": {
"reported_tcb": {
"bootloader": 255,
"tee": 255,
"snp": 255,
"microcode": 255
"header": {
"qe_svn": "//8=",
"pce_svn": "//8=",
"qe_vendor_id": "KioqKioqKioqKioqKioqKg=="
},
"committed_tcb": {
"bootloader": 255,
"tee": 255,
"snp": 255,
"microcode": 255
},
"launch_tcb": {
"bootloader": 255,
"tee": 255,
"snp": 255,
"microcode": 255
}
"td_quote_body": {
"tee_tcb_svn": "/////////////////////w==",
"xfam": "AQIDBAUGBwg="
}
}
EOF
# has an older version
readonly older_report_path="$tmpdir/snpReportOld.json"
cat << EOF > "$older_report_path"
cat << EOF > "${older_report_path}"
{
"header": {
"qe_svn": "//8=",
"pce_svn": "/v8=",
"qe_vendor_id": "KioqKioqKioqKioqKioqKg=="
},
"td_quote_body": {
"tee_tcb_svn": "/////////////////////g==",
"xfam": "AQIDBAUGBwg="
}
}
EOF
elif [[ ${attestationVariant} == *-sev-snp ]]; then
cat << EOF > "${current_report_path}"
{
"snp_report": {
"reported_tcb": {
"bootloader": 1,
"tee": 1,
"snp": 1,
"microcode": 1
},
"committed_tcb": {
"bootloader": 1,
"tee": 1,
"snp": 1,
"microcode": 1
},
"launch_tcb": {
"bootloader": 1,
"tee": 1,
"snp": 1,
"microcode": 1
}
}
}
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": {
@ -131,22 +153,40 @@ cat << EOF > "$older_report_path"
}
}
EOF
else
echo "Unknown attestation variant: ${attestationVariant}"
exit 1
fi
# upload a fake latest version for the fetcher
${configapi_cli} upload "${attestationVariant}" attestation-report "${current_report_path}" --force --upload-date "2000-01-01-01-01" --region "${region}" --bucket "${bucket}"
# report 3 versions with different dates to fill the reporter cache
readonly date_oldest="2023-02-01-03-04"
${configapi_cli} upload "$csp" snp-report "$older_report_path" --upload-date "$date_oldest" --region "$region" --bucket "$bucket" --cache-window-size 3
${configapi_cli} upload "${attestationVariant}" attestation-report "${older_report_path}" --upload-date "${date_oldest}" --region "${region}" --bucket "${bucket}" --cache-window-size 3
readonly date_older="2023-02-02-03-04"
${configapi_cli} upload "$csp" snp-report "$older_report_path" --upload-date "$date_older" --region "$region" --bucket "$bucket" --cache-window-size 3
${configapi_cli} upload "${attestationVariant}" attestation-report "${older_report_path}" --upload-date "${date_older}" --region "${region}" --bucket "${bucket}" --cache-window-size 3
readonly date="2023-02-03-03-04"
${configapi_cli} upload "$csp" snp-report "$report_path" --upload-date "$date" --region "$region" --bucket "$bucket" --cache-window-size 3
${configapi_cli} upload "${attestationVariant}" attestation-report "${report_path}" --upload-date "${date}" --region "${region}" --bucket "${bucket}" --cache-window-size 3
# expect that $date_oldest is served as latest version
basepath="constellation/v1/attestation/${attestationType}"
basepath="constellation/v1/attestation/${attestationVariant}"
baseurl="https://d33dzgxuwsgbpw.cloudfront.net/${basepath}"
if ! curl -fsSL "${baseurl}"/${date_oldest}.json > version.json; then
if ! curl -fsSL "${baseurl}/${date_oldest}.json" > version.json; then
echo "Checking for uploaded version file ${basepath}/${date_oldest}.json: request returned ${?}"
exit 1
fi
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:"
@ -155,10 +195,13 @@ if ! cmp -s <(echo -n '{"bootloader":255,"tee":255,"snp":255,"microcode":254}')
echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}'
exit 1
fi
if ! curl -fsSL "${baseurl}"/${date_oldest}.json.sig > /dev/null; then
fi
if ! curl -fsSL "${baseurl}/${date_oldest}.json.sig" > /dev/null; then
echo "Checking for uploaded version signature file ${basepath}/${date_oldest}.json.sig: request returned ${?}"
exit 1
fi
# check list endpoint
if ! curl -fsSL "${baseurl}"/list > list.json; then
echo "Checking for uploaded list file ${basepath}/list: request returned ${?}"
@ -174,28 +217,28 @@ if ! cmp -s <(echo -n '["2023-02-01-03-04.json","2000-01-01-01-01.json"]') list.
fi
# check that the other versions are not uploaded
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_older}.json)
if [[ $http_code -ne 404 ]]; then
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_older}.json")
if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date_older}.json, but got ${http_code}"
exit 1
fi
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date}.json.sig)
if [[ $http_code -ne 404 ]]; then
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date}.json.sig")
if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date}.json, but got ${http_code}"
exit 1
fi
${configapi_cli} delete "$csp" snp-report "$date_oldest" --region "$region" --bucket "$bucket"
${configapi_cli} delete "${attestationVariant}" attestation-report "${date_oldest}" --region "${region}" --bucket "${bucket}"
# Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail.
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json)
if [[ $http_code -ne 404 ]]; then
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json")
if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}"
exit 1
fi
# Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail.
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json.sig)
if [[ $http_code -ne 404 ]]; then
http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json.sig")
if [[ ${http_code} -ne 404 ]]; then
echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}"
exit 1
fi

View File

@ -7,6 +7,7 @@ package main
import (
"context"
"encoding/binary"
"errors"
"fmt"
"log/slog"
@ -17,30 +18,30 @@ import (
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi/cli/client"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/edgelesssys/constellation/v2/internal/verify"
"github.com/google/go-tdx-guest/proto/tdx"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)
func newUploadCmd() *cobra.Command {
uploadCmd := &cobra.Command{
Use: "upload {aws|azure|gcp} {snp-report|guest-firmware} <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",
Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first."+
Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first.\n"+
"The CLI then determines the lowest version within the cache-window present in the cache and writes that value to the config api if necessary. "+
"For guest-firmware objects the object is added to the API directly. "+
"For guest-firmware objects the object is added to the API directly.\n"+
"Please authenticate with AWS through your preferred method (e.g. environment variables, CLI) "+
"to be able to upload to S3. Set the %s and %s environment variables to authenticate with cosign.",
envCosignPrivateKey, envCosignPwd,
),
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure snp-report /some/path/report.json",
Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure-sev-snp attestation-report /some/path/report.json",
Args: cobra.MatchAll(cobra.ExactArgs(3), isCloudProvider(0), isValidKind(1)),
Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)),
PreRunE: envCheck,
RunE: runUpload,
}
@ -91,42 +92,19 @@ func runUpload(cmd *cobra.Command, args []string) (retErr error) {
return fmt.Errorf("creating client: %w", err)
}
var attestation variant.Variant
switch uploadCfg.provider {
case cloudprovider.AWS:
attestation = variant.AWSSEVSNP{}
case cloudprovider.Azure:
attestation = variant.AzureSEVSNP{}
case cloudprovider.GCP:
attestation = variant.GCPSEVSNP{}
default:
return fmt.Errorf("unsupported cloud provider: %s", uploadCfg.provider)
return uploadReport(ctx, client, uploadCfg, file.NewHandler(afero.NewOsFs()), log)
}
return uploadReport(ctx, attestation, client, uploadCfg, file.NewHandler(afero.NewOsFs()), log)
}
func uploadReport(ctx context.Context,
attestation variant.Variant,
apiClient *client.Client,
cfg uploadConfig,
fs file.Handler,
log *slog.Logger,
func uploadReport(
ctx context.Context, apiClient *client.Client,
cfg uploadConfig, fs file.Handler, log *slog.Logger,
) error {
if cfg.kind != snpReport {
if cfg.kind != attestationReport {
return fmt.Errorf("kind %s not supported", cfg.kind)
}
log.Info(fmt.Sprintf("Reading SNP report from file: %s", cfg.path))
var report verify.Report
if err := fs.ReadJSON(cfg.path, &report); err != nil {
return fmt.Errorf("reading snp report: %w", err)
}
inputVersion := convertTCBVersionToSNPVersion(report.SNPReport.LaunchTCB)
log.Info(fmt.Sprintf("Input report: %+v", inputVersion))
latestAPIVersionAPI, err := attestationconfigapi.NewFetcherWithCustomCDNAndCosignKey(cfg.url, cfg.cosignPublicKey).FetchLatestVersion(ctx, attestation)
apiFetcher := attestationconfigapi.NewFetcherWithCustomCDNAndCosignKey(cfg.url, cfg.cosignPublicKey)
latestVersionInAPI, err := apiFetcher.FetchLatestVersion(ctx, cfg.variant)
if err != nil {
var notFoundErr *fetcher.NotFoundError
if errors.As(err, &notFoundErr) {
@ -136,12 +114,39 @@ func uploadReport(ctx context.Context,
}
}
latestAPIVersion := latestAPIVersionAPI.SEVSNPVersion
if err := apiClient.UploadSEVSNPVersionLatest(ctx, attestation, inputVersion, latestAPIVersion, cfg.uploadDate, cfg.force); err != nil {
if errors.Is(err, client.ErrNoNewerVersion) {
log.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v", inputVersion, latestAPIVersion))
return nil
var newVersion, latestVersion any
switch cfg.variant {
case variant.AWSSEVSNP{}, variant.AzureSEVSNP{}, variant.GCPSEVSNP{}:
latestVersion = latestVersionInAPI.SEVSNPVersion
log.Info(fmt.Sprintf("Reading SNP report from file: %s", cfg.path))
var report verify.Report
if err := fs.ReadJSON(cfg.path, &report); err != nil {
return fmt.Errorf("reading snp report: %w", err)
}
newVersion = convertTCBVersionToSNPVersion(report.SNPReport.LaunchTCB)
log.Info(fmt.Sprintf("Input SNP report: %+v", newVersion))
case variant.AzureTDX{}:
latestVersion = latestVersionInAPI.TDXVersion
log.Info(fmt.Sprintf("Reading TDX report from file: %s", cfg.path))
var report *tdx.QuoteV4
if err := fs.ReadJSON(cfg.path, &report); err != nil {
return fmt.Errorf("reading tdx report: %w", err)
}
newVersion = convertQuoteToTDXVersion(report)
log.Info(fmt.Sprintf("Input TDX report: %+v", newVersion))
default:
return fmt.Errorf("variant %s not supported", cfg.variant)
}
if err := apiClient.UploadLatestVersion(
ctx, cfg.variant, newVersion, latestVersion, cfg.uploadDate, cfg.force,
); err != nil && !errors.Is(err, client.ErrNoNewerVersion) {
return fmt.Errorf("updating latest version: %w", err)
}
@ -157,8 +162,18 @@ func convertTCBVersionToSNPVersion(tcb verify.TCBVersion) attestationconfigapi.S
}
}
func convertQuoteToTDXVersion(quote *tdx.QuoteV4) attestationconfigapi.TDXVersion {
return attestationconfigapi.TDXVersion{
QESVN: binary.LittleEndian.Uint16(quote.Header.QeSvn),
PCESVN: binary.LittleEndian.Uint16(quote.Header.PceSvn),
QEVendorID: [16]byte(quote.Header.QeVendorId),
XFAM: [8]byte(quote.TdQuoteBody.Xfam),
TEETCBSVN: [16]byte(quote.TdQuoteBody.TeeTcbSvn),
}
}
type uploadConfig struct {
provider cloudprovider.Provider
variant variant.Variant
kind objectKind
path string
uploadDate time.Time
@ -210,12 +225,16 @@ func newConfig(cmd *cobra.Command, args [3]string) (uploadConfig, error) {
return uploadConfig{}, fmt.Errorf("getting cache window size: %w", err)
}
provider := cloudprovider.FromString(args[0])
variant, err := variant.FromString(args[0])
if err != nil {
return uploadConfig{}, fmt.Errorf("invalid attestation variant: %q: %w", args[0], err)
}
kind := kindFromString(args[1])
path := args[2]
return uploadConfig{
provider: provider,
variant: variant,
kind: kind,
path: path,
uploadDate: uploadDate,

View File

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

View File

@ -20,7 +20,7 @@ const cosignPublicKey = constants.CosignPublicKeyReleases
// Fetcher fetches config API resources without authentication.
type Fetcher interface {
FetchLatestVersion(ctx context.Context, attestation variant.Variant) (VersionAPIEntry, error)
FetchLatestVersion(ctx context.Context, attestation variant.Variant) (Entry, error)
}
// fetcher fetches AttestationCfg API resources without authentication.
@ -60,10 +60,10 @@ func newFetcherWithClientAndVerifier(client apifetcher.HTTPClient, cosignVerifie
}
// FetchLatestVersion returns the latest versions of the given type.
func (f *fetcher) FetchLatestVersion(ctx context.Context, variant variant.Variant) (VersionAPIEntry, error) {
func (f *fetcher) FetchLatestVersion(ctx context.Context, variant variant.Variant) (Entry, error) {
list, err := f.fetchVersionList(ctx, variant)
if err != nil {
return VersionAPIEntry{}, err
return Entry{}, err
}
// latest version is first in list
@ -71,11 +71,10 @@ func (f *fetcher) FetchLatestVersion(ctx context.Context, variant variant.Varian
}
// fetchVersionList fetches the version list information from the config API.
func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant) (VersionList, error) {
// TODO(derpsteb): Replace with FetchAndVerify once we move to v2 of the config API and the list is saved as (.json) file.
fetchedList, err := apifetcher.Fetch(ctx, f.HTTPClient, f.cdnURL, VersionList{Variant: variant})
func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant) (List, error) {
fetchedList, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, List{Variant: variant}, f.verifier)
if err != nil {
return VersionList{}, fmt.Errorf("fetching version list: %w", err)
return List{}, fmt.Errorf("fetching version list: %w", err)
}
// Set the attestation variant of the list as it is not part of the marshalled JSON retrieved by Fetch
@ -85,14 +84,14 @@ func (f *fetcher) fetchVersionList(ctx context.Context, variant variant.Variant)
}
// fetchVersion fetches the version information from the config API.
func (f *fetcher) fetchVersion(ctx context.Context, version string, variant variant.Variant) (VersionAPIEntry, error) {
obj := VersionAPIEntry{
func (f *fetcher) fetchVersion(ctx context.Context, version string, variant variant.Variant) (Entry, error) {
obj := Entry{
Version: version,
Variant: variant,
}
fetchedVersion, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, f.cdnURL, obj, f.verifier)
if err != nil {
return VersionAPIEntry{}, fmt.Errorf("fetching version %q: %w", version, err)
return Entry{}, fmt.Errorf("fetching version %q: %w", version, err)
}
// Set the attestation variant of the list as it is not part of the marshalled JSON retrieved by FetchAndVerify

View File

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

View File

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

View File

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

View File

@ -53,7 +53,7 @@ type Client struct {
dirtyPaths []string // written paths to be invalidated
DryRun bool // no write operations are performed
Logger *slog.Logger
log *slog.Logger
}
// NewReadOnlyClient creates a new read-only client.
@ -77,7 +77,7 @@ func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID strin
s3ClientClose: staticUploadClientClose,
bucket: bucket,
DryRun: true,
Logger: log,
log: log,
}
clientClose := func(ctx context.Context) error {
return client.Close(ctx)
@ -106,7 +106,7 @@ func NewClient(ctx context.Context, region, bucket, distributionID string, dryRu
s3ClientClose: staticUploadClientClose,
bucket: bucket,
DryRun: dryRun,
Logger: log,
log: log,
}
clientClose := func(ctx context.Context) error {
return client.Close(ctx)
@ -119,7 +119,7 @@ func NewClient(ctx context.Context, region, bucket, distributionID string, dryRu
// It invalidates the CDN cache for all uploaded files.
func (c *Client) Close(ctx context.Context) error {
if c.s3ClientClose == nil {
c.Logger.Debug("Client has no s3ClientClose")
c.log.Debug("Client has no s3ClientClose")
return nil
}
return c.s3ClientClose(ctx)
@ -131,7 +131,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error {
Bucket: &c.bucket,
Prefix: &path,
}
c.Logger.Debug(fmt.Sprintf("Listing objects in %q", path))
c.log.Debug(fmt.Sprintf("Listing objects in %q", path))
objs := []s3types.Object{}
out := &s3.ListObjectsV2Output{IsTruncated: ptr(true)}
for out.IsTruncated != nil && *out.IsTruncated {
@ -142,10 +142,10 @@ func (c *Client) DeletePath(ctx context.Context, path string) error {
}
objs = append(objs, out.Contents...)
}
c.Logger.Debug(fmt.Sprintf("Found %d objects in %q", len(objs), path))
c.log.Debug(fmt.Sprintf("Found %d objects in %q", len(objs), path))
if len(objs) == 0 {
c.Logger.Warn(fmt.Sprintf("Path %s is already empty", path))
c.log.Warn(fmt.Sprintf("Path %s is already empty", path))
return nil
}
@ -155,7 +155,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error {
}
if c.DryRun {
c.Logger.Debug(fmt.Sprintf("DryRun: Deleting %d objects with IDs %v", len(objs), objIDs))
c.log.Debug(fmt.Sprintf("DryRun: Deleting %d objects with IDs %v", len(objs), objIDs))
return nil
}
@ -167,7 +167,7 @@ func (c *Client) DeletePath(ctx context.Context, path string) error {
Objects: objIDs,
},
}
c.Logger.Debug(fmt.Sprintf("Deleting %d objects in %q", len(objs), path))
c.log.Debug(fmt.Sprintf("Deleting %d objects in %q", len(objs), path))
if _, err := c.s3Client.DeleteObjects(ctx, deleteIn); err != nil {
return fmt.Errorf("deleting objects in %s: %w", path, err)
}
@ -197,7 +197,7 @@ func Fetch[T APIObject](ctx context.Context, c *Client, obj T) (T, error) {
Key: ptr(obj.JSONPath()),
}
c.Logger.Debug(fmt.Sprintf("Fetching %T from s3: %q", obj, obj.JSONPath()))
c.log.Debug(fmt.Sprintf("Fetching %T from s3: %q", obj, obj.JSONPath()))
out, err := c.s3Client.GetObject(ctx, in)
var noSuchkey *s3types.NoSuchKey
if errors.As(err, &noSuchkey) {
@ -231,7 +231,7 @@ func Update(ctx context.Context, c *Client, obj APIObject) error {
}
if c.DryRun {
c.Logger.With(slog.String("bucket", c.bucket), slog.String("key", obj.JSONPath()), slog.String("body", string(rawJSON))).Debug("DryRun: s3 put object")
c.log.With(slog.String("bucket", c.bucket), slog.String("key", obj.JSONPath()), slog.String("body", string(rawJSON))).Debug("DryRun: s3 put object")
return nil
}
@ -243,7 +243,7 @@ func Update(ctx context.Context, c *Client, obj APIObject) error {
c.dirtyPaths = append(c.dirtyPaths, "/"+obj.JSONPath())
c.Logger.Debug(fmt.Sprintf("Uploading %T to s3: %q", obj, obj.JSONPath()))
c.log.Debug(fmt.Sprintf("Uploading %T to s3: %q", obj, obj.JSONPath()))
if _, err := c.Upload(ctx, in); err != nil {
return fmt.Errorf("uploading %T: %w", obj, err)
}
@ -306,7 +306,7 @@ func Delete(ctx context.Context, c *Client, obj APIObject) error {
Key: ptr(obj.JSONPath()),
}
c.Logger.Debug(fmt.Sprintf("Deleting %T from s3: %q", obj, obj.JSONPath()))
c.log.Debug(fmt.Sprintf("Deleting %T from s3: %q", obj, obj.JSONPath()))
if _, err := c.DeleteObject(ctx, in); err != nil {
return fmt.Errorf("deleting s3 object at %s: %w", obj.JSONPath(), err)
}

View File

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

View File

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

View File

@ -1165,7 +1165,7 @@ type AzureTDX struct {
// Expected 48 byte hex-encoded MR_SEAM value.
MRSeam encoding.HexBytes `json:"mrSeam,omitempty" yaml:"mrSeam,omitempty"`
// description: |
// Expected 8 byte hex-encoded XFAM field.
// Expected 8 byte hex-encoded eXtended Features Available Mask (XFAM) field. Defaults to the latest available XFAM on Azure VMs. Unset to disable validation.
XFAM AttestationVersion[encoding.HexBytes] `json:"xfam" yaml:"xfam"`
// description: |
// Intel Root Key certificate used to verify the TDX certificate chain.

View File

@ -545,22 +545,22 @@ func init() {
GCPSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
GCPSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
GCPSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
GCPSEVSNPDoc.Fields[1].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[1].Type = ""
GCPSEVSNPDoc.Fields[1].Note = ""
GCPSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
GCPSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
GCPSEVSNPDoc.Fields[2].Name = "teeVersion"
GCPSEVSNPDoc.Fields[2].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[2].Type = ""
GCPSEVSNPDoc.Fields[2].Note = ""
GCPSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
GCPSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
GCPSEVSNPDoc.Fields[3].Name = "snpVersion"
GCPSEVSNPDoc.Fields[3].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[3].Type = ""
GCPSEVSNPDoc.Fields[3].Note = ""
GCPSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
GCPSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
GCPSEVSNPDoc.Fields[4].Name = "microcodeVersion"
GCPSEVSNPDoc.Fields[4].Type = "AttestationVersion"
GCPSEVSNPDoc.Fields[4].Type = ""
GCPSEVSNPDoc.Fields[4].Note = ""
GCPSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
GCPSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version."
@ -623,22 +623,22 @@ func init() {
AWSSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
AWSSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
AWSSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
AWSSEVSNPDoc.Fields[1].Type = "AttestationVersion"
AWSSEVSNPDoc.Fields[1].Type = ""
AWSSEVSNPDoc.Fields[1].Note = ""
AWSSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
AWSSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
AWSSEVSNPDoc.Fields[2].Name = "teeVersion"
AWSSEVSNPDoc.Fields[2].Type = "AttestationVersion"
AWSSEVSNPDoc.Fields[2].Type = ""
AWSSEVSNPDoc.Fields[2].Note = ""
AWSSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
AWSSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
AWSSEVSNPDoc.Fields[3].Name = "snpVersion"
AWSSEVSNPDoc.Fields[3].Type = "AttestationVersion"
AWSSEVSNPDoc.Fields[3].Type = ""
AWSSEVSNPDoc.Fields[3].Note = ""
AWSSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
AWSSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
AWSSEVSNPDoc.Fields[4].Name = "microcodeVersion"
AWSSEVSNPDoc.Fields[4].Type = "AttestationVersion"
AWSSEVSNPDoc.Fields[4].Type = ""
AWSSEVSNPDoc.Fields[4].Note = ""
AWSSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
AWSSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version."
@ -685,22 +685,22 @@ func init() {
AzureSEVSNPDoc.Fields[0].Description = "Expected TPM measurements."
AzureSEVSNPDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
AzureSEVSNPDoc.Fields[1].Name = "bootloaderVersion"
AzureSEVSNPDoc.Fields[1].Type = "AttestationVersion"
AzureSEVSNPDoc.Fields[1].Type = ""
AzureSEVSNPDoc.Fields[1].Note = ""
AzureSEVSNPDoc.Fields[1].Description = "Lowest acceptable bootloader version."
AzureSEVSNPDoc.Fields[1].Comments[encoder.LineComment] = "Lowest acceptable bootloader version."
AzureSEVSNPDoc.Fields[2].Name = "teeVersion"
AzureSEVSNPDoc.Fields[2].Type = "AttestationVersion"
AzureSEVSNPDoc.Fields[2].Type = ""
AzureSEVSNPDoc.Fields[2].Note = ""
AzureSEVSNPDoc.Fields[2].Description = "Lowest acceptable TEE version."
AzureSEVSNPDoc.Fields[2].Comments[encoder.LineComment] = "Lowest acceptable TEE version."
AzureSEVSNPDoc.Fields[3].Name = "snpVersion"
AzureSEVSNPDoc.Fields[3].Type = "AttestationVersion"
AzureSEVSNPDoc.Fields[3].Type = ""
AzureSEVSNPDoc.Fields[3].Note = ""
AzureSEVSNPDoc.Fields[3].Description = "Lowest acceptable SEV-SNP version."
AzureSEVSNPDoc.Fields[3].Comments[encoder.LineComment] = "Lowest acceptable SEV-SNP version."
AzureSEVSNPDoc.Fields[4].Name = "microcodeVersion"
AzureSEVSNPDoc.Fields[4].Type = "AttestationVersion"
AzureSEVSNPDoc.Fields[4].Type = ""
AzureSEVSNPDoc.Fields[4].Note = ""
AzureSEVSNPDoc.Fields[4].Description = "Lowest acceptable microcode version."
AzureSEVSNPDoc.Fields[4].Comments[encoder.LineComment] = "Lowest acceptable microcode version."
@ -752,22 +752,22 @@ func init() {
AzureTDXDoc.Fields[0].Description = "Expected TPM measurements."
AzureTDXDoc.Fields[0].Comments[encoder.LineComment] = "Expected TPM measurements."
AzureTDXDoc.Fields[1].Name = "qeSVN"
AzureTDXDoc.Fields[1].Type = "uint16"
AzureTDXDoc.Fields[1].Type = ""
AzureTDXDoc.Fields[1].Note = ""
AzureTDXDoc.Fields[1].Description = "Minimum required QE security version number (SVN)."
AzureTDXDoc.Fields[1].Comments[encoder.LineComment] = "Minimum required QE security version number (SVN)."
AzureTDXDoc.Fields[2].Name = "pceSVN"
AzureTDXDoc.Fields[2].Type = "uint16"
AzureTDXDoc.Fields[2].Type = ""
AzureTDXDoc.Fields[2].Note = ""
AzureTDXDoc.Fields[2].Description = "Minimum required PCE security version number (SVN)."
AzureTDXDoc.Fields[2].Comments[encoder.LineComment] = "Minimum required PCE security version number (SVN)."
AzureTDXDoc.Fields[3].Name = "teeTCBSVN"
AzureTDXDoc.Fields[3].Type = "HexBytes"
AzureTDXDoc.Fields[3].Type = ""
AzureTDXDoc.Fields[3].Note = ""
AzureTDXDoc.Fields[3].Description = "Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN)."
AzureTDXDoc.Fields[3].Comments[encoder.LineComment] = "Component-wise minimum required 16 byte hex-encoded TEE_TCB security version number (SVN)."
AzureTDXDoc.Fields[4].Name = "qeVendorID"
AzureTDXDoc.Fields[4].Type = "HexBytes"
AzureTDXDoc.Fields[4].Type = ""
AzureTDXDoc.Fields[4].Note = ""
AzureTDXDoc.Fields[4].Description = "Expected 16 byte hex-encoded QE_VENDOR_ID field."
AzureTDXDoc.Fields[4].Comments[encoder.LineComment] = "Expected 16 byte hex-encoded QE_VENDOR_ID field."
@ -777,10 +777,10 @@ func init() {
AzureTDXDoc.Fields[5].Description = "Expected 48 byte hex-encoded MR_SEAM value."
AzureTDXDoc.Fields[5].Comments[encoder.LineComment] = "Expected 48 byte hex-encoded MR_SEAM value."
AzureTDXDoc.Fields[6].Name = "xfam"
AzureTDXDoc.Fields[6].Type = "HexBytes"
AzureTDXDoc.Fields[6].Type = ""
AzureTDXDoc.Fields[6].Note = ""
AzureTDXDoc.Fields[6].Description = "Expected 8 byte hex-encoded XFAM field."
AzureTDXDoc.Fields[6].Comments[encoder.LineComment] = "Expected 8 byte hex-encoded XFAM field."
AzureTDXDoc.Fields[6].Description = "Expected 8 byte hex-encoded eXtended Features Available Mask (XFAM) field. Defaults to the latest available XFAM on Azure VMs. Unset to disable validation."
AzureTDXDoc.Fields[6].Comments[encoder.LineComment] = "Expected 8 byte hex-encoded eXtended Features Available Mask (XFAM) field. Defaults to the latest available XFAM on Azure VMs. Unset to disable validation."
AzureTDXDoc.Fields[7].Name = "intelRootKey"
AzureTDXDoc.Fields[7].Type = "Certificate"
AzureTDXDoc.Fields[7].Note = ""

View File

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

View File

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

View File

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