feat/replace-pfsense-with-opnsense #29
32 changed files with 55 additions and 45 deletions
|
|
@ -57,8 +57,8 @@ variable "mgmt_ip_range" {
|
|||
default = ""
|
||||
}
|
||||
|
||||
variable "pfsense_machine_type" {
|
||||
description = "Machine type for the pfSense firewall (e.g. c2i.2, c2i.4)."
|
||||
variable "opnsense_machine_type" {
|
||||
description = "Machine type for the OPNsense firewall (e.g. c2i.2, c2i.4)."
|
||||
type = string
|
||||
default = "c2i.2"
|
||||
}
|
||||
|
|
@ -12,41 +12,52 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Place pfsense.qcow2 at ./image/pfsense.qcow2 before first apply.
|
||||
# Download pfSense 2.7.x AMD64 from netgate.com and convert to qcow2 if needed.
|
||||
resource "null_resource" "opnsense_image_file" {
|
||||
triggers = {
|
||||
always_run = timestamp()
|
||||
}
|
||||
provisioner "local-exec" {
|
||||
command = "curl -o opnsense.qcow2 https://opnsense.object.storage.eu01.onstackit.cloud/opnsense-26.1-amd64-21-05-2026.qcow2"
|
||||
}
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_image" "pfsense_image" {
|
||||
# Upload VPN Appliance Image to STACKIT
|
||||
resource "stackit_image" "opnsense_image" {
|
||||
project_id = local.hub_project_id
|
||||
name = "pfsense-2.7.x-amd64"
|
||||
local_file_path = "./image/pfsense.qcow2"
|
||||
name = "opnsense-26.1-amd64-image"
|
||||
local_file_path = "opnsense.qcow2"
|
||||
disk_format = "qcow2"
|
||||
min_disk_size = 10
|
||||
depends_on = [null_resource.opnsense_image_file]
|
||||
min_disk_size = 16
|
||||
min_ram = 2
|
||||
config = {
|
||||
uefi = false
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_volume" "pfsense_volume" {
|
||||
resource "stackit_volume" "opnsense_volume" {
|
||||
project_id = local.hub_project_id
|
||||
name = "pfsense-root"
|
||||
name = "opnsense-root"
|
||||
availability_zone = var.default_zone
|
||||
size = 16
|
||||
performance_class = "storage_premium_perf4"
|
||||
source = {
|
||||
id = stackit_image.pfsense_image.image_id
|
||||
id = stackit_image.opnsense_image.image_id
|
||||
type = "image"
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_server" "pfsense" {
|
||||
resource "stackit_server" "opnsense" {
|
||||
project_id = local.hub_project_id
|
||||
name = "pfsense"
|
||||
name = "opnsense"
|
||||
availability_zone = var.default_zone
|
||||
machine_type = var.pfsense_machine_type
|
||||
machine_type = var.opnsense_machine_type
|
||||
boot_volume = {
|
||||
source_type = "volume"
|
||||
source_id = stackit_volume.pfsense_volume.volume_id
|
||||
source_id = stackit_volume.opnsense_volume.volume_id
|
||||
}
|
||||
# WAN boots first (vtnet0); LAN and MGMT are attached in order below → vtnet1–2.
|
||||
network_interfaces = [stackit_network_interface.nic_wan.network_interface_id]
|
||||
|
|
@ -54,14 +65,14 @@ resource "stackit_server" "pfsense" {
|
|||
|
||||
resource "stackit_server_network_interface_attach" "attach_lan" {
|
||||
project_id = local.hub_project_id
|
||||
server_id = stackit_server.pfsense.server_id
|
||||
server_id = stackit_server.opnsense.server_id
|
||||
network_interface_id = stackit_network_interface.nic_lan.network_interface_id
|
||||
depends_on = [stackit_server.pfsense]
|
||||
depends_on = [stackit_server.opnsense]
|
||||
}
|
||||
|
||||
resource "stackit_server_network_interface_attach" "attach_mgmt" {
|
||||
project_id = local.hub_project_id
|
||||
server_id = stackit_server.pfsense.server_id
|
||||
server_id = stackit_server.opnsense.server_id
|
||||
network_interface_id = stackit_network_interface.nic_mgmt.network_interface_id
|
||||
depends_on = [stackit_server_network_interface_attach.attach_lan]
|
||||
}
|
||||
|
|
@ -23,16 +23,16 @@ output "hub_project_id" {
|
|||
}
|
||||
|
||||
output "firewall_lan_ip" {
|
||||
description = "pfSense LAN IP — set as hub_firewall_lan_ip in spoke terraform.tfvars."
|
||||
description = "OPNsense LAN IP — set as hub_firewall_lan_ip in spoke terraform.tfvars."
|
||||
value = stackit_network_interface.nic_lan.ipv4
|
||||
}
|
||||
|
||||
output "wan_public_ip" {
|
||||
description = "WAN public IP of the pfSense firewall."
|
||||
description = "WAN public IP of the OPNsense firewall."
|
||||
value = stackit_public_ip.wan_public_ip.ip
|
||||
}
|
||||
|
||||
output "mgmt_public_ip" {
|
||||
description = "Public IP of the pfSense MGMT interface. Access the web UI at https://<ip>/"
|
||||
description = "Public IP of the OPNsense MGMT interface. Access the web UI at https://<ip>/"
|
||||
value = stackit_public_ip.mgmt_public_ip.ip
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ variable "spoke_subnet" {
|
|||
}
|
||||
|
||||
variable "hub_firewall_lan_ip" {
|
||||
description = "LAN IP of the active pfSense node. Used as the default route next-hop for all spoke traffic. Run `terraform output firewall_lan_ip` in 001-hub-project."
|
||||
description = "LAN IP of the active OPNsense node. Used as the default route next-hop for all spoke traffic. Run `terraform output firewall_lan_ip` in 001-hub-project."
|
||||
type = string
|
||||
default = "10.28.0.20"
|
||||
}
|
||||
|
|
@ -63,7 +63,7 @@ variable "spoke_subnet" {
|
|||
}
|
||||
|
||||
variable "hub_firewall_lan_ip" {
|
||||
description = "LAN IP of the active pfSense node. Used as the default route next-hop for all spoke traffic. Run `terraform output firewall_lan_ip` in 001-hub-project."
|
||||
description = "LAN IP of the active OPNsense node. Used as the default route next-hop for all spoke traffic. Run `terraform output firewall_lan_ip` in 001-hub-project."
|
||||
type = string
|
||||
default = "10.28.0.20"
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# Hub-and-Spoke VPN on STACKIT — pfSense Reference Implementation
|
||||
# Hub-and-Spoke VPN on STACKIT — OPNsense Reference Implementation
|
||||
|
||||
A reference implementation of a **hub-and-spoke network topology** on [STACKIT](https://www.stackit.de/), provisioned with Terraform.
|
||||
|
||||
The hub deploys a **pfSense firewall** as the central routing and security component. All spoke traffic is forwarded through pfSense for routing, NAT, and policy enforcement. Each project is a self-contained Terraform stack with independent state.
|
||||
The hub deploys an **OPNsense firewall** as the central routing and security component. All spoke traffic is forwarded through OPNsense for routing, NAT, and policy enforcement. Each project is a self-contained Terraform stack with independent state.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ The hub deploys a **pfSense firewall** as the central routing and security compo
|
|||
| +-------------------------------------------------------------+ |
|
||||
| | 001-hub-project | |
|
||||
| | | |
|
||||
| | pfSense Firewall | |
|
||||
| | OPNsense Firewall | |
|
||||
| | +------------------+------------------+ | |
|
||||
| | | Interface | IP | | |
|
||||
| | +------------------+------------------+ | |
|
||||
|
|
@ -38,7 +38,7 @@ The hub deploys a **pfSense firewall** as the central routing and security compo
|
|||
+-------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
**Traffic flow:** All spoke traffic (including internet-bound) is forwarded to the pfSense LAN NIC (`10.28.0.20`) via a routing table route attached to each spoke network. pfSense handles routing, NAT, and firewall policy centrally.
|
||||
**Traffic flow:** All spoke traffic (including internet-bound) is forwarded to the OPNsense LAN NIC (`10.28.0.20`) via a routing table route attached to each spoke network. OPNsense handles routing, NAT, and firewall policy centrally.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -46,22 +46,22 @@ The hub deploys a **pfSense firewall** as the central routing and security compo
|
|||
|
||||
```
|
||||
hub-and-spoke-vpn/
|
||||
├── 001-hub-project/ # Hub: pfSense firewall, network area, routing tables
|
||||
│ ├── 000-backend.tf # S3 remote state backend
|
||||
│ ├── 000-variables.tf # Input variables (pfsense_machine_type, mgmt_ip_range)
|
||||
│ ├── 010-provider.tf # STACKIT provider
|
||||
│ ├── 020-projects.tf # STACKIT project + shared network area (SNA)
|
||||
│ ├── 030-network.tf # Subnets, routing tables, NICs, security groups
|
||||
│ ├── 040-hub-fw-pfsense.tf # pfSense image, volume, server, public IPs
|
||||
│ ├── 050-outputs.tf # network_area_id, firewall_lan_ip, public IPs (needed by spokes)
|
||||
│ └── backend.conf.example # Backend credential template
|
||||
├── 001-hub-project/ # Hub: OPNsense firewall, network area, routing tables
|
||||
│ ├── 000-backend.tf # S3 remote state backend
|
||||
│ ├── 000-variables.tf # Input variables (opnsense_machine_type, mgmt_ip_range)
|
||||
│ ├── 010-provider.tf # STACKIT provider
|
||||
│ ├── 020-projects.tf # STACKIT project + shared network area (SNA)
|
||||
│ ├── 030-network.tf # Subnets, routing tables, NICs, security groups
|
||||
│ ├── 040-hub-fw-opnsense.tf # OPNsense image, volume, server, public IPs
|
||||
│ ├── 050-outputs.tf # network_area_id, firewall_lan_ip, public IPs (needed by spokes)
|
||||
│ └── backend.conf.example # Backend credential template
|
||||
│
|
||||
├── 002-spoke-project/ # Spoke A: example Linux servers (RHEL 9)
|
||||
│ ├── 000-backend.tf
|
||||
│ ├── 000-variables.tf
|
||||
│ ├── 010-provider.tf
|
||||
│ ├── 020-projects.tf
|
||||
│ ├── 030-network.tf # Spoke subnet + routing table (default → pfSense LAN)
|
||||
│ ├── 030-network.tf # Spoke subnet + routing table (default → OPNsense LAN)
|
||||
│ ├── 040-servers.tf # Two Linux server examples (different machine types)
|
||||
│ ├── 050-outputs.tf
|
||||
│ └── backend.conf.example
|
||||
|
|
@ -161,7 +161,7 @@ The minimum required values are documented in each `000-variables.tf`.
|
|||
Deploy in numbered order. The hub creates the shared network area that spokes depend on.
|
||||
|
||||
```sh
|
||||
# Step 1 — Deploy the hub (creates the network area and the pfSense firewall)
|
||||
# Step 1 — Deploy the hub (creates the network area and the OPNsense firewall)
|
||||
cd 001-hub-project
|
||||
terraform init -backend-config=backend.conf
|
||||
terraform apply
|
||||
|
|
@ -182,9 +182,9 @@ terraform apply
|
|||
|
||||
---
|
||||
|
||||
## Hub Firewall (pfSense)
|
||||
## Hub Firewall (OPNsense)
|
||||
|
||||
pfSense is provisioned from a qcow2 image with three network interfaces:
|
||||
OPNsense is provisioned from a qcow2 image with three network interfaces:
|
||||
|
||||
| Interface | Subnet | IP | Purpose |
|
||||
| --------- | --------------- | ------------ | -------------------------- |
|
||||
|
|
@ -194,7 +194,7 @@ pfSense is provisioned from a qcow2 image with three network interfaces:
|
|||
|
||||
The WAN interface boots first (`vtnet0`); LAN and MGMT are attached sequentially and appear as `vtnet1` and `vtnet2`. The MGMT interface is protected by a security group that restricts SSH, HTTP, and HTTPS access to the CIDR set in `mgmt_ip_range`.
|
||||
|
||||
**pfSense image:** Place `pfsense.qcow2` at `001-hub-project/image/pfsense.qcow2` before the first apply. Download pfSense 2.7.x AMD64 from netgate.com and convert to qcow2 if needed.
|
||||
**OPNsense image:** The image is downloaded automatically during `terraform apply` via a `null_resource` provisioner. The qcow2 image is fetched from the STACKIT Object Storage endpoint and uploaded to STACKIT as a custom image.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -244,7 +244,6 @@ A single generic module used by all spokes. Select the OS by passing the appropr
|
|||
| Backend credentials | `backend.conf` per project | Object Storage bucket + S3 keys |
|
||||
| Service account key | `keys/service-account.json` per project | Downloaded from STACKIT portal |
|
||||
| Cloud-init password | `cloud-init/*.yml` | Replace placeholder hash with a real one |
|
||||
| pfSense image | `001-hub-project/image/pfsense.qcow2` | Download separately |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -252,4 +251,4 @@ A single generic module used by all spokes. Select the OS by passing the appropr
|
|||
|
||||
- [STACKIT Terraform Provider](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs)
|
||||
- [STACKIT Documentation](https://docs.stackit.cloud/)
|
||||
- [pfSense Documentation](https://docs.netgate.com/pfsense/en/latest/)
|
||||
- [OPNsense Documentation](https://docs.opnsense.org/)
|
||||
|
|
@ -39,7 +39,7 @@ default_zone = "eu01-1"
|
|||
|
||||
# --- Hub: network access control (001-hub-project only) ----------------------
|
||||
|
||||
# CIDR allowed to reach the pfSense management interface (SSH, HTTP, HTTPS).
|
||||
# CIDR allowed to reach the OPNsense management interface (SSH, HTTP, HTTPS).
|
||||
# Example: your office public IP or VPN exit in /32 or /24 notation.
|
||||
# Leave empty (or remove) to create no management access rules.
|
||||
# mgmt_ip_range = "<x.x.x.x/x>"
|
||||
|
|
@ -51,6 +51,6 @@ default_zone = "eu01-1"
|
|||
# 003-spoke-project default: 10.28.2.0/28
|
||||
# spoke_subnet = "10.28.1.0/28"
|
||||
|
||||
# LAN IP of the pfSense firewall — used as the default route next-hop for spoke traffic.
|
||||
# LAN IP of the OPNsense firewall — used as the default route next-hop for spoke traffic.
|
||||
# Run `terraform output firewall_lan_ip` in 001-hub-project.
|
||||
# hub_firewall_lan_ip = "10.28.0.20"
|
||||
Loading…
Add table
Add a link
Reference in a new issue