From d0e53cbb595ff34fb89487d225d7d304fcae4e3b Mon Sep 17 00:00:00 2001 From: Malte Poll Date: Tue, 23 May 2023 09:17:27 +0200 Subject: [PATCH] cli: image info (v2) --- cli/internal/cloudcmd/BUILD.bazel | 3 +- cli/internal/cloudcmd/clients.go | 7 +- cli/internal/cloudcmd/clients_test.go | 7 +- cli/internal/cloudcmd/create.go | 11 +- cli/internal/cmd/BUILD.bazel | 2 +- cli/internal/cmd/upgradeapply.go | 14 +- cli/internal/cmd/upgradeapply_test.go | 7 +- cli/internal/kubernetes/BUILD.bazel | 5 +- cli/internal/kubernetes/upgrade.go | 15 +- cli/internal/kubernetes/upgrade_test.go | 7 +- e2e/internal/upgrade/BUILD.bazel | 2 +- e2e/internal/upgrade/image.go | 31 +-- e2e/internal/upgrade/upgrade_test.go | 1 + image/upload/internal/cmd/api.go | 4 +- image/upload/internal/cmd/aws.go | 16 +- image/upload/internal/cmd/azure.go | 16 +- image/upload/internal/cmd/flags.go | 40 ++-- image/upload/internal/cmd/gcp.go | 16 +- image/upload/internal/cmd/nop.go | 16 +- image/upload/internal/cmd/upload.go | 26 +-- image/upload/upload.go | 4 +- internal/config/config.go | 17 ++ .../imagefetcher}/BUILD.bazel | 15 +- .../imagefetcher/imagefetcher.go | 93 ++++---- .../imagefetcher/imagefetcher_test.go | 218 +++++++----------- .../image => internal/imagefetcher}/raw.go | 2 +- .../imagefetcher}/raw_test.go | 2 +- internal/osimage/archive/archive.go | 2 +- internal/osimage/aws/awsupload.go | 12 +- internal/osimage/azure/azureupload.go | 22 +- internal/osimage/gcp/gcpupload.go | 16 +- internal/osimage/nop/BUILD.bazel | 1 + internal/osimage/nop/nop.go | 3 +- internal/osimage/osimage.go | 16 +- internal/versionsapi/cli/rm.go | 35 ++- internal/versionsapi/imageinfo.go | 48 ++-- internal/versionsapi/imageinfo_test.go | 138 ++++++----- 37 files changed, 429 insertions(+), 461 deletions(-) rename {cli/internal/image => internal/imagefetcher}/BUILD.bazel (75%) rename cli/internal/image/image.go => internal/imagefetcher/imagefetcher.go (59%) rename cli/internal/image/image_test.go => internal/imagefetcher/imagefetcher_test.go (55%) rename {cli/internal/image => internal/imagefetcher}/raw.go (99%) rename {cli/internal/image => internal/imagefetcher}/raw_test.go (99%) diff --git a/cli/internal/cloudcmd/BUILD.bazel b/cli/internal/cloudcmd/BUILD.bazel index cb3b3f5d2..3ffd9e8f4 100644 --- a/cli/internal/cloudcmd/BUILD.bazel +++ b/cli/internal/cloudcmd/BUILD.bazel @@ -17,7 +17,6 @@ go_library( deps = [ "//cli/internal/clusterid", "//cli/internal/iamid", - "//cli/internal/image", "//cli/internal/libvirt", "//cli/internal/terraform", "//internal/atls", @@ -27,6 +26,7 @@ go_library( "//internal/cloud/gcpshared", "//internal/config", "//internal/constants", + "//internal/imagefetcher", "//internal/variant", "@com_github_azure_azure_sdk_for_go//profiles/latest/attestation/attestation", "@com_github_azure_azure_sdk_for_go_sdk_azcore//policy", @@ -54,6 +54,7 @@ go_test( "//internal/cloud/cloudprovider", "//internal/cloud/gcpshared", "//internal/config", + "//internal/variant", "@com_github_hashicorp_terraform_json//:terraform-json", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", diff --git a/cli/internal/cloudcmd/clients.go b/cli/internal/cloudcmd/clients.go index e016de080..e5064bb4a 100644 --- a/cli/internal/cloudcmd/clients.go +++ b/cli/internal/cloudcmd/clients.go @@ -12,13 +12,16 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/config" + "github.com/edgelesssys/constellation/v2/internal/variant" tfjson "github.com/hashicorp/terraform-json" ) // imageFetcher gets an image reference from the versionsapi. type imageFetcher interface { - FetchReference(ctx context.Context, config *config.Config) (string, error) + FetchReference(ctx context.Context, + provider cloudprovider.Provider, attestationVariant variant.Variant, + image, region string, + ) (string, error) } type terraformClient interface { diff --git a/cli/internal/cloudcmd/clients_test.go b/cli/internal/cloudcmd/clients_test.go index a6b11487b..927cc315a 100644 --- a/cli/internal/cloudcmd/clients_test.go +++ b/cli/internal/cloudcmd/clients_test.go @@ -13,7 +13,7 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/config" + "github.com/edgelesssys/constellation/v2/internal/variant" tfjson "github.com/hashicorp/terraform-json" "go.uber.org/goleak" @@ -103,7 +103,10 @@ type stubImageFetcher struct { fetchReferenceErr error } -func (f *stubImageFetcher) FetchReference(_ context.Context, _ *config.Config) (string, error) { +func (f *stubImageFetcher) FetchReference(_ context.Context, + _ cloudprovider.Provider, _ variant.Variant, + _, _ string, +) (string, error) { return f.reference, f.fetchReferenceErr } diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index 854a1e6af..2bae9a365 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -25,12 +25,12 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" - "github.com/edgelesssys/constellation/v2/cli/internal/image" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" + "github.com/edgelesssys/constellation/v2/internal/imagefetcher" "github.com/edgelesssys/constellation/v2/internal/variant" ) @@ -48,7 +48,7 @@ type Creator struct { func NewCreator(out io.Writer) *Creator { return &Creator{ out: out, - image: image.New(), + image: imagefetcher.New(), newTerraformClient: func(ctx context.Context) (terraformClient, error) { return terraform.New(ctx, constants.TerraformWorkingDir) }, @@ -56,7 +56,7 @@ func NewCreator(out io.Writer) *Creator { return libvirt.New() }, newRawDownloader: func() rawDownloader { - return image.NewDownloader() + return imagefetcher.NewDownloader() }, policyPatcher: policyPatcher{}, } @@ -75,7 +75,10 @@ type CreateOptions struct { // Create creates the handed amount of instances and all the needed resources. func (c *Creator) Create(ctx context.Context, opts CreateOptions) (clusterid.File, error) { - image, err := c.image.FetchReference(ctx, opts.Config) + provider := opts.Config.GetProvider() + attestationVariant := opts.Config.GetAttestationConfig().GetVariant() + region := opts.Config.GetRegion() + image, err := c.image.FetchReference(ctx, provider, attestationVariant, opts.Config.Image, region) if err != nil { return clusterid.File{}, fmt.Errorf("fetching image reference: %w", err) } diff --git a/cli/internal/cmd/BUILD.bazel b/cli/internal/cmd/BUILD.bazel index 386adaa65..3bfb323c7 100644 --- a/cli/internal/cmd/BUILD.bazel +++ b/cli/internal/cmd/BUILD.bazel @@ -41,7 +41,6 @@ go_library( "//cli/internal/clusterid", "//cli/internal/helm", "//cli/internal/iamid", - "//cli/internal/image", "//cli/internal/kubernetes", "//cli/internal/libvirt", "//cli/internal/terraform", @@ -62,6 +61,7 @@ go_library( "//internal/file", "//internal/grpc/dialer", "//internal/grpc/retry", + "//internal/imagefetcher", "//internal/kms/uri", "//internal/kubernetes/kubectl", "//internal/license", diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index 09e662437..c28b7da4d 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -16,7 +16,6 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/helm" - "github.com/edgelesssys/constellation/v2/cli/internal/image" "github.com/edgelesssys/constellation/v2/cli/internal/kubernetes" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/upgrade" @@ -25,6 +24,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" + "github.com/edgelesssys/constellation/v2/internal/imagefetcher" "github.com/edgelesssys/constellation/v2/internal/variant" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -65,7 +65,7 @@ func runUpgradeApply(cmd *cobra.Command, _ []string) error { return err } - fetcher := image.New() + fetcher := imagefetcher.New() applyCmd := upgradeApplyCmd{upgrader: upgrader, log: log, fetcher: fetcher} return applyCmd.upgradeApply(cmd, fileHandler) @@ -194,7 +194,10 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, file file.Handler func (u *upgradeApplyCmd) parseUpgradeVars(cmd *cobra.Command, conf *config.Config, fetcher imageFetcher) ([]string, terraform.Variables, error) { // Fetch variables to execute Terraform script with - imageRef, err := fetcher.FetchReference(cmd.Context(), conf) + provider := conf.GetProvider() + attestationVariant := conf.GetAttestationConfig().GetVariant() + region := conf.GetRegion() + imageRef, err := fetcher.FetchReference(cmd.Context(), provider, attestationVariant, conf.Image, region) if err != nil { return nil, nil, fmt.Errorf("fetching image reference: %w", err) } @@ -264,7 +267,10 @@ func (u *upgradeApplyCmd) parseUpgradeVars(cmd *cobra.Command, conf *config.Conf } type imageFetcher interface { - FetchReference(ctx context.Context, conf *config.Config) (string, error) + FetchReference(ctx context.Context, + provider cloudprovider.Provider, attestationVariant variant.Variant, + image, region string, + ) (string, error) } // upgradeAttestConfigIfDiff checks if the locally configured measurements are different from the cluster's measurements. diff --git a/cli/internal/cmd/upgradeapply_test.go b/cli/internal/cmd/upgradeapply_test.go index 6b6cc933b..88b9aa124 100644 --- a/cli/internal/cmd/upgradeapply_test.go +++ b/cli/internal/cmd/upgradeapply_test.go @@ -200,6 +200,9 @@ type stubImageFetcher struct { fetchReferenceErr error } -func (s stubImageFetcher) FetchReference(context.Context, *config.Config) (string, error) { - return "", s.fetchReferenceErr +func (f stubImageFetcher) FetchReference(_ context.Context, + _ cloudprovider.Provider, _ variant.Variant, + _, _ string, +) (string, error) { + return "", f.fetchReferenceErr } diff --git a/cli/internal/kubernetes/BUILD.bazel b/cli/internal/kubernetes/BUILD.bazel index a676eba4c..53b795e20 100644 --- a/cli/internal/kubernetes/BUILD.bazel +++ b/cli/internal/kubernetes/BUILD.bazel @@ -12,14 +12,15 @@ go_library( visibility = ["//cli:__subpackages__"], deps = [ "//cli/internal/helm", - "//cli/internal/image", "//cli/internal/terraform", "//cli/internal/upgrade", "//internal/attestation/measurements", + "//internal/cloud/cloudprovider", "//internal/compatibility", "//internal/config", "//internal/constants", "//internal/file", + "//internal/imagefetcher", "//internal/kubernetes", "//internal/kubernetes/kubectl", "//internal/variant", @@ -45,10 +46,12 @@ go_test( embed = [":kubernetes"], deps = [ "//internal/attestation/measurements", + "//internal/cloud/cloudprovider", "//internal/compatibility", "//internal/config", "//internal/constants", "//internal/logger", + "//internal/variant", "//internal/versions", "//internal/versions/components", "//operators/constellation-node-operator/api/v1alpha1", diff --git a/cli/internal/kubernetes/upgrade.go b/cli/internal/kubernetes/upgrade.go index 5ffc6c1ff..316b1b326 100644 --- a/cli/internal/kubernetes/upgrade.go +++ b/cli/internal/kubernetes/upgrade.go @@ -16,14 +16,15 @@ import ( "time" "github.com/edgelesssys/constellation/v2/cli/internal/helm" - "github.com/edgelesssys/constellation/v2/cli/internal/image" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/upgrade" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" + "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" + "github.com/edgelesssys/constellation/v2/internal/imagefetcher" internalk8s "github.com/edgelesssys/constellation/v2/internal/kubernetes" "github.com/edgelesssys/constellation/v2/internal/kubernetes/kubectl" "github.com/edgelesssys/constellation/v2/internal/variant" @@ -122,7 +123,7 @@ func NewUpgrader(ctx context.Context, outWriter io.Writer, log debugLog) (*Upgra stableInterface: &stableClient{client: kubeClient}, dynamicInterface: &NodeVersionClient{client: unstructuredClient}, helmClient: helmClient, - imageFetcher: image.New(), + imageFetcher: imagefetcher.New(), outWriter: outWriter, tfUpgrader: tfUpgrader, log: log, @@ -164,7 +165,10 @@ func (u *Upgrader) UpgradeHelmServices(ctx context.Context, config *config.Confi // UpgradeNodeVersion upgrades the cluster's NodeVersion object and in turn triggers image & k8s version upgrades. // The versions set in the config are validated against the versions running in the cluster. func (u *Upgrader) UpgradeNodeVersion(ctx context.Context, conf *config.Config) error { - imageReference, err := u.imageFetcher.FetchReference(ctx, conf) + provider := conf.GetProvider() + attestationVariant := conf.GetAttestationConfig().GetVariant() + region := conf.GetRegion() + imageReference, err := u.imageFetcher.FetchReference(ctx, provider, attestationVariant, conf.Image, region) if err != nil { return fmt.Errorf("fetching image reference: %w", err) } @@ -526,5 +530,8 @@ type debugLog interface { // imageFetcher gets an image reference from the versionsapi. type imageFetcher interface { - FetchReference(ctx context.Context, config *config.Config) (string, error) + FetchReference(ctx context.Context, + provider cloudprovider.Provider, attestationVariant variant.Variant, + image, region string, + ) (string, error) } diff --git a/cli/internal/kubernetes/upgrade_test.go b/cli/internal/kubernetes/upgrade_test.go index bcbae368d..fd0e3bf5e 100644 --- a/cli/internal/kubernetes/upgrade_test.go +++ b/cli/internal/kubernetes/upgrade_test.go @@ -14,10 +14,12 @@ import ( "testing" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" + "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/variant" "github.com/edgelesssys/constellation/v2/internal/versions" "github.com/edgelesssys/constellation/v2/internal/versions/components" updatev1alpha1 "github.com/edgelesssys/constellation/v2/operators/constellation-node-operator/v2/api/v1alpha1" @@ -550,6 +552,9 @@ type stubImageFetcher struct { fetchReferenceErr error } -func (f *stubImageFetcher) FetchReference(_ context.Context, _ *config.Config) (string, error) { +func (f *stubImageFetcher) FetchReference(_ context.Context, + _ cloudprovider.Provider, _ variant.Variant, + _, _ string, +) (string, error) { return f.reference, f.fetchReferenceErr } diff --git a/e2e/internal/upgrade/BUILD.bazel b/e2e/internal/upgrade/BUILD.bazel index 23160f1a6..26c6c3b7a 100644 --- a/e2e/internal/upgrade/BUILD.bazel +++ b/e2e/internal/upgrade/BUILD.bazel @@ -14,10 +14,10 @@ go_library( "//internal/attestation/measurements", "//internal/cloud/cloudprovider", "//internal/constants", + "//internal/imagefetcher", "//internal/logger", "//internal/variant", "//internal/versionsapi", - "//internal/versionsapi/fetcher", "@sh_helm_helm_v3//pkg/action", "@sh_helm_helm_v3//pkg/cli", ], diff --git a/e2e/internal/upgrade/image.go b/e2e/internal/upgrade/image.go index 64ccdab25..7e8d28e8e 100644 --- a/e2e/internal/upgrade/image.go +++ b/e2e/internal/upgrade/image.go @@ -10,14 +10,13 @@ package upgrade import ( "context" - "errors" "net/http" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" + "github.com/edgelesssys/constellation/v2/internal/imagefetcher" "github.com/edgelesssys/constellation/v2/internal/variant" "github.com/edgelesssys/constellation/v2/internal/versionsapi" - "github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher" ) type upgradeInfo struct { @@ -27,13 +26,12 @@ type upgradeInfo struct { } func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider, - attestationVariant variant.Variant, toImage string, + attestationVariant variant.Variant, toImage, region string, ) (upgradeInfo, error) { info := upgradeInfo{ measurements: make(measurements.M), shortPath: toImage, } - versionsClient := fetcher.NewFetcher() ver, err := versionsapi.NewVersionFromShortPath(toImage, versionsapi.VersionKindImage) if err != nil { @@ -55,11 +53,8 @@ func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider, } info.measurements = fetchedMeasurements - imageRef, err := fetchImageRef(ctx, versionsClient, csp, versionsapi.ImageInfo{ - Ref: ver.Ref, - Stream: ver.Stream, - Version: ver.Version, - }) + fetcher := imagefetcher.New() + imageRef, err := fetcher.FetchReference(ctx, csp, attestationVariant, toImage, region) if err != nil { return upgradeInfo{}, err } @@ -67,21 +62,3 @@ func fetchUpgradeInfo(ctx context.Context, csp cloudprovider.Provider, return info, nil } - -func fetchImageRef(ctx context.Context, client *fetcher.Fetcher, csp cloudprovider.Provider, imageInfo versionsapi.ImageInfo) (string, error) { - imageInfo, err := client.FetchImageInfo(ctx, imageInfo) - if err != nil { - return "", err - } - - switch csp { - case cloudprovider.GCP: - return imageInfo.GCP["sev-es"], nil - case cloudprovider.Azure: - return imageInfo.Azure["cvm"], nil - case cloudprovider.AWS: - return imageInfo.AWS["eu-central-1"], nil - default: - return "", errors.New("finding wanted image") - } -} diff --git a/e2e/internal/upgrade/upgrade_test.go b/e2e/internal/upgrade/upgrade_test.go index ee5414e09..8d524a51b 100644 --- a/e2e/internal/upgrade/upgrade_test.go +++ b/e2e/internal/upgrade/upgrade_test.go @@ -245,6 +245,7 @@ func writeUpgradeConfig(require *require.Assertions, image string, kubernetes st cfg.GetProvider(), cfg.GetAttestationConfig().GetVariant(), image, + cfg.GetRegion(), ) require.NoError(err) diff --git a/image/upload/internal/cmd/api.go b/image/upload/internal/cmd/api.go index f2b3445b3..6da36bd8c 100644 --- a/image/upload/internal/cmd/api.go +++ b/image/upload/internal/cmd/api.go @@ -16,10 +16,10 @@ import ( type archivist interface { Archive(ctx context.Context, - version versionsapi.Version, csp, variant string, img io.Reader, + version versionsapi.Version, csp, attestationVariant string, img io.Reader, ) (string, error) } type uploader interface { - Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) + Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) } diff --git a/image/upload/internal/cmd/aws.go b/image/upload/internal/cmd/aws.go index 9817420db..93bbc599c 100644 --- a/image/upload/internal/cmd/aws.go +++ b/image/upload/internal/cmd/aws.go @@ -84,14 +84,14 @@ func runAWS(cmd *cobra.Command, _ []string) error { } uploadReq := &osimage.UploadRequest{ - Provider: flags.provider, - Version: flags.version, - Variant: flags.variant, - SBDatabase: sbDatabase, - UEFIVarStore: uefiVarStore, - Size: size, - Timestamp: flags.timestamp, - Image: file, + Provider: flags.provider, + Version: flags.version, + AttestationVariant: flags.attestationVariant, + SBDatabase: sbDatabase, + UEFIVarStore: uefiVarStore, + Size: size, + Timestamp: flags.timestamp, + Image: file, } return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out) } diff --git a/image/upload/internal/cmd/azure.go b/image/upload/internal/cmd/azure.go index aa760c069..6a0b35db9 100644 --- a/image/upload/internal/cmd/azure.go +++ b/image/upload/internal/cmd/azure.go @@ -85,14 +85,14 @@ func runAzure(cmd *cobra.Command, _ []string) error { } uploadReq := &osimage.UploadRequest{ - Provider: flags.provider, - Version: flags.version, - Variant: flags.variant, - SBDatabase: sbDatabase, - UEFIVarStore: uefiVarStore, - Size: size, - Timestamp: flags.timestamp, - Image: file, + Provider: flags.provider, + Version: flags.version, + AttestationVariant: flags.attestationVariant, + SBDatabase: sbDatabase, + UEFIVarStore: uefiVarStore, + Size: size, + Timestamp: flags.timestamp, + Image: file, } return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out) } diff --git a/image/upload/internal/cmd/flags.go b/image/upload/internal/cmd/flags.go index 06b3fbf42..37a6f57ab 100644 --- a/image/upload/internal/cmd/flags.go +++ b/image/upload/internal/cmd/flags.go @@ -18,16 +18,16 @@ import ( ) type commonFlags struct { - rawImage string - pki string - provider cloudprovider.Provider - variant string - version versionsapi.Version - timestamp time.Time - region string - bucket string - out string - logLevel zapcore.Level + rawImage string + pki string + provider cloudprovider.Provider + attestationVariant string + version versionsapi.Version + timestamp time.Time + region string + bucket string + out string + logLevel zapcore.Level } func parseCommonFlags(cmd *cobra.Command) (commonFlags, error) { @@ -43,7 +43,7 @@ func parseCommonFlags(cmd *cobra.Command) (commonFlags, error) { if pki == "" { pki = filepath.Join(workspaceDir, "image/pki") } - variant, err := cmd.Flags().GetString("variant") + attestationVariant, err := cmd.Flags().GetString("attestation-variant") if err != nil { return commonFlags{}, err } @@ -88,15 +88,15 @@ func parseCommonFlags(cmd *cobra.Command) (commonFlags, error) { } return commonFlags{ - rawImage: rawImage, - pki: pki, - variant: variant, - version: ver, - timestamp: timestmp, - region: region, - bucket: bucket, - out: out, - logLevel: logLevel, + rawImage: rawImage, + pki: pki, + attestationVariant: attestationVariant, + version: ver, + timestamp: timestmp, + region: region, + bucket: bucket, + out: out, + logLevel: logLevel, }, nil } diff --git a/image/upload/internal/cmd/gcp.go b/image/upload/internal/cmd/gcp.go index b562e9ee1..ca08bf212 100644 --- a/image/upload/internal/cmd/gcp.go +++ b/image/upload/internal/cmd/gcp.go @@ -85,14 +85,14 @@ func runGCP(cmd *cobra.Command, _ []string) error { } uploadReq := &osimage.UploadRequest{ - Provider: flags.provider, - Version: flags.version, - Variant: flags.variant, - SBDatabase: sbDatabase, - UEFIVarStore: uefiVarStore, - Size: size, - Timestamp: flags.timestamp, - Image: file, + Provider: flags.provider, + Version: flags.version, + AttestationVariant: flags.attestationVariant, + SBDatabase: sbDatabase, + UEFIVarStore: uefiVarStore, + Size: size, + Timestamp: flags.timestamp, + Image: file, } return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out) } diff --git a/image/upload/internal/cmd/nop.go b/image/upload/internal/cmd/nop.go index abc415392..df541fd31 100644 --- a/image/upload/internal/cmd/nop.go +++ b/image/upload/internal/cmd/nop.go @@ -68,14 +68,14 @@ func runNOP(cmd *cobra.Command, provider cloudprovider.Provider, _ []string) err } uploadReq := &osimage.UploadRequest{ - Provider: flags.provider, - Version: flags.version, - Variant: flags.variant, - SBDatabase: sbDatabase, - UEFIVarStore: uefiVarStore, - Size: size, - Timestamp: flags.timestamp, - Image: file, + Provider: flags.provider, + Version: flags.version, + AttestationVariant: flags.attestationVariant, + SBDatabase: sbDatabase, + UEFIVarStore: uefiVarStore, + Size: size, + Timestamp: flags.timestamp, + Image: file, } return uploadImage(cmd.Context(), archiveC, uploadC, uploadReq, out) } diff --git a/image/upload/internal/cmd/upload.go b/image/upload/internal/cmd/upload.go index 7439efada..fe6380f3e 100644 --- a/image/upload/internal/cmd/upload.go +++ b/image/upload/internal/cmd/upload.go @@ -13,14 +13,13 @@ import ( "io" "strings" - "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/osimage" "github.com/edgelesssys/constellation/v2/internal/versionsapi" ) func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req *osimage.UploadRequest, out io.Writer) error { // upload to S3 archive - archiveURL, err := archiveC.Archive(ctx, req.Version, strings.ToLower(req.Provider.String()), req.Variant, req.Image) + archiveURL, err := archiveC.Archive(ctx, req.Version, strings.ToLower(req.Provider.String()), req.AttestationVariant, req.Image) if err != nil { return err } @@ -34,8 +33,12 @@ func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req return err } if len(imageReferences) == 0 { - imageReferences = map[string]string{ - req.Variant: archiveURL, + imageReferences = []versionsapi.ImageInfoEntry{ + { + CSP: req.Provider.String(), + AttestationVariant: req.AttestationVariant, + Reference: archiveURL, + }, } } @@ -43,20 +46,7 @@ func uploadImage(ctx context.Context, archiveC archivist, uploadC uploader, req Ref: req.Version.Ref, Stream: req.Version.Stream, Version: req.Version.Version, - } - switch req.Provider { - case cloudprovider.AWS: - imageInfo.AWS = imageReferences - case cloudprovider.Azure: - imageInfo.Azure = imageReferences - case cloudprovider.GCP: - imageInfo.GCP = imageReferences - case cloudprovider.OpenStack: - imageInfo.OpenStack = imageReferences - case cloudprovider.QEMU: - imageInfo.QEMU = imageReferences - default: - return fmt.Errorf("uploading image: cloud provider %s is not yet supported", req.Provider.String()) + List: imageReferences, } if err := json.NewEncoder(out).Encode(imageInfo); err != nil { diff --git a/image/upload/upload.go b/image/upload/upload.go index fe0b79e8e..36be1d8a4 100644 --- a/image/upload/upload.go +++ b/image/upload/upload.go @@ -42,7 +42,7 @@ func newRootCmd() *cobra.Command { rootCmd.PersistentFlags().String("raw-image", "", "Path to os image in CSP specific format that should be uploaded.") rootCmd.PersistentFlags().String("pki", "", "Base path to the PKI (secure boot signing) files.") - rootCmd.PersistentFlags().String("variant", "", "Variant of the image being uploaded.") + rootCmd.PersistentFlags().String("attestation-variant", "", "Attestation variant of the image being uploaded.") rootCmd.PersistentFlags().String("version", "", "Shortname of the os image version.") rootCmd.PersistentFlags().String("timestamp", "", "Optional timestamp to use for resource names. Uses format 2006-01-02T15:04:05Z07:00.") rootCmd.PersistentFlags().String("region", "eu-central-1", "AWS region of the archive S3 bucket") @@ -50,7 +50,7 @@ func newRootCmd() *cobra.Command { rootCmd.PersistentFlags().String("out", "", "Optional path to write the upload result to. If not set, the result is written to stdout.") rootCmd.PersistentFlags().Bool("verbose", false, "Enable verbose output") must(rootCmd.MarkPersistentFlagRequired("raw-image")) - must(rootCmd.MarkPersistentFlagRequired("variant")) + must(rootCmd.MarkPersistentFlagRequired("attestation-variant")) must(rootCmd.MarkPersistentFlagRequired("version")) rootCmd.AddCommand(cmd.NewAWSCmd()) diff --git a/internal/config/config.go b/internal/config/config.go index 01748857f..897a232f7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -546,6 +546,23 @@ func (c *Config) GetAttestationConfig() AttestationCfg { return &DummyCfg{} } +// GetRegion returns the configured region. +func (c *Config) GetRegion() string { + switch c.GetProvider() { + case cloudprovider.AWS: + return c.Provider.AWS.Region + case cloudprovider.Azure: + return c.Provider.Azure.Location + case cloudprovider.GCP: + return c.Provider.GCP.Region + case cloudprovider.OpenStack: + return c.Provider.OpenStack.RegionName + case cloudprovider.QEMU: + return "" + } + return "" +} + // UpdateMAAURL updates the MAA URL in the config. func (c *Config) UpdateMAAURL(maaURL string) { if c.Attestation.AzureSEVSNP != nil { diff --git a/cli/internal/image/BUILD.bazel b/internal/imagefetcher/BUILD.bazel similarity index 75% rename from cli/internal/image/BUILD.bazel rename to internal/imagefetcher/BUILD.bazel index 68a4cdcab..6556c7891 100644 --- a/cli/internal/image/BUILD.bazel +++ b/internal/imagefetcher/BUILD.bazel @@ -2,16 +2,15 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") load("//bazel/go:go_test.bzl", "go_test") go_library( - name = "image", + name = "imagefetcher", srcs = [ - "image.go", + "imagegfetcher.go", "raw.go", ], - importpath = "github.com/edgelesssys/constellation/v2/cli/internal/image", + importpath = "github.com/edgelesssys/constellation/v2/internal/imagefetcher", visibility = ["//cli:__subpackages__"], deps = [ "//internal/cloud/cloudprovider", - "//internal/config", "//internal/variant", "//internal/versionsapi", "//internal/versionsapi/fetcher", @@ -21,16 +20,16 @@ go_library( ) go_test( - name = "image_test", + name = "imagefetcher_test", srcs = [ - "image_test.go", + "imagefetcher_test.go", "raw_test.go", ], - embed = [":image"], + embed = [":imagefetcher"], deps = [ "//internal/cloud/cloudprovider", - "//internal/config", "//internal/file", + "//internal/variant", "//internal/versionsapi", "@com_github_spf13_afero//:afero", "@com_github_stretchr_testify//assert", diff --git a/cli/internal/image/image.go b/internal/imagefetcher/imagefetcher.go similarity index 59% rename from cli/internal/image/image.go rename to internal/imagefetcher/imagefetcher.go index c9e246389..f18b6bf8a 100644 --- a/cli/internal/image/image.go +++ b/internal/imagefetcher/imagefetcher.go @@ -5,11 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only */ /* -Package image provides helping wrappers around a versionsapi fetcher. +Package imagefetcher provides helping wrappers around a versionsapi fetcher. It also enables local image overrides and download of raw images. */ -package image +package imagefetcher import ( "context" @@ -20,7 +20,6 @@ import ( "regexp" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/variant" "github.com/edgelesssys/constellation/v2/internal/versionsapi" "github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher" @@ -42,14 +41,11 @@ func New() *Fetcher { } // FetchReference fetches the image reference for a given image version uid, CSP and image variant. -func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (string, error) { - provider := config.GetProvider() - variant, err := imageVariant(provider, config) - if err != nil { - return "", fmt.Errorf("determining variant: %w", err) - } - - ver, err := versionsapi.NewVersionFromShortPath(config.Image, versionsapi.VersionKindImage) +func (f *Fetcher) FetchReference(ctx context.Context, + provider cloudprovider.Provider, attestationVariant variant.Variant, + image, region string, +) (string, error) { + ver, err := versionsapi.NewVersionFromShortPath(image, versionsapi.VersionKindImage) if err != nil { return "", fmt.Errorf("parsing config image short path: %w", err) } @@ -83,29 +79,18 @@ func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (st return "", fmt.Errorf("validating image info file: %w", err) } - return getReferenceFromImageInfo(provider, variant, imgInfo) + return getReferenceFromImageInfo(provider, attestationVariant.String(), imgInfo, filters(provider, region)...) } -// imageVariant returns the image variant for a given CSP and configuration. -func imageVariant(provider cloudprovider.Provider, config *config.Config) (string, error) { +func filters(provider cloudprovider.Provider, region string) []filter { + var filters []filter switch provider { case cloudprovider.AWS: - return config.Provider.AWS.Region, nil - case cloudprovider.Azure: - if config.GetAttestationConfig().GetVariant().Equal(variant.AzureTrustedLaunch{}) { - return "trustedlaunch", nil - } - return "cvm", nil - - case cloudprovider.GCP: - return "sev-es", nil - case cloudprovider.OpenStack: - return "sev", nil - case cloudprovider.QEMU: - return "default", nil - default: - return "", fmt.Errorf("unsupported provider: %s", provider) + filters = append(filters, func(i versionsapi.ImageInfoEntry) bool { + return i.Region == region + }) } + return filters } func getFromFile(fs *afero.Afero, imgInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) { @@ -132,36 +117,36 @@ func imageInfoFilename(imgInfo versionsapi.ImageInfo) string { } // getReferenceFromImageInfo returns the image reference for a given CSP and image variant. -func getReferenceFromImageInfo(provider cloudprovider.Provider, variant string, imgInfo versionsapi.ImageInfo, +func getReferenceFromImageInfo(provider cloudprovider.Provider, + attestationVariant string, imgInfo versionsapi.ImageInfo, + filt ...filter, ) (string, error) { - var providerList map[string]string - switch provider { - case cloudprovider.AWS: - providerList = imgInfo.AWS - case cloudprovider.Azure: - providerList = imgInfo.Azure - case cloudprovider.GCP: - providerList = imgInfo.GCP - case cloudprovider.OpenStack: - providerList = imgInfo.OpenStack - case cloudprovider.QEMU: - providerList = imgInfo.QEMU - default: - return "", fmt.Errorf("image not available in image info for CSP %q", provider.String()) + var correctVariant versionsapi.ImageInfoEntry + var found bool +variantLoop: + for _, variant := range imgInfo.List { + gotCSP := cloudprovider.FromString(variant.CSP) + if gotCSP != provider || variant.AttestationVariant != attestationVariant { + continue + } + for _, f := range filt { + if !f(variant) { + continue variantLoop + } + } + correctVariant = variant + found = true + break + } + if !found { + return "", fmt.Errorf("image not available in image info for CSP %q, variant %q and other filters", provider.String(), attestationVariant) } - if providerList == nil { - return "", fmt.Errorf("image not available in image info for CSP %q", provider.String()) - } - - ref, ok := providerList[variant] - if !ok { - return "", fmt.Errorf("image not available in image info for variant %q", variant) - } - - return ref, nil + return correctVariant.Reference, nil } type versionsAPIImageInfoFetcher interface { FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) } + +type filter func(versionsapi.ImageInfoEntry) bool diff --git a/cli/internal/image/image_test.go b/internal/imagefetcher/imagefetcher_test.go similarity index 55% rename from cli/internal/image/image_test.go rename to internal/imagefetcher/imagefetcher_test.go index 88d9082cb..5b5e75280 100644 --- a/cli/internal/image/image_test.go +++ b/internal/imagefetcher/imagefetcher_test.go @@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ -package image +package imagefetcher import ( "context" @@ -14,8 +14,8 @@ import ( "testing" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/file" + "github.com/edgelesssys/constellation/v2/internal/variant" "github.com/edgelesssys/constellation/v2/internal/versionsapi" "github.com/spf13/afero" "github.com/stretchr/testify/assert" @@ -32,49 +32,88 @@ func TestGetReference(t *testing.T) { info versionsapi.ImageInfo provider cloudprovider.Provider variant string + filter filter wantReference string wantErr bool }{ + "reference exists with filter": { + info: versionsapi.ImageInfo{ + List: []versionsapi.ImageInfoEntry{ + {CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someReference"}, + {CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someOtherReference", Region: "someRegion"}, + }, + }, + provider: cloudprovider.AWS, + variant: "aws-nitro-tpm", + filter: func(entry versionsapi.ImageInfoEntry) bool { + return entry.Region == "someRegion" + }, + wantReference: "someOtherReference", + }, "reference exists aws": { - info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}}, + info: versionsapi.ImageInfo{ + List: []versionsapi.ImageInfoEntry{ + {CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someReference"}, + }, + }, provider: cloudprovider.AWS, - variant: "someVariant", + variant: "aws-nitro-tpm", wantReference: "someReference", }, "reference exists azure": { - info: versionsapi.ImageInfo{Azure: map[string]string{"someVariant": "someReference"}}, + info: versionsapi.ImageInfo{ + List: []versionsapi.ImageInfoEntry{ + {CSP: "azure", AttestationVariant: "azure-sev-snp", Reference: "someReference"}, + }, + }, provider: cloudprovider.Azure, - variant: "someVariant", + variant: "azure-sev-snp", wantReference: "someReference", }, "reference exists gcp": { - info: versionsapi.ImageInfo{GCP: map[string]string{"someVariant": "someReference"}}, + info: versionsapi.ImageInfo{ + List: []versionsapi.ImageInfoEntry{ + {CSP: "gcp", AttestationVariant: "gcp-sev-es", Reference: "someReference"}, + }, + }, provider: cloudprovider.GCP, - variant: "someVariant", + variant: "gcp-sev-es", wantReference: "someReference", }, "reference exists openstack": { - info: versionsapi.ImageInfo{OpenStack: map[string]string{"someVariant": "someReference"}}, + info: versionsapi.ImageInfo{ + List: []versionsapi.ImageInfoEntry{ + {CSP: "openstack", AttestationVariant: "qemu-vtpm", Reference: "someReference"}, + }, + }, provider: cloudprovider.OpenStack, - variant: "someVariant", + variant: "qemu-vtpm", wantReference: "someReference", }, "reference exists qemu": { - info: versionsapi.ImageInfo{QEMU: map[string]string{"someVariant": "someReference"}}, + info: versionsapi.ImageInfo{ + List: []versionsapi.ImageInfoEntry{ + {CSP: "qemu", AttestationVariant: "qemu-vtpm", Reference: "someReference"}, + }, + }, provider: cloudprovider.QEMU, - variant: "someVariant", + variant: "qemu-vtpm", wantReference: "someReference", }, "csp does not exist": { - info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}}, + info: versionsapi.ImageInfo{List: []versionsapi.ImageInfoEntry{}}, provider: cloudprovider.Unknown, variant: "someVariant", wantErr: true, }, "variant does not exist": { - info: versionsapi.ImageInfo{AWS: map[string]string{"someVariant": "someReference"}}, + info: versionsapi.ImageInfo{ + List: []versionsapi.ImageInfoEntry{ + {CSP: "aws", AttestationVariant: "dummy", Reference: "someReference"}, + }, + }, provider: cloudprovider.AWS, - variant: "nonExistingVariant", + variant: "aws-nitro-tpm", wantErr: true, }, "info is empty": { @@ -83,12 +122,6 @@ func TestGetReference(t *testing.T) { variant: "someVariant", wantErr: true, }, - "csp is nil": { - info: versionsapi.ImageInfo{AWS: nil}, - provider: cloudprovider.AWS, - variant: "someVariant", - wantErr: true, - }, } for name, tc := range testCases { @@ -96,7 +129,11 @@ func TestGetReference(t *testing.T) { assert := assert.New(t) require := require.New(t) - reference, err := getReferenceFromImageInfo(tc.provider, tc.variant, tc.info) + var filters []filter + if tc.filter != nil { + filters = []filter{tc.filter} + } + reference, err := getReferenceFromImageInfo(tc.provider, tc.variant, tc.info, filters...) if tc.wantErr { assert.Error(err) @@ -108,79 +145,6 @@ func TestGetReference(t *testing.T) { } } -func TestImageVariant(t *testing.T) { - testCases := map[string]struct { - csp cloudprovider.Provider - config *config.Config - wantVariant string - wantErr bool - }{ - "AWS region": { - csp: cloudprovider.AWS, - config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{ - AWS: &config.AWSConfig{Region: "someRegion"}, - }}, - wantVariant: "someRegion", - }, - "Azure cvm": { - csp: cloudprovider.Azure, - config: &config.Config{ - Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}}, - Attestation: config.AttestationConfig{AzureSEVSNP: &config.AzureSEVSNP{}}, - }, - wantVariant: "cvm", - }, - "Azure trustedlaunch": { - csp: cloudprovider.Azure, - config: &config.Config{ - Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}}, - Attestation: config.AttestationConfig{AzureTrustedLaunch: &config.AzureTrustedLaunch{}}, - }, - wantVariant: "trustedlaunch", - }, - "GCP": { - csp: cloudprovider.GCP, - config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{ - GCP: &config.GCPConfig{}, - }}, - wantVariant: "sev-es", - }, - "OpenStack": { - csp: cloudprovider.OpenStack, - config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{ - OpenStack: &config.OpenStackConfig{}, - }}, - wantVariant: "sev", - }, - "QEMU": { - csp: cloudprovider.QEMU, - config: &config.Config{Image: "someImage", Provider: config.ProviderConfig{ - QEMU: &config.QEMUConfig{}, - }}, - wantVariant: "default", - }, - "invalid": { - csp: cloudprovider.Provider(9999), - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - vari, err := imageVariant(tc.csp, tc.config) - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - assert.Equal(tc.wantVariant, vari) - }) - } -} - func TestFetchReference(t *testing.T) { img := "ref/abc/stream/nightly/v1.2.3" newImgInfo := func() versionsapi.ImageInfo { @@ -188,49 +152,47 @@ func TestFetchReference(t *testing.T) { Ref: "abc", Stream: "nightly", Version: "v1.2.3", - QEMU: map[string]string{"default": "someReference"}, - AWS: map[string]string{"foo": "bar"}, - Azure: map[string]string{"foo": "bar"}, - GCP: map[string]string{"foo": "bar"}, + List: []versionsapi.ImageInfoEntry{ + { + CSP: "qemu", + AttestationVariant: "dummy", + Reference: "someReference", + }, + }, } } imgInfoPath := imageInfoFilename(newImgInfo()) testCases := map[string]struct { - config *config.Config + provider cloudprovider.Provider + image string imageInfoFetcher versionsAPIImageInfoFetcher localFile []byte wantReference string wantErr bool }{ "reference fetched remotely": { - config: &config.Config{ - Image: img, - Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}, - }, + provider: cloudprovider.QEMU, + image: img, imageInfoFetcher: &stubVersionsAPIImageFetcher{ fetchImageInfoInfo: newImgInfo(), }, wantReference: "someReference", }, "reference fetched remotely fails": { - config: &config.Config{ - Image: img, - Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}, - }, + provider: cloudprovider.QEMU, + image: img, imageInfoFetcher: &stubVersionsAPIImageFetcher{ fetchImageInfoErr: errors.New("failed"), }, wantErr: true, }, "reference fetched locally": { - config: &config.Config{ - Image: img, - Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}, - }, + provider: cloudprovider.QEMU, + image: img, localFile: func() []byte { info := newImgInfo() - info.QEMU["default"] = "localOverrideReference" + info.List[0].Reference = "localOverrideReference" file, err := json.Marshal(info) require.NoError(t, err) return file @@ -238,16 +200,14 @@ func TestFetchReference(t *testing.T) { wantReference: "localOverrideReference", }, "local file first": { - config: &config.Config{ - Image: img, - Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}, - }, + provider: cloudprovider.QEMU, + image: img, imageInfoFetcher: &stubVersionsAPIImageFetcher{ fetchImageInfoInfo: newImgInfo(), }, localFile: func() []byte { info := newImgInfo() - info.QEMU["default"] = "localOverrideReference" + info.List[0].Reference = "localOverrideReference" file, err := json.Marshal(info) require.NoError(t, err) return file @@ -255,18 +215,14 @@ func TestFetchReference(t *testing.T) { wantReference: "localOverrideReference", }, "local file is invalid": { - config: &config.Config{ - Image: img, - Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}, - }, + provider: cloudprovider.QEMU, + image: img, localFile: []byte("invalid"), wantErr: true, }, "local file has invalid image info": { - config: &config.Config{ - Image: img, - Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}, - }, + provider: cloudprovider.QEMU, + image: img, localFile: func() []byte { info := newImgInfo() info.Ref = "" @@ -277,11 +233,9 @@ func TestFetchReference(t *testing.T) { wantErr: true, }, "image version does not exist": { - config: &config.Config{ - Image: "nonExistingImageName", - Provider: config.ProviderConfig{QEMU: &config.QEMUConfig{}}, - }, - wantErr: true, + provider: cloudprovider.QEMU, + image: "nonExistingImageName", + wantErr: true, }, } @@ -302,7 +256,7 @@ func TestFetchReference(t *testing.T) { fs: af, } - reference, err := fetcher.FetchReference(context.Background(), tc.config) + reference, err := fetcher.FetchReference(context.Background(), tc.provider, variant.Dummy{}, tc.image, "someRegion") if tc.wantErr { assert.Error(err) diff --git a/cli/internal/image/raw.go b/internal/imagefetcher/raw.go similarity index 99% rename from cli/internal/image/raw.go rename to internal/imagefetcher/raw.go index ecf3b2c09..593b0d9e7 100644 --- a/cli/internal/image/raw.go +++ b/internal/imagefetcher/raw.go @@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ -package image +package imagefetcher import ( "context" diff --git a/cli/internal/image/raw_test.go b/internal/imagefetcher/raw_test.go similarity index 99% rename from cli/internal/image/raw_test.go rename to internal/imagefetcher/raw_test.go index d7b61e506..e2bbd8b9d 100644 --- a/cli/internal/image/raw_test.go +++ b/internal/imagefetcher/raw_test.go @@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH SPDX-License-Identifier: AGPL-3.0-only */ -package image +package imagefetcher import ( "bytes" diff --git a/internal/osimage/archive/archive.go b/internal/osimage/archive/archive.go index 5cb833b7a..4014bacea 100644 --- a/internal/osimage/archive/archive.go +++ b/internal/osimage/archive/archive.go @@ -51,7 +51,7 @@ func (a *Archivist) Archive(ctx context.Context, version versionsapi.Version, cs if err != nil { return "", err } - a.log.Debugf("Archiving OS image %s %s %v to s3://%v/%v", csp, variant, version.ShortPath(), a.bucket, key) + a.log.Debugf("Archiving OS image %s %s %v to s3://%v/%v", csp, attestationVariant, version.ShortPath(), a.bucket, key) _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{ Bucket: &a.bucket, Key: &key, diff --git a/internal/osimage/aws/awsupload.go b/internal/osimage/aws/awsupload.go index 43fab5d6e..c5b722186 100644 --- a/internal/osimage/aws/awsupload.go +++ b/internal/osimage/aws/awsupload.go @@ -72,7 +72,7 @@ func New(region, bucketName string, log *logger.Logger) (*Uploader, error) { } // Upload uploads an OS image to AWS. -func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) { +func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { blobName := fmt.Sprintf("image-%s-%s-%d.raw", req.Version.Stream, req.Version.Version, req.Timestamp.Unix()) imageName := imageName(req.Version, req.Timestamp) allRegions := []string{u.region} @@ -129,6 +129,7 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[ } // wait for replication, tag, publish + var imageInfo []versionsapi.ImageInfoEntry for _, region := range allRegions { if err := u.waitForImage(ctx, amiIDs[region], region); err != nil { return nil, fmt.Errorf("waiting for image to become available in region %s: %w", region, err) @@ -142,8 +143,15 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[ if err := u.publishImage(ctx, amiIDs[region], region); err != nil { return nil, fmt.Errorf("publishing image in region %s: %w", region, err) } + imageInfo = append(imageInfo, versionsapi.ImageInfoEntry{ + CSP: "aws", + AttestationVariant: req.AttestationVariant, + Reference: amiIDs[region], + Region: region, + }) } - return amiIDs, nil + + return imageInfo, nil } func (u *Uploader) ensureBucket(ctx context.Context) error { diff --git a/internal/osimage/azure/azureupload.go b/internal/osimage/azure/azureupload.go index bc8154ada..22b23af5e 100644 --- a/internal/osimage/azure/azureupload.go +++ b/internal/osimage/azure/azureupload.go @@ -93,9 +93,9 @@ func New(subscription, location, resourceGroup string, log *logger.Logger) (*Upl } // Upload uploads an OS image to Azure. -func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) { +func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { formattedTime := req.Timestamp.Format(timestampFormat) - diskName := fmt.Sprintf("constellation-%s-%s-%s", req.Version.Stream, formattedTime, req.Variant) + diskName := fmt.Sprintf("constellation-%s-%s-%s", req.Version.Stream, formattedTime, req.AttestationVariant) var sigName string switch req.Version.Stream { case "stable": @@ -140,7 +140,7 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[ if err := u.ensureSIG(ctx, sigName); err != nil { return nil, fmt.Errorf("ensuring sig exists: %w", err) } - if err := u.ensureImageDefinition(ctx, sigName, definitionName, req.Version, req.Variant); err != nil { + if err := u.ensureImageDefinition(ctx, sigName, definitionName, req.Version, req.AttestationVariant); err != nil { return nil, fmt.Errorf("ensuring image definition exists: %w", err) } @@ -154,8 +154,12 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[ return nil, fmt.Errorf("getting image reference: %w", err) } - return map[string]string{ - req.Variant: imageReference, + return []versionsapi.ImageInfoEntry{ + { + CSP: "azure", + AttestationVariant: req.AttestationVariant, + Reference: imageReference, + }, }, nil } @@ -337,7 +341,7 @@ func (u *Uploader) ensureSIG(ctx context.Context, sigName string) error { } // ensureImageDefinition creates an image definition (component of a SIG) if it does not exist yet. -func (u *Uploader) ensureImageDefinition(ctx context.Context, sigName, definitionName string, version versionsapi.Version, variant string) error { +func (u *Uploader) ensureImageDefinition(ctx context.Context, sigName, definitionName string, version versionsapi.Version, attestationVariant string) error { _, err := u.image.Get(ctx, u.resourceGroup, sigName, definitionName, &armcomputev4.GalleryImagesClientGetOptions{}) if err == nil { u.log.Debugf("Image definition %s/%s in %s exists", sigName, definitionName, u.resourceGroup) @@ -349,10 +353,10 @@ func (u *Uploader) ensureImageDefinition(ctx context.Context, sigName, definitio // based on wether a VMGS was provided or not. // VMGS provided: ConfidentialVM // No VMGS provided: ConfidentialVMSupported - switch strings.ToLower(variant) { - case "cvm": + switch strings.ToLower(attestationVariant) { + case "azure-sev-snp": securityType = string("ConfidentialVMSupported") - case "trustedlaunch": + case "azure-trustedlaunch": securityType = string(armcomputev4.SecurityTypesTrustedLaunch) } offer := imageOffer(version) diff --git a/internal/osimage/gcp/gcpupload.go b/internal/osimage/gcp/gcpupload.go index c42c9d4a3..995b730eb 100644 --- a/internal/osimage/gcp/gcpupload.go +++ b/internal/osimage/gcp/gcpupload.go @@ -61,8 +61,8 @@ func New(ctx context.Context, project, location, bucketName string, log *logger. } // Upload uploads an OS image to GCP. -func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[string]string, error) { - imageName := u.imageName(req.Version, req.Variant) +func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { + imageName := u.imageName(req.Version, req.AttestationVariant) blobName := imageName + ".tar.gz" if err := u.ensureBucket(ctx); err != nil { return nil, fmt.Errorf("setup: ensuring bucket exists: %w", err) @@ -86,8 +86,12 @@ func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) (map[ if err != nil { return nil, fmt.Errorf("creating image: %w", err) } - return map[string]string{ - req.Variant: imageRef, + return []versionsapi.ImageInfoEntry{ + { + CSP: "gcp", + AttestationVariant: req.AttestationVariant, + Reference: imageRef, + }, }, nil } @@ -220,8 +224,8 @@ func (u *Uploader) blobURL(blobName string) string { }).String() } -func (u *Uploader) imageName(version versionsapi.Version, variant string) string { - return strings.ReplaceAll(version.Version, ".", "-") + "-" + variant + "-" + version.Stream +func (u *Uploader) imageName(version versionsapi.Version, attestationVariant string) string { + return strings.ReplaceAll(version.Version, ".", "-") + "-" + attestationVariant + "-" + version.Stream } func (u *Uploader) imageFamily(version versionsapi.Version) string { diff --git a/internal/osimage/nop/BUILD.bazel b/internal/osimage/nop/BUILD.bazel index cebcb202c..b0a9f49e3 100644 --- a/internal/osimage/nop/BUILD.bazel +++ b/internal/osimage/nop/BUILD.bazel @@ -8,5 +8,6 @@ go_library( deps = [ "//internal/logger", "//internal/osimage", + "//internal/versionsapi", ], ) diff --git a/internal/osimage/nop/nop.go b/internal/osimage/nop/nop.go index aeb0564f1..05b207f00 100644 --- a/internal/osimage/nop/nop.go +++ b/internal/osimage/nop/nop.go @@ -12,6 +12,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/logger" "github.com/edgelesssys/constellation/v2/internal/osimage" + "github.com/edgelesssys/constellation/v2/internal/versionsapi" ) // Uploader is a no-op uploader. @@ -25,7 +26,7 @@ func New(log *logger.Logger) *Uploader { } // Upload pretends to upload images to a csp. -func (u *Uploader) Upload(_ context.Context, req *osimage.UploadRequest) (map[string]string, error) { +func (u *Uploader) Upload(_ context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { u.log.Debugf("Skipping image upload of %s since this CSP does not require images to be uploaded in advance.", req.Version.ShortPath()) return nil, nil } diff --git a/internal/osimage/osimage.go b/internal/osimage/osimage.go index 8c46054e1..9e6032cb9 100644 --- a/internal/osimage/osimage.go +++ b/internal/osimage/osimage.go @@ -18,12 +18,12 @@ import ( // UploadRequest is a request to upload an os image. type UploadRequest struct { - Provider cloudprovider.Provider - Version versionsapi.Version - Variant string - SBDatabase secureboot.Database - UEFIVarStore secureboot.UEFIVarStore - Size int64 - Timestamp time.Time - Image io.ReadSeeker + Provider cloudprovider.Provider + Version versionsapi.Version + AttestationVariant string + SBDatabase secureboot.Database + UEFIVarStore secureboot.UEFIVarStore + Size int64 + Timestamp time.Time + Image io.ReadSeeker } diff --git a/internal/versionsapi/cli/rm.go b/internal/versionsapi/cli/rm.go index a42de7463..1e4e4e69e 100644 --- a/internal/versionsapi/cli/rm.go +++ b/internal/versionsapi/cli/rm.go @@ -211,24 +211,23 @@ func deleteImage(ctx context.Context, clients rmImageClients, ver versionsapi.Ve return fmt.Errorf("fetching image info: %w", err) } - log.Infof("Deleting AWS images from %s", imageInfo.JSONPath()) - for awsRegion, awsImage := range imageInfo.AWS { - if err := clients.aws.deleteImage(ctx, awsImage, awsRegion, dryrun, log); err != nil { - retErr = errors.Join(retErr, fmt.Errorf("deleting AWS image %s: %w", awsImage, err)) - } - } - - log.Infof("Deleting GCP images from %s", imageInfo.JSONPath()) - for _, gcpImage := range imageInfo.GCP { - if err := clients.gcp.deleteImage(ctx, gcpImage, dryrun, log); err != nil { - retErr = errors.Join(retErr, fmt.Errorf("deleting GCP image %s: %w", gcpImage, err)) - } - } - - log.Infof("Deleting Azure images from %s", imageInfo.JSONPath()) - for _, azImage := range imageInfo.Azure { - if err := clients.az.deleteImage(ctx, azImage, dryrun, log); err != nil { - retErr = errors.Join(retErr, fmt.Errorf("deleting Azure image %s: %w", azImage, err)) + for _, entry := range imageInfo.List { + switch entry.CSP { + case "aws": + log.Infof("Deleting AWS images from %s", imageInfo.JSONPath()) + if err := clients.aws.deleteImage(ctx, entry.Reference, entry.Region, dryrun, log); err != nil { + retErr = errors.Join(retErr, fmt.Errorf("deleting AWS image %s: %w", entry.Reference, err)) + } + case "gcp": + log.Infof("Deleting GCP images from %s", imageInfo.JSONPath()) + if err := clients.gcp.deleteImage(ctx, entry.Reference, dryrun, log); err != nil { + retErr = errors.Join(retErr, fmt.Errorf("deleting GCP image %s: %w", entry.Reference, err)) + } + case "azure": + log.Infof("Deleting Azure images from %s", imageInfo.JSONPath()) + if err := clients.az.deleteImage(ctx, entry.Reference, dryrun, log); err != nil { + retErr = errors.Join(retErr, fmt.Errorf("deleting Azure image %s: %w", entry.Reference, err)) + } } } diff --git a/internal/versionsapi/imageinfo.go b/internal/versionsapi/imageinfo.go index 2b25add65..f3ac3ea75 100644 --- a/internal/versionsapi/imageinfo.go +++ b/internal/versionsapi/imageinfo.go @@ -24,22 +24,22 @@ type ImageInfo struct { Stream string `json:"stream,omitempty"` // Version is the version of the image. Version string `json:"version,omitempty"` - // AWS is a map of AWS regions to AMI IDs. - AWS map[string]string `json:"aws,omitempty"` - // Azure is a map of image types to Azure image IDs. - Azure map[string]string `json:"azure,omitempty"` - // GCP is a map of image types to GCP image IDs. - GCP map[string]string `json:"gcp,omitempty"` - // OpenStack is a map of image types to OpenStack image IDs. - OpenStack map[string]string `json:"openstack,omitempty"` - // QEMU is a map of image types to QEMU image URLs. - QEMU map[string]string `json:"qemu,omitempty"` + // List contains the image variants for this version. + List []ImageInfoEntry `json:"list,omitempty"` +} + +// ImageInfoEntry contains information about a single image variant. +type ImageInfoEntry struct { + CSP string `json:"csp"` + AttestationVariant string `json:"attestationVariant"` + Reference string `json:"reference"` + Region string `json:"region,omitempty"` } // JSONPath returns the S3 JSON path for this object. func (i ImageInfo) JSONPath() string { return path.Join( - constants.CDNAPIPrefix, + constants.CDNAPIPrefixV2, "ref", i.Ref, "stream", i.Stream, i.Version, @@ -71,20 +71,8 @@ func (i ImageInfo) ValidateRequest() error { if !semver.IsValid(i.Version) { retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", i.Version)) } - if len(i.AWS) != 0 { - retErr = errors.Join(retErr, errors.New("AWS map must be empty for request")) - } - if len(i.Azure) != 0 { - retErr = errors.Join(retErr, errors.New("Azure map must be empty for request")) - } - if len(i.GCP) != 0 { - retErr = errors.Join(retErr, errors.New("GCP map must be empty for request")) - } - if len(i.OpenStack) != 0 { - retErr = errors.Join(retErr, errors.New("OpenStack map must be empty for request")) - } - if len(i.QEMU) != 0 { - retErr = errors.Join(retErr, errors.New("QEMU map must be empty for request")) + if len(i.List) != 0 { + retErr = errors.Join(retErr, errors.New("list must be empty for request")) } return retErr @@ -102,14 +90,8 @@ func (i ImageInfo) Validate() error { if !semver.IsValid(i.Version) { retErr = errors.Join(retErr, fmt.Errorf("version %q is not a valid semver", i.Version)) } - var providers int - providers += len(i.AWS) - providers += len(i.Azure) - providers += len(i.GCP) - providers += len(i.OpenStack) - providers += len(i.QEMU) - if providers == 0 { - retErr = errors.Join(retErr, errors.New("one or more providers must be specified")) + if len(i.List) == 0 { + retErr = errors.Join(retErr, errors.New("one or more image variants must be specified in the list")) } return retErr diff --git a/internal/versionsapi/imageinfo_test.go b/internal/versionsapi/imageinfo_test.go index 88cfe9552..1ff1911c3 100644 --- a/internal/versionsapi/imageinfo_test.go +++ b/internal/versionsapi/imageinfo_test.go @@ -24,7 +24,7 @@ func TestImageInfoJSONPath(t *testing.T) { Stream: "nightly", Version: "v1.0.0", }, - wantPath: constants.CDNAPIPrefix + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json", + wantPath: constants.CDNAPIPrefixV2 + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json", }, "image info release": { info: ImageInfo{ @@ -32,7 +32,7 @@ func TestImageInfoJSONPath(t *testing.T) { Stream: "stable", Version: "v1.0.0", }, - wantPath: constants.CDNAPIPrefix + "/ref/-/stream/stable/v1.0.0/image/info.json", + wantPath: constants.CDNAPIPrefixV2 + "/ref/-/stream/stable/v1.0.0/image/info.json", }, } @@ -55,7 +55,7 @@ func TestImageInfoURL(t *testing.T) { Stream: "nightly", Version: "v1.0.0", }, - wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json", + wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefixV2 + "/ref/test-ref/stream/nightly/v1.0.0/image/info.json", }, "image info release": { info: ImageInfo{ @@ -63,7 +63,7 @@ func TestImageInfoURL(t *testing.T) { Stream: "stable", Version: "v1.0.0", }, - wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefix + "/ref/-/stream/stable/v1.0.0/image/info.json", + wantURL: constants.CDNRepositoryURL + "/" + constants.CDNAPIPrefixV2 + "/ref/-/stream/stable/v1.0.0/image/info.json", }, } @@ -87,10 +87,35 @@ func TestImageInfoValidate(t *testing.T) { Ref: "test-ref", Stream: "nightly", Version: "v1.0.0", - AWS: map[string]string{"key": "value", "key2": "value2"}, - GCP: map[string]string{"key": "value", "key2": "value2"}, - Azure: map[string]string{"key": "value", "key2": "value2"}, - QEMU: map[string]string{"key": "value", "key2": "value2"}, + List: []ImageInfoEntry{ + { + CSP: "aws", + AttestationVariant: "aws-nitro-tpm", + Reference: "ami-123", + Region: "us-east-1", + }, + { + CSP: "aws", + AttestationVariant: "aws-nitro-tpm", + Reference: "ami-123", + Region: "us-east-2", + }, + { + CSP: "gcp", + AttestationVariant: "gcp-sev-es", + Reference: "gcp-123", + }, + { + CSP: "azure", + AttestationVariant: "azure-sev-snp", + Reference: "azure-123", + }, + { + CSP: "qemu", + AttestationVariant: "qemu-vtpm", + Reference: "https://example.com/qemu-123/image.raw", + }, + }, }, }, "invalid ref": { @@ -98,10 +123,14 @@ func TestImageInfoValidate(t *testing.T) { Ref: "", Stream: "nightly", Version: "v1.0.0", - AWS: map[string]string{"key": "value", "key2": "value2"}, - GCP: map[string]string{"key": "value", "key2": "value2"}, - Azure: map[string]string{"key": "value", "key2": "value2"}, - QEMU: map[string]string{"key": "value", "key2": "value2"}, + List: []ImageInfoEntry{ + { + CSP: "aws", + AttestationVariant: "aws-nitro-tpm", + Reference: "ami-123", + Region: "us-east-1", + }, + }, }, wantErr: true, }, @@ -110,10 +139,14 @@ func TestImageInfoValidate(t *testing.T) { Ref: "test-ref", Stream: "", Version: "v1.0.0", - AWS: map[string]string{"key": "value", "key2": "value2"}, - GCP: map[string]string{"key": "value", "key2": "value2"}, - Azure: map[string]string{"key": "value", "key2": "value2"}, - QEMU: map[string]string{"key": "value", "key2": "value2"}, + List: []ImageInfoEntry{ + { + CSP: "aws", + AttestationVariant: "aws-nitro-tpm", + Reference: "ami-123", + Region: "us-east-1", + }, + }, }, wantErr: true, }, @@ -122,14 +155,18 @@ func TestImageInfoValidate(t *testing.T) { Ref: "test-ref", Stream: "nightly", Version: "", - AWS: map[string]string{"key": "value", "key2": "value2"}, - GCP: map[string]string{"key": "value", "key2": "value2"}, - Azure: map[string]string{"key": "value", "key2": "value2"}, - QEMU: map[string]string{"key": "value", "key2": "value2"}, + List: []ImageInfoEntry{ + { + CSP: "aws", + AttestationVariant: "aws-nitro-tpm", + Reference: "ami-123", + Region: "us-east-1", + }, + }, }, wantErr: true, }, - "no provider": { + "no entries in list": { info: ImageInfo{ Ref: "test-ref", Stream: "nightly", @@ -197,48 +234,19 @@ func TestImageInfoValidateRequest(t *testing.T) { }, wantErr: true, }, - "invalid gcp": { + "request contains entries": { info: ImageInfo{ Ref: "test-ref", Stream: "nightly", Version: "v1.0.0", - GCP: map[string]string{"key": "value", "key2": "value2"}, - }, - wantErr: true, - }, - "invalid azure": { - info: ImageInfo{ - Ref: "test-ref", - Stream: "nightly", - Version: "v1.0.0", - Azure: map[string]string{"key": "value", "key2": "value2"}, - }, - wantErr: true, - }, - "invalid aws": { - info: ImageInfo{ - Ref: "test-ref", - Stream: "nightly", - Version: "v1.0.0", - AWS: map[string]string{"key": "value", "key2": "value2"}, - }, - wantErr: true, - }, - "invalid qemu": { - info: ImageInfo{ - Ref: "test-ref", - Stream: "nightly", - Version: "v1.0.0", - QEMU: map[string]string{"key": "value", "key2": "value2"}, - }, - wantErr: true, - }, - "invalid openstack": { - info: ImageInfo{ - Ref: "test-ref", - Stream: "nightly", - Version: "v1.0.0", - OpenStack: map[string]string{"key": "value", "key2": "value2"}, + List: []ImageInfoEntry{ + { + CSP: "aws", + AttestationVariant: "aws-nitro-tpm", + Reference: "ami-123", + Region: "us-east-1", + }, + }, }, wantErr: true, }, @@ -247,10 +255,14 @@ func TestImageInfoValidateRequest(t *testing.T) { Ref: "", Stream: "", Version: "", - AWS: map[string]string{"key": "value", "key2": "value2"}, - GCP: map[string]string{"key": "value", "key2": "value2"}, - Azure: map[string]string{"key": "value", "key2": "value2"}, - QEMU: map[string]string{"key": "value", "key2": "value2"}, + List: []ImageInfoEntry{ + { + CSP: "aws", + AttestationVariant: "aws-nitro-tpm", + Reference: "ami-123", + Region: "us-east-1", + }, + }, }, wantErr: true, },