From ca0c4c942787a4210145cc4fa41886421db9fa1a Mon Sep 17 00:00:00 2001 From: Malte Poll <1780588+malt3@users.noreply.github.com> Date: Wed, 6 Mar 2024 20:48:40 +0100 Subject: [PATCH] terraform-provider: Add support for STACKIT / OpenStack --- cli/internal/cmd/apply.go | 2 +- cli/internal/cmd/apply_test.go | 2 +- cli/internal/cmd/applyhelm.go | 16 ++- cli/internal/cmd/init_test.go | 2 +- cli/internal/cmd/upgradeapply_test.go | 4 +- internal/constellation/helm.go | 7 +- internal/constellation/helm/BUILD.bazel | 1 - internal/constellation/helm/helm.go | 10 +- internal/constellation/helm/helm_test.go | 2 +- internal/constellation/helm/loader.go | 17 ++- internal/constellation/helm/loader_test.go | 15 +- internal/constellation/helm/overrides.go | 7 +- .../docs/data-sources/attestation.md | 2 + .../docs/data-sources/image.md | 1 + .../docs/resources/cluster.md | 20 +++ .../examples/full/stackit/main.tf | 128 ++++++++++++++++++ .../internal/provider/BUILD.bazel | 4 + .../provider/attestation_data_source_test.go | 52 +++++++ .../internal/provider/cluster_resource.go | 128 +++++++++++++++++- .../provider/cluster_resource_test.go | 75 ++++++++++ .../internal/provider/convert.go | 6 +- .../internal/provider/image_data_source.go | 7 +- .../provider/image_data_source_test.go | 32 +++++ .../internal/provider/shared_attributes.go | 7 +- 24 files changed, 511 insertions(+), 36 deletions(-) create mode 100644 terraform-provider-constellation/examples/full/stackit/main.tf diff --git a/cli/internal/cmd/apply.go b/cli/internal/cmd/apply.go index b15ae249d6c..0c524302e8c 100644 --- a/cli/internal/cmd/apply.go +++ b/cli/internal/cmd/apply.go @@ -844,7 +844,7 @@ type applier interface { // methods required to install/upgrade Helm charts PrepareHelmCharts( - flags helm.Options, state *state.State, serviceAccURI string, masterSecret uri.MasterSecret, openStackCfg *config.OpenStackConfig, + flags helm.Options, state *state.State, serviceAccURI string, masterSecret uri.MasterSecret, ) (helm.Applier, bool, error) // methods to interact with Kubernetes diff --git a/cli/internal/cmd/apply_test.go b/cli/internal/cmd/apply_test.go index a177cd1d417..064e1f42b24 100644 --- a/cli/internal/cmd/apply_test.go +++ b/cli/internal/cmd/apply_test.go @@ -554,7 +554,7 @@ func (s *stubConstellApplier) Init(context.Context, atls.Validator, *state.State type helmApplier interface { PrepareHelmCharts( - flags helm.Options, stateFile *state.State, serviceAccURI string, masterSecret uri.MasterSecret, openStackCfg *config.OpenStackConfig, + flags helm.Options, stateFile *state.State, serviceAccURI string, masterSecret uri.MasterSecret, ) ( helm.Applier, bool, error) } diff --git a/cli/internal/cmd/applyhelm.go b/cli/internal/cmd/applyhelm.go index 79ae2a6d727..b9e1538d6dc 100644 --- a/cli/internal/cmd/applyhelm.go +++ b/cli/internal/cmd/applyhelm.go @@ -43,6 +43,18 @@ func (a *applyCmd) runHelmApply(cmd *cobra.Command, conf *config.Config, stateFi ApplyTimeout: a.flags.helmTimeout, AllowDestructive: helm.DenyDestructive, } + if conf.Provider.OpenStack != nil { + var deployYawolLoadBalancer bool + if conf.Provider.OpenStack.DeployYawolLoadBalancer != nil { + deployYawolLoadBalancer = *conf.Provider.OpenStack.DeployYawolLoadBalancer + } + options.OpenStackValues = &helm.OpenStackValues{ + DeployYawolLoadBalancer: deployYawolLoadBalancer, + FloatingIPPoolID: conf.Provider.OpenStack.FloatingIPPoolID, + YawolFlavorID: conf.Provider.OpenStack.YawolFlavorID, + YawolImageID: conf.Provider.OpenStack.YawolImageID, + } + } a.log.Debug("Getting service account URI") serviceAccURI, err := cloudcmd.GetMarshaledServiceAccountURI(conf, a.fileHandler) @@ -51,7 +63,7 @@ func (a *applyCmd) runHelmApply(cmd *cobra.Command, conf *config.Config, stateFi } a.log.Debug("Preparing Helm charts") - executor, includesUpgrades, err := a.applier.PrepareHelmCharts(options, stateFile, serviceAccURI, masterSecret, conf.Provider.OpenStack) + executor, includesUpgrades, err := a.applier.PrepareHelmCharts(options, stateFile, serviceAccURI, masterSecret) if errors.Is(err, helm.ErrConfirmationMissing) { if !a.flags.yes { cmd.PrintErrln("WARNING: Upgrading cert-manager will destroy all custom resources you have manually created that are based on the current version of cert-manager.") @@ -65,7 +77,7 @@ func (a *applyCmd) runHelmApply(cmd *cobra.Command, conf *config.Config, stateFi } } options.AllowDestructive = helm.AllowDestructive - executor, includesUpgrades, err = a.applier.PrepareHelmCharts(options, stateFile, serviceAccURI, masterSecret, conf.Provider.OpenStack) + executor, includesUpgrades, err = a.applier.PrepareHelmCharts(options, stateFile, serviceAccURI, masterSecret) } var upgradeErr *compatibility.InvalidUpgradeError if err != nil { diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index f55b7e77cc1..8d6d2b1bb52 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -279,7 +279,7 @@ type stubHelmApplier struct { } func (s stubHelmApplier) PrepareHelmCharts( - _ helm.Options, _ *state.State, _ string, _ uri.MasterSecret, _ *config.OpenStackConfig, + _ helm.Options, _ *state.State, _ string, _ uri.MasterSecret, ) (helm.Applier, bool, error) { return stubRunner{}, false, s.err } diff --git a/cli/internal/cmd/upgradeapply_test.go b/cli/internal/cmd/upgradeapply_test.go index 8cf546c378e..f396cc828c3 100644 --- a/cli/internal/cmd/upgradeapply_test.go +++ b/cli/internal/cmd/upgradeapply_test.go @@ -376,9 +376,9 @@ type mockApplier struct { } func (m *mockApplier) PrepareHelmCharts( - helmOpts helm.Options, stateFile *state.State, str string, masterSecret uri.MasterSecret, openStackCfg *config.OpenStackConfig, + helmOpts helm.Options, stateFile *state.State, str string, masterSecret uri.MasterSecret, ) (helm.Applier, bool, error) { - args := m.Called(helmOpts, stateFile, helmOpts, str, masterSecret, openStackCfg) + args := m.Called(helmOpts, stateFile, helmOpts, str, masterSecret) return args.Get(0).(helm.Applier), args.Bool(1), args.Error(2) } diff --git a/internal/constellation/helm.go b/internal/constellation/helm.go index e8b9a815f1c..1378ce3a0fe 100644 --- a/internal/constellation/helm.go +++ b/internal/constellation/helm.go @@ -9,7 +9,6 @@ package constellation import ( "errors" - "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constellation/helm" "github.com/edgelesssys/constellation/v2/internal/constellation/state" "github.com/edgelesssys/constellation/v2/internal/kms/uri" @@ -17,18 +16,18 @@ import ( // PrepareHelmCharts loads Helm charts for Constellation and returns an executor to apply them. func (a *Applier) PrepareHelmCharts( - flags helm.Options, state *state.State, serviceAccURI string, masterSecret uri.MasterSecret, openStackCfg *config.OpenStackConfig, + flags helm.Options, state *state.State, serviceAccURI string, masterSecret uri.MasterSecret, ) (helm.Applier, bool, error) { if a.helmClient == nil { return nil, false, errors.New("helm client not initialized") } - return a.helmClient.PrepareApply(flags, state, serviceAccURI, masterSecret, openStackCfg) + return a.helmClient.PrepareApply(flags, state, serviceAccURI, masterSecret) } type helmApplier interface { PrepareApply( - flags helm.Options, stateFile *state.State, serviceAccURI string, masterSecret uri.MasterSecret, openStackCfg *config.OpenStackConfig, + flags helm.Options, stateFile *state.State, serviceAccURI string, masterSecret uri.MasterSecret, ) ( helm.Applier, bool, error) } diff --git a/internal/constellation/helm/BUILD.bazel b/internal/constellation/helm/BUILD.bazel index d579dddb977..6e3c5eee780 100644 --- a/internal/constellation/helm/BUILD.bazel +++ b/internal/constellation/helm/BUILD.bazel @@ -467,7 +467,6 @@ go_library( "//internal/cloud/gcpshared", "//internal/cloud/openstack", "//internal/compatibility", - "//internal/config", "//internal/constants", "//internal/constellation/helm/imageversion", "//internal/constellation/state", diff --git a/internal/constellation/helm/helm.go b/internal/constellation/helm/helm.go index ab043821497..d3f1e20f3a3 100644 --- a/internal/constellation/helm/helm.go +++ b/internal/constellation/helm/helm.go @@ -35,7 +35,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "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/constellation/state" "github.com/edgelesssys/constellation/v2/internal/file" @@ -91,13 +90,14 @@ type Options struct { MicroserviceVersion semver.Semver HelmWaitMode WaitMode ApplyTimeout time.Duration + OpenStackValues *OpenStackValues } // PrepareApply loads the charts and returns the executor to apply them. func (h Client) PrepareApply( - flags Options, stateFile *state.State, serviceAccURI string, masterSecret uri.MasterSecret, openStackCfg *config.OpenStackConfig, + flags Options, stateFile *state.State, serviceAccURI string, masterSecret uri.MasterSecret, ) (Applier, bool, error) { - releases, err := h.loadReleases(flags.CSP, flags.AttestationVariant, flags.K8sVersion, masterSecret, stateFile, flags, serviceAccURI, openStackCfg) + releases, err := h.loadReleases(flags.CSP, flags.AttestationVariant, flags.K8sVersion, masterSecret, stateFile, flags, serviceAccURI) if err != nil { return nil, false, fmt.Errorf("loading Helm releases: %w", err) } @@ -111,11 +111,11 @@ func (h Client) PrepareApply( func (h Client) loadReleases( csp cloudprovider.Provider, attestationVariant variant.Variant, k8sVersion versions.ValidK8sVersion, secret uri.MasterSecret, - stateFile *state.State, flags Options, serviceAccURI string, openStackCfg *config.OpenStackConfig, + stateFile *state.State, flags Options, serviceAccURI string, ) ([]release, error) { helmLoader := newLoader(csp, attestationVariant, k8sVersion, stateFile, h.cliVersion) h.log.Debug("Created new Helm loader") - return helmLoader.loadReleases(flags.Conformance, flags.DeployCSIDriver, flags.HelmWaitMode, secret, serviceAccURI, openStackCfg) + return helmLoader.loadReleases(flags.Conformance, flags.DeployCSIDriver, flags.HelmWaitMode, secret, serviceAccURI, flags.OpenStackValues) } // Applier runs the Helm actions. diff --git a/internal/constellation/helm/helm_test.go b/internal/constellation/helm/helm_test.go index aed7689d003..f93e49a8a92 100644 --- a/internal/constellation/helm/helm_test.go +++ b/internal/constellation/helm/helm_test.go @@ -217,7 +217,7 @@ func TestHelmApply(t *testing.T) { SetInfrastructure(state.Infrastructure{UID: "testuid"}). SetClusterValues(state.ClusterValues{MeasurementSalt: []byte{0x41}}), fakeServiceAccURI(csp), - uri.MasterSecret{Key: []byte("secret"), Salt: []byte("masterSalt")}, nil) + uri.MasterSecret{Key: []byte("secret"), Salt: []byte("masterSalt")}) var upgradeErr *compatibility.InvalidUpgradeError if tc.expectError { assert.Error(t, err) diff --git a/internal/constellation/helm/loader.go b/internal/constellation/helm/loader.go index 6f6c95d4feb..994575f6ff4 100644 --- a/internal/constellation/helm/loader.go +++ b/internal/constellation/helm/loader.go @@ -21,7 +21,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "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/constellation/helm/imageversion" "github.com/edgelesssys/constellation/v2/internal/constellation/state" @@ -115,9 +114,17 @@ func newLoader(csp cloudprovider.Provider, attestationVariant variant.Variant, k // that the new release is installed after the existing one to avoid name conflicts. type releaseApplyOrder []release +// OpenStackValues are helm values for OpenStack. +type OpenStackValues struct { + DeployYawolLoadBalancer bool + FloatingIPPoolID string + YawolFlavorID string + YawolImageID string +} + // loadReleases loads the embedded helm charts and returns them as a HelmReleases object. func (i *chartLoader) loadReleases(conformanceMode, deployCSIDriver bool, helmWaitMode WaitMode, masterSecret uri.MasterSecret, - serviceAccURI string, openStackCfg *config.OpenStackConfig, + serviceAccURI string, openStackValues *OpenStackValues, ) (releaseApplyOrder, error) { ciliumRelease, err := i.loadRelease(ciliumInfo, helmWaitMode) if err != nil { @@ -143,7 +150,7 @@ func (i *chartLoader) loadReleases(conformanceMode, deployCSIDriver bool, helmWa } svcVals, err := extraConstellationServicesValues(i.csp, i.attestationVariant, masterSecret, - serviceAccURI, i.stateFile.Infrastructure, openStackCfg) + serviceAccURI, i.stateFile.Infrastructure, openStackValues) if err != nil { return nil, fmt.Errorf("extending constellation-services values: %w", err) } @@ -169,13 +176,13 @@ func (i *chartLoader) loadReleases(conformanceMode, deployCSIDriver bool, helmWa } releases = append(releases, awsRelease) } - if i.csp == cloudprovider.OpenStack && openStackCfg.DeployYawolLoadBalancer != nil && *openStackCfg.DeployYawolLoadBalancer { + if i.csp == cloudprovider.OpenStack && openStackValues != nil && openStackValues.DeployYawolLoadBalancer { yawolRelease, err := i.loadRelease(yawolLBControllerInfo, WaitModeNone) if err != nil { return nil, fmt.Errorf("loading yawol chart: %w", err) } - yawolVals, err := extraYawolValues(serviceAccURI, i.stateFile.Infrastructure, openStackCfg) + yawolVals, err := extraYawolValues(serviceAccURI, i.stateFile.Infrastructure, openStackValues) if err != nil { return nil, fmt.Errorf("extending yawol chart values: %w", err) } diff --git a/internal/constellation/helm/loader_test.go b/internal/constellation/helm/loader_test.go index 9f394e2ea50..762f544b319 100644 --- a/internal/constellation/helm/loader_test.go +++ b/internal/constellation/helm/loader_test.go @@ -175,6 +175,19 @@ func TestConstellationServices(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) require := require.New(t) + var openstackValues *OpenStackValues + if tc.config.Provider.OpenStack != nil { + var deploy bool + if tc.config.Provider.OpenStack.DeployYawolLoadBalancer != nil { + deploy = *tc.config.Provider.OpenStack.DeployYawolLoadBalancer + } + openstackValues = &OpenStackValues{ + DeployYawolLoadBalancer: deploy, + FloatingIPPoolID: tc.config.Provider.OpenStack.FloatingIPPoolID, + YawolFlavorID: tc.config.Provider.OpenStack.YawolFlavorID, + YawolImageID: tc.config.Provider.OpenStack.YawolImageID, + } + } chartLoader := chartLoader{ csp: tc.config.GetProvider(), @@ -199,7 +212,7 @@ func TestConstellationServices(t *testing.T) { UID: "uid", Azure: &state.Azure{}, GCP: &state.GCP{}, - }, tc.config.Provider.OpenStack) + }, openstackValues) require.NoError(err) values = mergeMaps(values, extraVals) diff --git a/internal/constellation/helm/overrides.go b/internal/constellation/helm/overrides.go index 6dfea0c2c40..cf454735ada 100644 --- a/internal/constellation/helm/overrides.go +++ b/internal/constellation/helm/overrides.go @@ -18,7 +18,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" "github.com/edgelesssys/constellation/v2/internal/cloud/openstack" - "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constellation/state" "github.com/edgelesssys/constellation/v2/internal/kms/uri" @@ -83,7 +82,7 @@ func extraCiliumValues(provider cloudprovider.Provider, conformanceMode bool, ou // Values set inside this function are only applied during init, not during upgrade. func extraConstellationServicesValues( csp cloudprovider.Provider, attestationVariant variant.Variant, masterSecret uri.MasterSecret, serviceAccURI string, - output state.Infrastructure, openStackCfg *config.OpenStackConfig, + output state.Infrastructure, openStackCfg *OpenStackValues, ) (map[string]any, error) { extraVals := map[string]any{} extraVals["join-service"] = map[string]any{ @@ -152,7 +151,7 @@ func extraConstellationServicesValues( // extraYawolValues extends the given values map by some values depending on user input. // Values set inside this function are only applied during init, not during upgrade. -func extraYawolValues(serviceAccURI string, output state.Infrastructure, openStackCfg *config.OpenStackConfig) (map[string]any, error) { +func extraYawolValues(serviceAccURI string, output state.Infrastructure, openStackCfg *OpenStackValues) (map[string]any, error) { extraVals := map[string]any{} creds, err := openstack.AccountKeyFromURI(serviceAccURI) @@ -163,7 +162,7 @@ func extraYawolValues(serviceAccURI string, output state.Infrastructure, openSta extraVals["yawol-config"] = map[string]any{ "secretData": yawolIni, } - if openStackCfg.DeployYawolLoadBalancer != nil && *openStackCfg.DeployYawolLoadBalancer { + if openStackCfg != nil && openStackCfg.DeployYawolLoadBalancer { extraVals["yawol-controller"] = map[string]any{ "yawolOSSecretName": "yawolkey", // has to be larger than ~30s to account for slow OpenStack API calls. diff --git a/terraform-provider-constellation/docs/data-sources/attestation.md b/terraform-provider-constellation/docs/data-sources/attestation.md index 7ad4d491e8e..ec4118c0f73 100644 --- a/terraform-provider-constellation/docs/data-sources/attestation.md +++ b/terraform-provider-constellation/docs/data-sources/attestation.md @@ -33,6 +33,7 @@ data "constellation_attestation" "test" { * `azure-sev-snp` * `azure-tdx` * `gcp-sev-es` + * `qemu-vtpm` - `csp` (String) CSP (Cloud Service Provider) to use. (e.g. `azure`) See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports. - `image` (Attributes) Constellation OS Image to use on the nodes. (see [below for nested schema](#nestedatt--image)) @@ -82,6 +83,7 @@ Read-Only: * `azure-sev-snp` * `azure-tdx` * `gcp-sev-es` + * `qemu-vtpm` ### Nested Schema for `attestation.azure_firmware_signer_config` diff --git a/terraform-provider-constellation/docs/data-sources/image.md b/terraform-provider-constellation/docs/data-sources/image.md index d72b9ca91ed..7f7186b56f4 100644 --- a/terraform-provider-constellation/docs/data-sources/image.md +++ b/terraform-provider-constellation/docs/data-sources/image.md @@ -32,6 +32,7 @@ data "constellation_image" "example" { * `azure-sev-snp` * `azure-tdx` * `gcp-sev-es` + * `qemu-vtpm` - `csp` (String) CSP (Cloud Service Provider) to use. (e.g. `azure`) See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports. diff --git a/terraform-provider-constellation/docs/resources/cluster.md b/terraform-provider-constellation/docs/resources/cluster.md index 282493ce869..7b6d1ca2103 100644 --- a/terraform-provider-constellation/docs/resources/cluster.md +++ b/terraform-provider-constellation/docs/resources/cluster.md @@ -86,6 +86,7 @@ See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview - `gcp` (Attributes) GCP-specific configuration. (see [below for nested schema](#nestedatt--gcp)) - `in_cluster_endpoint` (String) The endpoint of the cluster. When not set, the out-of-cluster endpoint is used. - `license_id` (String) Constellation license ID. When not set, the community license is used. +- `openstack` (Attributes) OpenStack-specific configuration. (see [below for nested schema](#nestedatt--openstack)) ### Read-Only @@ -110,6 +111,7 @@ Required: * `azure-sev-snp` * `azure-tdx` * `gcp-sev-es` + * `qemu-vtpm` Optional: @@ -211,6 +213,24 @@ Required: - `project_id` (String) ID of the GCP project the cluster resides in. - `service_account_key` (String) Base64-encoded private key JSON object of the service account used within the cluster. + + +### Nested Schema for `openstack` + +Required: + +- `cloud` (String) Name of the cloud in the clouds.yaml file. +- `floating_ip_pool_id` (String) Floating IP pool to use for the VMs. +- `network_id` (String) OpenStack network ID to use for the VMs. +- `subnet_id` (String) OpenStack subnet ID to use for the VMs. + +Optional: + +- `clouds_yaml_path` (String) Path to the clouds.yaml file. +- `deploy_yawol_load_balancer` (Boolean) Whether to deploy a YAWOL load balancer. +- `yawol_flavor_id` (String) OpenStack flavor used by the yawollet. +- `yawol_image_id` (String) OpenStack OS image used by the yawollet. + ## Import Import is supported using the following syntax: diff --git a/terraform-provider-constellation/examples/full/stackit/main.tf b/terraform-provider-constellation/examples/full/stackit/main.tf new file mode 100644 index 00000000000..22ef92451be --- /dev/null +++ b/terraform-provider-constellation/examples/full/stackit/main.tf @@ -0,0 +1,128 @@ +terraform { + required_providers { + constellation = { + source = "edgelesssys/constellation" + version = "0.0.0" // replace with the version you want to use + } + random = { + source = "hashicorp/random" + version = "3.6.0" + } + } +} + +locals { + name = "constell" + image_version = "vX.Y.Z" + kubernetes_version = "vX.Y.Z" + microservice_version = "vX.Y.Z" + csp = "stackit" + attestation_variant = "qemu-vtpm" + zone = "eu01-1" + cloud = "stackit" + clouds_yaml_path = "~/.config/openstack/clouds.yaml" + floating_ip_pool_id = "970ace5c-458f-484a-a660-0903bcfd91ad" + stackit_project_id = "" // replace with the STACKIT project id + control_plane_count = 3 + worker_count = 2 + instance_type = "m1a.8cd" + deploy_yawol_load_balancer = true + yawol_image_id = "bcd6c13e-75d1-4c3f-bf0f-8f83580cc1be" + yawol_flavor_id = "3b11b27e-6c73-470d-b595-1d85b95a8cdf" + + master_secret = random_bytes.master_secret.hex + master_secret_salt = random_bytes.master_secret_salt.hex + measurement_salt = random_bytes.measurement_salt.hex +} + +resource "random_bytes" "master_secret" { + length = 32 +} + +resource "random_bytes" "master_secret_salt" { + length = 32 +} + +resource "random_bytes" "measurement_salt" { + length = 32 +} + +module "stackit_infrastructure" { + // replace $VERSION with the Constellation version you want to use, e.g., v2.14.0 + source = "https://github.com/edgelesssys/constellation/releases/download/$VERSION/terraform-module.zip//terraform-module/openstack" + name = local.name + node_groups = { + control_plane_default = { + role = "control-plane" + flavor_id = local.instance_type + state_disk_size = 30 + state_disk_type = "storage_premium_perf6" + initial_count = local.control_plane_count + zone = local.zone + }, + worker_default = { + role = "worker" + flavor_id = local.instance_type + state_disk_size = 30 + state_disk_type = "storage_premium_perf6" + initial_count = local.worker_count + zone = local.zone + } + } + image_id = data.constellation_image.bar.image.reference + debug = false + cloud = local.cloud + openstack_clouds_yaml_path = local.clouds_yaml_path + floating_ip_pool_id = local.floating_ip_pool_id + stackit_project_id = local.stackit_project_id +} + +data "constellation_attestation" "foo" { + csp = local.csp + attestation_variant = local.attestation_variant + image = data.constellation_image.bar.image +} + +data "constellation_image" "bar" { + csp = local.csp + attestation_variant = local.attestation_variant + version = local.image_version + marketplace_image = true +} + +resource "constellation_cluster" "stackit_example" { + csp = local.csp + name = module.stackit_infrastructure.name + uid = module.stackit_infrastructure.uid + image = data.constellation_image.bar.image + attestation = data.constellation_attestation.foo.attestation + kubernetes_version = local.kubernetes_version + constellation_microservice_version = local.microservice_version + init_secret = module.stackit_infrastructure.init_secret + master_secret = local.master_secret + master_secret_salt = local.master_secret_salt + measurement_salt = local.measurement_salt + out_of_cluster_endpoint = module.stackit_infrastructure.out_of_cluster_endpoint + in_cluster_endpoint = module.stackit_infrastructure.in_cluster_endpoint + api_server_cert_sans = module.stackit_infrastructure.api_server_cert_sans + openstack = { + cloud = local.cloud + clouds_yaml_path = local.clouds_yaml_path + floating_ip_pool_id = local.floating_ip_pool_id + deploy_yawol_load_balancer = local.deploy_yawol_load_balancer + yawol_image_id = local.yawol_image_id + yawol_flavor_id = local.yawol_flavor_id + network_id = module.stackit_infrastructure.network_id + subnet_id = module.stackit_infrastructure.lb_subnetwork_id + } + network_config = { + ip_cidr_node = module.stackit_infrastructure.ip_cidr_node + ip_cidr_service = "10.96.0.0/12" + } +} + +output "kubeconfig" { + value = constellation_cluster.stackit_example.kubeconfig + sensitive = true + description = "KubeConfig for the Constellation cluster." +} diff --git a/terraform-provider-constellation/internal/provider/BUILD.bazel b/terraform-provider-constellation/internal/provider/BUILD.bazel index 23cd58f8f02..1fac7618a92 100644 --- a/terraform-provider-constellation/internal/provider/BUILD.bazel +++ b/terraform-provider-constellation/internal/provider/BUILD.bazel @@ -23,6 +23,8 @@ go_library( "//internal/attestation/variant", "//internal/cloud/azureshared", "//internal/cloud/cloudprovider", + "//internal/cloud/openstack", + "//internal/cloud/openstack/clouds", "//internal/compatibility", "//internal/config", "//internal/constants", @@ -30,6 +32,7 @@ go_library( "//internal/constellation/helm", "//internal/constellation/kubecmd", "//internal/constellation/state", + "//internal/file", "//internal/grpc/dialer", "//internal/imagefetcher", "//internal/kms/uri", @@ -53,6 +56,7 @@ go_library( "@com_github_hashicorp_terraform_plugin_framework//types/basetypes", "@com_github_hashicorp_terraform_plugin_framework_validators//stringvalidator", "@com_github_hashicorp_terraform_plugin_log//tflog", + "@com_github_spf13_afero//:afero", ], ) diff --git a/terraform-provider-constellation/internal/provider/attestation_data_source_test.go b/terraform-provider-constellation/internal/provider/attestation_data_source_test.go index 3d8e7342e7f..4fed9fbe321 100644 --- a/terraform-provider-constellation/internal/provider/attestation_data_source_test.go +++ b/terraform-provider-constellation/internal/provider/attestation_data_source_test.go @@ -110,6 +110,58 @@ func TestAccAttestationSource(t *testing.T) { }, }, }, + "STACKIT qemu-vtpm success": { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: bazelPreCheck, + Steps: []resource.TestStep{ + { + Config: testingConfig + ` + data "constellation_attestation" "test" { + csp = "stackit" + attestation_variant = "qemu-vtpm" + image = { + version = "v2.13.0" + reference = "v2.13.0" + short_path = "v2.13.0" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.variant", "qemu-vtpm"), + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.bootloader_version", "0"), // since this is not supported on STACKIT, we expect 0 + + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.expected", "0000000000000000000000000000000000000000000000000000000000000000"), + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.warn_only", "false"), + ), + }, + }, + }, + "openstack qemu-vtpm success": { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: bazelPreCheck, + Steps: []resource.TestStep{ + { + Config: testingConfig + ` + data "constellation_attestation" "test" { + csp = "openstack" + attestation_variant = "qemu-vtpm" + image = { + version = "v2.13.0" + reference = "v2.13.0" + short_path = "v2.13.0" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.variant", "qemu-vtpm"), + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.bootloader_version", "0"), // since this is not supported on OpenStack, we expect 0 + + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.expected", "0000000000000000000000000000000000000000000000000000000000000000"), + resource.TestCheckResourceAttr("data.constellation_attestation.test", "attestation.measurements.15.warn_only", "false"), + ), + }, + }, + }, } for name, tc := range testCases { diff --git a/terraform-provider-constellation/internal/provider/cluster_resource.go b/terraform-provider-constellation/internal/provider/cluster_resource.go index f2dfb91c857..a12fe38dad9 100644 --- a/terraform-provider-constellation/internal/provider/cluster_resource.go +++ b/terraform-provider-constellation/internal/provider/cluster_resource.go @@ -26,6 +26,8 @@ import ( "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/azureshared" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" + openstackshared "github.com/edgelesssys/constellation/v2/internal/cloud/openstack" + "github.com/edgelesssys/constellation/v2/internal/cloud/openstack/clouds" "github.com/edgelesssys/constellation/v2/internal/compatibility" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" @@ -33,6 +35,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constellation/helm" "github.com/edgelesssys/constellation/v2/internal/constellation/kubecmd" "github.com/edgelesssys/constellation/v2/internal/constellation/state" + "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/grpc/dialer" "github.com/edgelesssys/constellation/v2/internal/kms/uri" "github.com/edgelesssys/constellation/v2/internal/license" @@ -50,6 +53,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/spf13/afero" ) var ( @@ -96,6 +100,7 @@ type ClusterResourceModel struct { Attestation types.Object `tfsdk:"attestation"` GCP types.Object `tfsdk:"gcp"` Azure types.Object `tfsdk:"azure"` + OpenStack types.Object `tfsdk:"openstack"` OwnerID types.String `tfsdk:"owner_id"` ClusterID types.String `tfsdk:"cluster_id"` @@ -129,6 +134,17 @@ type azureAttribute struct { LoadBalancerName string `tfsdk:"load_balancer_name"` } +type openStackAttribute struct { + Cloud string `tfsdk:"cloud"` + CloudsYAMLPath string `tfsdk:"clouds_yaml_path"` + FloatingIPPoolID string `tfsdk:"floating_ip_pool_id"` + DeployYawolLoadBalancer bool `tfsdk:"deploy_yawol_load_balancer"` + YawolImageID string `tfsdk:"yawol_image_id"` + YawolFlavorID string `tfsdk:"yawol_flavor_id"` + NetworkID string `tfsdk:"network_id"` + SubnetID string `tfsdk:"subnet_id"` +} + // extraMicroservicesAttribute is the extra microservices attribute's data model. type extraMicroservicesAttribute struct { CSIDriver bool `tfsdk:"csi_driver"` @@ -333,6 +349,53 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, }, + "openstack": schema.SingleNestedAttribute{ + MarkdownDescription: "OpenStack-specific configuration.", + Description: "OpenStack-specific configuration.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "cloud": schema.StringAttribute{ + MarkdownDescription: "Name of the cloud in the clouds.yaml file.", + Description: "Name of the cloud in the clouds.yaml file.", + Required: true, + }, + "clouds_yaml_path": schema.StringAttribute{ + MarkdownDescription: "Path to the clouds.yaml file.", + Description: "Path to the clouds.yaml file.", + Optional: true, + }, + "floating_ip_pool_id": schema.StringAttribute{ + MarkdownDescription: "Floating IP pool to use for the VMs.", + Description: "Floating IP pool to use for the VMs.", + Required: true, + }, + "deploy_yawol_load_balancer": schema.BoolAttribute{ + MarkdownDescription: "Whether to deploy a YAWOL load balancer.", + Description: "Whether to deploy a YAWOL load balancer.", + Optional: true, + }, + "yawol_image_id": schema.StringAttribute{ + MarkdownDescription: "OpenStack OS image used by the yawollet.", + Description: "OpenStack OS image used by the yawollet.", + Optional: true, + }, + "yawol_flavor_id": schema.StringAttribute{ + MarkdownDescription: "OpenStack flavor used by the yawollet.", + Description: "OpenStack flavor used by the yawollet.", + Optional: true, + }, + "network_id": schema.StringAttribute{ + MarkdownDescription: "OpenStack network ID to use for the VMs.", + Description: "OpenStack network ID to use for the VMs.", + Required: true, + }, + "subnet_id": schema.StringAttribute{ + MarkdownDescription: "OpenStack subnet ID to use for the VMs.", + Description: "OpenStack subnet ID to use for the VMs.", + Required: true, + }, + }, + }, // Computed (output) attributes "owner_id": schema.StringAttribute{ @@ -406,6 +469,26 @@ func (r *ClusterResource) ValidateConfig(ctx context.Context, req resource.Valid "GCP configuration not allowed", "When csp is not set to 'gcp', setting the 'gcp' configuration has no effect.", ) } + + // OpenStack Config is required for OpenStack + if (strings.EqualFold(data.CSP.ValueString(), cloudprovider.OpenStack.String()) || + strings.EqualFold(data.CSP.ValueString(), "stackit")) && + data.OpenStack.IsNull() { + resp.Diagnostics.AddAttributeError( + path.Root("openstack"), + "OpenStack configuration missing", "When csp is set to 'openstack' or 'stackit', the 'openstack' configuration must be set.", + ) + } + + // OpenStack Config should not be set for other CSPs + if !strings.EqualFold(data.CSP.ValueString(), cloudprovider.OpenStack.String()) && + !strings.EqualFold(data.CSP.ValueString(), "stackit") && + !data.OpenStack.IsNull() { + resp.Diagnostics.AddAttributeWarning( + path.Root("openstack"), + "OpenStack configuration not allowed", "When csp is not set to 'openstack' or 'stackit', setting the 'openstack' configuration has no effect.", + ) + } } // Configure configures the resource. @@ -779,6 +862,7 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel, serviceAccPayload := constellation.ServiceAccountPayload{} var gcpConfig gcpAttribute var azureConfig azureAttribute + var openStackConfig openStackAttribute switch csp { case cloudprovider.GCP: convertDiags = data.GCP.As(ctx, &gcpConfig, basetypes.ObjectAsOptions{}) @@ -815,6 +899,33 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel, PreferredAuthMethod: azureshared.AuthMethodUserAssignedIdentity, UamiResourceID: azureConfig.UamiResourceID, } + case cloudprovider.OpenStack: + convertDiags = data.OpenStack.As(ctx, &openStackConfig, basetypes.ObjectAsOptions{}) + diags.Append(convertDiags...) + if diags.HasError() { + return diags + } + cloudsYAML, err := clouds.ReadCloudsYAML(file.NewHandler(afero.NewOsFs()), openStackConfig.CloudsYAMLPath) + if err != nil { + diags.AddError("Reading clouds.yaml", err.Error()) + return diags + } + cloud, ok := cloudsYAML.Clouds[openStackConfig.Cloud] + if !ok { + diags.AddError("Reading clouds.yaml", fmt.Sprintf("Cloud %s not found in clouds.yaml", openStackConfig.Cloud)) + return diags + } + serviceAccPayload.OpenStack = openstackshared.AccountKey{ + AuthURL: cloud.AuthInfo.AuthURL, + Username: cloud.AuthInfo.Username, + Password: cloud.AuthInfo.Password, + ProjectID: cloud.AuthInfo.ProjectID, + ProjectName: cloud.AuthInfo.ProjectName, + UserDomainName: cloud.AuthInfo.UserDomainName, + ProjectDomainName: cloud.AuthInfo.ProjectDomainName, + RegionName: cloud.RegionName, + } + } serviceAccURI, err := constellation.MarshalServiceAccountURI(csp, serviceAccPayload) if err != nil { @@ -861,6 +972,11 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel, ProjectID: gcpConfig.ProjectID, IPCidrPod: networkCfg.IPCidrPod.ValueString(), } + case cloudprovider.OpenStack: + stateFile.Infrastructure.OpenStack = &state.OpenStack{ + NetworkID: openStackConfig.NetworkID, + SubnetID: openStackConfig.SubnetID, + } } // Check license @@ -937,6 +1053,14 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel, masterSecret: secrets.masterSecret, serviceAccURI: serviceAccURI, } + if csp == cloudprovider.OpenStack { + payload.openStackHelmValues = &helm.OpenStackValues{ + DeployYawolLoadBalancer: openStackConfig.DeployYawolLoadBalancer, + FloatingIPPoolID: openStackConfig.FloatingIPPoolID, + YawolImageID: openStackConfig.YawolImageID, + YawolFlavorID: openStackConfig.YawolFlavorID, + } + } helmDiags := r.applyHelmCharts(ctx, applier, payload, stateFile) diags.Append(helmDiags...) if diags.HasError() { @@ -1063,6 +1187,7 @@ type applyHelmChartsPayload struct { DeployCSIDriver bool // Whether to deploy the CSI driver. masterSecret uri.MasterSecret // master secret of the cluster. serviceAccURI string // URI of the service account used within the cluster. + openStackHelmValues *helm.OpenStackValues // OpenStack-specific Helm values. } // applyHelmCharts applies the Helm charts to the cluster. @@ -1083,10 +1208,11 @@ func (r *ClusterResource) applyHelmCharts(ctx context.Context, applier *constell // Allow destructive changes to the cluster. // The user has previously been warned about this when planning a microservice version change. AllowDestructive: helm.AllowDestructive, + OpenStackValues: payload.openStackHelmValues, } executor, _, err := applier.PrepareHelmCharts(options, state, - payload.serviceAccURI, payload.masterSecret, nil) + payload.serviceAccURI, payload.masterSecret) var upgradeErr *compatibility.InvalidUpgradeError if err != nil { if !errors.As(err, &upgradeErr) { diff --git a/terraform-provider-constellation/internal/provider/cluster_resource_test.go b/terraform-provider-constellation/internal/provider/cluster_resource_test.go index d9df7171399..fb1b5c4fc10 100644 --- a/terraform-provider-constellation/internal/provider/cluster_resource_test.go +++ b/terraform-provider-constellation/internal/provider/cluster_resource_test.go @@ -489,6 +489,68 @@ func TestAccClusterResource(t *testing.T) { }, }, }, + "stackit config missing": { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion(providerVersion), + PreCheck: bazelPreCheck, + Steps: []resource.TestStep{ + { + Config: fullClusterTestingConfig(t, "openstack") + fmt.Sprintf(` + resource "constellation_cluster" "test" { + csp = "stackit" + name = "constell" + uid = "test" + image = data.constellation_image.bar.image + attestation = data.constellation_attestation.foo.attestation + init_secret = "deadbeef" + master_secret = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + master_secret_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + measurement_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + out_of_cluster_endpoint = "192.0.2.1" + in_cluster_endpoint = "192.0.2.1" + network_config = { + ip_cidr_node = "0.0.0.0/24" + ip_cidr_service = "0.0.0.0/24" + ip_cidr_pod = "0.0.0.0/24" + } + kubernetes_version = "%s" + constellation_microservice_version = "%s" + } + `, versions.Default, providerVersion), + ExpectError: regexp.MustCompile(".*When csp is set to 'openstack' or 'stackit', the 'openstack' configuration\nmust be set.*"), + }, + }, + }, + "openstack config missing": { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion(providerVersion), + PreCheck: bazelPreCheck, + Steps: []resource.TestStep{ + { + Config: fullClusterTestingConfig(t, "openstack") + fmt.Sprintf(` + resource "constellation_cluster" "test" { + csp = "openstack" + name = "constell" + uid = "test" + image = data.constellation_image.bar.image + attestation = data.constellation_attestation.foo.attestation + init_secret = "deadbeef" + master_secret = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + master_secret_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + measurement_salt = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + out_of_cluster_endpoint = "192.0.2.1" + in_cluster_endpoint = "192.0.2.1" + network_config = { + ip_cidr_node = "0.0.0.0/24" + ip_cidr_service = "0.0.0.0/24" + ip_cidr_pod = "0.0.0.0/24" + } + kubernetes_version = "%s" + constellation_microservice_version = "%s" + } + `, versions.Default, providerVersion), + ExpectError: regexp.MustCompile(".*When csp is set to 'openstack' or 'stackit', the 'openstack' configuration\nmust be set.*"), + }, + }, + }, } for name, tc := range testCases { @@ -547,6 +609,19 @@ func fullClusterTestingConfig(t *testing.T, csp string) string { attestation_variant = "gcp-sev-es" image = data.constellation_image.bar.image }`, image) + case "openstack": + return providerConfig + fmt.Sprintf(` + data "constellation_image" "bar" { + version = "%s" + attestation_variant = "qemu-vtpm" + csp = "openstack" + } + + data "constellation_attestation" "foo" { + csp = "openstack" + attestation_variant = "qemu-vtpm" + image = data.constellation_image.bar.image + }`, image) default: t.Fatal("unknown csp") return "" diff --git a/terraform-provider-constellation/internal/provider/convert.go b/terraform-provider-constellation/internal/provider/convert.go index 552bdcdd215..08772816838 100644 --- a/terraform-provider-constellation/internal/provider/convert.go +++ b/terraform-provider-constellation/internal/provider/convert.go @@ -122,6 +122,10 @@ func convertFromTfAttestationCfg(tfAttestation attestationAttribute, attestation attestationConfig = &config.GCPSEVES{ Measurements: c11nMeasurements, } + case variant.QEMUVTPM{}: + attestationConfig = &config.QEMUVTPM{ + Measurements: c11nMeasurements, + } default: return nil, fmt.Errorf("unknown attestation variant: %s", attestationVariant) } @@ -177,7 +181,7 @@ func convertToTfAttestation(attVar variant.Variant, snpVersions attestationconfi XFAM: hex.EncodeToString(tdxCfg.XFAM), } tfAttestation.TDX = tfTdxCfg - case variant.GCPSEVES{}: + case variant.GCPSEVES{}, variant.QEMUVTPM{}: // no additional fields default: return tfAttestation, fmt.Errorf("unknown attestation variant: %s", attVar) diff --git a/terraform-provider-constellation/internal/provider/image_data_source.go b/terraform-provider-constellation/internal/provider/image_data_source.go index 5e97bdcb45a..6ed11c36392 100644 --- a/terraform-provider-constellation/internal/provider/image_data_source.go +++ b/terraform-provider-constellation/internal/provider/image_data_source.go @@ -252,9 +252,10 @@ func (d *ImageDataSource) Read(ctx context.Context, req datasource.ReadRequest, // Save data into Terraform state diags := resp.State.SetAttribute(ctx, path.Root("image"), imageAttribute{ - Reference: imageRef, - Version: imageSemver, - ShortPath: apiCompatibleVer.ShortPath(), + Reference: imageRef, + Version: imageSemver, + ShortPath: apiCompatibleVer.ShortPath(), + MarketplaceImage: data.MarketplaceImage.ValueBoolPointer(), }) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { diff --git a/terraform-provider-constellation/internal/provider/image_data_source_test.go b/terraform-provider-constellation/internal/provider/image_data_source_test.go index 787b7aacfaf..669899e3964 100644 --- a/terraform-provider-constellation/internal/provider/image_data_source_test.go +++ b/terraform-provider-constellation/internal/provider/image_data_source_test.go @@ -141,6 +141,38 @@ func TestAccImageDataSource(t *testing.T) { }, }, }, + "stackit success": { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: bazelPreCheck, + Steps: []resource.TestStep{ + { + Config: testingConfig + ` + data "constellation_image" "test" { + version = "v2.16.0" + attestation_variant = "qemu-vtpm" + csp = "stackit" + } + `, + Check: resource.TestCheckResourceAttr("data.constellation_image.test", "image.reference", "8ffc1740-1e41-4281-b872-f8088ffd7692"), // should be immutable, + }, + }, + }, + "openstack success": { + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + PreCheck: bazelPreCheck, + Steps: []resource.TestStep{ + { + Config: testingConfig + ` + data "constellation_image" "test" { + version = "v2.16.0" + attestation_variant = "qemu-vtpm" + csp = "openstack" + } + `, + Check: resource.TestCheckResourceAttr("data.constellation_image.test", "image.reference", "8ffc1740-1e41-4281-b872-f8088ffd7692"), // should be immutable, + }, + }, + }, "unknown attestation variant": { ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, PreCheck: bazelPreCheck, diff --git a/terraform-provider-constellation/internal/provider/shared_attributes.go b/terraform-provider-constellation/internal/provider/shared_attributes.go index 163794e9bde..b6f96cd1701 100644 --- a/terraform-provider-constellation/internal/provider/shared_attributes.go +++ b/terraform-provider-constellation/internal/provider/shared_attributes.go @@ -31,11 +31,12 @@ func newAttestationVariantAttributeSchema(t attributeType) schema.Attribute { " * `aws-nitro-tpm`\n" + " * `azure-sev-snp`\n" + " * `azure-tdx`\n" + - " * `gcp-sev-es`\n", + " * `gcp-sev-es`\n" + + " * `qemu-vtpm`\n", Required: isInput, Computed: !isInput, Validators: []validator.String{ - stringvalidator.OneOf("aws-sev-snp", "aws-nitro-tpm", "azure-sev-snp", "azure-tdx", "gcp-sev-es"), + stringvalidator.OneOf("aws-sev-snp", "aws-nitro-tpm", "azure-sev-snp", "azure-tdx", "gcp-sev-es", "qemu-vtpm"), }, } } @@ -47,7 +48,7 @@ func newCSPAttributeSchema() schema.Attribute { "See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview/clouds) that Constellation supports.", Required: true, Validators: []validator.String{ - stringvalidator.OneOf("aws", "azure", "gcp"), + stringvalidator.OneOf("aws", "azure", "gcp", "openstack", "stackit"), }, } }