Skip to content

microsoftexpert/tf-mod-aws-dx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🟧 AWS Direct Connect Terraform Module

Provisions a dedicated Direct Connect connection (or LAG) together with its private/public/transit virtual interfaces, an optional Direct Connect gateway, and its VGW/Transit-Gateway associations — a complete, BGP-ready hybrid-network on-ramp from one module call. Built for the AWS provider v6.x.

Terraform aws module type resources


🧩 Overview

  • 🔌 One on-ramp, fully wired. Creates aws_dx_connection (keystone) plus everything that is meaningless without it: an optional LAG, the private/public/transit virtual interfaces ridden on it, an optional Direct Connect gateway, and its VGW/TGW associations.
  • 🧵 VIFs ride the right target automatically. Each virtual interface attaches to the LAG when one is created, otherwise to the bare connection — overridable per VIF with connection_id.
  • 🚪 Gateway fallback built in. Private/transit VIFs and gateway associations that omit an explicit dx_gateway_id attach to the module-created DX gateway when present, so you describe intent, not plumbing.
  • 🗺️ for_each, never count. VIFs and associations are keyed by stable caller strings, so adding or removing one never re-indexes the rest.
  • 🔐 Secure-by-default leanings. MACsec encryption_mode is surfaced (prefer should_encrypt/must_encrypt for PII), private/transit VIFs are preferred over public, and per-VIF BGP MD5 auth is supported.
  • 🏷️ Tags everywhere. var.tags flows to the connection, LAG, DX gateway, and every VIF and merges with provider default_tags; the merged set is surfaced as tags_all.
  • 🌍 Provider-inherited Region. No region variable — the caller's provider (or alias) sets the Region; the DX location code, not a Terraform argument, pins the physical facility.

💡 Why it matters: A consistent, secure-by-default Direct Connect on-ramp keeps hybrid connectivity to the estate predictable and auditable. The blast radius of a misrouted VIF or an unencrypted PII path is large — this module makes the secure shape the easy shape.


❤️ 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

tf-mod-aws-dx is a networking edge module — it consumes a VGW from tf-mod-aws-vpn or a Transit Gateway from tf-mod-aws-transit-gateway as association targets, and (when MACsec is used) a secret ARN from tf-mod-aws-secrets-manager. It originates the VIFs and DX gateway that carry on-prem traffic into the VPC fabric.

flowchart LR
 vpc["tf-mod-aws-vpc<br/>VPC + subnets"]
 vpn["tf-mod-aws-vpn<br/>VGW"]
 tgw["tf-mod-aws-transit-gateway"]
 sm["tf-mod-aws-secrets-manager<br/>MACsec CKN/CAK"]
 dx["tf-mod-aws-dx"]
 onprem["On-prem / colo<br/>router (BGP)"]
 pub["Public AWS<br/>service endpoints"]

 vpn -->|"vpn_gateway_id"| dx
 tgw -->|"transit_gateway_id"| dx
 sm -->|"secret arn (MACsec)"| dx
 vpc -.->|"VGW attaches to VPC"| vpn
 onprem ===|"BGP over cross-connect"| dx
 dx -->|"public VIF"| pub

 style dx fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
Loading

🧬 What this module builds

flowchart TD
 subgraph mod["tf-mod-aws-dx"]
 conn["aws_dx_connection.this<br/>(keystone)<br/>dedicated port @ DX location"]
 lag["aws_dx_lag.this<br/>optional, guarded for_each"]
 gw["aws_dx_gateway.this<br/>optional, guarded for_each"]
 priv["aws_dx_private_virtual_interface.this<br/>for_each private_virtual_interfaces"]
 pub["aws_dx_public_virtual_interface.this<br/>for_each public_virtual_interfaces"]
 tran["aws_dx_transit_virtual_interface.this<br/>for_each transit_virtual_interfaces"]
 assoc["aws_dx_gateway_association.this<br/>for_each gateway_associations"]
 end

 conn -->|"migrated into"| lag
 lag -.->|"VIFs ride LAG when present"| priv
 lag -.-> pub
 lag -.-> tran
 conn -->|"else VIFs ride connection"| priv
 conn --> pub
 conn --> tran
 gw --> priv
 gw --> tran
 gw --> assoc

 style conn fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
 style lag stroke-dasharray: 5 5
 style gw stroke-dasharray: 5 5
Loading
Resource Count Created when
aws_dx_connection.this 1 always (keystone)
aws_dx_lag.this 0 or 1 lag != null
aws_dx_gateway.this 0 or 1 dx_gateway != null
aws_dx_private_virtual_interface.this 0..N one per private_virtual_interfaces entry
aws_dx_public_virtual_interface.this 0..N one per public_virtual_interfaces entry
aws_dx_transit_virtual_interface.this 0..N one per transit_virtual_interfaces entry
aws_dx_gateway_association.this 0..N one per gateway_associations entry

✅ Provider / Versions

Requirement Version
Terraform >= 1.12.0
hashicorp/aws >= 6.0, < 7.0

The module declares only a required_providers block (providers.tf) and inherits the configured provider. There is no provider {} block and no credential variable — credentials resolve through the standard AWS chain at the root/pipeline level (env vars → SSO/shared credentials → assume_role → instance profile / IRSA → OIDC web identity).


🔑 Required IAM Permissions

Least-privilege actions the Terraform execution identity needs to manage this module.

Action Required for Notes
directconnect:CreateConnection, directconnect:DeleteConnection, directconnect:DescribeConnections Connection lifecycle Keystone CRUD
directconnect:UpdateConnection encryption_mode / connection attribute updates Only when MACsec attributes change
directconnect:CreateLag, directconnect:DeleteLag, directconnect:DescribeLags, directconnect:AssociateConnectionWithLag LAG lifecycle Only when lag is set; migrates the keystone into the LAG
directconnect:CreatePrivateVirtualInterface, directconnect:CreatePublicVirtualInterface, directconnect:CreateTransitVirtualInterface VIF creation One per VIF map entry
directconnect:DeleteVirtualInterface, directconnect:DescribeVirtualInterfaces VIF lifecycle
directconnect:CreateDirectConnectGateway, directconnect:DeleteDirectConnectGateway, directconnect:DescribeDirectConnectGateways DX gateway lifecycle Only when dx_gateway is set
directconnect:CreateDirectConnectGatewayAssociation, directconnect:DeleteDirectConnectGatewayAssociation, directconnect:DescribeDirectConnectGatewayAssociations DX gateway ↔ VGW/TGW association Single-account path
directconnect:AcceptDirectConnectGatewayAssociationProposal, directconnect:DescribeDirectConnectGatewayAssociationProposals Cross-account association Only with proposal_id + associated_gateway_owner_account_id
directconnect:TagResource, directconnect:UntagResource, directconnect:DescribeTags Tagging Connection / LAG / DX gateway / VIFs
ec2:DescribeVpnGateways, ec2:DescribeTransitGateways Resolving association targets Read-only on VGW/TGW

🔐 MACsec: when MACsec is enabled, the identity additionally needs secretsmanager:GetSecretValue on the CKN/CAK secret ARN, plus directconnect:AssociateMacSecKey if key association is performed (key association itself is out of scope for this module).

ℹ️ No service-linked role and no iam:PassRole are required by Direct Connect for the resources this module manages.


📋 AWS Prerequisites

  • Physical cross-connect (mandatory). A Letter of Authorization / Connecting Facility Assignment (LOA-CFA) and a provisioned cross-connect at a Direct Connect location are required before a dedicated connection becomes operational. This is arranged out of band with the colocation/carrier provider — Terraform creates only the logical connection, which sits in requested/pending until the cross-connect is completed.
  • No service-linked role is required for Direct Connect itself.
  • DX location code. location is a DX facility code (e.g. EqDC2), not a Region. Discover valid codes with the aws_dx_locations data source or DescribeLocations.
  • Association targets. A VGW (tf-mod-aws-vpn) or Transit Gateway (tf-mod-aws-transit-gateway) must exist before a DX gateway association is created. Transit VIFs require a DX gateway.
  • BGP. Each VIF needs a customer-side BGP ASN; public VIFs additionally require public IPv4/IPv6 prefixes you own (route_filter_prefixes). AWS assigns the Amazon-side ASN unless overridden via dx_gateway.amazon_side_asn.
  • MACsec (optional). Only supported on specific dedicated port speeds/locations; CKN/CAK material lives in Secrets Manager and is associated out of band.
  • Quotas (per Direct Connect quotas):
  • 50 virtual interfaces per dedicated connection (adjustable).
  • 2 LAGs per Region and up to 10 connections per LAG (port speeds must match within a LAG).
  • 100 BGP advertised routes per private/transit VIF; 1,000 per public VIF.
  • 3 DX gateways in a transit-gateway association by default; 10 TGWs/VGWs per DX gateway.
  • Raise via Service Quotas where adjustable.

📁 Module Structure

tf-mod-aws-dx/
├── providers.tf # required_providers (aws >= 6.0, < 7.0); no provider block
├── variables.tf # name → required conn args → optional conn → LAG → gateway → VIF maps → associations → tags
├── main.tf # aws_dx_connection.this + LAG / gateway / VIF for_each + associations
├── outputs.tf # id + arn + connection/LAG/gateway/VIF/association attributes + tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM permissions, prerequisites, gotchas

⚙️ Quick Start

Smallest working call — a dedicated 1 Gbps connection with a single private VIF to an existing VGW:

module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-primary"
  bandwidth = "1Gbps"
  location  = "EqDC2"

  private_virtual_interfaces = {
    prod-vif = {
      bgp_asn        = 65000
      vlan           = 100
      vpn_gateway_id = module.vpn.vpn_gateway_id # from tf-mod-aws-vpn
    }
  }

  tags = {
    Environment = "prod"
    CostCenter  = "1234"
  }
}

🔌 Cross-Module Contract

Consumes

Input Type Source module
private_virtual_interfaces[*].vpn_gateway_id string (VGW id) tf-mod-aws-vpn
gateway_associations[*].associated_gateway_id string (VGW or TGW id) tf-mod-aws-vpn / tf-mod-aws-transit-gateway
*.dx_gateway_id (when using an external gateway) string this module / external
MACsec CKN/CAK secret (by ARN, out of band) string (Secrets Manager ARN) tf-mod-aws-secrets-manager

Networking-edge module — it needs no sibling output to create the connection itself; association targets are wired in only when you attach a DX gateway to a VGW/TGW.

Emits

Output Description Consumed by
id DX connection id (dxcon-…) VIF wiring, monitoring
arn DX connection ARN — the cross-resource reference type IAM policies, tagging audits
connection_id / connection_arn / connection_name Connection aliases LAG / VIF references
aws_device DX endpoint device the port terminates on resiliency planning
jumbo_frame_capable / macsec_capable / has_logical_redundancy Connection capability flags MTU / MACsec / redundancy decisions
port_encryption_status MACsec port link encryption status PII posture verification
owner_account_id Account that owns the connection cross-account sharing
vif_target_id LAG id when present, else connection id (what VIFs ride) wiring checks
lag_id / lag_arn LAG identifiers when a LAG is created; else null connection association
dx_gateway_id / dx_gateway_arn / dx_gateway_owner_account_id DX gateway identifiers when created; else null TGW/VGW associations, cross-account sharing
private_virtual_interface_ids / public_… / transit_… Per-type map of VIF key → id route propagation, monitoring
virtual_interface_ids / virtual_interface_arns Combined map of all VIF key → id / ARN route propagation, monitoring
gateway_association_ids Map of association key → association id drift / inspection
gateway_association_states Map of association key → associated gateway type (transitGateway / virtualPrivateGateway) operational verification
transit_gateway_attachment_ids Map of association key → TGW attachment id (transitGateway only) TGW route-table association
tags_all All tags incl. provider default_tags (resource tags win) governance/audit

📚 Example Library

1 · Private VIF to a VGW (from tf-mod-aws-vpn)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-vgw"
  bandwidth = "1Gbps"
  location  = "EqDC2"

  private_virtual_interfaces = {
    prod = {
      bgp_asn        = 65000
      vlan           = 100
      vpn_gateway_id = module.vpn.vpn_gateway_id
    }
  }
}
2 · DX gateway + private VIF (auto-attach to the module gateway)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-dxgw"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  dx_gateway = {
    name            = "casey-dxgw"
    amazon_side_asn = "64512"
  }

  # Omits both vpn_gateway_id and dx_gateway_id → attaches to the module's DX gateway.
  private_virtual_interfaces = {
    prod = {
      bgp_asn = 65000
      vlan    = 101
    }
  }
}
3 · Transit VIF to a Transit Gateway (from tf-mod-aws-transit-gateway)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-transit"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  dx_gateway = {
    name            = "casey-dxgw-transit"
    amazon_side_asn = "64512"
  }

  transit_virtual_interfaces = {
    prod = {
      bgp_asn = 65000
      vlan    = 200
      # dx_gateway_id omitted → uses the module's DX gateway
    }
  }

  gateway_associations = {
    tgw = {
      associated_gateway_id = module.tgw.transit_gateway_id # from tf-mod-aws-transit-gateway
      allowed_prefixes      = ["10.0.0.0/8"]
    }
  }
}
4 · Public VIF (advertise owned public prefixes)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-public"
  bandwidth = "1Gbps"
  location  = "EqDC2"

  # Public VIFs reach public AWS endpoints; prefer private/transit for PII traffic.
  public_virtual_interfaces = {
    edge = {
      bgp_asn               = 65000
      vlan                  = 300
      route_filter_prefixes = ["203.0.113.0/24"] # public CIDRs you own
    }
  }
}
5 · LAG bundling dedicated connections
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-lag-member"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  lag = {
    name                  = "casey-dx-lag"
    connections_bandwidth = "10Gbps"
    location              = "EqDC2"
    # connection_id defaults to the keystone connection (migrated into the LAG)
  }

  # VIFs ride the LAG automatically once it exists.
  private_virtual_interfaces = {
    prod = {
      bgp_asn        = 65000
      vlan           = 100
      vpn_gateway_id = module.vpn.vpn_gateway_id
    }
  }
}
6 · Tags (merge with provider default_tags)
# Caller's provider block owns default_tags; the module never sets it.
provider "aws" {
  default_tags { tags = { Owner = "platform", ManagedBy = "terraform" } }
}

module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-tagged"
  bandwidth = "1Gbps"
  location  = "EqDC2"

  tags = {
    Environment = "prod" # resource tag — wins over default_tags on key conflict
    DataClass   = "internal"
  }
}

# module.dx.tags_all == { Owner, ManagedBy, Environment, DataClass }
7 · Per-resource tag overrides (connection vs LAG vs VIF)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-scoped-tags"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  tags = { Environment = "prod" } # module-wide

  lag = {
    name                  = "casey-dx-lag"
    connections_bandwidth = "10Gbps"
    location              = "EqDC2"
    tags                  = { Tier = "transport" } # merged over module tags on the LAG
  }

  private_virtual_interfaces = {
    prod = {
      bgp_asn        = 65000
      vlan           = 100
      vpn_gateway_id = module.vpn.vpn_gateway_id
      tags           = { Tier = "data-plane" } # merged over module tags on this VIF
    }
  }
}
8 · MACsec-capable connection with secure encryption mode
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-macsec"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  request_macsec  = true             # FORCE-NEW — only on supported speeds/locations
  encryption_mode = "should_encrypt" # use "must_encrypt" to hard-fail unencrypted traffic

  # CKN/CAK association (aws_dx_macsec_key_association + Secrets Manager) is out of
  # scope for this module — wire it from tf-mod-aws-secrets-manager out of band.
}
9 · Jumbo frames + per-VIF BGP MD5 auth
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-jumbo"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  private_virtual_interfaces = {
    prod = {
      bgp_asn          = 65000
      vlan             = 100
      vpn_gateway_id   = module.vpn.vpn_gateway_id
      mtu              = 9001        # jumbo frames (private VIF: 1500 | 9001)
      bgp_auth_key     = var.bgp_md5 # supply via a sensitive variable, not literal
      amazon_address   = "169.254.0.1/30"
      customer_address = "169.254.0.2/30"
    }
  }
}
10 · IPv6 private VIF
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-v6"
  bandwidth = "1Gbps"
  location  = "EqDC2"

  private_virtual_interfaces = {
    prod-v6 = {
      bgp_asn        = 65000
      vlan           = 110
      address_family = "ipv6" # AWS auto-assigns peer addresses for IPv6
      vpn_gateway_id = module.vpn.vpn_gateway_id
    }
  }
}
11 · SiteLink-enabled transit VIF
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-sitelink"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  dx_gateway = {
    name            = "casey-dxgw-sl"
    amazon_side_asn = "64512"
  }

  transit_virtual_interfaces = {
    prod = {
      bgp_asn          = 65000
      vlan             = 250
      sitelink_enabled = true # PoP-to-PoP connectivity over the DX network
      mtu              = 8500 # transit VIF jumbo: 1500 | 8500
    }
  }
}
12 · Cross-account DX gateway association (accept a proposal)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-xacct"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  dx_gateway = {
    name            = "casey-dxgw-shared"
    amazon_side_asn = "64512"
  }

  # The TGW/VGW owner (other account) creates a proposal; this side accepts it.
  gateway_associations = {
    partner-tgw = {
      proposal_id                         = "abcd1234-..."
      associated_gateway_owner_account_id = "111122223333"
      allowed_prefixes                    = ["10.20.0.0/16"]
    }
  }
}
13 · connection_id override (VIF on a specific port)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-override"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  private_virtual_interfaces = {
    on-existing = {
      bgp_asn        = 65000
      vlan           = 120
      connection_id  = "dxcon-existingid" # ride a pre-existing connection, not the keystone
      vpn_gateway_id = module.vpn.vpn_gateway_id
    }
  }
}
14 · skip_destroy (preserve the physical port on teardown)
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name      = "casey-dx-keep-port"
  bandwidth = "10Gbps"
  location  = "EqDC2"

  # On destroy, removed from state but NOT deleted in AWS (physical cross-connect kept).
  skip_destroy = true
}
15 · End-to-end composition — hybrid on-ramp (VPC + VPN/VGW + TGW + DX)
# Network foundation
module "vpc" {
  source     = "git::https://github.com/microsoftexpert/tf-mod-aws-vpc?ref=v1.0.0"
  name       = "casey-core"
  cidr_block = "10.0.0.0/16"
}

# Transit Gateway hub
module "tgw" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-transit-gateway?ref=v1.0.0"
  name   = "casey-hub"
}

# MACsec secret (CKN/CAK) — associated out of band
module "macsec" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-secrets-manager?ref=v1.0.0"
  name   = "casey/dx/macsec"
}

# This module — connection + DX gateway + transit VIF + TGW association
module "dx" {
  source = "git::https://github.com/microsoftexpert/tf-mod-aws-dx?ref=v1.0.0"

  name            = "casey-dx-prod"
  bandwidth       = "10Gbps"
  location        = "EqDC2"
  request_macsec  = true
  encryption_mode = "should_encrypt"

  dx_gateway = {
    name            = "casey-dxgw-prod"
    amazon_side_asn = "64512"
  }

  transit_virtual_interfaces = {
    prod = {
      bgp_asn = 65000
      vlan    = 200
    }
  }

  gateway_associations = {
    hub = {
      associated_gateway_id = module.tgw.transit_gateway_id
      allowed_prefixes      = [module.vpc.cidr_block]
    }
  }

  tags = { Environment = "prod", DataClass = "internal" }
}

# Wire downstream: associate module.dx.transit_gateway_attachment_ids["hub"]
# with the appropriate TGW route table in the hub.

📥 Inputs

Name Type Default Description
name string required Connection display name (keystone).
bandwidth string required Port speed (e.g. 1Gbps, 10Gbps). FORCE-NEW.
location string required DX location code (e.g. EqDC2). FORCE-NEW.
provider_name string null Carrier/colocation provider name.
request_macsec bool false Request a MACsec-capable connection. FORCE-NEW.
encryption_mode string null no_encrypt / should_encrypt / must_encrypt (MACsec-capable, when available).
skip_destroy bool false Remove from state on destroy without deleting in AWS.
lag object({…}) null Optional LAG; the keystone connection is migrated into it.
dx_gateway object({ name, amazon_side_asn, tags }) null Optional Direct Connect gateway (private/transit fallback target).
private_virtual_interfaces map(object({…})) {} Private VIFs to a VGW / DX gateway, keyed by caller string.
public_virtual_interfaces map(object({…})) {} Public VIFs (require route_filter_prefixes).
transit_virtual_interfaces map(object({…})) {} Transit VIFs (require a DX gateway).
gateway_associations map(object({…})) {} DX gateway ↔ VGW/TGW associations (single- or cross-account).
tags map(string) {} Tags for all taggable resources (merge with default_tags).

See variables.tf for full heredoc schemas, per-VIF fields, timeouts, and validation rules.


🧾 Outputs

Name Description
id Connection id (dxcon-…).
arn Connection ARN (cross-resource reference type).
connection_id / connection_arn / connection_name Connection aliases.
aws_device DX endpoint device the port terminates on.
jumbo_frame_capable / macsec_capable / has_logical_redundancy Connection capability flags.
port_encryption_status MACsec port link encryption status.
owner_account_id Account that owns the connection.
vif_target_id LAG id when present, else connection id (what VIFs ride).
lag_id / lag_arn LAG identifiers (or null).
dx_gateway_id / dx_gateway_arn / dx_gateway_owner_account_id DX gateway identifiers (or null).
private_virtual_interface_ids / public_virtual_interface_ids / transit_virtual_interface_ids Per-type VIF id maps.
virtual_interface_ids / virtual_interface_arns Combined VIF id / ARN maps.
gateway_association_ids / gateway_association_states / transit_gateway_attachment_ids Association attributes.
tags_all All tags incl. provider default_tags.

🧠 Architecture Notes

  • ARN / ID formats:
  • Connection id: dxcon-xxxxxxxx; ARN arn:aws:directconnect:<region>:<account>:dxcon/<id>.
  • LAG id: dxlag-xxxxxxxx; DX gateway id is a UUID (account-global, no Region segment in the resource id).
  • VIF id: dxvif-xxxxxxxx; ARN arn:aws:directconnect:<region>:<account>:dxvif/<id>.
  • arn is the cross-resource reference type throughout AWS — prefer it for IAM/policy references.
  • Force-new / immutable fields: connection bandwidth, location, request_macsec; LAG connections_bandwidth, location; DX gateway amazon_side_asn; VIF vlan, bgp_asn, address_family. Changing any destroys and recreates the resource — and for the connection that means a new physical cross-connect. Plan port speed and location up front.
  • VIF target resolution: VIFs ride the LAG (local.vif_connection_id) when a LAG is created, otherwise the keystone connection; a per-VIF connection_id overrides. Private/transit VIFs and associations that omit dx_gateway_id fall back to the module-created DX gateway (local.dx_gateway_id) when present.
  • tagstags_alldefault_tags: var.tags is applied to each taggable resource (child collections merge per-item tags over var.tags); tags_all is the provider-computed merge of resource tags over provider default_tags, with resource tags winning on key conflict. default_tags is configured in the caller's provider block — never inside this module. Gateway associations are not taggable in AWS.
  • Asynchronous provisioning / eventual consistency: a new connection is created in requested/pending and only becomes available after the physical cross-connect is completed and accepted — apply does not wait for the cross-connect. VIFs depend on a connection that may still be pending; BGP sessions only come up once on-prem routing is configured.
  • Destroy ordering: VIFs and gateway associations must be deleted before the connection / DX gateway. Terraform handles this via implicit dependencies, but cross-account associations and proposals can lag — allow time and re-run if a delete reports the gateway still in use. With skip_destroy = true, the connection is dropped from state but the physical port is preserved.
  • us-east-1 globals: N/A. Direct Connect is a regional service tied to a DX location; it has none of the CloudFront/WAFv2/ACM us-east-1 coupling. Region comes from the provider, not a variable.

🧱 Design Principles

Secure-by-default posture and every opt-out, explicitly:

Posture Default Opt-out
MACsec encryption (where supported) prefer encryption_mode = "should_encrypt" / "must_encrypt" for PII when MACsec is requested encryption_mode = "no_encrypt" (discouraged) or leave MACsec off (request_macsec = false)
VIF type for PII private / transit VIFs preferred (stay on the AWS backbone) configure a public_virtual_interfaces entry explicitly
BGP authentication per-VIF bgp_auth_key (MD5) supported omit → AWS generates a key
Jumbo frames provider default (1500) unless set mtu = 9001 (private) / 8500 (transit)
Connection deletion normal destroy deletes the logical connection skip_destroy = true preserves the physical port
One target per private VIF validation forbids setting both vpn_gateway_id and dx_gateway_id choose exactly one (or rely on the DX-gateway fallback)

⚠️ Direct Connect transit is not encrypted by default. The cross-connect is private but plaintext unless MACsec is enabled or traffic rides an encrypted overlay (VPN over DX / Transit Gateway). For PII/privacy-regulation traffic, layer encryption-in-transit at the application or VPN layer, or use MACsec where the port/location supports it. Document any no_encrypt choice in your root module so reviewers can see what was loosened.

Other principles:

  • One composite, one keystone. aws_dx_connection.this is the keystone; the module owns only what is meaningless without it (LAG migration, VIFs, optional DX gateway, associations). VGW/TGW targets are referenced by id from sibling modules, keeping blast radius to the DX logical objects.
  • for_each, never count, for VIFs and associations — keyed by stable caller strings so reorders don't churn the plan.
  • Primary outputs id + arn, plus the connection capability flags, VIF/association maps, and tags_all.

🚀 Runbook

# Validate without backend or credentials
terraform init -backend=false
terraform validate
terraform fmt -check

plan / apply require valid AWS credentials (profile / SSO / OIDC) resolved through the standard provider chain, a configured Region, and the Direct Connect / EC2 actions listed above. A new connection becomes usable only after the physical cross-connect (LOA-CFA) is completed out of band — apply will succeed while the connection is still pending.


🧪 Testing

  • terraform init -backend=false && terraform validate — schema + reference integrity.
  • terraform fmt -check — canonical formatting.
  • terraform plan against a sandbox account to confirm the connection, VIF maps, optional LAG/gateway, and associations materialize as expected (note: the connection will plan/apply as pending until the cross-connect exists).
  • Assert module.<name>.arn, virtual_interface_ids, dx_gateway_id, and tags_all in your root-module test harness.

💬 Example Output

module.dx.aws_dx_connection.this: Creation complete after 4s [id=dxcon-fgabc123]
module.dx.aws_dx_gateway.this["this"]: Creation complete [id=abcd1234-...]
module.dx.aws_dx_transit_virtual_interface.this["prod"]: Creation complete [id=dxvif-fg0a1b2c]
module.dx.aws_dx_gateway_association.this["hub"]: Creation complete

Outputs:
arn = "arn:aws:directconnect:us-east-1:123456789012:dxcon/dxcon-fgabc123"
id = "dxcon-fgabc123"
dx_gateway_id = "abcd1234-ef56-..."
virtual_interface_ids = { "prod" = "dxvif-fg0a1b2c" }
transit_gateway_attachment_ids = { "hub" = "tgw-attach-0a1b2c3d" }
tags_all = { "DataClass" = "internal", "Environment" = "prod" }

🔍 Troubleshooting

Symptom Likely cause Fix
Connection stuck in requested / pending Physical cross-connect (LOA-CFA) not yet completed at the DX location Complete the cross-connect with the colocation/carrier; apply does not wait for it
Each private VIF may set at most one of vpn_gateway_id or dx_gateway_id Both targets set on one private VIF Set exactly one, or omit both to use the module DX-gateway fallback
Transit VIF apply fails — no gateway Transit VIFs require a DX gateway Create one via dx_gateway (auto-attach) or pass dx_gateway_id
bandwidth / location change forces replacement Both are FORCE-NEW on the connection Expect a destroy/recreate and a new cross-connect; plan capacity up front
BGP session down after apply On-prem router / BGP peering not configured, or VIF still pending Configure the customer side; confirm amazon_address/customer_address and bgp_auth_key match
Public VIF rejected route_filter_prefixes not public CIDRs you own / not advertisable Advertise only owned, public prefixes; private VIFs don't use route filters
Tag drift on every plan A tag also set by provider default_tags with a different value Let resource tags win, or remove the overlap from default_tags
LAG won't destroy Member connections still attached Set lag.force_destroy = true (deletes members — not recoverable)
Cross-account association won't create Missing/expired proposal or wrong owner account Recreate the proposal in the target account; set proposal_id + associated_gateway_owner_account_id
AccessDenied on directconnect:* Terraform identity missing the actions above Grant the least-privilege action set; add secretsmanager:GetSecretValue for MACsec
Destroy reports gateway "still in use" Association/VIF delete lagging (eventual consistency) Re-run destroy after VIFs/associations clear

🔗 Related Docs


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

About

No description or website provided.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages