Skip to content

Terraform Modules

Reusable Terraform modules published by b'nerd for common infrastructure patterns on b'nerd Cloud (OpenStack-based).

Source: app/terraform/common


Available Modules

Module Purpose
ipsec-gateway Site-to-site IPsec VPN gateway VM (Ubuntu + strongSwan)

ipsec-gateway

Deploys a Linux IPsec VPN gateway VM on OpenStack — a lightweight site-to-site tunnel endpoint with one public/management NIC and one dedicated NIC per tunnel.

What it does

  • Creates one OpenStack network + subnet + router interface per tunnel.
  • Creates a public/management port on an existing network you provide.
  • Associates a pre-allocated floating IP with the first tunnel port.
  • Creates two security groups: public (IKE/NAT-T/ESP/SSH) and internal ("allow all" on tunnel-side ports).
  • Bootstraps the VM via cloud-init: enables IP forwarding and installs strongswan + strongswan-swanctl.

What it does NOT do

  • Configure swanctl, PSKs, iptables forwarding rules, or any application running on top (HAProxy, BGP, etc.). Software configuration is done separately — typically with Ansible against the floating IP after apply.
  • Create the router, management network/subnet, or floating IP — provide these as inputs.
  • Generate or manage SSH keys; you supply a public key.

Architecture

  Site A ── IPsec Tunnel 1 ──►┐
                               │  Gateway VM (<floating_ip>)
                               │  Ubuntu 24.04 + strongSwan
  Site B ── IPsec Tunnel 2 ──►│
                               │  eth0: public/mgmt NIC
                               │  eth1: tunnel1 NIC (10.0.0.0/24)
                               │  eth2: tunnel2 NIC (10.0.1.0/24)
                               └──► Existing OpenStack router
                                    (each tunnel subnet attached as
                                     router interface)

The floating IP binds to the first tunnel port by default, which is where incoming IKE/NAT-T packets should terminate. Use floating_ip_target = "mgmt" only when external SSH must reach the VM without depending on L3-router conntrack.

Prerequisites

  • Terraform >= 1.5
  • OpenStack provider ~> 3.0 (credentials via OS_* env vars or clouds.yaml)
  • An existing OpenStack router
  • An existing network + subnet for the management port, attached to that router
  • A pre-allocated floating IP
  • A VM image (Ubuntu 24.04 recommended)

Usage

From the Git source (recommended — pin a tag):

module "ipsec_gateway" {
  source = "git::https://git.bnerd.net/cloud/terraform/common.git//modules/ipsec-gateway?ref=v1.0.0"

  prefix         = "acme-gw"
  router_id      = data.openstack_networking_router_v2.router.id
  network_id     = "<management-network-uuid>"
  subnet_id      = "<management-subnet-uuid>"
  floating_ip    = "203.0.113.10"
  image_id       = data.openstack_images_image_v2.ubuntu.id
  ssh_public_key = tls_private_key.gateway.public_key_openssh

  tunnels = [
    {
      name         = "tunnel1"
      local_cidr   = "10.0.0.0/24"
      remote_cidrs = ["192.168.10.0/24"]
    },
    {
      name         = "tunnel2"
      local_cidr   = "10.0.1.0/24"
      remote_cidrs = ["192.168.20.0/24", "192.168.30.0/24"]
    },
  ]
}

Vendored locally:

module "ipsec_gateway" {
  source = "../../path/to/terraform/common/modules/ipsec-gateway"
  # same inputs as above
}

Inputs

Required

Name Type Description
prefix string Prefix applied to every resource name this module creates
router_id string ID of the existing router to attach tunnel networks to
network_id string ID of the existing network for the management port
subnet_id string ID of the existing subnet on the management network
floating_ip string Pre-allocated floating IP address
image_id string OpenStack image ID for the VM
ssh_public_key string SSH public key to inject into the VM
tunnels list(object({ name=string, local_cidr=string, remote_cidrs=list(string) })) One entry per tunnel. At least one required.

Optional

Name Type Default Description
flavor_name string "m1.small" OpenStack flavor for the VM
availability_zone string "az1" OpenStack availability zone
dns_nameservers list(string) ["8.8.8.8", "8.8.4.4"] DNS servers injected into tunnel subnets
ssh_allowed_cidrs list(string) ["0.0.0.0/0"] Source CIDRs allowed to SSH. Restrict this in production.
sshd_port number 2222 SSH port (moved off 22 when HAProxy handles that port)
floating_ip_target string "tunnel" Bind floating IP to "tunnel" (first tunnel port) or "mgmt"

Outputs

Name Description
gateway_floating_ip Public floating IP of the gateway
gateway_internal_ip Internal IP on the management network
gateway_instance_id OpenStack instance ID
tunnel_network_ids Map of tunnel name → network ID
tunnel_subnet_ids Map of tunnel name → subnet ID
tunnel_gateway_ips Map of tunnel name → gateway IP on that tunnel net

Security model

The public security group (<prefix>-ipsec-gw) allows:

Port / proto Purpose Source
500/udp IKE 0.0.0.0/0
4500/udp NAT-T (IPsec NAT traversal) 0.0.0.0/0
IP proto 50 ESP (encapsulated payload) 0.0.0.0/0
var.sshd_port SSH management var.ssh_allowed_cidrs

The internal security group (<prefix>-ipsec-internal) allows all IPv4 ingress on the tunnel-side ports — required because the gateway forwards arbitrary traffic from remote sites that OpenStack port security would otherwise drop. Each tunnel port also has allowed_address_pairs entries for the local subnet and every declared remote subnet.

NAT traversal

The floating IP is a 1:1 NAT in OpenStack. IKEv2 handles this automatically via NAT-T (UDP 4500). Use local_addrs = %any in your swanctl config so strongSwan detects its real address behind NAT.

Notes

  • First tunnel is special: var.tunnels[0] is the entry whose port carries the floating IP. Reordering the list will recreate the association.
  • Existing router only: the module attaches to a router you already own — it never creates one, avoiding collisions with shared networks.
  • Next steps: after terraform apply, configure swanctl/PSKs/iptables on the VM (typically via Ansible against the floating IP).
  • A complete, runnable example is in the source under modules/ipsec-gateway/examples/basic/.

See Also