A hardened, standalone Terraform module that manages a single Azure NetApp Files account — the regional parent that capacity pools, volumes, and snapshots are created under — targeting
hashicorp/azurerm ~> 4.0.
- 🏦 Creates one
azurerm_netapp_account— the keystonethis— as the regional parent for Azure NetApp Files. - 🪪 Renders an optional managed
identityblock (SystemAssignedorUserAssigned) used to reach a Key Vault key for customer-managed key (CMK) encryption. - 🔗 Renders an optional
active_directoryblock for SMB and dual-protocol volumes, with AES encryption and LDAP signing on by default. - 🔐 Keeps the account credentials-free by default: no Active Directory join, no identity, and no plaintext secret in state.
- 🧱 Leaves capacity pools, volumes, snapshots, backup vaults/policies, diagnostics, private connectivity, CMK linkage, and RBAC to sibling modules — consumed by
id.
💡 Why it matters: The NetApp account is a lightweight control-plane object, but it is where the Active Directory join and the encryption identity live. Getting AES encryption, LDAP signing, and secret handling right here is what lets every downstream volume mount securely. The empty call yields an account with no embedded credentials and no AD exposure; each relaxation is an explicit, reviewable opt-in.
If this module saves you time, please consider supporting its continued development:
- ⭐ Star the repository on GitHub
- 🤝 Connect on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
flowchart LR
rg["terraform-azurerm-resource-group"]
kv["terraform-azurerm-key-vault"]
vnet["terraform-azurerm-virtual-network: subnet delegated to Microsoft.NetApp/volumes"]
acct["terraform-azurerm-netapp-account"]
enc["terraform-azurerm-netapp-account-encryption: who holds the key"]
pool["terraform-azurerm-netapp-pool: the BILLED capacity"]
vol["terraform-azurerm-netapp-volume: what clients mount"]
snap["terraform-azurerm-netapp-snapshot: one on-demand, in the pool"]
spol["terraform-azurerm-netapp-snapshot-policy: the schedule"]
vault["terraform-azurerm-netapp-backup-vault: the destination"]
bpol["terraform-azurerm-netapp-backup-policy: the retention"]
vghana["terraform-azurerm-netapp-volume-group-sap-hana"]
vgora["terraform-azurerm-netapp-volume-group-oracle"]
rg -->|"resource_group_name, location"| acct
kv -->|"encryption_key URI"| enc
acct -->|"id, BY ID"| enc
acct -->|"account_name, BY NAME"| pool
acct -->|"account_name, BY NAME"| vault
acct -->|"account_name, BY NAME"| bpol
acct -->|"account_name, BY NAME"| spol
acct -->|"account_name, BY NAME"| vghana
acct -->|"account_name, BY NAME"| vgora
pool -->|"pool_name, BY NAME"| vol
pool -->|"capacity_pool_id, BY ID"| vghana
pool -->|"capacity_pool_id, BY ID"| vgora
vnet -->|"subnet_id, must be delegated"| vol
vol -->|"name plus pool_name, BY NAME"| snap
spol -->|"snapshot_policy_id, BY ID"| vol
vault -->|"backup_vault_id, BY ID"| vol
bpol -->|"backup_policy_id, BY ID"| vol
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class acct keystone;
class enc,pool,vol,snap,spol,vault,bpol,vghana,vgora me;
class rg,kv,vnet sib;
graph TD
name["name"]
rgn["resource_group_name"]
loc["location"]
idv["identity input"]
adv["active_directory input"]
tagsv["tags"]
acct["azurerm_netapp_account.this"]
idblk["identity block: SystemAssigned or UserAssigned"]
adblk["active_directory block: SMB, Kerberos, LDAP"]
oid["output: id"]
oname["output: name"]
opid["output: principal_id"]
otid["output: tenant_id"]
name -->|"required, force-new"| acct
rgn -->|"required, force-new"| acct
loc -->|"required, force-new"| acct
tagsv --> acct
idv --> idblk
adv --> adblk
idblk --> acct
adblk --> acct
acct --> oid
acct --> oname
acct -->|"identity[0].principal_id"| opid
acct -->|"identity[0].tenant_id"| otid
classDef this fill:#0078D4,color:#fff,stroke:#004578,stroke-width:2px;
classDef keystone fill:#004578,color:#fff,stroke:#004578,stroke-width:2px;
classDef ext fill:#eef2f7,color:#1b1f24,stroke:#c3cbd6;
class idblk,adblk this;
class acct keystone;
class name,rgn,loc,idv,adv,tagsv,oid,oname,opid,otid ext;
Resource inventory
| Resource | Count | Role |
|---|---|---|
azurerm_netapp_account.this |
1 | Keystone; the regional NetApp Files account |
identity (nested block) |
0–1 | Managed identity for CMK encryption |
active_directory (nested block) |
0–1 | AD/LDAP connection for SMB and dual-protocol volumes |
timeouts (nested block) |
0–1 | Optional operation timeouts |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
| Provider | hashicorp/azurerm ~> 4.0 |
| Provider block | Not declared here — the caller configures provider "azurerm" { features {} }, auth, and subscription |
Schema notes that bite (verified against the live provider schema):
- 🔒
name,resource_group_name, andlocationare all force-new — changing any of them replaces the account (and orphans downstream pools/volumes if they are not re-parented). - 🪪 Changing
identity.typefromSystemAssignedtoUserAssignedis supported in place, but the reverse is not supported from Terraform. - 🧾
identity.identity_idsis required whenidentity.type = "UserAssigned". - 🌐 Azure allows only one Active Directory connection per subscription per NetApp account.
- 🔐
active_directory.server_root_ca_certificateandactive_directory.ldap_over_tls_enabledare mutually required by presence, not by value — the provider declaresRequiredWithon each pointing at the other, and the SDK checks whether the argument is present, never what it is set to. Supplyingldap_over_tls_enabled = falsetherefore demands a certificate exactly astruedoes. Omit both, or supply both. - 🎟️ Kerberos volumes require both
active_directory.kerberos_ad_nameandactive_directory.kerberos_kdc_ip. - ♻️ On import,
active_directory.passwordandactive_directory.server_root_ca_certificatecannot be read back from Azure and must be redeclared in configuration. - 🧱 The account itself has no public-network-access argument — data-plane reachability is governed by the delegated subnets that volumes (sibling modules) are placed in.
Least-privilege, scoped to the target resource group:
- A custom role granting
Microsoft.NetApp/netAppAccounts/*(create/read/update/delete the account), or the built-in Contributor role on the resource group. - When a managed identity is attached for CMK, the identity additionally needs key permissions on the target Key Vault key (Key Vault Crypto Service Encryption User on the key). That grant is applied by the account-encryption sibling module, not here.
ℹ️ The role must be held by the caller's authenticated identity (the provider's principal), not by the module. The module never authenticates.
- An existing resource group in a supported US Azure region (for example
eastus2,westus2). - The
Microsoft.NetAppresource provider registered on the target subscription. - For customer-managed keys: an existing Key Vault and key, plus a managed identity granted access to the key (wired through the account-encryption sibling module).
- For Active Directory joins: reachable AD DNS servers and a domain-join account; for LDAP over TLS, a base64-encoded root CA certificate.
- The caller configures the
provider "azurerm" { features {} }block, auth, and subscription; this module declares none of them.
terraform-azurerm-netapp-account/
├── providers.tf # required_version >= 1.12.0; azurerm ~> 4.0; no provider block
├── variables.tf # deeply-typed object() schemas + tags/timeouts tail
├── main.tf # keystone azurerm_netapp_account.this; dynamic identity/active_directory/timeouts
├── outputs.tf # id first, then name, then principal_id / tenant_id
├── README.md # this document
├── SCOPE.md # the cross-module contract (lightweight, standalone)
├── LICENSE # MIT, Copyright (c) 2026 Casey Wood
└── .gitignore # canonical library ignore set
provider "azurerm" {
features {}
}
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-platform-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
tags = {
environment = "prod"
owner = "platform-team"
}
}ℹ️ The caller owns the
provider "azurerm" { features {} }block, authentication, and the subscription. Pin the module with?ref=v1.0.0— never a branch.
Consumes
| Input | Type | Source |
|---|---|---|
resource_group_name |
string |
terraform-azurerm-resource-group output name |
location |
string |
caller / resource group location |
identity.identity_ids |
set(string) |
terraform-azurerm-user-assigned-identity output id |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
NetApp Account Resource ID (first) | terraform-azurerm-monitor-diagnostic-setting, terraform-azurerm-role-assignments, and terraform-azurerm-netapp-account-encryption (netapp_account_id) — note the pool, volume, backup-vault, backup-policy and snapshot-policy modules consume name, not this |
name |
NetApp Account name | terraform-azurerm-netapp-pool, -backup-vault, -backup-policy, -snapshot-policy, -volume-group-sap-hana, -volume-group-oracle (account_name), and terraform-azurerm-netapp-volume / -snapshot — the whole family addresses the account by name; also diagnostics and tagging |
principal_id |
System-assigned identity principal (object) ID, or null | terraform-azurerm-role-assignments, Key Vault access for CMK |
tenant_id |
Identity tenant ID, or null | documentation / CMK wiring |
1 · Minimal, hardened base account
The empty call: no identity, no Active Directory, no embedded secret. This is the secure baseline.
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-core-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
}🔒 No credentials are stored and no AD is joined until you opt in.
2 · Account with organizational tags
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-data-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
tags = {
environment = "prod"
cost_center = "1234"
data_class = "internal"
}
}💡
tagsis near-universal on ARM resources and drives cost and governance reporting.
3 · System-assigned identity (CMK-ready)
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-cmk-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
identity = {
type = "SystemAssigned"
}
}💡 The account's
principal_idoutput is then granted access to a Key Vault key by the account-encryption sibling module.
4 · User-assigned identity for CMK
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-uai-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
identity = {
type = "UserAssigned"
identity_ids = [module.netapp_identity.id]
}
}
⚠️ identity_idsis required whentype = "UserAssigned". Only one identity type is supported at a time — the combined"SystemAssigned, UserAssigned"value is not valid for NetApp Files.
5 · Active Directory join for SMB
The base AD connection needed before SMB volumes can be created. AES encryption and LDAP signing are on by default.
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-smb-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
active_directory = {
dns_servers = ["10.0.0.4", "10.0.0.5"]
domain = "corp.example.com"
smb_server_name = "ANFSMB01"
username = "svc-anf-join"
password = var.ad_join_password # provisioned out of band
}
}🔒
passwordis sensitive; supply it from a Key Vault reference or a CI secret — never commit a literal.
6 · AD with organizational unit and site
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-smb-ou-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
active_directory = {
dns_servers = ["10.0.0.4"]
domain = "corp.example.com"
smb_server_name = "ANFSMB02"
username = "svc-anf-join"
password = var.ad_join_password
organizational_unit = "OU=NetApp,OU=Servers,DC=corp,DC=example,DC=com"
site_name = "EastUS2"
}
}ℹ️ Omitting
organizational_unitdefaults toCN=Computers; omittingsite_namedefaults toDefault-First-Site-Name.
7 · AES encryption plus Kerberos volumes
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-kerberos-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
active_directory = {
dns_servers = ["10.0.0.4"]
domain = "corp.example.com"
smb_server_name = "ANFSMB03"
username = "svc-anf-join"
password = var.ad_join_password
aes_encryption_enabled = true # already the module default
kerberos_ad_name = "ANFKRB"
kerberos_kdc_ip = "10.0.0.6"
}
}
⚠️ Kerberos volumes need bothkerberos_ad_nameandkerberos_kdc_ip; supplying only one has no effect at volume-create time.
8 · Adjusting LDAP signing
ldap_signing_enabled defaults to true (the provider default is false). Only disable it for a directory that cannot sign LDAP traffic.
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-ldap-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
active_directory = {
dns_servers = ["10.0.0.4"]
domain = "corp.example.com"
smb_server_name = "ANFSMB04"
username = "svc-anf-join"
password = var.ad_join_password
ldap_signing_enabled = false # opt-out of the secure default
}
}
⚠️ Disabling LDAP signing weakens directory-traffic integrity; keep the defaulttrueunless a directory constraint forces otherwise.
9 · LDAP over TLS with a root CA certificate
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-ldaps-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
active_directory = {
dns_servers = ["10.0.0.4"]
domain = "corp.example.com"
smb_server_name = "ANFSMB05"
username = "svc-anf-join"
password = var.ad_join_password
ldap_over_tls_enabled = true
server_root_ca_certificate = var.ldap_root_ca # base64 PEM, provisioned out of band
}
}🔒
server_root_ca_certificateis required whenldap_over_tls_enabled = trueand is sensitive — the module enforces this at plan time.
10 · Dual-protocol: local NFS users with LDAP
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-dual-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
active_directory = {
dns_servers = ["10.0.0.4"]
domain = "corp.example.com"
smb_server_name = "ANFSMB06"
username = "svc-anf-join"
password = var.ad_join_password
local_nfs_users_with_ldap_allowed = true
}
}
⚠️ Enablinglocal_nfs_users_with_ldap_allowedwidens access to local NFS users in addition to LDAP users; it defaults tofalse.
11 · Custom operation timeouts
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-timeouts-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
timeouts = {
create = "45m"
delete = "45m"
}
}ℹ️ Azure defaults are 30m create, 5m read, 30m update, 30m delete. Only override what you need.
12 · Many accounts at scale with for_each
Instantiate one account per region/environment from a keyed map in the root module.
locals {
netapp_accounts = {
prod_eastus2 = { name = "anf-prod-e2", resource_group_name = "rg-netapp-prod-eastus2", location = "eastus2" }
prod_westus2 = { name = "anf-prod-w2", resource_group_name = "rg-netapp-prod-westus2", location = "westus2" }
dr_centralus = { name = "anf-dr-cus", resource_group_name = "rg-netapp-dr-centralus", location = "centralus" }
}
}
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
for_each = local.netapp_accounts
name = each.value.name
resource_group_name = each.value.resource_group_name
location = each.value.location
tags = { environment = split("_", each.key)[0] }
}💡 Keying by a stable string means adding or removing one account never re-plans the others.
13 · Least-privilege secret handling
Pull the AD join password from Key Vault at plan time instead of a variable literal.
data "azurerm_key_vault_secret" "ad_join" {
name = "anf-ad-join-password"
key_vault_id = var.secrets_key_vault_id
}
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-secure-prod"
resource_group_name = "rg-netapp-prod-eastus2"
location = "eastus2"
identity = { type = "SystemAssigned" }
active_directory = {
dns_servers = ["10.0.0.4"]
domain = "corp.example.com"
smb_server_name = "ANFSMB07"
username = "svc-anf-join"
password = data.azurerm_key_vault_secret.ad_join.value
}
}🔒 The whole
active_directoryobject is marked sensitive, so neither the password nor the root CA appears in plan output. They are still written to state in plaintext — and this provider cannot read either value back from Azure, so state holds the only copy. Protect the backend accordingly.
14 · Wiring the resource-group sibling
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-netapp-prod-eastus2"
location = "eastus2"
}
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-wired-prod"
resource_group_name = module.rg.name
location = module.rg.location
}💡 Feeding
module.rg.nameandmodule.rg.locationestablishes the implicit create-order — the resource group is created first.
15 · 🏗️ End-to-end composition
Resource group → user-assigned identity → this NetApp account (with AD + CMK-ready identity) → account-encryption → capacity pool → volume. This module owns only the account; the pool and volume are sibling modules that consume its id.
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-netapp-prod-eastus2"
location = "eastus2"
}
module "netapp_identity" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-user-assigned-identity.git?ref=v1.0.0"
name = "id-anf-prod"
resource_group_name = module.rg.name
location = module.rg.location
}
module "netapp_account" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account.git?ref=v1.0.0"
name = "anf-platform-prod"
resource_group_name = module.rg.name
location = module.rg.location
identity = {
type = "UserAssigned"
identity_ids = [module.netapp_identity.id]
}
active_directory = {
dns_servers = ["10.0.0.4", "10.0.0.5"]
domain = "corp.example.com"
smb_server_name = "ANFSMB00"
username = "svc-anf-join"
password = var.ad_join_password
}
tags = { environment = "prod" }
}
# CMK linkage — sibling module. Consumes this account BY ID (unlike the pool/volume modules,
# which take the account name) plus one of its identities.
# module "netapp_encryption" {
# source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-account-encryption.git?ref=v1.0.0"
# netapp_account_id = module.netapp_account.id
# encryption_key = var.key_vault_key_id
#
# system_assigned_identity_principal_id = module.netapp_account.principal_id
# # The identity still needs Get / Wrap Key / Unwrap Key on the key — a separate grant.
# }
# Capacity pool — sibling module (consumes this account BY NAME, not by id):
# module "netapp_pool" {
# source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-pool.git?ref=v1.0.0"
# account_name = module.netapp_account.name
# ...
# }
# Volume — sibling module (consumes the pool + a delegated subnet):
# module "netapp_volume" {
# source = "git::https://github.com/microsoftexpert/terraform-azurerm-netapp-volume.git?ref=v1.0.0"
# account_name = module.netapp_account.name
# ...
# }💡 The account is the regional parent. Pools set the service level and provisioned capacity; volumes carry the mount targets and live in a delegated subnet — both are separate modules that reference this account's
id/name.
Identity & placement (required)
| Name | Type | Description |
|---|---|---|
name |
string |
Account name (force-new). |
resource_group_name |
string |
Existing resource group (force-new). |
location |
string |
Azure region (force-new). |
Optional configuration
| Name | Type | Default | Description |
|---|---|---|---|
identity |
object |
null |
Managed identity (SystemAssigned or UserAssigned) for CMK. |
active_directory |
object (sensitive) |
null |
AD/LDAP connection for SMB and dual-protocol volumes. |
tags |
map(string) |
{} |
Tags applied to the account. |
timeouts |
object |
null |
Optional create/read/update/delete timeouts. |
Full object() schemas
variable "identity" {
type = object({
type = string # "SystemAssigned" | "UserAssigned"
identity_ids = optional(set(string)) # required when type = "UserAssigned"
})
default = null
}
variable "active_directory" {
type = object({
dns_servers = list(string)
domain = string
smb_server_name = string
username = string
password = string # sensitive; out of band
organizational_unit = optional(string)
site_name = optional(string)
kerberos_ad_name = optional(string)
kerberos_kdc_ip = optional(string)
aes_encryption_enabled = optional(bool, true) # provider default: false
ldap_signing_enabled = optional(bool, true) # provider default: false
ldap_over_tls_enabled = optional(bool, false) # requires server_root_ca_certificate
server_root_ca_certificate = optional(string) # sensitive; out of band
local_nfs_users_with_ldap_allowed = optional(bool, false)
})
default = null
sensitive = true
}
variable "timeouts" {
type = object({
create = optional(string)
read = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}| Output | Description | Kind |
|---|---|---|
id |
The Azure Resource ID of the NetApp Account | Passthrough |
name |
The name of the NetApp Account | Passthrough |
resource_group_name |
The resource group holding the NetApp Account | Passthrough |
location |
The region the account resides in, normalized by the provider (an input of "East US" is emitted as "eastus") | Passthrough |
resource_group_id |
The Azure Resource ID of the containing resource group, derived by trimming the provider path from this account's ID | Passthrough |
arm_resource_type |
The ARM resource type this module creates | Derived |
tags |
The tags applied to the account | Passthrough |
tag_count |
The number of tag pairs on the account, against an Azure ceiling of 50 per resource | Passthrough |
principal_id |
The service principal (object) ID of the account's system-assigned managed identity, or null when no system-assigned identity is configured | Derived |
tenant_id |
The tenant ID of the account's managed identity, or null when no identity is configured | Derived |
identity_type |
The configured identity type - "SystemAssigned", "UserAssigned", or null when no identity block was rendered | Derived |
user_assigned_identity_ids |
The set of user-assigned identity Resource IDs attached to the account, or an empty set | Derived |
has_system_assigned_identity |
Whether the account carries a system-assigned managed identity, which is the only identity that produces a principal_id this module can emit | Derived |
user_assigned_identity_declared_without_ids |
True when identity.type is "UserAssigned" but no identity IDs were supplied | Derived |
identity_downgrade_is_not_supported |
Always true, and it is the one identity change that cannot be walked back | Constant |
active_directory_configured |
Whether an Active Directory connection is attached to this account | Passthrough |
active_directory_domain |
The Active Directory domain the account joins, or null | Derived |
smb_server_name |
The NetBIOS name registered as the SMB server computer account, or null | Derived |
active_directory_dns_server_count |
How many domain-controller IPv4 addresses were supplied, or 0 | Derived |
kerberos_volume_ready |
Whether BOTH kerberos_ad_name and kerberos_kdc_ip are set, which is what creating a Kerberos volume against this account requires | Derived |
aes_encryption_enabled |
Whether AES encryption is enabled for SMB communication | Derived |
ldap_signing_enabled |
Whether LDAP traffic is signed | Derived |
ldap_over_tls_enabled |
Whether LDAP traffic is secured with TLS | Derived |
local_nfs_users_with_ldap_allowed |
Whether local NFS client users may access NFS volumes alongside LDAP users | Derived |
has_server_root_ca_certificate |
Whether a root CA certificate was supplied for LDAP over TLS | Derived |
ad_credentials_are_never_read_back_from_azure |
Always true | Constant |
sensitive_marking_redacts_plan_output_not_state |
Always true, and it is the distinction most often collapsed | Constant |
ad_credentials_must_be_redeclared_after_import |
Always true | Constant |
ldap_tls_pairing_is_presence_based |
Always true, and it is the trap most likely to break a first apply | Constant |
force_new_arguments |
The arguments that destroy and recreate this account rather than updating it | Derived |
only_one_active_directory_connection_per_account |
Always true at the resource level: the provider permits at most one active_directory block on an account | Constant |
delete_requires_child_resources_removed |
Always true | Constant |
is_regional_and_not_migratable_between_subscriptions |
Always true | Constant |
max_capacity_pools_per_account |
The Azure default ceiling on capacity pools under a single NetApp account, adjustable by support request | Passthrough |
max_accounts_per_region_per_subscription |
The Azure default ceiling on NetApp accounts in one region for one subscription, adjustable by support request | Passthrough |
regional_capacity_quota_tib |
The default regional capacity quota per subscription, in TiB, from the Azure NetApp Files resource-limits table | Passthrough |
timeout_defaults |
The provider's built-in timeouts for this resource, which appear nowhere in the schema | Derived |
effective_timeouts |
The timeouts actually in force: the caller's values where supplied, the provider's defaults otherwise | Derived |
🔒 No secret is emitted. The AD password and root CA certificate are never exposed as outputs.
- Force-new triad.
name,resource_group_name, andlocationeach force replacement. Because capacity pools and volumes are parented to this account byid/name, a replacement here cascades — treat these three as immutable after the first apply. - Identity direction is one-way. The provider supports moving
identity.typefromSystemAssignedtoUserAssigned, but not back. Choose the target identity model deliberately. - Single AD per subscription. Azure permits only one Active Directory connection per subscription per NetApp account; the module renders at most one
active_directoryblock (max 1 in the schema). - Secret round-trip on import.
active_directory.passwordandactive_directory.server_root_ca_certificatecannot be read back from Azure. After an import they will show as changes until redeclared — this is expected provider behavior, not module drift. - Sensitive-by-container. Terraform cannot mark a single nested attribute of an input object sensitive, so the entire
active_directoryvariable issensitive = true. This redacts the password and root CA in plan output only — both are written to state in plaintext, and neither is readable back from Azure, so state is their only copy. The cost is also redacting non-secret AD fields in plan output. try()on every optional field.main.tfrenders each optional nested field throughtry(...), so an omitted key is absent rather than an error, and the object-leveloptional(...)defaults supply the secure values.features {}dependence. The provider will not initialize without a caller-sideprovider "azurerm" { features {} }block; that is expected and belongs to the root module.
| Concern | Secure default (empty call) | Opt-out / opt-in (caller types it) |
|---|---|---|
| Embedded credentials | No identity block; prefer a managed identity |
Set identity = { type = "SystemAssigned" } (or UserAssigned) |
| SMB AES encryption | aes_encryption_enabled = true (provider default is false) |
Set false |
| LDAP signing | ldap_signing_enabled = true (provider default is false) |
Set false |
| LDAP over TLS | ldap_over_tls_enabled = false; requires a root CA to enable |
Set true + supply server_root_ca_certificate |
| Local NFS users with LDAP | local_nfs_users_with_ldap_allowed = false |
Set true |
| AD join | None (no active_directory block) |
Supply the active_directory object |
| Secret handling | active_directory object is sensitive; secrets provisioned out of band |
— |
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module with
?ref=v1.0.0— never a branch. - This library is plan-only during authoring; a human runs
terraform plan/applyfrom CI against real credentials.
The offline proof gate exercised for this module:
terraform init -backend=false— resolvesazurerm ~> 4.0with no backend.terraform validate— type-checks theobject()schemas and thedynamic/try()rendering against the pinned provider schema. Reports Success.terraform fmt -check— enforces canonical formatting (exit code0).
What the gate does not cover: terraform validate never calls Azure. Only terraform plan (run by a human against real credentials) exercises the ARM API — for example the "single AD per subscription" and "identity type is one-way" constraints surface at plan/apply, not at validate.
$ terraform output
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-netapp-prod-eastus2/providers/Microsoft.NetApp/netAppAccounts/anf-platform-prod"
name = "anf-platform-prod"
principal_id = "66666666-7777-8888-9999-aaaaaaaaaaaa"
tenant_id = "bbbbbbbb-cccc-dddd-eeee-ffffffffffff"| Symptom | Cause | Fix |
|---|---|---|
| Plan forces replacement of the account | Changed name, resource_group_name, or location |
These are force-new; revert, or plan the downstream pool/volume re-parenting deliberately. |
Apply fails on a UserAssigned account, with no local error first |
type = "UserAssigned" and no identity_ids. Nothing catches this offline. This module validates only the reverse pairing (IDs supplied without UserAssigned), and the provider's expand checks only that same direction, so the request is sent with userAssignedIdentities unset and Azure rejects it. |
Supply identity.identity_ids with at least one user-assigned identity Resource ID. |
server_root_ca_certificate is required when ... ldap_over_tls_enabled is true |
LDAP over TLS enabled without a cert | Provide the base64 root CA in server_root_ca_certificate. |
Cannot switch identity back to SystemAssigned |
Provider supports only SystemAssigned → UserAssigned |
Recreate the account if you must revert the identity model. |
Perpetual diff on password / server_root_ca_certificate after import |
Azure does not return these values | Redeclare them in configuration; the diff clears on the next apply. |
| SMB volume create fails after account exists | AD connection missing or misconfigured | Supply a valid active_directory block before creating SMB volumes. |
| Kerberos volume create fails | Only one of the Kerberos fields set | Provide both kerberos_ad_name and kerberos_kdc_ip. |
- Provider resource:
azurerm_netapp_account - Azure NetApp Files documentation: Microsoft.NetApp
- Sibling modules:
terraform-azurerm-resource-group,terraform-azurerm-user-assigned-identity,terraform-azurerm-key-vault,terraform-azurerm-netapp-pool,terraform-azurerm-netapp-volume,terraform-azurerm-netapp-snapshot,terraform-azurerm-netapp-backup-vault,terraform-azurerm-netapp-backup-policy.terraform-azurerm-netapp-account-encryption(customer-managed key linkage),terraform-azurerm-netapp-snapshot-policy,terraform-azurerm-netapp-volume-group-sap-hana,terraform-azurerm-netapp-volume-group-oracle. - This module's
SCOPE.md(the cross-module contract).
💙 "Infrastructure as Code should be standardized, consistent, and secure."