diff --git a/docs/data-sources/alb_waf_managed_rule_set.md b/docs/data-sources/alb_waf_managed_rule_set.md
new file mode 100644
index 000000000..1f335dabf
--- /dev/null
+++ b/docs/data-sources/alb_waf_managed_rule_set.md
@@ -0,0 +1,71 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_alb_waf_managed_rule_set Data Source - stackit"
+subcategory: ""
+description: |-
+ ALB WAF Managed Rule Set DataSource schema. Uses the default_region specified in the provider configuration as a fallback in case no region is defined on datasource level.
+ ~> This datasource 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_managed_rule_set (Data Source)
+
+ALB WAF Managed Rule Set DataSource schema. Uses the `default_region` specified in the provider configuration as a fallback in case no `region` is defined on datasource level.
+
+~> This datasource 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_managed_rule_set" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-managed-rule-set"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) Managed Rule Set configuration name.
+- `project_id` (String) STACKIT project ID associated with the ALB WAF Managed Rule Set.
+
+### Optional
+
+- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used.
+
+### Read-Only
+
+- `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.
+
+
+### Nested Schema for `groups`
+
+Read-Only:
+
+- `description` (String) A description of what this group covers.
+- `group_name` (String) The name for the rule group.
+- `rules` (Attributes Map) Rules of the rule group. (see [below for nested schema](#nestedatt--groups--rules))
+
+
+### Nested Schema for `groups.rules`
+
+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/index.md b/docs/index.md
index 45110c0c4..c3ceba33e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -173,6 +173,7 @@ See this [example](https://professional-service.git.onstackit.cloud/professional
- `alb_certificates_custom_endpoint` (String) Custom endpoint for the Application Load Balancer TLS Certificate service
- `alb_custom_endpoint` (String) Custom endpoint for the Application Load Balancer service
+- `alb_waf_custom_endpoint` (String) Custom endpoint for the Application Load Balancer Web Application Firewall service
- `authorization_custom_endpoint` (String) Custom endpoint for the Membership service
- `cdn_custom_endpoint` (String) Custom endpoint for the CDN service
- `credentials_path` (String) Path of JSON from where the credentials are read. Takes precedence over the env var `STACKIT_CREDENTIALS_PATH`. Default value is `~/.stackit/credentials.json`.
diff --git a/docs/resources/alb_waf_managed_rule_set.md b/docs/resources/alb_waf_managed_rule_set.md
new file mode 100644
index 000000000..eeb2c93f3
--- /dev/null
+++ b/docs/resources/alb_waf_managed_rule_set.md
@@ -0,0 +1,72 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "stackit_alb_waf_managed_rule_set Resource - stackit"
+subcategory: ""
+description: |-
+ ALB WAF Managed Rule Set 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_managed_rule_set (Resource)
+
+ALB WAF Managed Rule Set 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_managed_rule_set" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-managed-rule-set"
+ type = "TYPE_OWASP_CRS"
+}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) Managed Rule Set configuration name.
+- `project_id` (String) STACKIT project ID associated with the ALB WAF Managed Rule Set.
+- `type` (String) Type of the Managed Rule Set.
+
+### Optional
+
+- `region` (String) STACKIT region name the resource is located in. If not defined, the provider region is used.
+
+### Read-Only
+
+- `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.
+
+
+### Nested Schema for `groups`
+
+Read-Only:
+
+- `description` (String) A description of what this group covers.
+- `group_name` (String) The name for the rule group.
+- `rules` (Attributes Map) Rules of the rule group. (see [below for nested schema](#nestedatt--groups--rules))
+
+
+### Nested Schema for `groups.rules`
+
+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_managed_rule_set/data-source.tf b/examples/data-sources/stackit_alb_waf_managed_rule_set/data-source.tf
new file mode 100644
index 000000000..2b5b6bf29
--- /dev/null
+++ b/examples/data-sources/stackit_alb_waf_managed_rule_set/data-source.tf
@@ -0,0 +1,4 @@
+data "stackit_alb_waf_managed_rule_set" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-managed-rule-set"
+}
diff --git a/examples/resources/stackit_alb_waf_managed_rule_set/resource.tf b/examples/resources/stackit_alb_waf_managed_rule_set/resource.tf
new file mode 100644
index 000000000..f30363ed4
--- /dev/null
+++ b/examples/resources/stackit_alb_waf_managed_rule_set/resource.tf
@@ -0,0 +1,5 @@
+resource "stackit_alb_waf_managed_rule_set" "example" {
+ project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ name = "example-managed-rule-set"
+ type = "TYPE_OWASP_CRS"
+}
diff --git a/go.mod b/go.mod
index 879120105..6e59f3a60 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +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/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 ad771eb9c..4c4830217 100644
--- a/go.sum
+++ b/go.sum
@@ -672,6 +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/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/core/core.go b/stackit/internal/core/core.go
index d71a0cfed..478b92c71 100644
--- a/stackit/internal/core/core.go
+++ b/stackit/internal/core/core.go
@@ -42,6 +42,7 @@ type ProviderData struct {
DefaultRegion string
ALBCertificatesCustomEndpoint string
ALBCustomEndpoint string
+ AlbWafCustomEndpoint string
AuthorizationCustomEndpoint string
CdnCustomEndpoint string
DnsCustomEndpoint string
diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go
new file mode 100644
index 000000000..b8e539850
--- /dev/null
+++ b/stackit/internal/services/albwaf/albwaf_acc_test.go
@@ -0,0 +1,184 @@
+package albwaf_test
+
+import (
+ "context"
+ _ "embed"
+ "errors"
+ "fmt"
+ "maps"
+ "strings"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/config"
+ "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "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"
+
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
+)
+
+var (
+ //go:embed testdata/managed-rule-set.tf
+ managedRuleSetConfig string
+)
+
+var testManagedRuleSet = config.Variables{
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
+ "type": config.StringVariable("TYPE_OWASP_CRS"),
+}
+
+var testManagedRuleSetUpdated = func() config.Variables {
+ updatedConfig := config.Variables{}
+ maps.Copy(updatedConfig, testManagedRuleSet)
+ updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"])))
+ return updatedConfig
+}
+
+func TestAccManagedRuleSet(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
+ CheckDestroy: testAccCheckDestroy,
+ Steps: []resource.TestStep{
+ // Creation
+ {
+ ConfigVariables: testManagedRuleSet,
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), managedRuleSetConfig),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "project_id", testutil.ProjectId),
+ 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"),
+ ),
+ },
+ // Data source
+ {
+ ConfigVariables: testManagedRuleSet,
+ Config: fmt.Sprintf(`
+ %s
+ %s
+
+ data "stackit_alb_waf_managed_rule_set" "managed_rule_set" {
+ project_id = stackit_alb_waf_managed_rule_set.managed_rule_set.project_id
+ name = stackit_alb_waf_managed_rule_set.managed_rule_set.name
+ }
+ `,
+ testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), managedRuleSetConfig,
+ ),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "project_id", testutil.ProjectId),
+ resource.TestCheckResourceAttr("data.stackit_alb_waf_managed_rule_set.managed_rule_set", "region", testutil.Region),
+ resource.TestCheckResourceAttrPair(
+ "data.stackit_alb_waf_managed_rule_set.managed_rule_set", "id",
+ "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"),
+ ),
+ },
+ // Import
+ {
+ ConfigVariables: testManagedRuleSet,
+ ResourceName: "stackit_alb_waf_managed_rule_set.managed_rule_set",
+ ImportStateIdFunc: func(s *terraform.State) (string, error) {
+ r, ok := s.RootModule().Resources["stackit_alb_waf_managed_rule_set.managed_rule_set"]
+ if !ok {
+ return "", fmt.Errorf("couldn't find resource stackit_alb_waf_managed_rule_set.managed_rule_set")
+ }
+ 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: testManagedRuleSetUpdated(),
+ Config: fmt.Sprintf("%s\n%s", testutil.NewConfigBuilder().EnableBetaResources(true).BuildProviderConfig(), managedRuleSetConfig),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_alb_waf_managed_rule_set.managed_rule_set", plancheck.ResourceActionReplace),
+ },
+ },
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("stackit_alb_waf_managed_rule_set.managed_rule_set", "project_id", testutil.ProjectId),
+ 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"),
+ ),
+ },
+ // Deletion is done by the framework implicitly
+ },
+ })
+}
+
+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)
+ }
+
+ return client, nil
+}
+
+func testAccCheckDestroy(s *terraform.State) error {
+ checkFunctions := []func(s *terraform.State) error{
+ testAlbWafManagedRuleSetDestroy,
+ }
+ var errs []error
+
+ for _, f := range checkFunctions {
+ func() {
+ err := f(s)
+ if err != nil {
+ errs = append(errs, err)
+ }
+ }()
+ }
+ return errors.Join(errs...)
+}
+
+func testAlbWafManagedRuleSetDestroy(s *terraform.State) error {
+ ctx := context.Background()
+ client, err := createClient()
+ if err != nil {
+ return err
+ }
+
+ managedRuleSetsToDestroy := []string{}
+ for _, rs := range s.RootModule().Resources {
+ if rs.Type != "stackit_alb_waf_managed_rule_set" {
+ continue
+ }
+ // managed rule set transform id: "[projectId],[region],[name]"
+ name := strings.Split(rs.Primary.ID, core.Separator)[2]
+ managedRuleSetsToDestroy = append(managedRuleSetsToDestroy, name)
+ }
+
+ resp, err := client.DefaultAPI.ListManagedRuleSets(ctx, testutil.ProjectId, testutil.Region).Execute()
+ if err != nil {
+ return fmt.Errorf("getting resp: %w", err)
+ }
+
+ for _, item := range resp.Items {
+ if utils.Contains(managedRuleSetsToDestroy, item.GetName()) {
+ _, err := client.DefaultAPI.DeleteManagedRuleSet(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
+}
diff --git a/stackit/internal/services/albwaf/managed_rule_set/datasource.go b/stackit/internal/services/albwaf/managed_rule_set/datasource.go
new file mode 100644
index 000000000..b802907a4
--- /dev/null
+++ b/stackit/internal/services/albwaf/managed_rule_set/datasource.go
@@ -0,0 +1,201 @@
+package managed_rule_set
+
+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 = &managedRuleSetDataSource{}
+ _ datasource.DataSourceWithConfigure = &managedRuleSetDataSource{}
+)
+
+type managedRuleSetDataSource struct {
+ client *albWaf.APIClient
+ providerData core.ProviderData
+}
+
+func NewManagedRuleSetDataSource() datasource.DataSource {
+ return &managedRuleSetDataSource{}
+}
+
+func (d *managedRuleSetDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ var ok bool
+ d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
+ if !ok {
+ return
+ }
+
+ features.CheckBetaResourcesEnabled(ctx, &d.providerData, &resp.Diagnostics, "stackit_alb_waf_managed_rule_set", core.Datasource)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ apiClient := utils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ d.client = apiClient
+ tflog.Info(ctx, "ALB WAF client configured")
+}
+
+func (d *managedRuleSetDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_alb_waf_managed_rule_set"
+}
+
+func (d *managedRuleSetDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Managed Rule Set DataSource schema. %s", core.DatasourceRegionFallbackDocstring), core.Datasource),
+ 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",
+ ),
+ },
+ },
+ "type": schema.StringAttribute{
+ Description: descriptions["type"],
+ Computed: true,
+ },
+ "version": schema.StringAttribute{
+ 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,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "description": schema.StringAttribute{
+ Description: descriptions["group_description"],
+ Computed: true,
+ },
+ "group_name": schema.StringAttribute{
+ Description: descriptions["group_name"],
+ Computed: true,
+ },
+ "rules": schema.MapNestedAttribute{
+ Description: descriptions["group_rules"],
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "description": schema.StringAttribute{
+ Description: descriptions["rule_description"],
+ Computed: true,
+ },
+ "mode": schema.StringAttribute{
+ Description: descriptions["rule_mode"],
+ Computed: true,
+ },
+ "severity": schema.StringAttribute{
+ Description: descriptions["rule_severity"],
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func (d *managedRuleSetDataSource) 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 := d.providerData.GetRegionWithOverride(model.Region)
+ ctx = tflog.SetField(ctx, "project_id", projectId)
+ ctx = tflog.SetField(ctx, "region", region)
+ ctx = tflog.SetField(ctx, "name", name)
+
+ managedRuleSetResp, err := d.client.DefaultAPI.GetManagedRuleSet(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 Managed Rule Set 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 Managed Rule Set", err.Error())
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ err = mapFields(ctx, managedRuleSetResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Managed Rule Set", 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 Managed Rule Set read")
+}
diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go
new file mode 100644
index 000000000..0409ec6e8
--- /dev/null
+++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go
@@ -0,0 +1,520 @@
+package managed_rule_set
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+ "regexp"
+ "strings"
+
+ "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/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-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"
+ tfutils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
+ "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
+)
+
+var (
+ _ resource.Resource = &managedRuleSetResource{}
+ _ resource.ResourceWithConfigure = &managedRuleSetResource{}
+ _ resource.ResourceWithImportState = &managedRuleSetResource{}
+ _ resource.ResourceWithModifyPlan = &managedRuleSetResource{}
+)
+
+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"`
+ Groups types.Map `tfsdk:"groups"`
+ Type types.String `tfsdk:"type"`
+ Usage types.Object `tfsdk:"usage"`
+ Version types.String `tfsdk:"version"`
+}
+
+type RuleGroupModel struct {
+ Description types.String `tfsdk:"description"`
+ GroupName types.String `tfsdk:"group_name"`
+ Rules types.Map `tfsdk:"rules"`
+}
+
+var ruleGroupType = map[string]attr.Type{
+ "description": types.StringType,
+ "group_name": types.StringType,
+ "rules": types.MapType{
+ ElemType: types.ObjectType{AttrTypes: ruleType},
+ },
+}
+
+type RuleModel struct {
+ Description types.String `tfsdk:"description"`
+ Mode types.String `tfsdk:"mode"`
+ Severity types.String `tfsdk:"severity"`
+}
+
+var ruleType = map[string]attr.Type{
+ "description": types.StringType,
+ "mode": types.StringType,
+ "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
+}
+
+func NewManagedRuleSetResource() resource.Resource {
+ return &managedRuleSetResource{}
+}
+
+func (r *managedRuleSetResource) 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_managed_rule_set", 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 *managedRuleSetResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_alb_waf_managed_rule_set"
+}
+
+// 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 Managed Rule Set.",
+ "region": "STACKIT region name the resource is located in. If not defined, the provider region is used.",
+ "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.",
+ "group_rules": "Rules of the rule group.",
+ "rule_description": "A description of what this rule does.",
+ "rule_mode": "The current mode of the rule.",
+ "rule_severity": "Impact level.",
+}
+
+func (r *managedRuleSetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Description: features.AddBetaDescription(fmt.Sprintf("ALB WAF Managed Rule Set 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",
+ ),
+ },
+ },
+ "type": schema.StringAttribute{
+ Description: descriptions["type"],
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "version": schema.StringAttribute{
+ 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,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "description": schema.StringAttribute{
+ Description: descriptions["group_description"],
+ Computed: true,
+ },
+ "group_name": schema.StringAttribute{
+ Description: descriptions["group_name"],
+ Computed: true,
+ },
+ "rules": schema.MapNestedAttribute{
+ Description: descriptions["group_rules"],
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "description": schema.StringAttribute{
+ Description: descriptions["rule_description"],
+ Computed: true,
+ },
+ "mode": schema.StringAttribute{
+ Description: descriptions["rule_mode"],
+ Computed: true,
+ },
+ "severity": schema.StringAttribute{
+ Description: descriptions["rule_severity"],
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func (r *managedRuleSetResource) 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 *managedRuleSetResource) 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 Managed Rule Set",
+ 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 Managed Rule Set state imported")
+}
+
+func (r *managedRuleSetResource) 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 Managed Rule Set", fmt.Sprintf("Creating API payload: %v", err))
+ return
+ }
+
+ createResp, err := r.client.DefaultAPI.CreateManagedRuleSet(ctx, projectId, region).CreateManagedRuleSetPayload(*payload).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Managed Rule Set", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ if createResp.Name == nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Managed Rule Set", "Got empty Managed Rule Set name")
+ return
+ }
+ managedRuleSetName := *createResp.Name
+
+ ctx = tfutils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
+ "project_id": projectId,
+ "region": region,
+ "name": managedRuleSetName,
+ })
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ err = mapFields(ctx, createResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating ALB WAF Managed Rule Set", 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 Managed Rule Set created")
+}
+
+func (r *managedRuleSetResource) 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 Managed Rule Set is not updatable")
+}
+
+func (r *managedRuleSetResource) 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)
+
+ managedRuleSetResp, err := r.client.DefaultAPI.GetManagedRuleSet(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 Managed Rule Set", err.Error())
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ err = mapFields(ctx, managedRuleSetResp, &model, region)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading ALB WAF Managed Rule Set", 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 Managed Rule Set read")
+}
+
+func (r *managedRuleSetResource) 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.DeleteManagedRuleSet(ctx, projectId, region, name).Execute()
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting ALB WAF Managed Rule Set", fmt.Sprintf("Calling API: %v", err))
+ return
+ }
+
+ ctx = core.LogResponse(ctx)
+
+ tflog.Info(ctx, "ALB WAF Managed Rule Set deleted")
+}
+
+func toCreatePayload(_ context.Context, model *Model) (*albWaf.CreateManagedRuleSetPayload, error) {
+ if model == nil {
+ return nil, fmt.Errorf("nil model")
+ }
+
+ payload := &albWaf.CreateManagedRuleSetPayload{
+ Name: model.Name.ValueStringPointer(),
+ Type: new(albWaf.MRSType(model.Type.ValueString())),
+ }
+
+ return payload, nil
+}
+
+func mapFields(ctx context.Context, managedRuleSet *albWaf.GetManagedRuleSetResponse, model *Model, region string) error {
+ if managedRuleSet == nil {
+ return fmt.Errorf("response input is nil")
+ }
+ if model == nil {
+ return fmt.Errorf("model input is nil")
+ }
+
+ var diags diag.Diagnostics
+
+ model.Id = tfutils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.Name.ValueString())
+ model.Name = types.StringValue(model.Name.ValueString())
+ model.Region = types.StringValue(region)
+
+ model.Type = types.StringPointerValue((*string)(managedRuleSet.Type))
+ model.Version = types.StringPointerValue(managedRuleSet.Version)
+
+ groupsMap := map[string]attr.Value{}
+ if groups, ok := managedRuleSet.GetGroupsOk(); ok {
+ for groupKey, group := range *groups {
+ groupTF := RuleGroupModel{
+ Description: types.StringPointerValue(group.Description),
+ GroupName: types.StringPointerValue(group.GroupName),
+ }
+
+ ruleMap := map[string]attr.Value{}
+ if rules, ok := group.GetRulesOk(); ok {
+ for ruleKey, rule := range *rules {
+ ruleTF := RuleModel{
+ Description: types.StringPointerValue(rule.Description),
+ Mode: types.StringPointerValue((*string)(rule.Mode)),
+ Severity: types.StringPointerValue(rule.Severity),
+ }
+
+ ruleMap[ruleKey], diags = types.ObjectValueFrom(ctx, ruleType, ruleTF)
+ if diags.HasError() {
+ return fmt.Errorf("mapping role: %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))
+ }
+
+ groupsMap[groupKey], diags = types.ObjectValueFrom(ctx, ruleGroupType, groupTF)
+ if diags.HasError() {
+ return fmt.Errorf("mapping group: %w", core.DiagsToError(diags))
+ }
+ }
+ }
+ model.Groups, diags = types.MapValue(
+ types.ObjectType{AttrTypes: ruleGroupType},
+ groupsMap,
+ )
+ if diags.HasError() {
+ 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
new file mode 100644
index 000000000..9b7bc9548
--- /dev/null
+++ b/stackit/internal/services/albwaf/managed_rule_set/resource_test.go
@@ -0,0 +1,111 @@
+package managed_rule_set
+
+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-managed-rule-set")
+ testId = types.StringValue(testProjectId.ValueString() + "," + testRegion.ValueString() + "," + testName.ValueString())
+)
+
+func TestToCreatePayload(t *testing.T) {
+ tests := []struct {
+ name string
+ model *Model
+ expected *albWaf.CreateManagedRuleSetPayload
+ isValid bool
+ }{
+ {
+ name: "default",
+ model: &Model{
+ Name: testName,
+ Id: testId,
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)),
+ },
+ expected: &albWaf.CreateManagedRuleSetPayload{
+ Name: testName.ValueStringPointer(),
+ Type: new(albWaf.MRSTYPE_TYPE_OWASP_CRS),
+ },
+ 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.GetManagedRuleSetResponse
+ expected *Model
+ isValid bool
+ }{
+ {
+ name: "default",
+ state: &Model{
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Name: testName,
+ Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)),
+ Id: testId,
+ Groups: types.MapValueMust(types.ObjectType{AttrTypes: ruleGroupType}, map[string]attr.Value{}),
+ },
+ region: testRegion.ValueString(),
+ input: &albWaf.GetManagedRuleSetResponse{
+ Groups: &map[string]albWaf.MRSRuleGroup{},
+ Name: testName.ValueStringPointer(),
+ Type: new(albWaf.MRSTYPE2_TYPE_OWASP_CRS),
+ },
+ expected: &Model{
+ ProjectId: testProjectId,
+ Region: testRegion,
+ Name: testName,
+ Type: types.StringValue(string(albWaf.MRSTYPE_TYPE_OWASP_CRS)),
+ Id: testId,
+ Groups: types.MapValueMust(types.ObjectType{AttrTypes: ruleGroupType}, map[string]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/testdata/managed-rule-set.tf b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf
new file mode 100644
index 000000000..de5736aca
--- /dev/null
+++ b/stackit/internal/services/albwaf/testdata/managed-rule-set.tf
@@ -0,0 +1,10 @@
+
+variable "project_id" {}
+variable "type" {}
+variable "name" {}
+
+resource "stackit_alb_waf_managed_rule_set" "managed_rule_set" {
+ project_id = var.project_id
+ type = var.type
+ name = var.name
+}
diff --git a/stackit/internal/services/albwaf/utils/util.go b/stackit/internal/services/albwaf/utils/util.go
new file mode 100644
index 000000000..9ed484a50
--- /dev/null
+++ b/stackit/internal/services/albwaf/utils/util.go
@@ -0,0 +1,31 @@
+package utils
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/stackitcloud/stackit-sdk-go/core/config"
+ 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/utils"
+)
+
+func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags *diag.Diagnostics) *albWaf.APIClient {
+ apiClientConfigOptions := []config.ConfigurationOption{
+ config.WithCustomAuth(providerData.RoundTripper),
+ utils.UserAgentConfigOption(providerData.Version),
+ }
+ if providerData.AlbWafCustomEndpoint != "" {
+ apiClientConfigOptions = append(apiClientConfigOptions, config.WithEndpoint(providerData.AlbWafCustomEndpoint))
+ }
+
+ apiClient, err := albWaf.NewAPIClient(apiClientConfigOptions...)
+ if err != nil {
+ core.LogAndAddError(ctx, diags, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err))
+ return nil
+ }
+
+ return apiClient
+}
diff --git a/stackit/internal/services/albwaf/utils/util_test.go b/stackit/internal/services/albwaf/utils/util_test.go
new file mode 100644
index 000000000..ccd9f606c
--- /dev/null
+++ b/stackit/internal/services/albwaf/utils/util_test.go
@@ -0,0 +1,94 @@
+package utils
+
+import (
+ "context"
+ "os"
+ "reflect"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients"
+ "github.com/stackitcloud/stackit-sdk-go/core/config"
+ 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/utils"
+)
+
+const (
+ testVersion = "1.2.3"
+ testCustomEndpoint = "https://alb-waf-custom-endpoint.api.stackit.cloud"
+)
+
+func TestConfigureClient(t *testing.T) {
+ /* mock authentication by setting service account token env variable */
+ os.Clearenv()
+ err := os.Setenv(sdkClients.ServiceAccountToken, "mock-val")
+ if err != nil {
+ t.Errorf("error setting env variable: %v", err)
+ }
+
+ type args struct {
+ providerData *core.ProviderData
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ expected *albWaf.APIClient
+ }{
+ {
+ name: "default endpoint",
+ args: args{
+ providerData: &core.ProviderData{
+ Version: testVersion,
+ },
+ },
+ expected: func() *albWaf.APIClient {
+ apiClient, err := albWaf.NewAPIClient(
+ utils.UserAgentConfigOption(testVersion),
+ )
+ if err != nil {
+ t.Errorf("error configuring client: %v", err)
+ }
+ return apiClient
+ }(),
+ wantErr: false,
+ },
+ {
+ name: "custom endpoint",
+ args: args{
+ providerData: &core.ProviderData{
+ Version: testVersion,
+ AlbWafCustomEndpoint: testCustomEndpoint,
+ },
+ },
+ expected: func() *albWaf.APIClient {
+ apiClient, err := albWaf.NewAPIClient(
+ utils.UserAgentConfigOption(testVersion),
+ config.WithEndpoint(testCustomEndpoint),
+ )
+ if err != nil {
+ t.Errorf("error configuring client: %v", err)
+ }
+ return apiClient
+ }(),
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+ diags := diag.Diagnostics{}
+
+ actual := ConfigureClient(ctx, tt.args.providerData, &diags)
+ if diags.HasError() != tt.wantErr {
+ t.Errorf("ConfigureClient() error = %v, want %v", diags.HasError(), tt.wantErr)
+ }
+
+ if !reflect.DeepEqual(actual, tt.expected) {
+ t.Errorf("ConfigureClient() = %v, want %v", actual, tt.expected)
+ }
+ })
+ }
+}
diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go
index 9693516a7..3887570cb 100644
--- a/stackit/internal/testutil/testutil.go
+++ b/stackit/internal/testutil/testutil.go
@@ -69,6 +69,7 @@ var (
ALBCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CUSTOM_ENDPOINT", providerName: "alb_custom_endpoint"}
ALBCertCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_CERT_CUSTOM_ENDPOINT", providerName: "alb_certificates_custom_endpoint"}
+ AlbWafCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_ALB_WAF_CUSTOM_ENDPOINT", providerName: "alb_waf_custom_endpoint"}
CdnCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_CDN_CUSTOM_ENDPOINT", providerName: "cdn_custom_endpoint"}
DnsCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DNS_CUSTOM_ENDPOINT", providerName: "dns_custom_endpoint"}
DremioCustomEndpoint = customEndpointConfig{envVarName: "TF_ACC_DREMIO_CUSTOM_ENDPOINT", providerName: "dremio_custom_endpoint"}
@@ -107,6 +108,7 @@ var (
allCustomEndpoints = []customEndpointConfig{
ALBCustomEndpoint,
ALBCertCustomEndpoint,
+ AlbWafCustomEndpoint,
CdnCustomEndpoint,
DnsCustomEndpoint,
EdgeCloudCustomEndpoint,
diff --git a/stackit/provider.go b/stackit/provider.go
index 99d425d73..f99c5eb3c 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"
+ 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"
cdnCustomDomain "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/cdn/customdomain"
@@ -186,6 +187,7 @@ type providerModel struct {
AuthorizationCustomEndpoint types.String `tfsdk:"authorization_custom_endpoint"`
CdnCustomEndpoint types.String `tfsdk:"cdn_custom_endpoint"`
ALBCertificatesCustomEndpoint types.String `tfsdk:"alb_certificates_custom_endpoint"`
+ AlbWafCustomEndpoint types.String `tfsdk:"alb_waf_custom_endpoint"`
DnsCustomEndpoint types.String `tfsdk:"dns_custom_endpoint"`
DremioCustomEndpoint types.String `tfsdk:"dremio_custom_endpoint"`
EdgeCloudCustomEndpoint types.String `tfsdk:"edgecloud_custom_endpoint"`
@@ -244,6 +246,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
"region": "Region will be used as the default location for regional services. Not all services require a region, some are global",
"default_region": "Region will be used as the default location for regional services. Not all services require a region, some are global",
"alb_certificates_custom_endpoint": "Custom endpoint for the Application Load Balancer TLS Certificate service",
+ "alb_waf_custom_endpoint": "Custom endpoint for the Application Load Balancer Web Application Firewall service",
"alb_custom_endpoint": "Custom endpoint for the Application Load Balancer service",
"cdn_custom_endpoint": "Custom endpoint for the CDN service",
"dns_custom_endpoint": "Custom endpoint for the DNS service",
@@ -374,6 +377,10 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
Optional: true,
Description: descriptions["alb_certificates_custom_endpoint"],
},
+ "alb_waf_custom_endpoint": schema.StringAttribute{
+ Optional: true,
+ Description: descriptions["alb_waf_custom_endpoint"],
+ },
"dns_custom_endpoint": schema.StringAttribute{
Optional: true,
Description: descriptions["dns_custom_endpoint"],
@@ -563,6 +570,7 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
setStringField(providerConfig.ALBCertificatesCustomEndpoint, func(v string) { providerData.ALBCertificatesCustomEndpoint = v })
setStringField(providerConfig.ALBCustomEndpoint, func(v string) { providerData.ALBCustomEndpoint = v })
+ setStringField(providerConfig.AlbWafCustomEndpoint, func(v string) { providerData.AlbWafCustomEndpoint = v })
setStringField(providerConfig.AuthorizationCustomEndpoint, func(v string) { providerData.AuthorizationCustomEndpoint = v })
setStringField(providerConfig.CdnCustomEndpoint, func(v string) { providerData.CdnCustomEndpoint = v })
setStringField(providerConfig.DnsCustomEndpoint, func(v string) { providerData.DnsCustomEndpoint = v })
@@ -672,6 +680,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,
+ albWafManagedRuleSet.NewManagedRuleSetDataSource,
alertGroup.NewAlertGroupDataSource,
cdn.NewDistributionDataSource,
cert.NewCertificatesDataSource,
@@ -784,6 +793,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,
+ albWafManagedRuleSet.NewManagedRuleSetResource,
alertGroup.NewAlertGroupResource,
cdn.NewDistributionResource,
cert.NewCertificatesResource,