feat(cdn): Add support for configuring WAF#1372
feat(cdn): Add support for configuring WAF#1372matheuspolitano wants to merge 55 commits intostackitcloud:mainfrom
Conversation
Co-authored-by: Marcel Jacek <72880145+marceljk@users.noreply.github.com>
Co-authored-by: Marcel Jacek <72880145+marceljk@users.noreply.github.com>
| // Map WAF Update/Removal | ||
| if !utils.IsUndefined(configPlanModel.Waf) { | ||
| var wafModel wafConfig | ||
| diags := configPlanModel.Waf.As(ctx, &wafModel, basetypes.ObjectAsOptions{}) | ||
| if diags.HasError() { | ||
| core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", "Error mapping WAF config") | ||
| return | ||
| } | ||
| wafPatch := cdnSdk.WafConfigPatch{ | ||
| Mode: new(cdnSdk.WafMode(wafModel.Mode.ValueString())), | ||
| Type: new(cdnSdk.WafType(wafModel.Type.ValueString())), | ||
| AllowedHttpVersions: getSortedWafList(ctx, wafModel.AllowedHttpVersions), | ||
| AllowedRequestContentTypes: getSortedWafList(ctx, wafModel.AllowedRequestContentTypes), | ||
| AllowedHttpMethods: getSortedWafList(ctx, wafModel.AllowedHttpMethods), | ||
| EnabledRuleIds: getSortedWafList(ctx, wafModel.EnabledRuleIds), | ||
| DisabledRuleIds: getSortedWafList(ctx, wafModel.DisabledRuleIds), | ||
| LogOnlyRuleIds: getSortedWafList(ctx, wafModel.LogOnlyRuleIds), | ||
| EnabledRuleGroupIds: getSortedWafList(ctx, wafModel.EnabledRuleGroupIds), | ||
| DisabledRuleGroupIds: getSortedWafList(ctx, wafModel.DisabledRuleGroupIds), | ||
| LogOnlyRuleGroupIds: getSortedWafList(ctx, wafModel.LogOnlyRuleGroupIds), | ||
| EnabledRuleCollectionIds: getSortedWafList(ctx, wafModel.EnabledRuleCollectionIds), | ||
| DisabledRuleCollectionIds: getSortedWafList(ctx, wafModel.DisabledRuleCollectionIds), | ||
| LogOnlyRuleCollectionIds: getSortedWafList(ctx, wafModel.LogOnlyRuleCollectionIds), | ||
| } | ||
| if !utils.IsUndefined(wafModel.ParanoiaLevel) { | ||
| pl := cdnSdk.WafParanoiaLevel(wafModel.ParanoiaLevel.ValueString()) | ||
| wafPatch.ParanoiaLevel = &pl | ||
| } | ||
| configPatch.Waf = &wafPatch | ||
| } else if !utils.IsUndefined(configStateModel.Waf) { | ||
| // User explicitly removed the WAF block from their terraform configuration | ||
| modeDisabled := cdnSdk.WafMode(cdnSdk.WAFMODE_DISABLED) | ||
| typeFree := cdnSdk.WafType(cdnSdk.WAFTYPE_FREE) | ||
|
|
||
| wafPatch := cdnSdk.WafConfigPatch{ | ||
| Mode: &modeDisabled, | ||
| Type: &typeFree, | ||
| // Send empty arrays to clear rules, keeping the API happy | ||
| EnabledRuleIds: []string{}, | ||
| DisabledRuleIds: []string{}, | ||
| LogOnlyRuleIds: []string{}, | ||
| EnabledRuleGroupIds: []string{}, | ||
| DisabledRuleGroupIds: []string{}, | ||
| LogOnlyRuleGroupIds: []string{}, | ||
| EnabledRuleCollectionIds: []string{}, | ||
| DisabledRuleCollectionIds: []string{}, | ||
| LogOnlyRuleCollectionIds: []string{}, | ||
| // Intentionally omitted (nil) to avoid the 422 Unprocessable Entity error: | ||
| // AllowedHttpVersions, AllowedRequestContentTypes, AllowedHttpMethods | ||
| } | ||
| configPatch.Waf = &wafPatch | ||
| } |
There was a problem hiding this comment.
Why do you need the state here? Is it needed to set WAFMODE_DISABLED or can configPatch.Waf set to null to remove it?
Nevertheless, I think you can check here also configPlanModel.Waf.IsNull() instead of !utils.IsUndefined(configStateModel.Waf) and would have the same outcome but without the need accessing the state
There was a problem hiding this comment.
We are no longer persisting states once the WAF is removed, it is only disabled and change the type to free a pushed the API
There was a problem hiding this comment.
But for this you don't need the state. You can just set WafConfigPatch always to mode=disabled and type=free, when in the terraform config nothing related to Waf is defined.
Then you can remove the added stateModel and revert all renamings from Model -> PlanModel. I would actually prefer to avoid here, handling the state and config model, because this can lead in the future to issue when someone mixes them. And this resource is already really big.
…ment-configuring-waf
| func SortedStringsToListValue(items []string) basetypes.ListValue { | ||
| if len(items) == 0 { |
There was a problem hiding this comment.
| func SortedStringsToListValue(items []string) basetypes.ListValue { | |
| if len(items) == 0 { | |
| func SortedStringsToListValue(items []string) basetypes.ListValue { | |
| if items == nil { | |
| return types.ListNull(types.StringType) | |
| } | |
| if len(items) == 0 { |
I would return here a null list in case the string slice is also null. null list != empty list
This will also need some adjustment to the tests
| "mode": schema.StringAttribute{ | ||
| Required: true, | ||
| Description: schemaDescriptions["waf_mode"], | ||
| Validators: []validator.String{stringvalidator.OneOf(string(cdnSdk.WAFMODE_DISABLED), string(cdnSdk.WAFMODE_ENABLED), string(cdnSdk.WAFMODE_LOG_ONLY))}, |
There was a problem hiding this comment.
We have in our SDK also a slice with all the enum. Use it here instead of referencing each enum by hand
| Validators: []validator.String{stringvalidator.OneOf(string(cdnSdk.WAFMODE_DISABLED), string(cdnSdk.WAFMODE_ENABLED), string(cdnSdk.WAFMODE_LOG_ONLY))}, | |
| Validators: []validator.String{stringvalidator.OneOf(sdkUtils.EnumSliceToStringSlice(cdnSdk.AllowedWafModeEnumValues)...)}, |
| "type": schema.StringAttribute{ | ||
| Required: true, | ||
| Description: schemaDescriptions["waf_type"], | ||
| Validators: []validator.String{stringvalidator.OneOf(string(cdnSdk.WAFTYPE_PREMIUM), string(cdnSdk.WAFTYPE_FREE))}, |
| "paranoia_level": schema.StringAttribute{ | ||
| Optional: true, | ||
| Description: schemaDescriptions["waf_paranoia_level"], | ||
| Validators: []validator.String{stringvalidator.OneOf(string(cdnSdk.WAFPARANOIALEVEL_L1), string(cdnSdk.WAFPARANOIALEVEL_L2), string(cdnSdk.WAFPARANOIALEVEL_L3), string(cdnSdk.WAFPARANOIALEVEL_L4))}, |
| // Map WAF Update/Removal | ||
| if !utils.IsUndefined(configPlanModel.Waf) { | ||
| var wafModel wafConfig | ||
| diags := configPlanModel.Waf.As(ctx, &wafModel, basetypes.ObjectAsOptions{}) | ||
| if diags.HasError() { | ||
| core.LogAndAddError(ctx, &resp.Diagnostics, "Update CDN distribution", "Error mapping WAF config") | ||
| return | ||
| } | ||
| wafPatch := cdnSdk.WafConfigPatch{ | ||
| Mode: new(cdnSdk.WafMode(wafModel.Mode.ValueString())), | ||
| Type: new(cdnSdk.WafType(wafModel.Type.ValueString())), | ||
| AllowedHttpVersions: getSortedWafList(ctx, wafModel.AllowedHttpVersions), | ||
| AllowedRequestContentTypes: getSortedWafList(ctx, wafModel.AllowedRequestContentTypes), | ||
| AllowedHttpMethods: getSortedWafList(ctx, wafModel.AllowedHttpMethods), | ||
| EnabledRuleIds: getSortedWafList(ctx, wafModel.EnabledRuleIds), | ||
| DisabledRuleIds: getSortedWafList(ctx, wafModel.DisabledRuleIds), | ||
| LogOnlyRuleIds: getSortedWafList(ctx, wafModel.LogOnlyRuleIds), | ||
| EnabledRuleGroupIds: getSortedWafList(ctx, wafModel.EnabledRuleGroupIds), | ||
| DisabledRuleGroupIds: getSortedWafList(ctx, wafModel.DisabledRuleGroupIds), | ||
| LogOnlyRuleGroupIds: getSortedWafList(ctx, wafModel.LogOnlyRuleGroupIds), | ||
| EnabledRuleCollectionIds: getSortedWafList(ctx, wafModel.EnabledRuleCollectionIds), | ||
| DisabledRuleCollectionIds: getSortedWafList(ctx, wafModel.DisabledRuleCollectionIds), | ||
| LogOnlyRuleCollectionIds: getSortedWafList(ctx, wafModel.LogOnlyRuleCollectionIds), | ||
| } | ||
| if !utils.IsUndefined(wafModel.ParanoiaLevel) { | ||
| pl := cdnSdk.WafParanoiaLevel(wafModel.ParanoiaLevel.ValueString()) | ||
| wafPatch.ParanoiaLevel = &pl | ||
| } | ||
| configPatch.Waf = &wafPatch | ||
| } else if !utils.IsUndefined(configStateModel.Waf) { | ||
| // User explicitly removed the WAF block from their terraform configuration | ||
| modeDisabled := cdnSdk.WafMode(cdnSdk.WAFMODE_DISABLED) | ||
| typeFree := cdnSdk.WafType(cdnSdk.WAFTYPE_FREE) | ||
|
|
||
| wafPatch := cdnSdk.WafConfigPatch{ | ||
| Mode: &modeDisabled, | ||
| Type: &typeFree, | ||
| // Send empty arrays to clear rules, keeping the API happy | ||
| EnabledRuleIds: []string{}, | ||
| DisabledRuleIds: []string{}, | ||
| LogOnlyRuleIds: []string{}, | ||
| EnabledRuleGroupIds: []string{}, | ||
| DisabledRuleGroupIds: []string{}, | ||
| LogOnlyRuleGroupIds: []string{}, | ||
| EnabledRuleCollectionIds: []string{}, | ||
| DisabledRuleCollectionIds: []string{}, | ||
| LogOnlyRuleCollectionIds: []string{}, | ||
| // Intentionally omitted (nil) to avoid the 422 Unprocessable Entity error: | ||
| // AllowedHttpVersions, AllowedRequestContentTypes, AllowedHttpMethods | ||
| } | ||
| configPatch.Waf = &wafPatch | ||
| } |
There was a problem hiding this comment.
But for this you don't need the state. You can just set WafConfigPatch always to mode=disabled and type=free, when in the terraform config nothing related to Waf is defined.
Then you can remove the added stateModel and revert all renamings from Model -> PlanModel. I would actually prefer to avoid here, handling the state and config model, because this can lead in the future to issue when someone mixes them. And this resource is already really big.
Description
https://jira.schwarz/browse/STACKITCDN-723
I want to add support for the waf configuration block in the CDN distribution resource,so that users can programmatically manage security tiers, paranoia levels, and granular rule overrides (Enabled/Disabled/Log-only) via Terraform.
Checklist
make fmtexamples/directory)make generate-docs(will be checked by CI)make test(will be checked by CI)make lint(will be checked by CI)