Skill v1.0.1
currentAutomated scan100/100+5 new
name: terraform-azure description: Provision Azure infrastructure with Terraform. Configure providers, manage state, and deploy resources. Use when implementing IaC for Azure. category: devops risk: critical source: https://github.com/BagelHole/DevOps-Security-Agent-Skills source_repo: BagelHole/DevOps-Security-Agent-Skills source_type: community date_added: '2026-09-20' license: MIT license_source: https://github.com/BagelHole/DevOps-Security-Agent-Skills/blob/main/LICENSE compatibility: Requires the relevant OS/platform tooling and privileged access where noted. Docs-only; helper scripts and templates not bundled. metadata: author: devops-skills version: '1.0'
Terraform Azure
Provision and manage Azure infrastructure with Terraform using the AzureRM provider. Covers provider configuration, remote state, resource groups, VNets, AKS, Key Vault, complete .tf file examples, and production workflows.
Prerequisites
# Prerequisite: reviewed, organization-approved Terraform package installed.# Verify installationterraform version# Install Azure CLI and login# Prerequisite: organization-approved Azure CLI installed from a verified signed package.az versionaz loginaz account set --subscription "my-subscription-id"# Create storage account for remote stateaz group create --name tfstate-rg --location eastusTFSTATE_ACCOUNT="tfstate$(openssl rand -hex 4)"az storage account create \--name "$TFSTATE_ACCOUNT" \--resource-group tfstate-rg \--sku Standard_LRS \--encryption-services blobaz storage container create \--name tfstate \--account-name "$TFSTATE_ACCOUNT" --auth-mode login
These historical AzureRM3.x/AzureAD2.x fragments must be evaluated against a pinned compatible provider/lockfile; do not silently upgrade to latest. Confirm subscription/tenant and use short-lived least-privilege credentials. Provisioning/state changes require an approved target, cost review and recoverable plan. Restrict/encrypt/version the backend and grant intended Blob data roles; sensitive only redacts display, not secrets in state/plans. Save artifacts privately and never commit credentials/state.
Provider Configuration
providers.tf
terraform {required_version = ">= 1.5.0"required_providers {azurerm = {source = "hashicorp/azurerm"version = "~> 3.80"}azuread = {source = "hashicorp/azuread"version = "~> 2.47"}random = {source = "hashicorp/random"version = "~> 3.6"}}backend "azurerm" {resource_group_name = "tfstate-rg"storage_account_name = "REPLACE_WITH_CREATED_ACCOUNT"use_azuread_auth = truecontainer_name = "tfstate"key = "prod.terraform.tfstate"}}provider "azurerm" {features {key_vault {purge_soft_delete_on_destroy = falserecover_soft_deleted_key_vaults = true}resource_group {prevent_deletion_if_contains_resources = true}}# Optional: use a specific subscription# subscription_id = var.subscription_id}provider "azuread" {}
variables.tf
variable "environment" {description = "Environment name (dev, staging, prod)"type = stringvalidation {condition = contains(["dev", "staging", "prod"], var.environment)error_message = "Environment must be dev, staging, or prod."}}variable "location" {description = "Azure region for all resources"type = stringdefault = "eastus"}variable "project_name" {description = "Project name used in resource naming"type = stringdefault = "myapp"}variable "tags" {description = "Tags applied to all resources"type = map(string)default = {}}variable "sql_admin_password" {description = "SQL Server admin password"type = stringsensitive = true}variable "allowed_ip_ranges" {description = "Approved Key Vault administrator IP ranges"type = list(string)default = []}variable "kubernetes_version" {description = "Explicit AKS version verified supported for the target region"type = string}variable "aks_admin_group_id" {description = "Azure AD group ID for AKS admin access"type = string}locals {name_prefix = "${var.project_name}-${var.environment}"common_tags = merge(var.tags, {environment = var.environmentproject = var.project_namemanaged_by = "terraform"})}
terraform.tfvars (per environment)
# terraform.prod.tfvarsenvironment = "prod"location = "eastus"project_name = "myapp"aks_admin_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"tags = {cost_center = "engineering"owner = "platform-team"}
Resource Group
resource-group.tf
resource "azurerm_resource_group" "main" {name = "${local.name_prefix}-rg"location = var.locationtags = local.common_tags}
Virtual Network
network.tf
resource "azurerm_virtual_network" "main" {name = "${local.name_prefix}-vnet"location = azurerm_resource_group.main.locationresource_group_name = azurerm_resource_group.main.nameaddress_space = ["10.0.0.0/16"]tags = local.common_tags}resource "azurerm_subnet" "aks" {name = "aks-subnet"resource_group_name = azurerm_resource_group.main.namevirtual_network_name = azurerm_virtual_network.main.nameaddress_prefixes = ["10.0.0.0/22"]service_endpoints = ["Microsoft.KeyVault"]}resource "azurerm_subnet" "app" {name = "app-subnet"resource_group_name = azurerm_resource_group.main.namevirtual_network_name = azurerm_virtual_network.main.nameaddress_prefixes = ["10.0.8.0/24"]service_endpoints = ["Microsoft.KeyVault"]}resource "azurerm_subnet" "data" {name = "data-subnet"resource_group_name = azurerm_resource_group.main.namevirtual_network_name = azurerm_virtual_network.main.nameaddress_prefixes = ["10.0.9.0/24"]private_endpoint_network_policies_enabled = true}resource "azurerm_network_security_group" "app" {name = "${local.name_prefix}-app-nsg"location = azurerm_resource_group.main.locationresource_group_name = azurerm_resource_group.main.namesecurity_rule {name = "AllowHTTPS"priority = 100direction = "Inbound"access = "Allow"protocol = "Tcp"source_port_range = "*"destination_port_range = "443"source_address_prefix = "*"destination_address_prefix = "*"}tags = local.common_tags}resource "azurerm_subnet_network_security_group_association" "app" {subnet_id = azurerm_subnet.app.idnetwork_security_group_id = azurerm_network_security_group.app.id}
AKS Cluster
aks.tf
resource "azurerm_log_analytics_workspace" "aks" {name = "${local.name_prefix}-law"location = azurerm_resource_group.main.locationresource_group_name = azurerm_resource_group.main.namesku = "PerGB2018"retention_in_days = 30tags = local.common_tags}resource "azurerm_kubernetes_cluster" "main" {name = "${local.name_prefix}-aks"location = azurerm_resource_group.main.locationresource_group_name = azurerm_resource_group.main.namedns_prefix = "${var.project_name}-${var.environment}"kubernetes_version = var.kubernetes_version # verified supported in regiondefault_node_pool {name = "system"vm_size = "Standard_D4s_v5"enable_auto_scaling = truemin_count = 2max_count = 5zones = [1, 2, 3]vnet_subnet_id = azurerm_subnet.aks.idos_disk_size_gb = 128os_disk_type = "Managed"max_pods = 50node_labels = {role = "system"}}identity {type = "SystemAssigned"}network_profile {network_plugin = "azure"network_policy = "calico"service_cidr = "10.1.0.0/16"dns_service_ip = "10.1.0.10"load_balancer_sku = "standard"}azure_active_directory_role_based_access_control {managed = trueazure_rbac_enabled = trueadmin_group_object_ids = [var.aks_admin_group_id]}oms_agent {log_analytics_workspace_id = azurerm_log_analytics_workspace.aks.id}key_vault_secrets_provider {secret_rotation_enabled = truesecret_rotation_interval = "2m"}tags = local.common_tags}resource "azurerm_kubernetes_cluster_node_pool" "app" {name = "app"kubernetes_cluster_id = azurerm_kubernetes_cluster.main.idvm_size = "Standard_D8s_v5"enable_auto_scaling = truemin_count = 2max_count = 20zones = [1, 2, 3]vnet_subnet_id = azurerm_subnet.aks.idmax_pods = 50node_labels = {workload = "app"}node_taints = ["dedicated=app:NoSchedule"]tags = local.common_tags}
Key Vault
keyvault.tf
data "azurerm_client_config" "current" {}resource "azurerm_key_vault" "main" {name = "${var.project_name}-${var.environment}-kv"location = azurerm_resource_group.main.locationresource_group_name = azurerm_resource_group.main.nametenant_id = data.azurerm_client_config.current.tenant_idsku_name = "standard"soft_delete_retention_days = 90purge_protection_enabled = trueenabled_for_disk_encryption = truenetwork_acls {default_action = "Deny"bypass = "AzureServices"ip_rules = var.allowed_ip_rangesvirtual_network_subnet_ids = [azurerm_subnet.app.id,azurerm_subnet.aks.id,]}tags = local.common_tags}resource "azurerm_key_vault_access_policy" "terraform" {key_vault_id = azurerm_key_vault.main.idtenant_id = data.azurerm_client_config.current.tenant_idobject_id = data.azurerm_client_config.current.object_idsecret_permissions = ["Get", "List", "Set", "Delete", "Recover"]key_permissions = ["Get", "List", "Create", "Delete", "Recover","WrapKey", "UnwrapKey"]}resource "azurerm_key_vault_access_policy" "aks" {key_vault_id = azurerm_key_vault.main.idtenant_id = data.azurerm_client_config.current.tenant_idobject_id = azurerm_kubernetes_cluster.main.key_vault_secrets_provider[0].secret_identity[0].object_idsecret_permissions = ["Get", "List"]}resource "azurerm_key_vault_secret" "sql_password" {name = "sql-admin-password"value = var.sql_admin_passwordkey_vault_id = azurerm_key_vault.main.iddepends_on = [azurerm_key_vault_access_policy.terraform]}
Contents
When to Use
- You need multi-cloud or cloud-agnostic Infrastructure as Code.
- Your team standardizes on Terraform across AWS, Azure, and GCP.
- You need plan/apply workflows with change preview before deployment.
- You want modular, reusable infrastructure components.
- You need state locking and drift detection for production infrastructure.
Limitations
- Infrastructure commands can disrupt services: confirm target host/scope and have backups/snapshots before mutating state.
- Docs-only import: upstream scripts and templates not bundled.