Manage a Cisco ACI Policy-Based Redirect (PBR) service redirect policy β the L4-L7 traffic-steering construct (class
vnsSvcRedirectPol, DNuni/tn-{tenant}/svcCont/svcRedirectPol-{name}) together with its backup policy, health groups, and L3 / L1-L2 destinations β as a typed, secure-by-default building block targetingCiscoDevNet/aci ~> 2.20.
This module manages a Cisco ACI Policy-Based Redirect (PBR) configuration as one coherent, secure-by-default unit:
- π The service redirect policy (
aci_service_redirect_policy.this) β the PBR construct that steers matched traffic to a pool of service-node destinations, addressed by the Distinguished Nameuni/tn-{tenant}/svcCont/svcRedirectPol-{name}. - π§― A paired backup policy (
aci_service_redirect_backup_policy.this,for_each) β the fallback destination pool used when the primary pool's health drops. - π©Ί Redirect health groups (
aci_l4_l7_redirect_health_group.this,for_each) β tracking objects that destinations bind to for up/down health monitoring. - π― L3 destinations (
aci_destination_of_redirected_traffic.this,for_each) β the IP-addressed service nodes traffic is redirected to. - π L1/L2 destinations (
aci_pbr_l1_l2_destination.this,for_each) β concrete-interface-attached destinations for transparent (L1/L2) service insertion, under either the primary or the backup policy. - π·οΈ The ACI metadata tail β
annotation(preserved asorchestrator:terraform),name_alias, anddescriptionwhere the live schema exposes them. - π Scope, not credentials β the policy takes its parent tenant DN (
tenant_dn) as a required input; authentication and the APIC URL are the caller's provider concern and are never module variables.
π‘ Why it matters: PBR is how ACI inserts firewalls, load balancers, and other L4-L7 devices into a traffic path without routing through them by default gateway β a contract subject or service graph template points its redirect relation at this policy's
id, and everything downstream (destination health, backup failover, threshold behavior) is governed by what this module builds. A correctly-scoped, health-tracked redirect policy is the difference between transparent service insertion and a silent black hole when a service node fails.
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!
graph LR
tenant["terraform-aci-tenant"]:::sib
redirect["terraform-aci-l4-l7-redirect (this module)"]:::this
pol["aci_service_redirect_policy - class vnsSvcRedirectPol - DN uni/tn-{t}/svcCont/svcRedirectPol-{n}"]:::keystone
device["terraform-aci-l4-l7-device"]:::sib
ipsla["terraform-aci-ip-sla"]:::sib
contract["terraform-aci-contract"]:::sib
sgt["terraform-aci-l4-l7-service-graph-template"]:::sib
tenant -->|"tenant_dn"| redirect
redirect -->|"manages"| pol
device -->|"concrete_interface_dn (l1_l2_destinations)"| redirect
ipsla -->|"ip_sla_monitoring_policy_dn"| redirect
redirect -->|"id (PBR policy DN)"| contract
redirect -->|"id (PBR policy DN)"| sgt
classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
classDef keystone fill:#0D274D,color:#fff,stroke:#0D274D;
classDef sib fill:#f5f5f5,color:#333,stroke:#cccccc;
The service redirect policy sits between the tenant (its parent, via tenant_dn) and the constructs that steer traffic into it. It optionally binds to a concrete interface (for L1/L2 destinations) and an IP SLA monitoring policy β each by DN β and emits its id (the policy DN) for a contract subject or service graph template to consume as the PBR redirect relation.
graph TD
tdn["tenant_dn (required)"]:::in
srp["service_redirect_policy object (PBR posture + metadata)"]:::in
bp["backup_policies map (for_each)"]:::in
hg["health_groups map (for_each)"]:::in
dest["destinations map (for_each, L3)"]:::in
l12["l1_l2_destinations map (for_each, concrete interface)"]:::in
this["aci_service_redirect_policy.this (keystone, vnsSvcRedirectPol)"]:::this
backup["aci_service_redirect_backup_policy.this (for_each, vnsBackupPol)"]:::this
health["aci_l4_l7_redirect_health_group.this (for_each, vnsRedirectHealthGroup)"]:::this
l3d["aci_destination_of_redirected_traffic.this (for_each, vnsRedirectDest)"]:::this
l12d["aci_pbr_l1_l2_destination.this (for_each, vnsL1L2RedirectDest)"]:::this
oid["output: id (policy DN)"]:::out
obp["output: backup_policy_dns (map)"]:::out
ohg["output: health_group_dns (map)"]:::out
odest["output: destination_dns (map)"]:::out
ol12["output: l1_l2_destination_dns (map)"]:::out
tdn --> this
srp --> this
tdn --> backup
bp --> backup
tdn --> health
hg --> health
this --> l3d
dest --> l3d
health -.->|"health_group_key"| l3d
this --> l12d
backup -.->|"backup_policy_key"| l12d
health -.->|"health_group_key"| l12d
l12 --> l12d
this --> oid
backup --> obp
health --> ohg
l3d --> odest
l12d --> ol12
classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
classDef in fill:#f5f5f5,color:#333,stroke:#cccccc;
classDef out fill:#eeeeff,color:#333,stroke:#9999ff;
Resource inventory
| Resource | Name | Cardinality | Role |
|---|---|---|---|
aci_service_redirect_policy |
this |
1 (keystone) | The PBR service redirect policy (vnsSvcRedirectPol). |
aci_service_redirect_backup_policy |
this |
0..N (for_each over backup_policies) |
Paired PBR backup / fallback policy (vnsBackupPol). |
aci_l4_l7_redirect_health_group |
this |
0..N (for_each over health_groups) |
Health-tracking group for destinations (vnsRedirectHealthGroup). |
aci_destination_of_redirected_traffic |
this |
0..N (for_each over destinations) |
L3 redirect destination (vnsRedirectDest). |
aci_pbr_l1_l2_destination |
this |
0..N (for_each over l1_l2_destinations) |
L1/L2 redirect destination (vnsL1L2RedirectDest). |
| Requirement | Value |
|---|---|
| Terraform | >= 1.3.0 (uses optional() object defaults) |
| Provider | CiscoDevNet/aci ~> 2.20 |
| Provider block | None in this module β the caller configures and authenticates the provider (username/password, X.509 signature, or login domain) out of band. |
| Scope | Tenant-scoped β requires a parent tenant_dn. |
Schema notes that bite (verified against the live provider schema):
- π
service_redirect_policy.nameis immutable. Changing it forces replacement of the policy β and every destination and relation under its DN. Treat renames as migrations. β οΈ The parent is wired throughtenant_dn, not aparent_dnattribute. Every resource in this module (aci_service_redirect_policy,aci_service_redirect_backup_policy,aci_l4_l7_redirect_health_group) is a classic (SDKv2) resource whose only parent-wiring attribute on the live schema istenant_dnβ there is noparent_dnalternative for these resources, sotenant_dnis the current attribute here, unlike migrated resources (e.g.aci_bridge_domain) whereparent_dnsupersedes a deprecatedtenant_dn.- βΉοΈ Every yes/no-style knob is a plain string on the wire.
anycast_enabled,program_local_pod_only,resilient_hash_enabled, andthreshold_enableare"yes"/"no"strings in the provider; this module surfaces them asbooland renders the string form inmain.tf. The threshold percentages are also strings under the hood; this module surfaces them asnumber. β οΈ anycast_enabledandprogram_local_pod_onlyare mutually exclusive. The provider rejects both being"yes"simultaneously; this module enforces that as a plan-timevalidationso the conflict surfaces before apply, not as an APIC fault.- βΉοΈ Health-group and IP-SLA relations are classic flat attributes.
relation_vns_rs_ipsla_monitoring_pol,relation_vns_rs_redirect_health_group, andrelation_vns_rs_l1_l2_redirect_health_groupeach take a raw DN string β unlike the typedrelation_to_*"by name" objects seen on migrated resources elsewhere in this suite. This module wires the two health-group relations internally fromhealth_group_key, resolving to the DN of a health group created in the same call. β οΈ l1_l2_destinations[*].concrete_interface_dn(the provider'srelation_vns_rs_to_c_if) is REQUIRED on the live provider schema, even though the published resource documentation describes it as optional β the live schema wins, and this module requires it accordingly.- βΉοΈ An L1/L2 destination's parent can be either policy type.
aci_pbr_l1_l2_destinationaccepts apolicy_based_redirect_dnpointing at either a service redirect policy or a service redirect backup policy; this module exposes that choice vial1_l2_destinations[*].backup_policy_keyinstead of requiring a second, near-duplicate module. β οΈ validate_relation_dn(provider defaulttrue) fails the apply if a referenced concrete interface or IP SLA monitoring policy DN does not exist β fix the missing object rather than disabling validation. Mistypedhealth_group_key/backup_policy_keyvalues are instead caught at plan time by this module's ownvalidationblocks.
Scope the caller's APIC login to the least privilege this module needs:
- Create / modify the redirect policy, its backup policy, health groups, and destinations: the
tenant-adminrole (or a custom role with tenant-l4-l7-services write privilege), scoped to the tenant's own security domain. - Referenced concrete interfaces and IP SLA monitoring policy: read privilege on each object named in
l1_l2_destinations[*].concrete_interface_dnandservice_redirect_policy.ip_sla_monitoring_policy_dn.
The module never sees a credential β authentication is a provider/caller concern supplied out of band (e.g. ACI_USERNAME / ACI_PASSWORD, or ACI_PRIVATE_KEY / ACI_CERT_NAME for signature-based auth).
- A reachable Cisco APIC (
ACI_URL) whose version is compatible with the~> 2.20provider, with the provider configured and authenticated by the caller. In production, setinsecure = falsewith proper CA trust β the provider's own default (insecure = true) is not a safe steady state. - The parent tenant (
tenant_dn) already exists. - Any concrete interface named in
l1_l2_destinations[*].concrete_interface_dnalready exists (typically created by an L4-L7 logical device module) so the provider's DN validation passes. - Any IP SLA monitoring policy named in
service_redirect_policy.ip_sla_monitoring_policy_dnalready exists. - Redirecting traffic through this policy requires a consuming contract subject or service graph template to point its PBR relation at this module's
idβ that wiring is the consumer's responsibility, not this module's.
terraform-aci-l4-l7-redirect/
βββ providers.tf # terraform{} + required_providers (aci ~> 2.20); no provider block
βββ variables.tf # tenant_dn + service_redirect_policy object + backup_policies / health_groups / destinations / l1_l2_destinations maps
βββ main.tf # aci_service_redirect_policy.this (keystone) + 4 for_each siblings/children
βββ outputs.tf # id (the policy DN) first, then name and per-collection DN maps
βββ README.md # this document
βββ SCOPE.md # cross-module contract (scope, consumes/emits, roles, prerequisites)
βββ LICENSE # MIT
βββ .gitignore # canonical library ignore set
# The caller configures the provider (authentication is out of band).
provider "aci" {
# username / password, or private_key + cert_name for signature auth;
# url = "https://apic.example.com"; set insecure = false in production.
}
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id # from terraform-aci-tenant
service_redirect_policy = { name = "fw-pbr" }
destinations = {
"10.10.1.10" = { ip = "10.10.1.10" }
}
}
output "redirect_policy_dn" {
value = module.redirect.id # pass this as the PBR relation target on a contract subject
}Consumes
| Input | Type | Typical source |
|---|---|---|
tenant_dn |
string (DN) | terraform-aci-tenant |
service_redirect_policy |
object({...}) |
caller (name + PBR posture + metadata tail + IP SLA relation DN) |
backup_policies |
map(object({...})) |
caller |
health_groups |
map(object({...})) |
caller |
destinations |
map(object({...})) |
caller |
l1_l2_destinations[*].concrete_interface_dn |
string (DN) | terraform-aci-l4-l7-device |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Service redirect policy DN (uni/tn-{tenant}/svcCont/svcRedirectPol-{name}) β primary reference |
contract subjects / service graph templates (PBR redirect relation) |
name |
Service redirect policy name | composition / audit |
backup_policy_dns |
Map of backup_policies key β backup policy DN |
audits / downstream reference |
health_group_dns |
Map of health_groups key β health group DN |
audits / downstream reference |
destination_dns |
Map of destinations key β L3 destination DN |
audits / downstream reference |
l1_l2_destination_dns |
Map of l1_l2_destinations key β L1/L2 destination DN |
audits / downstream reference |
1 Β· Minimal β an L3 redirect policy with secure defaults
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "fw-pbr" }
}π‘ The minimal call creates only the policy: destination type
L3, hashing on the full source/destination/protocol tuple, threshold monitoring disabled, and resilient hashing / anycast / pod-local programming all off. No destinations exist yet.
2 Β· Choosing a different hashing algorithm
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = {
name = "fw-pbr"
hashing_algorithm = "sip"
}
}βΉοΈ
hashing_algorithmmust be one ofsip,dip,sip-dip-prototype. The default (sip-dip-prototype) load-balances on the fullest available tuple; narrowing tosipordippins flows to a destination by only the source or destination address.
3 Β· Threshold monitoring with a deny-on-down posture
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = {
name = "fw-pbr"
threshold_enable = true
min_threshold_percent = 50
max_threshold_percent = 100
threshold_down_action = "deny"
}
}
β οΈ threshold_down_action = "deny"drops traffic outright when the destination pool falls belowmin_threshold_percenthealthy members β a deliberate fail-closed posture. The default"permit"instead forwards normally (bypassing PBR) when the pool is unhealthy.
4 Β· Resilient hashing for flow stability across pool changes
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = {
name = "fw-pbr"
resilient_hash_enabled = true
}
}βΉοΈ
resilient_hash_enabled = truekeeps existing flows pinned to their current destination when the pool's membership changes, instead of rehashing the entire flow table β reduces mid-session disruption when adding or removing a service node.
5 Β· Anycast-addressed destination pool (opt-in)
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = {
name = "fw-pbr-anycast"
anycast_enabled = true
}
}
β οΈ anycast_enabledandprogram_local_pod_onlyare mutually exclusive β setting bothtruefails plan-time validation. Anycast addressing lets destinations share a single virtual address across pods; pod-local programming instead confines redirect to the local pod's leaf switches.
6 Β· A single L3 destination
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "fw-pbr" }
destinations = {
"10.10.1.10" = { ip = "10.10.1.10", mac = "00:50:56:AA:BB:CC" }
}
}π‘
destinationsis a map keyed by a stable natural key β here the destination IP itself β so adding a second destination later never disturbs this one's address in state.
7 Β· Multiple L3 destinations (an active/active pool)
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "fw-pbr" }
destinations = {
"10.10.1.10" = { ip = "10.10.1.10", mac = "00:50:56:AA:BB:CC", dest_name = "fw-node-1" }
"10.10.1.11" = { ip = "10.10.1.11", mac = "00:50:56:AA:BB:CD", dest_name = "fw-node-2" }
}
}βΉοΈ Each map entry becomes its own
aci_destination_of_redirected_trafficresource viafor_eachβ order-independent, and safe to add or remove entries without touching the others.
8 Β· A redirect health group bound to a destination
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "fw-pbr" }
health_groups = {
"fw-health" = { name = "fw-health" }
}
destinations = {
"10.10.1.10" = {
ip = "10.10.1.10"
health_group_key = "fw-health"
}
}
}βΉοΈ
health_group_keyreferences a key inhealth_groupsand is wired internally to the destination's DN-based health-group relation β no manual DN plumbing required. A destination that stops responding to health tracking is pulled from the active pool.
9 Β· A paired PBR backup policy
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "fw-pbr" }
backup_policies = {
"fw-pbr-backup" = { name = "fw-pbr-backup" }
}
}βΉοΈ A backup policy is a tenant-scoped sibling of the primary policy, not nested under its DN. Its own L1/L2 destinations are attached separately via
l1_l2_destinations[*].backup_policy_keyβ see example 11.
10 Β· An L1/L2 destination bound to a concrete interface
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "transparent-fw-pbr" }
l1_l2_destinations = {
"fw-l1l2-1" = {
destination_name = "fw-l1l2-1"
concrete_interface_dn = module.l4l7_device.concrete_interface_dns["fw-node-1-if1"]
}
}
}
β οΈ concrete_interface_dnis required on the live provider schema for every L1/L2 destination β it is what makes transparent (non-routed) service insertion possible.
11 Β· An L1/L2 destination under the backup policy instead of the primary
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "transparent-fw-pbr" }
backup_policies = {
"fw-pbr-backup" = { name = "fw-pbr-backup" }
}
l1_l2_destinations = {
"fw-l1l2-backup-1" = {
destination_name = "fw-l1l2-backup-1"
concrete_interface_dn = module.l4l7_device.concrete_interface_dns["fw-node-2-if1"]
backup_policy_key = "fw-pbr-backup"
}
}
}βΉοΈ Setting
backup_policy_keycreates this L1/L2 destination under the named backup policy's DN instead of the primary keystone's β the key must match an entry inbackup_policies, validated at plan time.
12 Β· Binding to an IP SLA monitoring policy
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = {
name = "fw-pbr"
ip_sla_monitoring_policy_dn = module.ip_sla.id
}
}βΉοΈ
ip_sla_monitoring_policy_dnis a classic flat relation β a raw DN, unlike the "by name" typed relations on migrated resources elsewhere in this suite. The referenced IP SLA monitoring policy must exist so the provider's DN validation passes.
13 Β· A custom annotation and GUI alias
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = {
name = "fw-pbr"
name_alias = "Firewall-PBR"
annotation = "orchestrator:terraform:platform-team"
}
}π Keep the
orchestrator:terraformprefix so Terraform-managed objects stay identifiable in APIC. This suite defaultsannotationtoorchestrator:terraform; override it only to extend, not to erase, that marker.
14 Β· A fully-specified PBR configuration (all pieces combined)
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = {
name = "fw-pbr"
description = "Firewall PBR β production web tier"
hashing_algorithm = "sip-dip-prototype"
resilient_hash_enabled = true
threshold_enable = true
min_threshold_percent = 50
max_threshold_percent = 100
threshold_down_action = "bypass"
}
backup_policies = {
"fw-pbr-backup" = { name = "fw-pbr-backup" }
}
health_groups = {
"fw-health" = { name = "fw-health" }
}
destinations = {
"10.10.1.10" = { ip = "10.10.1.10", dest_name = "fw-node-1", health_group_key = "fw-health" }
"10.10.1.11" = { ip = "10.10.1.11", dest_name = "fw-node-2", health_group_key = "fw-health" }
}
}15 Β· Many redirect policies from one definition (caller-side for_each)
locals {
redirect_policies = {
"fw-pbr" = {
service_redirect_policy = { name = "fw-pbr" }
destinations = { "10.10.1.10" = { ip = "10.10.1.10" } }
}
"lb-pbr" = {
service_redirect_policy = { name = "lb-pbr" }
destinations = { "10.10.2.10" = { ip = "10.10.2.10" } }
}
}
}
module "redirects" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
for_each = local.redirect_policies
tenant_dn = module.tenant.id
service_redirect_policy = each.value.service_redirect_policy
destinations = each.value.destinations
}
output "redirect_policy_dns" {
value = { for k, m in module.redirects : k => m.id }
}π‘ Instantiate the module with
for_eachat the caller level to manage a fleet of redirect policies (one per service tier or device pair) from a single, auditable map.
16 Β· ποΈ End-to-end composition β tenant β L4-L7 device β redirect policy β contract subject
provider "aci" {
# configured + authenticated by the caller; insecure = false in production
}
# 1) The tenant β the root everything nests under.
module "tenant" {
source = "git::https://github.com/microsoftexpert/terraform-aci-tenant.git?ref=v1.0.0"
tenant = { name = "core-prod", description = "Core production tenant" }
}
# 2) An L4-L7 logical device exposing concrete interfaces for L1/L2 insertion.
module "l4l7_device" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-device.git?ref=v1.0.0"
tenant_dn = module.tenant.id
device = { name = "fw-cluster" }
}
# 3) This module: a redirect policy with a health group and an L3 destination.
module "redirect" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l4-l7-redirect.git?ref=v1.0.0"
tenant_dn = module.tenant.id
service_redirect_policy = { name = "fw-pbr", resilient_hash_enabled = true }
health_groups = {
"fw-health" = { name = "fw-health" }
}
destinations = {
"10.10.1.10" = { ip = "10.10.1.10", dest_name = "fw-node-1", health_group_key = "fw-health" }
}
}
# 4) A contract whose subject points its PBR relation at this policy's DN.
module "contract" {
source = "git::https://github.com/microsoftexpert/terraform-aci-contract.git?ref=v1.0.0"
tenant_dn = module.tenant.id
contract = { name = "web-to-app" }
subjects = {
"web-to-fw" = {
name = "web-to-fw"
redirect_policy_dn = module.redirect.id
}
}
}
output "redirect_policy_dn" { value = module.redirect.id }ποΈ One tenant, one L4-L7 device, and one redirect policy in; a fully health-tracked PBR path out β the contract subject binds to it purely by consuming
module.redirect.id. This module is the traffic-steering anchor between the device's data plane and every contract that redirects through it.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
tenant_dn |
string |
β | β | Parent tenant DN (uni/tn-{name}); wired to every resource's tenant_dn. |
service_redirect_policy |
object({...}) |
β | β | The redirect policy: name, PBR posture, metadata tail, and IP SLA relation DN. |
backup_policies |
map(object({...})) |
β | {} |
Paired PBR backup policies, keyed by a stable natural key. |
health_groups |
map(object({...})) |
β | {} |
Redirect health groups, keyed by a stable natural key. |
destinations |
map(object({...})) |
β | {} |
L3 destinations under the policy, keyed by destination IP. |
l1_l2_destinations |
map(object({...})) |
β | {} |
L1/L2 destinations, keyed by destination name. |
Full input schema (from variables.tf)
variable "tenant_dn" {
type = string
# validation: must match ^uni/tn- (a tenant DN)
}
variable "service_redirect_policy" {
type = object({
name = string # REQUIRED, immutable (force-new), 1-64 chars
description = optional(string, null)
dest_type = optional(string, "L3") # L1 | L2 | L3
hashing_algorithm = optional(string, "sip-dip-prototype") # sip | dip | sip-dip-prototype
threshold_down_action = optional(string, "permit") # bypass | deny | permit
anycast_enabled = optional(bool, false) # mutually exclusive with program_local_pod_only
program_local_pod_only = optional(bool, false) # mutually exclusive with anycast_enabled
resilient_hash_enabled = optional(bool, false)
threshold_enable = optional(bool, false)
min_threshold_percent = optional(number, null) # 0-100
max_threshold_percent = optional(number, null) # 0-100
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
ip_sla_monitoring_policy_dn = optional(string, null) # classic flat relation, raw DN
})
# validations: name length/charset; dest_type/hashing_algorithm/threshold_down_action enums;
# anycast_enabled/program_local_pod_only mutual exclusion; threshold percent ranges
}
variable "backup_policies" {
type = map(object({
name = string # REQUIRED, immutable, 1-64 chars
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
}))
default = {}
# validations: name length/charset
}
variable "health_groups" {
type = map(object({
name = string # REQUIRED, immutable, 1-64 chars
description = optional(string, null)
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
}))
default = {}
# validations: name length/charset
}
variable "destinations" {
type = map(object({
ip = string # REQUIRED
ip2 = optional(string, null)
mac = optional(string, null) # required by APIC releases before 5.2
dest_name = optional(string, null)
pod_id = optional(number, 1) # 1-255
health_group_key = optional(string, null) # key into var.health_groups
description = optional(string, null)
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
}))
default = {}
# validations: pod_id range; health_group_key must exist in var.health_groups
}
variable "l1_l2_destinations" {
type = map(object({
destination_name = string # REQUIRED
name = optional(string, null)
mac = optional(string, null)
pod_id = optional(number, 1) # 1-255
concrete_interface_dn = string # REQUIRED on the live provider schema
health_group_key = optional(string, null) # key into var.health_groups
backup_policy_key = optional(string, null) # key into var.backup_policies
description = optional(string, null)
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
}))
default = {}
# validations: destination_name length; pod_id range; health_group_key / backup_policy_key existence
}| Output | Description | Notes |
|---|---|---|
id |
Service redirect policy Distinguished Name (uni/tn-{tenant}/svcCont/svcRedirectPol-{name}) |
Primary cross-module reference β pass as the PBR relation target on a contract subject / service graph template. |
name |
Service redirect policy name | For composition / audit. |
backup_policy_dns |
Map of backup_policies key β backup policy DN |
For downstream reference / audit. |
health_group_dns |
Map of health_groups key β health group DN |
For downstream reference / audit. |
destination_dns |
Map of destinations key β L3 destination DN |
For downstream reference / audit. |
l1_l2_destination_dns |
Map of l1_l2_destinations key β L1/L2 destination DN |
For downstream reference / audit. |
- One keystone, four
for_eachcollections.aci_service_redirect_policy.thisis the keystone;aci_service_redirect_backup_policy.thisandaci_l4_l7_redirect_health_group.thisare tenant-scoped siblings (parented bytenant_dn, not the keystone's DN), whileaci_destination_of_redirected_traffic.thisandaci_pbr_l1_l2_destination.thisare true children (parented by the keystone's or a backup policy's DN). - All five resources are classic (SDKv2). None expose a
parent_dnalternative; every parent-wiring attribute (tenant_dn,service_redirect_policy_dn,policy_based_redirect_dn) is the current, non-deprecated attribute for its resource. - Internal by-key relation wiring.
destinations[*].health_group_key,l1_l2_destinations[*].health_group_key, andl1_l2_destinations[*].backup_policy_keyreference natural keys in this module's ownhealth_groups/backup_policiesmaps;main.tfresolves each to the sibling resource's DN with a conditional expression, andvariables.tfvalidates the key exists beforeplanreaches the resource graph. - Boolean and numeric ergonomics. Every yes/no-style knob and both threshold percentages are provider strings; this module surfaces them as
bool/numberand renders the string form (tostring(),? "yes" : "no") inmain.tf. - Stable
for_eachkeys. Every collection is keyed by a stable natural key (policy name, health-group name, destination IP, or destination name) β nevercountβ so inserting or removing one entry never disturbs another's address in state. - Secure by default. The minimal call yields destination type
L3, full-tuple hashing, threshold monitoring disabled, and every permissive flag (anycast_enabled,program_local_pod_only,resilient_hash_enabled,threshold_enable) off.
| Concern | Secure default | How to opt out (deliberately) |
|---|---|---|
service_redirect_policy.dest_type |
L3 β routed redirect |
Set L1 / L2 for transparent service insertion via l1_l2_destinations. |
service_redirect_policy.threshold_enable |
false β no threshold-based bypass |
Set true with min_threshold_percent / max_threshold_percent to fail predictably when the pool degrades. |
service_redirect_policy.anycast_enabled / program_local_pod_only |
false / false |
Set exactly one true for anycast or pod-local programming; both true is rejected at plan time. |
service_redirect_policy.resilient_hash_enabled |
false |
Set true to preserve existing flows' hash bucket across pool membership changes. |
service_redirect_policy.annotation |
orchestrator:terraform β Terraform-managed objects stay identifiable in APIC |
Extend the marker; do not blank it. |
service_redirect_policy.ip_sla_monitoring_policy_dn |
null β no relation forced |
Set explicitly to bind IP SLA monitoring. |
destinations[*].health_group_key / l1_l2_destinations[*].health_group_key |
null β no health tracking forced |
Set to a key in health_groups to bind health tracking. |
| Transport (provider) | This suite instructs callers to set insecure = false with CA trust |
The provider default is insecure = true; do not keep it as a steady state. |
| Secrets | None accepted or emitted | n/a β this module carries no secret material; credentials are provider config. |
# From the module directory (offline, no credentials, no backend):
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module by immutable tag:
?ref=v1.0.0β never a branch. - This module is plan-only from the library's perspective. A human runs
terraform plan/applyagainst a sub-production APIC from their own pipeline, with a login scoped to the permissions above. No cloud apply happens here.
The offline proof gate for this module:
- β
terraform validateβ parses the module, resolves theservice_redirect_policyand every collection's object type, runs the name/enum/mutual-exclusion/range/key-existence validations, and confirms every argument exists in the provider schema. - β
terraform fmt -checkβ canonical formatting. - β Not exercised offline (only a real
plan/applyagainst an APIC covers these): DN validation ofconcrete_interface_dnandip_sla_monitoring_policy_dn(server-sidevalidate_relation_dn), APIC-side name-collision checks, health-group up/down state, and the computed DNs returned asid/ the per-collection DN maps.
$ terraform output
id = "uni/tn-core-prod/svcCont/svcRedirectPol-fw-pbr"
name = "fw-pbr"
backup_policy_dns = {}
health_group_dns = {
"fw-health" = "uni/tn-core-prod/svcCont/redirectHealthGroup-fw-health"
}
destination_dns = {
"10.10.1.10" = "uni/tn-core-prod/svcCont/svcRedirectPol-fw-pbr/RedirectDest_ip-[10.10.1.10]"
}
l1_l2_destination_dns = {}
| Symptom | Cause | Fix |
|---|---|---|
service_redirect_policy.name must be 1-64 characters |
Name is empty or too long | Use a 1-64 character name. |
service_redirect_policy.name may contain only letters, digits, and the characters _ . : - |
Name has spaces or unsupported characters | Remove spaces/special characters (ACI naming rules). |
Changing service_redirect_policy.name wants to destroy/recreate the policy |
name is immutable (force-new) |
Treat a rename as a migration; expect the policy and its destinations to be replaced. |
anycast_enabled and program_local_pod_only cannot both be true |
Both flags set true in the same call |
Set only one, matching the intended addressing model. |
... must be one of: L1, L2, L3 (or similar enum error) |
A posture field is outside its closed enum | Use one of the documented values for that field. |
health_group_key must be null or a key present in var.health_groups |
Typo, or the health group wasn't added to health_groups |
Add the health group entry, or correct the key. |
backup_policy_key must be null or a key present in var.backup_policies |
Typo, or the backup policy wasn't added to backup_policies |
Add the backup policy entry, or correct the key. |
Apply fails validating concrete_interface_dn / ip_sla_monitoring_policy_dn |
The referenced object does not exist yet | Create the referenced object first (or in the same apply); do not disable validate_relation_dn. |
| Traffic isn't being redirected at all | No consumer (contract subject / service graph template) points its PBR relation at this module's id |
Wire module.redirect.id into the consuming subject's redirect relation. |
Post ... 401 / authentication error |
Provider not configured or wrong credentials | Configure the aci provider with valid credentials and url; prefer signature auth for automation. |
- Cisco ACI provider β
aci_service_redirect_policy - Cisco ACI provider β
aci_service_redirect_backup_policy - Cisco ACI provider β
aci_destination_of_redirected_traffic - Cisco ACI provider β
aci_l4_l7_redirect_health_group - Cisco ACI provider β
aci_pbr_l1_l2_destination - Cisco ACI provider β provider configuration & authentication
- Cisco APIC object model β classes
vnsSvcRedirectPol(redirect policy),vnsBackupPol(backup policy),vnsRedirectDest(L3 destination),vnsL1L2RedirectDest(L1/L2 destination),vnsRedirectHealthGroup(health group). - Sibling modules:
terraform-aci-tenant,terraform-aci-l4-l7-device,terraform-aci-ip-sla,terraform-aci-contract,terraform-aci-l4-l7-service-graph-template. - This module's
SCOPE.mdβ the cross-module contract.
π "Infrastructure as Code should be standardized, consistent, and secure."