Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azurerm_windows_function_app does not remove ip_restriction and scm_ip_restriction rules #18793

Closed
1 task done
mg-8 opened this issue Oct 17, 2022 · 3 comments · Fixed by #20987
Closed
1 task done
Labels

Comments

@mg-8
Copy link

mg-8 commented Oct 17, 2022

Is there an existing issue for this?

  • I have searched the existing issues

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Terraform Version

1.3.2

AzureRM Provider Version

3.26.0

Affected Resource(s)/Data Source(s)

azurerm_windows_function_app

Terraform Configuration Files

**Root Module**
locals {
  function_apps = {
    primary = {
      function_app = {
        name                        = "test-func-01"
        use_32_bit_worker           = true
        user_assigned_identity_ref  = "func_one"
        scm_use_main_ip_restriction = true
        virtual_network_integration = {
          subnet_name = "func-app-asp-subnet"
        }
      }
      storage_account = {
        name             = "teststfunc01"
        tier             = "Standard"
        replication_type = "LRS"
        ip_rules = []
        container_names = []
        private_endpoint = {
          sub_resource_names = ["blob", "table", "file"]
        }
      }
      network = {
        virtual_network_name = "test-vnet"
        resource_group_name  = "test-network-rg"
        subnet_name          = "test-subnet"
      }
      ip_restrictions = [
        {
          action      = "Allow"
          name        = "AzureDevOps"
          priority    = 10
          service_tag = "AzureDevOps"
        },
      ]
      scm_ip_restrictions = [
        {
          action      = "Allow"
          name        = "Test"
          priority    = 10
          service_tag = "AzureDevOps"
        },
      ]
    }    
  }
}

module "function_app" {
  source = "./modules/function-app"

  for_each = local.function_apps

  resource_group = {
    name     = azurerm_resource_group.this.name
    location = azurerm_resource_group.this.location
  }

  function_app = {
    name                      = each.value.function_app.name
    use_32_bit_worker         = each.value.function_app.use_32_bit_worker
    app_service_plan_id       = azurerm_service_plan.this["function_app"].id
    user_assigned_identity_id = azurerm_user_assigned_identity.this[each.value.function_app.user_assigned_identity_ref].id

    application_insights = {
      connection_string   = azurerm_application_insights.this.connection_string
      instrumentation_key = azurerm_application_insights.this.instrumentation_key
    }
    ip_restriction              = each.value.ip_restrictions
    scm_ip_restriction      = each.value.scm_ip_restrictions
  }

  storage_account = {
    name                     = each.value.storage_account.name
    account_tier             = each.value.storage_account.tier
    account_replication_type = each.value.storage_account.replication_type
    container_names          = each.value.storage_account.container_names
    ip_rules                 = each.value.storage_account.ip_rules
  }

  virtual_network = {
    name                    = each.value.network.virtual_network_name
    resource_group_name     = each.value.network.resource_group_name
    integration_subnet_name = each.value.function_app.virtual_network_integration.subnet_name

    private_endpoint = {
      subnet_name       = each.value.network.subnet_name
      subresource_names = each.value.storage_account.private_endpoint.sub_resource_names
    }
  }
}


**Child Module**
variable "resource_group" {
  type = object({
    name     = string
    location = string
  })
}

variable "function_app" {
  type = object({
    name                             = string
    use_32_bit_worker                = optional(bool, false)
    runtime_scale_monitoring_enabled = optional(bool, true)
    pre_warmed_instance_count        = optional(number, 1)
    app_service_plan_id              = string
    user_assigned_identity_id        = string
    application_insights = optional(object({
      connection_string   = string
      instrumentation_key = string
    }))
    ip_restriction = optional(list(object({
      action                    = string
      priority                  = number
      name                      = optional(string)
      ip_address                = optional(string)  
      service_tag               = optional(string)
      virtual_network_subnet_id = optional(string)
      headers = optional(object({
        x_azure_fdid      = optional(list(string))
        x_fd_health_probe = optional(list(string))
        x_forwarded_for   = optional(list(string))
        x_forwarded_host  = optional(list(string))
      }))
    })))
    scm_ip_restriction = optional(list(object({
      action                    = string
      priority                  = number
      name                      = optional(string)
      ip_address                = optional(string)  
      service_tag               = optional(string)
      virtual_network_subnet_id = optional(string)
      headers = optional(object({
        x_azure_fdid      = optional(list(string))
        x_fd_health_probe = optional(list(string))
        x_forwarded_for   = optional(list(string))
        x_forwarded_host  = optional(list(string))
      }))
    })))
    scm_use_main_ip_restriction = optional(bool, false)
  })
}

variable "storage_account" {
  type = object({
    name                     = string
    account_tier             = string
    account_replication_type = string
    container_names          = list(string)
    ip_rules                 = list(string)
  })
}

variable "virtual_network" {
  type = object({
    name                    = string
    resource_group_name     = string
    integration_subnet_name = string
    private_endpoint = object({
      subnet_name       = string
      subresource_names = list(string)
    })
  })
}

resource "azurerm_storage_account" "this" {
  name                             = var.storage_account.name
  resource_group_name              = var.resource_group.name
  location                         = var.resource_group.location
  account_tier                     = var.storage_account.account_tier
  account_replication_type         = var.storage_account.account_replication_type
  allow_nested_items_to_be_public  = false
  cross_tenant_replication_enabled = false
  enable_https_traffic_only        = true
  default_to_oauth_authentication  = true
  public_network_access_enabled    = true

  lifecycle {
    ignore_changes = [
      tags,
    ]
  }
}

resource "azurerm_storage_container" "this" {
  for_each = toset(var.storage_account.container_names)

  name                  = lower(each.key)
  storage_account_name  = azurerm_storage_account.this.name
  container_access_type = "private"
}

resource "azurerm_storage_account_network_rules" "this" {
  storage_account_id = azurerm_storage_account.this.id
  default_action     = "Deny"
  ip_rules           = var.storage_account.ip_rules
  bypass = [
    "Metrics",
    "Logging",
    "AzureServices"
  ]

  depends_on = [
    azurerm_windows_function_app.this,
  ]
}

data "azurerm_subnet" "this" {
  name                 = var.virtual_network.private_endpoint.subnet_name
  virtual_network_name = var.virtual_network.name
  resource_group_name  = var.virtual_network.resource_group_name
}

resource "azurerm_private_endpoint" "storage_account" {
  for_each = toset(var.virtual_network.private_endpoint.subresource_names)

  name                          = format("%s-%s-pe", azurerm_storage_account.this.name, each.key)
  resource_group_name           = var.resource_group.name
  location                      = var.resource_group.location
  subnet_id                     = data.azurerm_subnet.this.id
  custom_network_interface_name = format("%s-%s-pe-nic", azurerm_storage_account.this.name, each.key)

  private_service_connection {
    name                           = format("%s-%s-pe-conn", azurerm_storage_account.this.name, each.key)
    private_connection_resource_id = azurerm_storage_account.this.id
    is_manual_connection           = false
    subresource_names              = [each.key]
  }

  lifecycle {
    ignore_changes = [
      tags,
      private_dns_zone_group,
    ]
  }
}

data "azurerm_subnet" "vnet_integration" {
  name                 = var.virtual_network.integration_subnet_name
  virtual_network_name = var.virtual_network.name
  resource_group_name  = var.virtual_network.resource_group_name
}

resource "azurerm_windows_function_app" "this" {
  name                       = var.function_app.name
  resource_group_name        = var.resource_group.name
  location                   = var.resource_group.location
  service_plan_id            = var.function_app.app_service_plan_id
  storage_account_name       = azurerm_storage_account.this.name
  storage_account_access_key = sensitive(azurerm_storage_account.this.primary_access_key)
  https_only                 = true
  client_certificate_enabled = true
  client_certificate_mode    = "Required"
  virtual_network_subnet_id  = data.azurerm_subnet.vnet_integration.id

  identity {
    type = "UserAssigned"
    identity_ids = [
      var.function_app.user_assigned_identity_id,
    ]
  }

  site_config {
    pre_warmed_instance_count              = var.function_app.pre_warmed_instance_count
    http2_enabled                          = true
    use_32_bit_worker                      = var.function_app.use_32_bit_worker
    vnet_route_all_enabled                 = true
    runtime_scale_monitoring_enabled       = var.function_app.runtime_scale_monitoring_enabled
    application_insights_connection_string = sensitive(var.function_app.application_insights.connection_string)
    application_insights_key               = sensitive(var.function_app.application_insights.instrumentation_key)
    scm_use_main_ip_restriction = var.function_app.scm_use_main_ip_restriction

    dynamic "ip_restriction" {
      for_each = { for k, v in var.function_app.ip_restriction == null ? [] : var.function_app.ip_restriction : k => v }

      content {
        action                    = ip_restriction.value.action
        ip_address                = ip_restriction.value.ip_address
        name                      = ip_restriction.value.name
        priority                  = ip_restriction.value.priority
        service_tag               = ip_restriction.value.service_tag
        virtual_network_subnet_id = ip_restriction.value.virtual_network_subnet_id

        dynamic "headers" {
          for_each = ip_restriction.value.headers == null ? [] : [1]

          content {
            x_azure_fdid      = ip_restriction.value.headers.x_azure_fdid
            x_fd_health_probe = ip_restriction.value.headers.x_fd_health_probe
            x_forwarded_for   = ip_restriction.value.headers.x_forwarded_for
            x_forwarded_host  = ip_restriction.value.headers.x_forwarded_host
          }
        }
      }
    }

    dynamic "scm_ip_restriction" {
      for_each = { for k, v in var.function_app.scm_ip_restriction == null ? [] : var.function_app.scm_ip_restriction : k => v }

      content {
        action                    = scm_ip_restriction.value.action
        ip_address                = scm_ip_restriction.value.ip_address
        name                      = scm_ip_restriction.value.name
        priority                  = scm_ip_restriction.value.priority
        service_tag               = scm_ip_restriction.value.service_tag
        virtual_network_subnet_id = scm_ip_restriction.value.virtual_network_subnet_id

        dynamic "headers" {
          for_each = scm_ip_restriction.value.headers == null ? [] : [1]

          content {
            x_azure_fdid      = scm_ip_restriction.value.headers.x_azure_fdid
            x_fd_health_probe = scm_ip_restriction.value.headers.x_fd_health_probe
            x_forwarded_for   = scm_ip_restriction.value.headers.x_forwarded_for
            x_forwarded_host  = scm_ip_restriction.value.headers.x_forwarded_host
          }
        }
      }
    }
  }

  lifecycle {
    ignore_changes = [
      tags,
      app_settings,
    ]
  }
}

resource "azurerm_private_endpoint" "this" {
  name                          = format("%s-%s", azurerm_windows_function_app.this.name, "sites-pe")
  resource_group_name           = var.resource_group.name
  location                      = var.resource_group.location
  subnet_id                     = data.azurerm_subnet.this.id
  custom_network_interface_name = format("%s-%s", azurerm_windows_function_app.this.name, "sites-pe-nic")

  private_service_connection {
    name                           = format("%s-%s", azurerm_windows_function_app.this.name, "sites-pe-conn")
    private_connection_resource_id = azurerm_windows_function_app.this.id
    is_manual_connection           = false
    subresource_names              = ["sites"]
  }

  lifecycle {
    ignore_changes = [
      tags,
      private_dns_zone_group,
    ]
  }
}

Debug Output/Panic Output

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Expected Behaviour

Remove ip_restriction and scm_ip_restriction rules

Actual Behaviour

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Steps to Reproduce

locals {
function_apps = {
primary = {
function_app = {
name = "test-func-01"
use_32_bit_worker = true
user_assigned_identity_ref = "func_one"
scm_use_main_ip_restriction = true
virtual_network_integration = {
subnet_name = "func-app-asp-subnet"
}
}
storage_account = {
name = "teststfunc01"
tier = "Standard"
replication_type = "LRS"
ip_rules = []
container_names = []
private_endpoint = {
sub_resource_names = ["blob", "table", "file"]
}
}
network = {
virtual_network_name = "test-vnet"
resource_group_name = "test-network-rg"
subnet_name = "test-subnet"
}
ip_restrictions = [
#{
#action = "Allow"
#name = "AzureDevOps"
#priority = 10
#service_tag = "AzureDevOps"
#},
]
scm_ip_restrictions = [
#{
#action = "Allow"
#name = "Test"
#priority = 10
#service_tag = "AzureDevOps"
#},
]
}
}
}

terraform plan
terraform apply

Important Factoids

No response

References

No response

@mg-8 mg-8 added the bug label Oct 17, 2022
@github-actions github-actions bot removed the bug label Oct 17, 2022
@mg-8 mg-8 changed the title azurerm_windows_function_app does not remove ip_restriction azurerm_windows_function_app does not remove ip_restriction rules Oct 17, 2022
@mg-8 mg-8 changed the title azurerm_windows_function_app does not remove ip_restriction rules azurerm_windows_function_app does not remove ip_restriction and scm_ip_restriction rules Oct 17, 2022
@xiaxyi
Copy link
Contributor

xiaxyi commented Oct 19, 2022

Thanks @mg-8 for raising this issue, by saying TF not remove the ip restrictions. are you referring to the behavior that the config cannot be updated or removed?

@mg-8
Copy link
Author

mg-8 commented Oct 21, 2022

Thanks @mg-8 for raising this issue, by saying TF not remove the ip restrictions. are you referring to the behavior that the config cannot be updated or removed?

@xiaxyi that's somewhat correct:

a) it does not remove the set config
b) update existing config works
c) if you configure two rules and remove only one rule then it works fine but you will not be able to remove all the rules/config as it does not detect any changes

I hope this helps?

@github-actions
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.