example/pfsense-hub-and-spoke #6
31 changed files with 1502 additions and 0 deletions
|
|
@ -0,0 +1,31 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "<your-state-bucket-name>"
|
||||
key = "001-hub-project/terraform.tfstate"
|
||||
endpoints = {
|
||||
s3 = "https://object.storage.eu01.onstackit.cloud"
|
||||
}
|
||||
region = "eu01"
|
||||
skip_credentials_validation = true
|
||||
skip_region_validation = true
|
||||
skip_s3_checksum = true
|
||||
skip_requesting_account_id = true
|
||||
# Credentials: set via backend.conf or environment variables (see above)
|
||||
# access_key = "<your-access-key>"
|
||||
# secret_key = "<your-secret-key>"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "stackit_service_account_key_path" {
|
||||
description = "Path to the STACKIT service account key file (JSON). Keep this file out of version control."
|
||||
type = string
|
||||
default = "./keys/service-account.json"
|
||||
}
|
||||
|
||||
variable "stackit_organization_id" {
|
||||
description = "STACKIT Organization ID (UUID). Found in the portal under Organization > Settings."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_folder_id" {
|
||||
description = "STACKIT Folder ID (UUID) that will contain the hub project."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_region" {
|
||||
description = "STACKIT region (e.g. eu01)."
|
||||
type = string
|
||||
default = "eu01"
|
||||
}
|
||||
|
||||
variable "default_zone" {
|
||||
description = "Availability zone within the region (e.g. eu01-1)."
|
||||
type = string
|
||||
default = "eu01-1"
|
||||
}
|
||||
|
||||
variable "project_name" {
|
||||
description = "Display name of the hub project in STACKIT."
|
||||
type = string
|
||||
default = "hub-project"
|
||||
}
|
||||
|
||||
variable "org_admin" {
|
||||
description = "Email address of the STACKIT user who will be set as project owner."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "mgmt_ip_range" {
|
||||
description = "CIDR range allowed to access the firewall management interface (SSH, HTTP, HTTPS). Example: your office or VPN exit IP in /32 or /24 notation."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "pfsense_machine_type" {
|
||||
description = "Machine type for the pfSense firewall (e.g. c2i.2, c2i.4)."
|
||||
type = string
|
||||
default = "c2i.2"
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
stackit = {
|
||||
source = "stackitcloud/stackit"
|
||||
version = ">= 0.80.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "stackit" {
|
||||
default_region = var.stackit_region
|
||||
service_account_key_path = var.stackit_service_account_key_path
|
||||
experiments = ["routing-tables", "network", "iam"]
|
||||
enable_beta_resources = true
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
locals {
|
||||
sna_id = stackit_network_area.sna.network_area_id
|
||||
hub_project_id = stackit_resourcemanager_project.hub.project_id
|
||||
}
|
||||
|
||||
resource "stackit_network_area" "sna" {
|
||||
name = "hub-and-spoke-sna"
|
||||
organization_id = var.stackit_organization_id
|
||||
labels = {
|
||||
"preview/routingtables" = "true"
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_network_area_region" "sna" {
|
||||
organization_id = var.stackit_organization_id
|
||||
network_area_id = stackit_network_area.sna.network_area_id
|
||||
ipv4 = {
|
||||
transfer_network = "172.3.0.0/16"
|
||||
network_ranges = [
|
||||
{
|
||||
prefix = "10.28.0.0/16"
|
||||
}
|
||||
]
|
||||
default_nameservers = ["1.1.1.1"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_resourcemanager_project" "hub" {
|
||||
parent_container_id = var.stackit_folder_id
|
||||
name = var.project_name
|
||||
owner_email = var.org_admin
|
||||
labels = {
|
||||
"networkArea" = stackit_network_area.sna.network_area_id
|
||||
}
|
||||
}
|
||||
153
examples/pfsense-hub-and-spoke/001-hub-project/030-network.tf
Normal file
153
examples/pfsense-hub-and-spoke/001-hub-project/030-network.tf
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_routing_table" "rt_firewall_lan" {
|
||||
network_area_id = stackit_network_area.sna.network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
name = "rt_firewall_lan"
|
||||
system_routes = true
|
||||
}
|
||||
|
||||
resource "stackit_routing_table" "rt_firewall_wan" {
|
||||
network_area_id = stackit_network_area.sna.network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
name = "rt_firewall_wan"
|
||||
system_routes = true
|
||||
}
|
||||
|
||||
resource "stackit_routing_table_route" "fw_network" {
|
||||
network_area_id = stackit_network_area.sna.network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
routing_table_id = stackit_routing_table.rt_firewall_lan.routing_table_id
|
||||
destination = {
|
||||
type = "cidrv4"
|
||||
value = "0.0.0.0/0"
|
||||
}
|
||||
next_hop = {
|
||||
type = "ipv4"
|
||||
value = stackit_network_interface.nic_lan.ipv4
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_routing_table_route" "fw_network_wan" {
|
||||
network_area_id = stackit_network_area.sna.network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
routing_table_id = stackit_routing_table.rt_firewall_wan.routing_table_id
|
||||
destination = {
|
||||
type = "cidrv4"
|
||||
value = "0.0.0.0/0"
|
||||
}
|
||||
next_hop = {
|
||||
type = "internet"
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_network" "wan_network" {
|
||||
project_id = local.hub_project_id
|
||||
name = "wan-network"
|
||||
ipv4_nameservers = ["1.1.1.1", "9.9.9.9"]
|
||||
ipv4_prefix = "10.28.0.0/28"
|
||||
routing_table_id = stackit_routing_table.rt_firewall_wan.routing_table_id
|
||||
}
|
||||
|
||||
resource "stackit_network" "lan_network" {
|
||||
project_id = local.hub_project_id
|
||||
name = "lan-network"
|
||||
ipv4_nameservers = ["1.1.1.1", "9.9.9.9"]
|
||||
ipv4_prefix = "10.28.0.16/28"
|
||||
routing_table_id = stackit_routing_table.rt_firewall_lan.routing_table_id
|
||||
}
|
||||
|
||||
resource "stackit_network" "mgmt_network" {
|
||||
project_id = local.hub_project_id
|
||||
name = "mgmt-network"
|
||||
ipv4_nameservers = ["1.1.1.1", "9.9.9.9"]
|
||||
ipv4_prefix = "10.28.0.32/28"
|
||||
}
|
||||
|
||||
resource "stackit_network_interface" "nic_wan" {
|
||||
project_id = local.hub_project_id
|
||||
network_id = stackit_network.wan_network.network_id
|
||||
security = false
|
||||
ipv4 = "10.28.0.4"
|
||||
}
|
||||
|
||||
resource "stackit_network_interface" "nic_lan" {
|
||||
project_id = local.hub_project_id
|
||||
network_id = stackit_network.lan_network.network_id
|
||||
security = false
|
||||
ipv4 = "10.28.0.20"
|
||||
}
|
||||
|
||||
resource "stackit_security_group" "mgmt_sg" {
|
||||
project_id = local.hub_project_id
|
||||
name = "firewall-mgmt-sg"
|
||||
description = "Allow SSH and HTTPS to the hub firewall management interface"
|
||||
}
|
||||
|
||||
resource "stackit_security_group_rule" "allow_ssh" {
|
||||
count = var.mgmt_ip_range != "" ? 1 : 0
|
||||
|
||||
project_id = local.hub_project_id
|
||||
security_group_id = stackit_security_group.mgmt_sg.security_group_id
|
||||
direction = "ingress"
|
||||
protocol = {
|
||||
name = "tcp"
|
||||
}
|
||||
port_range = {
|
||||
min = 22
|
||||
max = 22
|
||||
}
|
||||
ip_range = var.mgmt_ip_range
|
||||
}
|
||||
|
||||
resource "stackit_security_group_rule" "allow_http" {
|
||||
count = var.mgmt_ip_range != "" ? 1 : 0
|
||||
|
||||
project_id = local.hub_project_id
|
||||
security_group_id = stackit_security_group.mgmt_sg.security_group_id
|
||||
direction = "ingress"
|
||||
protocol = {
|
||||
name = "tcp"
|
||||
}
|
||||
port_range = {
|
||||
min = 80
|
||||
max = 80
|
||||
}
|
||||
ip_range = var.mgmt_ip_range
|
||||
}
|
||||
|
||||
resource "stackit_security_group_rule" "allow_https" {
|
||||
count = var.mgmt_ip_range != "" ? 1 : 0
|
||||
|
||||
project_id = local.hub_project_id
|
||||
security_group_id = stackit_security_group.mgmt_sg.security_group_id
|
||||
direction = "ingress"
|
||||
protocol = {
|
||||
name = "tcp"
|
||||
}
|
||||
port_range = {
|
||||
min = 443
|
||||
max = 443
|
||||
}
|
||||
ip_range = var.mgmt_ip_range
|
||||
}
|
||||
|
||||
resource "stackit_network_interface" "nic_mgmt" {
|
||||
project_id = local.hub_project_id
|
||||
network_id = stackit_network.mgmt_network.network_id
|
||||
security = true
|
||||
ipv4 = "10.28.0.36"
|
||||
security_group_ids = [stackit_security_group.mgmt_sg.security_group_id]
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# 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 "stackit_image" "pfsense_image" {
|
||||
project_id = local.hub_project_id
|
||||
name = "pfsense-2.7.x-amd64"
|
||||
local_file_path = "./image/pfsense.qcow2"
|
||||
disk_format = "qcow2"
|
||||
min_disk_size = 10
|
||||
min_ram = 2
|
||||
config = {
|
||||
uefi = false
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_volume" "pfsense_volume" {
|
||||
project_id = local.hub_project_id
|
||||
name = "pfsense-root"
|
||||
availability_zone = var.default_zone
|
||||
size = 16
|
||||
performance_class = "storage_premium_perf4"
|
||||
source = {
|
||||
id = stackit_image.pfsense_image.image_id
|
||||
type = "image"
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_server" "pfsense" {
|
||||
project_id = local.hub_project_id
|
||||
name = "pfsense"
|
||||
availability_zone = var.default_zone
|
||||
machine_type = var.pfsense_machine_type
|
||||
boot_volume = {
|
||||
source_type = "volume"
|
||||
source_id = stackit_volume.pfsense_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]
|
||||
}
|
||||
|
||||
resource "stackit_server_network_interface_attach" "attach_lan" {
|
||||
project_id = local.hub_project_id
|
||||
server_id = stackit_server.pfsense.server_id
|
||||
network_interface_id = stackit_network_interface.nic_lan.network_interface_id
|
||||
depends_on = [stackit_server.pfsense]
|
||||
}
|
||||
|
||||
resource "stackit_server_network_interface_attach" "attach_mgmt" {
|
||||
project_id = local.hub_project_id
|
||||
server_id = stackit_server.pfsense.server_id
|
||||
network_interface_id = stackit_network_interface.nic_mgmt.network_interface_id
|
||||
depends_on = [stackit_server_network_interface_attach.attach_lan]
|
||||
}
|
||||
|
||||
resource "stackit_public_ip" "wan_public_ip" {
|
||||
project_id = local.hub_project_id
|
||||
network_interface_id = stackit_network_interface.nic_wan.network_interface_id
|
||||
}
|
||||
|
||||
resource "stackit_public_ip" "mgmt_public_ip" {
|
||||
project_id = local.hub_project_id
|
||||
network_interface_id = stackit_network_interface.nic_mgmt.network_interface_id
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
output "network_area_id" {
|
||||
description = "Shared Network Area ID — required by all spoke projects."
|
||||
value = local.sna_id
|
||||
}
|
||||
|
||||
output "hub_project_id" {
|
||||
description = "STACKIT project ID of the hub."
|
||||
value = local.hub_project_id
|
||||
}
|
||||
|
||||
output "firewall_lan_ip" {
|
||||
description = "pfSense 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."
|
||||
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>/"
|
||||
value = stackit_public_ip.mgmt_public_ip.ip
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# Copy this file to backend.conf and fill in your STACKIT Object Storage credentials.
|
||||
# backend.conf is gitignored and must never be committed to version control.
|
||||
#
|
||||
# Usage: terraform init -backend-config=backend.conf
|
||||
#
|
||||
# Create an Object Storage bucket in the STACKIT portal and generate
|
||||
# S3-compatible access credentials under "Object Storage > Credentials".
|
||||
|
||||
bucket = "<your-state-bucket-name>"
|
||||
access_key = "<your-access-key-id>"
|
||||
secret_key = "<your-secret-access-key>"
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "<your-state-bucket-name>"
|
||||
key = "002-spoke-project/terraform.tfstate"
|
||||
endpoints = {
|
||||
s3 = "https://object.storage.eu01.onstackit.cloud"
|
||||
}
|
||||
region = "eu01"
|
||||
skip_credentials_validation = true
|
||||
skip_region_validation = true
|
||||
skip_s3_checksum = true
|
||||
skip_requesting_account_id = true
|
||||
# Credentials: set via backend.conf or environment variables (see above)
|
||||
# access_key = "<your-access-key>"
|
||||
# secret_key = "<your-secret-key>"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "stackit_service_account_key_path" {
|
||||
description = "Path to the STACKIT service account key file (JSON). Keep this file out of version control."
|
||||
type = string
|
||||
default = "./keys/service-account.json"
|
||||
}
|
||||
|
||||
variable "stackit_organization_id" {
|
||||
description = "STACKIT Organization ID (UUID)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_network_area_id" {
|
||||
description = "Shared Network Area ID from the hub project. Run `terraform output network_area_id` in 001-hub-project."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_folder_id" {
|
||||
description = "STACKIT Folder ID (UUID) that will contain this spoke project."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_region" {
|
||||
description = "STACKIT region (e.g. eu01)."
|
||||
type = string
|
||||
default = "eu01"
|
||||
}
|
||||
|
||||
variable "default_zone" {
|
||||
description = "Availability zone within the region (e.g. eu01-1)."
|
||||
type = string
|
||||
default = "eu01-1"
|
||||
}
|
||||
|
||||
variable "project_name" {
|
||||
description = "Display name of this spoke project in STACKIT."
|
||||
type = string
|
||||
default = "spoke-project-02"
|
||||
}
|
||||
|
||||
variable "org_admin" {
|
||||
description = "Email address of the STACKIT user who will be set as project owner."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "spoke_subnet" {
|
||||
description = "IPv4 prefix for this spoke's network. Must be within the network area range (10.28.0.0/16)."
|
||||
type = string
|
||||
default = "10.28.1.0/28"
|
||||
}
|
||||
|
||||
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."
|
||||
type = string
|
||||
default = "10.28.0.20"
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
stackit = {
|
||||
source = "stackitcloud/stackit"
|
||||
version = ">= 0.80.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "stackit" {
|
||||
default_region = var.stackit_region
|
||||
service_account_key_path = var.stackit_service_account_key_path
|
||||
experiments = ["routing-tables", "network", "iam"]
|
||||
enable_beta_resources = true
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_resourcemanager_project" "spoke" {
|
||||
parent_container_id = var.stackit_folder_id
|
||||
name = var.project_name
|
||||
owner_email = var.org_admin
|
||||
labels = {
|
||||
"networkArea" = var.stackit_network_area_id
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_routing_table" "rt_default" {
|
||||
name = "rt_spoke_002"
|
||||
network_area_id = var.stackit_network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
system_routes = false
|
||||
}
|
||||
|
||||
resource "stackit_routing_table_route" "default" {
|
||||
destination = {
|
||||
type = "cidrv4"
|
||||
value = "0.0.0.0/0"
|
||||
}
|
||||
next_hop = {
|
||||
type = "ipv4"
|
||||
value = var.hub_firewall_lan_ip
|
||||
}
|
||||
network_area_id = var.stackit_network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
routing_table_id = stackit_routing_table.rt_default.routing_table_id
|
||||
}
|
||||
|
||||
resource "stackit_network" "spoke_network" {
|
||||
project_id = stackit_resourcemanager_project.spoke.project_id
|
||||
name = "spoke-network"
|
||||
ipv4_nameservers = ["1.1.1.1", "9.9.9.9"]
|
||||
ipv4_prefix = var.spoke_subnet
|
||||
routing_table_id = stackit_routing_table.rt_default.routing_table_id
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
module "server_a" {
|
||||
source = "../modules/server"
|
||||
|
||||
project_id = stackit_resourcemanager_project.spoke.project_id
|
||||
network_id = stackit_network.spoke_network.network_id
|
||||
availability_zone = var.default_zone
|
||||
name = "server-a"
|
||||
machine_type = "c2i.2"
|
||||
disk_size = 50
|
||||
disk_performance_class = "storage_premium_perf1"
|
||||
user_data = templatefile("${path.module}/../cloud-init/user-init-linux.yml", {})
|
||||
}
|
||||
|
||||
module "server_b" {
|
||||
source = "../modules/server"
|
||||
|
||||
project_id = stackit_resourcemanager_project.spoke.project_id
|
||||
network_id = stackit_network.spoke_network.network_id
|
||||
availability_zone = var.default_zone
|
||||
name = "server-b"
|
||||
machine_type = "m2a.8d"
|
||||
disk_size = 100
|
||||
disk_performance_class = "storage_premium_perf1"
|
||||
user_data = templatefile("${path.module}/../cloud-init/user-init-linux.yml", {})
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
output "spoke_project_id" {
|
||||
description = "STACKIT project ID of this spoke."
|
||||
value = stackit_resourcemanager_project.spoke.project_id
|
||||
}
|
||||
|
||||
output "server_a_ip" {
|
||||
description = "Primary IP address of server-a."
|
||||
value = module.server_a.primary_ip
|
||||
}
|
||||
|
||||
output "server_b_ip" {
|
||||
description = "Primary IP address of server-b."
|
||||
value = module.server_b.primary_ip
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# Copy this file to backend.conf and fill in your STACKIT Object Storage credentials.
|
||||
# backend.conf is gitignored and must never be committed to version control.
|
||||
#
|
||||
# Usage: terraform init -backend-config=backend.conf
|
||||
#
|
||||
# Create an Object Storage bucket in the STACKIT portal and generate
|
||||
# S3-compatible access credentials under "Object Storage > Credentials".
|
||||
|
||||
bucket = "<your-state-bucket-name>"
|
||||
access_key = "<your-access-key-id>"
|
||||
secret_key = "<your-secret-access-key>"
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "<your-state-bucket-name>"
|
||||
key = "003-spoke-project/terraform.tfstate"
|
||||
endpoints = {
|
||||
s3 = "https://object.storage.eu01.onstackit.cloud"
|
||||
}
|
||||
region = "eu01"
|
||||
skip_credentials_validation = true
|
||||
skip_region_validation = true
|
||||
skip_s3_checksum = true
|
||||
skip_requesting_account_id = true
|
||||
# Credentials: set via backend.conf or environment variables (see above)
|
||||
# access_key = "<your-access-key>"
|
||||
# secret_key = "<your-secret-key>"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "stackit_service_account_key_path" {
|
||||
description = "Path to the STACKIT service account key file (JSON). Keep this file out of version control."
|
||||
type = string
|
||||
default = "./keys/service-account.json"
|
||||
}
|
||||
|
||||
variable "stackit_organization_id" {
|
||||
description = "STACKIT Organization ID (UUID)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_network_area_id" {
|
||||
description = "Shared Network Area ID from the hub project. Run `terraform output network_area_id` in 001-hub-project."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_folder_id" {
|
||||
description = "STACKIT Folder ID (UUID) that will contain this spoke project."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_region" {
|
||||
description = "STACKIT region (e.g. eu01)."
|
||||
type = string
|
||||
default = "eu01"
|
||||
}
|
||||
|
||||
variable "default_zone" {
|
||||
description = "Availability zone within the region (e.g. eu01-1)."
|
||||
type = string
|
||||
default = "eu01-1"
|
||||
}
|
||||
|
||||
variable "project_name" {
|
||||
description = "Display name of this spoke project in STACKIT."
|
||||
type = string
|
||||
default = "spoke-project-03"
|
||||
}
|
||||
|
||||
variable "org_admin" {
|
||||
description = "Email address of the STACKIT user who will be set as project owner."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "spoke_subnet" {
|
||||
description = "IPv4 prefix for this spoke's network. Must be within the network area range (10.28.0.0/16)."
|
||||
type = string
|
||||
default = "10.28.2.0/28"
|
||||
}
|
||||
|
||||
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."
|
||||
type = string
|
||||
default = "10.28.0.20"
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
stackit = {
|
||||
source = "stackitcloud/stackit"
|
||||
version = ">= 0.80.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "stackit" {
|
||||
default_region = var.stackit_region
|
||||
service_account_key_path = var.stackit_service_account_key_path
|
||||
experiments = ["routing-tables", "network", "iam"]
|
||||
enable_beta_resources = true
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_resourcemanager_project" "spoke" {
|
||||
parent_container_id = var.stackit_folder_id
|
||||
name = var.project_name
|
||||
owner_email = var.org_admin
|
||||
labels = {
|
||||
"networkArea" = var.stackit_network_area_id
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_routing_table" "rt_default" {
|
||||
name = "rt_spoke_003"
|
||||
network_area_id = var.stackit_network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
system_routes = false
|
||||
}
|
||||
|
||||
resource "stackit_routing_table_route" "default" {
|
||||
destination = {
|
||||
type = "cidrv4"
|
||||
value = "0.0.0.0/0"
|
||||
}
|
||||
next_hop = {
|
||||
type = "ipv4"
|
||||
value = var.hub_firewall_lan_ip
|
||||
}
|
||||
network_area_id = var.stackit_network_area_id
|
||||
organization_id = var.stackit_organization_id
|
||||
routing_table_id = stackit_routing_table.rt_default.routing_table_id
|
||||
}
|
||||
|
||||
resource "stackit_network" "spoke_network" {
|
||||
project_id = stackit_resourcemanager_project.spoke.project_id
|
||||
name = "spoke-network"
|
||||
ipv4_nameservers = ["1.1.1.1", "9.9.9.9"]
|
||||
ipv4_prefix = var.spoke_subnet
|
||||
routing_table_id = stackit_routing_table.rt_default.routing_table_id
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
locals {
|
||||
windows_image_id = "c3304694-a03f-47c7-8d4c-348eecc7d212"
|
||||
}
|
||||
|
||||
module "windows_server_a" {
|
||||
source = "../modules/server"
|
||||
|
||||
project_id = stackit_resourcemanager_project.spoke.project_id
|
||||
network_id = stackit_network.spoke_network.network_id
|
||||
availability_zone = var.default_zone
|
||||
name = "windows-server-a"
|
||||
image_id = local.windows_image_id
|
||||
machine_type = "m2i.8"
|
||||
disk_size = 100
|
||||
disk_performance_class = "storage_premium_perf2"
|
||||
user_data = templatefile("${path.module}/../cloud-init/user-init-windows.yml", {})
|
||||
}
|
||||
|
||||
module "windows_server_b" {
|
||||
source = "../modules/server"
|
||||
|
||||
project_id = stackit_resourcemanager_project.spoke.project_id
|
||||
network_id = stackit_network.spoke_network.network_id
|
||||
availability_zone = var.default_zone
|
||||
name = "windows-server-b"
|
||||
image_id = local.windows_image_id
|
||||
machine_type = "n2.14d.g1"
|
||||
disk_size = 100
|
||||
disk_performance_class = "storage_premium_perf1"
|
||||
user_data = templatefile("${path.module}/../cloud-init/user-init-windows.yml", {})
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
output "spoke_project_id" {
|
||||
description = "STACKIT project ID of this spoke."
|
||||
value = stackit_resourcemanager_project.spoke.project_id
|
||||
}
|
||||
|
||||
output "windows_server_a_ip" {
|
||||
description = "Primary IP address of windows-server-a."
|
||||
value = module.windows_server_a.primary_ip
|
||||
}
|
||||
|
||||
output "windows_server_b_ip" {
|
||||
description = "Primary IP address of windows-server-b."
|
||||
value = module.windows_server_b.primary_ip
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# Copy this file to backend.conf and fill in your STACKIT Object Storage credentials.
|
||||
# backend.conf is gitignored and must never be committed to version control.
|
||||
#
|
||||
# Usage: terraform init -backend-config=backend.conf
|
||||
#
|
||||
# Create an Object Storage bucket in the STACKIT portal and generate
|
||||
# S3-compatible access credentials under "Object Storage > Credentials".
|
||||
|
||||
bucket = "<your-state-bucket-name>"
|
||||
access_key = "<your-access-key-id>"
|
||||
secret_key = "<your-secret-access-key>"
|
||||
255
examples/pfsense-hub-and-spoke/README.md
Normal file
255
examples/pfsense-hub-and-spoke/README.md
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
# Hub-and-Spoke VPN on STACKIT — pfSense 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.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Internet
|
||||
|
|
||||
+-------------------------------------------------------------------+
|
||||
| STACKIT Network Area |
|
||||
| |
|
||||
| +-------------------------------------------------------------+ |
|
||||
| | 001-hub-project | |
|
||||
| | | |
|
||||
| | pfSense Firewall | |
|
||||
| | +------------------+------------------+ | |
|
||||
| | | Interface | IP | | |
|
||||
| | +------------------+------------------+ | |
|
||||
| | | WAN | 10.28.0.4 | | |
|
||||
| | | LAN | 10.28.0.20 <-- | | |
|
||||
| | | MGMT | 10.28.0.36 | | |
|
||||
| | +------------------+------------------+ | |
|
||||
| | | |
|
||||
| | default route next-hop: 10.28.0.20 | |
|
||||
| +---------------------------+---------------------------------+ |
|
||||
| | |
|
||||
| +----------------+----------------+ |
|
||||
| | | |
|
||||
| +----------+----------+ +------------+---------+ |
|
||||
| | 002-spoke-project | | 003-spoke-project | |
|
||||
| | 10.28.1.0/28 | | 10.28.2.0/28 | |
|
||||
| +---------------------+ +----------------------+ |
|
||||
+-------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
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
|
||||
│
|
||||
├── 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)
|
||||
│ ├── 040-servers.tf # Two Linux server examples (different machine types)
|
||||
│ ├── 050-outputs.tf
|
||||
│ └── backend.conf.example
|
||||
│
|
||||
├── 003-spoke-project/ # Spoke B: example Windows Server instances
|
||||
│ ├── 000-backend.tf
|
||||
│ ├── 000-variables.tf
|
||||
│ ├── 010-provider.tf
|
||||
│ ├── 020-projects.tf
|
||||
│ ├── 030-network.tf
|
||||
│ ├── 040-servers.tf # Two Windows server examples (standard + GPU-enabled)
|
||||
│ ├── 050-outputs.tf
|
||||
│ └── backend.conf.example
|
||||
│
|
||||
├── modules/
|
||||
│ └── server/ # Generic compute module (volume + NIC + server)
|
||||
│ ├── main.tf
|
||||
│ ├── variables.tf # Pass image_id to choose OS; see variable description
|
||||
│ └── outputs.tf
|
||||
│
|
||||
├── cloud-init/
|
||||
│ ├── user-init-linux.yml # Cloud-init for Linux instances
|
||||
│ └── user-init-windows.yml # Cloud-init for Windows instances
|
||||
│
|
||||
├── terraform.tfvars.example # Variable template for all projects
|
||||
└── Architecture/
|
||||
└── hub-and-spoke.drawio # Architecture diagram (draw.io)
|
||||
```
|
||||
|
||||
**File numbering convention:** Files within each project are numbered to make the dependency and deployment order explicit:
|
||||
|
||||
- `000` — backend and variables (no dependencies)
|
||||
- `010` — provider configuration
|
||||
- `020` — STACKIT project and network area
|
||||
- `030` — networking (routing tables, subnets, interfaces)
|
||||
- `040` — compute (VMs)
|
||||
- `050` — outputs
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Terraform](https://developer.hashicorp.com/terraform/install) >= 1.5
|
||||
- A STACKIT account with an organization, a folder, and sufficient IAM permissions
|
||||
- A STACKIT service account with a JSON key file
|
||||
- A STACKIT Object Storage bucket for Terraform remote state
|
||||
- S3-compatible access credentials for that bucket
|
||||
|
||||
---
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Service account key
|
||||
|
||||
Download your STACKIT service account key from the portal and place it in the `keys/` directory of each project you want to deploy:
|
||||
|
||||
```
|
||||
001-hub-project/keys/service-account.json
|
||||
002-spoke-project/keys/service-account.json
|
||||
003-spoke-project/keys/service-account.json
|
||||
```
|
||||
|
||||
The `keys/` directory is gitignored. Never commit key files.
|
||||
|
||||
### 2. Backend credentials
|
||||
|
||||
Copy `backend.conf.example` to `backend.conf` in each project directory:
|
||||
|
||||
```sh
|
||||
cp 001-hub-project/backend.conf.example 001-hub-project/backend.conf
|
||||
# edit the file and fill in your bucket name and credentials
|
||||
```
|
||||
|
||||
`backend.conf` is gitignored. Initialize Terraform with:
|
||||
|
||||
```sh
|
||||
terraform init -backend-config=backend.conf
|
||||
```
|
||||
|
||||
### 3. Variable values
|
||||
|
||||
Copy `terraform.tfvars.example` into each project directory and fill in your values:
|
||||
|
||||
```sh
|
||||
cp terraform.tfvars.example 001-hub-project/terraform.tfvars
|
||||
cp terraform.tfvars.example 002-spoke-project/terraform.tfvars
|
||||
cp terraform.tfvars.example 003-spoke-project/terraform.tfvars
|
||||
# edit each file
|
||||
```
|
||||
|
||||
The minimum required values are documented in each `000-variables.tf`.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Order
|
||||
|
||||
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)
|
||||
cd 001-hub-project
|
||||
terraform init -backend-config=backend.conf
|
||||
terraform apply
|
||||
|
||||
# Step 2 — Copy outputs into spoke terraform.tfvars
|
||||
terraform output network_area_id # → set as stackit_network_area_id in spokes
|
||||
terraform output firewall_lan_ip # → set as hub_firewall_lan_ip in spokes (default: 10.28.0.20)
|
||||
|
||||
# Step 3 — Deploy spokes (independently, in any order)
|
||||
cd ../002-spoke-project
|
||||
terraform init -backend-config=backend.conf
|
||||
terraform apply
|
||||
|
||||
cd ../003-spoke-project
|
||||
terraform init -backend-config=backend.conf
|
||||
terraform apply
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hub Firewall (pfSense)
|
||||
|
||||
pfSense is provisioned from a qcow2 image with three network interfaces:
|
||||
|
||||
| Interface | Subnet | IP | Purpose |
|
||||
| --------- | --------------- | ------------ | -------------------------- |
|
||||
| WAN | `10.28.0.0/28` | `10.28.0.4` | Internet uplink |
|
||||
| LAN | `10.28.0.16/28` | `10.28.0.20` | Default gateway for spokes |
|
||||
| MGMT | `10.28.0.32/28` | `10.28.0.36` | Web UI / SSH access |
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Spoke Projects
|
||||
|
||||
### 002-spoke-project — Linux servers (RHEL 9)
|
||||
|
||||
Two servers showing different machine type profiles:
|
||||
|
||||
| Server | Machine Type | Purpose |
|
||||
| ---------- | ------------ | ------------------------ |
|
||||
| `server-a` | `c2i.2` | General-purpose compute |
|
||||
| `server-b` | `m1a.8d` | Memory-optimized compute |
|
||||
|
||||
### 003-spoke-project — Windows Server instances
|
||||
|
||||
Two servers showing Windows with different compute profiles:
|
||||
|
||||
| Server | Machine Type | Purpose |
|
||||
| ------------------ | ------------ | ---------------------------- |
|
||||
| `windows-server-a` | `m2i.8` | Standard Windows workload |
|
||||
| `windows-server-b` | `n2.14d.g1` | GPU-enabled Windows workload |
|
||||
|
||||
---
|
||||
|
||||
## Server Module (`modules/server`)
|
||||
|
||||
A single generic module used by all spokes. Select the OS by passing the appropriate `image_id`. Supported operating systems include:
|
||||
|
||||
- RHEL 9 _(default)_
|
||||
- Windows Server 2022
|
||||
- Debian 12
|
||||
|
||||
**Image UUIDs:** Image IDs change between releases and vary by region. Retrieve the current image UUIDs from STACKIT before deploying. Set the appropriate `image_id` value in your Terraform configuration.
|
||||
|
||||
---
|
||||
|
||||
## What You Must Adapt Before Use
|
||||
|
||||
| Value | Where | Description |
|
||||
| ------------------------- | --------------------------------------- | ------------------------------------------------------- |
|
||||
| `stackit_organization_id` | all `000-variables.tf` / tfvars | Your STACKIT org UUID |
|
||||
| `stackit_folder_id` | all `000-variables.tf` / tfvars | Folder that contains the projects |
|
||||
| `stackit_network_area_id` | spoke `000-variables.tf` / tfvars | Output of `001-hub-project` |
|
||||
| `org_admin` | all `000-variables.tf` / tfvars | Project owner email |
|
||||
| `mgmt_ip_range` | `001-hub-project` tfvars | CIDR allowed to reach the firewall management interface |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [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/)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#cloud-config
|
||||
# ---------------------------------------------------------------------------
|
||||
# Example cloud-init for Linux instances (RHEL / Debian).
|
||||
# Creates a local admin user for initial access.
|
||||
#
|
||||
# IMPORTANT: Replace the password hash below with a hash of your own
|
||||
# secure password before deploying. Never use a shared or well-known
|
||||
# default password in production.
|
||||
#
|
||||
# Generate a SHA-512 hash on Linux/macOS:
|
||||
# python3 -c "import crypt; print(crypt.crypt('YourPassword', crypt.mksalt(crypt.METHOD_SHA512)))"
|
||||
# ---------------------------------------------------------------------------
|
||||
users:
|
||||
- name: admin-user
|
||||
groups: sudo
|
||||
shell: /bin/bash
|
||||
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
|
||||
lock_passwd: false
|
||||
passwd: "<replace-with-your-sha512-password-hash>"
|
||||
|
||||
chpasswd:
|
||||
expire: false
|
||||
|
||||
ssh_pwauth: true
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#cloud-config
|
||||
# ---------------------------------------------------------------------------
|
||||
# Example cloud-init for Windows Server instances.
|
||||
# Creates a local administrator account.
|
||||
#
|
||||
# IMPORTANT: Replace the username and password below with your own values
|
||||
# before deploying. Never use shared or well-known default credentials.
|
||||
# ---------------------------------------------------------------------------
|
||||
users:
|
||||
- name: admin-user
|
||||
groups: administrators
|
||||
passwd: "<replace-with-your-secure-password>"
|
||||
55
examples/pfsense-hub-and-spoke/modules/server/main.tf
Normal file
55
examples/pfsense-hub-and-spoke/modules/server/main.tf
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
stackit = {
|
||||
source = "stackitcloud/stackit"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_volume" "boot_volume" {
|
||||
project_id = var.project_id
|
||||
name = "${var.name}-volume"
|
||||
availability_zone = var.availability_zone
|
||||
size = var.disk_size
|
||||
performance_class = var.disk_performance_class
|
||||
source = {
|
||||
type = "image"
|
||||
id = var.image_id
|
||||
}
|
||||
}
|
||||
|
||||
resource "stackit_network_interface" "nic" {
|
||||
project_id = var.project_id
|
||||
network_id = var.network_id
|
||||
security = var.security_enabled
|
||||
}
|
||||
|
||||
resource "stackit_server" "server" {
|
||||
project_id = var.project_id
|
||||
name = var.name
|
||||
availability_zone = var.availability_zone
|
||||
machine_type = var.machine_type
|
||||
|
||||
boot_volume = {
|
||||
source_type = "volume"
|
||||
source_id = stackit_volume.boot_volume.volume_id
|
||||
}
|
||||
|
||||
network_interfaces = [stackit_network_interface.nic.network_interface_id]
|
||||
|
||||
user_data = var.user_data
|
||||
}
|
||||
26
examples/pfsense-hub-and-spoke/modules/server/outputs.tf
Normal file
26
examples/pfsense-hub-and-spoke/modules/server/outputs.tf
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
output "server_id" {
|
||||
value = stackit_server.server.server_id
|
||||
}
|
||||
|
||||
output "server_name" {
|
||||
value = stackit_server.server.name
|
||||
}
|
||||
|
||||
output "primary_ip" {
|
||||
description = "Primary IP address of the server's network interface."
|
||||
value = stackit_network_interface.nic.ipv4
|
||||
}
|
||||
74
examples/pfsense-hub-and-spoke/modules/server/variables.tf
Normal file
74
examples/pfsense-hub-and-spoke/modules/server/variables.tf
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "project_id" {
|
||||
description = "STACKIT project ID."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "network_id" {
|
||||
description = "Network ID (UUID) to attach the server to."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "name" {
|
||||
description = "Server hostname."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "availability_zone" {
|
||||
description = "Availability zone (e.g. eu01-1)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "machine_type" {
|
||||
description = "Machine type / flavor (e.g. c2i.2, m1a.8d, g2i.16)."
|
||||
type = string
|
||||
default = "c2i.2"
|
||||
}
|
||||
|
||||
variable "image_id" {
|
||||
description = <<-EOT
|
||||
Boot image UUID.
|
||||
Defaults to RHEL 9 (eu01) — verify the current ID in the STACKIT portal under Compute > Images.
|
||||
For Windows Server 2022 (eu01) use: c3304694-a03f-47c7-8d4c-348eecc7d212
|
||||
For Debian 12 (eu01) use: b80c8bf2-3f0b-4049-9473-1487141a8e2a
|
||||
EOT
|
||||
type = string
|
||||
default = "857bf127-1a68-4f34-bda6-4772e8d04a08"
|
||||
}
|
||||
|
||||
variable "disk_size" {
|
||||
description = "Boot volume size in GB."
|
||||
type = number
|
||||
default = 50
|
||||
}
|
||||
|
||||
variable "disk_performance_class" {
|
||||
description = "Storage performance class (e.g. storage_premium_perf1)."
|
||||
type = string
|
||||
default = "storage_premium_perf1"
|
||||
}
|
||||
|
||||
variable "user_data" {
|
||||
description = "Cloud-init user data string."
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "security_enabled" {
|
||||
description = "Enable port security on the network interface."
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
56
examples/pfsense-hub-and-spoke/terraform.tfvars.example
Normal file
56
examples/pfsense-hub-and-spoke/terraform.tfvars.example
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# ---------------------------------------------------------------------------
|
||||
# terraform.tfvars.example
|
||||
#
|
||||
# Copy this file into a project directory (e.g. 001-hub-project/terraform.tfvars)
|
||||
# and fill in your values. Do NOT commit terraform.tfvars to version control.
|
||||
#
|
||||
# Alternatively, export variables as environment variables:
|
||||
# export TF_VAR_stackit_organization_id="<uuid>"
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# --- STACKIT Identity --------------------------------------------------------
|
||||
|
||||
# Your STACKIT Organization ID.
|
||||
# Portal: Organization > Settings > Organization ID
|
||||
stackit_organization_id = "<your-stackit-organization-id>"
|
||||
|
||||
# Folder that will contain the STACKIT project(s).
|
||||
# Portal: Resource Manager > Folders
|
||||
stackit_folder_id = "<your-stackit-folder-id>"
|
||||
|
||||
# Network Area ID — created by 001-hub-project on first apply.
|
||||
# For spoke projects: run `terraform output network_area_id` in 001-hub-project.
|
||||
# Not needed for 001-hub-project itself.
|
||||
# stackit_network_area_id = "<output-of-hub-project>"
|
||||
|
||||
# Email address of the STACKIT user set as project owner.
|
||||
org_admin = "<admin@example.com>"
|
||||
|
||||
# --- Service Account ---------------------------------------------------------
|
||||
|
||||
# Path to the STACKIT service account key file.
|
||||
# Download: STACKIT portal > Service Accounts > Keys > Create key
|
||||
stackit_service_account_key_path = "./keys/service-account.json"
|
||||
|
||||
# --- Region ------------------------------------------------------------------
|
||||
|
||||
stackit_region = "eu01"
|
||||
default_zone = "eu01-1"
|
||||
|
||||
# --- Hub: network access control (001-hub-project only) ----------------------
|
||||
|
||||
# CIDR allowed to reach the pfSense 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>"
|
||||
|
||||
# --- Spoke: networking (002 and 003 spoke projects) --------------------------
|
||||
|
||||
# Subnet for this spoke. Must be within the network area range (10.28.0.0/16).
|
||||
# 002-spoke-project default: 10.28.1.0/28
|
||||
# 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.
|
||||
# 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