diff --git a/internal/api/attestationconfigapi/cli/BUILD.bazel b/internal/api/attestationconfigapi/cli/BUILD.bazel index 26dddb44ce..2d3961fa2f 100644 --- a/internal/api/attestationconfigapi/cli/BUILD.bazel +++ b/internal/api/attestationconfigapi/cli/BUILD.bazel @@ -21,6 +21,9 @@ go_library( "//internal/constants", "//internal/logger", "//internal/staticupload", + "@com_github_aws_aws_sdk_go//aws", + "@com_github_aws_aws_sdk_go//aws/session", + "@com_github_aws_aws_sdk_go//service/s3", "@com_github_spf13_cobra//:cobra", "@org_uber_go_zap//:zap", ], diff --git a/internal/api/attestationconfigapi/cli/delete.go b/internal/api/attestationconfigapi/cli/delete.go index e3e2c20009..35f585e64e 100644 --- a/internal/api/attestationconfigapi/cli/delete.go +++ b/internal/api/attestationconfigapi/cli/delete.go @@ -9,7 +9,11 @@ import ( "context" "errors" "fmt" + "os" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/staticupload" @@ -27,6 +31,13 @@ func newDeleteCmd() *cobra.Command { } cmd.Flags().StringP("version", "v", "", "Name of the version to delete (without .json suffix)") must(cmd.MarkFlagRequired("version")) + + recursivelyCmd := &cobra.Command{ + Use: "recursive", + Short: "delete all objects from the API path", + RunE: runRecursiveDelete, + } + cmd.AddCommand(recursivelyCmd) return cmd } @@ -85,3 +96,57 @@ func runDelete(cmd *cobra.Command, _ []string) (retErr error) { } return deleteCmd.delete(cmd) } + +func runRecursiveDelete(cmd *cobra.Command, _ []string) (retErr error) { + region, err := cmd.Flags().GetString("region") + if err != nil { + return fmt.Errorf("getting region: %w", err) + } + + bucket, err := cmd.Flags().GetString("bucket") + if err != nil { + return fmt.Errorf("getting bucket: %w", err) + } + + sess, err := session.NewSession(&aws.Config{ + Region: aws.String(region), + }) + if err != nil { + return + } + + // Create an S3 client. + svc := s3.New(sess) + + path := "constellation/v1/attestation/azure-sev-snp" + // List all objects in the path. + resp, err := svc.ListObjectsV2(&s3.ListObjectsV2Input{ + Bucket: aws.String(bucket), + Prefix: aws.String(path), + }) + if err != nil { + fmt.Println("Error listing objects:", err) + os.Exit(1) + } + + // Delete all objects in the path. + var keys []*s3.ObjectIdentifier + for _, obj := range resp.Contents { + keys = append(keys, &s3.ObjectIdentifier{ + Key: obj.Key, + }) + } + if len(keys) > 0 { + _, err = svc.DeleteObjects(&s3.DeleteObjectsInput{ + Bucket: aws.String(bucket), + Delete: &s3.Delete{ + Objects: keys, + Quiet: aws.Bool(true), + }, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/internal/api/attestationconfigapi/cli/e2e/test.sh.in b/internal/api/attestationconfigapi/cli/e2e/test.sh.in index 0106fd7b94..3e06b80c5f 100755 --- a/internal/api/attestationconfigapi/cli/e2e/test.sh.in +++ b/internal/api/attestationconfigapi/cli/e2e/test.sh.in @@ -28,6 +28,24 @@ tmpdir=$(mktemp -d) readonly tmpdir registerExitHandler "rm -rf $tmpdir" +# empty the bucket version state +${configapi_cli} delete recursive --region "$region" --bucket "$bucket" --distribution "$distribution" + +# the high version numbers ensure that it's newer than the current latest value +readonly current_claim_path="$tmpdir/currentMaaClaim.json" +cat << EOF > "$current_claim_path" +{ + "x-ms-isolation-tee": { + "x-ms-sevsnpvm-tee-svn": 1, + "x-ms-sevsnpvm-snpfw-svn": 1, + "x-ms-sevsnpvm-microcode-svn": 1, + "x-ms-sevsnpvm-bootloader-svn": 1 + } +} +EOF +# upload a fake latest version for the fetcher +${configapi_cli} --force --maa-claims-path "$current_claim_path" --upload-date "2000-01-01-01-01" --region "$region" --bucket "$bucket" --distribution "$distribution" + # the high version numbers ensure that it's newer than the current latest value readonly claim_path="$tmpdir/maaClaim.json" cat << EOF > "$claim_path" @@ -70,11 +88,11 @@ if ! curl -fsSL ${baseurl}/${date_oldest}.json > version.json; then fi # check that version values are equal to expected if ! cmp -s <(echo -n '{"bootloader":255,"tee":255,"snp":255,"microcode":254}') version.json; then - echo "The version content:" - cat version.json - echo " is not equal to the expected version content:" - echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}' - exit 1 + echo "The version content:" + cat version.json + echo " is not equal to the expected version content:" + echo '{"bootloader":255,"tee":255,"snp":255,"microcode":254}' + exit 1 fi if ! curl -fsSL ${baseurl}/${date_oldest}.json.sig > /dev/null; then echo "Checking for uploaded version signature file constellation/v1/attestation/azure-sev-snp/${date_oldest}.json.sig: request returned ${?}" diff --git a/internal/api/attestationconfigapi/cli/main.go b/internal/api/attestationconfigapi/cli/main.go index 5d69d4a79b..3f0d25c7e9 100644 --- a/internal/api/attestationconfigapi/cli/main.go +++ b/internal/api/attestationconfigapi/cli/main.go @@ -136,6 +136,10 @@ func runCmd(cmd *cobra.Command, _ []string) (retErr error) { return fmt.Errorf("creating client: %w", err) } if err := client.UploadAzureSEVSNPVersionLatest(ctx, inputVersion, latestAPIVersion, flags.uploadDate, flags.force); err != nil { + if errors.Is(err, attestationconfigapi.ErrNoNewerVersion) { + log.Infof("Input version: %+v is not newer than latest API version: %+v", inputVersion, latestAPIVersion) + return nil + } return fmt.Errorf("updating latest version: %w", err) } return nil diff --git a/internal/api/attestationconfigapi/client.go b/internal/api/attestationconfigapi/client.go index 0794d52f32..d406a6dbf8 100644 --- a/internal/api/attestationconfigapi/client.go +++ b/internal/api/attestationconfigapi/client.go @@ -7,6 +7,7 @@ package attestationconfigapi import ( "context" + "errors" "fmt" "time" @@ -14,6 +15,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/sigstore" + "github.com/edgelesssys/constellation/v2/internal/staticupload" ) @@ -75,6 +77,10 @@ func (a Client) List(ctx context.Context, attestation variant.Variant) ([]string if attestation.Equal(variant.AzureSEVSNP{}) { versions, err := apiclient.Fetch(ctx, a.s3Client, AzureSEVSNPVersionList{}) if err != nil { + var notFoundErr *apiclient.NotFoundError + if errors.As(err, ¬FoundErr) { + return nil, nil + } return nil, err } return versions, nil diff --git a/internal/api/attestationconfigapi/reporter.go b/internal/api/attestationconfigapi/reporter.go index 4f38dd089a..b83b3a357b 100644 --- a/internal/api/attestationconfigapi/reporter.go +++ b/internal/api/attestationconfigapi/reporter.go @@ -11,6 +11,7 @@ package attestationconfigapi import ( "context" + "errors" "fmt" "path" "sort" @@ -33,6 +34,9 @@ const versionWindowSize = 15 var reportVersionDir = path.Join(attestationURLPath, variant.AzureSEVSNP{}.String(), cachedVersionsSubDir) +// ErrNoNewerVersion is returned if the input version is not newer than the latest API version. +var ErrNoNewerVersion = errors.New("input version is not newer than latest API version") + // UploadAzureSEVSNPVersionLatest 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. @@ -61,7 +65,7 @@ func (c Client) UploadAzureSEVSNPVersionLatest(ctx context.Context, inputVersion c.s3Client.Logger.Infof("Found minimal version: %+v with date: %s", minVersion, minDate) shouldUpdateAPI, err := isInputNewerThanOtherVersion(minVersion, latestAPIVersion) if err != nil { - return fmt.Errorf("comparing new and other versions: %w", err) + return ErrNoNewerVersion } if !shouldUpdateAPI { c.s3Client.Logger.Infof("Input version: %+v is not newer than latest API version: %+v", minVersion, latestAPIVersion) @@ -115,8 +119,9 @@ func (c Client) listCachedVersions(ctx context.Context) ([]string, error) { func (c Client) findMinVersion(ctx context.Context, versionDates []string) (AzureSEVSNPVersion, string, error) { var minimalVersion *AzureSEVSNPVersion var minimalDate string - sort.Strings(versionDates) // the oldest date with the minimal version should be taken + 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, reportedAzureSEVSNPVersionAPI{Version: date + ".json"}) if err != nil {