config: deprecate confidentialVM option for Azure clusters in favor of using attestationVariant option (#1539)

* Remove confidentialVM option from azure provider config

* Fix cloudcmd creator test

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-03-29 14:04:37 +02:00 committed by GitHub
parent 1b832ac959
commit fc0efb6309
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 115 additions and 48 deletions

View File

@ -31,6 +31,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/variant"
) )
// Creator creates cloud resources. // Creator creates cloud resources.
@ -205,12 +206,17 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
InstanceType: insType, InstanceType: insType,
StateDiskType: config.Provider.Azure.StateDiskType, StateDiskType: config.Provider.Azure.StateDiskType,
ImageID: image, ImageID: image,
ConfidentialVM: *config.Provider.Azure.ConfidentialVM,
SecureBoot: *config.Provider.Azure.SecureBoot, SecureBoot: *config.Provider.Azure.SecureBoot,
CreateMAA: config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback, CreateMAA: config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback,
Debug: config.IsDebugCluster(), 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) vars = normalizeAzureURIs(vars)
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {

View File

@ -16,6 +16,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -55,29 +56,55 @@ func TestCreator(t *testing.T) {
wantTerraformRollback: true, wantTerraformRollback: true,
}, },
"azure": { "azure": {
tfClient: &stubTerraformClient{ip: ip}, tfClient: &stubTerraformClient{ip: ip},
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
config: config.Default(), 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{}, policyPatcher: &stubPolicyPatcher{},
}, },
"azure new policy patch error": { "azure new policy patch error": {
tfClient: &stubTerraformClient{ip: ip}, tfClient: &stubTerraformClient{ip: ip},
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
config: config.Default(), config: func() *config.Config {
cfg := config.Default()
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
return cfg
}(),
policyPatcher: &stubPolicyPatcher{someErr}, policyPatcher: &stubPolicyPatcher{someErr},
wantErr: true, wantErr: true,
}, },
"azure newTerraformClient error": { "azure newTerraformClient error": {
newTfClientErr: someErr, newTfClientErr: someErr,
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
config: config.Default(), config: func() *config.Config {
policyPatcher: &stubPolicyPatcher{}, cfg := config.Default()
wantErr: true, cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
return cfg
}(),
policyPatcher: &stubPolicyPatcher{},
wantErr: true,
}, },
"azure create cluster error": { "azure create cluster error": {
tfClient: &stubTerraformClient{createClusterErr: someErr}, tfClient: &stubTerraformClient{createClusterErr: someErr},
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
config: config.Default(), config: func() *config.Config {
cfg := config.Default()
cfg.AttestationVariant = variant.AzureSEVSNP{}.String()
return cfg
}(),
policyPatcher: &stubPolicyPatcher{}, policyPatcher: &stubPolicyPatcher{},
wantErr: true, wantErr: true,
wantRollback: true, wantRollback: true,

View File

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -95,7 +96,12 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
printedAWarning = true 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.") cmd.PrintErrln("Disabling Confidential VMs is insecure. Use only for evaluation purposes.")
printedAWarning = true printedAWarning = true
if conf.IDKeyDigestPolicy() == idkeydigest.StrictChecking || conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback { if conf.IDKeyDigestPolicy() == idkeydigest.StrictChecking || conf.IDKeyDigestPolicy() == idkeydigest.MAAFallback {

View File

@ -12,6 +12,7 @@ go_library(
deps = [ deps = [
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/config", "//internal/config",
"//internal/variant",
"//internal/versionsapi", "//internal/versionsapi",
"//internal/versionsapi/fetcher", "//internal/versionsapi/fetcher",
"@com_github_schollz_progressbar_v3//:progressbar", "@com_github_schollz_progressbar_v3//:progressbar",
@ -30,6 +31,7 @@ go_test(
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/config", "//internal/config",
"//internal/file", "//internal/file",
"//internal/variant",
"//internal/versionsapi", "//internal/versionsapi",
"@com_github_spf13_afero//:afero", "@com_github_spf13_afero//:afero",
"@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//assert",

View File

@ -21,6 +21,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "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"
"github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher" "github.com/edgelesssys/constellation/v2/internal/versionsapi/fetcher"
"github.com/spf13/afero" "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. // 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) { func (f *Fetcher) FetchReference(ctx context.Context, config *config.Config) (string, error) {
provider := config.GetProvider() provider := config.GetProvider()
variant, err := variant(provider, config) variant, err := imageVariant(provider, config)
if err != nil { if err != nil {
return "", fmt.Errorf("determining variant: %w", err) 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) return getReferenceFromImageInfo(provider, variant, imgInfo)
} }
// variant returns the image variant for a given CSP and configuration. // imageVariant returns the image variant for a given CSP and configuration.
func variant(provider cloudprovider.Provider, config *config.Config) (string, error) { func imageVariant(provider cloudprovider.Provider, config *config.Config) (string, error) {
switch provider { switch provider {
case cloudprovider.AWS: case cloudprovider.AWS:
return config.Provider.AWS.Region, nil return config.Provider.AWS.Region, nil
case cloudprovider.Azure: case cloudprovider.Azure:
if *config.Provider.Azure.ConfidentialVM { attestVariant, err := variant.FromString(config.AttestationVariant)
return "cvm", nil 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: case cloudprovider.GCP:
return "sev-es", nil return "sev-es", nil

View File

@ -16,6 +16,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/edgelesssys/constellation/v2/internal/versionsapi" "github.com/edgelesssys/constellation/v2/internal/versionsapi"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/stretchr/testify/assert" "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 { testCases := map[string]struct {
csp cloudprovider.Provider csp cloudprovider.Provider
config *config.Config config *config.Config
@ -125,18 +126,16 @@ func TestVariant(t *testing.T) {
"Azure cvm": { "Azure cvm": {
csp: cloudprovider.Azure, csp: cloudprovider.Azure,
config: &config.Config{ config: &config.Config{
Image: "someImage", Provider: config.ProviderConfig{ AttestationVariant: variant.AzureSEVSNP{}.String(),
Azure: &config.AzureConfig{ConfidentialVM: func() *bool { b := true; return &b }()}, Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
},
}, },
wantVariant: "cvm", wantVariant: "cvm",
}, },
"Azure trustedlaunch": { "Azure trustedlaunch": {
csp: cloudprovider.Azure, csp: cloudprovider.Azure,
config: &config.Config{ config: &config.Config{
Image: "someImage", Provider: config.ProviderConfig{ AttestationVariant: variant.AzureTrustedLaunch{}.String(),
Azure: &config.AzureConfig{ConfidentialVM: func() *bool { b := false; return &b }()}, Image: "someImage", Provider: config.ProviderConfig{Azure: &config.AzureConfig{}},
},
}, },
wantVariant: "trustedlaunch", wantVariant: "trustedlaunch",
}, },
@ -172,7 +171,7 @@ func TestVariant(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t) require := require.New(t)
vari, err := variant(tc.csp, tc.config) vari, err := imageVariant(tc.csp, tc.config)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return

View File

@ -23,6 +23,7 @@ import (
"fmt" "fmt"
"io/fs" "io/fs"
"os" "os"
"reflect"
"strings" "strings"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest" "github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
@ -183,7 +184,7 @@ type AzureConfig struct {
DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"` DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"`
// description: | // description: |
// Use Confidential VMs. Always needs to be true. // 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: | // description: |
// Enable secure boot for VMs. If enabled, the OS image has to include a virtual machine guest state (VMGS) blob. // 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"` SecureBoot *bool `yaml:"secureBoot" validate:"required"`
@ -330,7 +331,6 @@ func Default() *Config {
DeployCSIDriver: toPtr(true), DeployCSIDriver: toPtr(true),
IDKeyDigest: idkeydigest.DefaultsFor(cloudprovider.Azure), IDKeyDigest: idkeydigest.DefaultsFor(cloudprovider.Azure),
EnforceIDKeyDigest: idkeydigest.MAAFallback, EnforceIDKeyDigest: idkeydigest.MAAFallback,
ConfidentialVM: toPtr(true),
SecureBoot: toPtr(false), SecureBoot: toPtr(false),
Measurements: measurements.DefaultsFor(cloudprovider.Azure), 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. // IsDebugCluster checks whether the cluster is configured as a debug cluster.
func (c *Config) IsDebugCluster() bool { func (c *Config) IsDebugCluster() bool {
if c.DebugCluster != nil && *c.DebugCluster { if c.DebugCluster != nil && *c.DebugCluster {
@ -544,6 +539,17 @@ func (c *Config) Validate(force bool) error {
return err 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 // Register AWS, Azure & GCP InstanceType validation error types
if err := validate.RegisterTranslation("aws_instance_type", trans, registerTranslateAWSInstanceTypeError, translateAWSInstanceTypeError); err != nil { if err := validate.RegisterTranslation("aws_instance_type", trans, registerTranslateAWSInstanceTypeError, translateAWSInstanceTypeError); err != nil {
return err 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. // 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 return err
} }
@ -626,6 +632,10 @@ func (c *Config) Validate(force bool) error {
return err return err
} }
if err := validate.RegisterValidation("deprecated", warnDeprecated); err != nil {
return err
}
// Register provider validation // Register provider validation
validate.RegisterStructValidation(validateProvider, ProviderConfig{}) validate.RegisterStructValidation(validateProvider, ProviderConfig{})

View File

@ -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[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].Name = "confidentialVM"
AzureConfigDoc.Fields[10].Type = "bool" 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].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[10].Comments[encoder.LineComment] = "Use Confidential VMs. Always needs to be true."
AzureConfigDoc.Fields[11].Name = "secureBoot" AzureConfigDoc.Fields[11].Name = "secureBoot"

View File

@ -102,14 +102,12 @@ func validateAWSInstanceType(fl validator.FieldLevel) bool {
return validInstanceTypeForProvider(fl.Field().String(), false, cloudprovider.AWS) return validInstanceTypeForProvider(fl.Field().String(), false, cloudprovider.AWS)
} }
func validateAzureInstanceType(fl validator.FieldLevel) bool { func (c *Config) validateAzureInstanceType(fl validator.FieldLevel) bool {
azureConfig := fl.Parent().Interface().(AzureConfig) attestVariant, err := variant.FromString(c.AttestationVariant)
var acceptNonCVM bool if err != nil {
if azureConfig.ConfidentialVM != nil { return false
// 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
} }
acceptNonCVM := attestVariant.Equal(variant.AzureTrustedLaunch{})
return validInstanceTypeForProvider(fl.Field().String(), acceptNonCVM, cloudprovider.Azure) 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 { func (c *Config) translateAzureInstanceTypeError(ut ut.Translator, fe validator.FieldError) string {
// Suggest trusted launch VMs if confidential VMs have been specifically disabled // Suggest trusted launch VMs if confidential VMs have been specifically disabled
var t string 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)) t, _ = ut.T("azure_instance_type", fe.Field(), fmt.Sprintf("%v", instancetypes.AzureTrustedLaunchInstanceTypes))
} else { } else {
t, _ = ut.T("azure_instance_type", fe.Field(), fmt.Sprintf("%v", instancetypes.AzureCVMInstanceTypes)) 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 { func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVersion string) error {
if fieldName == "Image" { if fieldName == "image" {
imageVersion, err := versionsapi.NewVersionFromShortPath(configuredVersion, versionsapi.VersionKindImage) imageVersion, err := versionsapi.NewVersionFromShortPath(configuredVersion, versionsapi.VersionKindImage)
if err != nil { if err != nil {
return err return err
@ -408,7 +411,7 @@ func validateVersionCompatibilityHelper(binaryVersion, fieldName, configuredVers
configuredVersion = imageVersion.Version configuredVersion = imageVersion.Version
} }
if fieldName == "MicroserviceVersion" { if fieldName == "microserviceVersion" {
cliVersion := compatibility.EnsurePrefixV(binaryVersion) cliVersion := compatibility.EnsurePrefixV(binaryVersion)
serviceVersion := compatibility.EnsurePrefixV(configuredVersion) serviceVersion := compatibility.EnsurePrefixV(configuredVersion)
if semver.Compare(cliVersion, serviceVersion) == -1 { if semver.Compare(cliVersion, serviceVersion) == -1 {
@ -522,3 +525,12 @@ func (c *Config) addMissingVariant() {
c.AttestationVariant = variant.QEMUVTPM{}.String() 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
}

View File

@ -38,7 +38,7 @@ func TestValidateVersionCompatibilityHelper(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
err := validateVersionCompatibilityHelper(tc.cli, "Image", tc.target) err := validateVersionCompatibilityHelper(tc.cli, "image", tc.target)
if tc.wantError { if tc.wantError {
assert.Error(err) assert.Error(err)
return return