Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🟦 Infoblox NIOS Delegated Zone Terraform Module

Creates and manages a single NIOS delegated DNS zone (infoblox_zone_delegated / WAPI zone_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.

Terraform infoblox [module] [type] [resources]


🧩 Overview

  • 🟦 Wraps the single infoblox_zone_delegated resource named this — 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, fqdn in address/cidr form).
  • 🕒 Exposes the delegation TTL (delegated_ttl) as the universal ttl input — null inherits, 0 disables caching.
  • 🏷️ Universal NIOS fields: optional comment and ext_attrs (map(string), JSON-encoded only when non-empty), plus operational disable / locked flags whose defaults match the provider's (no drift).
  • 🔗 Emits the WAPI reference id plus fqdn / dns_view shaped 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.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

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!


🗺️ Where this fits in the family

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;
Loading

🧬 What this module builds

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;
Loading

📁 Module Structure

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

⚙️ Quick Start

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" },
  ]
}

🔑 Required NIOS Permissions

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: The Terraform Internal ID EA (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 first terraform apply, or the apply fails with a connection/EA error. The infoblox_ip_allocation / infoblox_ip_association resources (a separate module) likewise require superuser — this zone_delegated module does not.


📋 NIOS Prerequisites

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" for Tenant ID / CMP Type; "type": "ENUM" with list_values True/False for Cloud API Owned).


🔌 Typical wiring

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

🧠 Architecture Notes

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 the fqdn CIDR; since fqdn cannot change, zone_format is immutable in practice. Set it correctly for IPV4 / IPV6 delegations 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) : null

The 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's ttlUndef). 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.


📚 Example Library (copy-paste)

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_ip example: zone_delegated is a DNS container with no ip_addr / cidr allocation argument, so the next-available-IP pattern does not apply here. See terraform-infoblox-ip-allocation / terraform-infoblox-ipv4-network for that.


📦 Inputs (high-level)

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) or ns_group must be set — enforced by an XOR validation {} on delegate_to.


🧾 Outputs

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

🧱 Design Principles

  • Single resource named this — one infoblox_zone_delegated, four files, no provider block.
  • House variable order — fqdn → dns_view → delegation target → optional config → universal NIOS fields.
  • Deeply-typed delegate_to object — list(object({ name, address })); no loose map/any.
  • Closed value sets validated — zone_format, the lowercase fqdn / delegate_to[*].name rules, and the delegate_to ⊕ ns_group XOR are all enforced with validation {}.
  • Universal NIOS fields — comment + ext_attrs present, ext_attrs JSON-encoded with the length > 0 guard; DNS ttl exposed (maps to delegated_ttl).
  • Provider-matching defaults — disable / locked default to false so unset = no drift.
  • No Azure idioms — no tags, no resource_group_name, no object_id; the primary output is the WAPI id.
  • Credentials never in the module — grid auth is root/pipeline-level env vars only.

🚀 Runbook

terraform init -backend=false
terraform validate
terraform fmt -check

ℹ️ terraform plan / apply require 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 -check run offline.


🔍 Troubleshooting

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.

🔗 Related Docs


💙 "Infrastructure as Code should be standardized, consistent, and secure."

About

Terraform module: terraform-infoblox-zone-delegated

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages