Continuing my journey of TerraForm with Microsoft’s Azure cloud, I needed to create some blob storage with NFS enabled (which currently has to be done at the storage account level and can only be turned on or off at account creation time).

This is slightly complicated because NFSv3 enabled storage accounts cannot allow public network access, preventing azurerm_storage_container from working, so we have to use an ARM template to deploy as a workaround.

Firstly the standard resource group definition:

resource "azurerm_resource_group" "storage" {
  name     = "RG-STORAGE-001"
  location = "West Europe"

  tags = merge(var.base_tags, var.tags_live)
}

Next, the storage account - note that you must set is_hns_enabled to true and enable_https_traffic_only to false in order to enable NFS support. The subnets were defined as in my previous post, these rules allow access to the storage account (including its NFS shares) only from the subnets listed:

resource "azurerm_storage_account" "system" {
  name = "stasystem001"
  resource_group_name = azurerm_resource_group.storage.name
  location = azurerm_resource_group.storage.location
  account_tier = "Standard"
  account_replication_type = "ZRS"
  min_tls_version = "TLS1_2"

  is_hns_enabled = true
  enable_https_traffic_only = false
  nfsv3_enabled = true

  network_rules {
    default_action             = "Deny"
    virtual_network_subnet_ids = [
      azurerm_subnet.access.id,
      azurerm_subnet.devtest.id,
      azurerm_subnet.production.id
    ]
  }

  tags = merge(var.base_tags, var.tags_live)
}

One thing not covered by my previous posts are the private DNS zones for setting up private endpoint connections to various services correctly:

resource "azurerm_private_dns_zone" "blob_storage" {
  name                = "privatelink.blob.core.windows.net"
  resource_group_name = azurerm_resource_group.network.name
  tags = merge(var.base_tags, var.tags_live)
}

resource "azurerm_private_dns_zone" "file_storage" {
  name                = "privatelink.file.core.windows.net"
  resource_group_name = azurerm_resource_group.network.name
  tags = merge(var.base_tags, var.tags_live)
}

resource "azurerm_private_dns_zone" "mariadb" {
  name                = "privatelink.mariadb.database.azure.com"
  resource_group_name = azurerm_resource_group.network.name
  tags = merge(var.base_tags, var.tags_live)
}

resource "azurerm_private_dns_zone" "cosmos_mongo" {
  name                = "privatelink.mongo.cosmos.azure.com"
  resource_group_name = azurerm_resource_group.network.name
  tags = merge(var.base_tags, var.tags_live)
}

Next I create a private endpoint and enable Azure defender:

resource "azurerm_private_endpoint" "storage_system" {
  name                = "EP-HPC-STASYSTEM-001"
  location            = azurerm_resource_group.storage.location
  resource_group_name = azurerm_resource_group.storage.name
  subnet_id           = azurerm_subnet.private.id

  private_service_connection {
    name                           = "pc-stasystem-001"
    private_connection_resource_id = azurerm_storage_account.system.id
    is_manual_connection           = false
    subresource_names = ["blob"]
  }

  private_dns_zone_group {
    name = "stasystem001"
    private_dns_zone_ids = [azurerm_private_dns_zone.blob_storage.id]
  }

  tags = merge(var.base_tags, var.tags_live)
}

resource "azurerm_advanced_threat_protection" "storage_system" {
  target_resource_id = azurerm_storage_account.system.id
  enabled            = true
}

Finally the ARM template for a blob container:

resource "azurerm_template_deployment" "yum-storage" {
  name = "yum-storage-deployment"
  depends_on = [ azurerm_storage_account.system ]
  resource_group_name = azurerm_resource_group.storage.name
  deployment_mode = "Incremental"

  parameters = {
    storage_account_name = azurerm_storage_account.system.name
    container_name = "yum-repos"
  }

  template_body  = <<EOF
{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "storage_account_name": {
            "type": "string"
        },
        "container_name": {
            "type": "string"
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
            "name": "[concat(parameters('storage_account_name'), '/default/', parameters('container_name'))]",
            "apiVersion": "2021-02-01",
            "properties": {"publicAccess": "None"}
        }
    ]
}
EOF
}