Creates and manages a single NIOS delegated DNS zone (
infoblox_zone_delegated/ WAPIzone_delegated) — hands authority for a subzone off to a set of remote name servers or to a NIOS name server group. Built for infoblox v2.x / WAPI v2.12.3.
- 🟦 Wraps the single
infoblox_zone_delegatedresource namedthis— no provider block, no credentials. - 🤝 Delegates authority for a subzone to either explicit remote name servers (
delegate_to) or a NIOS name server group (ns_group) — exactly one, enforced at plan time. - 🌐 Supports forward delegations (
FORWARD) and reverse delegations (IPV4/IPV6,fqdninaddress/cidrform). - 🕒 Exposes the delegation TTL (
delegated_ttl) as the universalttlinput —nullinherits,0disables caching. - 🏷️ Universal NIOS fields: optional
commentandext_attrs(map(string), JSON-encoded only when non-empty), plus operationaldisable/lockedflags whose defaults match the provider's (no drift). - 🔗 Emits the WAPI reference
idplusfqdn/dns_viewshaped exactly as sibling DNS modules consume them.
💡 Why it matters: A delegated zone is how NIOS cedes responsibility for a subzone (e.g. a cloud provider's
aws.example.com) to external authoritative servers while keeping the parent zone in the grid. The delegated zone must be a subzone of an existing authoritative zone — see Architecture Notes.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
This module consumes a dns_view (from terraform-infoblox-dns-view) and emits id / fqdn / dns_view for sibling DNS modules and operational tooling (see 🔌 Typical wiring below); the delegated fqdn must fall under an existing authoritative zone (terraform-infoblox-zone-auth), wired via depends_on.
flowchart TD
NV["terraform-infoblox-network-view<br/>(network_view)"]
DV["terraform-infoblox-dns-view<br/>(dns_view)"]
ZA["terraform-infoblox-zone-auth<br/>(parent authoritative zone)"]
ZD["terraform-infoblox-zone-delegated<br/>(this module — WAPI zone_delegated)"]
NV -->|network_view| DV
DV -->|dns_view| ZA
DV -->|dns_view| ZD
ZA -.must be a subzone of.-> ZD
classDef me fill:#00B4D8,color:#fff,stroke:#0077A3,stroke-width:2px;
class ZD me;
One resource — infoblox_zone_delegated.this — a thin renderer over the WAPI zone_delegated object. Delegation is via delegate_to (a dynamic block) XOR ns_group; fqdn / dns_view / zone_format are immutable.
flowchart TD
IN["fqdn (immutable, lowercase)<br/>dns_view / zone_format (immutable)<br/>delegation target: delegate_to (dynamic) XOR ns_group<br/>ttl / disable / locked / comment / ext_attrs"]
THIS["infoblox_zone_delegated.this<br/>(WAPI zone_delegated — delegated DNS subzone)"]
ID["id (WAPI _ref, e.g. zone_delegated/...)"]
OUT["fqdn / dns_view / delegate_to / ns_group"]
IN --> THIS
THIS --> ID
THIS --> OUT
classDef me fill:#00B4D8,color:#fff,stroke:#0077A3,stroke-width:2px;
class THIS me;
terraform-infoblox-zone-delegated/
├── providers.tf # required_providers only — no provider {} block
├── variables.tf # fqdn, dns_view, delegate_to, ns_group, zone_format, disable, locked, comment, ext_attrs, ttl
├── main.tf # single resource infoblox_zone_delegated.this
├── outputs.tf # id (primary), fqdn, dns_view, zone_format, delegate_to, ns_group, ttl, comment
├── README.md
└── SCOPE.md
module "delegated_aws_example_com" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
{ name = "ns-2034.awsdns-62.co.uk", address = "10.10.1.1" },
]
}Place the delegation in a non-default view by wiring dns_view from the DNS-view module:
module "delegated_aws_example_com" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
dns_view = module.dns_view_external.dns_view # from terraform-infoblox-dns-view
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}| Role / License | Required for | Notes |
|---|---|---|
DNS Admin (or a group with create/modify/delete on zone_delegated) |
Creating, updating and deleting the delegated zone | Minimum role for normal operation of this module. |
| Cloud Network Automation license | Auto-creating the four required Extensible Attributes | If the EAs already exist on the grid, the license is not strictly required to use them. |
| Superuser | Auto-creating the Terraform Internal ID EA on first apply; import workflows |
Needed only when the EA does not already exist and you want the provider/admin to create it. |
⚠️ Superuser callout: TheTerraform Internal IDEA (read-only, CR flag) can only be auto-created by a superuser. If your Terraform identity is not superuser, an administrator must pre-create all four EAs (see Prerequisites below) before the firstterraform apply, or the apply fails with a connection/EA error. Theinfoblox_ip_allocation/infoblox_ip_associationresources (a separate module) likewise require superuser — thiszone_delegatedmodule does not.
These four Extensible Attributes must exist on the grid before terraform apply. They are installed automatically by the Cloud Network Automation license, or created manually / via cURL.
| # | Extensible Attribute | Type | Notes |
|---|---|---|---|
| 1 | Tenant ID |
String | CMP tenant identifier |
| 2 | CMP Type |
String | Cloud-management-platform type (e.g. Terraform) |
| 3 | Cloud API Owned |
List (True, False) |
Marks objects owned by the cloud API |
| 4 | Terraform Internal ID |
String, read-only (CR flag) | Drift detection when a NIOS reference changes outside Terraform |
Create the Terraform Internal ID EA via WAPI (target WAPI v2.12.3):
curl -k -u "$INFOBLOX_USERNAME:$INFOBLOX_PASSWORD" \
-H "Content-Type: application/json" \
-X POST "https://$INFOBLOX_SERVER/wapi/v2.12.3/extensibleattributedef" \
-d '{
"name": "Terraform Internal ID",
"type": "STRING",
"flags": "CR",
"comment": "Internal ID for Terraform Resource"
}'The other three EAs are created the same way (
"type": "STRING"forTenant ID/CMP Type;"type": "ENUM"withlist_valuesTrue/FalseforCloud API Owned).
| This module output | Feeds into |
|---|---|
id |
Imports, reference-by-ID wiring |
fqdn |
Sibling DNS modules, operational reporting |
dns_view |
Sibling DNS modules |
zone_format |
Reverse-zone consumers |
delegate_to |
Operational reporting |
ns_group |
Operational reporting |
ttl |
Operational reporting |
comment |
Operational reporting |
WAPI reference string (id output). The primary id is the NIOS WAPI object reference, not a UUID. For a delegated zone it looks like:
zone_delegated/ZG5zLnpvbmUkLl9kZWZhdWx0LmNvbS5leGFtcGxlLmF3cw:aws.example.com/default
The opaque base64-like segment is the object handle; the trailing name/view is human-readable. Use this string for terraform import and for any reference-by-ID wiring.
Immutable / force-new fields.
fqdn— NIOS does not allow a delegated-zone name to change. Editing it forces destroy + recreate. Choose it carefully before the first apply.zone_format— for reverse zones the format is tied to thefqdnCIDR; sincefqdncannot change,zone_formatis immutable in practice. Set it correctly forIPV4/IPV6delegations up front.
Delegated zone must be a subzone of an authoritative zone. NIOS only accepts a delegated zone whose fqdn falls under an existing authoritative zone (e.g. aws.example.com requires the example.com zone_auth to exist). Create the parent with terraform-infoblox-zone-auth first, or the WAPI rejects the create.
Exactly one delegation target. NIOS requires either delegate_to (one or more remote name servers) or ns_group — never both, never neither. The module enforces this with a cross-field XOR validation on delegate_to so the misconfiguration is caught at plan time, not by the WAPI:
condition = (length(var.delegate_to) > 0) != (var.ns_group != null)The delegate_to block is rendered with a dynamic block driven by the list; when the list is empty the block is omitted entirely and delegation is governed by ns_group.
Lowercase-only FQDNs. NIOS DNS lookups are case-sensitive. A capital letter in fqdn (or in any delegate_to[*].name) produces a delegation that appears to fail to resolve. The module enforces var.fqdn == lower(var.fqdn) and the same rule on each delegate_to[*].name with validation {} blocks — keep all zone names and name-server FQDNs lowercase.
ext_attrs JSON-encoding & the length > 0 guard. Callers always pass a plain map(string); the module encodes it for the provider:
ext_attrs = length(var.ext_attrs) > 0 ? jsonencode(var.ext_attrs) : nullThe provider's ext_attrs argument is a JSON string, so jsonencode is required. The length > 0 guard sends null (argument omitted) for an empty map rather than the string "{}" — without it, an unused EA map shows up as perpetual drift on every subsequent plan.
TTL behaviour (ttl → provider delegated_ttl).
null/ omitted → inherit (the provider'sttlUndef). This surfaces in Terraform state as a negative value such as-1— that is expected, not drift.0→ disable caching for this delegation.- positive integer → explicit TTL in seconds.
The provider field is named delegated_ttl; the module exposes it as the universal ttl for consistency across the terraform-infoblox-* suite, and the ttl output re-emits the provider value (a negative number signals the inherited/unset state).
Terraform Internal ID EA & drift detection. On first apply the provider stamps the delegated zone with the Terraform Internal ID EA. If a NIOS admin manually changes the object such that its WAPI reference changes, Terraform uses this stable internal ID (not the volatile ref) to re-locate the object and detect drift instead of proposing a spurious recreate. The EA is not written to terraform.tfstate and must never be placed in a normal ext_attrs block.
Eventual consistency. The NIOS WAPI is eventually consistent — a read immediately following a create may not see the delegated zone. Re-plan, allow a brief settle, or sequence dependent modules with depends_on.
1 · Minimal — delegate to one remote name server
module "delegated_minimal" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}2 · Delegate to multiple remote name servers
module "delegated_multi_ns" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
{ name = "ns-2034.awsdns-62.co.uk", address = "10.10.1.1" },
{ name = "ns-0917.awsdns-52.net", address = "10.20.1.1" },
]
}3 · Delegation with a comment
module "delegated_with_comment" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "cloud.example.com"
comment = "Cloud subzone delegated to provider DNS — managed by Terraform"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}4 · Delegation with Extensible Attributes (jsonencode pattern)
module "delegated_with_eas" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "prod.example.com"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
# Callers pass a plain map(string). The module runs jsonencode internally.
ext_attrs = {
"Tenant ID" = "prod"
"CMP Type" = "Terraform"
"Cloud API Owned" = "True"
}
}5 · Explicit DNS view (string)
module "delegated_external_view" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
dns_view = "external"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}6 · Delegate via a NIOS name server group (instead of delegate_to)
module "delegated_via_nsgroup" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "lab.example.com"
ns_group = "demoGroup" # must already exist on the grid; omit delegate_to entirely
}7 · IPv4 reverse delegation
module "delegated_reverse_v4" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "10.1.0.0/24" # address/cidr form for reverse zones
zone_format = "IPV4"
comment = "Reverse delegation for 10.1.0.0/24"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}8 · IPv6 reverse delegation
module "delegated_reverse_v6" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "3001:db8::/64"
zone_format = "IPV6"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}9 · Explicit TTL on the delegation
module "delegated_with_ttl" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
ttl = 3600 # 1h; maps to the provider's delegated_ttl
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}10 · Disable caching for the delegation (ttl = 0)
module "delegated_no_cache" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
ttl = 0 # disable caching; omit ttl entirely (null) to inherit instead
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}11 · Disabled and locked delegation
module "delegated_disabled_locked" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "staging.example.com"
disable = true # not served while disabled
locked = true # restrict conflicting admin changes (continues serving DNS data)
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
}12 · Fully-specified delegation (every input, delegate_to path)
module "delegated_full" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "complete.example.com"
dns_view = "internal"
zone_format = "FORWARD"
disable = false
locked = false
ttl = 1800
comment = "Fully specified delegated zone"
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
{ name = "ns-2034.awsdns-62.co.uk", address = "10.10.1.1" },
]
ext_attrs = {
"Tenant ID" = "shared-services"
"CMP Type" = "Terraform"
"Cloud API Owned" = "True"
}
}13 · Cross-module wiring — DNS view → parent zone → delegation (end-to-end)
module "dns_view_external" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-view?ref=v1.0.0"
name = "external"
}
# The delegated zone must be a subzone of an existing authoritative zone.
module "zone_example_com" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-auth?ref=v1.0.0"
fqdn = "example.com"
dns_view = module.dns_view_external.dns_view
}
module "delegated_aws_example_com" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-delegated?ref=v1.0.0"
fqdn = "aws.example.com"
dns_view = module.zone_example_com.dns_view # same view as the parent zone
delegate_to = [
{ name = "ns-1488.awsdns-58.org", address = "10.1.1.1" },
]
depends_on = [module.zone_example_com] # parent authoritative zone must exist first
}ℹ️ No
next_available_ipexample:zone_delegatedis a DNS container with noip_addr/cidrallocation argument, so the next-available-IP pattern does not apply here. Seeterraform-infoblox-ip-allocation/terraform-infoblox-ipv4-networkfor that.
| Name | Type | Default | Description |
|---|---|---|---|
fqdn |
string |
— (required) | Delegated zone name (FQDN for forward, address/cidr for reverse). Immutable, lowercase-only. |
dns_view |
string |
"default" |
DNS view (view) the zone resides in. Wire from terraform-infoblox-dns-view. |
delegate_to |
list(object({ name, address })) |
[] |
Remote name servers serving the subzone. Supply this OR ns_group. Names lowercase-only. |
ns_group |
string |
null |
NIOS name server group serving the zone; must pre-exist. Supply this OR delegate_to. |
zone_format |
string |
"FORWARD" |
One of FORWARD, IPV4, IPV6. Immutable in practice for reverse zones. |
disable |
bool |
false |
Whether the zone is disabled (not served). Matches provider default → no drift. |
locked |
bool |
false |
Restrict conflicting admin changes (zone still serves DNS). Matches provider default. |
comment |
string |
null |
Human-readable description. |
ext_attrs |
map(string) |
{} |
NIOS Extensible Attributes; JSON-encoded internally, omitted when empty. |
ttl |
number |
null |
Delegation TTL → provider delegated_ttl. null = inherit, 0 = disable caching, positive = seconds. |
Delegation rule: exactly one of
delegate_to(non-empty) orns_groupmust be set — enforced by an XORvalidation {}ondelegate_to.
| Output | Description | Typically consumed by |
|---|---|---|
id |
Primary. WAPI object reference string of the delegated zone. | Imports, reference-by-ID wiring |
fqdn |
Zone name as stored in NIOS. | Sibling DNS modules, operational reporting |
dns_view |
DNS view the zone resides in. | Sibling DNS modules |
zone_format |
FORWARD / IPV4 / IPV6. |
Reverse-zone consumers |
delegate_to |
List of { name, address } remote name servers (empty when delegating via ns_group). |
Operational reporting |
ns_group |
Name server group (null when delegating via delegate_to). |
Operational reporting |
ttl |
The delegated_ttl applied (negative = inherited/unset). |
Operational reporting |
comment |
Zone comment (null if none). | Operational reporting |
- Single resource named
this— oneinfoblox_zone_delegated, four files, no provider block. - House variable order —
fqdn→dns_view→ delegation target → optional config → universal NIOS fields. - Deeply-typed
delegate_toobject —list(object({ name, address })); no loosemap/any. - Closed value sets validated —
zone_format, the lowercasefqdn/delegate_to[*].namerules, and thedelegate_to⊕ns_groupXOR are all enforced withvalidation {}. - Universal NIOS fields —
comment+ext_attrspresent,ext_attrsJSON-encoded with thelength > 0guard; DNSttlexposed (maps todelegated_ttl). - Provider-matching defaults —
disable/lockeddefault tofalseso unset = no drift. - No Azure idioms — no
tags, noresource_group_name, noobject_id; the primary output is the WAPIid. - Credentials never in the module — grid auth is root/pipeline-level env vars only.
terraform init -backend=false
terraform validate
terraform fmt -checkℹ️
terraform plan/applyrequire a live NIOS grid endpoint (INFOBLOX_SERVER/INFOBLOX_USERNAME/INFOBLOX_PASSWORD), the four EAs in place, and an existing authoritative parent zone.init -backend=false/validate/fmt -checkrun offline.
| Symptom | Likely cause | Fix |
|---|---|---|
terraform apply fails with a connection/EA error immediately |
One or more of the four required EAs missing on the grid | Create them via the cURL command above, or install the Cloud Network Automation license. |
| WAPI auth failure / 401 | INFOBLOX_USERNAME / INFOBLOX_PASSWORD / INFOBLOX_SERVER not set or wrong |
Set the env vars at pipeline/root level; verify the role has DNS Admin rights. |
| Plan fails validation: "Supply exactly one delegation target…" | Both delegate_to and ns_group set, or neither |
Provide exactly one — a non-empty delegate_to list or ns_group. |
| WAPI rejects create — parent zone not found | The delegated fqdn is not a subzone of an existing authoritative zone |
Create the parent with terraform-infoblox-zone-auth first (depends_on). |
| Delegation "doesn't resolve" though it applied cleanly | Capital letters in fqdn or a delegate_to[*].name (NIOS lookups are case-sensitive) |
Use lowercase only — the module's validation blocks this; fix any pre-existing objects. |
| Plan proposes destroy/recreate of the zone | fqdn (or reverse-zone zone_format) was changed — both are immutable |
Revert the change, or accept recreation of the delegation. |
ttl shows as -1 (or another negative number) in state/output |
ttl left null → inherited (ttlUndef) |
Expected, not drift. Set a positive ttl only if you need an explicit value. |
Perpetual ext_attrs drift showing "{}" |
EA map passed as empty without the encode guard (custom callers) | This module already guards with length > 0 ? jsonencode(...): null — pass a plain map(string). |
| Import assigns a new Terraform Internal ID, then a later apply is aborted | Internal ID stamped before management was finalized | Re-run terraform plan/apply to completion; do not destroy immediately after an import. |
| Eventual consistency — zone not visible to a follow-on read right after create | NIOS WAPI is eventually consistent | Re-run plan; allow a brief settle, or add explicit depends_on. |
infoblox_zone_delegatedresourceinfoblox_zone_delegateddata sourceterraform-infoblox-dns-view— upstreamdns_viewsourceterraform-infoblox-zone-auth— the parent authoritative zone a delegation must sit underterraform-infoblox-zone-forward— sibling zone type (conditional forwarding)
💙 "Infrastructure as Code should be standardized, consistent, and secure."