diff --git a/.github/actions/e2e_attestationconfigapi/action.yml b/.github/actions/e2e_attestationconfigapi/action.yml index a98b54670f..02ec280847 100644 --- a/.github/actions/e2e_attestationconfigapi/action.yml +++ b/.github/actions/e2e_attestationconfigapi/action.yml @@ -2,9 +2,9 @@ name: E2E Attestationconfig API Test description: "Test the attestationconfig CLI is functional." inputs: - csp: - description: "Cloud provider to run tests against" - default: "azure" + attestationVariant: + description: "attestation variant to run tests against" + default: "azure-sev-snp" cosignPrivateKey: description: "Cosign private key" required: true @@ -30,4 +30,4 @@ runs: COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }} COSIGN_PASSWORD: ${{ inputs.cosignPassword }} run: | - bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.csp }} + bazel run //internal/api/attestationconfigapi/cli:cli_e2e_test -- ${{ inputs.attestationVariant }} diff --git a/.github/actions/e2e_verify/action.yml b/.github/actions/e2e_verify/action.yml index f330751f56..61cefeef0a 100644 --- a/.github/actions/e2e_verify/action.yml +++ b/.github/actions/e2e_verify/action.yml @@ -68,9 +68,9 @@ runs: case "${{ inputs.attestationVariant }}" in - "azure-sev-snp"|"aws-sev-snp"|"gcp-sev-snp") + "azure-sev-snp"|"azure-tdx"|"aws-sev-snp"|"gcp-sev-snp") echo "Extracting TCB versions for API update" - constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "snp-report-${node}.json" + constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 -o json > "attestation-report-${node}.json" ;; *) constellation verify --cluster-id "${clusterID}" --node-endpoint localhost:9090 @@ -88,22 +88,19 @@ runs: aws-region: eu-central-1 - name: Upload extracted TCBs - if: github.ref_name == 'main' && (inputs.attestationVariant == 'azure-sev-snp' || inputs.attestationVariant == 'aws-sev-snp' || inputs.attestationVariant == 'gcp-sev-snp') + if: github.ref_name == 'main' && (inputs.attestationVariant == 'azure-sev-snp' || inputs.attestationVariant == 'azure-tdx' || inputs.attestationVariant == 'aws-sev-snp' || inputs.attestationVariant == 'gcp-sev-snp') shell: bash env: COSIGN_PASSWORD: ${{ inputs.cosignPassword }} COSIGN_PRIVATE_KEY: ${{ inputs.cosignPrivateKey }} run: | - reports=(snp-report-*.json) + reports=(attestation-report-*.json) if [ -z ${#reports[@]} ]; then exit 1 fi - attestationVariant=${{ inputs.attestationVariant }} - cloudProvider=${attestationVariant%%-*} - for file in "${reports[@]}"; do path=$(realpath "${file}") cat "${path}" - bazel run //internal/api/attestationconfigapi/cli -- upload "${cloudProvider}" snp-report "${path}" + bazel run //internal/api/attestationconfigapi/cli -- upload {{ inputs.attestationVariant }} attestation-report "${path}" done diff --git a/.github/workflows/e2e-attestationconfigapi.yml b/.github/workflows/e2e-attestationconfigapi.yml index f00ae908d5..145e7b52dd 100644 --- a/.github/workflows/e2e-attestationconfigapi.yml +++ b/.github/workflows/e2e-attestationconfigapi.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false max-parallel: 1 matrix: - csp: ["azure", "aws", "gcp"] + attestationVariant: ["azure-sev-snp", "azure-tdx", "aws-sev-snp", "gcp-sev-snp"] runs-on: ubuntu-22.04 permissions: id-token: write @@ -36,4 +36,4 @@ jobs: with: cosignPrivateKey: ${{ secrets.COSIGN_DEV_PRIVATE_KEY }} cosignPassword: ${{ secrets.COSIGN_DEV_PASSWORD }} - csp: ${{ matrix.csp }} + attestationVariant: ${{ matrix.attestationVariant }} diff --git a/internal/api/attestationconfigapi/cli/client/BUILD.bazel b/internal/api/attestationconfigapi/cli/client/BUILD.bazel index b7e553f059..5e8d4cc8a6 100644 --- a/internal/api/attestationconfigapi/cli/client/BUILD.bazel +++ b/internal/api/attestationconfigapi/cli/client/BUILD.bazel @@ -30,7 +30,6 @@ go_test( embed = [":client"], deps = [ "//internal/api/attestationconfigapi", - "//internal/attestation/variant", "@com_github_stretchr_testify//assert", ], ) diff --git a/internal/api/attestationconfigapi/cli/client/client.go b/internal/api/attestationconfigapi/cli/client/client.go index 8882ff87b7..06bf83e8b8 100644 --- a/internal/api/attestationconfigapi/cli/client/client.go +++ b/internal/api/attestationconfigapi/cli/client/client.go @@ -17,7 +17,6 @@ import ( "log/slog" "path" "strings" - "time" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go/aws" @@ -108,29 +107,6 @@ func (c Client) deleteVersion(versions attestationconfigapi.VersionList, version return ops, nil } -func (c Client) constructUploadCmd(attestation variant.Variant, version attestationconfigapi.SEVSNPVersion, versionNames attestationconfigapi.VersionList, date time.Time) []crudCmd { - if !attestation.Equal(versionNames.Variant) { - return nil - } - - dateStr := date.Format(VersionFormat) + ".json" - var res []crudCmd - - res = append(res, putCmd{ - apiObject: attestationconfigapi.VersionAPIEntry{Version: dateStr, Variant: attestation, SEVSNPVersion: version}, - signer: c.signer, - }) - - versionNames.AddVersion(dateStr) - - res = append(res, putCmd{ - apiObject: versionNames, - signer: c.signer, - }) - - return res -} - 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), diff --git a/internal/api/attestationconfigapi/cli/client/client_test.go b/internal/api/attestationconfigapi/cli/client/client_test.go index 81962abf8f..7331d55bac 100644 --- a/internal/api/attestationconfigapi/cli/client/client_test.go +++ b/internal/api/attestationconfigapi/cli/client/client_test.go @@ -7,37 +7,11 @@ 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", @@ -58,9 +32,3 @@ func TestDeleteAzureSEVSNPVersions(t *testing.T) { apiObject: attestationconfigapi.VersionList{List: []string{"2023-01-01.json", "2019-01-01.json"}}, }) } - -type fakeSigner struct{} - -func (fakeSigner) Sign(_ []byte) ([]byte, error) { - return []byte("signature"), nil -} diff --git a/internal/api/attestationconfigapi/cli/client/reportersnp.go b/internal/api/attestationconfigapi/cli/client/reportersnp.go index 3462558c0b..881f22165b 100644 --- a/internal/api/attestationconfigapi/cli/client/reportersnp.go +++ b/internal/api/attestationconfigapi/cli/client/reportersnp.go @@ -65,8 +65,8 @@ func (c Client) UploadSEVSNPVersionLatest( } c.s3Client.Logger.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate)) - if isInputNewerThanOtherVersion(minVersion, latestVersionInAPI) { - c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v", minVersion, latestVersionInAPI)) + if !isInputNewerThanOtherVersion(minVersion, latestVersionInAPI) { + c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v. Skipping list update", minVersion, latestVersionInAPI)) return ErrNoNewerVersion } @@ -92,7 +92,24 @@ func (c Client) uploadSEVSNPVersion(ctx context.Context, attestation variant.Var if err != nil { return fmt.Errorf("fetch version list: %w", err) } - ops := c.constructUploadCmd(attestation, version, versions, date) + if !attestation.Equal(versions.Variant) { + return nil + } + + dateStr := date.Format(VersionFormat) + ".json" + var ops []crudCmd + + ops = append(ops, putCmd{ + apiObject: apiSEVSNPVersion{version: dateStr, variant: attestation, SEVSNPVersion: version, cached: false}, + signer: c.signer, + }) + + versions.AddVersion(dateStr) + + ops = append(ops, putCmd{ + apiObject: versions, + signer: c.signer, + }) return executeAllCmds(ctx, c.s3Client, ops) } @@ -101,36 +118,43 @@ func (c Client) uploadSEVSNPVersion(ctx context.Context, attestation variant.Var func (c Client) cacheSEVSNPVersion(ctx context.Context, variant variant.Variant, version attestationconfigapi.SEVSNPVersion, date time.Time) error { dateStr := date.Format(VersionFormat) + ".json" res := putCmd{ - apiObject: cachedSEVSNPVersion{version: dateStr, variant: variant, SEVSNPVersion: version}, + apiObject: apiSEVSNPVersion{version: dateStr, variant: variant, SEVSNPVersion: version, cached: true}, signer: c.signer, } return res.Execute(ctx, c.s3Client) } -// findMinSEVSNPVersion finds the minimal version of the given version dates among the latest cached values in the version window size. +// findMinSEVSNPVersion finds the minimal version (the version with the lowest SVNs) of the given version dates among the latest cached values in the version window size. func (c Client) findMinSEVSNPVersion(ctx context.Context, attestationVariant variant.Variant, versionDates []string) (attestationconfigapi.SEVSNPVersion, string, error) { - var minimalVersion attestationconfigapi.SEVSNPVersion + var minimalVersion *attestationconfigapi.SEVSNPVersion var minimalDate string sort.Sort(sort.Reverse(sort.StringSlice(versionDates))) // sort in reverse order to slice the latest versions versionDates = versionDates[:c.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, cachedSEVSNPVersion{version: date + ".json", variant: attestationVariant}) + obj, err := client.Fetch(ctx, c.s3Client, apiSEVSNPVersion{version: date + ".json", variant: attestationVariant, cached: true}) if err != nil { return attestationconfigapi.SEVSNPVersion{}, "", fmt.Errorf("get object: %w", err) } // Need to set this explicitly as the variant is not part of the marshalled JSON. obj.variant = attestationVariant - // If the version we fetched has higher SVNs than the current minimal version, update the minimal version. - if isInputNewerThanOtherVersion(obj.SEVSNPVersion, minimalVersion) { - minimalVersion = obj.SEVSNPVersion + if minimalVersion == nil { + minimalVersion = &obj.SEVSNPVersion + minimalDate = date + continue + } + + // If the current minimal version has newer versions than the one we just fetched, + // update the minimal version to the older version. + if isInputNewerThanOtherVersion(*minimalVersion, obj.SEVSNPVersion) { + minimalVersion = &obj.SEVSNPVersion minimalDate = date } } - return minimalVersion, minimalDate, nil + return *minimalVersion, minimalDate, nil } // isInputNewerThanOtherVersion compares all version fields and returns true if any input field is newer. @@ -153,45 +177,24 @@ func isInputNewerThanOtherVersion(input, other attestationconfigapi.SEVSNPVersio return true } -type cachedSEVSNPVersion struct { +type apiSEVSNPVersion struct { version string `json:"-"` variant variant.Variant `json:"-"` + cached bool `json:"-"` attestationconfigapi.SEVSNPVersion } // JSONPath returns the path to the JSON file for the request to the config api. // This is the path to the cached version in the S3 bucket. -func (c cachedSEVSNPVersion) JSONPath() string { - return path.Join(reportVersionDir(c.variant), c.version) -} - -// ValidateRequest validates the request. -func (c cachedSEVSNPVersion) ValidateRequest() error { - if !strings.HasSuffix(c.version, ".json") { - return fmt.Errorf("version has no .json suffix") +func (c apiSEVSNPVersion) JSONPath() string { + if c.cached { + return path.Join(reportVersionDir(c.variant), c.version) } - return nil -} - -// Validate is a No-Op at the moment. -func (c cachedSEVSNPVersion) Validate() error { - return nil -} - -type cachedTDXVersion struct { - version string `json:"-"` - variant variant.Variant `json:"-"` - attestationconfigapi.TDXVersion -} - -// JSONPath returns the path to the JSON file for the request to the config api. -// This is the path to the cached version in the S3 bucket. -func (c cachedTDXVersion) JSONPath() string { - return path.Join(reportVersionDir(c.variant), c.version) + return path.Join(attestationconfigapi.AttestationURLPath, c.variant.String(), c.version) } // ValidateRequest validates the request. -func (c cachedTDXVersion) ValidateRequest() error { +func (c apiSEVSNPVersion) ValidateRequest() error { if !strings.HasSuffix(c.version, ".json") { return fmt.Errorf("version has no .json suffix") } @@ -199,6 +202,6 @@ func (c cachedTDXVersion) ValidateRequest() error { } // Validate is a No-Op at the moment. -func (c cachedTDXVersion) Validate() error { +func (c apiSEVSNPVersion) Validate() error { return nil } diff --git a/internal/api/attestationconfigapi/cli/client/reportertdx.go b/internal/api/attestationconfigapi/cli/client/reportertdx.go index a36498f9e6..b3b458d734 100644 --- a/internal/api/attestationconfigapi/cli/client/reportertdx.go +++ b/internal/api/attestationconfigapi/cli/client/reportertdx.go @@ -10,7 +10,9 @@ import ( "bytes" "context" "fmt" + "path" "sort" + "strings" "time" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" @@ -55,7 +57,7 @@ func (c Client) UploadTDXVersionLatest( c.s3Client.Logger.Info(fmt.Sprintf("Found minimal version: %+v with date: %s", minVersion, minDate)) if !isInputNewerThanOtherTDXVersion(minVersion, latestVersionInAPI) { - c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v", minVersion, latestVersionInAPI)) + c.s3Client.Logger.Info(fmt.Sprintf("Input version: %+v is not newer than latest API version: %+v. Skipping list update", minVersion, latestVersionInAPI)) return ErrNoNewerVersion } @@ -77,45 +79,70 @@ func (c Client) UploadTDXVersionLatest( // The version name is the UTC timestamp of the date. // The /list entry stores the version name + .json suffix. func (c Client) uploadTDXVersion(ctx context.Context, attestation variant.Variant, version attestationconfigapi.TDXVersion, date time.Time) error { + versions, err := c.List(ctx, attestation) + if err != nil { + return fmt.Errorf("fetch version list: %w", err) + } + if !attestation.Equal(versions.Variant) { + return nil + } + dateStr := date.Format(VersionFormat) + ".json" - res := putCmd{ - apiObject: attestationconfigapi.VersionAPIEntry{Version: dateStr, Variant: attestation, TDXVersion: version}, + var ops []crudCmd + + ops = append(ops, putCmd{ + apiObject: apiTDXVersion{version: dateStr, variant: attestation, TDXVersion: version, cached: false}, signer: c.signer, - } - return res.Execute(ctx, c.s3Client) + }) + + versions.AddVersion(dateStr) + + ops = append(ops, putCmd{ + apiObject: versions, + signer: c.signer, + }) + + return executeAllCmds(ctx, c.s3Client, ops) } func (c Client) cacheTDXVersion(ctx context.Context, variant variant.Variant, version attestationconfigapi.TDXVersion, date time.Time) error { dateStr := date.Format(VersionFormat) + ".json" res := putCmd{ - apiObject: cachedTDXVersion{version: dateStr, variant: variant, TDXVersion: version}, + apiObject: apiTDXVersion{version: dateStr, variant: variant, TDXVersion: version, cached: true}, signer: c.signer, } return res.Execute(ctx, c.s3Client) } func (c Client) findMinTDXVersion(ctx context.Context, attestationVariant variant.Variant, versionDates []string) (attestationconfigapi.TDXVersion, string, error) { - var minimalVersion attestationconfigapi.TDXVersion + var minimalVersion *attestationconfigapi.TDXVersion var minimalDate string sort.Sort(sort.Reverse(sort.StringSlice(versionDates))) versionDates = versionDates[:c.cacheWindowSize] sort.Strings(versionDates) for _, date := range versionDates { - obj, err := client.Fetch(ctx, c.s3Client, cachedTDXVersion{version: date + ".json", variant: attestationVariant}) + obj, err := client.Fetch(ctx, c.s3Client, apiTDXVersion{version: date + ".json", variant: attestationVariant, cached: true}) if err != nil { return attestationconfigapi.TDXVersion{}, "", fmt.Errorf("fetching version: %w", err) } obj.variant = attestationVariant - // If the version we fetched has higher SVNs than the current minimal version, update the minimal version. - if isInputNewerThanOtherTDXVersion(obj.TDXVersion, minimalVersion) { - minimalVersion = obj.TDXVersion + if minimalVersion == nil { + minimalVersion = &obj.TDXVersion + minimalDate = date + continue + } + + // If the current minimal version has newer versions than the one we just fetched, + // update the minimal version to the older version. + if isInputNewerThanOtherTDXVersion(*minimalVersion, obj.TDXVersion) { + minimalVersion = &obj.TDXVersion minimalDate = date } } - return minimalVersion, minimalDate, nil + return *minimalVersion, minimalDate, nil } func isInputNewerThanOtherTDXVersion(input, other attestationconfigapi.TDXVersion) bool { @@ -125,12 +152,6 @@ func isInputNewerThanOtherTDXVersion(input, other attestationconfigapi.TDXVersio if input.QESVN < other.QESVN { return false } - if bytes.Equal(input.QEVendorID[:], other.QEVendorID[:]) { - return false - } - if bytes.Equal(input.XFAM[:], other.XFAM[:]) { - return false - } // Validate component-wise security version numbers for idx, inputVersion := range input.TEETCBSVN { @@ -139,5 +160,38 @@ func isInputNewerThanOtherTDXVersion(input, other attestationconfigapi.TDXVersio } } + if bytes.Equal(input.QEVendorID[:], other.QEVendorID[:]) && bytes.Equal(input.XFAM[:], other.XFAM[:]) { + return false + } + return true } + +type apiTDXVersion struct { + version string `json:"-"` + variant variant.Variant `json:"-"` + cached bool `json:"-"` + attestationconfigapi.TDXVersion +} + +// JSONPath returns the path to the JSON file for the request to the config api. +// This is the path to the cached version in the S3 bucket. +func (c apiTDXVersion) JSONPath() string { + if c.cached { + return path.Join(reportVersionDir(c.variant), c.version) + } + return path.Join(attestationconfigapi.AttestationURLPath, c.variant.String(), c.version) +} + +// ValidateRequest validates the request. +func (c apiTDXVersion) ValidateRequest() error { + if !strings.HasSuffix(c.version, ".json") { + return fmt.Errorf("version has no .json suffix") + } + return nil +} + +// Validate is a No-Op at the moment. +func (c apiTDXVersion) Validate() error { + return nil +} diff --git a/internal/api/attestationconfigapi/cli/delete.go b/internal/api/attestationconfigapi/cli/delete.go index 69dbeccb2c..013c69481d 100644 --- a/internal/api/attestationconfigapi/cli/delete.go +++ b/internal/api/attestationconfigapi/cli/delete.go @@ -29,7 +29,7 @@ func newDeleteCmd() *cobra.Command { Use: "delete {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} ", Short: "Delete an object from the attestationconfig API", Long: "Delete a specific object version from the config api. is the name of the object to delete (without .json suffix)", - Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete azure snp-report 1.0.0", + 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, @@ -39,7 +39,7 @@ func newDeleteCmd() *cobra.Command { Use: "recursive {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp}", Short: "delete all objects from the API path constellation/v1/attestation/", Long: "Delete all objects from the API path constellation/v1/attestation/", - Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure", + Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli delete recursive azure-sev-snp", Args: cobra.MatchAll(cobra.ExactArgs(1), isAttestationVariant(0)), RunE: runRecursiveDelete, } diff --git a/internal/api/attestationconfigapi/cli/e2e/test.sh.in b/internal/api/attestationconfigapi/cli/e2e/test.sh.in index 5fb23f06f7..0663a93b7b 100755 --- a/internal/api/attestationconfigapi/cli/e2e/test.sh.in +++ b/internal/api/attestationconfigapi/cli/e2e/test.sh.in @@ -19,39 +19,69 @@ 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}" + +readonly current_report_path="${tmpdir}/attestationReportCurrent.json" +readonly report_path="${tmpdir}/attestationReport.json" +readonly older_report_path="${tmpdir}/attestationReportOld.json" -# 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" +if [[ ${attestationVariant} == *-tdx ]]; then + # the high version numbers ensure that it's newer than the current latest value + cat << EOF > "${current_report_path}" +{ + "header": { + "qe_svn": "AAA=", + "pce_svn": "AAA=", + "qe_vendor_id": "k5pyM/ecTKmUCg2zlX8GBw==" + }, + "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": "k5pyM/ecTKmUCg2zlX8GBw==" + }, + "td_quote_body": { + "tee_tcb_svn": "BAEHAAAAAAAAAAAAAAAAAA==", + "xfam": "5xgGAAAAAAA=" + } +} +EOF + # has an older version + cat << EOF > "${older_report_path}" +{ + "header": { + "qe_svn": "//8=", + "pce_svn": "/v8=", + "qe_vendor_id": "k5pyM/ecTKmUCg2zlX8GBw==" + }, + "td_quote_body": { + "tee_tcb_svn": "BAEHAAAAAAAAAAAAAAAAAA==", + "xfam": "5xgGAAAAAAA=" + } +} +EOF +elif [[ ${attestationVariant} == *-sev-snp ]]; then + # the high version numbers ensure that it's newer than the current latest value + cat << EOF > "${current_report_path}" { "snp_report": { "reported_tcb": { @@ -75,12 +105,8 @@ cat << EOF > "$current_report_path" } } 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" + # the high version numbers ensure that it's newer than the current latest value + cat << EOF > "${report_path}" { "snp_report": { "reported_tcb": { @@ -104,10 +130,8 @@ cat << EOF > "$report_path" } } EOF - -# has an older version -readonly older_report_path="$tmpdir/snpReportOld.json" -cat << EOF > "$older_report_path" + # has an older version + cat << EOF > "${older_report_path}" { "snp_report": { "reported_tcb": { @@ -131,19 +155,26 @@ 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 @@ -155,7 +186,7 @@ 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 +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 @@ -174,28 +205,28 @@ if ! cmp -s <(echo -n '["2023-02-01-03-04.json","2000-01-01-01-01.json"]') list. fi # check that the other versions are not uploaded -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_older}.json) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_older}.json") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date_older}.json, but got ${http_code}" exit 1 fi -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date}.json.sig) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date}.json.sig") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date}.json, but got ${http_code}" exit 1 fi -${configapi_cli} delete "$csp" snp-report "$date_oldest" --region "$region" --bucket "$bucket" +${configapi_cli} delete "${attestationVariant}" attestation-report "${date_oldest}" --region "${region}" --bucket "${bucket}" # Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail. -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}" exit 1 fi # Omit -f to check for 404. We want to check that a file was deleted, therefore we expect the query to fail. -http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}"/${date_oldest}.json.sig) -if [[ $http_code -ne 404 ]]; then +http_code=$(curl -sSL -w '%{http_code}\n' -o /dev/null "${baseurl}/${date_oldest}.json.sig") +if [[ ${http_code} -ne 404 ]]; then echo "Expected HTTP code 404 for: ${basepath}/${date_oldest}.json, but got ${http_code}" exit 1 fi diff --git a/internal/api/attestationconfigapi/cli/upload.go b/internal/api/attestationconfigapi/cli/upload.go index 75b13b5857..5943c0ad26 100644 --- a/internal/api/attestationconfigapi/cli/upload.go +++ b/internal/api/attestationconfigapi/cli/upload.go @@ -32,14 +32,14 @@ func newUploadCmd() *cobra.Command { Use: "upload {aws-sev-snp|azure-sev-snp|azure-tdx|gcp-sev-snp} {attestation-report|guest-firmware} ", Short: "Upload an object to the attestationconfig API", - Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first."+ + Long: fmt.Sprintf("Upload a new object to the attestationconfig API. For snp-reports the new object is added to a cache folder first.\n"+ "The CLI then determines the lowest version within the cache-window present in the cache and writes that value to the config api if necessary. "+ - "For guest-firmware objects the object is added to the API directly. "+ - "Please authenticate with AWS through your preferred method (e.g. environment variables, CLI)"+ + "For guest-firmware objects the object is added to the API directly.\n"+ + "Please authenticate with AWS through your preferred method (e.g. environment variables, CLI) "+ "to be able to upload to S3. Set the %s and %s environment variables to authenticate with cosign.", envCosignPrivateKey, envCosignPwd, ), - Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure snp-report /some/path/report.json", + Example: "COSIGN_PASSWORD=$CPW COSIGN_PRIVATE_KEY=$CKEY cli upload azure-sev-snp attestation-report /some/path/report.json", Args: cobra.MatchAll(cobra.ExactArgs(3), isAttestationVariant(0), isValidKind(1)), PreRunE: envCheck, @@ -125,12 +125,12 @@ func uploadReport( newVersion := convertTCBVersionToSNPVersion(report.SNPReport.LaunchTCB) log.Info(fmt.Sprintf("Input SNP report: %+v", newVersion)) - if err := apiClient.UploadSEVSNPVersionLatest(ctx, cfg.variant, newVersion, latestVersionInAPI.SEVSNPVersion, 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", newVersion, latestVersionInAPI)) - } + if err := apiClient.UploadSEVSNPVersionLatest( + ctx, cfg.variant, newVersion, latestVersionInAPI.SEVSNPVersion, cfg.uploadDate, cfg.force, + ); err != nil && !errors.Is(err, client.ErrNoNewerVersion) { return fmt.Errorf("updating latest version: %w", err) } + case variant.AzureTDX{}: log.Info(fmt.Sprintf("Reading TDX report from file: %s", cfg.path)) var report *tdx.QuoteV4 @@ -141,10 +141,9 @@ func uploadReport( newVersion := convertQuoteToTDXVersion(report) log.Info(fmt.Sprintf("Input TDX report: %+v", newVersion)) - if err := apiClient.UploadTDXVersionLatest(ctx, cfg.variant, newVersion, latestVersionInAPI.TDXVersion, 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", newVersion, latestVersionInAPI)) - } + if err := apiClient.UploadTDXVersionLatest( + ctx, cfg.variant, newVersion, latestVersionInAPI.TDXVersion, cfg.uploadDate, cfg.force, + ); err != nil && !errors.Is(err, client.ErrNoNewerVersion) { return fmt.Errorf("updating latest version: %w", err) } diff --git a/internal/config/config.go b/internal/config/config.go index 24849b0211..b6533022b4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1165,7 +1165,7 @@ type AzureTDX struct { // Expected 48 byte hex-encoded MR_SEAM value. MRSeam encoding.HexBytes `json:"mrSeam,omitempty" yaml:"mrSeam,omitempty"` // description: | - // Expected 8 byte hex-encoded XFAM field. + // Expected 8 byte hex-encoded eXtended Features Available Mask (XFAM) field. Defaults to the latest available XFAM on Azure VMs. Unset to disable validation. XFAM AttestationVersion[encoding.HexBytes] `json:"xfam" yaml:"xfam"` // description: | // Intel Root Key certificate used to verify the TDX certificate chain.