diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index 1ff354a09..53460c029 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -31,6 +31,7 @@ import ( "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/variant" ) // Creator creates cloud resources. @@ -205,12 +206,17 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c InstanceType: insType, StateDiskType: config.Provider.Azure.StateDiskType, ImageID: image, - ConfidentialVM: *config.Provider.Azure.ConfidentialVM, SecureBoot: *config.Provider.Azure.SecureBoot, CreateMAA: config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback, Debug: config.IsDebugCluster(), } + attestVariant, err := variant.FromString(config.AttestationVariant) + if err != nil { + return clusterid.File{}, fmt.Errorf("parsing attestation variant: %w", err) + } + vars.ConfidentialVM = attestVariant.Equal(variant.AzureSEVSNP{}) + vars = normalizeAzureURIs(vars) if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil { diff --git a/cli/internal/cloudcmd/create_test.go b/cli/internal/cloudcmd/create_test.go index cf4440c29..b86a17be2 100644 --- a/cli/internal/cloudcmd/create_test.go +++ b/cli/internal/cloudcmd/create_test.go @@ -16,6 +16,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" "github.com/stretchr/testify/assert" ) @@ -55,29 +56,55 @@ func TestCreator(t *testing.T) { wantTerraformRollback: true, }, "azure": { - tfClient: &stubTerraformClient{ip: ip}, - provider: cloudprovider.Azure, - config: config.Default(), + tfClient: &stubTerraformClient{ip: ip}, + provider: cloudprovider.Azure, + config: func() *config.Config { + cfg := config.Default() + cfg.AttestationVariant = variant.AzureSEVSNP{}.String() + return cfg + }(), + policyPatcher: &stubPolicyPatcher{}, + }, + "azure trusted launch": { + tfClient: &stubTerraformClient{ip: ip}, + provider: cloudprovider.Azure, + config: func() *config.Config { + cfg := config.Default() + cfg.AttestationVariant = variant.AzureTrustedLaunch{}.String() + return cfg + }(), policyPatcher: &stubPolicyPatcher{}, }, "azure new policy patch error": { - tfClient: &stubTerraformClient{ip: ip}, - provider: cloudprovider.Azure, - config: config.Default(), + tfClient: &stubTerraformClient{ip: ip}, + provider: cloudprovider.Azure, + config: func() *config.Config { + cfg := config.Default() + cfg.AttestationVariant = variant.AzureSEVSNP{}.String() + return cfg + }(), policyPatcher: &stubPolicyPatcher{someErr}, wantErr: true, }, "azure newTerraformClient error": { newTfClientErr: someErr, provider: cloudprovider.Azure, - config: config.Default(), - policyPatcher: &stubPolicyPatcher{}, - wantErr: true, + config: func() *config.Config { + cfg := config.Default() + cfg.AttestationVariant = variant.AzureSEVSNP{}.String() + return cfg + }(), + policyPatcher: &stubPolicyPatcher{}, + wantErr: true, }, "azure create cluster error": { - tfClient: &stubTerraformClient{createClusterErr: someErr}, - provider: cloudprovider.Azure, - config: config.Default(), + tfClient: &stubTerraformClient{createClusterErr: someErr}, + provider: cloudprovider.Azure, + config: func() *config.Config { + cfg := config.Default() + cfg.AttestationVariant = variant.AzureSEVSNP{}.String() + return cfg + }(), policyPatcher: &stubPolicyPatcher{}, wantErr: true, wantRollback: true, diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index e005d8b80..7e3f27efe 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -18,6 +18,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/variant" "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -95,7 +96,12 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler printedAWarning = true } - if conf.IsAzureNonCVM() { + attestVariant, err := variant.FromString(conf.AttestationVariant) + if err != nil { + return fmt.Errorf("parsing attestation variant: %w", err) + } + + if attestVariant.Equal(variant.AzureTrustedLaunch{}) { cmd.PrintErrln("Disabling Confidential VMs is insecure. Use only for evaluation purposes.") printedAWarning = true if conf.IDKeyDigestPolicy() == idkeydigest.StrictChecking || conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback { diff --git a/cli/internal/image/BUILD.bazel b/cli/internal/image/BUILD.bazel index 92dcbe67e..1a1edaa40 100644 --- a/cli/internal/image/BUILD.bazel +++ b/cli/internal/image/BUILD.bazel @@ -12,6 +12,7 @@ go_library( deps = [ "//internal/cloud/cloudprovider", "//internal/config", + "//internal/variant", "//internal/versionsapi", "//internal/versionsapi/fetcher", "@com_github_schollz_progressbar_v3//:progressbar", @@ -30,6 +31,7 @@ go_test( "//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/cli/internal/image/image.go index 185d77f11..792105764 100644 --- a/cli/internal/image/image.go +++ b/cli/internal/image/image.go @@ -21,6 +21,7 @@ import ( "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" "github.com/spf13/afero" @@ -43,7 +44,7 @@ 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 := variant(provider, config) + variant, err := imageVariant(provider, config) if err != nil { return "", fmt.Errorf("determining variant: %w", err) } @@ -85,16 +86,20 @@ func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (st return getReferenceFromImageInfo(provider, variant, imgInfo) } -// variant returns the image variant for a given CSP and configuration. -func variant(provider cloudprovider.Provider, config *config.Config) (string, error) { +// imageVariant returns the image variant for a given CSP and configuration. +func imageVariant(provider cloudprovider.Provider, config *config.Config) (string, error) { switch provider { case cloudprovider.AWS: return config.Provider.AWS.Region, nil case cloudprovider.Azure: - if *config.Provider.Azure.ConfidentialVM { - return "cvm", nil + attestVariant, err := variant.FromString(config.AttestationVariant) + if err != nil { + return "", fmt.Errorf("parsing attestation variant: %w", err) } - return "trustedlaunch", nil + if attestVariant.Equal(variant.AzureTrustedLaunch{}) { + return "trustedlaunch", nil + } + return "cvm", nil case cloudprovider.GCP: return "sev-es", nil diff --git a/cli/internal/image/image_test.go b/cli/internal/image/image_test.go index 30923845a..42289bc19 100644 --- a/cli/internal/image/image_test.go +++ b/cli/internal/image/image_test.go @@ -16,6 +16,7 @@ import ( "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" @@ -108,7 +109,7 @@ func TestGetReference(t *testing.T) { } } -func TestVariant(t *testing.T) { +func TestImageVariant(t *testing.T) { testCases := map[string]struct { csp cloudprovider.Provider config *config.Config @@ -125,18 +126,16 @@ func TestVariant(t *testing.T) { "Azure cvm": { csp: cloudprovider.Azure, config: &config.Config{ - Image: "someImage", Provider: config.ProviderConfig{ - Azure: &config.AzureConfig{ConfidentialVM: func() *bool { b := true; return &b }()}, - }, + AttestationVariant: variant.AzureSEVSNP{}.String(), + Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}}, }, wantVariant: "cvm", }, "Azure trustedlaunch": { csp: cloudprovider.Azure, config: &config.Config{ - Image: "someImage", Provider: config.ProviderConfig{ - Azure: &config.AzureConfig{ConfidentialVM: func() *bool { b := false; return &b }()}, - }, + AttestationVariant: variant.AzureTrustedLaunch{}.String(), + Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}}, }, wantVariant: "trustedlaunch", }, @@ -172,7 +171,7 @@ func TestVariant(t *testing.T) { assert := assert.New(t) require := require.New(t) - vari, err := variant(tc.csp, tc.config) + vari, err := imageVariant(tc.csp, tc.config) if tc.wantErr { assert.Error(err) return diff --git a/internal/config/config.go b/internal/config/config.go index 56abf0289..e58efd54e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ import ( "fmt" "io/fs" "os" + "reflect" "strings" "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" @@ -183,7 +184,7 @@ type AzureConfig struct { DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"` // description: | // Use Confidential VMs. Always needs to be true. - ConfidentialVM *bool `yaml:"confidentialVM" validate:"required"` + ConfidentialVM *bool `yaml:"confidentialVM,omitempty" validate:"omitempty,deprecated"` // TODO: v2.8 remove // description: | // Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob. SecureBoot *bool `yaml:"secureBoot" validate:"required"` @@ -330,7 +331,6 @@ func Default() *Config { DeployCSIDriver: toPtr(true), IDKeyDigest: idkeydigest.DefaultsFor(cloudprovider.Azure), EnforceIDKeyDigest: idkeydigest.MAAFallback, - ConfidentialVM: toPtr(true), SecureBoot: toPtr(false), Measurements: measurements.DefaultsFor(cloudprovider.Azure), }, @@ -459,11 +459,6 @@ func (c *Config) RemoveProviderExcept(provider cloudprovider.Provider) { } } -// IsAzureNonCVM checks whether the chosen provider is azure and confidential VMs are disabled. -func (c *Config) IsAzureNonCVM() bool { - return c.Provider.Azure != nil && c.Provider.Azure.ConfidentialVM != nil && !*c.Provider.Azure.ConfidentialVM -} - // IsDebugCluster checks whether the cluster is configured as a debug cluster. func (c *Config) IsDebugCluster() bool { if c.DebugCluster != nil && *c.DebugCluster { @@ -544,6 +539,17 @@ func (c *Config) Validate(force bool) error { return err } + // Register name function to return yaml name tag + // This makes sure methods like fl.FieldName() return the yaml name tag instead of the struct field name + // e.g. struct{DataType string `yaml:"foo,omitempty"`} will return `foo` instead of `DataType` when calling fl.FieldName() + validate.RegisterTagNameFunc(func(fld reflect.StructField) string { + name, _, _ := strings.Cut(fld.Tag.Get("yaml"), ",") + if name == "-" { + return "" + } + return name + }) + // Register AWS, Azure & GCP InstanceType validation error types if err := validate.RegisterTranslation("aws_instance_type", trans, registerTranslateAWSInstanceTypeError, translateAWSInstanceTypeError); err != nil { return err @@ -613,7 +619,7 @@ func (c *Config) Validate(force bool) error { } // register custom validator with label azure_instance_type to validate the Azure instance type from config input. - if err := validate.RegisterValidation("azure_instance_type", validateAzureInstanceType); err != nil { + if err := validate.RegisterValidation("azure_instance_type", c.validateAzureInstanceType); err != nil { return err } @@ -626,6 +632,10 @@ func (c *Config) Validate(force bool) error { return err } + if err := validate.RegisterValidation("deprecated", warnDeprecated); err != nil { + return err + } + // Register provider validation validate.RegisterStructValidation(validateProvider, ProviderConfig{}) diff --git a/internal/config/config_doc.go b/internal/config/config_doc.go index f4948f04b..182f8771f 100644 --- a/internal/config/config_doc.go +++ b/internal/config/config_doc.go @@ -251,7 +251,7 @@ func init() { AzureConfigDoc.Fields[9].Comments[encoder.LineComment] = "Deploy Azure Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" AzureConfigDoc.Fields[10].Name = "confidentialVM" AzureConfigDoc.Fields[10].Type = "bool" - AzureConfigDoc.Fields[10].Note = "" + AzureConfigDoc.Fields[10].Note = "TODO: v2.8 remove\n" AzureConfigDoc.Fields[10].Description = "Use Confidential VMs. Always needs to be true." AzureConfigDoc.Fields[10].Comments[encoder.LineComment] = "Use Confidential VMs. Always needs to be true." AzureConfigDoc.Fields[11].Name = "secureBoot" diff --git a/internal/config/validation.go b/internal/config/validation.go index cba076459..a43a3e73a 100644 --- a/internal/config/validation.go +++ b/internal/config/validation.go @@ -102,14 +102,12 @@ func validateAWSInstanceType(fl validator.FieldLevel) bool { return validInstanceTypeForProvider(fl.Field().String(), false, cloudprovider.AWS) } -func validateAzureInstanceType(fl validator.FieldLevel) bool { - azureConfig := fl.Parent().Interface().(AzureConfig) - var acceptNonCVM bool - if azureConfig.ConfidentialVM != nil { - // This is the inverse of the config value (acceptNonCVMs is true if confidentialVM is false). - // We could make the validator the other way around, but this should be an explicit bypass rather than checking if CVMs are "allowed". - acceptNonCVM = !*azureConfig.ConfidentialVM +func (c *Config) validateAzureInstanceType(fl validator.FieldLevel) bool { + attestVariant, err := variant.FromString(c.AttestationVariant) + if err != nil { + return false } + acceptNonCVM := attestVariant.Equal(variant.AzureTrustedLaunch{}) return validInstanceTypeForProvider(fl.Field().String(), acceptNonCVM, cloudprovider.Azure) } @@ -282,7 +280,12 @@ func registerTranslateAzureInstanceTypeError(ut ut.Translator) error { func (c *Config) translateAzureInstanceTypeError(ut ut.Translator, fe validator.FieldError) string { // Suggest trusted launch VMs if confidential VMs have been specifically disabled var t string - if c.Provider.Azure != nil && c.Provider.Azure.ConfidentialVM != nil && !*c.Provider.Azure.ConfidentialVM { + + attestVariant, err := variant.FromString(c.AttestationVariant) + if err != nil { + return "" + } + if attestVariant.Equal(variant.AzureTrustedLaunch{}) { t, _ = ut.T("azure_instance_type", fe.Field(), fmt.Sprintf("%v", instancetypes.AzureTrustedLaunchInstanceTypes)) } else { t, _ = ut.T("azure_instance_type", fe.Field(), fmt.Sprintf("%v", instancetypes.AzureCVMInstanceTypes)) @@ -400,7 +403,7 @@ func validateVersionCompatibility(fl validator.FieldLevel) bool { } func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVersion string) error { - if fieldName == "Image" { + if fieldName == "image" { imageVersion, err := versionsapi.NewVersionFromShortPath(configuredVersion, versionsapi.VersionKindImage) if err != nil { return err @@ -408,7 +411,7 @@ func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVers configuredVersion = imageVersion.Version } - if fieldName == "MicroserviceVersion" { + if fieldName == "microserviceVersion" { cliVersion := compatibility.EnsurePrefixV(binaryVersion) serviceVersion := compatibility.EnsurePrefixV(configuredVersion) if semver.Compare(cliVersion, serviceVersion) == -1 { @@ -522,3 +525,12 @@ func (c *Config) addMissingVariant() { c.AttestationVariant = variant.QEMUVTPM{}.String() } } + +func warnDeprecated(fl validator.FieldLevel) bool { + fmt.Fprintf( + os.Stderr, + "WARNING: The config key %q is deprecated and will be removed in an upcoming version.\n", + fl.FieldName(), + ) + return true +} diff --git a/internal/config/validation_test.go b/internal/config/validation_test.go index 1e12a0ca6..ff3639668 100644 --- a/internal/config/validation_test.go +++ b/internal/config/validation_test.go @@ -38,7 +38,7 @@ func TestValidateVersionCompatibilityHelper(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) - err := validateVersionCompatibilityHelper(tc.cli, "Image", tc.target) + err := validateVersionCompatibilityHelper(tc.cli, "image", tc.target) if tc.wantError { assert.Error(err) return