diff --git a/azurerm/internal/services/frontdoor/client.go b/azurerm/internal/services/frontdoor/client.go index af7c6040c320..60fee3730207 100644 --- a/azurerm/internal/services/frontdoor/client.go +++ b/azurerm/internal/services/frontdoor/client.go @@ -8,6 +8,7 @@ import ( type Client struct { FrontDoorsClient *frontdoor.FrontDoorsClient FrontDoorsFrontendClient *frontdoor.FrontendEndpointsClient + FrontDoorsPolicyClient *frontdoor.PoliciesClient } func BuildClient(o *common.ClientOptions) *Client { @@ -17,8 +18,12 @@ func BuildClient(o *common.ClientOptions) *Client { frontDoorsFrontendClient := frontdoor.NewFrontendEndpointsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&frontDoorsFrontendClient.Client, o.ResourceManagerAuthorizer) + frontDoorsPolicyClient := frontdoor.NewPoliciesClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&frontDoorsPolicyClient.Client, o.ResourceManagerAuthorizer) + return &Client{ FrontDoorsClient: &frontDoorsClient, FrontDoorsFrontendClient: &frontDoorsFrontendClient, + FrontDoorsPolicyClient: &frontDoorsPolicyClient, } } diff --git a/azurerm/internal/services/frontdoor/helper.go b/azurerm/internal/services/frontdoor/helper.go index 6d95177cb73c..006e03a08228 100644 --- a/azurerm/internal/services/frontdoor/helper.go +++ b/azurerm/internal/services/frontdoor/helper.go @@ -75,6 +75,7 @@ func GetFrontDoorBasicRouteConfigurationType(i interface{}) string { return "ForwardingConfiguration" } } + func VerifyRoutingRuleFrontendEndpoints(routingRuleFrontends []interface{}, configFrontendEndpoints []interface{}) error { for _, routingRuleFrontend := range routingRuleFrontends { // Get the name of the frontend defined in the routing rule @@ -178,3 +179,25 @@ func VerifyCustomHttpsConfiguration(configFrontendEndpoints []interface{}) error return nil } + +func FlattenTransformSlice(input *[]frontdoor.TransformType) []interface{} { + result := make([]interface{}, 0) + + if input != nil { + for _, item := range *input { + result = append(result, string(item)) + } + } + return result +} + +func FlattenFrontendEndpointLinkSlice(input *[]frontdoor.FrontendEndpointLink) []interface{} { + result := make([]interface{}, 0) + + if input != nil { + for _, item := range *input { + result = append(result, *item.ID) + } + } + return result +} diff --git a/azurerm/internal/services/frontdoor/validate.go b/azurerm/internal/services/frontdoor/validate.go index 3f24097840f7..67e2e5a57880 100644 --- a/azurerm/internal/services/frontdoor/validate.go +++ b/azurerm/internal/services/frontdoor/validate.go @@ -23,6 +23,14 @@ func ValidateBackendPoolRoutingRuleName(i interface{}, k string) (_ []string, er return nil, errors } +func ValidateCustomBlockResponseBody(i interface{}, k string) (_ []string, errors []error) { + if m, regexErrs := validate.RegExHelper(i, k, `^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$`); !m { + errors = append(regexErrs, fmt.Errorf(`%q contains invalid characters, %q must contain only alphanumeric and equals sign characters.`, k, k)) + } + + return nil, errors +} + func ValidateFrontdoorSettings(d *schema.ResourceDiff) error { routingRules := d.Get("routing_rule").([]interface{}) configFrontendEndpoints := d.Get("frontend_endpoint").([]interface{}) diff --git a/azurerm/provider.go b/azurerm/provider.go index 7013fc867319..5351fcf65aff 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -262,6 +262,7 @@ func Provider() terraform.ResourceProvider { "azurerm_firewall_network_rule_collection": resourceArmFirewallNetworkRuleCollection(), "azurerm_firewall": resourceArmFirewall(), "azurerm_frontdoor": resourceArmFrontDoor(), + "azurerm_frontdoor_firewall_policy": resourceArmFrontDoorFirewallPolicy(), "azurerm_function_app": resourceArmFunctionApp(), "azurerm_hdinsight_hadoop_cluster": resourceArmHDInsightHadoopCluster(), "azurerm_hdinsight_hbase_cluster": resourceArmHDInsightHBaseCluster(), diff --git a/azurerm/resource_arm_front_door.go b/azurerm/resource_arm_front_door.go index ddd862f5945c..141d1b2e00dc 100644 --- a/azurerm/resource_arm_front_door.go +++ b/azurerm/resource_arm_front_door.go @@ -378,6 +378,10 @@ func resourceArmFrontDoor() *schema.Resource { Type: schema.TypeBool, Required: true, }, + "web_application_firewall_policy_link_id": { + Type: schema.TypeString, + Optional: true, + }, "custom_https_configuration": { Type: schema.TypeList, Optional: true, @@ -786,6 +790,7 @@ func expandArmFrontDoorFrontendEndpoint(input []interface{}, frontDoorPath strin isSessionAffinityEnabled := frontendEndpoint["session_affinity_enabled"].(bool) sessionAffinityTtlSeconds := int32(frontendEndpoint["session_affinity_ttl_seconds"].(int)) customHttpsConfiguration := frontendEndpoint["custom_https_configuration"].([]interface{}) + waf := frontendEndpoint["web_application_firewall_policy_link_id"].(string) name := frontendEndpoint["name"].(string) id := utils.String(frontDoorPath + "/FrontendEndpoints/" + name) @@ -805,6 +810,12 @@ func expandArmFrontDoorFrontendEndpoint(input []interface{}, frontDoorPath strin }, } + if waf != "" { + result.FrontendEndpointProperties.WebApplicationFirewallPolicyLink = &frontdoor.FrontendEndpointUpdateParametersWebApplicationFirewallPolicyLink{ + ID: utils.String(waf), + } + } + output = append(output, result) } @@ -1183,17 +1194,17 @@ func flattenArmFrontDoorFrontendEndpoint(input *[]frontdoor.FrontendEndpoint, re } if sessionAffinityEnabled := properties.SessionAffinityEnabledState; sessionAffinityEnabled != "" { - if sessionAffinityEnabled == frontdoor.SessionAffinityEnabledStateEnabled { - result["session_affinity_enabled"] = true - } else { - result["session_affinity_enabled"] = false - } + result["session_affinity_enabled"] = sessionAffinityEnabled == frontdoor.SessionAffinityEnabledStateEnabled } if sessionAffinityTtlSeconds := properties.SessionAffinityTTLSeconds; sessionAffinityTtlSeconds != nil { result["session_affinity_ttl_seconds"] = *sessionAffinityTtlSeconds } + if waf := properties.WebApplicationFirewallPolicyLink; waf != nil { + result["web_application_firewall_policy_link_id"] = *waf.ID + } + if properties.CustomHTTPSConfiguration != nil { customHTTPSConfiguration := properties.CustomHTTPSConfiguration if customHTTPSConfiguration.CertificateSource == frontdoor.CertificateSourceAzureKeyVault { diff --git a/azurerm/resource_arm_front_door_firewall_policy.go b/azurerm/resource_arm_front_door_firewall_policy.go new file mode 100644 index 000000000000..64c7a02d5a1b --- /dev/null +++ b/azurerm/resource_arm_front_door_firewall_policy.go @@ -0,0 +1,771 @@ +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/preview/frontdoor/mgmt/2019-04-01/frontdoor" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + afd "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/frontdoor" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmFrontDoorFirewallPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceArmFrontDoorFirewallPolicyCreateUpdate, + Read: resourceArmFrontDoorFirewallPolicyRead, + Update: resourceArmFrontDoorFirewallPolicyCreateUpdate, + Delete: resourceArmFrontDoorFirewallPolicyDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "location": azure.SchemaLocationForDataSource(), + + "resource_group_name": azure.SchemaResourceGroupName(), + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "mode": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + string(frontdoor.Detection), + string(frontdoor.Prevention), + }, false), + Default: string(frontdoor.Prevention), + }, + + "redirect_url": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.URLIsHTTPOrHTTPS, + }, + + "custom_block_response_status_code": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validate.IntInSlice([]int{ + 200, + 403, + 405, + 406, + 429, + }), + }, + + "custom_block_response_body": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: afd.ValidateCustomBlockResponseBody, + }, + + "custom_rule": { + Type: schema.TypeList, + MaxItems: 100, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + + "priority": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(frontdoor.MatchRule), + string(frontdoor.RateLimitRule), + }, false), + }, + + "rate_limit_duration_in_minutes": { + Type: schema.TypeInt, + Optional: true, + Default: 1, + }, + + "rate_limit_threshold": { + Type: schema.TypeInt, + Optional: true, + Default: 10, + }, + + "action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(frontdoor.Allow), + string(frontdoor.Block), + string(frontdoor.Log), + string(frontdoor.Redirect), + }, false), + }, + + "match_condition": { + Type: schema.TypeList, + Optional: true, + MaxItems: 100, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "match_variable": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(frontdoor.Cookies), + string(frontdoor.PostArgs), + string(frontdoor.QueryString), + string(frontdoor.RemoteAddr), + string(frontdoor.RequestBody), + string(frontdoor.RequestHeader), + string(frontdoor.RequestMethod), + string(frontdoor.RequestURI), + }, false), + }, + + "match_values": { + Type: schema.TypeList, + Required: true, + MaxItems: 100, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validate.NoEmptyStrings, + }, + }, + + "operator": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(frontdoor.Any), + string(frontdoor.BeginsWith), + string(frontdoor.Contains), + string(frontdoor.EndsWith), + string(frontdoor.Equal), + string(frontdoor.GeoMatch), + string(frontdoor.GreaterThan), + string(frontdoor.GreaterThanOrEqual), + string(frontdoor.IPMatch), + string(frontdoor.LessThan), + string(frontdoor.LessThanOrEqual), + string(frontdoor.RegEx), + }, false), + }, + + "selector": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "negation_condition": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "transforms": { + Type: schema.TypeList, + Optional: true, + MaxItems: 5, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(frontdoor.Lowercase), + string(frontdoor.RemoveNulls), + string(frontdoor.Trim), + string(frontdoor.Uppercase), + string(frontdoor.URLDecode), + string(frontdoor.URLEncode), + }, false), + }, + }, + }, + }, + }, + }, + }, + }, + + "managed_rule": { + Type: schema.TypeList, + MaxItems: 100, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "version": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "override": { + Type: schema.TypeList, + MaxItems: 100, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rule_group_name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "rule": { + Type: schema.TypeList, + MaxItems: 1000, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rule_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "action": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(frontdoor.Allow), + string(frontdoor.Block), + string(frontdoor.Log), + string(frontdoor.Redirect), + }, false), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + + "frontend_endpoint_ids": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "tags": tags.Schema(), + }, + } +} + +func resourceArmFrontDoorFirewallPolicyCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).frontdoor.FrontDoorsPolicyClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing args for Front Door Firewall Policy") + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + if requireResourcesToBeImported { + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("Error checking for present of existing Front Door Firewall Policy %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + if existing.ID != nil && *existing.ID != "" { + return tf.ImportAsExistsError("azurerm_frontdoor_firewall_policy", *existing.ID) + } + } + + location := azure.NormalizeLocation("Global") + enabled := frontdoor.PolicyEnabledStateDisabled + if d.Get("enabled").(bool) { + enabled = frontdoor.PolicyEnabledStateEnabled + } + mode := d.Get("mode").(string) + redirectUrl := d.Get("redirect_url").(string) + customBlockResponseStatusCode := d.Get("custom_block_response_status_code").(int) + customBlockResponseBody := d.Get("custom_block_response_body").(string) + customRules := d.Get("custom_rule").([]interface{}) + managedRules := d.Get("managed_rule").([]interface{}) + tags := d.Get("tags").(map[string]interface{}) + + frontdoorWebApplicationFirewallPolicy := frontdoor.WebApplicationFirewallPolicy{ + Name: utils.String(name), + Location: utils.String(location), + WebApplicationFirewallPolicyProperties: &frontdoor.WebApplicationFirewallPolicyProperties{ + PolicySettings: &frontdoor.PolicySettings{ + EnabledState: enabled, + Mode: frontdoor.PolicyMode(mode), + }, + CustomRules: expandArmFrontDoorFirewallCustomRules(customRules), + ManagedRules: expandArmFrontDoorFirewallManagedRules(managedRules), + }, + Tags: expandTags(tags), + } + + if redirectUrl != "" { + frontdoorWebApplicationFirewallPolicy.WebApplicationFirewallPolicyProperties.PolicySettings.RedirectURL = utils.String(redirectUrl) + } + if customBlockResponseBody != "" { + frontdoorWebApplicationFirewallPolicy.WebApplicationFirewallPolicyProperties.PolicySettings.CustomBlockResponseBody = utils.String(customBlockResponseBody) + } + if customBlockResponseStatusCode > 0 { + frontdoorWebApplicationFirewallPolicy.WebApplicationFirewallPolicyProperties.PolicySettings.CustomBlockResponseStatusCode = utils.Int32(int32(customBlockResponseStatusCode)) + } + + future, err := client.CreateOrUpdate(ctx, resourceGroup, name, frontdoorWebApplicationFirewallPolicy) + if err != nil { + return fmt.Errorf("Error creating Front Door Firewall policy %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for creation of Front Door Firewall %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error retrieving Front Door Firewall %q (Resource Group %q): %+v", name, resourceGroup, err) + } + if resp.ID == nil { + return fmt.Errorf("Cannot read Front Door Firewall %q (Resource Group %q) ID", name, resourceGroup) + } + d.SetId(*resp.ID) + + return resourceArmFrontDoorFirewallPolicyRead(d, meta) +} + +func resourceArmFrontDoorFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).frontdoor.FrontDoorsPolicyClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["frontdoorwebapplicationfirewallpolicies"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] Front Door Firewall Policy %q does not exist - removing from state", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error reading Front Door Firewall Policy %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resourceGroup) + + if location := resp.Location; location != nil { + d.Set("location", azure.NormalizeLocation(*location)) + } + + if properties := resp.WebApplicationFirewallPolicyProperties; properties != nil { + if policy := properties.PolicySettings; policy != nil { + + d.Set("enabled", policy.EnabledState == frontdoor.PolicyEnabledStateEnabled) + d.Set("mode", string(policy.Mode)) + d.Set("redirect_url", policy.RedirectURL) + d.Set("custom_block_response_status_code", policy.CustomBlockResponseStatusCode) + d.Set("custom_block_response_body", policy.CustomBlockResponseBody) + } + + if err := d.Set("custom_rule", flattenArmFrontDoorFirewallCustomRules(properties.CustomRules)); err != nil { + return fmt.Errorf("Error flattening `custom_rule`: %+v", err) + } + + if err := d.Set("managed_rule", flattenArmFrontDoorFirewallManagedRules(properties.ManagedRules)); err != nil { + return fmt.Errorf("Error flattening `managed_rule`: %+v", err) + } + + if err := d.Set("frontend_endpoint_ids", afd.FlattenFrontendEndpointLinkSlice(properties.FrontendEndpointLinks)); err != nil { + return fmt.Errorf("Error flattening `frontend_endpoint_ids`: %+v", err) + } + } + + return tags.FlattenAndSet(d, resp.Tags) +} + +func resourceArmFrontDoorFirewallPolicyDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).frontdoor.FrontDoorsPolicyClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + name := id.Path["frontdoorwebapplicationfirewallpolicies"] + + future, err := client.Delete(ctx, resourceGroup, name) + if err != nil { + if response.WasNotFound(future.Response()) { + return nil + } + return fmt.Errorf("Error deleting Front Door Firewall %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error waiting for deleting Front Door Firewall %q (Resource Group %q): %+v", name, resourceGroup, err) + } + } + + return nil +} + +func expandArmFrontDoorFirewallCustomRules(input []interface{}) *frontdoor.CustomRuleList { + if len(input) == 0 { + return nil + } + + output := make([]frontdoor.CustomRule, 0) + + for _, cr := range input { + custom := cr.(map[string]interface{}) + + enabled := frontdoor.CustomRuleEnabledStateDisabled + if custom["enabled"].(bool) { + enabled = frontdoor.CustomRuleEnabledStateEnabled + } + + name := custom["name"].(string) + priority := int32(custom["priority"].(int)) + ruleType := custom["type"].(string) + rateLimitDurationInMinutes := int32(custom["rate_limit_duration_in_minutes"].(int)) + rateLimitThreshold := int32(custom["rate_limit_threshold"].(int)) + matchConditions := custom["match_condition"].([]interface{}) + action := custom["action"].(string) + + customRule := frontdoor.CustomRule{ + Name: utils.String(name), + Priority: utils.Int32(priority), + EnabledState: enabled, + RuleType: frontdoor.RuleType(ruleType), + RateLimitDurationInMinutes: utils.Int32(rateLimitDurationInMinutes), + RateLimitThreshold: utils.Int32(rateLimitThreshold), + MatchConditions: expandArmFrontDoorFirewallMatchConditions(matchConditions), + Action: frontdoor.ActionType(action), + } + output = append(output, customRule) + } + + return &frontdoor.CustomRuleList{ + Rules: &output, + } +} + +func expandArmFrontDoorFirewallMatchConditions(input []interface{}) *[]frontdoor.MatchCondition { + if len(input) == 0 { + return nil + } + + result := make([]frontdoor.MatchCondition, 0) + + for _, v := range input { + match := v.(map[string]interface{}) + + matchVariable := match["match_variable"].(string) + selector := match["selector"].(string) + operator := match["operator"].(string) + negateCondition := match["negation_condition"].(bool) + matchValues := match["match_values"].([]interface{}) + transforms := match["transforms"].([]interface{}) + + matchCondition := frontdoor.MatchCondition{ + Operator: frontdoor.Operator(operator), + NegateCondition: &negateCondition, + MatchValue: utils.ExpandStringSlice(matchValues), + Transforms: expandArmFrontDoorFirewallTransforms(transforms), + } + + if matchVariable != "" { + matchCondition.MatchVariable = frontdoor.MatchVariable(matchVariable) + } + if selector != "" { + matchCondition.Selector = utils.String(selector) + } + + result = append(result, matchCondition) + } + + return &result +} + +func expandArmFrontDoorFirewallTransforms(input []interface{}) *[]frontdoor.TransformType { + if len(input) == 0 { + return nil + } + + result := make([]frontdoor.TransformType, 0) + for _, v := range input { + result = append(result, frontdoor.TransformType(v.(string))) + } + + return &result +} + +func expandArmFrontDoorFirewallManagedRules(input []interface{}) *frontdoor.ManagedRuleSetList { + if len(input) == 0 { + return nil + } + + managedRules := make([]frontdoor.ManagedRuleSet, 0) + + for _, mr := range input { + managedRule := mr.(map[string]interface{}) + + ruleType := managedRule["type"].(string) + version := managedRule["version"].(string) + overrides := managedRule["override"].([]interface{}) + + managedRuleSet := frontdoor.ManagedRuleSet{ + RuleSetType: utils.String(ruleType), + RuleSetVersion: utils.String(version), + } + + if ruleGroupOverrides := expandArmFrontDoorFirewallManagedRuleGroupOverride(overrides); ruleGroupOverrides != nil { + managedRuleSet.RuleGroupOverrides = ruleGroupOverrides + } + + managedRules = append(managedRules, managedRuleSet) + } + + return &frontdoor.ManagedRuleSetList{ + ManagedRuleSets: &managedRules, + } +} + +func expandArmFrontDoorFirewallManagedRuleGroupOverride(input []interface{}) *[]frontdoor.ManagedRuleGroupOverride { + if len(input) == 0 { + return nil + } + + managedRuleGroupOverrides := make([]frontdoor.ManagedRuleGroupOverride, 0) + for _, v := range input { + override := v.(map[string]interface{}) + + ruleGroupName := override["rule_group_name"].(string) + rules := override["rule"].([]interface{}) + + managedRuleGroupOverride := frontdoor.ManagedRuleGroupOverride{ + RuleGroupName: utils.String(ruleGroupName), + } + + if managedRuleOverride := expandArmFrontDoorFirewallRuleOverride(rules); managedRuleOverride != nil { + managedRuleGroupOverride.Rules = managedRuleOverride + } + + managedRuleGroupOverrides = append(managedRuleGroupOverrides, managedRuleGroupOverride) + } + + return &managedRuleGroupOverrides +} + +func expandArmFrontDoorFirewallRuleOverride(input []interface{}) *[]frontdoor.ManagedRuleOverride { + if len(input) == 0 { + return nil + } + + managedRuleOverrides := make([]frontdoor.ManagedRuleOverride, 0) + for _, v := range input { + rule := v.(map[string]interface{}) + + enabled := frontdoor.ManagedRuleEnabledStateDisabled + if rule["enabled"].(bool) { + enabled = frontdoor.ManagedRuleEnabledStateEnabled + } + ruleId := rule["rule_id"].(string) + action := rule["action"].(string) + + managedRuleOverride := frontdoor.ManagedRuleOverride{ + RuleID: utils.String(ruleId), + EnabledState: enabled, + Action: frontdoor.ActionType(action), + } + + managedRuleOverrides = append(managedRuleOverrides, managedRuleOverride) + } + + return &managedRuleOverrides +} + +func flattenArmFrontDoorFirewallCustomRules(input *frontdoor.CustomRuleList) []interface{} { + if input == nil || input.Rules == nil { + return make([]interface{}, 0) + } + + results := make([]interface{}, 0) + for _, r := range *input.Rules { + output := make(map[string]interface{}) + + output["name"] = r.Name + output["type"] = string(r.RuleType) + output["action"] = string(r.Action) + output["enabled"] = r.EnabledState == frontdoor.CustomRuleEnabledStateEnabled + output["match_condition"] = flattenArmFrontDoorFirewallMatchConditions(r.MatchConditions) + + if v := r.Priority; v != nil { + output["priority"] = int(*v) + } + + if v := r.RateLimitDurationInMinutes; v != nil { + output["rate_limit_duration_in_minutes"] = int(*v) + } + + if v := r.RateLimitThreshold; v != nil { + output["rate_limit_threshold"] = int(*v) + } + + results = append(results, output) + } + + return results +} + +func flattenArmFrontDoorFirewallMatchConditions(condition *[]frontdoor.MatchCondition) []interface{} { + if condition == nil { + return make([]interface{}, 0) + } + + results := make([]interface{}, 0) + for _, c := range *condition { + output := make(map[string]interface{}) + + output["match_variable"] = string(c.MatchVariable) + output["operator"] = string(c.Operator) + output["match_values"] = utils.FlattenStringSlice(c.MatchValue) + output["transforms"] = afd.FlattenTransformSlice(c.Transforms) + + if v := c.Selector; v != nil { + output["selector"] = *v + } + + if v := c.NegateCondition; v != nil { + output["negation_condition"] = *v + } + + results = append(results, output) + } + + return results +} + +func flattenArmFrontDoorFirewallManagedRules(input *frontdoor.ManagedRuleSetList) []interface{} { + if input == nil || input.ManagedRuleSets == nil { + return make([]interface{}, 0) + } + + results := make([]interface{}, 0) + for _, r := range *input.ManagedRuleSets { + output := make(map[string]interface{}) + + if v := r.RuleSetType; v != nil { + output["type"] = *v + } + + if v := r.RuleSetVersion; v != nil { + output["version"] = *v + } + + if v := r.RuleGroupOverrides; v != nil { + output["override"] = flattenArmFrontDoorFirewallOverrides(v) + } + + results = append(results, output) + } + + return results +} + +func flattenArmFrontDoorFirewallOverrides(groupOverride *[]frontdoor.ManagedRuleGroupOverride) []interface{} { + if groupOverride == nil { + return make([]interface{}, 0) + } + + results := make([]interface{}, 0) + for _, o := range *groupOverride { + output := make(map[string]interface{}) + + if v := o.RuleGroupName; v != nil { + output["rule_group_name"] = *v + } + + if rules := o.Rules; rules != nil { + output["rule"] = flattenArmFrontdoorFirewallRules(rules) + } + + results = append(results, output) + } + + return results +} + +func flattenArmFrontdoorFirewallRules(override *[]frontdoor.ManagedRuleOverride) []interface{} { + if override == nil { + return make([]interface{}, 0) + } + + results := make([]interface{}, 0) + for _, o := range *override { + output := make(map[string]interface{}) + + output["enabled"] = o.EnabledState == frontdoor.ManagedRuleEnabledStateEnabled + output["action"] = string(o.Action) + + if v := o.RuleID; v != nil { + output["rule_id"] = *v + } + + results = append(results, output) + } + + return results +} diff --git a/azurerm/resource_arm_front_door_firewall_policy_test.go b/azurerm/resource_arm_front_door_firewall_policy_test.go new file mode 100644 index 000000000000..275429e041a1 --- /dev/null +++ b/azurerm/resource_arm_front_door_firewall_policy_test.go @@ -0,0 +1,282 @@ +package azurerm + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMFrontDoorFirewallPolicy_basic(t *testing.T) { + resourceName := "azurerm_frontdoor_firewall_policy.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMFrontDoorFirewallPolicy_basic(ri, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFrontDoorFirewallPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFrontDoorFirewallPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("testAccFrontDoorWAF%d", ri)), + resource.TestCheckResourceAttr(resourceName, "mode", "Prevention"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMFrontDoorFirewallPolicy_update(t *testing.T) { + resourceName := "azurerm_frontdoor_firewall_policy.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMFrontDoorFirewallPolicy_update(ri, "", testLocation()) + configUpdate := testAccAzureRMFrontDoorFirewallPolicy_update(ri, testAccAzureRMFrontDoorFirewallPolicy_updateTemplate(), testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFrontDoorFirewallPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFrontDoorFirewallPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("testAccFrontDoorWAF%d", ri)), + resource.TestCheckResourceAttr(resourceName, "mode", "Prevention"), + ), + }, + { + Config: configUpdate, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFrontDoorFirewallPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("testAccFrontDoorWAF%d", ri)), + resource.TestCheckResourceAttr(resourceName, "mode", "Prevention"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.name", "Rule2"), + ), + }, + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFrontDoorFirewallPolicyExists(resourceName), + testCheckAzureRMFrontDoorFirewallPolicyAttrNotExists(resourceName, "custom_rule.1.name"), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("testAccFrontDoorWAF%d", ri)), + resource.TestCheckResourceAttr(resourceName, "mode", "Prevention"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAzureRMFrontDoorFirewallPolicy_complete(t *testing.T) { + resourceName := "azurerm_frontdoor_firewall_policy.test" + ri := tf.AccRandTimeInt() + config := testAccAzureRMFrontDoorFirewallPolicy_update(ri, testAccAzureRMFrontDoorFirewallPolicy_updateTemplate(), testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFrontDoorFirewallPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFrontDoorFirewallPolicyExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("testAccFrontDoorWAF%d", ri)), + resource.TestCheckResourceAttr(resourceName, "mode", "Prevention"), + resource.TestCheckResourceAttr(resourceName, "redirect_url", "https://www.contoso.com"), + resource.TestCheckResourceAttr(resourceName, "custom_block_response_status_code", "403"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.0.name", "Rule1"), + resource.TestCheckResourceAttr(resourceName, "custom_rule.1.name", "Rule2"), + resource.TestCheckResourceAttr(resourceName, "managed_rule.0.type", "DefaultRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule.1.type", "BotProtection"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testCheckAzureRMFrontDoorFirewallPolicyExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Front Door Firewall Policy not found: %s", resourceName) + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + client := testAccProvider.Meta().(*ArmClient).frontdoor.FrontDoorsPolicyClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + if resp, err := client.Get(ctx, resourceGroup, name); err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Front Door Firewall Policy %q (Resource Group %q) does not exist", name, resourceGroup) + } + return fmt.Errorf("Bad: Get on FrontDoorsPolicyClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMFrontDoorFirewallPolicyDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).frontdoor.FrontDoorsPolicyClient + ctx := testAccProvider.Meta().(*ArmClient).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_frontdoor_firewall_policy" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + if resp, err := client.Get(ctx, resourceGroup, name); err != nil { + if !utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Get on FrontDoorsPolicyClient: %+v", err) + } + } + + return nil + } + + return nil +} + +func testCheckAzureRMFrontDoorFirewallPolicyAttrNotExists(name string, attribute string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + if testAttr := rs.Primary.Attributes[attribute]; testAttr != "" { + return fmt.Errorf("Attribute still exists: %s", attribute) + } + + return nil + } +} + +func testAccAzureRMFrontDoorFirewallPolicy_basic(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "testAccRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_frontdoor_firewall_policy" "test" { + name = "testAccFrontDoorWAF%[1]d" + resource_group_name = azurerm_resource_group.test.name +} +`, rInt, location) +} + +func testAccAzureRMFrontDoorFirewallPolicy_updateTemplate() string { + return fmt.Sprintf(` + custom_rule { + name = "Rule2" + enabled = true + priority = 2 + rate_limit_duration_in_minutes = 1 + rate_limit_threshold = 10 + type = "MatchRule" + action = "Block" + + match_condition { + match_variable = "RemoteAddr" + operator = "IPMatch" + negation_condition = false + match_values = ["192.168.1.0/24"] + } + + match_condition { + match_variable = "RequestHeader" + selector = "UserAgent" + operator = "Contains" + negation_condition = false + match_values = ["windows"] + transforms = ["Lowercase", "Trim"] + } + } +`) +} + +func testAccAzureRMFrontDoorFirewallPolicy_update(rInt int, sTemplate string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "testAccRG-%[1]d" + location = "%[3]s" +} + +resource "azurerm_frontdoor_firewall_policy" "test" { + name = "testAccFrontDoorWAF%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + enabled = true + mode = "Prevention" + redirect_url = "https://www.contoso.com" + custom_block_response_status_code = 403 + custom_block_response_body = "PGh0bWw+CjxoZWFkZXI+PHRpdGxlPkhlbGxvPC90aXRsZT48L2hlYWRlcj4KPGJvZHk+CkhlbGxvIHdvcmxkCjwvYm9keT4KPC9odG1sPg==" + + custom_rule { + name = "Rule1" + enabled = true + priority = 1 + rate_limit_duration_in_minutes = 1 + rate_limit_threshold = 10 + type = "MatchRule" + action = "Block" + + match_condition { + match_variable = "RemoteAddr" + operator = "IPMatch" + negation_condition = false + match_values = ["192.168.1.0/24", "10.0.0.0/24"] + } + } + + %[2]s + + managed_rule { + type = "DefaultRuleSet" + version = "preview-0.1" + + override { + rule_group_name = "PHP" + + rule { + rule_id = "933111" + enabled = false + action = "Block" + } + } + } + + managed_rule { + type = "BotProtection" + version = "preview-0.1" + } +} +`, rInt, sTemplate, location) +} diff --git a/azurerm/resource_arm_front_door_test.go b/azurerm/resource_arm_front_door_test.go index 2b5acd763573..3d86db25ee1b 100644 --- a/azurerm/resource_arm_front_door_test.go +++ b/azurerm/resource_arm_front_door_test.go @@ -208,6 +208,34 @@ func TestAccAzureRMFrontDoor_complete(t *testing.T) { }) } +func TestAccAzureRMFrontDoor_waf(t *testing.T) { + resourceName := "azurerm_frontdoor.test" + ri := tf.AccRandTimeInt() + rs := strings.ToLower(acctest.RandString(5)) + config := testAccAzureRMFrontDoor_waf(ri, rs, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMFrontDoorDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMFrontDoorExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("testAccFrontDoor-%d", ri)), + resource.TestCheckResourceAttrSet(resourceName, "frontend_endpoint.0.web_application_firewall_policy_link_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testCheckAzureRMFrontDoorExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] @@ -394,3 +422,64 @@ resource "azurerm_frontdoor" "test" { } `, rInt, rString, location) } + +func testAccAzureRMFrontDoor_waf(rInt int, rString string, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "testAccRG-%[1]d" + location = "%[3]s" +} + +resource "azurerm_frontdoor_firewall_policy" "test" { + name = "accTestWAF%[1]d" + resource_group_name = azurerm_resource_group.test.name + mode = "Prevention" +} + +resource "azurerm_frontdoor" "test" { + name = "testAccFrontDoor-%[1]d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + enforce_backend_pools_certificate_name_check = false + + routing_rule { + name = "testAccRoutingRule1-%[1]d" + accepted_protocols = ["Http", "Https"] + patterns_to_match = ["/*"] + frontend_endpoints = ["testAccFrontendEndpoint1-%[1]d"] + forwarding_configuration { + forwarding_protocol = "MatchRequest" + backend_pool_name = "testAccBackendBing-%[1]d" + } + } + + backend_pool_load_balancing { + name = "testAccLoadBalancingSettings1-%[1]d" + } + + backend_pool_health_probe { + name = "testAccHealthProbeSetting1-%[1]d" + } + + backend_pool { + name = "testAccBackendBing-%[1]d" + backend { + host_header = "www.bing.com" + address = "www.bing.com" + http_port = 80 + https_port = 443 + } + + load_balancing_name = "testAccLoadBalancingSettings1-%[1]d" + health_probe_name = "testAccHealthProbeSetting1-%[1]d" + } + + frontend_endpoint { + name = "testAccFrontendEndpoint1-%[1]d" + host_name = "testAccFrontDoor-%[1]d.azurefd.net" + custom_https_provisioning_enabled = false + web_application_firewall_policy_link_id = azurerm_frontdoor_firewall_policy.test.id + } +} +`, rInt, rString, location) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 36a93fc9bbf5..43997c1e7dc9 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -1029,6 +1029,18 @@ +