hashicorp / terraform-provider-azurerm

Terraform provider for Azure Resource Manager
https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
Mozilla Public License 2.0
4.53k stars 4.61k forks source link

Support for azure_function_app when Default Storage IP Rule is Deny #13233

Open derekrprice opened 3 years ago

derekrprice commented 3 years ago

Community Note

Description

Issue #3522 isn't completely solved. When the storage container already exists with a default network rule of "Deny", the function app cannot be created. The Azure API returns a 403 error in that case during the creation attempt. I was able to work around this by temporarily setting the default network access rule for the storage account to "Allow", then re-running Terraform and allowing it to set the default rule back to Deny. Unless Terraform can somehow lookup the possible IPs for a function app before it is created and allow those, then I expect that it would have to do something similar, though I see that this certainly doesn't seem like an elegant workaround.

New or Affected Resource(s)

Potential Terraform Configuration

resource "azurerm_storage_account" "bug-demo" {
  name                = "bugdemo"
  resource_group_name = "mygroup"
  location            = "useast"

  account_tier                      = "Standard"
  account_kind                      = "StorageV2"
  account_replication_type          = "LRS"
  enable_https_traffic_only         = true
}

resource "azurerm_function_app" "tdp-logs" {
  name                      = "bug-demo"
  location                  = "useast"
  resource_group_name       = "mygroup"
  app_service_plan_id       = azurerm_app_service_plan.bug-demo.id
  storage_connection_string = azurerm_storage_account.bug-demo.primary_connection_string
  enable_builtin_logging    = "true"
  https_only                = "true"
  version                   = "~3"
}

locals {
  function_app_ips = split(",", azurerm_function_app.bug-demo.outbound_ip_addresses)
}

resource "azurerm_storage_account_network_rules" "bug-demo" {
  resource_group_name       = "mygroup"
  storage_account_name      = azurerm_storage_account.bug-demo.name

  default_action             = "Deny"
  ip_rules                   = concat(var.other_allowed_ips, local.function_app_ips)

  bypass                     = ["AzureServices", "Metrics"]
}

References

derekrprice commented 3 years ago

Just another note, and this probably deserves it's own ticket, but when first creating a azurerm_storage_account_network_rules, presumably since it shares an Azure resource ID with the azurerm_storage_account that was created earlier in the script, I get an error about it already existing and I needed to import it before continuing.

aristosvo commented 3 years ago

@derekrprice Can you explain how this could be done outside Terraform? It seems like an architectural problem, as you IP is definitely unknown before you create your azurerm_function_app, but you need your IP before you can create your azurerm_function_app.

The best solution would probably be to connect your azurerm_function_app to a VNet with VNet rules. Would that work?

derekrprice commented 3 years ago

I don't see an option to connect a vnet that is part of the azurerm_function_app resource. The doc says to use a azurerm_app_service_virtual_network_swift_connection to connect a function app to a vnet, but that resource depends on the function app already having been created as well, and so will suffer from the same bootstrapping problem. If the azurerm_function_app resource itself did have a vnet_connection attribute, that would probably solve this nicely.

We got here since we have a pre-existing function app which we manage ourselves inside AKS via the Azure Functions docker image. I won't get into the nitty gritty details, but suffice to say that we are migrating portions of the service to a fully managed Functions App. We wanted the new app instance to share the pre-existing storage in order to run in parallel with the original for a period of time, but that storage already had the Deny All default rule in place.

Anyhow, like I said, I managed to work around the issue by setting the storage IP rules to default to Allow temporarily via the portal. After that, Terraform was able to create the function app, restore the Deny rule, and add the new function IPs to the rule. Without the manual intervention, it just blocked creation of the function app, and that seemed to violate the ideal of Terraform simply reaching the (perfectly valid) state described by its configuration files but maybe that is too much smarts to expect from the tool, or maybe MS could be convinced to fix this in the API. I'm not sure why the storage permissions are required at function app create time anyhow since they shouldn't be needed until run time. Just wanted to point out that the problem wasn't fixed, at least not for pre-existing storage and network rules. I didn't intend to assert that the problem is easily solvable or even that Terraform is the right place for the solution, but I thought it was at least worth documenting and running past you and the gestalt.

eltimmo commented 2 years ago

Hi, you can solve this as follows. The first three steps allow the function to created as the storage is open, also terraform can figure out these dependences for us. For step 4 we just need to make sure the function is at least created before that happens the rules are added. This worked for me, hope this helps.

derekrprice commented 2 years ago

Yeah, this is easy enough if your storage has the default Allow rule, even without any vnet integration, just like my original configuration works when I temporarily set the default rule to Allow via the portal and re-run Terraform. The bootstrapping problem occurs when trying to build the initial function app if your storage already has a default Deny rule.

mhtonysantaferra commented 2 years ago

My team is running into this exact problem.

We cannot use the insecure work around to have it create due to the nature of software we are running.