mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
terraform-provider: add input validation (#2744)
* terraform-provider: add validation for `constellation_image` * terraform-provider: add validation for `constellation_cluster` * image: accept short path versions * terraform-provider: correct error statement Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * terraform-provider: remove superfluous log statements * terraform-provider: fix error assertion casing * terraform-provider: remove superfluous semver check * Update terraform-provider-constellation/internal/provider/shared_attributes.go Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com> --------- Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com>
This commit is contained in:
parent
db65f5116d
commit
82e2875927
@ -63,7 +63,8 @@ resource "constellation_cluster" "azure_example" {
|
||||
### Required
|
||||
|
||||
- `attestation` (Attributes) Attestation comprises the measurements and SEV-SNP specific parameters. The output of the [constellation_attestation](../data-sources/attestation.md) data source provides sensible defaults. (see [below for nested schema](#nestedatt--attestation))
|
||||
- `csp` (String) The Cloud Service Provider (CSP) the cluster should run on.
|
||||
- `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))
|
||||
- `init_secret` (String) Secret used for initialization of the cluster.
|
||||
- `master_secret` (String) Hex-encoded 32-byte master secret for the cluster.
|
||||
|
@ -26,8 +26,12 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// Ensure provider defined types fully satisfy framework interfaces.
|
||||
var _ datasource.DataSource = &AttestationDataSource{}
|
||||
var (
|
||||
// Ensure provider defined types fully satisfy framework interfaces.
|
||||
_ datasource.DataSource = &AttestationDataSource{}
|
||||
_ datasource.DataSourceWithValidateConfig = &AttestationDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &AttestationDataSource{}
|
||||
)
|
||||
|
||||
// NewAttestationDataSource creates a new attestation data source.
|
||||
func NewAttestationDataSource() datasource.DataSource {
|
||||
@ -109,9 +113,7 @@ func (d *AttestationDataSource) Schema(_ context.Context, _ datasource.SchemaReq
|
||||
// ValidateConfig validates the configuration for the image data source.
|
||||
func (d *AttestationDataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
|
||||
var data AttestationDataSourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
@ -36,22 +38,29 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/semver"
|
||||
"github.com/edgelesssys/constellation/v2/internal/versions"
|
||||
datastruct "github.com/edgelesssys/constellation/v2/terraform-provider-constellation/internal/data"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// Ensure provider defined types fully satisfy framework interfaces.
|
||||
var (
|
||||
_ resource.Resource = &ClusterResource{}
|
||||
_ resource.ResourceWithImportState = &ClusterResource{}
|
||||
_ resource.ResourceWithModifyPlan = &ClusterResource{}
|
||||
// Ensure provider defined types fully satisfy framework interfaces.
|
||||
_ resource.Resource = &ClusterResource{}
|
||||
_ resource.ResourceWithImportState = &ClusterResource{}
|
||||
_ resource.ResourceWithModifyPlan = &ClusterResource{}
|
||||
_ resource.ResourceWithValidateConfig = &ClusterResource{}
|
||||
|
||||
cidrRegex = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$`)
|
||||
hexRegex = regexp.MustCompile(`^[0-9a-fA-F]+$`)
|
||||
base64Regex = regexp.MustCompile(`^[-A-Za-z0-9+/]*={0,3}$`)
|
||||
)
|
||||
|
||||
// NewClusterResource creates a new cluster resource.
|
||||
@ -140,11 +149,7 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||
Description: "Name used in the cluster's named resources / cluster name.",
|
||||
Required: true, // TODO: Make optional and default to Constell.
|
||||
},
|
||||
"csp": schema.StringAttribute{
|
||||
MarkdownDescription: "The Cloud Service Provider (CSP) the cluster should run on.",
|
||||
Description: "The Cloud Service Provider (CSP) the cluster should run on.",
|
||||
Required: true,
|
||||
},
|
||||
"csp": newCSPAttributeSchema(),
|
||||
"uid": schema.StringAttribute{
|
||||
MarkdownDescription: "The UID of the cluster.",
|
||||
Description: "The UID of the cluster.",
|
||||
@ -199,16 +204,25 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||
MarkdownDescription: "CIDR range of the cluster's node network.",
|
||||
Description: "CIDR range of the cluster's node network.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.RegexMatches(cidrRegex, "Node IP CIDR must be a valid CIDR range."),
|
||||
},
|
||||
},
|
||||
"ip_cidr_pod": schema.StringAttribute{
|
||||
MarkdownDescription: "CIDR range of the cluster's pod network. Only required for clusters running on GCP.",
|
||||
Description: "CIDR range of the cluster's pod network. Only required for clusters running on GCP.",
|
||||
Optional: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.RegexMatches(cidrRegex, "Pod IP CIDR must be a valid CIDR range."),
|
||||
},
|
||||
},
|
||||
"ip_cidr_service": schema.StringAttribute{
|
||||
MarkdownDescription: "CIDR range of the cluster's service network.",
|
||||
Description: "CIDR range of the cluster's service network.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.RegexMatches(cidrRegex, "Service IP CIDR must be a valid CIDR range."),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -216,16 +230,28 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||
MarkdownDescription: "Hex-encoded 32-byte master secret for the cluster.",
|
||||
Description: "Hex-encoded 32-byte master secret for the cluster.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(64, 64),
|
||||
stringvalidator.RegexMatches(hexRegex, "Master secret must be a hex-encoded 32-byte value."),
|
||||
},
|
||||
},
|
||||
"master_secret_salt": schema.StringAttribute{
|
||||
MarkdownDescription: "Hex-encoded 32-byte master secret salt for the cluster.",
|
||||
Description: "Hex-encoded 32-byte master secret salt for the cluster.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(64, 64),
|
||||
stringvalidator.RegexMatches(hexRegex, "Master secret salt must be a hex-encoded 32-byte value."),
|
||||
},
|
||||
},
|
||||
"measurement_salt": schema.StringAttribute{
|
||||
MarkdownDescription: "Hex-encoded 32-byte measurement salt for the cluster.",
|
||||
Description: "Hex-encoded 32-byte measurement salt for the cluster.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthBetween(64, 64),
|
||||
stringvalidator.RegexMatches(hexRegex, "Measurement salt must be a hex-encoded 32-byte value."),
|
||||
},
|
||||
},
|
||||
"init_secret": schema.StringAttribute{
|
||||
MarkdownDescription: "Secret used for initialization of the cluster.",
|
||||
@ -242,6 +268,9 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||
MarkdownDescription: "Base64-encoded private key JSON object of the service account used within the cluster.",
|
||||
Description: "Base64-encoded private key JSON object of the service account used within the cluster.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.RegexMatches(base64Regex, "Service account key must be a base64-encoded JSON object."),
|
||||
},
|
||||
},
|
||||
"project_id": schema.StringAttribute{
|
||||
MarkdownDescription: "ID of the GCP project the cluster resides in.",
|
||||
@ -331,6 +360,67 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfig validates the configuration for the resource.
|
||||
func (r *ClusterResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
|
||||
var data ClusterResourceModel
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Azure Config is required for Azure
|
||||
if strings.EqualFold(data.CSP.ValueString(), cloudprovider.Azure.String()) && data.Azure.IsNull() {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("azure"),
|
||||
"Azure configuration missing", "When csp is set to 'azure', the 'azure' configuration must be set.",
|
||||
)
|
||||
}
|
||||
|
||||
// Azure Config should not be set for other CSPs
|
||||
if !strings.EqualFold(data.CSP.ValueString(), cloudprovider.Azure.String()) && !data.Azure.IsNull() {
|
||||
resp.Diagnostics.AddAttributeWarning(
|
||||
path.Root("azure"),
|
||||
"Azure configuration not allowed", "When csp is not set to 'azure', setting the 'azure' configuration has no effect.",
|
||||
)
|
||||
}
|
||||
|
||||
// GCP Config is required for GCP
|
||||
if strings.EqualFold(data.CSP.ValueString(), cloudprovider.GCP.String()) && data.GCP.IsNull() {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("gcp"),
|
||||
"GCP configuration missing", "When csp is set to 'gcp', the 'gcp' configuration must be set.",
|
||||
)
|
||||
}
|
||||
|
||||
// GCP Config should not be set for other CSPs
|
||||
if !strings.EqualFold(data.CSP.ValueString(), cloudprovider.GCP.String()) && !data.GCP.IsNull() {
|
||||
resp.Diagnostics.AddAttributeWarning(
|
||||
path.Root("gcp"),
|
||||
"GCP configuration not allowed", "When csp is not set to 'gcp', setting the 'gcp' configuration has no effect.",
|
||||
)
|
||||
}
|
||||
|
||||
networkCfg, diags := r.getNetworkConfig(ctx, &data)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
// Pod IP CIDR is required for GCP
|
||||
if strings.EqualFold(data.CSP.ValueString(), cloudprovider.GCP.String()) && networkCfg.IPCidrPod == "" {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("network_config").AtName("ip_cidr_pod"),
|
||||
"Pod IP CIDR missing", "When csp is set to 'gcp', 'ip_cidr_pod' must be set.",
|
||||
)
|
||||
}
|
||||
// Pod IP CIDR should not be set for other CSPs
|
||||
if !strings.EqualFold(data.CSP.ValueString(), cloudprovider.GCP.String()) && networkCfg.IPCidrPod != "" {
|
||||
resp.Diagnostics.AddAttributeWarning(
|
||||
path.Root("network_config").AtName("ip_cidr_pod"),
|
||||
"Pod IP CIDR not allowed", "When csp is not set to 'gcp', setting 'ip_cidr_pod' has no effect.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Configure configures the resource.
|
||||
func (r *ClusterResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
// Prevent panic if the provider has not been configured.
|
||||
@ -578,11 +668,8 @@ func (r *ClusterResource) apply(ctx context.Context, data *ClusterResourceModel,
|
||||
}
|
||||
|
||||
// parse network config
|
||||
var networkCfg networkConfigAttribute
|
||||
convertDiags = data.NetworkConfig.As(ctx, &networkCfg, basetypes.ObjectAsOptions{
|
||||
UnhandledNullAsEmpty: true, // we want to allow null values, as some of the field's subfields are optional.
|
||||
})
|
||||
diags.Append(convertDiags...)
|
||||
networkCfg, getDiags := r.getNetworkConfig(ctx, data)
|
||||
diags.Append(getDiags...)
|
||||
if diags.HasError() {
|
||||
return diags
|
||||
}
|
||||
@ -1050,6 +1137,15 @@ func (r *ClusterResource) getMicroserviceVersion(ctx context.Context, data *Clus
|
||||
return ver, diags
|
||||
}
|
||||
|
||||
// getNetworkConfig returns the network config from the Terraform state.
|
||||
func (r *ClusterResource) getNetworkConfig(ctx context.Context, data *ClusterResourceModel) (networkConfigAttribute, diag.Diagnostics) {
|
||||
var networkCfg networkConfigAttribute
|
||||
diags := data.NetworkConfig.As(ctx, &networkCfg, basetypes.ObjectAsOptions{
|
||||
UnhandledNullAsEmpty: true, // we want to allow null values, as some of the field's subfields are optional.
|
||||
})
|
||||
return networkCfg, diags
|
||||
}
|
||||
|
||||
// tfContextLogger is a logging adapter between the tflog package and
|
||||
// Constellation's logger.
|
||||
type tfContextLogger struct {
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAccClusteResourceImports(t *testing.T) {
|
||||
func TestAccClusterResourceImports(t *testing.T) {
|
||||
// Set the path to the Terraform binary for acceptance testing when running under Bazel.
|
||||
bazelPreCheck := func() { bazelSetTerraformBinaryPath(t) }
|
||||
|
||||
@ -107,3 +107,300 @@ func TestAccClusteResourceImports(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccClusterResource(t *testing.T) {
|
||||
// Set the path to the Terraform binary for acceptance testing when running under Bazel.
|
||||
bazelPreCheck := func() { bazelSetTerraformBinaryPath(t) }
|
||||
|
||||
testCases := map[string]resource.TestCase{
|
||||
"master secret not hex": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "aws") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "aws"
|
||||
name = "constell"
|
||||
uid = "test"
|
||||
image = data.constellation_image.bar.image
|
||||
attestation = data.constellation_attestation.foo.attestation
|
||||
init_secret = "deadbeef"
|
||||
master_secret = "xxx"
|
||||
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"
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*Master secret must be a hex-encoded 32-byte.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"master secret salt not hex": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "aws") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "aws"
|
||||
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 = "xxx"
|
||||
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"
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*Master secret salt must be a hex-encoded 32-byte.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"measurement salt not hex": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "aws") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "aws"
|
||||
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 = "xxx"
|
||||
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"
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*Measurement salt must be a hex-encoded 32-byte.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid node ip cidr": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "aws") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "aws"
|
||||
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.0x.0/xxx"
|
||||
ip_cidr_service = "0.0.0.0/24"
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*Node IP CIDR must be a valid CIDR.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid service ip cidr": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "aws") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "aws"
|
||||
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.0x.0/xxx"
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*Service IP CIDR must be a valid CIDR.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"azure config missing": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "azure") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "azure"
|
||||
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"
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*When csp is set to 'azure', the 'azure' configuration must be set.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"gcp config missing": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "gcp") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "gcp"
|
||||
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"
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*When csp is set to 'gcp', the 'gcp' configuration must be set.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"gcp pod ip cidr missing": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactoriesWithVersion("v2.13.0"),
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fullClusterTestingConfig(t, "gcp") + `
|
||||
resource "constellation_cluster" "test" {
|
||||
csp = "gcp"
|
||||
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"
|
||||
}
|
||||
gcp = {
|
||||
project_id = "test"
|
||||
service_account_key = "eyJ0ZXN0IjogInRlc3QifQ=="
|
||||
}
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*When csp is set to 'gcp', 'ip_cidr_pod' must be set.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
resource.Test(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func fullClusterTestingConfig(t *testing.T, csp string) string {
|
||||
t.Helper()
|
||||
|
||||
providerConfig := `
|
||||
provider "constellation" {}
|
||||
`
|
||||
|
||||
switch csp {
|
||||
case "aws":
|
||||
return providerConfig + `
|
||||
data "constellation_image" "bar" {
|
||||
version = "v2.13.0"
|
||||
attestation_variant = "aws-sev-snp"
|
||||
csp = "aws"
|
||||
region = "us-east-2"
|
||||
}
|
||||
|
||||
data "constellation_attestation" "foo" {
|
||||
csp = "aws"
|
||||
attestation_variant = "aws-sev-snp"
|
||||
image = data.constellation_image.bar.image
|
||||
}`
|
||||
case "azure":
|
||||
return providerConfig + `
|
||||
data "constellation_image" "bar" {
|
||||
version = "v2.13.0"
|
||||
attestation_variant = "azure-sev-snp"
|
||||
csp = "azure"
|
||||
}
|
||||
|
||||
data "constellation_attestation" "foo" {
|
||||
csp = "azure"
|
||||
attestation_variant = "azure-sev-snp"
|
||||
image = data.constellation_image.bar.image
|
||||
}`
|
||||
case "gcp":
|
||||
return providerConfig + `
|
||||
data "constellation_image" "bar" {
|
||||
version = "v2.13.0"
|
||||
attestation_variant = "gcp-sev-es"
|
||||
csp = "gcp"
|
||||
}
|
||||
|
||||
data "constellation_attestation" "foo" {
|
||||
csp = "gcp"
|
||||
attestation_variant = "gcp-sev-es"
|
||||
image = data.constellation_image.bar.image
|
||||
}`
|
||||
default:
|
||||
t.Fatal("unknown csp")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -16,6 +17,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"
|
||||
"github.com/edgelesssys/constellation/v2/internal/semver"
|
||||
"github.com/edgelesssys/constellation/v2/terraform-provider-constellation/internal/data"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
@ -26,10 +28,12 @@ import (
|
||||
|
||||
var (
|
||||
// Ensure provider defined types fully satisfy framework interfaces.
|
||||
_ datasource.DataSource = &ImageDataSource{}
|
||||
caseInsensitiveCommunityGalleriesRegexp = regexp.MustCompile(`(?i)\/communitygalleries\/`)
|
||||
caseInsensitiveImagesRegExp = regexp.MustCompile(`(?i)\/images\/`)
|
||||
caseInsensitiveVersionsRegExp = regexp.MustCompile(`(?i)\/versions\/`)
|
||||
_ datasource.DataSource = &ImageDataSource{}
|
||||
_ datasource.DataSourceWithValidateConfig = &ImageDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &ImageDataSource{}
|
||||
caseInsensitiveCommunityGalleriesRegexp = regexp.MustCompile(`(?i)\/communitygalleries\/`)
|
||||
caseInsensitiveImagesRegExp = regexp.MustCompile(`(?i)\/images\/`)
|
||||
caseInsensitiveVersionsRegExp = regexp.MustCompile(`(?i)\/versions\/`)
|
||||
)
|
||||
|
||||
// NewImageDataSource creates a new data source for fetching Constellation OS images
|
||||
@ -103,19 +107,48 @@ func (d *ImageDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
|
||||
// ValidateConfig validates the configuration for the image data source.
|
||||
func (d *ImageDataSource) ValidateConfig(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
|
||||
var data ImageDataSourceModel
|
||||
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Region must be set for AWS
|
||||
if data.CSP.Equal(types.StringValue("aws")) && data.Region.IsNull() {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("region"),
|
||||
"Region must be set for AWS", "When csp is set to 'aws', 'region' must be specified.",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Setting Region for non-AWS CSPs has no effect
|
||||
if !data.CSP.Equal(types.StringValue("aws")) && !data.Region.IsNull() {
|
||||
resp.Diagnostics.AddAttributeWarning(
|
||||
path.Root("region"),
|
||||
"Region should only be set for AWS", "When another CSP than AWS is used, setting 'region' has no effect.",
|
||||
)
|
||||
}
|
||||
|
||||
// Marketplace image is only supported for Azure
|
||||
if !data.CSP.Equal(types.StringValue("azure")) && !data.MarketplaceImage.IsNull() {
|
||||
resp.Diagnostics.AddAttributeWarning(
|
||||
path.Root("marketplace_image"),
|
||||
"Marketplace images are currently only supported on Azure", "When another CSP than Azure is used, setting 'marketplace_image' has no effect.",
|
||||
)
|
||||
}
|
||||
|
||||
// Version should be a valid semver or short path, if set
|
||||
if !data.Version.IsNull() {
|
||||
_, semverErr := semver.New(data.Version.ValueString())
|
||||
|
||||
_, shortpathErr := versionsapi.NewVersionFromShortPath(data.Version.ValueString(), versionsapi.VersionKindImage)
|
||||
|
||||
if semverErr != nil && shortpathErr != nil {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("version"),
|
||||
"Invalid Version",
|
||||
fmt.Sprintf("When parsing the version (%s), an error occurred: %s", data.Version.ValueString(), errors.Join(semverErr, shortpathErr)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,6 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
// Read testing
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
@ -61,7 +60,6 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
// Read testing
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
@ -78,7 +76,6 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
// Read testing
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
@ -96,7 +93,6 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
// Read testing
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
@ -115,7 +111,6 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
// Read testing
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
@ -132,7 +127,6 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
// Read testing
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
@ -149,7 +143,6 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
// Read testing
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
@ -162,6 +155,22 @@ func TestAccImageDataSource(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"invalid version": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
data "constellation_image" "test" {
|
||||
version = "xxx"
|
||||
attestation_variant = "azure-sev-snp"
|
||||
csp = "azure"
|
||||
}
|
||||
`,
|
||||
ExpectError: regexp.MustCompile(".*Invalid Version.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
|
Loading…
Reference in New Issue
Block a user