diff --git a/docs/data-sources/alb_waf_custom_rule_group.md b/docs/data-sources/alb_waf_custom_rule_group.md new file mode 100644 index 000000000..4adfc985d --- /dev/null +++ b/docs/data-sources/alb_waf_custom_rule_group.md @@ -0,0 +1,87 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_alb_waf_custom_rule_group Data Source - stackit" +subcategory: "" +description: |- + ALB WAF Custom Rule Group resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level. + ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. +--- + +# stackit_alb_waf_custom_rule_group (Data Source) + +ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. + +~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. + +## Example Usage + +```terraform +data "stackit_alb_waf_custom_rule_group" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-custom-rule-group" +} +``` + + +## Schema + +### Required + +- `name` (String) Custom rule group configuration name. +- `project_id` (String) STACKIT project ID associated with the ALB WAF Custom Rule Group. + +### Optional + +- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`". +- `rules` (Attributes List) Enriched rules containing auto-generated IDs and computed severity values. (see [below for nested schema](#nestedatt--rules)) + + +### Nested Schema for `rules` + +Read-Only: + +- `behavior` (Attributes) (see [below for nested schema](#nestedatt--rules--behavior)) +- `conditions` (Attributes List) (see [below for nested schema](#nestedatt--rules--conditions)) +- `description` (String) A clear description explaining the threat vector or criteria addressed by this rule. +- `id` (Number) Backend auto-allocated unique rule ID within the valid 1-99999 threshold. + + +### Nested Schema for `rules.behavior` + +Read-Only: + +- `action` (String) The protective stance action. ACTION_DENY forces a 403 status response code. +- `log` (Boolean) Determines whether an entry should be generated in the security ledger upon a rule hit. +- `log_msg` (String) Custom notification message string mapped to underlying logdata contexts. Required if log is true. +- `severity` (String) Severity classification metric used by internal analytics graphs. + + + +### Nested Schema for `rules.conditions` + +Read-Only: + +- `operator` (Attributes) The comparison logic executed against the transformed variable. (see [below for nested schema](#nestedatt--rules--conditions--operator)) +- `transformations` (List of String) Ordered normalization steps applied before the operator runs. +- `variable` (Attributes) The part of the HTTP transaction to inspect. (see [below for nested schema](#nestedatt--rules--conditions--variable)) + + +### Nested Schema for `rules.conditions.operator` + +Read-Only: + +- `type` (String) The operational evaluation type definition macro. +- `value` (String) The text or rule regex pattern arguments applied inside the operator execution loop. + + + +### Nested Schema for `rules.conditions.variable` + +Read-Only: + +- `type` (String) The targeted validation engine variable macro. +- `value` (String) Optional key element context for map variables (e.g., matching a 'Host' header key). diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md index 1f335dabf..c14a428bb 100644 --- a/docs/data-sources/alb_waf_managed_rule_set.md +++ b/docs/data-sources/alb_waf_managed_rule_set.md @@ -39,7 +39,6 @@ data "stackit_alb_waf_managed_rule_set" "example" { - `groups` (Attributes Map) Inventory of all available Managed Rule Set groups and their current configuration. (see [below for nested schema](#nestedatt--groups)) - `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`". - `type` (String) Type of the Managed Rule Set. -- `usage` (Attributes) Managed Rule Set usage (see [below for nested schema](#nestedatt--usage)) - `version` (String) Managed Rule Set version. @@ -59,13 +58,3 @@ Read-Only: - `description` (String) A description of what this rule does. - `mode` (String) The current mode of the rule. - `severity` (String) Impact level. - - - - -### Nested Schema for `usage` - -Read-Only: - -- `count` (Number) Number of WAFs using this Managed Rule Set. -- `items` (List of String) List of WAFs that use this Managed Rule Set. diff --git a/docs/resources/alb_waf_custom_rule_group.md b/docs/resources/alb_waf_custom_rule_group.md new file mode 100644 index 000000000..e5bcee6a1 --- /dev/null +++ b/docs/resources/alb_waf_custom_rule_group.md @@ -0,0 +1,133 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "stackit_alb_waf_custom_rule_group Resource - stackit" +subcategory: "" +description: |- + ALB WAF Custom Rule Group resource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on resource level. + ~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our guide https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources for how to opt-in to use beta resources. +--- + +# stackit_alb_waf_custom_rule_group (Resource) + +ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on resource level. + +~> This resource is in beta and may be subject to breaking changes in the future. Use with caution. See our [guide](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/guides/opting_into_beta_resources) for how to opt-in to use beta resources. + +## Example Usage + +```terraform +resource "stackit_alb_waf_custom_rule_group" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-custom-rule-group" + rules = [ + { + description = "My custom rule group" + behavior = { + action = "ACTION_DENY" + log = true + logMsg = "Some custom notification message string" + } + conditions = [ + { + operator = { + type = "OPERATOR_BEGINS_WITH" + value = "allowed objects" + } + transformations = [ + "TRANSFORMATION_LOWERCASE" + ] + variable = { + type = "VARIABLE_REQUEST_HEADERS" + value = "Host" + } + } + ] + } + ] +} +``` + + +## Schema + +### Required + +- `name` (String) Custom rule group configuration name. +- `project_id` (String) STACKIT project ID associated with the ALB WAF Custom Rule Group. +- `rules` (Attributes List) Enriched rules containing auto-generated IDs and computed severity values. (see [below for nested schema](#nestedatt--rules)) + +### Optional + +- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used. + +### Read-Only + +- `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`". + + +### Nested Schema for `rules` + +Required: + +- `behavior` (Attributes) (see [below for nested schema](#nestedatt--rules--behavior)) +- `conditions` (Attributes List) (see [below for nested schema](#nestedatt--rules--conditions)) + +Optional: + +- `description` (String) A clear description explaining the threat vector or criteria addressed by this rule. + +Read-Only: + +- `id` (Number) Backend auto-allocated unique rule ID within the valid 1-99999 threshold. + + +### Nested Schema for `rules.behavior` + +Required: + +- `action` (String) The protective stance action. ACTION_DENY forces a 403 status response code. + +Optional: + +- `log` (Boolean) Determines whether an entry should be generated in the security ledger upon a rule hit. +- `log_msg` (String) Custom notification message string mapped to underlying logdata contexts. Required if log is true. + +Read-Only: + +- `severity` (String) Severity classification metric used by internal analytics graphs. + + + +### Nested Schema for `rules.conditions` + +Required: + +- `operator` (Attributes) The comparison logic executed against the transformed variable. (see [below for nested schema](#nestedatt--rules--conditions--operator)) +- `variable` (Attributes) The part of the HTTP transaction to inspect. (see [below for nested schema](#nestedatt--rules--conditions--variable)) + +Optional: + +- `transformations` (List of String) Ordered normalization steps applied before the operator runs. + + +### Nested Schema for `rules.conditions.operator` + +Required: + +- `type` (String) The operational evaluation type definition macro. + +Optional: + +- `value` (String) The text or rule regex pattern arguments applied inside the operator execution loop. + + + +### Nested Schema for `rules.conditions.variable` + +Required: + +- `type` (String) The targeted validation engine variable macro. + +Optional: + +- `value` (String) Optional key element context for map variables (e.g., matching a 'Host' header key). diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md index eeb2c93f3..390e30ded 100644 --- a/docs/resources/alb_waf_managed_rule_set.md +++ b/docs/resources/alb_waf_managed_rule_set.md @@ -40,7 +40,6 @@ resource "stackit_alb_waf_managed_rule_set" "example" { - `groups` (Attributes Map) Inventory of all available Managed Rule Set groups and their current configuration. (see [below for nested schema](#nestedatt--groups)) - `id` (String) Terraform's internal resource identifier. Structured as "`project_id`,`region`,`name`". -- `usage` (Attributes) Managed Rule Set usage (see [below for nested schema](#nestedatt--usage)) - `version` (String) Managed Rule Set version. @@ -60,13 +59,3 @@ Read-Only: - `description` (String) A description of what this rule does. - `mode` (String) The current mode of the rule. - `severity` (String) Impact level. - - - - -### Nested Schema for `usage` - -Read-Only: - -- `count` (Number) Number of WAFs using this Managed Rule Set. -- `items` (List of String) List of WAFs that use this Managed Rule Set. diff --git a/examples/data-sources/stackit_alb_waf_custom_rule_group/data-source.tf b/examples/data-sources/stackit_alb_waf_custom_rule_group/data-source.tf new file mode 100644 index 000000000..1182aaac4 --- /dev/null +++ b/examples/data-sources/stackit_alb_waf_custom_rule_group/data-source.tf @@ -0,0 +1,4 @@ +data "stackit_alb_waf_custom_rule_group" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-custom-rule-group" +} diff --git a/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf new file mode 100644 index 000000000..b34c43a61 --- /dev/null +++ b/examples/resources/stackit_alb_waf_custom_rule_group/resource.tf @@ -0,0 +1,29 @@ +resource "stackit_alb_waf_custom_rule_group" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-custom-rule-group" + rules = [ + { + description = "My custom rule group" + behavior = { + action = "ACTION_DENY" + log = true + logMsg = "Some custom notification message string" + } + conditions = [ + { + operator = { + type = "OPERATOR_BEGINS_WITH" + value = "allowed objects" + } + transformations = [ + "TRANSFORMATION_LOWERCASE" + ] + variable = { + type = "VARIABLE_REQUEST_HEADERS" + value = "Host" + } + } + ] + } + ] +} diff --git a/go.mod b/go.mod index f530b0b66..c60ea4438 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/hashicorp/terraform-plugin-testing v1.16.0 github.com/stackitcloud/stackit-sdk-go/core v0.26.0 github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0 - github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0 + github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0 github.com/stackitcloud/stackit-sdk-go/services/cdn v1.19.0 github.com/stackitcloud/stackit-sdk-go/services/certificates v1.9.0 github.com/stackitcloud/stackit-sdk-go/services/dns v0.21.0 diff --git a/go.sum b/go.sum index 78bb4a31f..74c2781a8 100644 --- a/go.sum +++ b/go.sum @@ -672,8 +672,8 @@ github.com/stackitcloud/stackit-sdk-go/core v0.26.0 h1:jQEb9gkehfp6VCP6TcYk7BI10 github.com/stackitcloud/stackit-sdk-go/core v0.26.0/go.mod h1:WU1hhxnjXw2EV7CYa1nlEvNpMiRY6CvmIOaHuL3pOaA= github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0 h1:WoWlHdzISGXPEaJOYt6HP5F9M5nbyCJL6VqRJZIaOQs= github.com/stackitcloud/stackit-sdk-go/services/alb v0.16.0/go.mod h1:eK6oRB5Tmpt6KbXQ4UYBGg2LgW5bPtVoncL9E8JSRww= -github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0 h1:0WsTSSZ0LjNpM3E1d3MgkBXmzMQThVQ7IuXhL2w4EyM= -github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.10.0/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE= +github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0 h1:ejTZTnGKFUWs9Ch9U30Jd+tpDA/SnHuSF9DpfD6w+To= +github.com/stackitcloud/stackit-sdk-go/services/albwaf v0.11.0/go.mod h1:4M9G1I64kZwlXO32ZoIpt0GAN4SpZ1SYerwCVVIBGoE= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.2 h1:b7WJ/vwxlVmNNX91kI3obqGcuoPAyaCbDL5aCMQ/sNg= github.com/stackitcloud/stackit-sdk-go/services/authorization v0.15.2/go.mod h1:T/JF25XGJ3GqER/1L2N//DgY8x5tY7gA3N+/0nvmOWY= github.com/stackitcloud/stackit-sdk-go/services/cdn v1.19.0 h1:k+KJ4gp9awhJMY5y55vDqRSr6G/S9+8haTNILGbgH9s= diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go index b8e539850..7b5455f8a 100644 --- a/stackit/internal/services/albwaf/albwaf_acc_test.go +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -15,17 +15,62 @@ import ( "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/stackitcloud/stackit-sdk-go/core/utils" - albwaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" + albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" ) var ( + //go:embed testdata/custom-rule-group-min.tf + customRuleGroupMinConfig string + + //go:embed testdata/custom-rule-group-max.tf + customRuleGroupMaxConfig string + //go:embed testdata/managed-rule-set.tf managedRuleSetConfig string ) +var testCustomRuleGroupMin = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), + "action": config.StringVariable("ACTION_DENY"), + "operator_type": config.StringVariable("OPERATOR_VALIDATE_UTF8_ENCODING"), + "operator_value": config.StringVariable("foo"), + "transformation": config.StringVariable("TRANSFORMATION_LOWERCASE"), + "variable_type": config.StringVariable("VARIABLE_RESPONSE_STATUS"), +} + +var testCustomRuleGroupMinUpdated = func() config.Variables { + updatedConfig := config.Variables{} + maps.Copy(updatedConfig, testCustomRuleGroupMin) + updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"]))) + return updatedConfig +} + +var testCustomRuleGroupMax = config.Variables{ + "project_id": config.StringVariable(testutil.ProjectId), + "name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), + "description": config.StringVariable("foo bar"), + "action": config.StringVariable("ACTION_DENY"), + "log": config.BoolVariable(true), + "log_msg": config.StringVariable("foo-bar"), + "operator_type": config.StringVariable("OPERATOR_CONTAINS"), + "operator_value": config.StringVariable("foo"), + "transformation": config.StringVariable("TRANSFORMATION_LOWERCASE"), + "variable_type": config.StringVariable("VARIABLE_REQUEST_HEADERS"), + "variable_value": config.StringVariable("bar"), +} + +var testCustomRuleGroupMaxUpdated = func() config.Variables { + updatedConfig := config.Variables{} + maps.Copy(updatedConfig, testCustomRuleGroupMax) + updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"]))) + // updatedConfig["log"] = config.BoolVariable(false) + return updatedConfig +} + var testManagedRuleSet = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)), @@ -39,6 +84,263 @@ var testManagedRuleSetUpdated = func() config.Variables { return updatedConfig } +func TestAccCustomRuleGroupMin(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Creation + { + ConfigVariables: testCustomRuleGroupMin, + Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMinConfig), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region), + resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMin["name"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", "false"), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["operator_type"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "0"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["variable_type"])), + ), + }, + // Data source + { + ConfigVariables: testCustomRuleGroupMin, + Config: fmt.Sprintf(` + %s + %s + + data "stackit_alb_waf_custom_rule_group" "custom_rule_group" { + project_id = stackit_alb_waf_custom_rule_group.custom_rule_group.project_id + name = stackit_alb_waf_custom_rule_group.custom_rule_group.name + } + `, + testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMinConfig, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "id", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "id", + ), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMin["name"])), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id", + ), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMin["action"])), + // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", "false"), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity", + ), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["operator_type"])), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "0"), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMin["variable_type"])), + ), + }, + // Import + { + ConfigVariables: testCustomRuleGroupMin, + ResourceName: "stackit_alb_waf_custom_rule_group.custom_rule_group", + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources["stackit_alb_waf_custom_rule_group.custom_rule_group"] + if !ok { + return "", fmt.Errorf("couldn't find resource stackit_alb_waf_custom_rule_group.custom_rule_group") + } + policyId, ok := r.Primary.Attributes["name"] + if !ok { + return "", fmt.Errorf("couldn't find attribute name") + } + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, policyId), nil + }, + ImportState: true, + ImportStateVerify: true, + }, + // Update + { + ConfigVariables: testCustomRuleGroupMinUpdated(), + Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMinConfig), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("stackit_alb_waf_custom_rule_group.custom_rule_group", plancheck.ResourceActionReplace), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region), + resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["name"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rule.0.id"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["action"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", "false"), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["operator_type"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "0"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMinUpdated()["variable_type"])), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} + +func TestAccCustomRuleGroupMax(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + CheckDestroy: testAccCheckDestroy, + Steps: []resource.TestStep{ + // Creation + { + ConfigVariables: testCustomRuleGroupMax, + Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMaxConfig), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region), + resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMax["name"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.description", testutil.ConvertConfigVariable(testCustomRuleGroupMax["description"])), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_type"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_value"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "1"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.0", testutil.ConvertConfigVariable(testCustomRuleGroupMax["transformation"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_type"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_value"])), + ), + }, + // Data source + { + ConfigVariables: testCustomRuleGroupMax, + Config: fmt.Sprintf(` + %s + %s + + data "stackit_alb_waf_custom_rule_group" "custom_rule_group" { + project_id = stackit_alb_waf_custom_rule_group.custom_rule_group.project_id + name = stackit_alb_waf_custom_rule_group.custom_rule_group.name + } + `, + testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMaxConfig, + ), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "id", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "id", + ), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMax["name"])), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.description", testutil.ConvertConfigVariable(testCustomRuleGroupMax["description"])), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.id", + ), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMax["action"])), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log"])), + // resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMax["log_msg"])), + resource.TestCheckResourceAttrPair( + "data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity", + "stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity", + ), + + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_type"])), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["operator_value"])), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "1"), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.0", testutil.ConvertConfigVariable(testCustomRuleGroupMax["transformation"])), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_type"])), + resource.TestCheckResourceAttr("data.stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.value", testutil.ConvertConfigVariable(testCustomRuleGroupMax["variable_value"])), + ), + }, + // Import + { + ConfigVariables: testCustomRuleGroupMax, + ResourceName: "stackit_alb_waf_custom_rule_group.custom_rule_group", + ImportStateIdFunc: func(s *terraform.State) (string, error) { + r, ok := s.RootModule().Resources["stackit_alb_waf_custom_rule_group.custom_rule_group"] + if !ok { + return "", fmt.Errorf("couldn't find resource stackit_alb_waf_custom_rule_group.custom_rule_group") + } + policyId, ok := r.Primary.Attributes["name"] + if !ok { + return "", fmt.Errorf("couldn't find attribute name") + } + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, policyId), nil + }, + ImportState: true, + ImportStateVerify: true, + }, + // Update + { + ConfigVariables: testCustomRuleGroupMaxUpdated(), + Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), customRuleGroupMaxConfig), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("stackit_alb_waf_custom_rule_group.custom_rule_group", plancheck.ResourceActionReplace), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "project_id", testutil.ProjectId), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "region", testutil.Region), + resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "id"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "name", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["name"])), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.#", "1"), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rule.0.description", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["description"])), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rule.0.id"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.action", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["action"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log"])), + // resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.log_msg", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["log_msg"])), + // resource.TestCheckResourceAttrSet("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.behavior.severity"), + + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.#", "1"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.type", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["operator_type"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.operator.value", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["operator_value"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.#", "1"), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.transformations.0", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["transformation"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.type", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["variable_type"])), + resource.TestCheckResourceAttr("stackit_alb_waf_custom_rule_group.custom_rule_group", "rules.0.conditions.0.variable.value", testutil.ConvertConfigVariable(testCustomRuleGroupMaxUpdated()["variable_value"])), + ), + }, + // Deletion is done by the framework implicitly + }, + }) +} + func TestAccManagedRuleSet(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, @@ -53,8 +355,7 @@ func TestAccManagedRuleSet(t *testing.T) { resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region), resource.TestCheckResourceAttrSet("stackit_alb_waf_managed_rule_set.managed_rule_set", "id"), resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSet["name"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), + resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])), ), }, // Data source @@ -79,8 +380,7 @@ func TestAccManagedRuleSet(t *testing.T) { "stackit_alb_waf_managed_rule_set.managed_rule_set", "id", ), resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSet["name"])), - - resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), + resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "type", testutil.ConvertConfigVariable(testManagedRuleSet["type"])), ), }, // Import @@ -115,8 +415,7 @@ func TestAccManagedRuleSet(t *testing.T) { resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region), resource.TestCheckResourceAttrSet("stackit_alb_waf_managed_rule_set.managed_rule_set", "id"), resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "name", testutil.ConvertConfigVariable(testManagedRuleSetUpdated()["name"])), - - resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "usage.count", "0"), + resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "type", testutil.ConvertConfigVariable(testManagedRuleSetUpdated()["type"])), ), }, // Deletion is done by the framework implicitly @@ -124,8 +423,8 @@ func TestAccManagedRuleSet(t *testing.T) { }) } -func createClient() (*albwaf.APIClient, error) { - client, err := albwaf.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.AlbWafCustomEndpoint, false)...) +func createClient() (*albWaf.APIClient, error) { + client, err := albWaf.NewAPIClient(testutil.NewConfigBuilder().BuildClientOptions(testutil.AlbWafCustomEndpoint, false)...) if err != nil { return nil, fmt.Errorf("creating client: %w", err) } @@ -135,6 +434,7 @@ func createClient() (*albwaf.APIClient, error) { func testAccCheckDestroy(s *terraform.State) error { checkFunctions := []func(s *terraform.State) error{ + testAlbWafCustomRuleGroupDestroy, testAlbWafManagedRuleSetDestroy, } var errs []error @@ -150,6 +450,39 @@ func testAccCheckDestroy(s *terraform.State) error { return errors.Join(errs...) } +func testAlbWafCustomRuleGroupDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := createClient() + if err != nil { + return err + } + + customRuleGroupsToDestroy := []string{} + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_alb_waf_custom_rule_group" { + continue + } + // custom rule group transform id: "[projectId],[region],[name]" + name := strings.Split(rs.Primary.ID, core.Separator)[2] + customRuleGroupsToDestroy = append(customRuleGroupsToDestroy, name) + } + + resp, err := client.DefaultAPI.ListCustomRuleGroup(ctx, testutil.ProjectId, testutil.Region).Execute() + if err != nil { + return fmt.Errorf("getting resp: %w", err) + } + + for _, item := range resp.Items { + if utils.Contains(customRuleGroupsToDestroy, item.GetName()) { + _, err := client.DefaultAPI.DeleteCustomRuleGroup(ctx, testutil.ProjectId, testutil.Region, item.GetName()).Execute() + if err != nil { + return fmt.Errorf("deleting policy %s during CheckDestroy: %w", item.GetName(), err) + } + } + } + return nil +} + func testAlbWafManagedRuleSetDestroy(s *terraform.State) error { ctx := context.Background() client, err := createClient() diff --git a/stackit/internal/services/albwaf/custom_rule_group/datasource.go b/stackit/internal/services/albwaf/custom_rule_group/datasource.go new file mode 100644 index 000000000..9ff842a37 --- /dev/null +++ b/stackit/internal/services/albwaf/custom_rule_group/datasource.go @@ -0,0 +1,221 @@ +package custom_rule_group + +import ( + "context" + "errors" + "fmt" + "net/http" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ datasource.DataSource = &customRuleGroupDataSource{} + _ datasource.DataSourceWithConfigure = &customRuleGroupDataSource{} +) + +type customRuleGroupDataSource struct { + client *albWaf.APIClient + providerData core.ProviderData +} + +func NewCustomRuleGroupDataSource() datasource.DataSource { + return &customRuleGroupDataSource{} +} + +func (r *customRuleGroupDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + features.CheckBetaResourcesEnabled(ctx, &r.providerData, &resp.Diagnostics, "stackit_alb_waf_custom_rule_group", core.Resource) + if resp.Diagnostics.HasError() { + return + } + + apiClient := utils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + tflog.Info(ctx, "ALB WAF client configured") +} + +func (r *customRuleGroupDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_alb_waf_custom_rule_group" +} + +func (r *customRuleGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Custom Rule Group resource schema. %s", core.ResourceRegionFallbackDocstring), core.Resource), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: descriptions["region"], + Optional: true, + Computed: true, + }, + "name": schema.StringAttribute{ + Description: descriptions["name"], + Required: true, + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^[0-9a-z](?:(?:[0-9a-z]|-){0,61}[0-9a-z])?$`), + "must start and end with an alphanumeric character, may contain hyphens, and be 1-63 characters long", + ), + }, + }, + "rules": schema.ListNestedAttribute{ + Description: descriptions["rules"], + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "behavior": schema.SingleNestedAttribute{ + Description: descriptions["behavior"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Description: descriptions["behavior_action"], + Computed: true, + }, + "log": schema.BoolAttribute{ + Description: descriptions["behavior_log"], + Computed: true, + }, + "log_msg": schema.StringAttribute{ + Description: descriptions["behavior_log_msg"], + Computed: true, + }, + "severity": schema.StringAttribute{ + Description: descriptions["behavior_severity"], + Computed: true, + }, + }, + }, + "conditions": schema.ListNestedAttribute{ + Description: descriptions["rule_conditions"], + Computed: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "operator": schema.SingleNestedAttribute{ + Description: descriptions["operator"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: descriptions["operator_type"], + Computed: true, + }, + "value": schema.StringAttribute{ + Description: descriptions["operator_value"], + Computed: true, + }, + }, + }, + "transformations": schema.ListAttribute{ + Description: descriptions["transformations"], + Computed: true, + ElementType: types.StringType, + }, + "variable": schema.SingleNestedAttribute{ + Description: descriptions["variable"], + Computed: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: descriptions["variable_type"], + Computed: true, + }, + "value": schema.StringAttribute{ + Description: descriptions["variable_value"], + Computed: true, + }, + }, + }, + }, + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["rule_description"], + Computed: true, + }, + "id": schema.Int32Attribute{ + Description: descriptions["rule_id"], + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (r *customRuleGroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.Config.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + name := model.Name.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "name", name) + + customRuleGroupResp, err := r.client.DefaultAPI.GetCustomRuleGroup(ctx, projectId, region, name).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { + core.LogAndAddError(ctx, &resp.Diagnostics, fmt.Sprintf("ALB WAF Custom Rule Group with name %q not found in project %q and region %q", name, projectId, region), err.Error()) + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", err.Error()) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, customRuleGroupResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "ALB WAF Custom Rule Group read") +} diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go new file mode 100644 index 000000000..6c3f18beb --- /dev/null +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -0,0 +1,752 @@ +package custom_rule_group + +import ( + "context" + "errors" + "fmt" + "net/http" + "regexp" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "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" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" + + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" + + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/utils" + tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" +) + +var ( + _ resource.Resource = &customRuleGroupResource{} + _ resource.ResourceWithConfigure = &customRuleGroupResource{} + _ resource.ResourceWithImportState = &customRuleGroupResource{} + _ resource.ResourceWithModifyPlan = &customRuleGroupResource{} + + variableTypeOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedConditionVariableTypeEnumValues) + transformationOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedConditionTransformationsInnerEnumValues) + operatorTypeOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedConditionOperatorTypeEnumValues) + actionOptions = sdkUtils.EnumSliceToStringSlice(albWaf.AllowedBehaviourActionEnumValues) +) + +type Model struct { + Id types.String `tfsdk:"id"` // needed by TF + ProjectId types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + Name types.String `tfsdk:"name"` + Rules types.List `tfsdk:"rules"` +} + +type RuleModel struct { + Behavior types.Object `tfsdk:"behavior"` + Conditions types.List `tfsdk:"conditions"` + Description types.String `tfsdk:"description"` + Id types.Int32 `tfsdk:"id"` +} + +var ruleType = map[string]attr.Type{ + "behavior": types.ObjectType{AttrTypes: behaviorType}, + "conditions": types.ListType{ + ElemType: types.ObjectType{AttrTypes: conditionType}, + }, + "description": types.StringType, + "id": types.Int32Type, +} + +type BehaviorModel struct { + Action types.String `tfsdk:"action"` + Log types.Bool `tfsdk:"log"` + LogMsg types.String `tfsdk:"log_msg"` + Severity types.String `tfsdk:"severity"` +} + +var behaviorType = map[string]attr.Type{ + "action": types.StringType, + "log": types.BoolType, + "log_msg": types.StringType, + "severity": types.StringType, +} + +type ConditionModel struct { + Operator types.Object `tfsdk:"operator"` + Transformations types.List `tfsdk:"transformations"` + Variable types.Object `tfsdk:"variable"` +} + +var conditionType = map[string]attr.Type{ + "operator": types.ObjectType{AttrTypes: operatorType}, + "transformations": types.ListType{ElemType: types.StringType}, + "variable": types.ObjectType{AttrTypes: variableType}, +} + +type OperatorModel struct { + Type types.String `tfsdk:"type"` + Value types.String `tfsdk:"value"` +} + +var operatorType = map[string]attr.Type{ + "type": types.StringType, + "value": types.StringType, +} + +type VariableModel struct { + Type types.String `tfsdk:"type"` + Value types.String `tfsdk:"value"` +} + +var variableType = map[string]attr.Type{ + "type": types.StringType, + "value": types.StringType, +} + +type customRuleGroupResource struct { + client *albWaf.APIClient + providerData core.ProviderData +} + +func NewCustomRuleGroupResource() resource.Resource { + return &customRuleGroupResource{} +} + +func (r *customRuleGroupResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + var ok bool + r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) + if !ok { + return + } + + features.CheckBetaResourcesEnabled(ctx, &r.providerData, &resp.Diagnostics, "stackit_alb_waf_custom_rule_group", core.Resource) + if resp.Diagnostics.HasError() { + return + } + + apiClient := utils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + r.client = apiClient + tflog.Info(ctx, "ALB WAF client configured") +} + +func (r *customRuleGroupResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_alb_waf_custom_rule_group" +} + +// descriptions for the attributes in the Schema. +var descriptions = map[string]string{ + "id": "Terraform's internal resource identifier. Structured as \"`project_id`,`region`,`name`\".", + "project_id": "STACKIT project ID associated with the ALB WAF Custom Rule Group.", + "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.", + "name": "Custom rule group configuration name.", + "rules": "Enriched rules containing auto-generated IDs and computed severity values.", + "rule_behavior": "Behavior of the rule.", + "rule_condition": "Conditions for this rule (order matters, first condition match triggers execution).", + "rule_description": "A clear description explaining the threat vector or criteria addressed by this rule.", + "rule_id": "Backend auto-allocated unique rule ID within the valid 1-99999 threshold.", + "behavior_action": "The protective stance action. ACTION_DENY forces a 403 status response code.", + "behavior_log": "Determines whether an entry should be generated in the security ledger upon a rule hit.", + "behavior_log_msg": "Custom notification message string mapped to underlying logdata contexts. Required if log is true.", + "behavior_severity": "Severity classification metric used by internal analytics graphs.", + "operator": "The comparison logic executed against the transformed variable.", + "operator_type": "The operational evaluation type definition macro.", + "operator_value": "The text or rule regex pattern arguments applied inside the operator execution loop.", + "transformations": "Ordered normalization steps applied before the operator runs.", + "variable": "The part of the HTTP transaction to inspect.", + "variable_type": "The targeted validation engine variable macro.", + "variable_value": "Optional key element context for map variables (e.g., matching a 'Host' header key).", +} + +func (r *customRuleGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Custom Rule Group resource schema. %s", core.ResourceRegionFallbackDocstring), core.Resource), + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: descriptions["id"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "project_id": schema.StringAttribute{ + Description: descriptions["project_id"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + validate.UUID(), + validate.NoSeparator(), + }, + }, + "region": schema.StringAttribute{ + Description: descriptions["region"], + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Description: descriptions["name"], + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.RegexMatches( + regexp.MustCompile(`^[0-9a-z](?:(?:[0-9a-z]|-){0,61}[0-9a-z])?$`), + "must start and end with an alphanumeric character, may contain hyphens, and be 1-63 characters long", + ), + }, + }, + "rules": schema.ListNestedAttribute{ + Description: descriptions["rules"], + Required: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplace(), + }, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "behavior": schema.SingleNestedAttribute{ + Description: descriptions["behavior"], + Required: true, + Attributes: map[string]schema.Attribute{ + "action": schema.StringAttribute{ + Description: descriptions["behavior_action"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(actionOptions...), + }, + }, + "log": schema.BoolAttribute{ + Description: descriptions["behavior_log"], + Optional: true, + }, + "log_msg": schema.StringAttribute{ + Description: descriptions["behavior_log_msg"], + Optional: true, + }, + "severity": schema.StringAttribute{ + Description: descriptions["behavior_severity"], + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + "conditions": schema.ListNestedAttribute{ + Description: descriptions["rule_conditions"], + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "operator": schema.SingleNestedAttribute{ + Description: descriptions["operator"], + Required: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: descriptions["operator_type"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(operatorTypeOptions...), + }, + }, + "value": schema.StringAttribute{ + Description: descriptions["operator_value"], + Optional: true, + }, + }, + }, + "transformations": schema.ListAttribute{ + Description: descriptions["transformations"], + Optional: true, + ElementType: types.StringType, + Validators: []validator.List{ + listvalidator.ValueStringsAre( + stringvalidator.OneOf(transformationOptions...), + ), + }, + }, + "variable": schema.SingleNestedAttribute{ + Description: descriptions["variable"], + Required: true, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Description: descriptions["variable_type"], + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(variableTypeOptions...), + }, + }, + "value": schema.StringAttribute{ + Description: descriptions["variable_value"], + Optional: true, + }, + }, + }, + }, + }, + }, + "description": schema.StringAttribute{ + Description: descriptions["rule_description"], + Optional: true, + }, + "id": schema.Int32Attribute{ + Description: descriptions["rule_id"], + Computed: true, + PlanModifiers: []planmodifier.Int32{ + int32planmodifier.UseStateForUnknown(), + }, + }, + }, + }, + }, + }, + } +} + +func (r *customRuleGroupResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var configModel Model + if req.Config.Raw.IsNull() { + return + } + resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) + if resp.Diagnostics.HasError() { + return + } + + var planModel Model + resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) + if resp.Diagnostics.HasError() { + return + } + + tfutils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *customRuleGroupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idParts := strings.Split(req.ID, core.Separator) + + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + core.LogAndAddError(ctx, &resp.Diagnostics, + "Error importing ALB WAF Custom Rule Group", + fmt.Sprintf("Expected import identifier with format: [project_id],[region],[name] Got: %q", req.ID), + ) + return + } + + ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": idParts[0], + "region": idParts[1], + "name": idParts[2], + }) + tflog.Info(ctx, "ALB WAF Custom Rule Group state imported") +} + +func (r *customRuleGroupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "name", model.Name) + + payload, err := toCreatePayload(ctx, &model) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", fmt.Sprintf("Creating API payload: %v", err)) + return + } + + createResp, err := r.client.DefaultAPI.CreateCustomRuleGroup(ctx, projectId, region).CreateCustomRuleGroupPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + if createResp.Name == nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", "Got empty Custom Rule Group name") + return + } + customRuleGroupName := *createResp.Name + + ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{ + "project_id": projectId, + "region": region, + "name": customRuleGroupName, + }) + if resp.Diagnostics.HasError() { + return + } + + err = mapFields(ctx, createResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Custom Rule Group", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "ALB WAF Custom Rule Group created") +} + +func (r *customRuleGroupResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + core.LogAndAddError(ctx, &resp.Diagnostics, "Ressource not updatable", "ALB WAF Custom Rule Group is not updatable") +} + +func (r *customRuleGroupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + name := model.Name.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "name", name) + + customRuleGroupResp, err := r.client.DefaultAPI.GetCustomRuleGroup(ctx, projectId, region, name).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", err.Error()) + return + } + + ctx = core.LogResponse(ctx) + + err = mapFields(ctx, customRuleGroupResp, &model, region) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Custom Rule Group", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + diags = resp.State.Set(ctx, model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "ALB WAF Custom Rule Group read") +} + +func (r *customRuleGroupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform + var model Model + diags := req.State.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + ctx = core.InitProviderContext(ctx) + + projectId := model.ProjectId.ValueString() + name := model.Name.ValueString() + region := r.providerData.GetRegionWithOverride(model.Region) + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "region", region) + ctx = tflog.SetField(ctx, "name", name) + + _, err := r.client.DefaultAPI.DeleteCustomRuleGroup(ctx, projectId, region, name).Execute() + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound { + tflog.Info(ctx, "ALB WAF Custom Rule Group was already deleted") + return + } + core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting ALB WAF Custom Rule Group", fmt.Sprintf("Calling API: %v", err)) + return + } + + ctx = core.LogResponse(ctx) + + tflog.Info(ctx, "ALB WAF Custom Rule Group deleted") +} + +func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateCustomRuleGroupPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + + payloadRules := []albWaf.CreateCustomRule{} + if !tfutils.IsUndefined(model.Rules) { + rules := []RuleModel{} + diags := model.Rules.ElementsAs(ctx, &rules, true) + if diags.HasError() { + return nil, fmt.Errorf("converting to rule map: %v", diags.Errors()) + } + + for _, rule := range rules { + behavior := BehaviorModel{} + if !tfutils.IsUndefined(rule.Behavior) { + diags := rule.Behavior.As(ctx, &behavior, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting to rule behavior: %v", diags.Errors()) + } + } + + conditions, err := toConditionsPayload(ctx, rule.Conditions) + if err != nil || conditions == nil { + return nil, fmt.Errorf("converting conditions: %w", err) + } + + payloadRules = append(payloadRules, albWaf.CreateCustomRule{ + Behaviour: albWaf.Behaviour{ // nolint:misspell // Generated from API spec + Action: albWaf.BehaviourAction(behavior.Action.ValueString()), + Log: behavior.Log.ValueBoolPointer(), + LogMsg: behavior.LogMsg.ValueStringPointer(), + }, + Conditions: *conditions, + Description: rule.Description.ValueStringPointer(), + }) + } + } + + payload := &albWaf.CreateCustomRuleGroupPayload{ + Name: model.Name.ValueString(), + Rules: payloadRules, + } + + return payload, nil +} + +func toConditionsPayload(ctx context.Context, conditions basetypes.ListValue) (*[]albWaf.Condition, error) { + result := []albWaf.Condition{} + + if !tfutils.IsUndefined(conditions) { + conditionModels := []ConditionModel{} + diags := conditions.ElementsAs(ctx, &conditionModels, true) + if diags.HasError() { + return nil, fmt.Errorf("converting to rule map: %v", diags.Errors()) + } + + for _, condition := range conditionModels { + transformations := []albWaf.ConditionTransformationsInner{} + if !tfutils.IsUndefined(condition.Transformations) { + diags := condition.Transformations.ElementsAs(ctx, &transformations, true) + if diags.HasError() { + return nil, fmt.Errorf("converting transformations: %v", diags.Errors()) + } + } + + var operatorModel = OperatorModel{} + diags = condition.Operator.As(ctx, &operatorModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting operator: %v", diags.Errors()) + } + + var variableModel = VariableModel{} + diags = condition.Variable.As(ctx, &variableModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting variable: %v", diags.Errors()) + } + + result = append(result, albWaf.Condition{ + Operator: albWaf.ConditionOperator{ + Type: albWaf.ConditionOperatorType(operatorModel.Type.ValueString()), + Value: operatorModel.Value.ValueStringPointer(), + }, + Transformations: transformations, + Variable: albWaf.ConditionVariable{ + Type: albWaf.ConditionVariableType(variableModel.Type.ValueString()), + Value: variableModel.Value.ValueStringPointer(), + }, + }) + } + } + + return &result, nil +} + +func mapFields(ctx context.Context, customRuleGroup *albWaf.GetCustomRuleGroupResponse, model *Model, region string) error { + if customRuleGroup == nil { + return fmt.Errorf("response input is nil") + } + if model == nil { + return fmt.Errorf("model input is nil") + } + + model.Id = tfutils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.Name.ValueString()) + model.Name = types.StringValue(model.Name.ValueString()) + model.Region = types.StringValue(region) + + rules, err := mapRules(ctx, &customRuleGroup.Rules) + if err != nil || rules == nil { + return fmt.Errorf("map rules: %w", err) + } + model.Rules = *rules + + return nil +} + +func mapRules(ctx context.Context, rules *[]albWaf.GetCustomRule) (*basetypes.ListValue, error) { + var diags diag.Diagnostics + var result basetypes.ListValue + + if rules != nil { + rulesList := []attr.Value{} + for _, rule := range *rules { + ruleTF := RuleModel{ + Id: types.Int32PointerValue(rule.Id), + Description: types.StringPointerValue(rule.Description), + } + + behavior, err := mapBehavior(ctx, rule.Behaviour) // nolint:misspell // Generated from API spec + if err != nil || behavior == nil { + return nil, fmt.Errorf("map behavior: %w", err) + } + ruleTF.Behavior = *behavior + + conditions, err := mapConditions(ctx, rule) + if err != nil || conditions == nil { + return nil, fmt.Errorf("map conditions: %w", err) + } + ruleTF.Conditions = *conditions + + rule, diags := types.ObjectValueFrom(ctx, ruleType, ruleTF) + if diags.HasError() { + return nil, fmt.Errorf("mapping rule: %w", core.DiagsToError(diags)) + } + rulesList = append(rulesList, rule) + } + result, diags = types.ListValue(types.ObjectType{AttrTypes: ruleType}, rulesList) + if diags.HasError() { + return nil, fmt.Errorf("creating rule object: %w", core.DiagsToError(diags)) + } + } else { + result = types.ListNull(types.ObjectType{AttrTypes: ruleType}) + } + + return &result, nil +} + +func mapBehavior(ctx context.Context, behavior *albWaf.GetBehaviour) (*basetypes.ObjectValue, error) { + var diags diag.Diagnostics + var result basetypes.ObjectValue + + if behavior != nil { + behaviorModel := BehaviorModel{ + Action: types.StringPointerValue((*string)(behavior.Action)), + Log: types.BoolPointerValue(behavior.Log), + LogMsg: types.StringPointerValue(behavior.LogMsg), + Severity: types.StringPointerValue((*string)(behavior.Severity)), + } + + result, diags = types.ObjectValueFrom(ctx, behaviorType, behaviorModel) + if diags.HasError() { + return nil, fmt.Errorf("creating behavior object: %w", core.DiagsToError(diags)) + } + } else { + result = types.ObjectNull(behaviorType) + } + + return &result, nil +} + +func mapConditions(ctx context.Context, rule albWaf.GetCustomRule) (*basetypes.ListValue, error) { + var diags diag.Diagnostics + var result basetypes.ListValue + + if conditions, ok := rule.GetConditionsOk(); ok { + conditionsList := []attr.Value{} + for _, condition := range conditions { + conditionTF := ConditionModel{} + + if operator, ok := condition.GetOperatorOk(); ok { + operatorModel := OperatorModel{ + Type: types.StringValue(string(operator.Type)), + Value: types.StringPointerValue(operator.Value), + } + + conditionTF.Operator, diags = types.ObjectValueFrom(ctx, operatorType, operatorModel) + if diags.HasError() { + return nil, fmt.Errorf("creating operator object: %w", core.DiagsToError(diags)) + } + } else { + conditionTF.Operator = types.ObjectNull(operatorType) + } + + conditionTF.Transformations, diags = types.ListValueFrom(ctx, types.StringType, condition.Transformations) + if diags.HasError() { + return nil, fmt.Errorf("mapping transformations: %w", core.DiagsToError(diags)) + } + + if variable, ok := condition.GetVariableOk(); ok { + variableModel := VariableModel{ + Type: types.StringValue(string(variable.Type)), + Value: types.StringPointerValue(variable.Value), + } + + conditionTF.Variable, diags = types.ObjectValueFrom(ctx, variableType, variableModel) + if diags.HasError() { + return nil, fmt.Errorf("creating variable object: %w", core.DiagsToError(diags)) + } + } else { + conditionTF.Variable = types.ObjectNull(variableType) + } + + condition, diags := types.ObjectValueFrom(ctx, conditionType, conditionTF) + if diags.HasError() { + return nil, fmt.Errorf("mapping condition: %w", core.DiagsToError(diags)) + } + conditionsList = append(conditionsList, condition) + } + result, diags = types.ListValue(types.ObjectType{AttrTypes: conditionType}, conditionsList) + if diags.HasError() { + return nil, fmt.Errorf("mapping conditions: %w", core.DiagsToError(diags)) + } + } else { + result = types.ListNull(types.ObjectType{AttrTypes: conditionType}) + } + + return &result, nil +} diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource_test.go b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go new file mode 100644 index 000000000..cb691f437 --- /dev/null +++ b/stackit/internal/services/albwaf/custom_rule_group/resource_test.go @@ -0,0 +1,328 @@ +package custom_rule_group + +import ( + "context" + _ "embed" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" +) + +var ( + testProjectId = types.StringValue(uuid.NewString()) + testRegion = types.StringValue("eu01") + testName = types.StringValue("test-custom-rule-group") + testId = types.StringValue(testProjectId.ValueString() + "," + testRegion.ValueString() + "," + testName.ValueString()) +) + +func TestToCreatePayload(t *testing.T) { + tests := []struct { + name string + model *Model + expected *albWaf.CreateCustomRuleGroupPayload + isValid bool + }{ + { + name: "default", + model: &Model{ + Name: testName, + Id: testId, + ProjectId: testProjectId, + Region: testRegion, + Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ + types.ObjectValueMust(ruleType, map[string]attr.Value{ + "behavior": types.ObjectValueMust(behaviorType, map[string]attr.Value{ + "action": types.StringValue("some-action"), + "log": types.BoolValue(true), + "log_msg": types.StringValue("Log: something happened"), + "severity": types.StringNull(), + }), + "conditions": types.ListValueMust(types.ObjectType{AttrTypes: conditionType}, []attr.Value{ + types.ObjectValueMust(conditionType, map[string]attr.Value{ + "operator": types.ObjectValueMust(operatorType, map[string]attr.Value{ + "type": types.StringValue("operator-type"), + "value": types.StringValue("operator-value"), + }), + "transformations": types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("foo"), + types.StringValue("bar"), + }), + "variable": types.ObjectValueMust(variableType, map[string]attr.Value{ + "type": types.StringValue("variable-type"), + "value": types.StringValue("variable-value"), + }), + }), + }), + "description": types.StringValue("foo-bar"), + "id": types.Int32Null(), + }), + }), + }, + expected: &albWaf.CreateCustomRuleGroupPayload{ + Name: testName.ValueString(), + Rules: []albWaf.CreateCustomRule{ + { + Behaviour: albWaf.Behaviour{ // nolint:misspell // Generated from API spec + Action: albWaf.BehaviourAction("some-action"), + Log: new(true), + LogMsg: new("Log: something happened"), + }, + Conditions: []albWaf.Condition{ + { + Operator: albWaf.ConditionOperator{ + Type: albWaf.ConditionOperatorType("operator-type"), + Value: new("operator-value"), + }, + Transformations: []albWaf.ConditionTransformationsInner{ + "foo", + "bar", + }, + Variable: albWaf.ConditionVariable{ + Type: albWaf.ConditionVariableType("variable-type"), + Value: new("variable-value"), + }, + }, + }, + Description: new("foo-bar"), + }, + }, + }, + isValid: true, + }, + { + name: "null values", + model: &Model{ + Name: testName, + Id: testId, + ProjectId: testProjectId, + Region: testRegion, + Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ + types.ObjectValueMust(ruleType, map[string]attr.Value{ + "behavior": types.ObjectValueMust(behaviorType, map[string]attr.Value{ + "action": types.StringNull(), + "log": types.BoolNull(), + "log_msg": types.StringNull(), + "severity": types.StringNull(), + }), + "conditions": types.ListValueMust(types.ObjectType{AttrTypes: conditionType}, []attr.Value{ + types.ObjectValueMust(conditionType, map[string]attr.Value{ + "operator": types.ObjectValueMust(operatorType, map[string]attr.Value{ + "type": types.StringNull(), + "value": types.StringNull(), + }), + "transformations": types.ListValueMust(types.StringType, []attr.Value{}), + "variable": types.ObjectValueMust(variableType, map[string]attr.Value{ + "type": types.StringNull(), + "value": types.StringNull(), + }), + }), + }), + "description": types.StringNull(), + "id": types.Int32Null(), + }), + }), + }, + expected: &albWaf.CreateCustomRuleGroupPayload{ + Name: testName.ValueString(), + Rules: []albWaf.CreateCustomRule{ + { + Behaviour: albWaf.Behaviour{}, // nolint:misspell // Generated from API spec + Conditions: []albWaf.Condition{ + { + Operator: albWaf.ConditionOperator{}, + Transformations: []albWaf.ConditionTransformationsInner{}, + Variable: albWaf.ConditionVariable{}, + }, + }, + }, + }, + }, + isValid: true, + }, + { + name: "no rules", + model: &Model{ + Name: testName, + Id: testId, + ProjectId: testProjectId, + Region: testRegion, + }, + expected: &albWaf.CreateCustomRuleGroupPayload{ + Name: testName.ValueString(), + Rules: []albWaf.CreateCustomRule{}, + }, + isValid: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := toCreatePayload(context.Background(), tt.model) + if (err != nil) == tt.isValid { + t.Errorf("toCreatePayload() error = %v, isValid %v", err, tt.isValid) + return + } + + if tt.isValid { + if diff := cmp.Diff(got, tt.expected); diff != "" { + t.Errorf("Data does not match: %s", diff) + } + } + }) + } +} + +func TestMapFields(t *testing.T) { + tests := []struct { + name string + state *Model + region string + input *albWaf.GetCustomRuleGroupResponse + expected *Model + isValid bool + }{ + { + name: "default", + state: &Model{ + ProjectId: testProjectId, + Region: testRegion, + Name: testName, + Id: testId, + Rules: types.ListNull(types.ObjectType{AttrTypes: ruleType}), + }, + region: testRegion.ValueString(), + input: &albWaf.GetCustomRuleGroupResponse{ + Name: testName.ValueStringPointer(), + Rules: []albWaf.GetCustomRule{ + { + Behaviour: &albWaf.GetBehaviour{ // nolint:misspell // Generated from API spec + Action: new(albWaf.GetBehaviourAction("some-action")), + Log: new(true), + LogMsg: new("Log: something happened"), + Severity: new(albWaf.GetBehaviourSeverity("critical")), + }, + Conditions: []albWaf.Condition{ + { + Operator: albWaf.ConditionOperator{ + Type: albWaf.ConditionOperatorType("operator-type"), + Value: new("operator-value"), + }, + Transformations: []albWaf.ConditionTransformationsInner{ + "foo", + "bar", + }, + Variable: albWaf.ConditionVariable{ + Type: albWaf.ConditionVariableType("variable-type"), + Value: new("variable-value"), + }, + }, + }, + Description: new("foo-bar"), + Id: new(int32(42)), + }, + }, + }, + expected: &Model{ + ProjectId: testProjectId, + Region: testRegion, + Name: testName, + Id: testId, + Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ + types.ObjectValueMust(ruleType, map[string]attr.Value{ + "behavior": types.ObjectValueMust(behaviorType, map[string]attr.Value{ + "action": types.StringValue("some-action"), + "log": types.BoolValue(true), + "log_msg": types.StringValue("Log: something happened"), + "severity": types.StringValue("critical"), + }), + "conditions": types.ListValueMust(types.ObjectType{AttrTypes: conditionType}, []attr.Value{ + types.ObjectValueMust(conditionType, map[string]attr.Value{ + "operator": types.ObjectValueMust(operatorType, map[string]attr.Value{ + "type": types.StringValue("operator-type"), + "value": types.StringValue("operator-value"), + }), + "transformations": types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("foo"), + types.StringValue("bar"), + }), + "variable": types.ObjectValueMust(variableType, map[string]attr.Value{ + "type": types.StringValue("variable-type"), + "value": types.StringValue("variable-value"), + }), + }), + }), + "description": types.StringValue("foo-bar"), + "id": types.Int32Value(42), + }), + }), + }, + isValid: true, + }, + { + name: "empty rule", + state: &Model{ + ProjectId: testProjectId, + Region: testRegion, + Name: testName, + Id: testId, + Rules: types.ListNull(types.ObjectType{AttrTypes: ruleType}), + }, + region: testRegion.ValueString(), + input: &albWaf.GetCustomRuleGroupResponse{ + Rules: []albWaf.GetCustomRule{ + {}, + }, + }, + expected: &Model{ + Name: testName, + Id: testId, + ProjectId: testProjectId, + Region: testRegion, + Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{ + types.ObjectValueMust(ruleType, map[string]attr.Value{ + "behavior": types.ObjectNull(behaviorType), + "conditions": types.ListNull(types.ObjectType{AttrTypes: conditionType}), + "description": types.StringNull(), + "id": types.Int32Null(), + }), + }), + }, + isValid: true, + }, + { + name: "no rules", + state: &Model{ + ProjectId: testProjectId, + Region: testRegion, + Name: testName, + Id: testId, + }, + region: testRegion.ValueString(), + input: &albWaf.GetCustomRuleGroupResponse{}, + expected: &Model{ + Name: testName, + Id: testId, + ProjectId: testProjectId, + Region: testRegion, + Rules: types.ListValueMust(types.ObjectType{AttrTypes: ruleType}, []attr.Value{}), + }, + isValid: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if err := mapFields(ctx, tt.input, tt.state, tt.region); (err == nil) != tt.isValid { + t.Errorf("unexpected error") + } + if tt.isValid { + if diff := cmp.Diff(tt.state, tt.expected); diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +} diff --git a/stackit/internal/services/albwaf/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go index b802907a4..f53205ae2 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/datasource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go @@ -11,7 +11,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1betaapi" @@ -100,21 +99,6 @@ func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.Schema Description: descriptions["version"], Computed: true, }, - "usage": schema.SingleNestedAttribute{ - Description: descriptions["usage"], - Computed: true, - Attributes: map[string]schema.Attribute{ - "count": schema.Int32Attribute{ - Description: descriptions["usage_count"], - Computed: true, - }, - "items": schema.ListAttribute{ - Description: descriptions["usage_items"], - Computed: true, - ElementType: types.StringType, - }, - }, - }, "groups": schema.MapNestedAttribute{ Description: descriptions["groups"], Computed: true, diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index 0409ec6e8..329e4192c 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -43,7 +43,6 @@ type Model struct { Name types.String `tfsdk:"name"` Groups types.Map `tfsdk:"groups"` Type types.String `tfsdk:"type"` - Usage types.Object `tfsdk:"usage"` Version types.String `tfsdk:"version"` } @@ -73,16 +72,6 @@ var ruleType = map[string]attr.Type{ "severity": types.StringType, } -type UsageModel struct { - Count types.Int32 `tfsdk:"count"` - Items types.List `tfsdk:"items"` -} - -var usageType = map[string]attr.Type{ - "count": types.Int32Type, - "items": types.ListType{ElemType: types.StringType}, -} - type managedRuleSetResource struct { client *albWaf.APIClient providerData core.ProviderData @@ -124,9 +113,6 @@ var descriptions = map[string]string{ "name": "Managed Rule Set configuration name.", "type": "Type of the Managed Rule Set.", "version": "Managed Rule Set version.", - "usage": "Managed Rule Set usage", - "usage_count": "Number of WAFs using this Managed Rule Set.", - "usage_items": "List of WAFs that use this Managed Rule Set.", "groups": "Inventory of all available Managed Rule Set groups and their current configuration.", "group_description": "A description of what this group covers.", "group_name": "The name for the rule group.", @@ -190,21 +176,6 @@ func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequ Description: descriptions["version"], Computed: true, }, - "usage": schema.SingleNestedAttribute{ - Description: descriptions["usage"], - Computed: true, - Attributes: map[string]schema.Attribute{ - "count": schema.Int32Attribute{ - Description: descriptions["usage_count"], - Computed: true, - }, - "items": schema.ListAttribute{ - Description: descriptions["usage_items"], - Computed: true, - ElementType: types.StringType, - }, - }, - }, "groups": schema.MapNestedAttribute{ Description: descriptions["groups"], Computed: true, @@ -432,8 +403,8 @@ func toCreatePayload(_ context.Context, model *Model) (*albWaf.CreateManagedRule } payload := &albWaf.CreateManagedRuleSetPayload{ - Name: model.Name.ValueStringPointer(), - Type: new(albWaf.MRSType(model.Type.ValueString())), + Name: model.Name.ValueString(), + Type: albWaf.MRSType(model.Type.ValueString()), } return payload, nil @@ -475,13 +446,13 @@ func mapFields(ctx context.Context, managedRuleSet *albWaf.GetManagedRuleSetResp ruleMap[ruleKey], diags = types.ObjectValueFrom(ctx, ruleType, ruleTF) if diags.HasError() { - return fmt.Errorf("mapping role: %w", core.DiagsToError(diags)) + return fmt.Errorf("mapping rule: %w", core.DiagsToError(diags)) } } } groupTF.Rules, diags = types.MapValue(types.ObjectType{AttrTypes: ruleType}, ruleMap) if diags.HasError() { - return fmt.Errorf("mapping roles: %w", core.DiagsToError(diags)) + return fmt.Errorf("mapping rules: %w", core.DiagsToError(diags)) } groupsMap[groupKey], diags = types.ObjectValueFrom(ctx, ruleGroupType, groupTF) @@ -498,23 +469,5 @@ func mapFields(ctx context.Context, managedRuleSet *albWaf.GetManagedRuleSetResp return fmt.Errorf("mapping groups: %w", core.DiagsToError(diags)) } - if usage, ok := managedRuleSet.GetUsageOk(); ok { - usageModel := UsageModel{ - Count: types.Int32PointerValue(usage.Count), - } - - usageModel.Items, diags = types.ListValueFrom(ctx, types.StringType, usage.GetItems()) - if diags.HasError() { - return fmt.Errorf("creating usage object: %w", core.DiagsToError(diags)) - } - - model.Usage, diags = types.ObjectValueFrom(ctx, usageType, usageModel) - if diags.HasError() { - return fmt.Errorf("creating usage object: %w", core.DiagsToError(diags)) - } - } else { - model.Usage = types.ObjectNull(usageType) - } - return nil } diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource_test.go b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go index 9b7bc9548..0158f48f6 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource_test.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go @@ -36,8 +36,8 @@ func TestToCreatePayload(t *testing.T) { Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)), }, expected: &albWaf.CreateManagedRuleSetPayload{ - Name: testName.ValueStringPointer(), - Type: new(albWaf.MRSTYPE_TYPE_OWASP_CRS), + Name: testName.ValueString(), + Type: albWaf.MRSTYPE_TYPE_OWASP_CRS, }, isValid: true, }, diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf new file mode 100644 index 000000000..74495fb3d --- /dev/null +++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-max.tf @@ -0,0 +1,42 @@ + +variable "project_id" {} +variable "name" {} +variable "description" {} +variable "action" {} +variable "log" {} +variable "log_msg" {} +variable "operator_type" {} +variable "operator_value" {} +variable "transformation" {} +variable "variable_type" {} +variable "variable_value" {} + +resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" { + project_id = var.project_id + name = var.name + rules = [ + { + description = var.description + behavior = { + action = var.action + log = var.log + logMsg = var.log_msg + } + conditions = [ + { + operator = { + type = var.operator_type + value = var.operator_value + } + transformations = [ + var.transformation + ] + variable = { + type = var.variable_type + value = var.variable_value + } + } + ] + } + ] +} diff --git a/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf new file mode 100644 index 000000000..cf1c92e6b --- /dev/null +++ b/stackit/internal/services/albwaf/testdata/custom-rule-group-min.tf @@ -0,0 +1,29 @@ + +variable "project_id" {} +variable "name" {} +variable "action" {} +variable "operator_type" {} +variable "variable_type" {} + +resource "stackit_alb_waf_custom_rule_group" "custom_rule_group" { + project_id = var.project_id + name = var.name + rules = [ + { + behavior = { + action = var.action + } + conditions = [ + { + operator = { + type = var.operator_type + value = "dummy" + } + variable = { + type = var.variable_type + } + } + ] + } + ] +} diff --git a/stackit/provider.go b/stackit/provider.go index f99c5eb3c..7116bd89d 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -24,6 +24,7 @@ import ( "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/access_token" alb "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/alb/applicationloadbalancer" cert "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albcertificates/certificate" + albWafCustomRuleGroup "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/custom_rule_group" albWafManagedRuleSet "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/albwaf/managed_rule_set" customRole "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/customrole" roleAssignements "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/authorization/roleassignments" @@ -680,6 +681,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource { dataSources := []func() datasource.DataSource{ alb.NewApplicationLoadBalancerDataSource, + albWafCustomRuleGroup.NewCustomRuleGroupDataSource, albWafManagedRuleSet.NewManagedRuleSetDataSource, alertGroup.NewAlertGroupDataSource, cdn.NewDistributionDataSource, @@ -793,6 +795,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource func (p *Provider) Resources(_ context.Context) []func() resource.Resource { resources := []func() resource.Resource{ alb.NewApplicationLoadBalancerResource, + albWafCustomRuleGroup.NewCustomRuleGroupResource, albWafManagedRuleSet.NewManagedRuleSetResource, alertGroup.NewAlertGroupResource, cdn.NewDistributionResource,